@cmodjs/core 0.1.5 → 0.1.7

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.
Files changed (66) hide show
  1. package/docs/commands.md +4 -3
  2. package/docs/hooks.md +2 -0
  3. package/docs/index.md +4 -4
  4. package/docs/jobs.md +23 -22
  5. package/docs/mod.md +24 -9
  6. package/docs/testing.md +1 -1
  7. package/jobs/check.d.ts +2 -2
  8. package/jobs/check.d.ts.map +1 -1
  9. package/jobs/check.js +5 -5
  10. package/jobs/context.d.ts +14 -0
  11. package/jobs/context.d.ts.map +1 -0
  12. package/jobs/{part-context.js → context.js} +6 -6
  13. package/jobs/permissions.d.ts +2 -2
  14. package/jobs/permissions.d.ts.map +1 -1
  15. package/jobs/permissions.js +7 -7
  16. package/jobs/program.d.ts +2 -2
  17. package/jobs/program.d.ts.map +1 -1
  18. package/jobs/prompt.d.ts +2 -2
  19. package/jobs/prompt.d.ts.map +1 -1
  20. package/jobs/prompt.js +17 -17
  21. package/jobs/slash-command.d.ts +2 -2
  22. package/jobs/slash-command.d.ts.map +1 -1
  23. package/jobs/slash-command.js +5 -5
  24. package/jobs/status-line.d.ts +2 -2
  25. package/jobs/status-line.d.ts.map +1 -1
  26. package/jobs/status-line.js +5 -5
  27. package/jobs/tool.d.ts +2 -2
  28. package/jobs/tool.d.ts.map +1 -1
  29. package/jobs/tool.js +5 -5
  30. package/mod.d.ts +3 -3
  31. package/mod.d.ts.map +1 -1
  32. package/package.json +2 -2
  33. package/register.d.ts +5 -0
  34. package/register.d.ts.map +1 -0
  35. package/{connect.js → register.js} +42 -39
  36. package/runtime/hooks.d.ts +1 -0
  37. package/runtime/hooks.d.ts.map +1 -1
  38. package/runtime/hooks.js +1 -0
  39. package/runtime/lifecycle.d.ts +1 -1
  40. package/runtime/lifecycle.d.ts.map +1 -1
  41. package/runtime/lifecycle.js +16 -13
  42. package/runtime/router.d.ts +1 -0
  43. package/runtime/router.d.ts.map +1 -1
  44. package/runtime/router.js +1 -0
  45. package/runtime/tool-calls.d.ts +3 -2
  46. package/runtime/tool-calls.d.ts.map +1 -1
  47. package/runtime/tool-calls.js +11 -14
  48. package/src/jobs/check.ts +7 -7
  49. package/src/jobs/{part-context.ts → context.ts} +10 -10
  50. package/src/jobs/permissions.ts +9 -9
  51. package/src/jobs/program.ts +2 -2
  52. package/src/jobs/prompt.ts +19 -19
  53. package/src/jobs/slash-command.ts +7 -7
  54. package/src/jobs/status-line.ts +7 -7
  55. package/src/jobs/tool.ts +7 -7
  56. package/src/mod.ts +3 -3
  57. package/src/{connect.ts → register.ts} +44 -40
  58. package/src/runtime/hooks.ts +2 -0
  59. package/src/runtime/lifecycle.ts +18 -14
  60. package/src/runtime/router.ts +2 -0
  61. package/src/runtime/tool-calls.ts +12 -14
  62. package/vendor.js +1 -3
  63. package/connect.d.ts +0 -4
  64. package/connect.d.ts.map +0 -1
  65. package/jobs/part-context.d.ts +0 -14
  66. package/jobs/part-context.d.ts.map +0 -1
package/docs/commands.md CHANGED
@@ -82,8 +82,8 @@ cmod publish [path] [--dry-run]
82
82
 
83
83
  Releases the mod at `path` (default: the current folder) at the version in its `plugin.json`:
84
84
 
85
- 1. Builds the release from the committed files, leaving out `cli/`, `.github/`, and `.claude/`.
86
- 2. Bundles the hooks module that `hooks/hooks.json` names, with the mod's source and packages, into one readable `.js` file, and points `hooks/hooks.json` at it. Anthropic's plugin directory reads a repository without installing its packages, so it can follow a mod only when all its code is in one file.
85
+ 1. Builds the release from the committed files, leaving out `cli/`, `.github/`, and `.claude/`. A `"files"` list in `package.json` limits the release to the paths it lists, plus the folders of the install and uninstall steps, `.claude-plugin/`, `package.json`, the README, and the license, the way `npm publish` reads it. List every folder the hooks module imports from, such as `"files": ["hooks", "src", "skills"]`, because the bundle in step 2 is built from the release.
86
+ 2. Bundles the hooks module that `hooks/hooks.json` names, with the mod's source and packages, into one readable `.js` file, writes each control character in it as a `\u` escape, and points `hooks/hooks.json` at it. Anthropic's plugin directory reads a repository without installing its packages, so it can follow a mod only when all its code is in one file.
87
87
  3. Checks the release with `claude plugin validate --strict`, and stops with the validator's message when it fails.
88
88
  4. Commits the release as the `release` branch, and builds the release archive from that commit.
89
89
  5. Builds the program `cli/` declares, and writes `SHA256SUMS` for every file of the release.
@@ -91,7 +91,7 @@ Releases the mod at `path` (default: the current folder) at the version in its `
91
91
  7. Commits that file, tags `v<version>`, pushes the tag and the `release` branch, and creates the GitHub release.
92
92
  8. Prints the link to paste as the Repository when you submit the mod at [claude.ai/directory/manage](https://claude.ai/directory/manage), such as `https://github.com/owner/greeter/tree/release`. The portal reads the branch from the link, so the directory follows `release`.
