@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 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 into `~/.local/bin`.
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
- Downloads the program the mod's `package.json` `cmod.program` names into `~/.local/bin`, 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.
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
 
@@ -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 into `~/.local/bin`. [Ship a program](#ship-a-program) explains it.
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` that 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.
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>`. A download whose SHA-256 differs installs nothing. Machines are `darwin-arm64`, `darwin-x64`, `linux-arm64`, and `linux-x64`.
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 all four. 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.
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 to `~/.local/bin/<program>`.
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 to `~/.local/bin/<program>`.
198
- - cmod refuses to replace a `~/.local/bin/<program>` it did not make, and a program `PATH` finds in a folder ahead of `~/.local/bin`.
199
- - `cmod unlink` and the uninstall remove the program.
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> }))
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmodjs/core",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Build Claude Code mods: hooks, panes, permissions, checks, and an install that just works.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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;AAMjJ,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,CA0VlB"}
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"}
@@ -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
- runtime = { claude: claudeCalls, progress: createProgress(claudeCalls) };
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,7 @@
1
+ import type { Claude } from './claude.js';
2
+ export type Programs = {
3
+ readonly claude: Claude;
4
+ refresh(): Promise<void>;
5
+ };
6
+ export declare function locatingPrograms(claude: Claude): Programs;
7
+ //# sourceMappingURL=programs.d.ts.map
@@ -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
+ }
@@ -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
- runtime = { claude: claudeCalls, progress: createProgress(claudeCalls) }
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
+ }