@cmodjs/core 0.2.1 → 0.2.3
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 +12 -3
- package/docs/install-steps.md +32 -9
- package/docs/mod.md +2 -2
- package/docs/permissions.md +1 -1
- package/package.json +1 -1
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +7 -2
- package/runtime/programs.d.ts +7 -0
- package/runtime/programs.d.ts.map +1 -0
- package/runtime/programs.js +24 -0
- package/src/runtime/lifecycle.ts +7 -2
- package/src/runtime/programs.ts +28 -0
package/docs/commands.md
CHANGED
|
@@ -25,7 +25,7 @@ Creates a mod in `./<name>`: a `defineMod` with one hook, one pane, and one rend
|
|
|
25
25
|
cmod link [path] [--yes] [--option key=value]...
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
Loads the checkout at `path` (default: the current folder) in every new Claude Code session, in place of the installed mod. Installs the cmod plugin when Claude Code lacks it, unless the checkout is cmod itself. Then installs the checkout's packages, builds the program `cli/` declares into `~/.local/bin`, writes the folder into `CLAUDE_CODE_PLUGIN_DIRS` in the `env` block of Claude Code's `settings.json`, and runs the checkout's install step. A session that is already running loads it after `/reload-plugins`.
|
|
28
|
+
Loads the checkout at `path` (default: the current folder) in every new Claude Code session, in place of the installed mod. Installs the cmod plugin when Claude Code lacks it, unless the checkout is cmod itself. Then installs the checkout's packages, builds the program `cli/` declares and links it into cmod's programs folder and `~/.local/bin`, writes the folder into `CLAUDE_CODE_PLUGIN_DIRS` in the `env` block of Claude Code's `settings.json`, and runs the checkout's install step. A session that is already running loads it after `/reload-plugins`.
|
|
29
29
|
|
|
30
30
|
- `--yes` approves the mod's install and uninstall commands without asking.
|
|
31
31
|
- `--option key=value` sets one of the mod's options through `claude plugin configure`, once per option. It refuses a key the mod does not declare, and names the keys it does ([options.md](options.md)).
|
|
@@ -36,7 +36,7 @@ Loads the checkout at `path` (default: the current folder) in every new Claude C
|
|
|
36
36
|
cmod unlink [path]
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Stops loading the checkout at `path` (default: the current folder) in new Claude Code sessions. Removes the folder from `CLAUDE_CODE_PLUGIN_DIRS` in Claude Code's `settings.json`, runs the checkout's uninstall step, and removes the program `cmod link` built
|
|
39
|
+
Stops loading the checkout at `path` (default: the current folder) in new Claude Code sessions. Removes the folder from `CLAUDE_CODE_PLUGIN_DIRS` in Claude Code's `settings.json`, runs the checkout's uninstall step, and removes the program `cmod link` built, with its links.
|
|
40
40
|
|
|
41
41
|
### cmod check
|
|
42
42
|
|
|
@@ -153,6 +153,15 @@ cmod remove <name>
|
|
|
153
153
|
|
|
154
154
|
Uninstalls the plugin through Claude Code. When cmod set the plugin up as a mod, it then runs the uninstall step cmod saved and removes what cmod set up.
|
|
155
155
|
|
|
156
|
+
### cmod enable and cmod disable
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
cmod enable <name>
|
|
160
|
+
cmod disable <name>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Turns the plugin on or off through Claude Code, as `/plugin` does. A running session follows after `/reload-plugins`. A mod keeps its setup, its data, and its program while it is off, and runs no install or uninstall step either way. They refuse a plugin linked with `cmod link`, which `cmod unlink` stops loading. The `Turn off` and `Turn on` buttons in `/mods` run them.
|
|
164
|
+
|
|
156
165
|
### cmod list
|
|
157
166
|
|
|
158
167
|
```text
|
|
@@ -183,7 +192,7 @@ cmod runs these itself. A person runs them to see a step's whole log, to finish
|
|
|
183
192
|
cmod setup <plugin-root> [--events] [--consent <sha256>] [--yes]
|
|
184
193
|
```
|
|
185
194
|
|
|
186
|
-
|
|
195
|
+
Refuses a machine the `os` and `cpu` keys of the mod's `package.json` leave out. Downloads the program its `cmod.program` names and links it into cmod's programs folder, which Claude Code runs it from by its name, and into `~/.local/bin` unless a file there is not cmod's. Runs the mod's install step, saves its uninstall step, grants the permissions its `cmod.permissions` lists, and records the mod as set up. A mod installed from a marketplace whose automatic updates the person has not turned on or off gets them turned on, through `"autoUpdate": true` on the marketplace's `extraKnownMarketplaces` entry in Claude Code's `settings.json`, and the consent question says so. The consent covers the scripts, the program, the keys, and the permissions together, and an update asks again only for what changed. An unchanged mod runs nothing. `--events` prints one event per line for a program to read, and the mod's progress line in Claude Code reads them.
|
|
187
196
|
|
|
188
197
|
### cmod teardown
|
|
189
198
|
|
package/docs/install-steps.md
CHANGED
|
@@ -19,7 +19,7 @@ Each key is optional.
|
|
|
19
19
|
|
|
20
20
|
- `install` is one shell command that sets the mod up.
|
|
21
21
|
- `uninstall` is one shell command that undoes it.
|
|
22
|
-
- `program` is the name of a program cmod downloads from the mod's GitHub release
|
|
22
|
+
- `program` is the name of a program cmod downloads from the mod's GitHub release, and Claude Code then runs by its name. [Ship a program](#ship-a-program) explains it.
|
|
23
23
|
- `keys` binds keys in Claude Code to the mod's slash commands. [Key bindings](#key-bindings) explains it.
|
|
24
24
|
|
|
25
25
|
cmod refuses a `cmod` key that breaks these rules, with the fix in the message:
|
|
@@ -49,7 +49,7 @@ Claude Code updates a plugin by itself only when its marketplace has automatic u
|
|
|
49
49
|
|
|
50
50
|
Claude Code downloads an update in the background and loads it at the next launch or `/reload-plugins`. An update whose scripts, keys, or permissions changed waits for consent again, so an update never runs a new install step unasked.
|
|
51
51
|
|
|
52
|
-
In Claude Code, the mod sets itself up through the `cmod`
|
|
52
|
+
In Claude Code, the mod sets itself up through the `cmod` in cmod's programs folder, or else the one PATH finds, and only through one that knows every step it has. A mod with `keys` waits for cmod 0.1.12 or later, because an older cmod skips the keys, and a mod with `permissions` waits for cmod 0.2.0 or later, because an older cmod grants none of them. While PATH finds an older one, the line above the prompt says `PATH finds cmod <version>, and <mod> needs cmod 0.1.12 or later. Run npm i -g @cmodjs/cli, or put ~/.local/bin ahead of the old cmod on PATH.` The mod sets up as soon as a new enough cmod answers, such as the one the cmod plugin installs into `~/.local/bin` at its first start.
|
|
53
53
|
|
|
54
54
|
## Steps in Claude Code
|
|
55
55
|
|
|
@@ -101,7 +101,7 @@ The step gets these environment variables:
|
|
|
101
101
|
| `CMOD_PLUGIN_ROOT` | The mod's folder |
|
|
102
102
|
| `CMOD_DATA` | The mod's data folder, which cmod creates first. It is the folder `mod.dataFolder` names. |
|
|
103
103
|
| `CMOD_VERSION` | The mod's version from `plugin.json` |
|
|
104
|
-
| `PATH` | The person's `PATH`, with the cmod program's own folder first, so the step can run `cmod download
|
|
104
|
+
| `PATH` | The person's `PATH`, with the cmod program's own folder first, so the step can run `cmod download`, then cmod's programs folder |
|
|
105
105
|
|
|
106
106
|
- A line `progress <done> <total> <label>` on standard output moves the progress bar.
|
|
107
107
|
- Every other line goes to the log.
|
|
@@ -160,7 +160,7 @@ cmod deletes `$CMOD_DATA` itself after the uninstall step.
|
|
|
160
160
|
cmod download <program> <machine> <url> <sha256> [<machine> <url> <sha256> …]
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
An install script runs `cmod download` to fetch a program built by someone else. It picks the download for this machine, checks its SHA-256, unpacks a `.tar.gz` or a `.zip` or takes the file as it is, and moves the file named `<program>` to `$CMOD_DATA/bin/<program
|
|
163
|
+
An install script runs `cmod download` to fetch a program built by someone else. It picks the download for this machine, checks its SHA-256, unpacks a `.tar.gz` or a `.zip` or takes the file as it is, and moves the file named `<program>` to `$CMOD_DATA/bin/<program>`, which Claude Code then runs by its name. A download whose SHA-256 differs installs nothing. Machines are `darwin-arm64`, `darwin-x64`, `linux-arm64`, and `linux-x64`.
|
|
164
164
|
|
|
165
165
|
`$CMOD_DATA/bin/<program>` is `${mod.dataFolder}/bin/<program>` in mod code. That folder is not on `PATH`, so mod code names the program by that full path, such as `` mod.process.run([`${mod.dataFolder}/bin/mermaid-ascii`, '--help']) ``, and so does the `command` of a `program` job.
|
|
166
166
|
|
|
@@ -186,16 +186,39 @@ A mod that builds its own command-line program keeps its source in `cli/`, and c
|
|
|
186
186
|
|
|
187
187
|
The program's name is the one `bin` command, or else `name`. A Rust program declares the same keys under `[package.metadata.cmod]` in `cli/Cargo.toml`, and its name is the one `[[bin]]` `name`, or else the `[package]` `name`.
|
|
188
188
|
|
|
189
|
-
2. The build writes one file per machine into `output`, each named `<program>-<os>-<arch>`, such as `hello-darwin-arm64`. cmod runs it with `CMOD_MACHINES` set to the machines it needs, separated by spaces: `cmod link` names this machine alone, and `cmod publish` names
|
|
189
|
+
2. The build writes one file per machine into `output`, each named `<program>-<os>-<arch>`, such as `hello-darwin-arm64`. cmod runs it with `CMOD_MACHINES` set to the machines it needs, separated by spaces: `cmod link` names this machine alone, and `cmod publish` names every machine the mod runs on ([Machines](#machines)), and fails when the build skips one. A build that ignores `CMOD_MACHINES` and builds every machine works too, only slower to link. cmod takes only the files the build wrote in that run, so a build left from an earlier run is never released.
|
|
190
190
|
|
|
191
191
|
3. The mod's own `package.json` names the program: `"cmod": { "program": "hello" }`. `.claude-plugin/plugin.json` names the GitHub `repository`.
|
|
192
192
|
|
|
193
193
|
Then:
|
|
194
194
|
|
|
195
|
-
- `cmod link` builds the program and links this machine's build
|
|
195
|
+
- `cmod link` builds the program and links this machine's build into cmod's programs folder and `~/.local/bin`.
|
|
196
196
|
- `cmod publish` builds every machine's file and attaches each to the GitHub release `v<version>`, with a `SHA256SUMS` file. It refuses a `cli/` program that the `package.json` `program` key does not name.
|
|
197
|
-
- On install, cmod downloads `<repository>/releases/download/v<version>/<program>-<machine>`, checks it against `SHA256SUMS`, runs `<program> --version`, and links it
|
|
198
|
-
- cmod
|
|
199
|
-
|
|
197
|
+
- On install, cmod downloads `<repository>/releases/download/v<version>/<program>-<machine>`, checks it against `SHA256SUMS`, runs `<program> --version`, and links it into cmod's programs folder and `~/.local/bin`.
|
|
198
|
+
- `cmod unlink` and the uninstall remove the program and both links.
|
|
199
|
+
|
|
200
|
+
## Run a program by its name
|
|
201
|
+
|
|
202
|
+
cmod links every program it installs into its programs folder, `~/.local/share/cmod/programs/<program>` (under `$XDG_DATA_HOME` when it is set): the program `cmod.program` names, each program `cmod download` installs, and `cmod` itself. Claude Code runs the one there by its name, whatever else `PATH` holds, so a mod's `trace` wins over `/usr/bin/trace`:
|
|
203
|
+
|
|
204
|
+
- The cmod plugin's `SessionStart` hook puts the programs folder first on `PATH` for every Bash command Claude runs.
|
|
205
|
+
- `mod.process.run` and `mod.process.spawn` run `[name, ...args]` from the programs folder when it holds `name`. A path such as `/usr/bin/trace` runs as given.
|
|
206
|
+
- Install and uninstall steps find the programs folder on `PATH` after cmod's own `bun`.
|
|
207
|
+
|
|
208
|
+
cmod also links the program into `~/.local/bin/<program>` so a terminal runs it, unless a file there is not cmod's. Setup then logs which program a terminal runs, and changes nothing outside cmod's folders.
|
|
209
|
+
|
|
210
|
+
## Machines
|
|
211
|
+
|
|
212
|
+
A mod runs on macOS and Linux, on arm64 and x64, unless the `os` and `cpu` keys of its `package.json` say less. They read as npm reads them, and a `!` in front leaves a value out:
|
|
213
|
+
|
|
214
|
+
```json
|
|
215
|
+
{ "name": "safe-delete", "os": ["darwin"], "cpu": ["arm64"] }
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- Setup refuses a machine the mod leaves out before any script runs, with `safe-delete runs on macOS on arm64, and this is Linux, so cmod set up nothing.`, in the terminal and in the mod's progress line in Claude Code.
|
|
219
|
+
- `cmod publish` builds the program for each machine the mod runs on.
|
|
220
|
+
- `cmod check` fails when a `cmod download` line in the install step has no download for one of those machines, and when `os` or `cpu` names a value cmod does not run mods on, such as `win32`.
|
|
221
|
+
|
|
222
|
+
Claude Code itself has no field for this, and installs the mod on any machine.
|
|
200
223
|
|
|
201
224
|
The cmod repository ships the `cmod` program this way. Its `package.json` holds `"cmod": { "program": "cmod" }`, and its `cli/package.json` holds `"cmod": { "build": "bun run build", "output": "dist" }`.
|
package/docs/mod.md
CHANGED
|
@@ -173,7 +173,7 @@ mod.process.run(argv: readonly string[], init?: ProcessRunInit): Promise<Process
|
|
|
173
173
|
mod.process.spawn(argv: readonly string[], init?: { cwd?, env?, input? }): HookStream<ProcessSpawnChunk, ProcessSpawnResult>
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
Both run a program without a shell. `argv[0]` is the program and the rest are its arguments.
|
|
176
|
+
Both run a program without a shell. `argv[0]` is the program and the rest are its arguments. A bare name that cmod installed, such as `trace`, runs from cmod's programs folder ([install-steps.md](install-steps.md#run-a-program-by-its-name)), and any other name runs through Claude Code's `PATH`.
|
|
177
177
|
|
|
178
178
|
- `init.cwd` is the program's folder, absolute or relative to the session's working folder. Without it the program runs in the session's working folder.
|
|
179
179
|
- `init.env` sets variables over Claude Code's own environment.
|
|
@@ -294,7 +294,7 @@ mod.settings.page(page: Pane<State>): void
|
|
|
294
294
|
mod.settings.open(pageId?: string): Promise<void>
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
-
`/mods` shows each mod's options, permissions, and keys. `page` adds a page of the mod's own to its entry there, drawn as any pane is ([ui.md](ui.md)). `open` opens that page, or with no id opens the mod's entry in `/mods`. `open` rejects for an id `page` did not add.
|
|
297
|
+
`/mods` shows each mod's description, options, permissions, and keys, and turns a mod off or removes it. `page` adds a page of the mod's own to its entry there, drawn as any pane is ([ui.md](ui.md)). `open` opens that page, or with no id opens the mod's entry in `/mods`. `open` rejects for an id `page` did not add.
|
|
298
298
|
|
|
299
299
|
```tsx
|
|
300
300
|
mod.settings.page(definePane({ id: 'runs', title: 'Runs', render: (current) => <Text>{current.state.session.lastRun}</Text> }))
|
package/docs/permissions.md
CHANGED
|
@@ -17,7 +17,7 @@ They sit in the `package.json` `cmod` key, beside `install` and `keys`, because
|
|
|
17
17
|
| Permission | The person reads | cmod checks |
|
|
18
18
|
| --- | --- | --- |
|
|
19
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` |
|
|
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` and the copy in cmod's programs folder |
|
|
21
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
22
|
| `"conversation": true` | Read this conversation | `mod.session.messages`, and reading Claude Code's transcript files |
|
|
23
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 |
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/runtime/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAA0B,aAAa,EAAyC,MAAM,aAAa,CAAA;AAC1I,OAAO,KAAK,EAAa,GAAG,EAAE,aAAa,EAAiC,MAAM,WAAW,CAAA;AAC7F,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAgS,KAAK,KAAK,EAAE,MAAM,eAAe,CAAA;AAGxU,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,EAAsG,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../src/runtime/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAA0B,aAAa,EAAyC,MAAM,aAAa,CAAA;AAC1I,OAAO,KAAK,EAAa,GAAG,EAAE,aAAa,EAAiC,MAAM,WAAW,CAAA;AAC7F,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,eAAe,CAAA;AAC1D,OAAO,EAAgS,KAAK,KAAK,EAAE,MAAM,eAAe,CAAA;AAGxU,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC,OAAO,EAAsG,KAAK,WAAW,EAAE,MAAM,YAAY,CAAA;AAOjJ,OAAO,EAA6B,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAKxE,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,KAAK,GAAG,UAAU,GAAG,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;AAEtG,MAAM,MAAM,SAAS,CAAC,KAAK,SAAS,MAAM,IAAI;IAC5C,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/E,KAAK,CAAC,CAAC,SAAS,WAAW,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAA;CACzG,CAAA;AAqCD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA2BhE;AAkCD,wBAAgB,eAAe,CAAC,KAAK,SAAS,MAAM,EAAE,QAAQ,SAAS,OAAO,GAAG,OAAO,EACtF,UAAU,EAAE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAClD,iBAAiB,GAAE,MAAM,OAAoB,EAC7C,OAAO,GAAE,aAAkB,EAC3B,UAAU,UAAO,GAChB,SAAS,CAAC,KAAK,CAAC,CA8VlB"}
|
package/runtime/lifecycle.js
CHANGED
|
@@ -5,6 +5,7 @@ import { beforeDeadline } from './deadline.js';
|
|
|
5
5
|
import { answerCall, dependencyCalls, notInstalled } from './dependencies.js';
|
|
6
6
|
import { classicHook, heldDecisionHook, permissionEvents, preToolUseHook, userSkillHook } from './hooks.js';
|
|
7
7
|
import { checkedAnswer, checkingGrants, checksAnswers, checkWrite, isCovered, itemOf, passedDown } from './grants.js';
|
|
8
|
+
import { locatingPrograms } from './programs.js';
|
|
8
9
|
import { createModFiles } from './metadata.js';
|
|
9
10
|
import { fitsOption } from '../options.js';
|
|
10
11
|
import { createInstaller } from './installer.js';
|
|
@@ -90,6 +91,7 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
90
91
|
let router = createRouter();
|
|
91
92
|
let phase = 'starting';
|
|
92
93
|
let runtime;
|
|
94
|
+
let programs;
|
|
93
95
|
let plugin;
|
|
94
96
|
const granted = new Set();
|
|
95
97
|
let line;
|
|
@@ -121,7 +123,8 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
121
123
|
failure ??= new Error(`${definition.name}: ${reason}${/[.!?]$/.test(reason) ? '' : '.'} ${fix}`);
|
|
122
124
|
showLine().fail(reason, fix);
|
|
123
125
|
};
|
|
124
|
-
const finish = () => {
|
|
126
|
+
const finish = async () => {
|
|
127
|
+
await programs?.refresh();
|
|
125
128
|
phase = 'ready';
|
|
126
129
|
claude().ui.invalidate('ui.render');
|
|
127
130
|
};
|
|
@@ -388,8 +391,10 @@ export function createLifecycle(definition, checksPermissions = () => true, opti
|
|
|
388
391
|
async start(claudeCalls, read) {
|
|
389
392
|
if (runtime !== undefined)
|
|
390
393
|
return;
|
|
391
|
-
|
|
394
|
+
programs = locatingPrograms(claudeCalls);
|
|
395
|
+
runtime = { claude: programs.claude, progress: createProgress(claudeCalls) };
|
|
392
396
|
try {
|
|
397
|
+
await programs.refresh();
|
|
393
398
|
const started = await read(claudeCalls).catch((error) => {
|
|
394
399
|
report(error);
|
|
395
400
|
return undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"programs.d.ts","sourceRoot":"","sources":["../src/runtime/programs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,QAAQ,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAA;AAE5E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAsBzD"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { storeFolder } from '../records.js';
|
|
2
|
+
export function locatingPrograms(claude) {
|
|
3
|
+
let folder;
|
|
4
|
+
let names = new Set();
|
|
5
|
+
const located = (argv) => {
|
|
6
|
+
const [name, ...rest] = argv;
|
|
7
|
+
return folder !== undefined && name !== undefined && names.has(name) ? [`${folder}/${name}`, ...rest] : argv;
|
|
8
|
+
};
|
|
9
|
+
return {
|
|
10
|
+
claude: {
|
|
11
|
+
...claude,
|
|
12
|
+
process: {
|
|
13
|
+
run: (argv, init) => claude.process.run(located(argv), init),
|
|
14
|
+
spawn: (request) => claude.process.spawn({ ...request, argv: located(request.argv) }),
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
async refresh() {
|
|
18
|
+
const [home, dataHome] = await Promise.all([claude.env.home(), claude.env.dataHome()]);
|
|
19
|
+
const programs = `${storeFolder({ HOME: home, XDG_DATA_HOME: dataHome })}/programs`;
|
|
20
|
+
names = new Set((await claude.fs.list(programs).catch(() => [])).map((entry) => entry.name));
|
|
21
|
+
folder = programs;
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
package/src/runtime/lifecycle.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { beforeDeadline } from './deadline.js'
|
|
|
9
9
|
import { answerCall, dependencyCalls, notInstalled } from './dependencies.js'
|
|
10
10
|
import { classicHook, heldDecisionHook, permissionEvents, preToolUseHook, userSkillHook, type HeldDecisions, type RoutedEvent } from './hooks.js'
|
|
11
11
|
import { checkedAnswer, checkingGrants, checksAnswers, checkWrite, isCovered, itemOf, passedDown, type AnswerCheck, type Grants } from './grants.js'
|
|
12
|
+
import { locatingPrograms, type Programs } from './programs.js'
|
|
12
13
|
import { createModFiles } from './metadata.js'
|
|
13
14
|
import { fitsOption } from '../options.js'
|
|
14
15
|
import { createInstaller } from './installer.js'
|
|
@@ -144,6 +145,7 @@ export function createLifecycle<State extends object, Declared extends Options =
|
|
|
144
145
|
let router: Router = createRouter()
|
|
145
146
|
let phase: Phase = 'starting'
|
|
146
147
|
let runtime: Pick<ModRuntime, 'claude' | 'progress'> | undefined
|
|
148
|
+
let programs: Programs | undefined
|
|
147
149
|
let plugin: Plugin | undefined
|
|
148
150
|
const granted = new Set<string>()
|
|
149
151
|
let line: ProgressLine | undefined
|
|
@@ -179,7 +181,8 @@ export function createLifecycle<State extends object, Declared extends Options =
|
|
|
179
181
|
showLine().fail(reason, fix)
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
const finish = () => {
|
|
184
|
+
const finish = async () => {
|
|
185
|
+
await programs?.refresh()
|
|
183
186
|
phase = 'ready'
|
|
184
187
|
claude().ui.invalidate('ui.render')
|
|
185
188
|
}
|
|
@@ -437,8 +440,10 @@ export function createLifecycle<State extends object, Declared extends Options =
|
|
|
437
440
|
},
|
|
438
441
|
async start(claudeCalls, read) {
|
|
439
442
|
if (runtime !== undefined) return
|
|
440
|
-
|
|
443
|
+
programs = locatingPrograms(claudeCalls)
|
|
444
|
+
runtime = { claude: programs.claude, progress: createProgress(claudeCalls) }
|
|
441
445
|
try {
|
|
446
|
+
await programs.refresh()
|
|
442
447
|
const started = await read(claudeCalls).catch((error: unknown) => {
|
|
443
448
|
report(error)
|
|
444
449
|
return undefined
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { storeFolder } from '../records.js'
|
|
2
|
+
import type { Claude } from './claude.js'
|
|
3
|
+
|
|
4
|
+
export type Programs = { readonly claude: Claude; refresh(): Promise<void> }
|
|
5
|
+
|
|
6
|
+
export function locatingPrograms(claude: Claude): Programs {
|
|
7
|
+
let folder: string | undefined
|
|
8
|
+
let names: ReadonlySet<string> = new Set()
|
|
9
|
+
const located = (argv: readonly string[]): readonly string[] => {
|
|
10
|
+
const [name, ...rest] = argv
|
|
11
|
+
return folder !== undefined && name !== undefined && names.has(name) ? [`${folder}/${name}`, ...rest] : argv
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
claude: {
|
|
15
|
+
...claude,
|
|
16
|
+
process: {
|
|
17
|
+
run: (argv, init) => claude.process.run(located(argv), init),
|
|
18
|
+
spawn: (request) => claude.process.spawn({ ...request, argv: located(request.argv) }),
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
async refresh() {
|
|
22
|
+
const [home, dataHome] = await Promise.all([claude.env.home(), claude.env.dataHome()])
|
|
23
|
+
const programs = `${storeFolder({ HOME: home, XDG_DATA_HOME: dataHome })}/programs`
|
|
24
|
+
names = new Set((await claude.fs.list(programs).catch(() => [])).map((entry) => entry.name))
|
|
25
|
+
folder = programs
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
}
|