93
93
 
94
- The release keeps `package.json` and `bun.lock`, so Claude Code still installs the mod's packages for its install and uninstall steps. `main` keeps the source only.
94
+ The release keeps `package.json`, so Claude Code still installs the mod's packages for its install and uninstall steps. It keeps `bun.lock` too, unless a `"files"` list leaves it out. `main` keeps the source only.
95
95
 
96
96
  It needs, and refuses to start without:
97
97
 
@@ -101,6 +101,7 @@ It needs, and refuses to start without:
101
101
  - the `claude` command, which validates the release
102
102
  - an `origin` remote on GitHub
103
103
  - a `package.json` with no dependency on a `file:` or `link:` path, which no user has
104
+ - a `"files"` list, when `package.json` has one, whose every path matches a committed file
104
105
  - a `"program"` in the `cmod` key of `package.json` that names the program `cli/` declares, when `cli/` declares one
105
106
  - the network, to hash the CMod plugin release it lists, unless the mod is CMod itself
106
107
  - for a real publish, a working tree with no uncommitted changes, a `v<version>` tag that does not exist yet, a `git push` to `origin` that succeeds, and the `gh` command, which creates the GitHub release
package/docs/hooks.md CHANGED
@@ -12,6 +12,8 @@ type ModHook<E> = (input: HookInputs[E]) => HookAnswer | void | Promise<HookAnsw
12
12
 
13
13
  Add hooks in `setup`. Each `mod.on` adds one more hook. Several hooks on one event all run.
14
14
 
15
+ A hook on `PreToolUse` or `PermissionRequest` takes part in Claude Code's permission check, so the mod's `hooks/register.ts` calls `registerPermissionCheck(addHook)` after `registerMod` ([mod.md](mod.md)). Without it the mod does not start.
16
+
15
17
  ```ts
16
18
  import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
17
19
 
package/docs/index.md CHANGED
@@ -15,7 +15,7 @@ my-mod/
15
15
  ├── .gitignore node_modules/ and .claude-plugin/types/
16
16
  ├── .oxlintrc.json leaves .claude-plugin/types/ out of the lint
17
17
  ├── hooks/hooks.json names hooks/register.ts as the hooks module
18
- ├── hooks/register.ts connect(on, myMod), and nothing else
18
+ ├── hooks/register.ts registerMod(addHook, myMod), and nothing else
19
19
  ├── src/mod.tsx defineMod: the state, the setup, the hooks, the panes
20
20
  ├── src/state.ts the starting state
21
21
  ├── src/panes/prompts.tsx one definePane per file
@@ -70,11 +70,11 @@ A mod imports from the files below, and every other file in this package is inte
70
70
 
71
71
  | Import | Exports | Docs |
72
72
  | --- | --- | --- |
73
- | `mod.js` | `defineMod`, `ModDefinition`, `Mod`, `PartContext`, `Part`, `ProgressStep`, `PaneHandle`, `longestMs`, `messageOf` | [mod.md](mod.md), [jobs.md](jobs.md), [ui.md](ui.md) |
73
+ | `mod.js` | `defineMod`, `ModDefinition`, `Mod`, `JobContext`, `Job`, `ProgressStep`, `PaneHandle`, `longestMs`, `messageOf` | [mod.md](mod.md), [jobs.md](jobs.md), [ui.md](ui.md) |
74
74
  | `mod.js` | `Claude`, `RoutedEvent`, `RoutedHook`, `ToolCalls` | [jobs.md](jobs.md) |
75
75
  | `mod.js` | `ModEvent`, `ModHook`, `HookAnswer` | [hooks.md](hooks.md) |
76
76
  | `mod.js` | `notInstalled` | [dependencies.md](dependencies.md) |
77
- | `connect.js` | `connect` | [mod.md](mod.md) |
77
+ | `register.js` | `registerMod`, `registerPermissionCheck` | [mod.md](mod.md) |
78
78
  | `testing.js` | `testMod`, `TestOptions`, `TestInput`, `TestedMod`, `Fakes`, `Shown`, `TestCall` | [testing.md](testing.md) |
79
79
  | `ui/define-pane.js` | `definePane`, `Pane` | [ui.md](ui.md) |
80
80
  | `ui/elements.js` | `Box`, `Text`, `Button`, `Link`, `Code`, `Markdown`, `Input`, `Select`, `Image`, `drawWith` | [ui.md](ui.md) |
@@ -87,7 +87,7 @@ A mod imports from the files below, and every other file in this package is inte
87
87
  | `jobs/prompt.js` | `prompt` | [jobs.md](jobs.md) |
88
88
  | `jobs/status-line.js` | `statusLine`, `Usage` | [jobs.md](jobs.md) |
89
89
  | `jobs/program.js` | `program`, `Program` | [jobs.md](jobs.md) |
90
- | `jobs/part-context.js` | `afterCall`, `modWithin`, `modOf`, `workspaceReader` | [jobs.md](jobs.md) |
90
+ | `jobs/context.js` | `afterCall`, `modWithin`, `modOf`, `workspaceReader` | [jobs.md](jobs.md) |
91
91
  | `jobs/permissions/decide-permission.js` | `decidePermission`, `stricterVerdict`, `Rule`, `PermissionRules`, `Decision`, `Verdict` | [jobs.md](jobs.md) |
92
92
  | `jobs/permissions/find-project-scope.js` | `findProjectScope`, `ProjectScope`, `Workspace` | [jobs.md](jobs.md) |
93
93
 
package/docs/jobs.md CHANGED
@@ -1,16 +1,16 @@
1
1
  # Jobs
2
2
 
3
- A job is a ready-made part of a mod: a slash command, a tool, permission rules, a check, a prompt, a status line, or a background program. Add each in `setup` with `mod.use`.
3
+ A job is a ready-made feature of a mod: a slash command, a tool, permission rules, a check, a prompt, a status line, or a background program. Add each in `setup` with `mod.use`.
4
4
 
5
5
  ```ts
