jobcan-agent
AGENTS.md1 starsChanged 44 days agoMITView on GitHub
Instructions for agents working in this repository. This file is the source of truth; CLAUDE.md only imports it. Unofficial project, not affiliated with DONUTS. 1. Every write tool is a dry run until confirm=true. Only pass confirm=true after the user has approved the exact preview. 2. Never ask for, echo, or store the user's Jobcan password. Credentials are read from the OS keychain by the server. 3. Never commit anything company-specific: company names, channels, people, Slack ids, form schemas, or…
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,542 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository4 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- backfillSkill
- cancel-leaveSkill
- request-leaveSkill
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
13 rules. About 1 is common, 6 are distinctive, and 6 are very specific to this project.
≈1
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 14
Every write tool is a dry run until confirm=true.
- Common: line 16
Never ask for, echo, or store the user's Jobcan password.
- Very specific: line 18
Never commit anything company-specific: company names, channels, people, Slack ids, form schemas, or presets.
- Distinctive: line 21
If a leave balance is short, present the options and let the user choose.
- Very specific: line 81
MCP = Jobcan only: No Slack/Calendar/company logic.
- Very specific: line 89
Every write is a DRY RUN until confirm=true.
- Distinctive: line 116
Only login and navigation are hardcoded.
- Very specific: line 139
Use the installed Google Chrome (channel: "chrome").
- Distinctive: line 143
After a successful request: post in the preset's channel using its template, add the reaction, and create an Out of office event on the …
- Distinctive: line 146
Before requesting: warn about calendar conflicts on those days.
- Distinctive: line 163
Read-only nightly smoke test against real Jobcan to catch UI changes.
- Distinctive: line 164
Every bug becomes an eval case before it's fixed.
- Very specific: line 207
Leave types: var holidays = {...} on /employee/holiday/new.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md
Instructions for agents working in this repository. This file is the source of truth; CLAUDE.md
only imports it. Unofficial project, not affiliated with DONUTS.
What this is
packages/mcp/: MCP server for Jobcan 勤怠管理. Jobcan only: no chat, calendar, or company logic.skills/: orchestration skills that combine the MCP with the connectors the agent has.preset.example.md: generic template for the user-local preset.
Rules you must follow
- Every write tool is a dry run until
confirm=true. Only passconfirm=trueafter the user has approved the exact preview. - Never ask for, echo, or store the user's Jobcan password. Credentials are read from the OS keychain by the server.
- Never commit anything company-specific: company names, channels, people, Slack ids, form
schemas, or presets. The preset lives at
~/.config/jobcan-agent/preset.md. Sweep the diff before every commit. - If a leave balance is short, present the options and let the user choose. Never pick one.
- On a captcha or an unexpected challenge, stop and ask before opening a visible window.
- READMEs come in several languages and must stay in step.
README.md(English) is the canonical text. Any change to it goes intoREADME.ja.mdandREADME.pt.mdin the same commit, and every version starts with the same language line linking all of them. Adding a language means adding the file and the link in every existing version.
Setup (development checkout)
npm install
npm run build
npm test
npm run verify -w packages/mcp # read-only check against real Jobcan, needs the Keychain entry
npm run bundle # rebuild packages/mcp/bundle/jobcan-mcp.mjsThe repository is also the Claude Code plugin jobcan (.claude-plugin/plugin.json, .mcp.json).
The plugin runs the committed bundle, because the marketplace pins a commit sha and nothing is
built on install. After any change under packages/mcp/src, run npm run bundle and commit the
bundle in the same commit. CI fails when the bundle differs from the source.
Register the server with your agent host, pointing at packages/mcp/dist/index.js; set
JOBCAN_ALLOW_WRITES=1 in its environment to allow writes. See packages/mcp/README.md for
credentials and environment variables, and the root README.md for the quick start.
Project spec and state
Unofficial, personal open-source project. Goal: anyone using Jobcan 勤怠管理 can ask their agent to file leave and manage attendance. Company-agnostic: nothing company-specific is ever committed.
0. Repository
Author identity for commits is Caio Lins <dev@clins.me> (GitHub clins1994), configured in this
repository's own git config. Remote: https://github.com/clins1994/jobcan-agent. MIT license.
0.5 Prior art
The author's unfinished Raycast extension, clins1994/raycast-jobcan, recorded Jobcan's login flow and endpoints. Everything useful from it is in What Jobcan does below.
1. Architecture: one monorepo, two layers
<repo>/
├── README.md # English, with a language line linking README.ja.md (Japanese) at the top.
├── README.ja.md # Japanese. "Unofficial" notice at the top of both.
├── AGENTS.md # setup instructions written FOR agents
├── packages/mcp/ # the MCP server (this scaffold moves here)
├── .claude-plugin/ # plugin.json: the repo is the Claude Code plugin `jobcan`
├── .mcp.json # starts the committed bundle packages/mcp/bundle/jobcan-mcp.mjs
├── skills/ # orchestration skills: `/jobcan:<skill>` in the plugin, or `npx skills add <owner>/<repo>`
│ ├── setup/ # interactive preset builder
│ ├── request-leave/ # references/: slack.md, teams.md, gcal.md, outlook.md
│ ├── cancel-leave/
│ └── backfill/ # month-end catch-up (the spec's jobcan-fix-punch)
└── preset.example.md # generic template only- MCP = Jobcan only. No Slack/Calendar/company logic. Returns typed, structured data.
- Skills = orchestration. Combine the MCP with whatever connectors the agent has (Slack or Teams; Google Calendar or Outlook). Provider notes live in references/ files, not one skill per provider combination.
- Distribution: npm (
npx),.mcpbin GitHub releases (one-click Claude Desktop install),server.jsonfor the MCP Registry. Skills show up on skills.sh automatically via install telemetry.
2. MCP tools (v1)
Every write is a DRY RUN until confirm=true.
Leaves
jobcan_check_date(date): workday / weekend / national holiday / company holiday / already requested. Jobcan's calendar is the source of truth;@holiday-jp/holiday_jpis the fallback.jobcan_get_leave_balance(): remaining days per type, minus PENDING requests; half day = 0.5jobcan_request_leave(dates|range, type, fields…, confirm): expands ranges, skips non-workdays with reasons, and previews "X of Y available, balance before → after". If the balance is short, return options and never pick one.jobcan_list_leave_requests(month?): id, date, type, statusjobcan_cancel_leave_request(id, confirm): if already approved, return a clear "contact your admin"
Holiday work
jobcan_request_holiday_work(work_date, compensation?, holiday_date?, reason, confirm): one request for the day worked and, for a substitute, the day off in exchange.
Attendance
jobcan_get_today(),jobcan_punch(action),jobcan_get_timesheet(month)(flag gaps),jobcan_request_correction(date, in?, out?, reason)
Discovery / dev
jobcan_discover_forms(request_type, explore_options=true): read-only. Selects every option in every select and re-snapshots, to capture conditional fields. Returns a field tree + fingerprint.jobcan_inspect_page(path): raw dev helper (already in the scaffold)
3. Forms are customized per company AND per employee type
- Filling is schema-driven (fields found by label/name from discovery), not fixed selectors. Only login and navigation are hardcoded.
- Validate input against the schema before submitting; missing required field → ask the user.
- Drift: fingerprint change or a Jobcan rejection → re-discover the changed form, show a diff, retry only after the user confirms.
4. Preset (user-local, never committed)
Saved at ~/.config/jobcan-agent/preset.md. Two parts:
- Policy (human-written, shared out of band, e.g. dropped in company chat): notification channel, mentions, message template, languages, reactions, cancel wording, admin contact
- Form schema (discovered per user by
jobcan:setup): leave types, required/optional fields, defaults the user chose ("day off" → which type, default reason…), plus fingerprint
jobcan:setup skill flow: crawl → ask only what forms can't answer (plain language) →
dry-run each form → user confirms → write preset.
5. Runtime rules
- Headless always (Playwright headless shell): no window, no Dock icon, no stolen focus.
- Separate persistent profile (
~/.jobcan-mcp/profile); never touch the user's own browser. - Captcha / unexpected challenge: stop and ASK before opening a visible window.
- Credentials never pass through chat or the model.
- Local dev: macOS Keychain (
security), already implemented - MCPB:
user_configfields markedsensitive(stored in the OS keychain by Claude Desktop) - Later: optional 1Password (
op read)
- Local dev: macOS Keychain (
- Use the installed Google Chrome (
channel: "chrome"); fall back to downloading Chromium. - TO TEST: does the background login end the user's own Jobcan browser session?
6. Skills behavior (side effects are optional, per available connector)
- After a successful request: post in the preset's channel using its template, add the reaction, and create an Out of office event on the user's own calendar (OOO auto-declines meetings; half days → only that half).
- Before requesting: warn about calendar conflicts on those days.
- The Jobcan request ID (
jobcan:<id>in the event description / message) links request ↔ message ↔ event. Cancel = withdraw in Jobcan + reply with the preset's cancel wording in the thread + delete the event. - Connector missing → skip that step with a one-line note.
- Setup:
setupopens a LOCAL page for the password (never pasted into chat);verifydoes a read-only login + balance check and reports ready or a plain-language fix.
7. Quality
- Split pure logic from a
JobcanClientinterface:PlaywrightClient+ in-memoryFakeClient. - Unit tests: ranges across weekends/holidays/month ends, 振替休日, Golden Week, New Year, half days, pending requests, exactly-zero and short balances, duplicates, past dates.
- Form fixtures for the fake: minimal, reason required, custom select, no half days, Y/M/D selects.
- Agent evals against the fake: prompt + seeded state → expected tool calls + final state (e.g. "off oct 5~15" → 8 workdays, no submit before confirm; "leave on Saturday" → nothing filed; ambiguous cancel → asks which one; Japanese in → Japanese out).
- Skill evals: correct connector calls, correct message, cancel cleans up all three.
- Read-only nightly smoke test against real Jobcan to catch UI changes.
- Every bug becomes an eval case before it's fixed.
8. Phases
- Own Mac: verify login/navigation, build logic + FakeClient + tests, discovery, one real end-to-end run
- MCPB bundle +
jobcan:setupskill + no-terminal install - Pilot with one non-technical user, no help given
- Later: remote transport (Streamable HTTP + OAuth, e.g. Cloudflare Workers + Browser Rendering) for ChatGPT and phones; late arrival / early leave / stepping-away requests
Current state (2026-09-30)
Works, against one company's real Jobcan:
packages/mcp/src/domain/: pure logic. Dates areYYYY-MM-DDstrings in Japan time, never localDates.leave-plan.tsandattendance-plan.tsare the planners behind every dry run.src/client/client.ts: theJobcanClientinterface.src/client/fake.ts+fixtures.ts: the in-memory fake with synthetic form fixtures.src/client/http/: the real client.src/client/http/session.tsallows only listed pages: a fixed set of GETs, the sign-in POST, and, withallowWrites, exactlyPOST /employee/holiday/confirm,POST /employee/holiday/save,POST /employee/holidayworking/confirm,POST /employee/holidayworking/save,POST /employee/adit/insert/andGET /employee/holiday/delete/. A redirect is never followed into a write; after a write, a redirect to an unlisted page is left unfollowed and the outcome is read from the list instead.src/usecases/leave.ts: what the tools call. A write is reached only whenconfirm === trueand the plan isready.replaceLeaveplans as if the old request were gone, withdraws, then files, and reports an uncovered day if the second step fails.src/index.ts: the MCP tools.JOBCAN_ALLOW_WRITES=1turns writes on.src/cli/verify.ts(npm run verify): read-only check of every page. Run it when Jobcan changes.- Skills
jobcan:request-leave,jobcan:cancel-leaveandjobcan:backfill(month-end catch-up; the spec called itjobcan-fix-punch) exist. The first was exercised by hand end to end; the procedure behind the third was run by hand before it was written as a skill.jobcan:setupdoes not exist. No MCPB bundle,server.json, evals or nightly smoke test yet. - 427 unit tests. Pages are fetched in English (
employee_language=encookie); parsers rely on it.
What Jobcan does
Verified live on 2026-09-29 and 2026-09-30.
- Sign-in:
GET id.jobcan.jp/users/sign_in?app_key=atd,POSTthe form (a Railsauthenticity_tokenplus hidden fields), then redirects through/oauth/authorizeandssl.jobcan.jp/jbcoauth/callbackto/employee. The session is thesidcookie. A second sign-in does not end an existing one. - Leave types:
var holidays = {...}on/employee/holiday/new; the visible select is empty until the page's script fills it.holiday_typeis the balance key (paid, compensatory, substitute, special, special2, ...).paid_type: 10 or 0 = full day, -1 = hourly, anything else = tenths of a day.hourlyLeaveDayMin= minutes per day of hourly leave. - Reason rule, from the page:
#holiday-reason-requiredalone = every type; with#holiday-reason-optionaltoo = every type except annual paid leave; neither = optional. - Hour selects (
start[h][0]etc.) start at 03:00: option value = hour − 3. Hours must be encoded through the select's labels (TimePartsField.hours). Minutes are on a 10-minute grid. - Balances:
/employee/attendance, "Remaining Vacations" card, matched to leave types by thetypelabel in the embedded data. Whether they already exclude pending requests is unknown; assumed not, which can only understate what is available. - Calendar: the attendance page's "Holiday Type" column, filled in for future months.
- Requests:
/employee/holiday/?search_type=term&from[y]…; hourly leave shows as10:00~14:00; a pending request reads "Waiting for Approval". - Filing:
POST /employee/holiday/confirmreturns the save form (nothing saved yet; past dates are accepted), then re-post that form's hidden fields toPOST /employee/holiday/save, then find the new request in the list. - Withdrawing a pending request:
/employee/holiday/delete-confirm/?applied_id=Nlinks toGET /employee/holiday/delete/?applied_id=N&token=…(a GET that writes), then Jobcan lands on/employee/holiday/delete-finish. Approved requests have no cancel control. - Holiday work:
/employee/holidayworking/newpostsholidayworking_{year,month,day},holiday_type(NaN= holiday work only,compensatory,substitute),useFlg=1withholiday_{year,month,day}for a named substitute day, anddescription, toPOST /employee/holidayworking/confirm. That returns the save form (nothing saved yet); its fields go toPOST /employee/holidayworking/save. Past dates pass the review step. The list has five columns: number, date, vacation type, status, reason. Clock times for a holiday need the day to count as worked first. - Manual clock records:
POST /employee/adit/insert/as XHR, one per time, with the clock-edit page'stoken,client_id,employee_id,timeas HHMM,group_id(spot) andnotice(required when the label says "(Required)"). Answer{result: 1}or{result: 0, errors}. Until approved, the timesheet hides the times and marks the rowjbc-table-warning(TimesheetDay.pendingApproval); the planner skips such days.
Owner decisions
- Reads of real Jobcan are fine without asking. Every write gets a previewed "yes" from the owner first (Jobcan, chat posts, calendar events alike).
- Writes for leave and attendance were enabled on 2026-09-30 and used for real.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

