@cmodjs/core 0.2.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/commands.md +58 -10
- package/docs/dependencies.md +12 -12
- package/docs/hooks.md +41 -28
- package/docs/index.md +29 -25
- package/docs/install-steps.md +9 -7
- package/docs/jobs.md +59 -111
- package/docs/mod.md +200 -174
- package/docs/options.md +2 -2
- package/docs/permissions.md +27 -16
- package/docs/session.md +248 -0
- package/docs/state.md +11 -7
- package/docs/testing.md +66 -23
- package/docs/ui.md +108 -76
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +5 -2
- package/jobs/context.d.ts +7 -8
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +23 -73
- package/jobs/permissions/decide-permission.d.ts +5 -3
- package/jobs/permissions/decide-permission.d.ts.map +1 -1
- package/jobs/permissions/decide-permission.js +6 -4
- package/jobs/permissions/find-project-scope.d.ts +1 -1
- package/jobs/permissions/find-project-scope.d.ts.map +1 -1
- package/jobs/permissions/find-project-scope.js +8 -11
- package/jobs/permissions/match-target.d.ts +2 -2
- package/jobs/permissions/match-target.d.ts.map +1 -1
- package/jobs/permissions.d.ts +1 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +6 -4
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +8 -3
- package/jobs/prompt.d.ts +4 -2
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +16 -15
- package/jobs/schedule.d.ts +2 -2
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +9 -9
- package/jobs/slash-command.d.ts +4 -4
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +14 -9
- package/jobs/status-line.d.ts +1 -11
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +8 -12
- package/jobs/tool.d.ts +2 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +15 -9
- package/mod.d.ts +154 -75
- package/mod.d.ts.map +1 -1
- package/mod.js +94 -3
- package/options.d.ts +2 -2
- package/options.js +19 -19
- package/package.json +3 -3
- package/records.d.ts +10 -8
- package/records.d.ts.map +1 -1
- package/records.js +28 -17
- package/register.d.ts.map +1 -1
- package/register.js +18 -6
- package/runtime/announce.d.ts +18 -0
- package/runtime/announce.d.ts.map +1 -0
- package/runtime/announce.js +66 -0
- package/runtime/bound.d.ts +4 -0
- package/runtime/bound.d.ts.map +1 -0
- package/runtime/bound.js +9 -0
- package/runtime/claude.d.ts +3 -3
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +16 -0
- package/runtime/conversations.d.ts.map +1 -0
- package/runtime/conversations.js +291 -0
- package/runtime/deadline.d.ts +0 -1
- package/runtime/deadline.d.ts.map +1 -1
- package/runtime/deadline.js +1 -1
- package/runtime/dependencies.d.ts +2 -0
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +9 -0
- package/runtime/engine.d.ts +17 -0
- package/runtime/engine.d.ts.map +1 -0
- package/runtime/engine.js +10 -0
- package/runtime/grants.d.ts +4 -0
- package/runtime/grants.d.ts.map +1 -1
- package/runtime/grants.js +63 -33
- package/runtime/hooks.d.ts +5 -4
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +10 -5
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +11 -9
- package/runtime/lifecycle.d.ts +3 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +317 -186
- package/runtime/metadata.d.ts +3 -1
- package/runtime/metadata.d.ts.map +1 -1
- package/runtime/metadata.js +21 -11
- package/runtime/options.d.ts +3 -1
- package/runtime/options.d.ts.map +1 -1
- package/runtime/options.js +4 -4
- package/runtime/programs.d.ts +8 -0
- package/runtime/programs.d.ts.map +1 -1
- package/runtime/programs.js +39 -1
- package/runtime/router.d.ts +2 -1
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +11 -3
- package/runtime/sessions.d.ts +36 -0
- package/runtime/sessions.d.ts.map +1 -0
- package/runtime/sessions.js +235 -0
- package/runtime/state.d.ts +6 -2
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +46 -13
- package/runtime/toaster.d.ts +31 -0
- package/runtime/toaster.d.ts.map +1 -0
- package/runtime/toaster.js +176 -0
- package/runtime/ui.d.ts +6 -17
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +36 -112
- package/session/context/context.d.ts +10 -0
- package/session/context/context.d.ts.map +1 -0
- package/session/context/context.js +2 -0
- package/session/context/turn/message/document.d.ts +7 -0
- package/session/context/turn/message/document.d.ts.map +1 -0
- package/session/context/turn/message/document.js +1 -0
- package/session/context/turn/message/message.d.ts +54 -0
- package/session/context/turn/message/message.d.ts.map +1 -0
- package/session/context/turn/message/message.js +137 -0
- package/session/context/turn/message/note.d.ts +8 -0
- package/session/context/turn/message/note.d.ts.map +1 -0
- package/session/context/turn/message/note.js +1 -0
- package/session/context/turn/message/notice.d.ts +6 -0
- package/session/context/turn/message/notice.d.ts.map +1 -0
- package/session/context/turn/message/notice.js +1 -0
- package/session/context/turn/message/prompt.d.ts +8 -0
- package/session/context/turn/message/prompt.d.ts.map +1 -0
- package/session/context/turn/message/prompt.js +1 -0
- package/session/context/turn/message/reply.d.ts +6 -0
- package/session/context/turn/message/reply.d.ts.map +1 -0
- package/session/context/turn/message/reply.js +1 -0
- package/session/context/turn/message/sender.d.ts +14 -0
- package/session/context/turn/message/sender.d.ts.map +1 -0
- package/session/context/turn/message/sender.js +30 -0
- package/session/context/turn/message/skill.d.ts +7 -0
- package/session/context/turn/message/skill.d.ts.map +1 -0
- package/session/context/turn/message/skill.js +1 -0
- package/session/context/turn/message/tool-call.d.ts +9 -0
- package/session/context/turn/message/tool-call.d.ts.map +1 -0
- package/session/context/turn/message/tool-call.js +1 -0
- package/session/context/turn/message/tool-result.d.ts +8 -0
- package/session/context/turn/message/tool-result.d.ts.map +1 -0
- package/session/context/turn/message/tool-result.js +1 -0
- package/session/context/turn/turn.d.ts +12 -0
- package/session/context/turn/turn.d.ts.map +1 -0
- package/session/context/turn/turn.js +2 -0
- package/session/conversation.d.ts +64 -0
- package/session/conversation.d.ts.map +1 -0
- package/session/conversation.js +123 -0
- package/session/goal/goal.d.ts +10 -0
- package/session/goal/goal.d.ts.map +1 -0
- package/session/goal/goal.js +1 -0
- package/session/goal/task.d.ts +16 -0
- package/session/goal/task.d.ts.map +1 -0
- package/session/goal/task.js +3 -0
- package/session/session.d.ts +99 -0
- package/session/session.d.ts.map +1 -0
- package/session/session.js +1 -0
- package/src/jobs/check.ts +7 -4
- package/src/jobs/context.ts +24 -82
- package/src/jobs/permissions/decide-permission.ts +10 -8
- package/src/jobs/permissions/find-project-scope.ts +8 -14
- package/src/jobs/permissions/match-target.ts +3 -3
- package/src/jobs/permissions.ts +8 -6
- package/src/jobs/program.ts +7 -3
- package/src/jobs/prompt.ts +21 -24
- package/src/jobs/schedule.ts +14 -14
- package/src/jobs/slash-command.ts +20 -14
- package/src/jobs/status-line.ts +9 -21
- package/src/jobs/tool.ts +17 -11
- package/src/mod.ts +209 -97
- package/src/options.ts +22 -22
- package/src/records.ts +35 -23
- package/src/register.ts +18 -6
- package/src/runtime/announce.ts +74 -0
- package/src/runtime/bound.ts +12 -0
- package/src/runtime/claude.ts +5 -3
- package/src/runtime/conversations.ts +302 -0
- package/src/runtime/deadline.ts +2 -2
- package/src/runtime/dependencies.ts +12 -0
- package/src/runtime/engine.ts +22 -0
- package/src/runtime/grants.ts +66 -31
- package/src/runtime/hooks.ts +19 -10
- package/src/runtime/installer.ts +10 -9
- package/src/runtime/lifecycle.ts +313 -181
- package/src/runtime/metadata.ts +33 -11
- package/src/runtime/options.ts +19 -4
- package/src/runtime/programs.ts +40 -1
- package/src/runtime/router.ts +11 -4
- package/src/runtime/sessions.ts +242 -0
- package/src/runtime/state.ts +50 -14
- package/src/runtime/toaster.ts +218 -0
- package/src/runtime/ui.ts +42 -139
- package/src/session/context/context.ts +12 -0
- package/src/session/context/turn/message/document.ts +8 -0
- package/src/session/context/turn/message/message.ts +165 -0
- package/src/session/context/turn/message/note.ts +9 -0
- package/src/session/context/turn/message/notice.ts +7 -0
- package/src/session/context/turn/message/prompt.ts +9 -0
- package/src/session/context/turn/message/reply.ts +7 -0
- package/src/session/context/turn/message/sender.ts +38 -0
- package/src/session/context/turn/message/skill.ts +8 -0
- package/src/session/context/turn/message/tool-call.ts +10 -0
- package/src/session/context/turn/message/tool-result.ts +9 -0
- package/src/session/context/turn/turn.ts +14 -0
- package/src/session/conversation.ts +169 -0
- package/src/session/goal/goal.ts +9 -0
- package/src/session/goal/task.ts +19 -0
- package/src/session/session.ts +94 -0
- package/src/testing/fake-claude.ts +23 -18
- package/src/testing/fake-elements.ts +3 -2
- package/src/testing/fake-files.ts +1 -1
- package/src/testing/fake-sessions.ts +203 -0
- package/src/testing.ts +112 -33
- package/src/ui/components.ts +3 -3
- package/src/ui/elements.ts +22 -8
- package/src/ui/markdown.ts +22 -26
- package/src/ui/places.ts +49 -0
- package/src/utils/call-effects.ts +3 -3
- package/src/utils/changelog.ts +32 -0
- package/src/utils/matcher.ts +37 -0
- package/src/utils/metadata.ts +17 -15
- package/src/utils/parse-shell.ts +24 -16
- package/src/utils/paths.ts +15 -1
- package/src/utils/text.ts +4 -0
- package/testing/fake-claude.d.ts +4 -2
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +18 -12
- package/testing/fake-elements.js +4 -2
- package/testing/fake-files.js +1 -1
- package/testing/fake-sessions.d.ts +74 -0
- package/testing/fake-sessions.d.ts.map +1 -0
- package/testing/fake-sessions.js +170 -0
- package/testing.d.ts +33 -13
- package/testing.d.ts.map +1 -1
- package/testing.js +92 -21
- package/ui/components.d.ts +2 -3
- package/ui/components.d.ts.map +1 -1
- package/ui/components.js +3 -3
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +19 -9
- package/ui/markdown.d.ts +35 -33
- package/ui/markdown.d.ts.map +1 -1
- package/ui/markdown.js +14 -16
- package/ui/places.d.ts +43 -0
- package/ui/places.d.ts.map +1 -0
- package/ui/places.js +31 -0
- package/utils/call-effects.d.ts +1 -1
- package/utils/call-effects.d.ts.map +1 -1
- package/utils/call-effects.js +2 -2
- package/utils/changelog.d.ts +9 -0
- package/utils/changelog.d.ts.map +1 -0
- package/utils/changelog.js +29 -0
- package/utils/matcher.d.ts +13 -0
- package/utils/matcher.d.ts.map +1 -0
- package/utils/matcher.js +32 -0
- package/utils/metadata.d.ts +3 -3
- package/utils/metadata.js +18 -14
- package/utils/parse-shell.d.ts +1 -0
- package/utils/parse-shell.d.ts.map +1 -1
- package/utils/parse-shell.js +22 -13
- package/utils/paths.d.ts +2 -0
- package/utils/paths.d.ts.map +1 -1
- package/utils/paths.js +13 -1
- package/utils/text.d.ts +1 -0
- package/utils/text.d.ts.map +1 -1
- package/utils/text.js +3 -0
- package/src/ui/slots.ts +0 -36
- package/ui/slots.d.ts +0 -208
- package/ui/slots.d.ts.map +0 -1
- package/ui/slots.js +0 -19
package/docs/install-steps.md
CHANGED
|
@@ -36,7 +36,7 @@ cmod refuses a `cmod` key that breaks these rules, with the fix in the message:
|
|
|
36
36
|
|
|
37
37
|
Before a step runs for the first time, cmod shows the person the commands, the keys, and the permissions, and asks to run them. cmod hashes the commands, the keys, the permissions, and every file in each script folder, and remembers the hash the person approved with each permission. A change to any of those asks again, and an added permission is the only one it asks about ([permissions.md](permissions.md)).
|
|
38
38
|
|
|
39
|
-
- In Claude Code, the mod opens the installer pane, `Install <mod>`, focused. It lists `<mod> wants to:`, each permission as one sentence, then `and change your computer:` with `Run <install> now, and <uninstall> when you remove it` and `Bind shift+tab to /mode`, naming only what the mod has. `Accept` (a) runs the setup, and a
|
|
39
|
+
- In Claude Code, the mod opens the installer pane, `Install <mod>`, focused. It lists `<mod> wants to:`, each permission as one sentence, then `and change your computer:` with `Run <install> now, and <uninstall> when you remove it` and `Bind shift+tab to /mode`, naming only what the mod has. `Accept` (a) runs the setup, and a loading toast above the prompt shows the install with a progress bar. `Not now` (n) and Escape decline.
|
|
40
40
|
- In a session with no screen, such as `claude -p`, the mod waits and logs `<mod> waits for consent to run <install>. Run cmod install <mod> in a terminal.`
|
|
41
41
|
- In a terminal, `cmod install`, `cmod link`, `cmod try`, and `cmod update` print the commands, the program, the keys, and the permissions, and ask `Set up <mod>? [y/N]`. `--yes` approves without asking.
|
|
42
42
|
- `Not now` leaves the mod off and logs `<mod> is not installed. Run cmod install <mod> to install it.`
|
|
@@ -56,13 +56,14 @@ In Claude Code, the mod sets itself up through the `cmod` in cmod's programs fol
|
|
|
56
56
|
Some setup only the person can do, such as signing in to a service. A mod lists those steps in `installer`, and the installer pane shows each one after the permissions and the options, once the mod has started:
|
|
57
57
|
|
|
58
58
|
```ts
|
|
59
|
+
import { process } from '../node_modules/@cmodjs/core/mod.js'
|
|
59
60
|
import { defineStep } from '../node_modules/@cmodjs/core/installer.js'
|
|
60
61
|
|
|
61
62
|
const signIn = defineStep({
|
|
62
63
|
id: 'sign-in',
|
|
63
64
|
title: 'Sign in to GitHub',
|
|
64
65
|
render: () => <Text>Run gh auth login in a terminal, then press Next.</Text>,
|
|
65
|
-
isDone: async (
|
|
66
|
+
isDone: async () => (await process.run(['gh', 'auth', 'status'])).exitCode === 0,
|
|
66
67
|
})
|
|
67
68
|
|
|
68
69
|
export const ciWatch = defineMod({ name: 'ci-watch', installer: [signIn], setup(mod) { … } })
|
|
@@ -75,6 +76,7 @@ defineStep<State>(step: { id: string; title: string; render(mod, props): RenderE
|
|
|
75
76
|
- `id` is 1 to 64 letters, digits, `_`, or `-`. `title` is the line above the step, with `(1 of 2)`. `defineStep` throws for either one missing.
|
|
76
77
|
- `render` draws the step's body, the way a pane's `render` does ([ui.md](ui.md)).
|
|
77
78
|
- `isDone` says whether the step is finished. A step that is done is skipped, so the pane opens only for what is left. `Next` checks `isDone`, and shows `Finish <title> first.` while it is false. The last step's button says `Finish`.
|
|
79
|
+
- An `isDone` that throws or rejects counts as not done. At start cmod logs `<mod>: the <title> step's isDone failed: <error>`, and `Next` shows `The <title> step's isDone failed: <error>`.
|
|
78
80
|
- `Not now` closes the pane and logs `<mod> needs one more step, starting with <title>. Run /mods <mod> to finish.` The mod keeps running. `/mods <mod>` shows `Needs setup: <title>` with a `Finish setup` button, which opens the pane on the first step left.
|
|
79
81
|
|
|
80
82
|
## Key bindings
|
|
@@ -115,7 +117,7 @@ The install step runs again on every new version of the mod and whenever its scr
|
|
|
115
117
|
|
|
116
118
|
- When the first install of a mod fails or is stopped, cmod runs the uninstall step to undo it. So the uninstall step must work on a partial install.
|
|
117
119
|
- When the install step of an upgrade fails or is stopped, cmod keeps the version it set up before.
|
|
118
|
-
- The failure shows
|
|
120
|
+
- The failure shows as an error toast with `Fix the cause, then retry, or run cmod install <mod> in a terminal.` and a `Retry` button that runs the install again.
|
|
119
121
|
|
|
120
122
|
## The uninstall step
|
|
121
123
|
|
|
@@ -162,7 +164,7 @@ cmod download <program> <machine> <url> <sha256> [<machine> <url> <sha256> …]
|
|
|
162
164
|
|
|
163
165
|
An install script runs `cmod download` to fetch a program built by someone else. It picks the download for this machine, checks its SHA-256, unpacks a `.tar.gz` or a `.zip` or takes the file as it is, and moves the file named `<program>` to `$CMOD_DATA/bin/<program>`, which Claude Code then runs by its name. A download whose SHA-256 differs installs nothing. Machines are `darwin-arm64`, `darwin-x64`, `linux-arm64`, and `linux-x64`.
|
|
164
166
|
|
|
165
|
-
`$CMOD_DATA/bin/<program>` is `${mod.dataFolder}/bin/<program>` in mod code. That folder is not on `PATH`, so mod code names the program by that full path, such as ``
|
|
167
|
+
`$CMOD_DATA/bin/<program>` is `${mod.dataFolder}/bin/<program>` in mod code. That folder is not on `PATH`, so mod code names the program by that full path, such as `` process.run([`${mod.dataFolder}/bin/mermaid-ascii`, '--help']) ``, and so does the `command` of a `program` job.
|
|
166
168
|
|
|
167
169
|
```sh
|
|
168
170
|
cmod download mermaid-ascii \
|
|
@@ -192,7 +194,7 @@ A mod that builds its own command-line program keeps its source in `cli/`, and c
|
|
|
192
194
|
|
|
193
195
|
Then:
|
|
194
196
|
|
|
195
|
-
- `cmod link` builds the program and links this machine's build into cmod's programs folder and `~/.local/bin`.
|
|
197
|
+
- `cmod link` builds the program and links this machine's build into cmod's programs folder and `~/.local/bin`. When the linked checkout's version changes, the next session's setup builds it again from the checkout, so a version with no release yet still sets up.
|
|
196
198
|
- `cmod publish` builds every machine's file and attaches each to the GitHub release `v<version>`, with a `SHA256SUMS` file. It refuses a `cli/` program that the `package.json` `program` key does not name.
|
|
197
199
|
- On install, cmod downloads `<repository>/releases/download/v<version>/<program>-<machine>`, checks it against `SHA256SUMS`, runs `<program> --version`, and links it into cmod's programs folder and `~/.local/bin`.
|
|
198
200
|
- `cmod unlink` and the uninstall remove the program and both links.
|
|
@@ -202,7 +204,7 @@ Then:
|
|
|
202
204
|
cmod links every program it installs into its programs folder, `~/.local/share/cmod/programs/<program>` (under `$XDG_DATA_HOME` when it is set): the program `cmod.program` names, each program `cmod download` installs, and `cmod` itself. Claude Code runs the one there by its name, whatever else `PATH` holds, so a mod's `trace` wins over `/usr/bin/trace`:
|
|
203
205
|
|
|
204
206
|
- The cmod plugin's `SessionStart` hook puts the programs folder first on `PATH` for every Bash command Claude runs.
|
|
205
|
-
- `
|
|
207
|
+
- `process.run` and `process.spawn` run `[name, ...args]` from the programs folder when it holds `name`. A path such as `/usr/bin/trace` runs as given.
|
|
206
208
|
- Install and uninstall steps find the programs folder on `PATH` after cmod's own `bun`.
|
|
207
209
|
|
|
208
210
|
cmod also links the program into `~/.local/bin/<program>` so a terminal runs it, unless a file there is not cmod's. Setup then logs which program a terminal runs, and changes nothing outside cmod's folders.
|
|
@@ -215,7 +217,7 @@ A mod runs on macOS and Linux, on arm64 and x64, unless the `os` and `cpu` keys
|
|
|
215
217
|
{ "name": "safe-delete", "os": ["darwin"], "cpu": ["arm64"] }
|
|
216
218
|
```
|
|
217
219
|
|
|
218
|
-
- Setup refuses a machine the mod leaves out before any script runs, with `safe-delete runs on macOS on arm64, and this is Linux, so cmod set up nothing.`, in the terminal and in the mod's
|
|
220
|
+
- Setup refuses a machine the mod leaves out before any script runs, with `safe-delete runs on macOS on arm64, and this is Linux, so cmod set up nothing.`, in the terminal and in the mod's error toast in Claude Code.
|
|
219
221
|
- `cmod publish` builds the program for each machine the mod runs on.
|
|
220
222
|
- `cmod check` fails when a `cmod download` line in the install step has no download for one of those machines, and when `os` or `cpu` names a value cmod does not run mods on, such as `win32`.
|
|
221
223
|
|
package/docs/jobs.md
CHANGED
|
@@ -27,18 +27,20 @@ Each job announces itself. The first time a version starts, Claude Mod Manager (
|
|
|
27
27
|
|
|
28
28
|
## Deadlines
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
A job gives each function it calls a deadline. The whole function must answer within it, every call it makes included, or the job goes on without its answer and names what passed the deadline, as in `the open_ticket tool passed the 30 s deadline of tool`.
|
|
31
31
|
|
|
32
|
-
| Job and function | Deadline
|
|
32
|
+
| Job and function | Deadline | Past the deadline |
|
|
33
33
|
| --- | --- | --- |
|
|
34
|
-
| `slashCommand`'s `reply` | 30 s |
|
|
35
|
-
| `tool`'s `execute` | 30 s |
|
|
36
|
-
| `prompt`'s `prompt` | 5 s |
|
|
37
|
-
| `statusLine`'s `text` | 2 s |
|
|
38
|
-
| `permissions`' `when` | 2 s | 2 s
|
|
39
|
-
| `check`'s `run` |
|
|
34
|
+
| `slashCommand`'s `reply` | 30 s | The command shows `/<name> failed: /<name> passed the 30 s deadline of slashCommand`. |
|
|
35
|
+
| `tool`'s `execute` | 30 s | The call is denied with `The <name> tool failed: the <name> tool passed the 30 s deadline of tool`. |
|
|
36
|
+
| `prompt`'s `prompt` | 5 s | The prompt adds nothing, and the reason goes to Claude Code's debug log. |
|
|
37
|
+
| `statusLine`'s `text` | 2 s | The status line keeps its last text, and the reason goes to Claude Code's debug log. |
|
|
38
|
+
| `permissions`' `when` | 2 s | The rule matches, and its reason adds `Its when check failed: it passed the 2 s deadline of permissions`. |
|
|
39
|
+
| `check`'s `run` | `timeoutMs`, 60 s by default, 10 minutes at most | The call's result says the command passed the deadline. |
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
A deadline stops waiting for the function, not the work it started: a program the function spawned runs on until it exits or the mod stops. A function that waits on something slow keeps it short, such as a `process.run` with its own `timeoutMs`.
|
|
42
|
+
|
|
43
|
+
Outside a job, in a listener or a render, a `mod.dependencies` call fails after 30 s, and the other calls have only Claude Code's own limits ([mod.md](mod.md)).
|
|
42
44
|
|
|
43
45
|
## slashCommand
|
|
44
46
|
|
|
@@ -48,17 +50,17 @@ slashCommand<State>(options: {
|
|
|
48
50
|
readonly description: string
|
|
49
51
|
readonly argumentHint?: string
|
|
50
52
|
readonly immediate?: true
|
|
51
|
-
readonly reply: (input: { args: string; positionals: string[] }, mod: Mod<State>) =>
|
|
52
|
-
}): Job<
|
|
53
|
+
readonly reply: (input: { args: string; positionals: string[] }, mod: Mod<State>) => CommandReply | void | Promise<CommandReply | void>
|
|
54
|
+
}): Job<Disposable, State>
|
|
53
55
|
|
|
54
|
-
type
|
|
56
|
+
type CommandReply = string | { text?: string; context?: string } | undefined
|
|
55
57
|
```
|
|
56
58
|
|
|
57
59
|
- `name` is the command without the slash: 1 to 64 letters, digits, `_`, or `-`. The person types `/<name>`. Another name throws `<mod>: "<name>" is not a slash command name.`
|
|
58
60
|
- `description` is the line the typeahead and `/help` show.
|
|
59
61
|
- `argumentHint` is drawn dim after the name, such as `[path]`.
|
|
60
62
|
- `immediate: true` runs the command at once while Claude is still working. Without it, the command waits for the turn to end.
|
|
61
|
-
- A command lasts for the session. Adding a command with a name the session already has replaces it, and Claude Code has no call that removes one, so a command whose source is gone answers that it no longer applies.
|
|
63
|
+
- A command lasts for the session. Adding a command with a name the session already has replaces it, and Claude Code has no call that removes one, so a command whose source is gone answers that it no longer applies. `dispose()` on the handle stops the mod answering the command.
|
|
62
64
|
- A key the mod binds in its `package.json` `"cmod": { "keys": { "shift+tab": "/mode" } }` runs the command ([install-steps.md](install-steps.md#key-bindings)). The command of a key takes `immediate: true`, so the key works while Claude works, and a `reply` that returns `undefined`, so a press adds no row to the conversation. It changes `mod.state`, and a pane or band that shows the state is how the person sees the press. cmod logs `<mod>: <key> runs /<name>, which waits for Claude's turn to end and adds a row to the conversation. Give /<name> immediate: true.` for a key whose command lacks it.
|
|
63
65
|
|
|
64
66
|
```ts
|
|
@@ -101,10 +103,10 @@ tool<Schema, State>(options: {
|
|
|
101
103
|
readonly description: string
|
|
102
104
|
readonly inputSchema?: Schema
|
|
103
105
|
readonly execute: (input: FromSchema<Schema>, mod: Mod<State>) => unknown
|
|
104
|
-
}): Job<{ readonly name: string }, State>
|
|
106
|
+
}): Job<Disposable & { readonly name: string }, State>
|
|
105
107
|
```
|
|
106
108
|
|
|
107
|
-
- `name` is 1 to 64 letters, digits, `_`, or `-`. Claude sees the tool as `mcp__<plugin name>__<name>`, and the handle's `name` is that full name.
|
|
109
|
+
- `name` is 1 to 64 letters, digits, `_`, or `-`. Claude sees the tool as `mcp__<plugin name>__<name>`, and the handle's `name` is that full name. `dispose()` on the handle stops the mod answering the tool.
|
|
108
110
|
- `description` tells Claude what the tool does.
|
|
109
111
|
- `inputSchema` is a JSON Schema with `type: 'object'`. It types `execute`'s `input`, so write it inline. cmod checks each call's input against it, draft 2020-12, and denies an input that does not fit with the list of its errors. Its properties cannot be named `tool`, `tool_use_id`, `consent`, or `agentId`, which Claude Code keeps for itself.
|
|
110
112
|
- `execute` returns the result Claude reads. A `string` and an array of content blocks go to Claude as they are. Any other value goes as JSON. `undefined` returns nothing.
|
|
@@ -175,7 +177,7 @@ permissions<State>(rules: PermissionRules<State> | ((state: State) => Permission
|
|
|
175
177
|
type PermissionRules<State> = { deny?: Rule<State>[]; ask?: Rule<State>[] }
|
|
176
178
|
|
|
177
179
|
type Rule<State> = Target & {
|
|
178
|
-
when?: (call: <the call of the target's key
|
|
180
|
+
when?: (call: <the call of the target's key> & { folder: string }, mod: Mod<State>) => boolean | Promise<boolean>
|
|
179
181
|
reason?: string
|
|
180
182
|
}
|
|
181
183
|
```
|
|
@@ -189,16 +191,16 @@ type Rule<State> = Target & {
|
|
|
189
191
|
- `permissions({})` throws `permissions: give it a deny or an ask rule, or remove it from setup.`
|
|
190
192
|
- A mod that uses `permissions` calls `registerPermissionCheck(addHook)` after `registerMod` in `hooks/register.ts` ([mod.md](mod.md)). Without it the mod does not start.
|
|
191
193
|
|
|
192
|
-
`when` gets the call, typed by the target's key. `jobs/permissions.js` exports each type, and `
|
|
194
|
+
`when` gets the call, typed by the target's key. `jobs/permissions.js` exports each type, and `Call` is any of them:
|
|
193
195
|
|
|
194
|
-
| Key | Call type | Fields beyond `tool`, `input`, `agentId`, and `
|
|
196
|
+
| Key | Call type | Fields beyond `tool`, `input`, `agentId`, `agentType`, and `folder` |
|
|
195
197
|
| --- | --- | --- |
|
|
196
198
|
| `command` | `CommandCall` | `commands`, each `[program, ...arguments]`, and `isFullyParsed` |
|
|
197
199
|
| `read`, `write` | `FileCall` | `path`, and for an `Edit` or a `Write`, `content`, the new text, and `previousContent`, the text before |
|
|
198
200
|
| `fetch` | `FetchCall` | `url` |
|
|
199
|
-
| `subagent`, `tool` | `
|
|
201
|
+
| `subagent`, `tool` | `Call` | none |
|
|
200
202
|
|
|
201
|
-
`tool` is the tool's name, such as `Bash`, and `input` is its input as Claude Code sent it, such as `{ command, description }` for `Bash`, so a rule reads any field cmod does not parse. `agentId` and `agentType` name the subagent that makes the call. In the main conversation `agentId` is absent, and `agentType` is the agent the session started as, when it started as one. `jobs/permissions.js` also exports `Target`, `Rule`, and `PermissionRules`.
|
|
203
|
+
`tool` is the tool's name, such as `Bash`, and `input` is its input as Claude Code sent it, such as `{ command, description }` for `Bash`, so a rule reads any field cmod does not parse. `agentId` and `agentType` name the subagent that makes the call. In the main conversation `agentId` is absent, and `agentType` is the agent the session started as, when it started as one. `folder` is the root of the git work tree that holds the call's file or folder, or that folder itself outside one, so `process.run(['git', 'status'], { cwd: call.folder })` asks the repository the call works in. `jobs/permissions.js` also exports `Target`, `Rule`, and `PermissionRules`.
|
|
202
204
|
|
|
203
205
|
```ts
|
|
204
206
|
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
|
|
@@ -256,7 +258,7 @@ It needs `"prompt": true`. Without the grant it adds nothing and logs once which
|
|
|
256
258
|
```ts
|
|
257
259
|
prompt<State>(options: {
|
|
258
260
|
name: string
|
|
259
|
-
prompt: string | ((input: { userPrompt?: string; call?:
|
|
261
|
+
prompt: string | ((input: { userPrompt?: string; call?: Call & { folder: string } }, mod: Mod<State>) => string | undefined | Promise<string | undefined>)
|
|
260
262
|
when?: RegExp | ((userPrompt: string) => boolean)
|
|
261
263
|
after?: Target | Target[]
|
|
262
264
|
}): Job<void, State>
|
|
@@ -268,16 +270,16 @@ prompt<State>(options: {
|
|
|
268
270
|
| --- | --- |
|
|
269
271
|
| neither | At the start of each conversation, as a block of context named `name`. A block of that name already in the context wins. Then again whenever `mod.state` changes and the text differs from what Claude last got. |
|
|
270
272
|
| `when` | With each prompt the person sends whose text matches the `RegExp`, or for which the function returns true. `input.userPrompt` is the prompt. |
|
|
271
|
-
| `after` | After each call that matches a target and succeeds, with the call's result. `input.call` is the matched call. |
|
|
273
|
+
| `after` | After each call that matches a target and succeeds, with the call's result. `input.call` is the matched call, with its `folder` as `permissions` gives it. |
|
|
272
274
|
|
|
273
275
|
- With `when` or `after`, the text goes to Claude under the heading `# <name>`.
|
|
274
|
-
- With neither, the text follows the mod's state. After a change to `mod.state`, cmod calls `prompt` again, and when the text differs from what Claude last got, it adds `# <name>` and the new text to the conversation as a
|
|
276
|
+
- With neither, the text follows the mod's state. After a change to `mod.state`, cmod calls `prompt` again, and when the text differs from what Claude last got, it adds `# <name>` and the new text to the conversation as a `Note` Claude reads on its next call to the model, as `sessions.current.send` does ([session.md](session.md#send-to-a-conversation)). Several changes in a row give one note with the last text. A note waits until the conversation has started, so a state change before the first prompt changes only the first block.
|
|
275
277
|
- A `prompt` function that returns `undefined` or `''` adds nothing. One that throws adds nothing, and the error goes to Claude Code's debug log.
|
|
276
278
|
- `name` cannot be blank. `when` and `after` together throw: add a second `prompt` for the other trigger. Two prompts with one name in a mod throw.
|
|
277
279
|
- A `when` prompt runs on `prompt.submit` and adds its text to the event it passes down. In a test, `fire('prompt.submit', …)` returns that text in the answer's `context`. [testing.md](testing.md#test-a-prompt-with-when) shows how.
|
|
278
280
|
|
|
279
281
|
```ts
|
|
280
|
-
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
|
|
282
|
+
import { defineMod, fs, sessions } from '../node_modules/@cmodjs/core/mod.js'
|
|
281
283
|
import { prompt } from '../node_modules/@cmodjs/core/jobs/prompt.js'
|
|
282
284
|
|
|
283
285
|
export const conventions = defineMod({
|
|
@@ -287,7 +289,7 @@ export const conventions = defineMod({
|
|
|
287
289
|
mod.use(prompt({ name: 'Commit messages', prompt: 'Write commit messages in the imperative mood.' }))
|
|
288
290
|
mod.use(prompt({ name: 'Mode', prompt: (_input, mod) => `You are in ${mod.state.session.mode} mode.` }))
|
|
289
291
|
mod.use(prompt({ name: 'Migrations', when: /\bmigrat/i, prompt: 'Every migration has a down step.' }))
|
|
290
|
-
mod.use(prompt({ name: 'Schema', after: { write: 'db/schema.sql' }, prompt: async (
|
|
292
|
+
mod.use(prompt({ name: 'Schema', after: { write: 'db/schema.sql' }, prompt: async () => `The schema is now:\n${await fs.read(`${sessions.current.project}/db/schema.sql`)}` }))
|
|
291
293
|
},
|
|
292
294
|
})
|
|
293
295
|
```
|
|
@@ -297,9 +299,9 @@ export const conventions = defineMod({
|
|
|
297
299
|
It needs `"prompt": true`, because each cron starts a turn ([permissions.md](permissions.md)).
|
|
298
300
|
|
|
299
301
|
```ts
|
|
300
|
-
schedule<State>(entry:
|
|
302
|
+
schedule<State>(entry: ScheduledPrompt | ((state: State) => ScheduledPrompt | undefined)): Job<void, State>
|
|
301
303
|
|
|
302
|
-
type
|
|
304
|
+
type ScheduledPrompt = { readonly cron: string; readonly prompt: string }
|
|
303
305
|
```
|
|
304
306
|
|
|
305
307
|
`schedule` gives Claude a prompt on a schedule, through the cron jobs Claude Code keeps for `/loop`. `cron` is a standard 5-field cron in local time, such as `*/10 * * * *` for every 10 minutes. At each fire Claude Code shows one `Running scheduled task` row and Claude answers the prompt.
|
|
@@ -368,13 +370,13 @@ type Program = {
|
|
|
368
370
|
`program` runs a background program, such as a preview server, and hands the mod its address.
|
|
369
371
|
|
|
370
372
|
- `command` is the program on `PATH` and its arguments. `environment` sets variables for it. An empty `command` throws.
|
|
371
|
-
- The program's first line on standard output must be its address: `127.0.0.1:<port>`, or `unix:<absolute socket path>`. `ready()` then resolves `{ url: 'http://127.0.0.1:<port>' }`, or `{ url: 'http://localhost', socketPath }`. Pass both to `
|
|
373
|
+
- The program's first line on standard output must be its address: `127.0.0.1:<port>`, or `unix:<absolute socket path>`. `ready()` then resolves `{ url: 'http://127.0.0.1:<port>' }`, or `{ url: 'http://localhost', socketPath }`. Pass both to `http.fetch`, which reaches that address with no `network` permission while the program runs.
|
|
372
374
|
- In an interactive session the program starts with the mod. Otherwise it starts at the first `ready()`.
|
|
373
375
|
- When the program exits, cmod starts it again after 1, 2, 4, 8, and 16 seconds. After 5 failed tries, or a first line that is not an address, the state is `fatal`, `ready()` rejects, and cmod logs `<mod>: the <program> program stopped: <reason>`.
|
|
374
376
|
- A program that ships with the mod gets onto `PATH` through the `program` key of the mod's `package.json` ([install-steps.md](install-steps.md)). A program an install step fetched with `cmod download` is not on `PATH`, so `command` names it by its full path, `${mod.dataFolder}/bin/<program>`.
|
|
375
377
|
|
|
376
378
|
```ts
|
|
377
|
-
import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
|
|
379
|
+
import { defineMod, http } from '../node_modules/@cmodjs/core/mod.js'
|
|
378
380
|
import { program } from '../node_modules/@cmodjs/core/jobs/program.js'
|
|
379
381
|
import { slashCommand } from '../node_modules/@cmodjs/core/jobs/slash-command.js'
|
|
380
382
|
|
|
@@ -386,9 +388,9 @@ export const preview = defineMod({
|
|
|
386
388
|
slashCommand({
|
|
387
389
|
name: 'preview-status',
|
|
388
390
|
description: 'Ask the preview server for its status',
|
|
389
|
-
reply: async (
|
|
391
|
+
reply: async () => {
|
|
390
392
|
const { url, socketPath } = await server.ready()
|
|
391
|
-
const response = await
|
|
393
|
+
const response = await http.fetch(`${url}/status`, socketPath === undefined ? {} : { socketPath })
|
|
392
394
|
return response.text
|
|
393
395
|
},
|
|
394
396
|
}),
|
|
@@ -405,96 +407,42 @@ A `Job` is a function. `mod.use` calls it with a `JobContext` and returns what i
|
|
|
405
407
|
type JobContext<State> = {
|
|
406
408
|
readonly mod: Mod<State>
|
|
407
409
|
announce(feature: string): void
|
|
408
|
-
reserveName(
|
|
409
|
-
readonly toolCalls: ToolCalls
|
|
410
|
+
reserveName(type: string, name: string, taken: string): Disposable
|
|
410
411
|
}
|
|
411
412
|
```
|
|
412
413
|
|
|
413
|
-
A job
|
|
414
|
-
|
|
415
|
-
- `mod.claude` holds Claude Code's own calls. [The claude members](#the-claude-members) lists each one. Each call checks the permission it needs, as `mod`'s own members do ([permissions.md](permissions.md)).
|
|
416
|
-
- `mod.claude.store` is where cmod saves `mod.state`, under keys that start with `<mod>.`, such as `<mod>.<key>`. A job of your own never writes those keys, or it changes the mod's state behind cmod's back.
|
|
417
|
-
- `mod.claude.on` adds a Claude Code hook-module handler, `(e, next) => …`, on a `classic.<ModEvent>` event or on `tool.check`, `tool.call`, `prompt.submit`, `prompt.context`, `command.run`, `session.measure`, `skill.prompt`, `ui.render`, `ui.press`, `ui.scroll`, `ui.close`, or `cmod.call`. The handler calls `next(e)` to pass the event on. cmod checks what it answers against the person's grants, part by part ([permissions.md](permissions.md#when-a-hook-answer-has-no-grant)). A job with a handler on `tool.check` or `classic.PermissionRequest` needs `registerPermissionCheck` in `hooks/register.ts` ([mod.md](mod.md)).
|
|
418
|
-
|
|
419
|
-
The context adds what only a job needs:
|
|
420
|
-
|
|
414
|
+
- `mod` is the mod that uses the job. A job builds on `mod.on`, `mod.every`, `mod.ui`, and the imports from `mod.js`, as `setup` does ([mod.md](mod.md)).
|
|
421
415
|
- `announce` adds a phrase to the list cmod logs when the mod first starts.
|
|
422
|
-
- `reserveName` throws `taken` when the mod already reserved that `
|
|
423
|
-
- `toolCalls.agentOf(toolUseId)` resolves `{ agentId?, agentType? }`, the subagent behind a call.
|
|
424
|
-
- `toolCalls.cwdOf(toolUseId)` returns the working folder when the call ran, or `undefined` once its `PostToolUse` or `PostToolUseFailure` has run. cmod keeps the folders of the last 100 calls.
|
|
416
|
+
- `reserveName` throws `taken` when the mod already reserved that `type` and `name`, so a job refuses a duplicate. `dispose()` on what it returns frees the name again.
|
|
425
417
|
|
|
426
|
-
|
|
427
|
-
import { defineMod, type Job } from '../node_modules/@cmodjs/core/mod.js'
|
|
418
|
+
A job that hands back a feature the mod turns off returns a `Disposable`, as `slashCommand` and `tool` do:
|
|
428
419
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
420
|
+
```ts
|
|
421
|
+
import { defineMod, sessions, Stop, ToolCall, type Disposable, type Job } from '../node_modules/@cmodjs/core/mod.js'
|
|
422
|
+
|
|
423
|
+
const editCounter = (tool: string): Job<Disposable & { readonly count: () => number }> => ({ mod, announce, reserveName }) => {
|
|
424
|
+
const reserved = reserveName('editCounter', tool, `${mod.name}: edits of ${tool} are already counted.`)
|
|
425
|
+
let edits = 0
|
|
426
|
+
const listening = sessions.current.on(ToolCall, { name: tool }, () => void (edits += 1))
|
|
427
|
+
announce(`a count of ${tool} calls`)
|
|
428
|
+
return {
|
|
429
|
+
count: () => edits,
|
|
430
|
+
dispose() {
|
|
431
|
+
listening.dispose()
|
|
432
|
+
reserved.dispose()
|
|
433
|
+
},
|
|
434
|
+
}
|
|
432
435
|
}
|
|
433
436
|
|
|
434
|
-
export const
|
|
435
|
-
name: '
|
|
437
|
+
export const counter = defineMod({
|
|
438
|
+
name: 'counter',
|
|
436
439
|
setup(mod) {
|
|
437
|
-
const
|
|
438
|
-
mod.on(
|
|
439
|
-
mod.ui.toast(`Turn ended at ${new Date(await started.now()).toISOString()}`)
|
|
440
|
-
})
|
|
440
|
+
const writes = mod.use(editCounter('Write'))
|
|
441
|
+
mod.on(Stop, () => mod.ui.toast(`${writes.count()} files written so far`))
|
|
441
442
|
},
|
|
442
443
|
})
|
|
443
444
|
```
|
|
444
445
|
|
|
445
|
-
### The claude members
|
|
446
|
-
|
|
447
|
-
`mod.js` exports the types `Claude`, `RoutedEvent`, `RoutedHook`, and `ToolCalls`, so a job of your own names them in its own helpers, such as `(claude: Claude, calls: ToolCalls): RoutedHook<'command.run'> => …`.
|
|
448
|
-
|
|
449
|
-
Each member calls the member of the same name in Claude Code's hooks API, `$`, with the same arguments. The `env` members read `$.env.get` instead. `mod.claude.on` adds a handler, as [Write a job of your own](#write-a-job-of-your-own) shows.
|
|
450
|
-
|
|
451
|
-
A member that does what a permission covers checks it first and rejects with the fix: `process` needs `run`, `http.fetch` needs `network`, `fs.write` outside the project and the data folder needs `files` or, for Claude Code's own settings files, `config`, `fs.read` of a transcript needs `conversation`, `session.messages` needs `conversation`, `session.append` and `prompt.submit` need `prompt`, `model.complete` needs `model`, `agent.spawn` needs `agents`, `config.set` needs `config`, and `tool.call` needs `tools`, except `CronCreate`, which needs `prompt`, and `CronList` and `CronDelete`, which need nothing ([permissions.md](permissions.md)).
|
|
452
|
-
|
|
453
|
-
| Member | What it does |
|
|
454
|
-
| --- | --- |
|
|
455
|
-
| `plugin.name` | The plugin's name. |
|
|
456
|
-
| `plugin.root` | The plugin's own folder, absolute, where the files the mod ships live. |
|
|
457
|
-
| `ui.toast(text, options?)` | Shows `text` for a few seconds, 4000 ms unless `options.timeoutMs` says otherwise. |
|
|
458
|
-
| `ui.status(text)` | Pins `text` as the plugin's one status line under the prompt. `undefined` removes it. |
|
|
459
|
-
| `ui.log(text, options?)` | Adds a dim line to the transcript that Claude does not read, or with `{ to: 'debug' }` a line to the debug log only. |
|
|
460
|
-
| `ui.notice(toolUseId, text)` | Shows `text` as a line under the open permission dialog of that call. `undefined` removes it. |
|
|
461
|
-
| `ui.invalidate(event)` | Draws `ui.render` again, or drops the cached answers of `prompt.context` and the other cached events. |
|
|
462
|
-
| `ui.ask(question, options?)` | Asks the person in Claude Code's question dialog, and resolves the label they chose. It rejects when they dismiss it, and in a `-p` run. |
|
|
463
|
-
| `ui.open(pane)` | Opens a pane, `{ id, title?, focus?, closeOnEscape?, holdToasts?, rows?, columns? }`, and resolves `{ isPlaced }`. |
|
|
464
|
-
| `ui.close(pane)` | Closes the open pane with that `{ id }`. |
|
|
465
|
-
| `ui.panes()` | The plugin's open panes: each one's id, title, and whether it is shown, holds the keyboard, and is placed. |
|
|
466
|
-
| `ui.scroll(args)` | What `mod.ui.scroll` calls. |
|
|
467
|
-
| `ui.resolve(e)` | The element table of the surface a `ui.render` event draws on. |
|
|
468
|
-
| `process.run(argv, init?)` | Runs a program to its end. `mod.process.run` is built on it. |
|
|
469
|
-
| `process.spawn(request)` | Starts a program and streams its output. `mod.process.spawn` is built on it. |
|
|
470
|
-
| `fs.read`, `fs.write`, `fs.list`, `fs.exists`, `fs.stat` | What `mod.fs` calls. A relative path is under the session's working folder. |
|
|
471
|
-
| `http.fetch(url, init?)` | What `mod.http.fetch` calls. |
|
|
472
|
-
| `settings.read(args?)` | Reads Claude Code's own `settings.json` files. Without `{ source }` it answers every source merged. `user`, `project`, `local`, `flag`, and `policy` each answer one. |
|
|
473
|
-
| `config.list()`, `config.set({ key, value })` | Lists the rows `/config` shows, and changes one, such as `<mod>.<option>`. `set` needs `config`. |
|
|
474
|
-
| `store.get`, `store.set`, `store.delete`, `store.keys` | The plugin's key-value store, where cmod also keeps `mod.state`. |
|
|
475
|
-
| `clock.now()` | Resolves milliseconds since the epoch. |
|
|
476
|
-
| `clock.after(ms, fn)` | Calls `fn` once after `ms` milliseconds, and returns `{ cancel() }`. |
|
|
477
|
-
| `clock.every(ms, fn)` | Calls `fn` every `ms` milliseconds, at least 1, and returns `{ cancel() }`. |
|
|
478
|
-
| `session.id()` | The session's id, the transcript file's name. |
|
|
479
|
-
| `session.root()` | The session's project root. A shell `cd` does not move it. |
|
|
480
|
-
| `session.cwd()` | The folder the session runs in. |
|
|
481
|
-
| `session.model()` | The main conversation's model, as `/model` shows it. |
|
|
482
|
-
| `session.usage()` | When the session began, the context window's fill, the rate-limit windows, and the cost, as the status line has them. |
|
|
483
|
-
| `session.surfaces()` | Every surface the session draws on, `terminal` first. Empty in a plain `-p` run. |
|
|
484
|
-
| `session.messages(args?)` | What `mod.session.messages` calls. |
|
|
485
|
-
| `session.append({ message, agentId? })` | Adds a message Claude reads to the conversation, or to a subagent's, and resolves `{ message, uuid }` or `{ deny }`. `mod.session.append` is built on it. |
|
|
486
|
-
| `prompt.submit({ text, asUser? })` | Sends a prompt Claude answers as a turn once the session is idle. `mod.session.submit` is built on it. |
|
|
487
|
-
| `model.complete(request, options?)` | What `mod.model.complete` calls. |
|
|
488
|
-
| `command.register(spec)` | Adds a slash command. `slashCommand` is built on it. |
|
|
489
|
-
| `tool.register(spec)` | Adds a tool. `tool` is built on it. |
|
|
490
|
-
| `tool.call({ tool, ...input })` | Calls a tool the way Claude does, through the permission check, and resolves `{ result, text, isError? }` or `{ deny }`. `schedule` is built on it, calling `CronCreate` and `CronDelete`. |
|
|
491
|
-
| `agent.list()` | The session's subagents and teammates so far. |
|
|
492
|
-
| `agent.spawn(args)` | What `mod.agent.spawn` calls. |
|
|
493
|
-
| `env.home()` | The `HOME` variable, or `undefined` when it is unset. |
|
|
494
|
-
| `env.dataHome()` | The `XDG_DATA_HOME` variable, or `undefined`. |
|
|
495
|
-
| `env.configHome()` | The `CLAUDE_CONFIG_DIR` variable, or `undefined`. |
|
|
496
|
-
| `cmod.call({ to, method, input })` | Calls `method` of the mod named `to`. `mod.dependencies` is built on it. |
|
|
497
|
-
|
|
498
446
|
### The building blocks
|
|
499
447
|
|
|
500
448
|
The built-in jobs are written from these building blocks, which a job of your own may import:
|
|
@@ -502,9 +450,9 @@ The built-in jobs are written from these building blocks, which a job of your ow
|
|
|
502
450
|
| Import | Export | What it does |
|
|
503
451
|
| --- | --- | --- |
|
|
504
452
|
| `jobs/context.js` | `afterCall(job, contextAfter, failed)` | Runs `contextAfter` after each call that succeeds, and adds the lines it returns to the call's result. |
|
|
505
|
-
| `jobs/context.js` | `
|
|
506
|
-
| `jobs/context.js` | `
|
|
507
|
-
| `jobs/context.js` | `workspaceReader(job)` | A function that reads the session's
|
|
453
|
+
| `jobs/context.js` | `inFolder(call, folder, workspace)` | Adds the `folder` a rule's `when` gets: the work tree that holds the call's path or folder, or the folder itself. |
|
|
454
|
+
| `jobs/context.js` | `withinDeadline(mod, deadline, subject, run)` | Runs `run`, and rejects with `<subject> passed the <n> s deadline of <job>` when it has not answered by `deadline`, `{ ms, job? }`. |
|
|
455
|
+
| `jobs/context.js` | `workspaceReader(job)` | A function that reads the session's project, working folder, home, file access, and project scope. |
|
|
508
456
|
| `jobs/permissions/decide-permission.js` | `decidePermission`, `stricterVerdict`, and the types `Rule`, `PermissionRules`, `Decision`, `Verdict` | Decides a call against permission rules. |
|
|
509
457
|
| `jobs/permissions/find-project-scope.js` | `findProjectScope`, and the types `ProjectScope`, `Workspace` | Finds the repository a project plugin belongs to. |
|
|
510
458
|
|