6
- mod.use<Handle>(part: Part<Handle, State>): Handle
6
+ mod.use<Handle>(job: Job<Handle, State>): Handle
7
7
 
8
- type Part<Handle, State> = (context: PartContext<State>) => Handle
8
+ type Job<Handle, State> = (context: JobContext<State>) => Handle
9
9
  ```
10
10
 
11
- Each job function, such as `slashCommand(options)`, returns a `Part`. `mod.use` runs the part and returns its handle. A job that has nothing to hand back returns `void`.
11
+ Each job function, such as `slashCommand(options)`, returns a `Job`. `mod.use` runs the job and returns its handle. A job that has nothing to hand back returns `void`.
12
12
 
13
- A job's bad options throw at one of two times. `check`, `permissions`, and `prompt` check their options when the job function is called. `slashCommand`, `tool`, `statusLine`, and `program` check theirs inside the part, at `mod.use`, so the error surfaces as `its setup function threw: …`.
13
+ A job's bad options throw at one of two times. `check`, `permissions`, and `prompt` check their options when the job function is called. `slashCommand`, `tool`, `statusLine`, and `program` check theirs when the job runs, at `mod.use`, so the error surfaces as `its setup function threw: …`.
14
14
 
15
15
  | Job | Import | Adds |
16
16
  | --- | --- | --- |
@@ -48,7 +48,7 @@ slashCommand<State>(options: {
48
48
  readonly argumentHint?: string
49
49
  readonly immediate?: true
50
50
  readonly reply: (input: { args: string; positionals: string[] }, mod: Mod<State>) => Reply | void | Promise<Reply | void>
51
- }): Part<void, State>
51
+ }): Job<void, State>
52
52
 
53
53
  type Reply = string | { text?: string; context?: string } | undefined
54
54
  ```
@@ -94,7 +94,7 @@ tool<Schema, State>(options: {
94
94
  readonly description: string
95
95
  readonly inputSchema?: Schema
96
96
  readonly execute: (input: FromSchema<Schema>, mod: Mod<State>) => unknown
97
- }): Part<{ readonly name: string }, State>
97
+ }): Job<{ readonly name: string }, State>
98
98
  ```
99
99
 
100
100
  - `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.
@@ -160,7 +160,7 @@ A `command` pattern is not a glob. CMod splits it into words the way it splits a
160
160
  ## permissions
161
161
 
162
162
  ```ts
163
- permissions<State>(rules: { deny?: Rule<State>[]; ask?: Rule<State>[] }): Part<void, State>
163
+ permissions<State>(rules: { deny?: Rule<State>[]; ask?: Rule<State>[] }): Job<void, State>
164
164
 
165
165
  type Rule<State> = Target & {
166
166
  when?: (call: <the call of the target's key>, mod: Mod<State>) => boolean | Promise<boolean>
@@ -174,6 +174,7 @@ type Rule<State> = Target & {
174
174
  - When CMod cannot read the call or the session, it denies the call with the reason.
175
175
  - For a PowerShell command, or a Bash command CMod cannot fully parse, a `command` rule matches when every word of its pattern appears in the command line. A `read` or `write` rule matches when the command line holds the last segment of its pattern, after the last `/`, and only when that segment holds none of `*?[]{}`. So `{ write: 'Domain.md' }` matches such a command that names `Domain.md`, and `{ write: '**/.env*' }` never matches one.
176
176
  - `permissions({})` throws `permissions: give it a deny or an ask rule, or remove it from setup.`
177
+ - 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.
177
178
 
178
179
  `when` gets the call, typed by the target's key. `jobs/permissions.js` exports each type:
179
180
 
@@ -211,7 +212,7 @@ export const guard = defineMod({
211
212
  ## check
212
213
 
213
214
  ```ts
214
- check<State>(options: { readonly after: Target | Target[]; readonly run: readonly string[]; readonly timeoutMs?: number }): Part<void, State>
215
+ check<State>(options: { readonly after: Target | Target[]; readonly run: readonly string[]; readonly timeoutMs?: number }): Job<void, State>
215
216
  ```
216
217
 
217
218
  - After each call that matches `after` and succeeds, CMod runs `run`. A call that was denied or failed runs nothing.
@@ -241,7 +242,7 @@ prompt<State>(options: {
241
242
  prompt: string | ((input: { userPrompt?: string; call?: ToolCall }, mod: Mod<State>) => string | undefined | Promise<string | undefined>)
242
243
  when?: RegExp | ((userPrompt: string) => boolean)
243
244
  after?: Target | Target[]
244
- }): Part<void, State>
245
+ }): Job<void, State>
245
246
  ```
246
247
 
247
248
  `prompt` adds text that Claude reads. `when` and `after` pick when:
@@ -277,7 +278,7 @@ export const conventions = defineMod({
277
278
  statusLine<State>(options: {
278
279
  readonly text: (usage: Usage, mod: Mod<State>) => string | undefined | Promise<string | undefined>
279
280
  readonly interval?: number
280
- }): Part<void, State>
281
+ }): Job<void, State>
281
282
 
282
283
  type Usage = { model: string; context: { tokens?: number; window: number; percent?: number }; cost?: { usd: number } }
283
284
  ```
@@ -303,7 +304,7 @@ export const usage = defineMod({
303
304
  ## program
304
305
 
305
306
  ```ts
306
- program(options: { readonly command: readonly string[]; readonly environment?: Record<string, string> }): Part<Program>
307
+ program(options: { readonly command: readonly string[]; readonly environment?: Record<string, string> }): Job<Program>
307
308
 
