@cmodjs/core 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/commands.md +38 -21
- package/docs/dependencies.md +27 -20
- package/docs/index.md +21 -17
- package/docs/install-steps.md +37 -29
- package/docs/jobs.md +25 -52
- package/docs/mod.md +93 -65
- package/docs/options.md +2 -2
- package/docs/permissions.md +4 -4
- package/docs/session.md +14 -12
- package/docs/testing.md +55 -32
- package/docs/ui.md +255 -263
- package/hotkeys.d.ts +22 -0
- package/hotkeys.d.ts.map +1 -0
- package/hotkeys.js +26 -0
- package/installer.d.ts +1 -1
- package/installer.d.ts.map +1 -1
- package/installer.js +11 -6
- package/jobs/check.d.ts +2 -1
- package/jobs/check.d.ts.map +1 -1
- package/jobs/check.js +2 -2
- package/jobs/context.d.ts +2 -1
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +1 -1
- package/jobs/permissions.d.ts +2 -1
- package/jobs/permissions.d.ts.map +1 -1
- package/jobs/permissions.js +2 -2
- package/jobs/program.js +1 -1
- package/jobs/prompt.d.ts +2 -1
- package/jobs/prompt.d.ts.map +1 -1
- package/jobs/prompt.js +20 -13
- package/jobs/schedule.d.ts +2 -1
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +15 -1
- package/jobs/slash-command.d.ts +6 -2
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +6 -5
- package/jobs/tool.d.ts +6 -4
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +3 -3
- package/mod.d.ts +63 -42
- package/mod.d.ts.map +1 -1
- package/mod.js +11 -1
- package/package.json +1 -1
- package/records.d.ts +13 -8
- package/records.d.ts.map +1 -1
- package/records.js +41 -31
- package/register.d.ts.map +1 -1
- package/register.js +1 -2
- package/runtime/announce.d.ts +1 -1
- package/runtime/announce.d.ts.map +1 -1
- package/runtime/announce.js +6 -7
- package/runtime/before.d.ts +2 -2
- package/runtime/before.d.ts.map +1 -1
- package/runtime/before.js +1 -1
- package/runtime/claude.d.ts +0 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/conversations.d.ts +4 -1
- package/runtime/conversations.d.ts.map +1 -1
- package/runtime/conversations.js +42 -14
- package/runtime/dependencies.d.ts.map +1 -1
- package/runtime/dependencies.js +2 -1
- package/runtime/engine.d.ts +5 -0
- package/runtime/engine.d.ts.map +1 -1
- package/runtime/grants.js +2 -2
- package/runtime/hooks.d.ts +1 -1
- package/runtime/hooks.d.ts.map +1 -1
- package/runtime/hotkeys.d.ts +15 -0
- package/runtime/hotkeys.d.ts.map +1 -0
- package/runtime/hotkeys.js +58 -0
- package/runtime/installer.d.ts +5 -2
- package/runtime/installer.d.ts.map +1 -1
- package/runtime/installer.js +23 -12
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +190 -141
- package/runtime/options.js +2 -2
- package/runtime/pages.d.ts +28 -0
- package/runtime/pages.d.ts.map +1 -0
- package/runtime/pages.js +98 -0
- package/runtime/parts.d.ts +14 -0
- package/runtime/parts.d.ts.map +1 -0
- package/runtime/parts.js +63 -0
- package/runtime/permission-check.d.ts +2 -2
- package/runtime/permission-check.d.ts.map +1 -1
- package/runtime/programs.js +1 -1
- package/runtime/router.d.ts +2 -2
- package/runtime/router.d.ts.map +1 -1
- package/runtime/router.js +1 -1
- package/runtime/sessions.d.ts +10 -9
- package/runtime/sessions.d.ts.map +1 -1
- package/runtime/sessions.js +14 -5
- package/runtime/state.d.ts +2 -1
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +1 -0
- package/runtime/toaster.d.ts +1 -1
- package/runtime/toaster.d.ts.map +1 -1
- package/runtime/toaster.js +10 -4
- package/runtime/ui.d.ts +87 -5
- package/runtime/ui.d.ts.map +1 -1
- package/runtime/ui.js +372 -109
- package/runtime/watches.d.ts +9 -0
- package/runtime/watches.d.ts.map +1 -0
- package/runtime/watches.js +73 -0
- package/runtime/widgets.d.ts +48 -0
- package/runtime/widgets.d.ts.map +1 -0
- package/runtime/widgets.js +219 -0
- package/session/context/turn/message/message.d.ts.map +1 -1
- package/session/context/turn/message/message.js +7 -2
- package/session/session.d.ts +11 -5
- package/session/session.d.ts.map +1 -1
- package/src/hotkeys.ts +51 -0
- package/src/installer.ts +8 -4
- package/src/jobs/check.ts +4 -3
- package/src/jobs/context.ts +3 -2
- package/src/jobs/permissions.ts +4 -3
- package/src/jobs/program.ts +1 -1
- package/src/jobs/prompt.ts +23 -14
- package/src/jobs/schedule.ts +16 -2
- package/src/jobs/slash-command.ts +10 -7
- package/src/jobs/tool.ts +7 -5
- package/src/mod.ts +59 -47
- package/src/records.ts +46 -31
- package/src/register.ts +2 -3
- package/src/runtime/announce.ts +6 -7
- package/src/runtime/before.ts +4 -4
- package/src/runtime/claude.ts +0 -1
- package/src/runtime/conversations.ts +66 -20
- package/src/runtime/dependencies.ts +2 -1
- package/src/runtime/engine.ts +5 -0
- package/src/runtime/grants.ts +2 -2
- package/src/runtime/hooks.ts +1 -0
- package/src/runtime/hotkeys.ts +80 -0
- package/src/runtime/installer.ts +24 -12
- package/src/runtime/lifecycle.ts +189 -136
- package/src/runtime/options.ts +2 -2
- package/src/runtime/pages.ts +127 -0
- package/src/runtime/parts.ts +72 -0
- package/src/runtime/permission-check.ts +2 -2
- package/src/runtime/programs.ts +1 -1
- package/src/runtime/router.ts +3 -3
- package/src/runtime/sessions.ts +24 -15
- package/src/runtime/state.ts +3 -1
- package/src/runtime/toaster.ts +10 -4
- package/src/runtime/ui.ts +405 -115
- package/src/runtime/watches.ts +75 -0
- package/src/runtime/widgets.ts +287 -0
- package/src/session/context/turn/message/message.ts +7 -2
- package/src/session/session.ts +10 -6
- package/src/testing/fake-claude.ts +9 -6
- package/src/testing/fake-elements.ts +7 -4
- package/src/testing/fake-sessions.ts +6 -4
- package/src/testing.ts +203 -46
- package/src/ui/areas.ts +27 -0
- package/src/ui/elements.ts +35 -0
- package/src/ui/markdown.ts +18 -18
- package/src/ui/panes.ts +73 -0
- package/src/ui/styles.ts +53 -0
- package/src/utils/subscription.ts +1 -0
- package/testing/fake-claude.d.ts +0 -1
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +9 -5
- package/testing/fake-elements.js +7 -4
- package/testing/fake-sessions.d.ts +2 -1
- package/testing/fake-sessions.d.ts.map +1 -1
- package/testing/fake-sessions.js +1 -0
- package/testing.d.ts +21 -6
- package/testing.d.ts.map +1 -1
- package/testing.js +206 -45
- package/ui/areas.d.ts +29 -0
- package/ui/areas.d.ts.map +1 -0
- package/ui/areas.js +6 -0
- package/ui/elements.d.ts +3 -0
- package/ui/elements.d.ts.map +1 -1
- package/ui/elements.js +36 -0
- package/ui/markdown.d.ts +14 -14
- package/ui/markdown.d.ts.map +1 -1
- package/ui/markdown.js +11 -11
- package/ui/panes.d.ts +71 -0
- package/ui/panes.d.ts.map +1 -0
- package/ui/panes.js +16 -0
- package/ui/styles.d.ts +44 -0
- package/ui/styles.d.ts.map +1 -0
- package/ui/styles.js +24 -0
- package/utils/subscription.d.ts +4 -0
- package/utils/subscription.d.ts.map +1 -0
- package/jobs/status-line.d.ts +0 -6
- package/jobs/status-line.d.ts.map +0 -1
- package/jobs/status-line.js +0 -39
- package/src/jobs/status-line.ts +0 -44
- package/src/ui/define-pane.ts +0 -27
- package/src/ui/places.ts +0 -49
- package/src/utils/disposable.ts +0 -3
- package/ui/define-pane.d.ts +0 -15
- package/ui/define-pane.d.ts.map +0 -1
- package/ui/define-pane.js +0 -12
- package/ui/places.d.ts +0 -43
- package/ui/places.d.ts.map +0 -1
- package/ui/places.js +0 -31
- package/utils/disposable.d.ts +0 -4
- package/utils/disposable.d.ts.map +0 -1
- /package/utils/{disposable.js → subscription.js} +0 -0
package/docs/commands.md
CHANGED
|
@@ -12,7 +12,7 @@ Each command below restates what `cmod <command> --help` prints, for a mod autho
|
|
|
12
12
|
cmod new <name> [--project]
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Creates a mod in `./<name>`: a `defineMod` with one listener,
|
|
15
|
+
Creates a mod in `./<name>`: a `defineMod` with one listener, a Sidebar page, a widget, and a hotkey that shows the page, a Skill, a test, and a `CHANGELOG.md` ([mod.md](mod.md#release-notes)). Then installs its packages.
|
|
16
16
|
|
|
17
17
|
- `<name>` is lowercase letters, digits, and `-`, starting with a letter.
|
|
18
18
|
- `--project` creates a project-scope plugin in `./.claude/skills/<name>/` of the repository in the current folder, which loads for everyone who trusts the repository. Run it in the repository's root.
|
|
@@ -25,7 +25,7 @@ Creates a mod in `./<name>`: a `defineMod` with one listener, one pane, and one
|
|
|
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 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.
|
|
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. Claude Code reads that list only when a session starts, so a session that is already running loads the checkout once the person starts a new one. From then on, Claude Code reloads the checkout in each session when a file in it is saved.
|
|
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, under the id `claude plugin list` shows for the linked checkout, such as `my-mod@inline`. It refuses a key the mod does not declare, and names the keys it does ([options.md](options.md)).
|
|
@@ -48,17 +48,21 @@ Runs every check this machine can run on the mod at `path` (default: the current
|
|
|
48
48
|
|
|
49
49
|
1. Installs its packages.
|
|
50
50
|
2. Checks its layout.
|
|
51
|
-
3. Checks that each step runs a script
|
|
52
|
-
4.
|
|
53
|
-
5.
|
|
54
|
-
6.
|
|
55
|
-
7.
|
|
56
|
-
8.
|
|
57
|
-
9.
|
|
58
|
-
10.
|
|
59
|
-
11.
|
|
60
|
-
12.
|
|
61
|
-
13.
|
|
51
|
+
3. Checks that each step runs a script.
|
|
52
|
+
4. Checks that `package.json` `"os"` and `"cpu"` list only machines cmod runs mods on, darwin and linux on arm64 and x64, and that the install step downloads a program for each machine they list.
|
|
53
|
+
5. Prints the permissions the person grants at install and can grant later, as the person reads them ([permissions.md](permissions.md)). It adds `"approve": true` when the mod allows or asks on the permission check and `hooks/register.ts` calls `registerPermissionCheck`. It fails a mod whose `src/` hears permissions while `register.ts` does not call `registerPermissionCheck`.
|
|
54
|
+
6. Reads the metadata of every file in the mod, and names each file whose metadata cmod cannot read.
|
|
55
|
+
7. Writes the options `defineMod` declares into `.claude-plugin/plugin.json` `userConfig`, and its hotkeys into `package.json` `"cmod"` `hotkeys`, keeping each file's own formatting, so Claude Code shows the options in `/config` ([options.md](options.md)). It loads `hooks/register.ts` to read them, without starting the mod.
|
|
56
|
+
8. Writes the `cmod` dependency in `.claude-plugin/plugin.json` as `>=` the version of the `@cmodjs/core` the mod installed, such as `>=4.0.0`, so every newer cmod runs the mod. A mod still on `@cmodjs/core` 3 keeps its own range ([dependencies.md](dependencies.md#call-the-methods-mod-dependencies)).
|
|
57
|
+
9. Checks its imports.
|
|
58
|
+
10. Looks for prebuilt binaries outside `cli/`.
|
|
59
|
+
11. Bundles the hooks module as `cmod publish` does, and names each reason a bundle fails.
|
|
60
|
+
12. Validates it with Claude Code, `claude plugin validate --strict`.
|
|
61
|
+
13. Compiles it against Claude Code's API with `tsc`, with `tsconfig.json`, and then with `tests/tsconfig.json` when it exists. `cmod new` writes both: the root config leaves Bun out of `src/`, which runs inside Claude Code, and the tests config adds `bun-types`, so a test can run a real program with `Bun.spawn` or read files with `node:fs`. When `.claude-plugin/types/` is missing, as in a fresh clone or in CI, Claude Code writes it first: cmod loads the mod in one `claude -p` run with a config folder of its own and no model to reach, so the run sends nothing.
|
|
62
|
+
14. Lints it with `oxlint`.
|
|
63
|
+
15. Runs, with `bun test`, every test file that does not import `claude-code/testing`. When `bunfig.toml` names a `[test]` `root`, it runs only the test files under that folder, as `bun test` does.
|
|
64
|
+
16. Runs, with `claude plugin test`, the test files that import `claude-code/testing`. It skips this when none does.
|
|
65
|
+
17. Checks its name against other plugins. Only a project plugin, in `.claude/skills/<name>/` of a repository, runs this check: it fails when an installed or linked plugin has the same name and hides the project plugin. Any other mod passes it at once.
|
|
62
66
|
|
|
63
67
|
`cmod check` fetches `tsc` 7.0.2 and `oxlint` 1.86.0 into the cmod store the first time. It ends with a count of passed, failed, and skipped checks, and exits 1 when one failed.
|
|
64
68
|
|
|
@@ -114,12 +118,15 @@ It needs, and refuses to start without:
|
|
|
114
118
|
- a `package.json` with no dependency on a `file:` or `link:` path, which no user has
|
|
115
119
|
- a `"files"` list, when `package.json` has one, whose every path matches a committed file
|
|
116
120
|
- a `"program"` in the `cmod` key of `package.json` that names the program `cli/` declares, when `cli/` declares one
|
|
121
|
+
- a `cmod` dependency in `.claude-plugin/plugin.json` of `>=` the `@cmodjs/core` the mod bundles, which `cmod check` writes
|
|
117
122
|
- for a real publish, a working tree with no uncommitted changes, no `v<version>` or `<name>--v<version>` tag yet, a `git push` to `origin` that succeeds, and the `gh` command, which creates the GitHub release
|
|
118
123
|
|
|
119
124
|
`--dry-run` builds everything into a temporary folder, and pushes nothing. It writes `marketplace.json` there too, so the working tree stays as it was. It allows uncommitted changes and an existing tag, and builds from the last commit. It prints the folder that holds the release, so you can read what the directory will read.
|
|
120
125
|
|
|
121
126
|
## Install mods
|
|
122
127
|
|
|
128
|
+
`cmod install`, `cmod update`, `cmod remove`, `cmod enable`, `cmod disable`, `cmod link`, `cmod unlink`, and `cmod try` write the time to `plugins-changed` in the cmod store when they finish, also when they fail partway, such as an update that updated some mods. The cmod plugin of every running session watches that file and runs `/reload-plugins`, so each session runs the mods the person has now. A reload clears each mod's `memory` state. A machine where cmod set up no mod has no store, and these commands write nothing there. `cmod permission` needs no reload, because a mod sees its grants change before its next call.
|
|
129
|
+
|
|
123
130
|
### cmod install
|
|
124
131
|
|
|
125
132
|
```text
|
|
@@ -142,7 +149,7 @@ Adds the plugin's marketplace to Claude Code and installs the plugin through Cla
|
|
|
142
149
|
cmod update [name] [--yes]
|
|
143
150
|
```
|
|
144
151
|
|
|
145
|
-
Updates the named plugin, or every plugin Claude Code has installed, through Claude Code, then runs the install step of each mod whose version or scripts changed. A linked plugin is skipped, because saving its files updates it. When Claude Code fails to update a plugin, `cmod update` names it, updates the rest, and exits 1.
|
|
152
|
+
Updates the named plugin, or every plugin Claude Code has installed, through Claude Code, then runs the install step of each mod whose version or scripts changed. A linked plugin is skipped, because saving its files updates it. When Claude Code fails to update a plugin, `cmod update` names it, updates the rest, and exits 1. Every running session then loads the new version.
|
|
146
153
|
|
|
147
154
|
- `--yes` approves changed install and uninstall commands without asking.
|
|
148
155
|
|
|
@@ -161,15 +168,17 @@ cmod enable <name>
|
|
|
161
168
|
cmod disable <name>
|
|
162
169
|
```
|
|
163
170
|
|
|
164
|
-
Turns the plugin on or off through Claude Code, as `/plugin` does
|
|
171
|
+
Turns the plugin on or off through Claude Code, as `/plugin` does, and every running session follows. 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 Settings run them.
|
|
165
172
|
|
|
166
173
|
### cmod list
|
|
167
174
|
|
|
168
175
|
```text
|
|
169
|
-
cmod list
|
|
176
|
+
cmod list [--json]
|
|
170
177
|
```
|
|
171
178
|
|
|
172
|
-
Lists every plugin Claude Code has installed or linked, with its version. A mod shows whether cmod set it up, and any other plugin shows whether Claude Code enabled it. It also lists every mod Claude Code removed whose uninstall step has not run yet.
|
|
179
|
+
Lists every plugin Claude Code has installed or linked, with its version. A mod shows whether cmod set it up, and any other plugin shows whether Claude Code enabled it. A plugin Claude Code turned off shows Claude Code's reason, such as `Requires "cmod@market" >=4.2.0, installed 4.0.0`. It also lists every mod Claude Code removed whose uninstall step has not run yet.
|
|
180
|
+
|
|
181
|
+
- `--json` prints the list as JSON: one object per row with its `name`, `version`, `state`, and `unsatisfied`, the dependencies Claude Code turned the plugin off for because they are older than the plugin needs.
|
|
173
182
|
|
|
174
183
|
- The `cmod` commands count a plugin as a mod only when its `package.json` has a `cmod` step key or cmod holds an install record for it. A mod made with `cmod new` has neither until its first Claude Code session sets it up, so until then `cmod list` shows it as `enabled`, like any other plugin.
|
|
175
184
|
|
|
@@ -205,7 +214,7 @@ Reads every conversation Claude Code saved, as the sessions, contexts, turns, ta
|
|
|
205
214
|
- `state` prints what a running conversation resumes from.
|
|
206
215
|
- `follow` prints each message, turn, context, task, and goal change of the sessions named as Claude Code saves it, until stopped with Ctrl+C. `--running` follows every running session of the project too, and each one that starts later. The cmod plugin runs it to hear other running sessions.
|
|
207
216
|
- `--json` prints JSON instead of one line each.
|
|
208
|
-
- `index` reads every saved conversation into the index at `~/.local/share/cmod/history-
|
|
217
|
+
- `index` reads every saved conversation into the index at `~/.local/share/cmod/history-8.sqlite`, which holds where each message is in its transcript, not a copy of it, and each conversation's turns, contexts, and tasks. While a mod other than cmod holds the `conversation` permission, the cmod plugin runs it in the background at each session's start, and a second run while one is running exits at once. The index forgets a conversation whose transcript is deleted.
|
|
209
218
|
|
|
210
219
|
## Run in an install script
|
|
211
220
|
|
|
@@ -227,7 +236,7 @@ cmod runs these itself. A person runs them to see a step's whole log, to finish
|
|
|
227
236
|
cmod setup <plugin-root> [--events] [--consent <sha256>] [--yes]
|
|
228
237
|
```
|
|
229
238
|
|
|
230
|
-
Refuses a machine the `os` and `cpu` keys of the mod's `package.json` leave out. Downloads the program its `cmod.program` names, or builds it from the checkout's `cli/` when `cmod link` loaded the mod, so a linked checkout whose version has no release yet still sets up, 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
|
|
239
|
+
Refuses a machine the `os` and `cpu` keys of the mod's `package.json` leave out. Downloads the program its `cmod.program` names, or builds it from the checkout's `cli/` when `cmod link` loaded the mod, so a linked checkout whose version has no release yet still sets up, 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 hotkeys, 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 install toast in Claude Code reads them.
|
|
231
240
|
|
|
232
241
|
### cmod teardown
|
|
233
242
|
|
|
@@ -243,7 +252,15 @@ Runs a removed mod's saved uninstall step, then deletes its install record, the
|
|
|
243
252
|
cmod permission <mod> <name> [value] on|off
|
|
244
253
|
```
|
|
245
254
|
|
|
246
|
-
Turns one permission of a set-up mod on or off, such as `cmod permission ci-watch model off` or `cmod permission ci-watch network api.github.com on`. A toggle in
|
|
255
|
+
Turns one permission of a set-up mod on or off, such as `cmod permission ci-watch model off` or `cmod permission ci-watch network api.github.com on`. A toggle in Settings runs it. It refuses a permission the mod's `package.json` `cmod.permissions` does not list, and the mod sees the change before its next call, with no reload ([permissions.md](permissions.md)).
|
|
256
|
+
|
|
257
|
+
### cmod hotkey
|
|
258
|
+
|
|
259
|
+
```text
|
|
260
|
+
cmod hotkey <mod> <hotkey> <combo>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Binds one hotkey of a mod to a new combo in Claude Code's `keybindings.json`, such as `cmod hotkey notes toggle ctrl+shift+n`, and removes the combo it had. A combo in Settings, under the mod, Hotkeys, runs it, and so does the cmod plugin for its own two hotkeys at its first start. It refuses a combo that is not one, and a combo the person already bound to something else, with `<combo> already runs something else in ~/.claude/keybindings.json. Pick another combo.` The mod's next update keeps the combo ([mod.md](mod.md#hotkeys)).
|
|
247
264
|
|
|
248
265
|
### cmod settings
|
|
249
266
|
|
|
@@ -267,4 +284,4 @@ Links the `rules/` folder of each mod that is on and has the `prompt` permission
|
|
|
267
284
|
cmod option <name> <key> <value>
|
|
268
285
|
```
|
|
269
286
|
|
|
270
|
-
Sets one option of an installed plugin through `claude plugin configure`, the way `/config` sets it, such as `cmod option ci-watch branch main`. A list option takes its items separated by commas. It refuses a key the plugin's `plugin.json` `userConfig` does not declare. A field in
|
|
287
|
+
Sets one option of an installed plugin through `claude plugin configure`, the way `/config` sets it, such as `cmod option ci-watch branch main`. A list option takes its items separated by commas. It refuses a key the plugin's `plugin.json` `userConfig` does not declare. A field in Settings and the option questions a mod asks at its first start run it, and the mod reads the new value at its next start ([options.md](options.md)).
|
package/docs/dependencies.md
CHANGED
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
# Call another mod
|
|
2
2
|
|
|
3
|
-
A mod offers methods to other mods
|
|
3
|
+
A mod offers methods to other mods by returning them from `setup`, as a VS Code extension returns its API from `activate`. Another mod lists it as a dependency and calls those methods through `mod.dependencies`, typed from the provider's own types file.
|
|
4
4
|
|
|
5
|
-
## Offer methods:
|
|
5
|
+
## Offer methods: return them from setup
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
What `setup` returns is the mod's API: an object whose every value is a method. Each method gets the caller's input and returns its result or a promise of it. It reaches the provider's `mod` and state through `setup`'s scope. Inputs and results cross from one plugin to another, so keep them plain JSON data. A `setup` that returns anything else stops the mod with `<mod>: setup returns its API, an object of methods other mods call, or nothing.` A one-line `setup` returns what its call returns, so `setup: () => sessions.current.onPrompt(…)` offers that `Subscription`'s `remove` to other mods. Give such a `setup` braces.
|
|
8
8
|
|
|
9
9
|
```ts
|
|
10
10
|
import { defineMod, fs } from '../node_modules/@cmodjs/core/mod.js'
|
|
11
11
|
|
|
12
12
|
export const tracer = defineMod({
|
|
13
13
|
name: 'tracer',
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
setup() {
|
|
15
|
+
return {
|
|
16
|
+
signatures: async ({ path }: { path: string }) =>
|
|
17
|
+
(await fs.read(path)).split('\n').flatMap((text, index) => {
|
|
18
|
+
const name = /^export function (\w+)/.exec(text)?.[1]
|
|
19
|
+
return name === undefined ? [] : [{ name, line: index + 1 }]
|
|
20
|
+
}),
|
|
21
|
+
}
|
|
20
22
|
},
|
|
21
|
-
setup() {},
|
|
22
23
|
})
|
|
23
24
|
```
|
|
24
25
|
|
|
26
|
+
A call that reaches the mod while it is still starting waits for `setup` to return.
|
|
27
|
+
|
|
25
28
|
## Type the methods: CmodDependencies
|
|
26
29
|
|
|
27
|
-
The provider types its `
|
|
30
|
+
The provider types the methods its `setup` returns in `types/index.d.ts`, and its `.claude-plugin/plugin.json` names that file as `"types": "./types/index.d.ts"`. The file adds the mod to `CmodDependencies`, which the Claude Mod Manager (cmod) plugin declares on the `claude-code` module:
|
|
28
31
|
|
|
29
32
|
```ts
|
|
30
33
|
export type TracerSignature = { name: string; line: number }
|
|
@@ -41,7 +44,7 @@ declare module 'claude-code' {
|
|
|
41
44
|
```
|
|
42
45
|
|
|
43
46
|
- Each method takes one input and returns a promise.
|
|
44
|
-
- Once the name is in `CmodDependencies`, `tsc` checks the provider's own `
|
|
47
|
+
- Once the name is in `CmodDependencies`, `tsc` checks what the provider's own `setup` returns against it, so a method that returns another shape fails `cmod check`.
|
|
45
48
|
- A types file augments only `'claude-code'`. Claude Code refuses a types file that augments any other module.
|
|
46
49
|
|
|
47
50
|
## Call the methods: mod.dependencies
|
|
@@ -49,16 +52,20 @@ declare module 'claude-code' {
|
|
|
49
52
|
A mod that calls tracer lists it in its own `.claude-plugin/plugin.json`, and Claude Code installs it with the mod:
|
|
50
53
|
|
|
51
54
|
```json
|
|
52
|
-
"dependencies": [{ "name": "cmod", "version": "
|
|
55
|
+
"dependencies": [{ "name": "cmod", "version": ">=4.0.0" }, "tracer"]
|
|
53
56
|
```
|
|
54
57
|
|
|
55
58
|
A provider that changes or removes a method breaks every mod that calls it, and with automatic updates the provider can update first. Name the versions you tested with a range, and Claude Code turns your mod off with a clear error instead of letting its calls fail one by one:
|
|
56
59
|
|
|
57
60
|
```json
|
|
58
|
-
"dependencies": [{ "name": "cmod", "version": "
|
|
61
|
+
"dependencies": [{ "name": "cmod", "version": ">=4.0.0" }, { "name": "tracer", "version": "^1.0" }]
|
|
59
62
|
```
|
|
60
63
|
|
|
61
|
-
Claude Code checks the range against the provider's `version` when it loads your mod. Where a marketplace lists the provider from its git repository, as every mod's marketplace lists cmod, Claude Code also installs it at the highest `<name>--v<version>` tag inside the range, which `cmod publish` pushes for every release ([Claude Code's plugin dependencies](https://code.claude.com/docs/en/plugins/dependencies)).
|
|
64
|
+
Claude Code checks the range against the provider's `version` when it loads your mod. Where a marketplace lists the provider from its git repository, as every mod's marketplace lists cmod, Claude Code also installs it at the highest `<name>--v<version>` tag inside the range, which `cmod publish` pushes for every release ([Claude Code's plugin dependencies](https://code.claude.com/docs/en/plugins/dependencies)).
|
|
65
|
+
|
|
66
|
+
cmod is a provider too, with one difference: every newer cmod runs every mod built on an older `@cmodjs/core`. So a mod names only the oldest cmod it runs on, the version of the `@cmodjs/core` it bundles, as `>=4.0.0`. `cmod check` writes that range whenever the mod's `@cmodjs/core` changes, and `cmod publish` refuses a release whose range differs. A caret range such as `^4.0.0` stops Claude Code from updating cmod past 4.x for every mod on the computer, so leave it to `cmod check`.
|
|
67
|
+
|
|
68
|
+
Each mod's marketplace lists its own copy of cmod, and Claude Code loads only one of them. When a mod needs a newer cmod than a copy Claude Code installed, or two copies differ, the cmod plugin updates every copy and reloads the mods, so a mod never waits for the person to update cmod.
|
|
62
69
|
|
|
63
70
|
Claude Code then lays tracer's types file beside the mod's own types, so the call is typed:
|
|
64
71
|
|
|
@@ -88,14 +95,14 @@ export const outline = defineMod({
|
|
|
88
95
|
A call that cannot answer rejects with one of these reasons:
|
|
89
96
|
|
|
90
97
|
```text
|
|
91
|
-
tracer is not installed.
|
|
98
|
+
tracer is not installed. Install it in Plugins.
|
|
92
99
|
tracer is not set up. Its toast says why.
|
|
93
100
|
tracer is installing. Try again when it's ready.
|
|
94
101
|
tracer has no method signatures.
|
|
95
102
|
tracer: <the message of the error the method threw>
|
|
96
103
|
```
|
|
97
104
|
|
|
98
|
-
- `tracer is not installed` covers a provider whose code did not load.
|
|
105
|
+
- `tracer is not installed` covers a provider whose code did not load. The caller's error toast shows a `Plugins` button, which opens Claude Code's `/plugin`.
|
|
99
106
|
- `tracer is not set up` covers a provider the person declined to install and one whose install failed. Its own toast shows why, with `Retry` or `Install`.
|
|
100
107
|
- `tracer is installing` covers a provider that is still running its install step. A provider that is only starting makes the call wait for it, up to 9 seconds.
|
|
101
108
|
- When tracer is disabled, Claude Code unloads the mod that lists it, with its own error: `Dependency "tracer" is disabled — enable it or remove the dependency`.
|
|
@@ -105,13 +112,13 @@ tracer: <the message of the error the method threw>
|
|
|
105
112
|
notInstalled(name: string): string
|
|
106
113
|
```
|
|
107
114
|
|
|
108
|
-
`notInstalled`, exported from `mod.js`, returns the first reason for a mod name: `<name> is not installed.
|
|
115
|
+
`notInstalled`, exported from `mod.js`, returns the first reason for a mod name: `<name> is not installed. Install it in Plugins.` A test uses it to expect that message.
|
|
109
116
|
|
|
110
117
|
## Test the calls
|
|
111
118
|
|
|
112
|
-
`testMod(outline, { dependencies: { tracer: { signatures: async () => [] } } })` answers tracer's methods with the functions given. A call to a mod the test does not fake fails with `tracer is not installed.
|
|
119
|
+
`testMod(outline, { dependencies: { tracer: { signatures: async () => [] } } })` answers tracer's methods with the functions given. A call to a mod the test does not fake fails with `tracer is not installed. Install it in Plugins.`, as in Claude Code. [testing.md](testing.md) covers it.
|
|
113
120
|
|
|
114
|
-
A test of the provider's own
|
|
121
|
+
A test of the provider's own methods fires `cmod.call`, as a caller's `mod.dependencies` does. The answer is `{ value }` with what the method returned, or `{ deny }` with the reason it failed:
|
|
115
122
|
|
|
116
123
|
```ts
|
|
117
124
|
const tested = testMod(tracer, { files: { '/work/a.ts': 'export function greet() {}\n' } })
|
package/docs/index.md
CHANGED
|
@@ -10,25 +10,27 @@ A mod is a Claude Code plugin built with `@cmodjs/core`. Claude Mod Manager (cmo
|
|
|
10
10
|
|
|
11
11
|
```text
|
|
12
12
|
my-mod/
|
|
13
|
-
├── .claude-plugin/plugin.json name, version, and "dependencies": [{ "name": "cmod", "version": "
|
|
13
|
+
├── .claude-plugin/plugin.json name, version, and "dependencies": [{ "name": "cmod", "version": ">=4.0.0" }]
|
|
14
14
|
├── .claude/CLAUDE.md facts for coding agents, starting with these docs
|
|
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
18
|
├── hooks/register.ts registerMod(addHook, myMod, options), and nothing else
|
|
19
|
-
├── src/mod.tsx defineMod: the state, the
|
|
19
|
+
├── src/mod.tsx defineMod: the state, the hotkeys, the setup, the listeners
|
|
20
20
|
├── src/state.ts the starting state
|
|
21
|
-
├── src/
|
|
22
|
-
├── src/
|
|
21
|
+
├── src/pages/prompts.tsx one Sidebar page per file
|
|
22
|
+
├── src/widgets/prompts.tsx one widget per file
|
|
23
|
+
├── src/components/prompt-count.tsx what the page and the widget draw with
|
|
23
24
|
├── skills/my-mod/SKILL.md a Skill the mod ships
|
|
25
|
+
├── rules/prompt-count.md a rule Claude reads
|
|
24
26
|
├── tests/mod.test.ts testMod tests, run with bun test
|
|
25
|
-
├── package.json depends on @cmodjs/core
|
|
27
|
+
├── package.json depends on @cmodjs/core, and lists the permissions and hotkeys
|
|
26
28
|
└── tsconfig.json extends .claude-plugin/types/tsconfig.json, with the JSX options
|
|
27
29
|
```
|
|
28
30
|
|
|
29
31
|
`cmod new --project` writes the same files, less `.claude/CLAUDE.md`, into `.claude/skills/<name>/` of the repository.
|
|
30
32
|
|
|
31
|
-
The template is a working example to replace. It counts the prompts of the session in `state.session.prompts`, shows the count in a
|
|
33
|
+
The template is a working example to replace. It counts the prompts of the session in `state.session.prompts`, shows the count below the prompt and in a Sidebar page that the `alt+shift+p` hotkey shows or hides, and tests all three. Replace the counter, the page, the widget, the component, the Skill, the rule, and the tests with the mod's own.
|
|
32
34
|
|
|
33
35
|
A mod imports `@cmodjs/core` by path, such as `../node_modules/@cmodjs/core/mod.js`, because Claude Code loads a plugin's own files only. Types such as `RenderElement` and `On` come from the `claude-code` module.
|
|
34
36
|
|
|
@@ -64,14 +66,16 @@ A program the mod needs lives as source in `cli/`. `cmod publish` builds it and
|
|
|
64
66
|
| Which hosts, programs, and files may my mod reach, and how does the person grant them? | [permissions.md](permissions.md) |
|
|
65
67
|
| How does my mod find files and keep its own keys on each one, such as on a Skill? | [mod.md](mod.md#metadata-a-file-s-own-keys) |
|
|
66
68
|
| How does the person finish a setup step only they can do, such as signing in? | [install-steps.md](install-steps.md#steps-in-claude-code) |
|
|
67
|
-
| How do I add my mod's own page to
|
|
69
|
+
| How do I add my mod's own page to Settings, or change Claude Code's settings? | [ui.md](ui.md#settings-pages), [mod.md](mod.md#settings-claude-code-s-settings-files) |
|
|
70
|
+
| How do I give my mod a hotkey the person can change? | [mod.md](mod.md#hotkeys) |
|
|
68
71
|
| How does my mod read and follow this conversation, its tasks, or earlier ones, search them, or start a task? | [session.md](session.md) |
|
|
69
72
|
| How does my mod hear a prompt, a tool call, a permission dialog, or a model switch, and change or refuse it? | [session.md](session.md#listen-to-a-record), [session.md](session.md#the-session-s-own-events) |
|
|
70
73
|
| How does my mod hear, change, or run any slash command, a built-in such as `/compact` included? | [mod.md](mod.md#commands-slash-commands) |
|
|
71
|
-
| How do I draw a pane, change
|
|
72
|
-
| How do I
|
|
74
|
+
| How do I draw a page in the Sidebar, a pane, change how Claude Code draws a row, or ask the person something? | [ui.md](ui.md) |
|
|
75
|
+
| How do I show a status, such as the branch or a CI result, by the prompt, while Claude works, after each turn, or on the Dashboard? | [ui.md](ui.md#widgets) |
|
|
76
|
+
| How do I add a slash command, a tool, a permission rule, a check, a prompt, a schedule, or a background program? | [jobs.md](jobs.md) |
|
|
73
77
|
| How does one mod call another? | [dependencies.md](dependencies.md) |
|
|
74
|
-
| How does a mod change the machine,
|
|
78
|
+
| How does a mod change the machine, ship a program, or clean up after itself? | [install-steps.md](install-steps.md) |
|
|
75
79
|
| How do I test a mod without Claude Code? | [testing.md](testing.md) |
|
|
76
80
|
| Which `cmod` command links, checks, tries in a clean Claude Code, or publishes a mod? | [commands.md](commands.md) |
|
|
77
81
|
|
|
@@ -81,32 +85,32 @@ A mod imports from the files below, and every other file in this package is inte
|
|
|
81
85
|
|
|
82
86
|
| Import | Exports | Docs |
|
|
83
87
|
| --- | --- | --- |
|
|
84
|
-
| `mod.js` | `defineMod`, `ModDefinition`, `Mod`, `
|
|
85
|
-
| `mod.js` | `
|
|
88
|
+
| `mod.js` | `defineMod`, `ModDefinition`, `Mod`, `Subscription`, `Timer`, `Reservation`, `Permissions`, `Allowed`, `JobContext`, `Job`, `SlashCommand`, `Tool`, `Step`, `longestMs`, `messageOf` | [mod.md](mod.md), [jobs.md](jobs.md), [install-steps.md](install-steps.md#steps-in-claude-code) |
|
|
89
|
+
| `mod.js` | `Hotkey`, `HotkeyInit`, `Combo`, `Key` | [mod.md](mod.md#hotkeys) |
|
|
90
|
+
| `mod.js` | `settings`, `SettingsFile`, `SettingsFileChange`, `SettingsFileChangeListener`, `config`, `ConfigRow`, `commands`, `CommandInfo`, `CommandRun`, `CommandRunAnswer`, `CommandRunResult`, `model`, `fs`, `Entry`, `Stat`, `FileMatch`, `FileChange`, `metadata`, `Metadata`, `process`, `http`, `clipboard`, `Imports` | [mod.md](mod.md) |
|
|
86
91
|
| `mod.js` | `sessions`, `Sessions`, `Session`, `CurrentSession`, `TaskOwner`, `RecordPart`, `OnMethods`, `BeforeMethods`, `Listener`, `ListenerTime`, `ReadOptions`, `AnswerOptions`, `Refusal`, `SearchOptions`, `SessionScope`, `SessionSend`, `TaskStartArgs`, `ToolCallArgs`, `Usage`, `Surface`, `Matcher` | [session.md](session.md) |
|
|
87
92
|
| `mod.js` | `Placement`, `ProjectSetup`, `SessionStartAsked`, `SessionStarted`, `SessionEnded`, `ModelChange`, `CwdChange`, `DirectoryAdd`, `Worktree`, `Notification`, `Elicitation`, `ElicitationResponse`, `McpServer`, `PermissionUpdate`, `PermissionCheck`, `PermissionRequest`, `PermissionDenial`, `BatchedCall`, `ToolBatch`, `Todo`, `ReplyDisplay` | [session.md](session.md#the-session-s-own-events) |
|
|
88
93
|
| `mod.js` | `Message`, `MessageType`, `Prompt`, `Note`, `Reply`, `ToolCall`, `ToolResult`, `Skill`, `InstructionFile`, `Notice`, `Sender`, `SenderType`, `Person`, `Mod`, `Schedule`, `Channel`, `Sdk`, `Engine` | [session.md](session.md#message) |
|
|
89
94
|
| `mod.js` | `Context`, `Turn`, `Goal`, `Task` | [session.md](session.md#turn-context-goal-and-task) |
|
|
90
95
|
| `mod.js` | `Toast`, `ToastId`, `ToastType`, `ToastOptions` | [ui.md](ui.md#toasts) |
|
|
91
|
-
| `mod.js` | `
|
|
96
|
+
| `mod.js` | `PageInit`, `Page`, `PageProps`, `PaneInit`, `Pane`, `PaneProps`, `OpenOptions`, `ScrollTarget`, `ScrollOptions` | [ui.md](ui.md#the-sidebar-and-its-pages) |
|
|
97
|
+
| `mod.js` | `WidgetInit`, `Widget`, `WidgetContext`, `Area`, `AbovePrompt`, `BelowPrompt`, `Working`, `TurnEnd`, `Dashboard` | [ui.md](ui.md#widgets) |
|
|
98
|
+
| `mod.js` | `StyleInit`, `Style`, `StyleProps`, `Kind`, `Row`, `Block`, `ToolGroup`, `ToolProgress`, `CommandOutput`, `AskUserQuestion` | [ui.md](ui.md#styles-change-how-claude-code-draws) |
|
|
92
99
|
| `mod.js` | `notInstalled` | [dependencies.md](dependencies.md) |
|
|
93
|
-
| `installer.js` | `defineStep`, `Step` | [install-steps.md](install-steps.md#steps-in-claude-code) |
|
|
94
100
|
| `register.js` | `registerMod`, `registerPermissionCheck`, `registeredMod` | [mod.md](mod.md) |
|
|
95
101
|
| `options.js` | `option`, `Option`, `Options`, `OptionValues`, `OptionValue`, `OptionType`, `UserConfigField`, `userConfigOf` | [options.md](options.md) |
|
|
96
102
|
| `shell.js` | `parseShell`, `ParsedShell`, `ShellCommand` | [mod.md](mod.md#shell-js-and-path-js-read-a-command-line) |
|
|
97
103
|
| `path.js` | `resolve`, `join`, `dirname`, `basename`, `extname`, `relative`, `isAbsolute`, `normalize` | [mod.md](mod.md#shell-js-and-path-js-read-a-command-line) |
|
|
98
104
|
| `testing.js` | `testMod`, `TestOptions`, `TestedMod`, `Raise`, `Fakes`, `Shown`, `ShownToast`, `TestCall`, `FakeSession`, `MessageDraft`, `RoutedEvent` | [testing.md](testing.md) |
|
|
99
|
-
| `ui/define-pane.js` | `definePane`, `Pane` | [ui.md](ui.md) |
|
|
100
105
|
| `ui/elements.js` | `Box`, `Text`, `Button`, `Link`, `Code`, `Markdown`, `Input`, `Select`, `Image`, `drawWith` | [ui.md](ui.md) |
|
|
101
106
|
| `ui/components.js` | `Tabs`, `Tab`, `Split`, `Panel`, `Toggle`, `Tooltip`, `Help`, `Dialog`, `Pagination`, `Progress` | [ui.md](ui.md) |
|
|
102
|
-
| `ui/markdown.js` | `Heading`, `Paragraph`, `CodeBlock`, `BlockQuote`, `List`, `Table`, `ThematicBreak`, `HtmlBlock`, `
|
|
107
|
+
| `ui/markdown.js` | `Heading`, `Paragraph`, `CodeBlock`, `BlockQuote`, `List`, `Table`, `ThematicBreak`, `HtmlBlock`, `Block`, `markdownBlocks`, `BlockProps`, `MarkdownReader`, `MarkdownPiece` | [ui.md](ui.md#markdown-blocks) |
|
|
103
108
|
| `jobs/slash-command.js` | `slashCommand`, `CommandReply` | [jobs.md](jobs.md) |
|
|
104
109
|
| `jobs/tool.js` | `tool` | [jobs.md](jobs.md) |
|
|
105
110
|
| `jobs/permissions.js` | `permissions`, `Rule`, `PermissionRules`, `Target`, `CommandCall`, `FileCall`, `FetchCall`, `Call` | [jobs.md](jobs.md) |
|
|
106
111
|
| `jobs/check.js` | `check` | [jobs.md](jobs.md) |
|
|
107
112
|
| `jobs/prompt.js` | `prompt` | [jobs.md](jobs.md) |
|
|
108
113
|
| `jobs/schedule.js` | `schedule`, `ScheduledPrompt` | [jobs.md](jobs.md) |
|
|
109
|
-
| `jobs/status-line.js` | `statusLine` | [jobs.md](jobs.md) |
|
|
110
114
|
| `jobs/program.js` | `program`, `Program` | [jobs.md](jobs.md) |
|
|
111
115
|
| `jobs/context.js` | `afterCall`, `inFolder`, `withinDeadline`, `workspaceReader` | [jobs.md](jobs.md#the-building-blocks) |
|
|
112
116
|
| `jobs/permissions/decide-permission.js` | `decidePermission`, `stricterVerdict`, `Rule`, `PermissionRules`, `Decision`, `Verdict` | [jobs.md](jobs.md) |
|
package/docs/install-steps.md
CHANGED
|
@@ -10,7 +10,7 @@ A mod that changes the machine, such as by adding a shell alias or downloading a
|
|
|
10
10
|
"install": "./setup/install.sh",
|
|
11
11
|
"uninstall": "./setup/uninstall.sh",
|
|
12
12
|
"program": "hello",
|
|
13
|
-
"
|
|
13
|
+
"hotkeys": { "cycle": { "title": "Cycle the mode", "default": "shift+tab" } }
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
```
|
|
@@ -20,7 +20,7 @@ Each key is optional.
|
|
|
20
20
|
- `install` is one shell command that sets the mod up.
|
|
21
21
|
- `uninstall` is one shell command that undoes it.
|
|
22
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
|
+
- `hotkeys` is the hotkeys `defineMod` declares, which `cmod check` writes here so cmod binds them at install without loading the mod. [Hotkeys](#hotkeys) explains it.
|
|
24
24
|
|
|
25
25
|
cmod refuses a `cmod` key that breaks these rules, with the fix in the message:
|
|
26
26
|
|
|
@@ -29,16 +29,16 @@ cmod refuses a `cmod` key that breaks these rules, with the fix in the message:
|
|
|
29
29
|
- Each names at least one script file inside the mod, and no script at the mod's root. cmod asks consent for the whole folder of each script, so put the scripts in a folder, such as `setup/`.
|
|
30
30
|
- A symbolic link in a script folder points at a file.
|
|
31
31
|
- `program` is a command name: letters, digits, `.`, `_`, and `-`, starting with a letter or digit.
|
|
32
|
-
-
|
|
32
|
+
- Each hotkey has a title and a default combo, as `defineMod` takes them ([mod.md](mod.md#hotkeys)).
|
|
33
33
|
- `.claude-plugin/plugin.json` has a `version`.
|
|
34
34
|
|
|
35
35
|
## Consent
|
|
36
36
|
|
|
37
|
-
Before a step runs for the first time, cmod shows the person the commands, the
|
|
37
|
+
Before a step runs for the first time, cmod shows the person the commands, the hotkeys, and the permissions, and asks to run them. cmod hashes the commands, the hotkeys, the permissions, and every file in each script folder, and remembers the hash the person approved with each permission. A change to any of those asks again, and an added permission is the only one it asks about ([permissions.md](permissions.md)).
|
|
38
38
|
|
|
39
|
-
- In Claude Code, the mod opens the installer pane, `Install <mod>`, focused. It lists `<mod> wants to:`, each permission as one sentence, then `and change your computer:` with `Run <install> now, and <uninstall> when you remove it` and `Bind shift+tab to
|
|
40
|
-
- In a session with no screen, such as `claude -p`, the mod waits and logs `<mod> waits for consent to run <install>. Run cmod install <mod> in a terminal.` That person is already in a terminal.
|
|
41
|
-
- In a terminal, `cmod install`, `cmod link`, `cmod try`, and `cmod update` print the commands, the program, the
|
|
39
|
+
- In Claude Code, the mod opens the installer pane, `Install <mod>`, focused. It lists `<mod> wants to:`, each permission as one sentence, then `and change your computer:` with `Run <install> now, and <uninstall> when you remove it` and `Bind shift+tab to Cycle the mode`, naming only what the mod has. `Accept` (a) runs the setup, and a loading toast above the prompt shows the install with a progress bar. `Not now` (n) and Escape decline.
|
|
40
|
+
- In a session with no screen, such as `claude -p`, the mod waits and logs `<mod> waits for consent to run <install>. Run cmod install <mod> in a terminal.` That person is already in a terminal, and no screen exists to draw a button on.
|
|
41
|
+
- In a terminal, `cmod install`, `cmod link`, `cmod try`, and `cmod update` print the commands, the program, the hotkeys, and the permissions, and ask `Set up <mod>? [y/N]`. `--yes` approves without asking.
|
|
42
42
|
- `Not now` leaves the mod off with the toast `Not installed` and an `Install` button that asks again.
|
|
43
43
|
|
|
44
44
|
The mod starts only once its install step has run ([mod.md](mod.md)).
|
|
@@ -47,50 +47,57 @@ The mod starts only once its install step has run ([mod.md](mod.md)).
|
|
|
47
47
|
|
|
48
48
|
Claude Code updates a plugin by itself only when its marketplace has automatic updates on, and they are off for every marketplace that is not Anthropic's. So the setup of a mod installed from a marketplace turns them on, through `"autoUpdate": true` on that marketplace's `extraKnownMarketplaces` entry in `settings.json`. The consent lists it as `Update <mod> automatically from the <marketplace> marketplace`, and a mod with nothing to consent to logs it. The setup never changes a choice the person made, in `settings.json` or with the toggle under `/plugin` Marketplaces, and leaves a linked mod alone.
|
|
49
49
|
|
|
50
|
-
Claude Code downloads an update in the background and loads it at the next launch or `/reload-plugins`. An update whose scripts,
|
|
50
|
+
Claude Code downloads an update in the background and loads it at the next launch or `/reload-plugins`. An update whose scripts, hotkeys, 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` program the cmod plugin installed, never one elsewhere on PATH, and only once that program is at least the version of the `@cmodjs/core` the mod bundles. Until then its toast says `Waiting for cmod`. After a minute it says `<mod> needs cmod <version> or later, and this computer has cmod <version>, so cmod is updating itself.` and runs `cmod update cmod` once. The mod sets up as soon as a new enough cmod is installed. When the update leaves cmod older, the next wait shows `<mod> needs cmod <version> or later, and the newest cmod is <version>.` with a Retry button instead of updating again.
|
|
53
53
|
|
|
54
54
|
## Steps in Claude Code
|
|
55
55
|
|
|
56
56
|
Some setup only the person can do, such as signing in to a service. A mod lists those steps in `installer`, and the installer pane shows each one after the permissions and the options, once the mod has started:
|
|
57
57
|
|
|
58
|
-
```
|
|
59
|
-
import {
|
|
60
|
-
import {
|
|
58
|
+
```tsx
|
|
59
|
+
import { defineMod, type Step } from '../node_modules/@cmodjs/core/mod.js'
|
|
60
|
+
import { Button, Text } from '../node_modules/@cmodjs/core/ui/elements.js'
|
|
61
|
+
import { isSignedIn, openSignIn } from './github.js'
|
|
61
62
|
|
|
62
|
-
const signIn =
|
|
63
|
+
const signIn: Step = {
|
|
63
64
|
id: 'sign-in',
|
|
64
65
|
title: 'Sign in to GitHub',
|
|
65
|
-
render: () => <
|
|
66
|
-
isDone:
|
|
67
|
-
}
|
|
66
|
+
render: () => <Button label="Open the sign-in page" onPress={() => void openSignIn()} />,
|
|
67
|
+
isDone: () => isSignedIn(),
|
|
68
|
+
}
|
|
68
69
|
|
|
69
70
|
export const ciWatch = defineMod({ name: 'ci-watch', installer: [signIn], setup(mod) { … } })
|
|
70
71
|
```
|
|
71
72
|
|
|
72
73
|
```ts
|
|
73
|
-
|
|
74
|
+
type Step<State> = {
|
|
75
|
+
readonly id: string
|
|
76
|
+
readonly title: string
|
|
77
|
+
render(mod: Mod<State>, props: PaneProps): RenderElement
|
|
78
|
+
isDone(mod: Mod<State>): boolean | Promise<boolean>
|
|
79
|
+
}
|
|
74
80
|
```
|
|
75
81
|
|
|
76
|
-
- `id` is 1 to 64 letters, digits, `_`, or
|
|
77
|
-
- `render` draws the step's body, the way a pane's `render` does ([ui.md](ui.md)).
|
|
82
|
+
- `id` is 1 to 64 letters, digits, `_`, or `-`, unique among the steps. `title` is the line above the step, with `(1 of 2)`. `defineMod` throws for an id that is not one, a blank title, or two steps with one id.
|
|
83
|
+
- `render` draws the step's body, the way a pane's `render` does ([ui.md](ui.md)). Give the step a `Button` that does the work or opens the place to do it, never a command to type.
|
|
78
84
|
- `isDone` says whether the step is finished. A step that is done is skipped, so the pane opens only for what is left. `Next` checks `isDone`, and shows `Finish <title> first.` while it is false. The last step's button says `Finish`.
|
|
79
|
-
- An `isDone` that throws or rejects counts as not done
|
|
80
|
-
- `Not now` closes the pane and
|
|
85
|
+
- An `isDone` that throws or rejects counts as not done, and `Next` shows `The <title> step's isDone failed: <error>`.
|
|
86
|
+
- `Not now` closes the pane, and the mod keeps running. A toast stays above the prompt, `Needs one more step` with `Starting with <title>.` under it, and its `Finish` button opens the pane on the first step left. Settings shows `Needs setup: <title>` with a `Finish setup` button too.
|
|
81
87
|
|
|
82
|
-
##
|
|
88
|
+
## Hotkeys
|
|
83
89
|
|
|
84
|
-
A plugin cannot bind a key in Claude Code: Claude Code reads key bindings only from the person's `keybindings.json`. So cmod writes
|
|
90
|
+
A plugin cannot bind a key in Claude Code: Claude Code reads key bindings only from the person's `keybindings.json`. So cmod writes each hotkey there, with consent, as the setup's last step. A hotkey runs a hidden command of the mod, `<mod>--<hotkey>`, which the mod hears as `onPress` ([mod.md](mod.md#hotkeys)):
|
|
85
91
|
|
|
86
92
|
```json
|
|
87
|
-
{ "bindings": [{ "context": "Chat", "bindings": { "shift+tab": "command:
|
|
93
|
+
{ "bindings": [{ "context": "Chat", "bindings": { "shift+tab": "command:modes--cycle" } }] }
|
|
88
94
|
```
|
|
89
95
|
|
|
90
|
-
- cmod writes each
|
|
91
|
-
- A
|
|
92
|
-
-
|
|
93
|
-
-
|
|
96
|
+
- cmod writes each combo into the `Chat` bindings of `~/.claude/keybindings.json`, or of `$CLAUDE_CONFIG_DIR/keybindings.json` when that variable is set. When the file is a symbolic link, cmod writes the file it points to. Claude Code reads the change at once, with no restart.
|
|
97
|
+
- A combo the person already bound to something else stays theirs, and the hotkey has no combo. The person picks one in Settings, under the mod, Hotkeys.
|
|
98
|
+
- The combo the person picks in Settings runs `cmod hotkey <mod> <hotkey> <combo>`. An update keeps the combo each hotkey has in `keybindings.json`, whether Settings or the person's own edit bound it ([commands.md](commands.md#cmod-hotkey)).
|
|
99
|
+
- An upgrade that drops a hotkey removes its binding. Removing the mod removes every binding that runs one of its hotkeys, and a `Chat` block that leaves empty. A binding the person moved to another action stays.
|
|
100
|
+
- A hotkey bound to `shift+tab` replaces Claude Code's own mode switch, and Claude Code then drops its `shift+tab` hint.
|
|
94
101
|
|
|
95
102
|
## How a step runs
|
|
96
103
|
|
|
@@ -118,7 +125,8 @@ The install step runs again on every new version of the mod and whenever its scr
|
|
|
118
125
|
- When the first install of a mod fails or is stopped, cmod runs the uninstall step to undo it. So the uninstall step must work on a partial install.
|
|
119
126
|
- When the install step of an upgrade fails or is stopped, cmod keeps the version it set up before.
|
|
120
127
|
- The failure shows at once as an error toast with `Retry`, `Report`, and `Hide` ([ui.md](ui.md#toasts)).
|
|
121
|
-
- When waiting can fix the failure, cmod also retries in the background after 2, 4, 8, 16, 32, 64, and 128 seconds, then every 128 seconds, and the toast says `Trying again in <n> s.` A try that works replaces the toast with `Installed`. Waiting can fix two failures: a linked checkout whose build or install step failed,
|
|
128
|
+
- When waiting can fix the failure, cmod also retries in the background after 2, 4, 8, 16, 32, 64, and 128 seconds, then every 128 seconds, and the toast says `Trying again in <n> s.` A try that works replaces the toast with `Installed`. Waiting can fix two failures: a linked checkout whose build or install step failed, and a release download that got 404 or no answer, as when a release was published seconds ago. Any other failure waits for `Retry`. A linked checkout's failed build runs again only once a file in its program's folder changes, so a retry with no change shows the same error without building.
|
|
129
|
+
- A new version of a linked checkout whose install steps did not change keeps running on its last install while cmod builds the new version in the background, and other sessions wait for that one build. A failed build shows the build's own error.
|
|
122
130
|
- Before each try, cmod reads the mod's install record, so a mod another session installed starts without running its install again.
|
|
123
131
|
|
|
124
132
|
## The uninstall step
|