@cmodjs/core 0.1.14 → 0.2.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.
Files changed (95) hide show
  1. package/docs/commands.md +31 -19
  2. package/docs/index.md +12 -4
  3. package/docs/install-steps.md +30 -4
  4. package/docs/jobs.md +25 -9
  5. package/docs/mod.md +103 -37
  6. package/docs/options.md +93 -0
  7. package/docs/permissions.md +88 -0
  8. package/docs/state.md +4 -31
  9. package/docs/testing.md +13 -2
  10. package/installer.d.ts +10 -0
  11. package/installer.d.ts.map +1 -0
  12. package/installer.js +8 -0
  13. package/jobs/context.d.ts +2 -2
  14. package/jobs/context.d.ts.map +1 -1
  15. package/jobs/context.js +5 -3
  16. package/jobs/permissions.js +1 -1
  17. package/jobs/program.d.ts.map +1 -1
  18. package/jobs/program.js +5 -1
  19. package/jobs/prompt.js +5 -5
  20. package/jobs/schedule.d.ts.map +1 -1
  21. package/jobs/schedule.js +2 -1
  22. package/jobs/slash-command.d.ts.map +1 -1
  23. package/jobs/slash-command.js +3 -2
  24. package/jobs/status-line.d.ts.map +1 -1
  25. package/jobs/status-line.js +3 -2
  26. package/jobs/tool.d.ts.map +1 -1
  27. package/jobs/tool.js +3 -2
  28. package/mod.d.ts +48 -10
  29. package/mod.d.ts.map +1 -1
  30. package/mod.js +3 -0
  31. package/options.d.ts +58 -0
  32. package/options.d.ts.map +1 -0
  33. package/options.js +66 -0
  34. package/package.json +1 -1
  35. package/records.d.ts +14 -1
  36. package/records.d.ts.map +1 -1
  37. package/records.js +106 -7
  38. package/register.d.ts +4 -2
  39. package/register.d.ts.map +1 -1
  40. package/register.js +8 -2
  41. package/runtime/claude.d.ts +5 -1
  42. package/runtime/claude.d.ts.map +1 -1
  43. package/runtime/grants.d.ts +28 -0
  44. package/runtime/grants.d.ts.map +1 -0
  45. package/runtime/grants.js +286 -0
  46. package/runtime/installer.d.ts +23 -0
  47. package/runtime/installer.d.ts.map +1 -0
  48. package/runtime/installer.js +113 -0
  49. package/runtime/lifecycle.d.ts +6 -3
  50. package/runtime/lifecycle.d.ts.map +1 -1
  51. package/runtime/lifecycle.js +220 -36
  52. package/runtime/metadata.d.ts +20 -0
  53. package/runtime/metadata.d.ts.map +1 -0
  54. package/runtime/metadata.js +143 -0
  55. package/runtime/options.d.ts +21 -0
  56. package/runtime/options.d.ts.map +1 -0
  57. package/runtime/options.js +100 -0
  58. package/runtime/state.d.ts +1 -0
  59. package/runtime/state.d.ts.map +1 -1
  60. package/runtime/state.js +5 -75
  61. package/src/installer.ts +17 -0
  62. package/src/jobs/context.ts +5 -3
  63. package/src/jobs/permissions.ts +1 -1
  64. package/src/jobs/program.ts +4 -1
  65. package/src/jobs/prompt.ts +5 -5
  66. package/src/jobs/schedule.ts +2 -1
  67. package/src/jobs/slash-command.ts +3 -2
  68. package/src/jobs/status-line.ts +3 -2
  69. package/src/jobs/tool.ts +3 -2
  70. package/src/mod.ts +46 -11
  71. package/src/options.ts +91 -0
  72. package/src/records.ts +117 -8
  73. package/src/register.ts +12 -3
  74. package/src/runtime/claude.ts +2 -0
  75. package/src/runtime/grants.ts +306 -0
  76. package/src/runtime/installer.ts +163 -0
  77. package/src/runtime/lifecycle.ts +220 -45
  78. package/src/runtime/metadata.ts +153 -0
  79. package/src/runtime/options.ts +102 -0
  80. package/src/runtime/state.ts +7 -70
  81. package/src/testing/fake-claude.ts +6 -1
  82. package/src/testing/fake-files.ts +9 -3
  83. package/src/testing.ts +40 -15
  84. package/src/utils/metadata.ts +242 -0
  85. package/testing/fake-claude.d.ts +4 -0
  86. package/testing/fake-claude.d.ts.map +1 -1
  87. package/testing/fake-claude.js +5 -1
  88. package/testing/fake-files.d.ts.map +1 -1
  89. package/testing/fake-files.js +9 -3
  90. package/testing.d.ts +6 -2
  91. package/testing.d.ts.map +1 -1
  92. package/testing.js +29 -13
  93. package/utils/metadata.d.ts +21 -0
  94. package/utils/metadata.d.ts.map +1 -0
  95. package/utils/metadata.js +250 -0