308
309
  type Program = {
309
310
  readonly state: 'stopped' | 'starting' | 'running' | 'backoff' | 'fatal'
@@ -345,10 +346,10 @@ export const preview = defineMod({
345
346
 
346
347
  ## Write a job of your own
347
348
 
348
- A `Part` is a function. `mod.use` calls it with a `PartContext` and returns what it returns:
349
+ A `Job` is a function. `mod.use` calls it with a `JobContext` and returns what it returns:
349
350
 
350
351
  ```ts
351
- type PartContext<State> = {
352
+ type JobContext<State> = {
352
353
  readonly mod: Mod<State>
353
354
  readonly claude: Claude
354
355
  on<N extends RoutedEvent>(event: N, hook: RoutedHook<N>): void
@@ -360,16 +361,16 @@ type PartContext<State> = {
360
361
 
361
362
  - `claude` reaches Claude Code itself. [The claude members](#the-claude-members) lists each one.
362
363
  - `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.
363
- - `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.close`, or `cmod.call`. The handler calls `next(e)` to pass the event on.
364
+ - `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.close`, or `cmod.call`. The handler calls `next(e)` to pass the event on. A job with a handler on `tool.check`, `classic.PreToolUse`, or `classic.PermissionRequest` needs `registerPermissionCheck` in `hooks/register.ts` ([mod.md](mod.md)).
364
365
  - `announce` adds a phrase to the list CMod logs when the mod first starts.
365
366
  - `reserveName` throws `taken` when the mod already reserved that `kind` and `name`. A job calls it to refuse a duplicate.
366
367
  - `toolCalls.agentOf(toolUseId)` resolves `{ agentId?, agentType? }`, the subagent behind a call.
367
- - `toolCalls.cwdOf(toolUseId)` returns the working folder at the call's `PreToolUse`, or `undefined` once its `PostToolUse` or `PostToolUseFailure` has run. CMod keeps the folders of the last 100 calls.
368
+ - `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.
368
369
 
369
370
  ```ts
370
- import { defineMod, type Part } from '../node_modules/@cmodjs/core/mod.js'
371
+ import { defineMod, type Job } from '../node_modules/@cmodjs/core/mod.js'
371
372
 
372
- const clock: Part<{ now(): Promise<number> }> = ({ claude, announce }) => {
373
+ const clock: Job<{ now(): Promise<number> }> = ({ claude, announce }) => {
373
374
  announce('a clock')
374
375
  return { now: () => claude.clock.now() }
375
376
  }
@@ -436,10 +437,10 @@ The built-in jobs are written from these building blocks, which a job of your ow
436
437
 
437
438
  | Import | Export | What it does |
438
439
  | --- | --- | --- |
439
- | `jobs/part-context.js` | `afterCall(part, contextAfter, failed)` | Runs `contextAfter` after each call that succeeds, and adds the lines it returns to the call's result. |
440
- | `jobs/part-context.js` | `modWithin(part, deadline, workTree?)` | A `mod` whose calls fail at `deadline`, `{ ms, job?, longestMs? }`. |
441
- | `jobs/part-context.js` | `modOf(part, { call?, folder }, workspace, deadline)` | `modWithin`, with `mod.process` calls run in the call's work tree. |
442
- | `jobs/part-context.js` | `workspaceReader(part)` | A function that reads the session's `projectRoot`, `cwd`, `home`, file access, and project scope. |
440
+ | `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. |
441
+ | `jobs/context.js` | `modWithin(job, deadline, workTree?)` | A `mod` whose calls fail at `deadline`, `{ ms, job?, longestMs? }`. |
442
+ | `jobs/context.js` | `modOf(job, { call?, folder }, workspace, deadline)` | `modWithin`, with `mod.process` calls run in the call's work tree. |
443
+ | `jobs/context.js` | `workspaceReader(job)` | A function that reads the session's `projectRoot`, `cwd`, `home`, file access, and project scope. |
443
444
  | `jobs/permissions/decide-permission.js` | `decidePermission`, `stricterVerdict`, and the types `Rule`, `PermissionRules`, `Decision`, `Verdict` | Decides a call against permission rules. |
444
445
  | `jobs/permissions/find-project-scope.js` | `findProjectScope`, and the types `ProjectScope`, `Workspace` | Finds the repository a project plugin belongs to. |
445
446
 
package/docs/mod.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Define a mod
2
2
 
3
- `defineMod` describes a mod: its name, its state, the methods it offers other mods, and the `setup` function that attaches everything to Claude Code. `connect` hands the definition to Claude Code.
3
+ `defineMod` describes a mod: its name, its state, the methods it offers other mods, and the `setup` function that attaches everything to Claude Code. `registerMod` registers the definition with Claude Code.
4
4
 
5
5
  ## defineMod
6
6
 
@@ -51,28 +51,43 @@ When a step fails, the mod does not start. Its progress line above the prompt na
51
51
  - `its state did not load: <error>` when reading the saved values or a `state.json` fails.
52
52
  - `its setup function threw: <error>` when `setup` throws or rejects.
53
53
  - `its open panes did not load: <error>` when Claude Code cannot list the open panes.
54
+ - `it decides permissions on <events>, so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod` when `setup` adds a hook on `PreToolUse` or `PermissionRequest`, or a job that decides permissions, and `register.ts` does not register the permission check.
54
55
 
55
56
  Each of these says `Fix it, then run /reload-plugins.`
56
57
 
57
- ## Connect the mod to Claude Code
58
+ ## Register the mod with Claude Code
58
59
 
59
- `hooks/hooks.json` names `hooks/register.ts` as the plugin's hooks module. `register.ts` calls `connect` and does nothing else:
60
+ `hooks/hooks.json` names `hooks/register.ts` as the plugin's hooks module. `register.ts` calls `registerMod` and does nothing else:
60
61
 
61
62
  ```ts
62
63
  import type { On } from 'claude-code'
63
- import { connect } from '../node_modules/@cmodjs/core/connect.js'
64
+ import { registerMod } from '../node_modules/@cmodjs/core/register.js'
64
65
  import { greeter } from '../src/mod.js'
65
66
 
66
- export function register(on: On): void {
67
- connect(on, greeter)
67
+ export function register(addHook: On): void {
68
+ registerMod(addHook, greeter)
68
69
  }
69
70
  ```
70
71
 
71
72
  ```ts
72
- connect<State>(on: On, definition: ModDefinition<State>): void
73
+ registerMod<State>(addHook: On, definition: ModDefinition<State>): void
74
+ registerPermissionCheck(addHook: On): void
73
75
  ```
74
76
 
75
- `connect` registers one handler per Claude Code event the mod can use, and routes each event to the mod. Keep `register.ts` this small. Claude Code checks a hooks module before it loads it, and refuses some shapes, such as a `$` passed to a function in another module. Declare `register` as a function, as here: `cmod publish` bundles the hooks module into one file, and Claude Code refuses a bundled `register` that is not one.
77
+ `registerMod` registers one handler per Claude Code event the mod can use, and routes each event to the mod. It leaves out Claude Code's permission check, the events `tool.check`, `PreToolUse`, and `PermissionRequest`.
78
+
79
+ A mod that decides permissions calls `registerPermissionCheck` after `registerMod`. That covers a hook on `PreToolUse` or `PermissionRequest`, the `permissions` job, and a job of your own on `tool.check`:
80
+
81
+ ```ts
82
+ export function register(addHook: On): void {
83
+ registerMod(addHook, guard)
84
+ registerPermissionCheck(addHook)
85
+ }
86
+ ```
87
+
88
+ A mod without it has no part in Claude Code's permission check, so the plugin directory reads it as one that never answers a permission. A mod that decides permissions without it does not start, and its progress line names the line to add.
89
+
90
+ Keep `register.ts` this small. Claude Code checks a hooks module before it loads it, and refuses some shapes, such as a `$` passed to a function in another module. The plugin directory reads the bundled module too, and flags it when the name of `register`'s first parameter is declared anywhere else in the bundle. `@cmodjs/core` declares `on` as the method `mod.on`, so name that parameter `addHook`. Declare `register` as a function, as here: `cmod publish` bundles the hooks module into one file, and Claude Code refuses a bundled `register` that is not one.
76
91
 
77
92
  ## What `mod` can call
78
93
 
@@ -86,7 +101,7 @@ type Mod<State> = {
86
101
  readonly projectRoot: string
87
102
  readonly cwd: string
88
103
  on(event, hook): void
89
- use(part): Handle
104
+ use(job): Handle
90
105
  readonly ui: { pane, render, toast, progress, ask }
91
106
  readonly process: { run, spawn }
92
107
  readonly fs: { read, write, list }
package/docs/testing.md CHANGED
@@ -69,7 +69,7 @@ type TestedMod<State> = {
69
69
 
70
70
  ### start
71
71
 
72
- Starts the mod: loads its state, runs `setup`, and adds its parts. `fire`, `lines`, `type`, `callTool`, `press`, and `moveTo` start the mod first, so a test calls `start` only before it reads `state` or `shown` with nothing else to run. It rejects with the reason when the mod does not start.
72
+ Starts the mod: loads its state, runs `setup`, and adds its hooks, panes, and jobs. `fire`, `lines`, `type`, `callTool`, `press`, and `moveTo` start the mod first, so a test calls `start` only before it reads `state` or `shown` with nothing else to run. It rejects with the reason when the mod does not start.
73
73
 
74
74
  ### fire
75
75
 
package/jobs/check.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { Part } from '../mod.js';
1
+ import type { Job } from '../mod.js';
2
2
  import { type Target } from './permissions/match-target.js';
3
3
  export declare function check<State extends object = Record<never, never>>(options: {
4
4
  readonly after: Target | Target[];
5
5
  readonly run: readonly string[];
6
6
  readonly timeoutMs?: number;
7
- }): Part<void, State>;
7
+ }): Job<void, State>;
8
8
  //# sourceMappingURL=check.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/jobs/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAOrC,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAIlF,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CA4BlM"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/jobs/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAOpC,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAIlF,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CA4BjM"}
package/jobs/check.js CHANGED
@@ -2,7 +2,7 @@ import { longestMs } from '../runtime/deadline.js';
2
2
  import { callEffects } from '../utils/call-effects.js';
3
3
  import { parentOf } from '../utils/paths.js';
4
4
  import { listed, messageOf } from '../utils/text.js';
5
- import { afterCall, modOf, targetWords } from './part-context.js';
5
+ import { afterCall, modOf, targetWords } from './context.js';
6
6
  import { matchTarget, targetOf } from './permissions/match-target.js';
7
7
  const defaultMs = 60_000;
8
8
  export function check(options) {
@@ -18,21 +18,21 @@ export function check(options) {
18
18
  if (!(timeoutMs > 0 && timeoutMs <= longestMs))
19
19
  throw new Error(`check: the ${named} has timeoutMs ${timeoutMs}. Set it above 0 and at most ${longestMs} (${longestMs / 60_000} minutes).`);
20
20
  const deadline = { ms: timeoutMs, job: 'check' };
21
- return (context) => {
22
- afterCall(context, async (use, workspace) => {
21
+ return (job) => {
22
+ afterCall(job, async (use, workspace) => {
23
23
  const triggered = await triggeredRun(targets, run, use, workspace);
24
24
  if (triggered === undefined)
25
25
  return [];
26
26
  try {
27
27
  const { command, folder } = triggered;
28
- const { exitCode, stdout, stderr } = await modOf(context, { folder }, workspace, deadline).process.run(command);
28
+ const { exitCode, stdout, stderr } = await modOf(job, { folder }, workspace, deadline).process.run(command);
29
29
  return exitCode === 0 ? [] : [`${command.join(' ')} exited with ${exitCode}:\n${`${stdout}${stderr}`.trim()}`];
30
30
  }
31
31
  catch (error) {
32
32
  return [`The ${named} failed: ${messageOf(error)}`];
33
33
  }
34
34
  }, (error) => [`The check job failed, so the ${named} did not run: ${messageOf(error)}`]);
35
- context.announce(`a ${named}`);
35
+ job.announce(`a ${named}`);
36
36
  };
37
37
  }
38
38
  async function triggeredRun(after, run, use, workspace) {
@@ -0,0 +1,14 @@
1
+ import type { JobContext, Mod } from '../mod.js';
2
+ import { type Deadline } from '../runtime/deadline.js';
3
+ import type { ToolCall, ToolUse } from '../utils/call-effects.js';
4
+ import { type Workspace } from './permissions/find-project-scope.js';
5
+ import { type Target } from './permissions/match-target.js';
6
+ export declare function workspaceReader({ claude }: JobContext): () => Promise<Workspace>;
7
+ export declare function afterCall<State extends object>(job: JobContext<State>, contextAfter: (use: ToolUse, workspace: Workspace) => Promise<readonly string[]>, failed: (error: unknown) => readonly string[]): void;
8
+ export declare function modOf<State extends object>(job: JobContext<State>, { call, folder }: {
9
+ readonly call?: ToolCall;
10
+ readonly folder: string;
11
+ }, workspace: Workspace, deadline: Deadline): Mod<State>;
12
+ export declare function modWithin<State extends object>({ mod, claude }: JobContext<State>, deadline: Deadline, workTree?: () => Promise<string | undefined>): Mod<State>;
13
+ export declare function targetWords(target: Target): string;
14
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/jobs/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAEhD,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAItE,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AACjE,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAA;AACtF,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAOrE,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAOhF;AAaD,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAC5C,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,YAAY,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,EAChF,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,MAAM,EAAE,GAC5C,IAAI,CAUN;AAMD,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAEjM;AAED,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAyB,GAAG,GAAG,CAAC,KAAK,CAAC,CAwBvL;AAwBD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASlD"}
@@ -23,10 +23,10 @@ async function readSession(claude) {
23
23
  throw new Error('HOME is not set, so ~ in a path has no meaning. Start Claude Code with HOME set.');
24
24
  return { home, fs, scope: await findProjectScope(claude.plugin.root, home, fs) };
25
25
  }
26
- export function afterCall(part, contextAfter, failed) {
27
- const readWorkspace = workspaceReader(part);
28
- part.on('tool.call', async (e, next) => {
29
- const before = Promise.all([useOf(part, e), readWorkspace()]);
26
+ export function afterCall(job, contextAfter, failed) {
27
+ const readWorkspace = workspaceReader(job);
28
+ job.on('tool.call', async (e, next) => {
29
+ const before = Promise.all([useOf(job, e), readWorkspace()]);
30
30
  await before.catch(() => undefined);
31
31
  const result = await next(e);
32
32
  if (result.deny !== undefined || result.isError === true)
@@ -38,8 +38,8 @@ export function afterCall(part, contextAfter, failed) {
38
38
  async function useOf({ toolCalls }, e) {
39
39
  return { tool: e.tool, input: toolInputOf(e), ...(await toolCalls.agentOf(e.tool_use_id)) };
40
40
  }
41
- export function modOf(context, { call, folder }, workspace, deadline) {
42
- return modWithin(context, deadline, async () => workspace.scope?.workTreeOf(call !== undefined && 'path' in call ? call.path : folder));
41
+ export function modOf(job, { call, folder }, workspace, deadline) {
42
+ return modWithin(job, deadline, async () => workspace.scope?.workTreeOf(call !== undefined && 'path' in call ? call.path : folder));
43
43
  }
44
44
  export function modWithin({ mod, claude }, deadline, workTree = async () => undefined) {
45
45
  const within = (call, task) => beforeDeadline(claude, deadline, call, task);
@@ -1,8 +1,8 @@
1
- import type { Mod, Part } from '../mod.js';
1
+ import type { Job, Mod } from '../mod.js';
2
2
  import { type PermissionRules as RulesFor, type Rule as RuleFor } from './permissions/decide-permission.js';
3
3
  export type { CommandCall, FetchCall, FileCall, ToolCall } from '../utils/call-effects.js';
4
4
  export type { Target } from './permissions/match-target.js';
5
5
  export type Rule<State extends object = Record<never, never>> = RuleFor<Mod<State>>;
6
6
  export type PermissionRules<State extends object = Record<never, never>> = RulesFor<Mod<State>>;
7
- export declare function permissions<State extends object = Record<never, never>>(rules: PermissionRules<State>): Part<void, State>;
7
+ export declare function permissions<State extends object = Record<never, never>>(rules: PermissionRules<State>): Job<void, State>;
8
8
  //# sourceMappingURL=permissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/jobs/permissions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAG1C,OAAO,EAAqC,KAAK,eAAe,IAAI,QAAQ,EAAE,KAAK,IAAI,IAAI,OAAO,EAAgB,MAAM,oCAAoC,CAAA;AAK5J,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAC1F,YAAY,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAC3D,MAAM,MAAM,IAAI,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;AACnF,MAAM,MAAM,eAAe,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;AAI/F,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAwBzH"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/jobs/permissions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAGzC,OAAO,EAAqC,KAAK,eAAe,IAAI,QAAQ,EAAE,KAAK,IAAI,IAAI,OAAO,EAAgB,MAAM,oCAAoC,CAAA;AAK5J,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAC1F,YAAY,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAC3D,MAAM,MAAM,IAAI,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;AACnF,MAAM,MAAM,eAAe,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;AAI/F,wBAAgB,WAAW,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAwBxH"}
@@ -1,7 +1,7 @@
1
1
  import { decidePermission, stricterVerdict } from './permissions/decide-permission.js';
2
2
  import { targetOf } from './permissions/match-target.js';
3
3
  import { messageOf } from '../utils/text.js';
4
- import { modOf, workspaceReader } from './part-context.js';
4
+ import { modOf, workspaceReader } from './context.js';
5
5
  const deadline = { ms: 2000, job: 'permissions' };
6
6
  export function permissions(rules) {
7
7
  const count = (rules.deny?.length ?? 0) + (rules.ask?.length ?? 0);
@@ -9,22 +9,22 @@ export function permissions(rules) {
9
9
  throw new Error('permissions: give it a deny or an ask rule, or remove it from setup.');
10
10
  for (const rule of [...(rules.deny ?? []), ...(rules.ask ?? [])])
11
11
  targetOf(rule);
12
- return (context) => {
13
- const readWorkspace = workspaceReader(context);
12
+ return (job) => {
13
+ const readWorkspace = workspaceReader(job);
14
14
  const verdictOf = async (e) => {
15
15
  try {
16
- const [agent, workspace] = await Promise.all([context.toolCalls.agentOf(e.tool_use_id), readWorkspace()]);
16
+ const [agent, workspace] = await Promise.all([job.toolCalls.agentOf(e.tool_use_id), readWorkspace()]);
17
17
  const use = { tool: e.tool, input: e.input, ...agent };
18
- return await decidePermission(rules, use, workspace, (call, folder) => modOf(context, { call, folder }, workspace, deadline));
18
+ return await decidePermission(rules, use, workspace, (call, folder) => modOf(job, { call, folder }, workspace, deadline));
19
19
  }
20
20
  catch (error) {
21
21
  return { decision: 'deny', reason: `The permissions job could not read the session, so it denies the call: ${messageOf(error)}` };
22
22
  }
23
23
  };
24
- context.on('tool.check', async (e, next) => {
24
+ job.on('tool.check', async (e, next) => {
25
25
  const [below, ours] = await Promise.all([next(e), verdictOf(e)]);
26
26
  return ours === undefined ? below : stricterVerdict(below, ours);
27
27
  });
28
- context.announce(`${count} permission rule${count === 1 ? '' : 's'}`);
28
+ job.announce(`${count} permission rule${count === 1 ? '' : 's'}`);
29
29
  };
30
30
  }
package/jobs/program.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Part } from '../mod.js';
1
+ import type { Job } from '../mod.js';
2
2
  export type Program = {
3
3
  readonly state: 'stopped' | 'starting' | 'running' | 'backoff' | 'fatal';
4
4
  ready(): Promise<{
@@ -9,5 +9,5 @@ export type Program = {
9
9
  export declare function program(options: {
10
10
  readonly command: readonly string[];
11
11
  readonly environment?: Record<string, string>;
12
- }): Part<Program>;
12
+ }): Job<Program>;
13
13
  //# sourceMappingURL=program.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/jobs/program.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAGrC,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;IACxE,KAAK,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACvD,CAAA;AAMD,wBAAgB,OAAO,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CAAC,OAAO,CAAC,CAyEtI"}
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/jobs/program.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAGpC,MAAM,MAAM,OAAO,GAAG;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;IACxE,KAAK,IAAI,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CACvD,CAAA;AAMD,wBAAgB,OAAO,CAAC,OAAO,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,GAAG,CAAC,OAAO,CAAC,CAyErI"}
package/jobs/prompt.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Mod, Part } from '../mod.js';
1
+ import type { Job, Mod } from '../mod.js';
2
2
  import { type ToolCall } from '../utils/call-effects.js';
3
3
  import { type Target } from './permissions/match-target.js';
4
4
  type PromptInput = {
@@ -11,6 +11,6 @@ type PromptOptions<State extends object> = {
11
11
  when?: RegExp | ((userPrompt: string) => boolean);
12
12
  after?: Target | Target[];
13
13
  };
14
- export declare function prompt<State extends object = Record<never, never>>({ name, prompt: text, when, after }: PromptOptions<State>): Part<void, State>;
14
+ export declare function prompt<State extends object = Record<never, never>>({ name, prompt: text, when, after }: PromptOptions<State>): Job<void, State>;
15
15
  export {};
16
16
  //# sourceMappingURL=prompt.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/jobs/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAe,MAAM,WAAW,CAAA;AAGvD,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAGlF,KAAK,WAAW,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE3D,KAAK,aAAa,CAAC,KAAK,SAAS,MAAM,IAAI;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IAC5G,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC1B,CAAA;AAID,wBAAgB,MAAM,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAoChJ"}
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/jobs/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAc,GAAG,EAAE,MAAM,WAAW,CAAA;AAGrD,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACrE,OAAO,EAAyB,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAGlF,KAAK,WAAW,GAAG;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,QAAQ,CAAA;CAAE,CAAA;AAE3D,KAAK,aAAa,CAAC,KAAK,SAAS,MAAM,IAAI;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;IAC5G,IAAI,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,CAAA;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CAC1B,CAAA;AAID,wBAAgB,MAAM,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAoC/I"}
package/jobs/prompt.js CHANGED
@@ -2,7 +2,7 @@ import { longestMs } from '../runtime/deadline.js';
2
2
  import { listed, messageOf } from '../utils/text.js';
3
3
  import { callEffects } from '../utils/call-effects.js';
4
4
  import { matchTarget, targetOf } from './permissions/match-target.js';
5
- import { afterCall, modOf, modWithin, targetWords } from './part-context.js';
5
+ import { afterCall, modOf, modWithin, targetWords } from './context.js';
6
6
  const deadline = { ms: 5000, longestMs, job: 'prompt' };
7
7
  export function prompt({ name, prompt: text, when, after }) {
8
8
  if (name.trim() === '')
@@ -12,9 +12,9 @@ export function prompt({ name, prompt: text, when, after }) {
12
12
  const targets = after === undefined ? [] : Array.isArray(after) ? after : [after];
13
13
  for (const target of targets)
14
14
  targetOf(target);
15
- return (part) => {
16
- part.reserveName('prompt', name, `prompt: ${part.mod.name} adds the name "${name}" two times. Give each prompt its own name.`);
17
- const log = (reason) => part.claude.ui.log(`prompt "${name}" added nothing: ${reason}`, { to: 'debug' });
15
+ return (job) => {
16
+ job.reserveName('prompt', name, `prompt: ${job.mod.name} adds the name "${name}" two times. Give each prompt its own name.`);
17
+ const log = (reason) => job.claude.ui.log(`prompt "${name}" added nothing: ${reason}`, { to: 'debug' });
18
18
  const textFor = async (input, mod) => {
19
19
  try {
20
20
  const value = typeof text === 'string' ? text : await text(input, mod);
@@ -30,21 +30,21 @@ export function prompt({ name, prompt: text, when, after }) {
30
30
  return value === undefined ? undefined : `# ${name}\n${value}`;
31
31
  };
32
32
  if (when !== undefined) {
33
- addAfterUserPrompts(part, when, (userPrompt) => headedText({ userPrompt }, modWithin(part, deadline)), log);
34
- part.announce(`the ${name} prompt after matching user prompts`);
33
+ addAfterUserPrompts(job, when, (userPrompt) => headedText({ userPrompt }, modWithin(job, deadline)), log);
34
+ job.announce(`the ${name} prompt after matching user prompts`);
35
35
  }
36
36
  else if (targets.length > 0) {
37
- addAfterCalls(part, targets, headedText, log);
38
- part.announce(`the ${name} prompt after ${listed(targets.map(targetWords))}`);
37
+ addAfterCalls(job, targets, headedText, log);
38
+ job.announce(`the ${name} prompt after ${listed(targets.map(targetWords))}`);
39
39
  }
40
40
  else {
41
- addOncePerConversation(part, name, () => textFor({}, modWithin(part, deadline)), log);
42
- part.announce(`the ${name} prompt`);
41
+ addOncePerConversation(job, name, () => textFor({}, modWithin(job, deadline)), log);
42
+ job.announce(`the ${name} prompt`);
43
43
  }
44
44
  };
45
45
  }
46
- function addOncePerConversation(part, name, textFor, log) {
47
- part.on('prompt.context', async (e, next) => {
46
+ function addOncePerConversation(job, name, textFor, log) {
47
+ job.on('prompt.context', async (e, next) => {
48
48
  const below = await next(e);
49
49
  if (below.blocks.some((block) => block.name === name)) {
50
50
  log(`a block named "${name}" is already in the context`);
@@ -54,8 +54,8 @@ function addOncePerConversation(part, name, textFor, log) {
54
54
  return value === undefined ? below : { ...below, blocks: [...below.blocks, { name, text: value }] };
55
55
  });
56
56
  }
57
- function addAfterUserPrompts(part, when, textFor, log) {
58
- part.on('prompt.submit', async (e, next) => {
57
+ function addAfterUserPrompts(job, when, textFor, log) {
58
+ job.on('prompt.submit', async (e, next) => {
59
59
  let value;
60
60
  try {
61
61
  if (typeof when === 'function' ? when(e.text) : e.text.search(when) >= 0)
@@ -67,14 +67,14 @@ function addAfterUserPrompts(part, when, textFor, log) {
67
67
  return next(value === undefined ? e : { ...e, context: [...(e.context ?? []), value] });
68
68
  });
69
69
  }
70
- function addAfterCalls(part, targets, textFor, log) {
71
- afterCall(part, async (use, workspace) => {
70
+ function addAfterCalls(job, targets, textFor, log) {
71
+ afterCall(job, async (use, workspace) => {
72
72
  const effects = callEffects(use, workspace);
73
73
  for (const target of targets) {
74
74
  const [matched] = await matchTarget(target, use, effects, workspace);
75
75
  if (matched === undefined)
76
76
  continue;
77
- const value = await textFor({ call: matched.call }, modOf(part, matched, workspace, deadline));
77
+ const value = await textFor({ call: matched.call }, modOf(job, matched, workspace, deadline));
78
78
  return value === undefined ? [] : [value];
79
79
  }
80
80
  return [];
@@ -1,4 +1,4 @@
1
- import type { Mod, Part } from '../mod.js';
1
+ import type { Job, Mod } from '../mod.js';
2
2
  export type Reply = string | {
3
3
  text?: string;
4
4
  context?: string;
@@ -12,5 +12,5 @@ export declare function slashCommand<State extends object = Record<never, never>
12
12
  args: string;
13
13
  positionals: string[];
14
14
  }, mod: Mod<State>) => Reply | void | Promise<Reply | void>;
15
- }): Part<void, State>;
15
+ }): Job<void, State>;
16
16
  //# sourceMappingURL=slash-command.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"slash-command.d.ts","sourceRoot":"","sources":["../src/jobs/slash-command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAM1C,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAA;AAM5E,wBAAgB,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;CAC1H,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CA4BpB"}
1
+ {"version":3,"file":"slash-command.d.ts","sourceRoot":"","sources":["../src/jobs/slash-command.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAMzC,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAA;AAM5E,wBAAgB,YAAY,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,KAAK,GAAG,IAAI,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;CAC1H,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CA4BnB"}