Clockify automate
Mode on. This is not a second init. Wizards configure forge + Cursor platforms + runaway, patch yaml, ensure Clockify resources, write Cursor glue, and install Clockify-owned runaway hooks when enabled.
Config root
Pass config_root on Clockify MCP calls. If it is already known this session and still this repo, reuse it — do not run git before every tool. Otherwise resolve once with git rev-parse --show-toplevel from the working directory (open tabs are not required). Non-git: use the Cursor window folder (same as init). In a multi-root workspace, git-toplevel the focused file/folder; if nothing is focused, ask or scan roots for .clockify/config.yml. Re-resolve when the focused root changes. Do not pass the .code-workspace parent.
Init first (if needed)
If .clockify/config.yml is missing (clockify_get_config with config_root returns found: false), perform clockify-init (workspace AskQuestion only, write v4 base yaml, ignore markers). It is idempotent. Then continue below. Do not restate or fork that skill’s config/ignore steps.
If config already exists, do not overwrite the whole file — patch only the entry.automated (and scope.project when the forge wizard sets it) fields below.
Paused resume: If any entry.automated.forge.*.enabled is true and root enabled is false (after /clockify-automate-disable), skip the wizards below unless the user asks to reconfigure. Set enabled: true, restore platforms.cursor.enabled when modes warrant it, rewrite Cursor rules, and install/remove runaway hooks per settings.runaway.enabled — same as clockify-automate-enable. Temporary pause without wizards: that skill pair.
Forge wizard
Skip this wizard only when any entry.automated.forge.*.enabled is already true, unless the user asks to reconfigure forge settings. After /clockify-unautomate, all forge.*.enabled are false — always run the full wizard. After /clockify-automate-disable, forge *.enabled flags stay — skip and treat like enable (flip root enabled on + refresh glue) unless they ask to reconfigure.
Forge — AskQuestion. Only GitHub is implemented; present it as the choice (other forges are stubs — do not offer them as working options). Set
entry.automated.forge.github.enabled: true(leavegitlab/bitbucketatenabled: false).Project — AskQuestion how
scope.projectresolves:Choose how the Clockify project is chosen: local_folder — project name = git toplevel / folder name fixed — you name a Clockify project prompt — ask each timelocal_folder→scope.project.from: local_folderfixed→scope.project.from: fixedand AskQuestion for the project name (concrete existing project names and/or a clearly labeled folder-name guess; no custom “Other” option — AskQuestion already injects one). That value isscope.project.name.prompt→scope.project.from: prompt
on_start description — AskQuestion:
How should forge timer descriptions be set? prompt — agent / caller supplies the string template — expand a template with tokensIf
template: offer preset{issue_number} - {issue_title}or ask for a custom template string (tokens:{issue_number},{issue_title},{label},{local_folder}). Writeentry.automated.settings.on_start.descriptionaccordingly (from: promptorfrom: template+template).on_start task — AskQuestion:
How should forge timer tasks be set? template — expand a template (e.g. {label}) local_folder — task name = folder name fixed — literal task name none — no task prompt — ask each timetemplate→from: template+ template string (default suggestion{label}; custom allowed). Setif_missing: createwhen using{label}or other create-worthy templates unless the user chooses otherwise.local_folder→from: local_folder,if_missing: create(or honor user preference).fixed→from: fixed+name(ask for the name),if_missing: create.none→from: none,if_missing: none.prompt→from: prompt,if_missing: prompt.
when_multiple_labels — Ask only if any chosen
settings.on_startdescription or task template contains{label}:When an issue has multiple labels, which label should resolve {label}? first — use the first label in forge/API list order prompt — ask which label each timeDefault if skipped:
first. Writeentry.automated.settings.on_start.when_multiple_labels.Client when ensuring project — Clients are set on the Clockify project, not in
config.yml. After the Clockify project name is known (local_folderfolder name, orscope.project.namefor fixed; skip client picker whenscope.project.fromisprompt), run the client flow before ensure:clockify_list_projects(name filter) in the pinned workspace — do not create yet.Project found with
clientId/clientName: skip AskQuestion. ChatClient: <name> (already set on project). Do not offer to change or reassign.Project missing, or found without a client:
- Always call
clockify_list_clientswithconfig_rootbefore AskQuestion (same pinned workspace). - Build AskQuestion from the result (no
0 -,1 -, … prefixes — AskQuestion letters A, B, C…):
None Kevin Daniel Create ClientUse each unarchived client name from step 1 between
NoneandCreate Client(label = name only — no ids). Only when the list is empty or the call fails:NoneandCreate Clientonly.- None: ensure the project without a client.
- Listed client name: new project →
clockify_ensure_projectwithclient_id. Existing project without client →clockify_set_project_clientwithproject_idandclient_id(notset_clienton ensure). - Create Client: only after they pick this option — ask in chat for the new name (not AskQuestion). Then
clockify_create_clientand associate viaclient_idon create orclockify_set_project_clienton existing. - List-clients error: still AskQuestion with
None+Create Client; chatClient: failed to retrieve clients; continue ensure. - Do not write client into
config.yml. Do not PATCH a project that already has a client.
- Always call
Cursor platforms wizard
Skip only when platforms.cursor.modes already has mode blocks (even if platforms.cursor.enabled is false after a pause), unless the user asks to reconfigure. After /clockify-unautomate, modes is {} — always ask.
Otherwise ask whether to enable Cursor Plan and Debug mode timers (defaults: yes for both). Optionally ask to rename the fixed task names (defaults: agent_planning, agent_debug).
Write under entry.automated.platforms.cursor:
enabled: truewhen either mode is onFor each enabled mode (
plan/debug), write the array trigger shape (same as forge triggers — not astart:/stop:map):plan: enabled: true triggers: - event: start action: start_timer - event: stop action: stop_timer description: from: prompt task: from: fixed name: agent_planning if_missing: createSame for
debugwithname: agent_debug(or the user-chosen names).
If the user declines both modes, leave platforms.cursor.enabled: false and modes: {}.
Runaway wizard
Skip only when any entry.automated.forge.*.enabled is already true and the user did not ask to reconfigure runaway. After /clockify-unautomate, always ask (example defaults restored). After /clockify-automate-disable, forge stays enabled — keep existing settings.runaway values (do not re-ask) unless they ask to reconfigure.
This is Clockify readiness, not IDE idle detection: when the plugin next sees a running timer past stop_after_minutes, AskQuestion what to do so automations have a clean state. Same check for in-session resume and for a preexisting timer started outside the plugin.
Enable? AskQuestion (default yes):
Warn when a running timer exceeds a runaway ceiling (forgotten / long-running)? yes — enable runaway checks (hooks + AskQuestion when past ceiling) no — leave long-running timers alone until you stop themMinutes — only if they chose yes. AskQuestion with presets (include 45 as the suggested default) and allow a custom positive integer via chat/Other. Calibrate to their workflow (e.g. “a timer this long would be unusual”). Write
stop_after_minutesas a positive int (YAML number).Answers feed the patch below:
entry.automated.settings.runaway.enabledand, when enabled,stop_after_minutes. If no: setenabled: falseand keep the existingstop_after_minutesvalue (so a later re-enable keeps their minutes).
Patch yaml
After wizards, patch .clockify/config.yml (do not wipe unrelated keys):
entry.automated.enabled: trueentry.automated.forge.github.enabled: true(from forge wizard; only github implemented)entry.automated.settings.on_startas answered (includingwhen_multiple_labels)entry.automated.triggers(forge):triggers: - event: issue_start action: start_timer - event: issue_finish action: stop_timer - event: issue_switch action: stop_then_start - event: pr_ship action: stop_timer - event: pr_closed action: stop_timerentry.automated.platforms.cursorfrom the Cursor wizardentry.automated.settings.runawayfrom the runaway wizardscope.projectfrom the forge project step when set
Keep entry.automated.settings.rounding and settings.overlap from the base yaml unless the user asks to change them (rounding uses start_mode / stop_mode only — no mode key).
Ensure
- Project / client — When
scope.project.fromislocal_folderorfixed, run the client flow (forge wizard step 6) if not already done this session, thenclockify_ensure_projectwithconfig_root(andclient_idwhen creating a new project). Skip ensure whenfromisprompt. - Forge label tasks — If any
settings.on_startdescription or task template contains{label}:gh label list --json name(or GitHub API). For each label:clockify_ensure_taskwithconfig_root,project_id, and labelname. If there are no remotes or labels, skip and say so — still keep the project if ensure ran. - local_folder / fixed on_start tasks — When
settings.on_start.task.fromislocal_folderorfixed,clockify_ensure_taskonce for that name whenif_missingiscreate. - Cursor fixed tasks — For each enabled Cursor mode with
task.from: fixed,clockify_ensure_taskfor thatname.
Write Cursor rules
Add or update .cursor/rules/clockify.mdc from the declared config (triggers + platforms), so the agent:
- Passes
config_rooton Clockify MCP calls: reuse the known git toplevel this session; re-resolve only if the folder or focused root changed (cwd first, not the open file) - Passes
entry_method: automatedonclockify_start_timer/clockify_stop_timer - Forge starts honor
entry.automated.settings.on_start(description + task templates/tokens; resolve{label}perwhen_multiple_labels:firstor AskQuestionprompt) - On starting work on an issue → start (issue fields / template tokens); honor
task.from/task.if_missing - On finishing issue work, shipping a PR, or closing/abandoning a PR in-session → stop
- On switching issues → warn with the running timer’s description/duration; stop-then-start only after the user confirms
- Plan / Debug: when starting a timer for that Cursor mode, pass
cursor_mode: planorcursor_mode: debugonclockify_start_timer(mode block overrides forgesettings.on_start) - Plan start only when there is no issue in context; if an issue is in context, use forge
issue_startinstead - Build (leaving Plan) = stop only — do not start a Build timer
- Warn before any start that would replace a different running timer (same as
issue_switch) - On session start / resume (and whenever checking a running timer) →
clockify_get_running_timer; ifrunaway.pastCeiling, AskQuestion (do not silently stop):- Keep running — valid long session
- Stop and cap —
clockify_stop_timerwithrunaway_stop: trueandentry_method: automated(end = start + stop_after_minutes; no stop rounding) - Stop at now — normal
clockify_stop_timerwithentry_method: automated(wall time + rounding)
- If a tool returns
overlap: true, ask beforeconfirm_overlap: trueunlesssettings.overlap.on_conflictisoverride
Also:
- Leftover rename: if
.cursor/rules/clockify-time.mdcstill exists, move its content intoclockify.mdc(or delete it after writing the new file). Do not leave both rule files. - In the managed
.gitignorestanza, ensure.cursor/rules/clockify.mdcis listed and drop any.cursor/rules/clockify-time.mdcline. - There is no PR-close Cursor hook;
pr_closedis the agent rule when the user closes or abandons a PR in this session. Plan/Debug detection is rule-first only (no mode hooks — #85 wontfix).
Runaway hooks
Required when entry.automated.settings.runaway.enabled is true (after the runaway wizard / patch). Not optional.
- Copy the plugin template
hooks/clockify-runaway.sh(next to this skill) to.cursor/hooks/clockify-runaway.shandchmod +xit. Overwrite on re-run so the script stays current. - Merge into
.cursor/hooks.json(create with"version": 1and emptyhooksif missing). Under each ofsessionStart,sessionEnd, andstop, ensure one entry whosecommandis.cursor/hooks/clockify-runaway.sh. Do not setfailClosed. Do not duplicate entries that already target that script path. - In the managed
.gitignorestanza, ensure.cursor/hooks/clockify-runaway.shis listed (personal glue — same intent as the rule path). Do not add.cursor/hooks.json(shared file; committing it is user/team choice). Do not ignore all of.cursor/or all of.cursor/hooks/. - If
settings.runaway.enabledis false: remove those Clockify-owned entries (command referencesclockify-runaway/.cursor/hooks/clockify-runaway.sh) and delete.cursor/hooks/clockify-runaway.shif present. Ifhooks.jsonhas no remaining hooks, delete the file. Remove.cursor/hooks/clockify-runaway.shfrom the managed.gitignorestanza. If.cursor/hooks/is then empty, delete the empty directory.
Ownership marker for /clockify-unautomate: the script path .cursor/hooks/clockify-runaway.sh. Leave unrelated hooks intact.
Cursor glue is personal (init already gitignores the rule path; automate adds the runaway script path when hooks are installed). Do not commit rules/hooks unless the team opts in; do not gitignore all of .cursor/. Do not gitignore .cursor/hooks.json.
Safe to re-run: update the rule and runaway script; do not duplicate hook entries. Re-run ensure when templates or Cursor task names changed.
Rule snippet
# Clockify time (from .clockify/config.yml entry.automated)
On Clockify MCP calls, pass config_root as the git toplevel (or folder root).
If already known this session and still this repo, reuse it; do not run git
before every tool. Otherwise `git rev-parse --show-toplevel` from the working
directory (open tabs are not required). In a multi-root workspace,
git-toplevel the focused path; re-resolve when focus moves to another root.
Do not pass the .code-workspace parent.
Forge starts: use entry.automated.settings.on_start for description and task.
Expand templates with issue_number, issue_title, label, local_folder. When a
template contains {label} and the issue has multiple labels, honor
settings.on_start.when_multiple_labels (first | prompt). Pass
entry_method: automated. If another timer is running, warn and confirm before
stopping it (same as issue_switch).
When they finish the issue, switch issues (after confirm), ship the PR, or
close or abandon the PR in this session, stop the timer with
entry_method: automated.
Cursor Plan/Debug: pass cursor_mode on clockify_start_timer. Plan start only
when no issue is in context; with an issue in context use issue_start. Build
(leaving Plan) is stop only — do not start a Build timer. Warn before start
when a different timer is running.
On session resume / when checking a running timer: if runaway.pastCeiling,
AskQuestion (keep running | stop and cap with runaway_stop | stop at now).
Honor settings.overlap.on_conflict.
Use Clockify MCP tools only; never invent project/task ids.
Do not
- Duplicate
clockify-initsteps here when config already exists - Treat
pr_mergedas valid — GitHub merge is an unwatched action and is not supported - Install a background daemon
- Skip init when config or ignore defaults are missing
- Skip the runaway wizard on a fresh automate (or after unautomate)
- Re-wizard after
/clockify-automate-disablewhen anyforge.*.enabledis already true (resume like enable unless the user asks to reconfigure) - Skip installing runaway hooks when
settings.runaway.enabledis true - Leave orphan Clockify runaway hooks/script when
settings.runaway.enabledis false - Silently stop a timer past the runaway ceiling — always AskQuestion first
- Prefix client-picker options with numbers (
NoneandCreate Clientare enough; AskQuestion adds A/B/C) - Use AskQuestion for the new client name after Create Client — chat only
- Write client into
config.yml - Offer GitLab/Bitbucket as working forge options (map stubs only; leave
forge.gitlab/forge.bitbucketatenabled: false) - Write
forge: github/forge: noneenum strings — use the forge map (forge.github.enabled, etc.) - Write rounding
mode— usestart_mode/stop_modeonly undersettings.rounding