@@ -0,0 +1,93 @@
1
+ # Options
2
+
3
+ An option is a value a person sets for a mod and the mod reads, such as a token, a URL, or how often to check. The mod declares its options in `defineMod`, and `mod.options` holds their values, typed from the declaration. Claude Code stores them: it shows each one in `/config`, keeps a secret in the system keychain, and reloads the mod when one changes.
4
+
5
+ `mod.state` is the other kind of value: what the mod owns and changes as it runs ([state.md](state.md)).
6
+
7
+ ## Declare the options
8
+
9
+ ```ts
10
+ import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
11
+ import { option } from '../node_modules/@cmodjs/core/options.js'
12
+
13
+ export const ciWatch = defineMod({
14
+ name: 'ci-watch',
15
+ options: {
16
+ githubToken: option.secret({ title: 'GitHub token', description: 'Reads your CI runs' }),
17
+ every: option.number({ title: 'Check every', description: 'Minutes between checks', default: 10, min: 1 }),
18
+ branch: option.text({ title: 'Branch', description: 'The branch to watch', default: 'main' }),
19
+ },
20
+ setup(mod) {
21
+ mod.ui.toast(`Watching ${mod.options.branch} every ${mod.options.every} minutes`)
22
+ },
23
+ })
24
+ ```
25
+
26
+ `mod.options.every` is a `number` and `mod.options.branch` a `string`, so `tsc` catches a typo or a wrong type.
27
+
28
+ | Helper | The value | Claude Code shows it as |
29
+ | --- | --- | --- |
30
+ | `option.text({ title, description, default? })` | `string` | a text field |
31
+ | `option.secret({ title, description })` | `string` | a masked field, kept in the keychain |
32
+ | `option.number({ title, description, default?, min?, max? })` | `number` | a number field |
33
+ | `option.toggle({ title, description, default? })` | `boolean`, `false` unless `default` says otherwise | a toggle |
34
+ | `option.choice(choices, { title, description, default? })` | one of `choices` | a picker |
35
+ | `option.folder({ title, description, default? })` | `string`, a folder path | a folder field |
36
+ | `option.file({ title, description, default? })` | `string`, a file path | a file field |
37
+ | `option.list({ title, description, default? })` | `readonly string[]` | a list of text |
38
+
39
+ - An option name is letters, digits, and `_`, starting with a letter, because Claude Code passes each option on as `CLAUDE_PLUGIN_OPTION_<NAME>`. `defineMod` throws for another name.
40
+ - `title` is the label `/config` shows. `description` is the help text under it.
41
+ - A secret has no default, so a token never ships in the mod.
42
+
43
+ ## Where the values come from
44
+
45
+ Each value is, in order of precedence:
46
+
47
+ 1. A value the person's organization set in managed settings. `/config` shows it locked.
48
+ 2. The value in the project's `options.json`.
49
+ 3. The value the person set in `/config`.
50
+ 4. The `default` in the declaration.
51
+
52
+ An option with no default and no value is asked before `setup` runs, on a page of the installer pane: one field per option, each saved to `/config` as the person submits it. A secret is set in `/config` itself, where Claude Code keeps it in the keychain, and the page says so. When the person chooses Not now, or no one can answer, as in `claude -p`, the mod does not start: its progress line says `it needs <title>` and `Set it in /config.`, and Claude Code reloads the mod once the value is set.
53
+
54
+ `cmod install`, `cmod link`, and `cmod try` set options from the terminal with `--option key=value`, once per option ([commands.md](commands.md)).
55
+
56
+ `mod.options` follows the values while the session runs. Read an option where you use it, as in `mod.options.branch`, so a change applies at once. A change in `/config` reloads the mod, and a `/cd` reads the new project's `options.json`.
57
+
58
+ ## Set options for a team
59
+
60
+ A project sets options for everyone who works in it with an `options.json` in its config folder, committed with the repository:
61
+
62
+ ```text
63
+ <project>/.claude/cmods/<mod>/options.json
64
+ ```
65
+
66
+ ```json
67
+ { "branch": "release", "every": 5 }
68
+ ```
69
+
70
+ cmod ignores each entry it cannot use and logs one line naming the file, the entry, and the fix:
71
+
72
+ - a file that is not JSON, or not a JSON object
73
+ - an option the mod does not declare
74
+ - a secret, because the file is committed for everyone to read
75
+ - a value the option does not take, such as `0` for an option with `min: 1`
76
+
77
+ ## plugin.json userConfig
78
+
79
+ Claude Code reads a plugin's options from `userConfig` in `.claude-plugin/plugin.json` before it loads the mod. `cmod check` writes `userConfig` from the options `defineMod` declares, so the declaration is the one place to change them. `cmod publish` refuses a `plugin.json` whose `userConfig` differs: run `cmod check`, then commit `.claude-plugin/plugin.json`.
80
+
81
+ `cmod check` never writes `required`, because Claude Code refuses to load a mod while a required option has no value. cmod asks for an option with no default instead.
82
+
83
+ `userConfigOf(options)`, exported from `options.js`, returns the `userConfig` for a declaration.
84
+
85
+ ## Test options
86
+
87
+ `testMod(mod, { options: { githubToken: 'ghp_test' } })` starts the tested mod with those values, as Claude Code passes them. Each option left out gets its default. A test that leaves out an option with no default fails with `<mod> needs <title>, which a person sets in /config. Give it to the tested mod: testMod(mod, { options: { <key>: … } }).` `fakes.config.list` answers the `/config` rows, so a test can lock a value as managed settings do ([testing.md](testing.md)).
88
+
89
+ ```ts
90
+ const tested = testMod(ciWatch, { options: { githubToken: 'ghp_test', branch: 'develop' } })
91
+ await tested.start()
92
+ expect(tested.shown.toasts).toContain('Watching develop every 10 minutes')
93
+ ```
@@ -0,0 +1,88 @@
1
+ # Permissions
2
+
3
+ A mod declares what it does beyond drawing and answering hooks: the hosts it connects to, the programs it runs, the files it changes outside its project, and whether it reads the conversation, adds text Claude reads, asks a model, starts agents, uses Claude's tools, changes Claude Code's settings, or approves tool calls. The person sees each permission as one sentence when they install the mod, turns each one on or off in `/mods`, and cmod checks every call and every answer against what they granted.
4
+
5
+ Permissions bind every mod built on `@cmodjs/core`, and the plugin directory reads them from `package.json`. They are a contract, not a sandbox: a Claude Code plugin can skip cmod and call Claude Code directly.
6
+
7
+ ## Declare the permissions
8
+
9
+ They sit in the `package.json` `cmod` key, beside `install` and `keys`, because the person reads them before any of the mod's code runs:
10
+
11
+ ```json
12
+ "cmod": {
13
+ "permissions": { "network": ["api.github.com"], "run": ["gh"], "prompt": true, "model": true }
14
+ }
15
+ ```
16
+
17
+ | Permission | The person reads | cmod checks |
18
+ | --- | --- | --- |
19
+ | `"network": ["api.github.com"]` | Connect to api.github.com | `mod.http.fetch` to that host. A path starting with `/` or `~/` names a socket, such as `"~/.docker/run/docker.sock"` |
20
+ | `"run": ["gh"]`, or `"run": "*"` | Run gh on your computer | `mod.process.run`, `mod.process.spawn`, and the `program` and `check` jobs. A program matches by its file name, so `"mermaid-ascii"` covers `${mod.dataFolder}/bin/mermaid-ascii` |
21
+ | `"files": ["~/.zshrc"]` | Change ~/.zshrc | `mod.fs.write` and `mod.metadata.update` outside the project and the mod's data folder. A folder covers every file below it |
22
+ | `"conversation": true` | Read this conversation | `mod.session.messages`, and reading Claude Code's transcript files |
23
+ | `"prompt": true` | Add text Claude reads and start turns | `mod.session.append`, `mod.session.submit`, the `prompt` and `schedule` jobs, and the hook answers listed below |
24
+ | `"model": true` | Ask a model, which uses your plan | `mod.model.complete` |
25
+ | `"agents": true` | Start agents | `mod.agent.spawn` |
26
+ | `"tools": true` | Use and change Claude's tool calls | `mod.claude.tool.call`, and a hook that changes a tool call's input |
27
+ | `"config": true` | Change your Claude Code settings | `mod.claude.config.set`, and writes to `.claude/settings.json`, `.claude/settings.local.json`, and `.mcp.json` |
28
+ | `"approve": true` | Approve Claude's tool calls for you | an allow or an ask from the mod's permission rules or hooks, and a `retry`. A deny needs no permission |
29
+
30
+ Reading files, drawing panes and slot renders, toasts, slash commands, tools of the mod's own, and hooks need no permission.
31
+
32
+ `cmod check` adds `"approve": true` to `package.json` when `hooks/register.ts` calls `registerPermissionCheck` and the line is missing, and says so.
33
+
34
+ ## What the person sees
35
+
36
+ The install opens the installer pane in Claude Code and lists every permission in `permissions`, with the install step, the program, and the keys. An update asks again only when it adds a permission, and lists only the new ones. Removing the mod revokes everything it was granted.
37
+
38
+ `/mods <mod>` lists the same permissions as toggles. A toggle runs `cmod permission <mod> <name> [value] on|off`, and the mod sees the change before its next call, with no reload.
39
+
40
+ `cmod check` prints the sentences, so you read what the person will see:
41
+
42
+ ```text
43
+ ✔ At install the person grants: Connect to api.github.com; Run gh on your computer; Ask a model, which uses your plan
44
+ ```
45
+
46
+ ## Check a permission before a feature uses it
47
+
48
+ ```ts
49
+ mod.permissions.has(name: 'network' | 'run' | 'files', value: string): boolean
50
+ mod.permissions.has(name: 'conversation' | 'prompt' | 'model' | 'agents' | 'tools' | 'config' | 'approve'): boolean
51
+ ```
52
+
53
+ `has` says whether the person has the permission turned on, so a mod hides a feature instead of failing it:
54
+
55
+ ```ts
56
+ if (mod.permissions.has('model')) {
57
+ const verdict = await mod.model.complete({ prompt: 'Is this reply a waste of time?' })
58
+ }
59
+ ```
60
+
61
+ ## When a call has no grant
62
+
63
+ The call rejects, and the error names the fix:
64
+
65
+ - An undeclared call names the line to add: `ci-watch calls http.fetch(https://api.github.com/repos), which needs "permissions": { "network": ["api.github.com"] } in package.json "cmod".`
66
+ - A declared call the person turned off names where to turn it on: `ci-watch calls model.complete without your grant to "Ask a model, which uses your plan". Turn it on in /mods ci-watch.`
67
+ - A `program` job without its grant stops at once with the same line, with no restarts.
68
+
69
+ ## When a hook answer has no grant
70
+
71
+ cmod checks the answer each hook returns, part by part. A part without its grant is dropped, and cmod logs once per permission which part it dropped and how to fix it:
72
+
73
+ ```text
74
+ ci-watch answered additionalContext without your grant to "Add text Claude reads and start turns", so cmod dropped it. Turn it on in /mods ci-watch.
75
+ ```
76
+
77
+ | Answer part | Permission |
78
+ | --- | --- |
79
+ | `additionalContext`, `initialUserMessage`, `suppressOriginalPrompt`, `updatedToolOutput`, a `prompt` job's text | `prompt` |
80
+ | a Stop or PostToolUse block | `prompt` |
81
+ | `updatedInput`, and a changed tool input passed down the chain | `tools` |
82
+ | an allow or an ask, and `retry` | `approve` |
83
+
84
+ A deny needs no permission. A tool of the mod's own, `mcp__<mod>__*`, answers freely.
85
+
86
+ ## Test permissions
87
+
88
+ `testMod` grants what the mod's `package.json` declares, so tests run as an installed mod does. An undeclared call fails the test with the line to add. `testMod(mod, { permissions: ['network:api.github.com'] })` grants exactly the list instead, so a test checks what happens with a permission turned off ([testing.md](testing.md)).
package/docs/state.md CHANGED
@@ -80,16 +80,11 @@ export const reads = defineMod({
80
80
 
81
81
  ## Where the values come from
82
82
 
83
- When the mod starts, each value is, in order of precedence:
83
+ When the mod starts, each value is the value the mod saved for this session, this project, or globally, or else the starting value in `defineMod`. After a `/cd` to another project, cmod loads that project's `project` values.
84
84
 
85
- 1. The value the mod saved for this session, this project, or globally.
86
- 2. The value in the project's `state.json`.
87
- 3. The value in the person's own `state.json`.
88
- 4. The starting value in `defineMod`.
85
+ `mod.state` holds what the mod owns and changes as it runs. What a person sets, such as a token or a branch to watch, is an option ([options.md](options.md)).
89
86
 
90
- After a `/cd` to another project, cmod loads that project's `project` values and reads its `state.json` again.
91
-
92
- ## Let a person change the starting values
87
+ ## Let a person replace a Skill's text
93
88
 
94
89
  Each mod has two config folders, outside its code:
95
90
 
@@ -98,29 +93,7 @@ Each mod has two config folders, outside its code:
98
93
  <project>/.claude/cmods/<mod>/ the team's, committed with the repository
99
94
  ```
100
95
 
101
- `CLAUDE_CONFIG_DIR` moves `~/.claude`. Removing the mod keeps both folders.
102
-
103
- A `state.json` in either folder lists only the values to change:
104
-
105
- ```json
106
- { "global": { "defaultCommitPolicy": "never" }, "session": { "prompts": 10 } }
107
- ```
108
-
109
- - A `session` value is what each new conversation starts with. A `project` value is what each project starts with.
110
- - A file in the project folder wins over the same value in the person's folder.
111
- - A project `state.json` cannot set `global` values, because one repository cannot change a value for every project.
112
- - A file cannot set `memory` values.
113
-
114
- cmod ignores each entry it cannot use and logs one line naming the file, the entry, and the fix:
115
-
116
- - a file that is not JSON, or not a JSON object
117
- - a group the mod does not declare
118
- - a key the mod does not declare
119
- - a value whose type differs from the starting value's
120
- - a `global` value in a project file
121
- - a `memory` value
122
-
123
- ## Let a person replace a Skill's text
96
+ `CLAUDE_CONFIG_DIR` moves `~/.claude`. Removing the mod keeps both folders. The project folder also holds the team's options, in `options.json` ([options.md](options.md)).
124
97
 
125
98
  A person replaces the text of a Skill the mod ships by putting a file at the same path in a config folder:
126
99
 
package/docs/testing.md CHANGED
@@ -3,7 +3,7 @@
3
3
  `testMod` runs a mod against a fake Claude Code in `bun test`, so a test drives the mod the way a person and Claude do and reads what they would see. It needs no running Claude Code.
4
4
 
5
5
  ```ts
6
- testMod<State>(definition: ModDefinition<State>, options?: TestOptions<State>): TestedMod<State>
6
+ testMod<State, Options>(definition: ModDefinition<State, string, Options>, options?: TestOptions<State, Options>): TestedMod<State>
7
7
  ```
8
8
 
9
9
  ```ts
@@ -24,24 +24,30 @@ Assert what a person or Claude sees: `tested.lines(...)`, `tested.shown`, `teste
24
24
  ## TestOptions
25
25
 
26
26
  ```ts
27
- type TestOptions<State> = {
27
+ type TestOptions<State, Options> = {
28
28
  readonly state?: { [Lifetime in keyof State]?: Partial<State[Lifetime]> }
29
+ readonly options?: Partial<OptionValues<Options>>
30
+ readonly permissions?: readonly string[]
29
31
  readonly scope?: 'user' | 'project'
30
32
  readonly projectRoot?: string
31
33
  readonly cwd?: string
32
34
  readonly dependencies?: { [Name in keyof CmodDependencies]?: CmodDependencies[Name] }
33
35
  readonly files?: Readonly<Record<string, string>>
36
+ readonly links?: Readonly<Record<string, string>>
34
37
  }
35
38
  ```
36
39
 
37
40
  | Option | What it sets | Default |
38
41
  | --- | --- | --- |
39
42
  | `state` | Values over the declared starting state | The declared state |
43
+ | `options` | The option values Claude Code passes, as a person set them ([options.md](options.md)) | none: each option gets its default |
44
+ | `permissions` | Exactly the permissions the person granted, such as `['network:api.github.com', 'model']` ([permissions.md](permissions.md)) | every permission the mod's `package.json` declares |
40
45
  | `scope` | `'project'` makes the mod a project plugin, in `<projectRoot>/.claude/skills/<name>` | `'user'` |
41
46
  | `projectRoot` | `mod.projectRoot` | `/test/plugins/<name>`, or `/test/project` for a project plugin |
42
47
  | `cwd` | `mod.cwd` | `projectRoot` |
43
48
  | `dependencies` | The methods other mods answer with | none: every call fails with `<name> is not installed. Run cmod install <name>.` |
44
49
  | `files` | The fake file system, absolute path to text | no files |
50
+ | `links` | Symbolic links in the fake file system, absolute link path to its target | no links |
45
51
 
46
52
  A project plugin finds its repository through a `.git` entry at `projectRoot`, so a test of one puts it in `files`, such as `files: { '/test/project/.git': '' }`. Without it the project plugin has no repository, and its rules match calls in every folder, as a user mod's do.
47
53
 
@@ -213,6 +219,7 @@ type Fakes = {
213
219
  fs: { read?, write?, list?, exists?, stat? }
214
220
  http: { fetch? }
215
221
  settings: { read? }
222
+ config: { list?, set? }
216
223
  ui: { ask?, open?, scroll? }
217
224
  session: { messages?, append? }
218
225
  prompt: { submit? }
@@ -234,7 +241,11 @@ Set a fake to answer a call:
234
241
  - `clock.after(ms, fn)` and `clock.every(ms, fn)` each return `{ cancel() }`, and so must a fake of either. `clock.after` runs a real timer by default. `clock.every` never fires unless a fake keeps `fn` for the test to call, so a test of `mod.every` keeps it and calls it once per tick.
235
242
  - `session.append` and `prompt.submit` take every note and prompt by default, into `shown.notes` and `shown.prompts`. A fake that answers `{ deny }` or `{ drop }` refuses one.
236
243
  - `model.complete` has no default: set it to the answer the model gives, such as `async () => ({ isAnswered: true, text: 'no', usage })`.
244
+ - `config.list` answers no `/config` rows by default, and `config.set` answers every change with `{ value }`. A `config.list` fake that answers a row with `isLocked: true` locks that option as managed settings do.
237
245
  - `tool.call` answers `CronCreate`, `CronDelete`, and `CronList` by default, keeping the crons a `schedule` makes in `shown.schedules`. A call to any other tool needs a fake.
246
+ - `files` takes symbolic links too: `testMod(mod, { files, links: { '/work/skills': '/shared/skills' } })` makes `/work/skills` a link to `/shared/skills`, so a test of `mod.fs.find` or `mod.metadata.update` follows it as a real file system does.
247
+ - `testMod` runs no installer pane: an option with no default and no value fails `start()` with its fix, and the `installer` steps are not shown. Test a step's `render` and `isDone` by calling them with the started `mod`.
248
+ - The `committer` below runs `git`, so its `package.json` declares `"permissions": { "run": ["git"] }`. A gated call the mod does not declare fails before it reaches a fake ([permissions.md](permissions.md)).
238
249
 
239
250
  ```ts
240
251
  import { expect, test } from 'bun:test'
package/installer.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { Frozen, RenderElement, RenderPropsOf } from 'claude-code';
2
+ import type { Mod } from './mod.js';
3
+ export type Step<State extends object = Record<never, never>> = {
4
+ readonly id: string;
5
+ readonly title: string;
6
+ render(mod: Mod<State>, props: Frozen<RenderPropsOf['Pane']>): RenderElement;
7
+ isDone(mod: Mod<State>): boolean | Promise<boolean>;
8
+ };
9
+ export declare function defineStep<State extends object = Record<never, never>>(step: Step<State>): Step<State>;
10
+ //# sourceMappingURL=installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["src/installer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,MAAM,MAAM,IAAI,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI;IAC9D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAA;IAC5E,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACpD,CAAA;AAID,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAItG"}
package/installer.js ADDED
@@ -0,0 +1,8 @@
1
+ const stepId = /^[A-Za-z0-9_-]{1,64}$/;
2
+ export function defineStep(step) {
3
+ if (!stepId.test(step.id))
4
+ throw new Error(`defineStep: "${step.id}" is not a step id. Use 1 to 64 letters, digits, "_", or "-".`);
5
+ if (step.title.trim() === '')
6
+ throw new Error(`defineStep: the step "${step.id}" needs a title, the line the installer shows above it.`);
7
+ return step;
8
+ }
package/jobs/context.d.ts CHANGED
@@ -3,12 +3,12 @@ import { type Deadline } from '../runtime/deadline.js';
3
3
  import type { ToolCall } from '../utils/call-effects.js';
4
4
  import { type Workspace } from './permissions/find-project-scope.js';
5
5
  import { type Target } from './permissions/match-target.js';
6
- export declare function workspaceReader({ claude }: JobContext): () => Promise<Workspace>;
6
+ export declare function workspaceReader({ mod: { claude } }: JobContext): () => Promise<Workspace>;
7
7
  export declare function afterCall<State extends object>(job: JobContext<State>, contextAfter: (call: ToolCall, workspace: Workspace) => Promise<readonly string[]>, failed: (error: unknown) => readonly string[]): void;
8
8
  export declare function modOf<State extends object>(job: JobContext<State>, { call, folder }: {
9
9
  readonly call?: ToolCall;
10
10
  readonly folder: string;
11
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>;
12
+ export declare function modWithin<State extends object>({ mod }: JobContext<State>, deadline: Deadline, workTree?: () => Promise<string | undefined>): Mod<State>;
13
13
  export declare function targetWords(target: Target): string;
14
14
  //# sourceMappingURL=context.d.ts.map
@@ -1 +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,MAAM,0BAA0B,CAAA;AACxD,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,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,EAClF,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,CA0BvL;AAwBD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASlD"}
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,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAA;AACtF,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAOrE,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAOzF;AAaD,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAC5C,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,EAClF,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,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAyB,GAAG,GAAG,CAAC,KAAK,CAAC,CA4B/K;AAwBD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASlD"}
package/jobs/context.js CHANGED
@@ -4,7 +4,7 @@ import { toolInputOf } from '../runtime/tool-calls.js';
4
4
  import { listed } from '../utils/text.js';
5
5
  import { findProjectScope } from './permissions/find-project-scope.js';
6
6
  import { targetOf } from './permissions/match-target.js';
7
- export function workspaceReader({ claude }) {
7
+ export function workspaceReader({ mod: { claude } }) {
8
8
  let session;
9
9
  return async () => {
10
10
  session ??= readSession(claude);
@@ -25,7 +25,7 @@ async function readSession(claude) {
25
25
  }
26
26
  export function afterCall(job, contextAfter, failed) {
27
27
  const readWorkspace = workspaceReader(job);
28
- job.on('tool.call', async (e, next) => {
28
+ job.mod.claude.on('tool.call', async (e, next) => {
29
29
  const before = Promise.all([useOf(job, e), readWorkspace()]);
30
30
  await before.catch(() => undefined);
31
31
  const result = await next(e);
@@ -41,7 +41,8 @@ async function useOf({ toolCalls }, e) {
41
41
  export function modOf(job, { call, folder }, workspace, deadline) {
42
42
  return modWithin(job, deadline, async () => workspace.scope?.workTreeOf(call !== undefined && 'path' in call ? call.path : folder));
43
43
  }
44
- export function modWithin({ mod, claude }, deadline, workTree = async () => undefined) {
44
+ export function modWithin({ mod }, deadline, workTree = async () => undefined) {
45
+ const { claude } = mod;
45
46
  const within = (call, task) => beforeDeadline(claude, deadline, call, task);
46
47
  const folderOf = async (cwd) => {
47
48
  const folder = cwd ?? (await workTree());
@@ -62,6 +63,7 @@ export function modWithin({ mod, claude }, deadline, workTree = async () => unde
62
63
  list: (path) => within('mod.fs.list', mod.fs.list(path)),
63
64
  exists: (path) => within('mod.fs.exists', mod.fs.exists(path)),
64
65
  stat: (path, options) => within('mod.fs.stat', mod.fs.stat(path, options)),
66
+ find: (glob) => within('mod.fs.find', mod.fs.find(glob)),
65
67
  },
66
68
  http: { fetch: (url, init) => within('mod.http.fetch', mod.http.fetch(url, init)) },
67
69
  dependencies: dependencyCalls(claude, within),
@@ -27,7 +27,7 @@ export function permissions(rules) {
27
27
  return { decision: 'deny', reason: `The permissions job could not read the session, so it denies the call: ${messageOf(error)}` };
28
28
  }
29
29
  };
30
- job.on('tool.check', async (e, next) => {
30
+ job.mod.claude.on('tool.check', async (e, next) => {
31
31
  const [below, ours] = await Promise.all([next(e), verdictOf(e)]);
32
32
  return ours === undefined ? below : stricterVerdict(below, ours);
33
33
  });
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/jobs/program.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAIpC,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,CA2ErI"}
package/jobs/program.js CHANGED
@@ -1,8 +1,10 @@
1
+ import { PermissionRefused } from '../runtime/grants.js';
1
2
  import { messageOf } from '../utils/text.js';
2
3
  const backoffSeconds = [1, 2, 4, 8, 16];
3
4
  export function program(options) {
4
5
  const { command, environment } = options;
5
- return ({ mod, claude, announce }) => {
6
+ return ({ mod, announce }) => {
7
+ const { claude } = mod;
6
8
  const [executable] = command;
7
9
  if (executable === undefined)
8
10
  throw new Error(`${mod.name}: the program has no command. Name the program on PATH and its arguments, such as ['preview-server', '--port', '0'].`);
@@ -47,6 +49,8 @@ export function program(options) {
47
49
  }
48
50
  }
49
51
  catch (error) {
52
+ if (error instanceof PermissionRefused)
53
+ return stop(`${messageOf(error)} Add it, then run /reload-plugins.`);
50
54
  lastError = messageOf(error);
51
55
  }
52
56
  address = undefined;
package/jobs/prompt.js CHANGED
@@ -15,7 +15,7 @@ export function prompt({ name, prompt: text, when, after }) {
15
15
  targetOf(target);
16
16
  return (job) => {
17
17
  job.reserveName('prompt', name, `prompt: ${job.mod.name} adds the name "${name}" two times. Give each prompt its own name.`);
18
- const log = (reason) => job.claude.ui.log(`prompt "${name}" added nothing: ${reason}`, { to: 'debug' });
18
+ const log = (reason) => job.mod.claude.ui.log(`prompt "${name}" added nothing: ${reason}`, { to: 'debug' });
19
19
  const textFor = async (input, mod) => {
20
20
  try {
21
21
  const value = typeof text === 'string' ? text : await text(input, mod);
@@ -49,13 +49,13 @@ function addKeptCurrent(job, name, textFor, log) {
49
49
  let hasStarted = false;
50
50
  let isQueued = false;
51
51
  let updates = Promise.resolve();
52
- job.on('classic.SessionStart', (e, next) => {
52
+ job.mod.claude.on('classic.SessionStart', (e, next) => {
53
53
  hasStarted = e.source === 'resume' || e.source === 'compact';
54
54
  if (!hasStarted)
55
55
  given = undefined;
56
56
  return next(e);
57
57
  });
58
- job.on('prompt.context', async (e, next) => {
58
+ job.mod.claude.on('prompt.context', async (e, next) => {
59
59
  const below = await next(e);
60
60
  hasStarted = true;
61
61
  if (below.blocks.some((block) => block.name === name)) {
@@ -73,7 +73,7 @@ function addKeptCurrent(job, name, textFor, log) {
73
73
  if (value === undefined || value === given)
74
74
  return;
75
75
  given = value;
76
- await job.claude.session.append({ message: { type: 'user', content: [{ type: 'text', text: `# ${name}\n${value}` }] } }).then((added) => {
76
+ await job.mod.claude.session.append({ message: { type: 'user', content: [{ type: 'text', text: `# ${name}\n${value}` }] } }).then((added) => {
77
77
  if (added.deny !== undefined)
78
78
  log(`Claude Code refused the new text: ${added.deny}`);
79
79
  });
@@ -86,7 +86,7 @@ function addKeptCurrent(job, name, textFor, log) {
86
86
  });
87
87
  }
88
88
  function addAfterUserPrompts(job, when, textFor, log) {
89
- job.on('prompt.submit', async (e, next) => {
89
+ job.mod.claude.on('prompt.submit', async (e, next) => {
90
90
  let value;
91
91
  try {
92
92
  if (typeof when === 'function' ? when(e.text) : e.text.search(when) >= 0)
@@ -1 +1 @@
1
- {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/jobs/schedule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAIpC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAIzE,wBAAgB,QAAQ,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,QAAQ,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAmDlJ"}
1
+ {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/jobs/schedule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAIpC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAA;AAIzE,wBAAgB,QAAQ,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,QAAQ,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAoDlJ"}
package/jobs/schedule.js CHANGED
@@ -5,7 +5,8 @@ export function schedule(entry) {
5
5
  if (typeof entry !== 'function')
6
6
  checked(entry);
7
7
  return (job) => {
8
- const { mod, claude } = job;
8
+ const { mod } = job;
9
+ const { claude } = mod;
9
10
  const order = scheduleCounts.get(mod) ?? 0;
10
11
  scheduleCounts.set(mod, order + 1);
11
12
  const storeKey = `cmod:schedule:${order}`;
@@ -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,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"}
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,CA6BnB"}
@@ -7,7 +7,8 @@ const commandName = /^[A-Za-z0-9_-]{1,64}$/;
7
7
  export function slashCommand(options) {
8
8
  const { name, description, argumentHint, immediate, reply } = options;
9
9
  return (job) => {
10
- const { mod, claude, announce, reserveName } = job;
10
+ const { mod, announce, reserveName } = job;
11
+ const { claude } = mod;
11
12
  if (!commandName.test(name))
12
13
  throw new Error(`${mod.name}: "${name}" is not a slash command name. Use 1 to 64 letters, digits, "_", or "-", without the slash.`);
13
14
  reserveName('slashCommand', name, `${mod.name}: the slash command /${name} is already added. Give each slashCommand its own name.`);
@@ -19,7 +20,7 @@ export function slashCommand(options) {
19
20
  });
20
21
  announce(`/${name}`);
21
22
  const replyMod = modWithin(job, deadline);
22
- job.on('command.run', async (e, next) => {
23
+ claude.on('command.run', async (e, next) => {
23
24
  if (e.command !== registered)
24
25
  return next(e);
25
26
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"status-line.d.ts","sourceRoot":"","sources":["../src/jobs/status-line.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAKzC,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACvB,CAAA;AAID,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IAC/E,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAClG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAyCnB"}
1
+ {"version":3,"file":"status-line.d.ts","sourceRoot":"","sources":["../src/jobs/status-line.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAKzC,MAAM,MAAM,KAAK,GAAG;IAClB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,IAAI,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;CACvB,CAAA;AAID,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IAC/E,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IAClG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CA0CnB"}
@@ -4,7 +4,8 @@ const deadline = { ms: 2000, job: 'statusLine' };
4
4
  export function statusLine(options) {
5
5
  const { text, interval = 10000 } = options;
6
6
  return (job) => {
7
- const { mod, claude, announce, reserveName } = job;
7
+ const { mod, announce, reserveName } = job;
8
+ const { claude } = mod;
8
9
  if (!(interval >= 1))
9
10
  throw new Error(`${mod.name}: the status line interval is ${interval}. Give it in milliseconds, 1 or more.`);
10
11
  reserveName('statusLine', '', `${mod.name}: a status line is already added. A mod has one status line: join the texts in one statusLine.`);
@@ -33,7 +34,7 @@ export function statusLine(options) {
33
34
  queue = queue.then(update);
34
35
  };
35
36
  announce('a status line');
36
- job.on('session.measure', (e, next) => {
37
+ claude.on('session.measure', (e, next) => {
37
38
  refresh();
38
39
  return next(e);
39
40
  });
@@ -1 +1 @@
1
- {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/jobs/tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC/D,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AASzC,wBAAgB,IAAI,CAAC,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,EAAE,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IAC3H,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,SAAS,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,OAAO,CAAA;CAC9G,GAAG,GAAG,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,CAAC,CAoCxC"}
1
+ {"version":3,"file":"tool.d.ts","sourceRoot":"","sources":["../src/jobs/tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC/D,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AASzC,wBAAgB,IAAI,CAAC,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,EAAE,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE;IAC3H,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,SAAS,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,KAAK,OAAO,CAAA;CAC9G,GAAG,GAAG,CAAC;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,CAAC,CAqCxC"}
package/jobs/tool.js CHANGED
@@ -7,7 +7,8 @@ const toolName = /^[A-Za-z0-9_-]{1,64}$/;
7
7
  export function tool(options) {
8
8
  const { name, description, inputSchema, execute } = options;
9
9
  return (job) => {
10
- const { mod, claude, announce, reserveName } = job;
10
+ const { mod, announce, reserveName } = job;
11
+ const { claude } = mod;
11
12
  if (!toolName.test(name))
12
13
  throw new Error(`${mod.name}: "${name}" is not a tool name. Use 1 to 64 letters, digits, "_", or "-".`);
13
14
  const properties = inputSchema?.properties ?? {};
@@ -24,7 +25,7 @@ export function tool(options) {
24
25
  });
25
26
  announce(`the ${name} tool`);
26
27
  const executeMod = modWithin(job, toolDeadline);
27
- job.on('tool.call', async (e, next) => {
28
+ claude.on('tool.call', async (e, next) => {
28
29
  if (e.tool !== registered)
29
30
  return next(e);
30
31
  const input = toolInputOf(e);