@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.
- package/docs/commands.md +4 -3
- package/docs/hooks.md +2 -0
- package/docs/index.md +4 -4
- package/docs/jobs.md +23 -22
- package/docs/mod.md +24 -9
- package/docs/testing.md +1 -1
- package/jobs/check.d.ts +2 -2
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +5 -5
- package/jobs/context.d.ts +14 -0
- package/jobs/context.d.ts.map +1 -0
- package/jobs/{part-context.js → context.js} +6 -6
- package/jobs/permissions.d.ts +2 -2
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +7 -7
- package/jobs/program.d.ts +2 -2
- package/jobs/program.d.ts.map +1 -1
- package/jobs/prompt.d.ts +2 -2
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +17 -17
- package/jobs/slash-command.d.ts +2 -2
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +5 -5
- package/jobs/status-line.d.ts +2 -2
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +5 -5
- package/jobs/tool.d.ts +2 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +5 -5
- package/mod.d.ts +3 -3
- package/mod.d.ts.map +1 -1
- package/package.json +2 -2
- package/register.d.ts +5 -0
- package/register.d.ts.map +1 -0
- package/{connect.js → register.js} +42 -39
- package/runtime/hooks.d.ts +1 -0
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hooks.js +1 -0
- package/runtime/lifecycle.d.ts +1 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +16 -13
- package/runtime/router.d.ts +1 -0
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +1 -0
- package/runtime/tool-calls.d.ts +3 -2
- package/runtime/tool-calls.d.ts.map +1 -1
- package/runtime/tool-calls.js +11 -14
- package/src/jobs/check.ts +7 -7
- package/src/jobs/{part-context.ts → context.ts} +10 -10
- package/src/jobs/permissions.ts +9 -9
- package/src/jobs/program.ts +2 -2
- package/src/jobs/prompt.ts +19 -19
- package/src/jobs/slash-command.ts +7 -7
- package/src/jobs/status-line.ts +7 -7
- package/src/jobs/tool.ts +7 -7
- package/src/mod.ts +3 -3
- package/src/{connect.ts → register.ts} +44 -40
- package/src/runtime/hooks.ts +2 -0
- package/src/runtime/lifecycle.ts +18 -14
- package/src/runtime/router.ts +2 -0
- package/src/runtime/tool-calls.ts +12 -14
- package/vendor.js +1 -3
- package/connect.d.ts +0 -4
- package/connect.d.ts.map +0 -1
- package/jobs/part-context.d.ts +0 -14
- 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
|
|
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
|
|
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`, `
|
|
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
|
-
| `
|
|
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/
|
|
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
|
|
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>(
|
|
6
|
+
mod.use<Handle>(job: Job<Handle, State>): Handle
|
|
7
7
|
|
|
8
|
-
type
|
|
8
|
+
type Job<Handle, State> = (context: JobContext<State>) => Handle
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
Each job function, such as `slashCommand(options)`, returns a `
|
|
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
|
|
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
|
-
}):
|
|
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
|
-
}):
|
|
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>[] }):
|
|
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 }):
|
|
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
|
-
}):
|
|
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
|
-
}):
|
|
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> }):
|
|
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 `
|
|
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
|
|
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
|
|
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
|
|
371
|
+
import { defineMod, type Job } from '../node_modules/@cmodjs/core/mod.js'
|
|
371
372
|
|
|
372
|
-
const clock:
|
|
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/
|
|
440
|
-
| `jobs/
|
|
441
|
-
| `jobs/
|
|
442
|
-
| `jobs/
|
|
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. `
|
|
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
|
-
##
|
|
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 `
|
|
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 {
|
|
64
|
+
import { registerMod } from '../node_modules/@cmodjs/core/register.js'
|
|
64
65
|
import { greeter } from '../src/mod.js'
|
|
65
66
|
|
|
66
|
-
export function register(
|
|
67
|
-
|
|
67
|
+
export function register(addHook: On): void {
|
|
68
|
+
registerMod(addHook, greeter)
|
|
68
69
|
}
|
|
69
70
|
```
|
|
70
71
|
|
|
71
72
|
```ts
|
|
72
|
-
|
|
73
|
+
registerMod<State>(addHook: On, definition: ModDefinition<State>): void
|
|
74
|
+
registerPermissionCheck(addHook: On): void
|
|
73
75
|
```
|
|
74
76
|
|
|
75
|
-
`
|
|
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(
|
|
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
|
|
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 {
|
|
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
|
-
}):
|
|
7
|
+
}): Job<void, State>;
|
|
8
8
|
//# sourceMappingURL=check.d.ts.map
|
package/jobs/check.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/jobs/check.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
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 './
|
|
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 (
|
|
22
|
-
afterCall(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
27
|
-
const readWorkspace = workspaceReader(
|
|
28
|
-
|
|
29
|
-
const before = Promise.all([useOf(
|
|
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(
|
|
42
|
-
return modWithin(
|
|
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);
|
package/jobs/permissions.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
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>):
|
|
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,
|
|
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"}
|
package/jobs/permissions.js
CHANGED
|
@@ -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 './
|
|
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 (
|
|
13
|
-
const readWorkspace = workspaceReader(
|
|
12
|
+
return (job) => {
|
|
13
|
+
const readWorkspace = workspaceReader(job);
|
|
14
14
|
const verdictOf = async (e) => {
|
|
15
15
|
try {
|
|
16
|
-
const [agent, workspace] = await Promise.all([
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
}):
|
|
12
|
+
}): Job<Program>;
|
|
13
13
|
//# sourceMappingURL=program.d.ts.map
|
package/jobs/program.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"program.d.ts","sourceRoot":"","sources":["../src/jobs/program.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
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 {
|
|
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>):
|
|
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
|
package/jobs/prompt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/jobs/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,
|
|
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 './
|
|
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 (
|
|
16
|
-
|
|
17
|
-
const log = (reason) =>
|
|
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(
|
|
34
|
-
|
|
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(
|
|
38
|
-
|
|
37
|
+
addAfterCalls(job, targets, headedText, log);
|
|
38
|
+
job.announce(`the ${name} prompt after ${listed(targets.map(targetWords))}`);
|
|
39
39
|
}
|
|
40
40
|
else {
|
|
41
|
-
addOncePerConversation(
|
|
42
|
-
|
|
41
|
+
addOncePerConversation(job, name, () => textFor({}, modWithin(job, deadline)), log);
|
|
42
|
+
job.announce(`the ${name} prompt`);
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
|
-
function addOncePerConversation(
|
|
47
|
-
|
|
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(
|
|
58
|
-
|
|
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(
|
|
71
|
-
afterCall(
|
|
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(
|
|
77
|
+
const value = await textFor({ call: matched.call }, modOf(job, matched, workspace, deadline));
|
|
78
78
|
return value === undefined ? [] : [value];
|
|
79
79
|
}
|
|
80
80
|
return [];
|
package/jobs/slash-command.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
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
|
-
}):
|
|
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,
|
|
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"}
|