@cmodjs/core 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  # @cmodjs/core
6
6
 
7
- The library every Claude Mod Manager mod imports. Claude Mod Manager (CMod) installs, tries, builds, checks, and publishes Claude Code mods: plugins with hooks, panes, slash commands, tools, permission rules, and an install step that runs with your consent.
7
+ The library every Claude Mod Manager mod imports. Claude Mod Manager (cmod) installs, tries, builds, checks, and publishes Claude Code mods: plugins with hooks, panes, slash commands, tools, permission rules, and an install step that runs with your consent.
8
8
 
9
9
  ```sh
10
10
  npm i -g @cmodjs/cli
@@ -17,4 +17,4 @@ cmod new my-mod
17
17
 
18
18
  This package ships its docs in `docs/`, so a mod's `node_modules/@cmodjs/core/docs/index.md` is the map of the docs for the version it installed. The same docs are on GitHub at [core/docs](https://github.com/heyJordanParker/cmod/blob/main/core/docs/index.md).
19
19
 
20
- The CMod plugin and the `cmod` source live at [github.com/heyJordanParker/cmod](https://github.com/heyJordanParker/cmod).
20
+ The cmod plugin and the `cmod` source live at [github.com/heyJordanParker/cmod](https://github.com/heyJordanParker/cmod).
package/docs/commands.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # cmod commands
2
2
 
3
- The `cmod` command of Claude Mod Manager (CMod) builds, checks, and publishes mods, and installs them. `npm i -g @cmodjs/cli` puts it on `PATH`. `cmod <command> --help` prints a command's arguments, and `cmod --version` prints the version.
3
+ The `cmod` command of Claude Mod Manager (cmod) builds, checks, and publishes mods, and installs them. `npm i -g @cmodjs/cli` puts it on `PATH`. `cmod <command> --help` prints a command's arguments, and `cmod --version` prints the version.
4
4
 
5
5
  Each command below restates what `cmod <command> --help` prints, for a mod author. The help text is the full word.
6
6
 
@@ -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]
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 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`.
29
29
 
30
30
  - `--yes` approves the mod's install and uninstall commands without asking.
31
31
 
@@ -57,7 +57,7 @@ Runs every check this machine can run on the mod at `path` (default: the current
57
57
  10. Runs, with `claude plugin test`, the test files that import `claude-code/testing`. It skips this when none does.
58
58
  11. 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.
59
59
 
60
- `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.
60
+ `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.
61
61
 
62
62
  ### cmod try
63
63
 
@@ -65,7 +65,7 @@ Runs every check this machine can run on the mod at `path` (default: the current
65
65
  cmod try <owner/repo | path> [--yes] [-- claude arguments]
66
66
  ```
67
67
 
68
- Starts one throwaway Claude Code session with a mod set up. Installs the mod's packages, installs the CMod plugin when Claude Code lacks it, and runs the mod's install step after asking consent. Then starts one Claude Code session with the mod loaded through `--plugin-dir`.
68
+ Starts one throwaway Claude Code session with a mod set up. Installs the mod's packages, installs the cmod plugin when Claude Code lacks it, and runs the mod's install step after asking consent. Then starts one Claude Code session with the mod loaded through `--plugin-dir`.
69
69
 
70
70
  - Unlike `cmod link`, it builds no program from `cli/`. A mod whose `package.json` `cmod.program` names a program downloads it from the GitHub release of the mod's version, so it fails until `cmod publish` has released that version.
71
71
 
@@ -82,12 +82,12 @@ cmod publish [path] [--dry-run]
82
82
 
83
83
  Releases the mod at `path` (default: the current folder) at the version in its `plugin.json`:
84
84
 
85
- 1. Builds the release from the committed files, leaving out `cli/`, `.github/`, and `.claude/`. A `"files"` list in `package.json` limits the release to the paths it lists, plus `.claude-plugin/`, `package.json`, the README, and the license, the way `npm publish` reads it. List every folder the hooks module imports from, such as `"files": ["hooks", "src", "skills"]`, because the bundle in step 2 is built from the release.
86
- 2. Bundles the hooks module that `hooks/hooks.json` names, with the mod's source and packages, into one readable `.js` file, and points `hooks/hooks.json` at it. Anthropic's plugin directory reads a repository without installing its packages, so it can follow a mod only when all its code is in one file.
85
+ 1. Builds the release from the committed files, leaving out `cli/`, `.github/`, and `.claude/`. A `"files"` list in `package.json` limits the release to the paths it lists, plus the folders of the install and uninstall steps, `.claude-plugin/`, `package.json`, the README, and the license, the way `npm publish` reads it. List every folder the hooks module imports from, such as `"files": ["hooks", "src", "skills"]`, because the bundle in step 2 is built from the release.
86
+ 2. Bundles the hooks module that `hooks/hooks.json` names, with the mod's source and packages, into one readable `.js` file, writes each control character in it as a `\u` escape, and points `hooks/hooks.json` at it. Anthropic's plugin directory reads a repository without installing its packages, so it can follow a mod only when all its code is in one file.
87
87
  3. Checks the release with `claude plugin validate --strict`, and stops with the validator's message when it fails.
88
88
  4. Commits the release as the `release` branch, and builds the release archive from that commit.
89
89
  5. Builds the program `cli/` declares, and writes `SHA256SUMS` for every file of the release.
90
- 6. Writes `.claude-plugin/marketplace.json`, listing the archive and the CMod plugin.
90
+ 6. Writes `.claude-plugin/marketplace.json`, listing the archive and the cmod plugin.
91
91
  7. Commits that file, tags `v<version>`, pushes the tag and the `release` branch, and creates the GitHub release.
92
92
  8. Prints the link to paste as the Repository when you submit the mod at [claude.ai/directory/manage](https://claude.ai/directory/manage), such as `https://github.com/owner/greeter/tree/release`. The portal reads the branch from the link, so the directory follows `release`.
93
93
 
@@ -103,7 +103,7 @@ It needs, and refuses to start without:
103
103
  - a `package.json` with no dependency on a `file:` or `link:` path, which no user has
104
104
  - a `"files"` list, when `package.json` has one, whose every path matches a committed file
105
105
  - a `"program"` in the `cmod` key of `package.json` that names the program `cli/` declares, when `cli/` declares one
106
- - the network, to hash the CMod plugin release it lists, unless the mod is CMod itself
106
+ - the network, to hash the cmod plugin release it lists, unless the mod is cmod itself
107
107
  - for a real publish, a working tree with no uncommitted changes, a `v<version>` tag that does not exist yet, a `git push` to `origin` that succeeds, and the `gh` command, which creates the GitHub release
108
108
 
109
109
  `--dry-run` builds and writes everything, and pushes nothing. 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.
@@ -117,7 +117,7 @@ cmod install <owner/repo | path> [name] [--yes]
117
117
  cmod install <name | name@marketplace> [--yes]
118
118
  ```
119
119
 
120
- Adds the plugin's marketplace to Claude Code and installs the plugin through Claude Code. A mod also gets the CMod plugin when Claude Code lacks it, and its install step runs. Any other plugin installs through Claude Code alone, and CMod keeps no record of it.
120
+ Adds the plugin's marketplace to Claude Code and installs the plugin through Claude Code. A mod also gets the cmod plugin when Claude Code lacks it, and its install step runs. Any other plugin installs through Claude Code alone, and cmod keeps no record of it.
121
121
 
122
122
  - When the marketplace lists several plugins, name the one to install after it.
123
123
  - Given `name@marketplace` of a marketplace Claude Code has added, it installs that plugin.
@@ -141,7 +141,7 @@ Updates the named plugin, or every plugin Claude Code has installed, through Cla
141
141
  cmod remove <name>
142
142
  ```
143
143
 
144
- 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.
144
+ 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.
145
145
 
146
146
  ### cmod list
147
147
 
@@ -149,9 +149,9 @@ Uninstalls the plugin through Claude Code. When CMod set the plugin up as a mod,
149
149
  cmod list
150
150
  ```
151
151
 
152
- 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.
152
+ 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.
153
153
 
154
- - 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.
154
+ - 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.
155
155
 
156
156
  ## Run in an install script
157
157
 
@@ -163,9 +163,9 @@ cmod download <program> <machine> <url> <sha256> [<machine> <url> <sha256> …]
163
163
 
164
164
  Downloads a program for an install script: fetches the download for this machine, checks its SHA-256, unpacks it, and moves `<program>` to `$CMOD_DATA/bin/<program>`. [install-steps.md](install-steps.md) explains it.
165
165
 
166
- ## Run by CMod
166
+ ## Run by cmod
167
167
 
168
- CMod runs these two itself. A person runs them to see a step's whole log or to finish a step.
168
+ cmod runs these two itself. A person runs them to see a step's whole log or to finish a step.
169
169
 
170
170
  ### cmod setup
171
171
 
@@ -24,7 +24,7 @@ export const tracer = defineMod({
24
24
 
25
25
  ## Type the methods: CmodDependencies
26
26
 
27
- The provider types its `api` 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:
27
+ The provider types its `api` 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
28
 
29
29
  ```ts
30
30
  export type TracerSignature = { name: string; line: number }
package/docs/hooks.md CHANGED
@@ -12,6 +12,8 @@ type ModHook<E> = (input: HookInputs[E]) => HookAnswer | void | Promise<HookAnsw
12
12
 
13
13
  Add hooks in `setup`. Each `mod.on` adds one more hook. Several hooks on one event all run.
14
14
 
15
+ A hook on `PreToolUse` or `PermissionRequest` takes part in Claude Code's permission check, so the mod's `hooks/register.ts` calls `registerPermissionCheck(addHook)` after `registerMod` ([mod.md](mod.md)). Without it the mod does not start.
16
+
15
17
  ```ts
16
18
  import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
17
19
 
@@ -37,7 +39,7 @@ export const safeDelete = defineMod({
37
39
  | `SessionEnd` | none |
38
40
  | `UserPromptSubmit` | none |
39
41
  | `InstructionsLoaded` | none |
40
- | `PreToolUse` | Claude Mod Manager (CMod) builds the input: `session_id`, `cwd`, `hook_event_name`, `tool_name`, `tool_input`, `tool_use_id`, `agent_id` inside a subagent, `agent_type` inside a subagent or when the session started as an agent, and `files`. It has no `transcript_path` and no `permission_mode`. |
42
+ | `PreToolUse` | Claude Mod Manager (cmod) builds the input: `session_id`, `cwd`, `hook_event_name`, `tool_name`, `tool_input`, `tool_use_id`, `agent_id` inside a subagent, `agent_type` inside a subagent or when the session started as an agent, and `files`. It has no `transcript_path` and no `permission_mode`. |
41
43
  | `PermissionRequest` | none |
42
44
  | `PermissionDenied` | none |
43
45
  | `PostToolUse` | adds `files` |
@@ -51,17 +53,17 @@ export const safeDelete = defineMod({
51
53
  | `StopFailure` | none |
52
54
  | `CwdChanged` | no `transcript_path` |
53
55
 
54
- CMod raises `CwdChanged` itself, never Claude Code. It raises it when the working folder changes: after `/cd`, after a Bash or PowerShell call, and at a prompt. `mod.cwd` and `mod.projectRoot` already hold the new folder when the hook runs.
56
+ cmod raises `CwdChanged` itself, never Claude Code. It raises it when the working folder changes: after `/cd`, after a Bash or PowerShell call, and at a prompt. `mod.cwd` and `mod.projectRoot` already hold the new folder when the hook runs.
55
57
 
56
58
  ## files: what a tool call reads and changes
57
59
 
58
- The input of `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` has `files: { read: string[]; changed: string[] }`, absolute paths that CMod works out from the call:
60
+ The input of `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` has `files: { read: string[]; changed: string[] }`, absolute paths that cmod works out from the call:
59
61
 
60
62
  - `Read` reads its `file_path`. `Edit`, `Write`, and `NotebookEdit` change their path.
61
63
  - `Grep` counts its `path`, or the working folder when it names none, and `Glob` counts its `pattern` joined to its `path`. Since `read` lists only files, a `Grep` of a folder and every `Glob` usually leave `read` empty.
62
64
  - A `Bash` command reads and changes the files its commands name, such as `cat a.ts > b.ts`. A path that holds a shell expansion, such as `$HOME`, is left out.
63
65
  - `read` lists only paths that are files now. `changed` lists every path the call writes, whether it exists or not.
64
- - When CMod cannot work the files out, both lists are empty, and the reason goes to Claude Code's debug log.
66
+ - When cmod cannot work the files out, both lists are empty, and the reason goes to Claude Code's debug log.
65
67
 
66
68
  ## HookAnswer: what a hook can answer
67
69
 
@@ -97,7 +99,7 @@ type HookAnswer = {
97
99
  - `continue: false` stops the session after the event, and `stopReason` is the text Claude Code shows. `PreToolUse` takes neither.
98
100
  - `decision: 'block'` with a `reason` blocks the event. On `PreToolUse` it denies the call with the reason. On `Stop` it keeps Claude going with the reason.
99
101
  - `systemMessage` shows the text to the person as a log line.
100
- - CMod ignores `suppressOutput`.
102
+ - cmod ignores `suppressOutput`.
101
103
  - `hookSpecificOutput.hookEventName` is optional. When set, it must be the event's own name.
102
104
 
103
105
  Each event reads only some `hookSpecificOutput` fields:
@@ -145,11 +147,11 @@ A hook that throws, rejects, or answers a field its event does not read fails:
145
147
 
146
148
  - On `PreToolUse` the call is denied with `<mod>: the PreToolUse hook failed: <error>`.
147
149
  - On `PermissionRequest` the request is denied with `<mod>: the PermissionRequest hook failed: <error>`.
148
- - On every other event CMod logs `<mod>: the <event> hook failed: <error>`, and the event goes on as if the hook answered nothing.
150
+ - On every other event cmod logs `<mod>: the <event> hook failed: <error>`, and the event goes on as if the hook answered nothing.
149
151
 
150
152
  ## The first SessionStart
151
153
 
152
- A mod that is still installing when the session starts misses Claude Code's `SessionStart`. CMod holds `SessionStart` up to 9 seconds for the mod to start. When the mod starts later, CMod runs its `SessionStart` hooks then, and Claude Code no longer reads their answer.
154
+ A mod that is still installing when the session starts misses Claude Code's `SessionStart`. cmod holds `SessionStart` up to 9 seconds for the mod to start. When the mod starts later, cmod runs its `SessionStart` hooks then, and Claude Code no longer reads their answer.
153
155
 
154
156
  ## Test a hook
155
157
 
package/docs/index.md CHANGED
@@ -4,7 +4,7 @@ These are the docs of the `@cmodjs/core` version installed beside them. Read the
4
4
 
5
5
  ## What a mod is
6
6
 
7
- A mod is a Claude Code plugin built with `@cmodjs/core`. Claude Mod Manager (CMod) installs mods, runs their install and uninstall steps with the person's consent, and builds and publishes new ones. The `cmod` command does that work in a terminal.
7
+ A mod is a Claude Code plugin built with `@cmodjs/core`. Claude Mod Manager (cmod) installs mods, runs their install and uninstall steps with the person's consent, and builds and publishes new ones. The `cmod` command does that work in a terminal.
8
8
 
9
9
  `cmod new my-mod` writes this layout:
10
10
 
@@ -15,7 +15,7 @@ my-mod/
15
15
  ├── .gitignore node_modules/ and .claude-plugin/types/
16
16
  ├── .oxlintrc.json leaves .claude-plugin/types/ out of the lint
17
17
  ├── hooks/hooks.json names hooks/register.ts as the hooks module
18
- ├── hooks/register.ts connect(on, myMod), and nothing else
18
+ ├── hooks/register.ts registerMod(addHook, myMod), and nothing else
19
19
  ├── src/mod.tsx defineMod: the state, the setup, the hooks, the panes
20
20
  ├── src/state.ts the starting state
21
21
  ├── src/panes/prompts.tsx one definePane per file
@@ -74,7 +74,7 @@ A mod imports from the files below, and every other file in this package is inte
74
74
  | `mod.js` | `Claude`, `RoutedEvent`, `RoutedHook`, `ToolCalls` | [jobs.md](jobs.md) |
75
75
  | `mod.js` | `ModEvent`, `ModHook`, `HookAnswer` | [hooks.md](hooks.md) |
76
76
  | `mod.js` | `notInstalled` | [dependencies.md](dependencies.md) |
77
- | `connect.js` | `connect` | [mod.md](mod.md) |
77
+ | `register.js` | `registerMod`, `registerPermissionCheck` | [mod.md](mod.md) |
78
78
  | `testing.js` | `testMod`, `TestOptions`, `TestInput`, `TestedMod`, `Fakes`, `Shown`, `TestCall` | [testing.md](testing.md) |
79
79
  | `ui/define-pane.js` | `definePane`, `Pane` | [ui.md](ui.md) |
80
80
  | `ui/elements.js` | `Box`, `Text`, `Button`, `Link`, `Code`, `Markdown`, `Input`, `Select`, `Image`, `drawWith` | [ui.md](ui.md) |
@@ -1,6 +1,6 @@
1
1
  # Install steps
2
2
 
3
- A mod that changes the machine, such as by adding a shell alias or downloading a program, names its install and uninstall steps in its `package.json`. The cmod program of Claude Mod Manager (CMod) runs them with the person's consent, and runs the uninstall step after the person removes the mod. The library never runs them.
3
+ A mod that changes the machine, such as by adding a shell alias or downloading a program, names its install and uninstall steps in its `package.json`. The cmod program of Claude Mod Manager (cmod) runs them with the person's consent, and runs the uninstall step after the person removes the mod. The library never runs them.
4
4
 
5
5
  ## The cmod key
6
6
 
@@ -18,20 +18,20 @@ Each key is optional.
18
18
 
19
19
  - `install` is one shell command that sets the mod up.
20
20
  - `uninstall` is one shell command that undoes it.
21
- - `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.
21
+ - `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
22
 
23
- CMod refuses a `cmod` key that breaks these rules, with the fix in the message:
23
+ cmod refuses a `cmod` key that breaks these rules, with the fix in the message:
24
24
 
25
25
  - `cmod` is an object.
26
26
  - `install` and `uninstall` are each one command on one line, with no tab.
27
- - 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/`.
27
+ - 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/`.
28
28
  - A symbolic link in a script folder points at a file.
29
29
  - `program` is a command name: letters, digits, `.`, `_`, and `-`, starting with a letter or digit.
30
30
  - `.claude-plugin/plugin.json` has a `version`.
31
31
 
32
32
  ## Consent
33
33
 
34
- Before a step runs for the first time, CMod shows the person the commands and asks to run them. CMod hashes the commands and every file in each script folder, and remembers the hash the person approved. A change to any of those files asks again.
34
+ Before a step runs for the first time, cmod shows the person the commands and asks to run them. cmod hashes the commands and every file in each script folder, and remembers the hash the person approved. A change to any of those files asks again.
35
35
 
36
36
  - In Claude Code, the mod asks `<mod> runs <install> to install, and <uninstall> when you remove it. Run it now?` with `Install` and `Not now`. A progress line above the prompt shows the install.
37
37
  - 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.`
@@ -42,14 +42,14 @@ The mod starts only once its install step has run ([mod.md](mod.md)).
42
42
 
43
43
  ## How a step runs
44
44
 
45
- CMod runs a step with `sh -c "<command>"` in the mod's folder. So `./setup/install.sh` runs the script itself, and the script must be executable: `chmod +x setup/*.sh`.
45
+ cmod runs a step with `sh -c "<command>"` in the mod's folder. So `./setup/install.sh` runs the script itself, and the script must be executable: `chmod +x setup/*.sh`.
46
46
 
47
47
  The step gets these environment variables:
48
48
 
49
49
  | Variable | Value |
50
50
  | --- | --- |
51
51
  | `CMOD_PLUGIN_ROOT` | The mod's folder |
52
- | `CMOD_DATA` | The mod's data folder, which CMod creates first. It is the folder `mod.dataFolder` names. |
52
+ | `CMOD_DATA` | The mod's data folder, which cmod creates first. It is the folder `mod.dataFolder` names. |
53
53
  | `CMOD_VERSION` | The mod's version from `plugin.json` |
54
54
  | `PATH` | The person's `PATH`, with the cmod program's own folder first, so the step can run `cmod download` |
55
55
 
@@ -63,18 +63,18 @@ The install step runs again on every new version of the mod and whenever its scr
63
63
 
64
64
  ## When the install step fails
65
65
 
66
- - 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.
67
- - When the install step of an upgrade fails or is stopped, CMod keeps the version it set up before.
66
+ - 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.
67
+ - When the install step of an upgrade fails or is stopped, cmod keeps the version it set up before.
68
68
  - The failure shows on the mod's progress line with `Fix the cause, then run: cmod install <mod>`.
69
69
 
70
70
  ## The uninstall step
71
71
 
72
- When the install step succeeds, CMod saves a copy of every script folder the uninstall step names. It runs the uninstall step from that copy, so it runs after the mod's own folder is gone.
72
+ When the install step succeeds, cmod saves a copy of every script folder the uninstall step names. It runs the uninstall step from that copy, so it runs after the mod's own folder is gone.
73
73
 
74
- - After the person removes the mod with `/plugin uninstall`, the CMod plugin runs the uninstall step at the next session start or prompt.
74
+ - After the person removes the mod with `/plugin uninstall`, the cmod plugin runs the uninstall step at the next session start or prompt.
75
75
  - `cmod remove <mod>` uninstalls the mod and runs the step at once.
76
76
  - In the copy, `CMOD_PLUGIN_ROOT` is the copy's folder, and `CMOD_DATA` and `CMOD_VERSION` are the same as at install.
77
- - After the step, CMod deletes the mod's install record, its program, its data folder, and the approvals of its scripts. It keeps the mod's config folders, because the files there are the person's.
77
+ - After the step, cmod deletes the mod's install record, its program, its data folder, and the approvals of its scripts. It keeps the mod's config folders, because the files there are the person's.
78
78
  - A failed uninstall step keeps the record. `cmod teardown <mod>` runs it again.
79
79
 
80
80
  ## Example
@@ -102,7 +102,7 @@ cat "$kept" > "$HOME/.zshrc"
102
102
  rm "$kept"
103
103
  ```
104
104
 
105
- CMod deletes `$CMOD_DATA` itself after the uninstall step.
105
+ cmod deletes `$CMOD_DATA` itself after the uninstall step.
106
106
 
107
107
  ## Download a program in an install step
108
108
 
@@ -122,7 +122,7 @@ cmod download mermaid-ascii \
122
122
 
123
123
  ## Ship a program
124
124
 
125
- A mod that builds its own command-line program keeps its source in `cli/`, and CMod builds it, releases it, and puts it on the person's `PATH`.
125
+ A mod that builds its own command-line program keeps its source in `cli/`, and cmod builds it, releases it, and puts it on the person's `PATH`.
126
126
 
127
127
  1. `cli/package.json` declares the build:
128
128
 
@@ -144,8 +144,8 @@ Then:
144
144
 
145
145
  - `cmod link` builds the program and links this machine's build to `~/.local/bin/<program>`.
146
146
  - `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.
147
- - 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>`.
148
- - CMod refuses to replace a `~/.local/bin/<program>` it did not make, and a program `PATH` already finds somewhere else.
147
+ - 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>`.
148
+ - cmod refuses to replace a `~/.local/bin/<program>` it did not make, and a program `PATH` already finds somewhere else.
149
149
  - `cmod unlink` and the uninstall remove the program.
150
150
 
151
- 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" }`.
151
+ 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/jobs.md CHANGED
@@ -22,7 +22,7 @@ A job's bad options throw at one of two times. `check`, `permissions`, and `prom
22
22
  | `statusLine` | `jobs/status-line.js` | A status line |
23
23
  | `program` | `jobs/program.js` | A background program the mod talks to over HTTP |
24
24
 
25
- Each job announces itself. The first time a version starts, Claude Mod Manager (CMod) logs what the mod added, such as `tasks added /tasks, the open_ticket tool and 2 permission rules.`
25
+ Each job announces itself. The first time a version starts, Claude Mod Manager (cmod) logs what the mod added, such as `tasks added /tasks, the open_ticket tool and 2 permission rules.`
26
26
 
27
27
  ## Deadlines
28
28
 
@@ -57,11 +57,11 @@ type Reply = string | { text?: string; context?: string } | undefined
57
57
  - `description` is the line the typeahead and `/help` show.
58
58
  - `argumentHint` is drawn dim after the name, such as `[path]`.
59
59
  - `immediate: true` runs the command at once while Claude is still working. Without it, the command waits for the turn to end.
60
- - `reply` gets `args` and `positionals`. `args` is everything after the name as Claude Code passes it, and `''` for a bare `/<name>`. CMod hands it on unchanged. `positionals` is that text split into words the way a shell splits them, so extra spaces never reach it. `tested.type('/todo milk')` drops the spaces after the name.
60
+ - `reply` gets `args` and `positionals`. `args` is everything after the name as Claude Code passes it, and `''` for a bare `/<name>`. cmod hands it on unchanged. `positionals` is that text split into words the way a shell splits them, so extra spaces never reach it. `tested.type('/todo milk')` drops the spaces after the name.
61
61
  - A `string` reply, or `text`, shows as the command's output. `context` is text Claude reads after the output, and the person never sees. `undefined` shows nothing.
62
62
  - A `reply` that throws shows `/<name> failed: <error>`.
63
63
  - Two commands with one name in a mod throw `<mod>: the slash command /<name> is already added. Give each slashCommand its own name.`
64
- - When Claude Code refuses to add the command, CMod logs `<mod>: /<name> is not added: <error>`.
64
+ - When Claude Code refuses to add the command, cmod logs `<mod>: /<name> is not added: <error>`.
65
65
 
66
66
  ```ts
67
67
  import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
@@ -99,7 +99,7 @@ tool<Schema, State>(options: {
99
99
 
100
100
  - `name` is 1 to 64 letters, digits, `_`, or `-`. Claude sees the tool as `mcp__<plugin name>__<name>`, and the handle's `name` is that full name.
101
101
  - `description` tells Claude what the tool does.
102
- - `inputSchema` is a JSON Schema with `type: 'object'`. It types `execute`'s `input`, so write it inline. CMod checks each call's input against it, draft 2020-12, and denies an input that does not fit with the list of its errors. Its properties cannot be named `tool`, `tool_use_id`, `consent`, or `agentId`, which Claude Code keeps for itself.
102
+ - `inputSchema` is a JSON Schema with `type: 'object'`. It types `execute`'s `input`, so write it inline. cmod checks each call's input against it, draft 2020-12, and denies an input that does not fit with the list of its errors. Its properties cannot be named `tool`, `tool_use_id`, `consent`, or `agentId`, which Claude Code keeps for itself.
103
103
  - `execute` returns the result Claude reads. A `string` and an array of content blocks go to Claude as they are. Any other value goes as JSON. `undefined` returns nothing.
104
104
  - An input that does not fit the schema is denied with one line per error under a heading, such as `The open_ticket tool input is not valid:` followed by `- #: Instance does not have required property "title".` The text after `- ` is the location in the input, `#` for its root, and the validator's message.
105
105
  - An `execute` that throws denies the call with `The <name> tool failed: <error>`.
@@ -147,13 +147,13 @@ export const tickets = defineMod({
147
147
 
148
148
  ### Command patterns
149
149
 
150
- A `command` pattern is not a glob. CMod splits it into words the way it splits a Bash command, and matches each command of the call:
150
+ A `command` pattern is not a glob. cmod splits it into words the way it splits a Bash command, and matches each command of the call:
151
151
 
152
152
  - `'*'` alone matches every shell command. A `*` inside a pattern is a plain character.
153
153
  - The first word is the program, matched by its name, so `/bin/rm` counts as `rm`.
154
154
  - Every other word that does not start with `-` must appear in the command in the pattern's order, and other words may sit between them: `git push` matches `git push origin main`.
155
155
  - Every flag, a word that starts with `-`, must appear somewhere in the command, in any order. `--force` also matches `--force=yes`.
156
- - CMod splits a cluster of short flags into single flags, in the pattern and in the command alike, so `rm -rf` matches `rm -rf`, `rm -fr`, and `rm -r -f`. A letter that takes a value keeps the rest of the cluster as its value. These programs keep their flags as written: `find`, `java`, `javac`, `go`, `gcc`, `g++`, `clang`, `clang++`, `swift`, `swiftc`, `xcodebuild`, `xcrun`, `ffmpeg`, `ffprobe`, `openssl`, `plutil`, `defaults`, `security`, and `codesign`.
156
+ - cmod splits a cluster of short flags into single flags, in the pattern and in the command alike, so `rm -rf` matches `rm -rf`, `rm -fr`, and `rm -r -f`. A letter that takes a value keeps the rest of the cluster as its value. These programs keep their flags as written: `find`, `java`, `javac`, `go`, `gcc`, `g++`, `clang`, `clang++`, `swift`, `swiftc`, `xcodebuild`, `xcrun`, `ffmpeg`, `ffprobe`, `openssl`, `plutil`, `defaults`, `security`, and `codesign`.
157
157
  - Words and flags match exactly, letter case included.
158
158
  - Each command of a pipeline or a `&&` chain counts, and so does the command a wrapper runs: `sudo rm -rf /` holds the commands `sudo` and `rm -rf /`. The wrappers are `sudo`, `env`, `doas`, `timeout`, `flock`, `stdbuf`, `watch`, `nohup`, `nice`, `time`, `exec`, `command`, and `xargs`.
159
159
 
@@ -168,12 +168,13 @@ type Rule<State> = Target & {
168
168
  }
169
169
  ```
170
170
 
171
- - Before each call, CMod tries every `deny` rule, then every `ask` rule. The first rule that matches decides, and its `reason` goes with the decision. No match leaves the call to Claude Code.
171
+ - Before each call, cmod tries every `deny` rule, then every `ask` rule. The first rule that matches decides, and its `reason` goes with the decision. No match leaves the call to Claude Code.
172
172
  - The strictest answer wins: a mod's `ask` or `deny` beats Claude Code's `allow`, and Claude Code's `deny` beats a mod's `ask`.
173
173
  - `when` narrows a rule. The rule matches only when `when` resolves true. A `when` that throws makes the rule match, and the reason adds `Its when check failed: <error>`.
174
- - When CMod cannot read the call or the session, it denies the call with the reason.
175
- - For a PowerShell command, or a Bash command CMod cannot fully parse, a `command` rule matches when every word of its pattern appears in the command line. A `read` or `write` rule matches when the command line holds the last segment of its pattern, after the last `/`, and only when that segment holds none of `*?[]{}`. So `{ write: 'Domain.md' }` matches such a command that names `Domain.md`, and `{ write: '**/.env*' }` never matches one.
174
+ - When cmod cannot read the call or the session, it denies the call with the reason.
175
+ - For a PowerShell command, or a Bash command cmod cannot fully parse, a `command` rule matches when every word of its pattern appears in the command line. A `read` or `write` rule matches when the command line holds the last segment of its pattern, after the last `/`, and only when that segment holds none of `*?[]{}`. So `{ write: 'Domain.md' }` matches such a command that names `Domain.md`, and `{ write: '**/.env*' }` never matches one.
176
176
  - `permissions({})` throws `permissions: give it a deny or an ask rule, or remove it from setup.`
177
+ - A mod that uses `permissions` calls `registerPermissionCheck(addHook)` after `registerMod` in `hooks/register.ts` ([mod.md](mod.md)). Without it the mod does not start.
177
178
 
178
179
  `when` gets the call, typed by the target's key. `jobs/permissions.js` exports each type:
179
180
 
@@ -214,8 +215,8 @@ export const guard = defineMod({
214
215
  check<State>(options: { readonly after: Target | Target[]; readonly run: readonly string[]; readonly timeoutMs?: number }): Job<void, State>
215
216
  ```
216
217
 
217
- - After each call that matches `after` and succeeds, CMod runs `run`. A call that was denied or failed runs nothing.
218
- - For a `read` or `write` target, CMod adds the matched files that still exist to the end of `run`, as absolute paths. For any other target it runs `run` as it is.
218
+ - After each call that matches `after` and succeeds, cmod runs `run`. A call that was denied or failed runs nothing.
219
+ - For a `read` or `write` target, cmod adds the matched files that still exist to the end of `run`, as absolute paths. For any other target it runs `run` as it is.
219
220
  - A mod installed for the person passes no folder, so Claude Code runs the command in the session's working folder. A project plugin runs it at the root of the work tree that holds the last matched file, or the matched command's folder.
220
221
  - A command that exits with another code than 0 adds `<command> exited with <code>:` and its output to the call's result, so Claude reads the failure right after its own call.
221
222
  - `timeoutMs` is 60 seconds by default and 10 minutes at most.
@@ -283,7 +284,7 @@ type Usage = { model: string; context: { tokens?: number; window: number; percen
283
284
  ```
284
285
 
285
286
  - `text` returns the status line's text, or `undefined` to clear it.
286
- - CMod calls `text` when the mod starts, every `interval` milliseconds (10000 by default), and whenever Claude Code measures the session. It sends the text only when it changed.
287
+ - cmod calls `text` when the mod starts, every `interval` milliseconds (10000 by default), and whenever Claude Code measures the session. It sends the text only when it changed.
287
288
  - A `text` that throws keeps the last text, and the error goes to Claude Code's debug log.
288
289
  - A mod has one status line. A second `statusLine` throws: join the texts in one.
289
290
  - An `interval` below 1 throws.
@@ -316,7 +317,7 @@ type Program = {
316
317
  - `command` is the program on `PATH` and its arguments. `environment` sets variables for it. An empty `command` throws.
317
318
  - The program's first line on standard output must be its address: `127.0.0.1:<port>`, or `unix:<absolute socket path>`. `ready()` then resolves `{ url: 'http://127.0.0.1:<port>' }`, or `{ url: 'http://localhost', socketPath }`. Pass both to `mod.http.fetch`.
318
319
  - In an interactive session the program starts with the mod. Otherwise it starts at the first `ready()`.
319
- - When the program exits, CMod starts it again after 1, 2, 4, 8, and 16 seconds. After 5 failed tries, or a first line that is not an address, the state is `fatal`, `ready()` rejects, and CMod logs `<mod>: the <program> program stopped: <reason>`.
320
+ - When the program exits, cmod starts it again after 1, 2, 4, 8, and 16 seconds. After 5 failed tries, or a first line that is not an address, the state is `fatal`, `ready()` rejects, and cmod logs `<mod>: the <program> program stopped: <reason>`.
320
321
  - A program that ships with the mod gets onto `PATH` through the `program` key of the mod's `package.json` ([install-steps.md](install-steps.md)). A program an install step fetched with `cmod download` is not on `PATH`, so `command` names it by its full path, `${mod.dataFolder}/bin/<program>`.
321
322
 
322
323
  ```ts
@@ -359,12 +360,12 @@ type JobContext<State> = {
359
360
  ```
360
361
 
361
362
  - `claude` reaches Claude Code itself. [The claude members](#the-claude-members) lists each one.
362
- - `claude.store` is where CMod saves `mod.state`, under keys that start with `<mod>.`, such as `<mod>.<key>`. A job of your own never writes those keys, or it changes the mod's state behind CMod's back.
363
- - `on` adds a Claude Code hook-module handler, `(e, next) => …`, on a `classic.<ModEvent>` event or on `tool.check`, `tool.call`, `prompt.submit`, `prompt.context`, `command.run`, `session.measure`, `skill.prompt`, `ui.render`, `ui.press`, `ui.close`, or `cmod.call`. The handler calls `next(e)` to pass the event on.
364
- - `announce` adds a phrase to the list CMod logs when the mod first starts.
363
+ - `claude.store` is where cmod saves `mod.state`, under keys that start with `<mod>.`, such as `<mod>.<key>`. A job of your own never writes those keys, or it changes the mod's state behind cmod's back.
364
+ - `on` adds a Claude Code hook-module handler, `(e, next) => …`, on a `classic.<ModEvent>` event or on `tool.check`, `tool.call`, `prompt.submit`, `prompt.context`, `command.run`, `session.measure`, `skill.prompt`, `ui.render`, `ui.press`, `ui.close`, or `cmod.call`. The handler calls `next(e)` to pass the event on. A job with a handler on `tool.check`, `classic.PreToolUse`, or `classic.PermissionRequest` needs `registerPermissionCheck` in `hooks/register.ts` ([mod.md](mod.md)).
365
+ - `announce` adds a phrase to the list cmod logs when the mod first starts.
365
366
  - `reserveName` throws `taken` when the mod already reserved that `kind` and `name`. A job calls it to refuse a duplicate.
366
367
  - `toolCalls.agentOf(toolUseId)` resolves `{ agentId?, agentType? }`, the subagent behind a call.
367
- - `toolCalls.cwdOf(toolUseId)` returns the working folder at the call's `PreToolUse`, or `undefined` once its `PostToolUse` or `PostToolUseFailure` has run. CMod keeps the folders of the last 100 calls.
368
+ - `toolCalls.cwdOf(toolUseId)` returns the working folder when the call ran, or `undefined` once its `PostToolUse` or `PostToolUseFailure` has run. cmod keeps the folders of the last 100 calls.
368
369
 
369
370
  ```ts
370
371
  import { defineMod, type Job } from '../node_modules/@cmodjs/core/mod.js'
@@ -412,7 +413,7 @@ Each member calls the member of the same name in Claude Code's hooks API, `$`, w
412
413
  | `fs.stat(path, options?)` | Resolves `{ kind, size, mtimeMs, isLink }`, and `realPath` with `{ resolve: true }`. It rejects for a missing path. |
413
414
  | `http.fetch(url, init?)` | What `mod.http.fetch` calls. |
414
415
  | `settings.read(args?)` | What `mod.settings.read` calls. |
415
- | `store.get`, `store.set`, `store.delete`, `store.keys` | The plugin's key-value store, where CMod also keeps `mod.state`. |
416
+ | `store.get`, `store.set`, `store.delete`, `store.keys` | The plugin's key-value store, where cmod also keeps `mod.state`. |
416
417
  | `clock.now()` | Resolves milliseconds since the epoch. |
417
418
  | `clock.after(ms, fn)` | Calls `fn` once after `ms` milliseconds, and returns `{ cancel() }`. |
418
419
  | `clock.every(ms, fn)` | Calls `fn` every `ms` milliseconds, at least 1, and returns `{ cancel() }`. |
package/docs/mod.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Define a mod
2
2
 
3
- `defineMod` describes a mod: its name, its state, the methods it offers other mods, and the `setup` function that attaches everything to Claude Code. `connect` hands the definition to Claude Code.
3
+ `defineMod` describes a mod: its name, its state, the methods it offers other mods, and the `setup` function that attaches everything to Claude Code. `registerMod` registers the definition with Claude Code.
4
4
 
5
5
  ## defineMod
6
6
 
@@ -17,7 +17,7 @@ type ModDefinition<State, Name> = {
17
17
  }
18
18
  ```
19
19
 
20
- - `name` is the mod's name. Use the `name` in `.claude-plugin/plugin.json`. Claude Mod Manager (CMod) keys the saved state and the `state.json` files by this name, and the data folder and the Skill overrides by the plugin's name, so the two must match. `defineMod` throws `defineMod: the mod needs a name, such as the name in .claude-plugin/plugin.json.` for a blank name.
20
+ - `name` is the mod's name. Use the `name` in `.claude-plugin/plugin.json`. Claude Mod Manager (cmod) keys the saved state and the `state.json` files by this name, and the data folder and the Skill overrides by the plugin's name, so the two must match. `defineMod` throws `defineMod: the mod needs a name, such as the name in .claude-plugin/plugin.json.` for a blank name.
21
21
  - `state` holds the starting values, grouped by how long they last. [state.md](state.md) explains the groups.
22
22
  - `api` holds the methods other mods call. [dependencies.md](dependencies.md) explains it.
23
23
  - `setup` runs once when the mod starts. It adds hooks, panes, slot renders, and jobs. It may be `async`.
@@ -41,9 +41,9 @@ export const greeter = defineMod({
41
41
 
42
42
  ## When setup runs
43
43
 
44
- Claude Code loads the mod at the start of each session. CMod then checks that the mod is set up. A mod with an install step that has not run waits for the person's consent and the install first ([install-steps.md](install-steps.md)). Then CMod loads the saved state, runs `setup`, and marks as open each of the mod's panes that Claude Code already shows.
44
+ Claude Code loads the mod at the start of each session. cmod then checks that the mod is set up. A mod with an install step that has not run waits for the person's consent and the install first ([install-steps.md](install-steps.md)). Then cmod loads the saved state, runs `setup`, and marks as open each of the mod's panes that Claude Code already shows.
45
45
 
46
- The first time a version starts, CMod shows the toast `<name> is ready` and logs `<name> added <what setup added>`, such as `the my-mod pane, a render of AbovePrompt and a hook on UserPromptSubmit`.
46
+ The first time a version starts, cmod shows the toast `<name> is ready` and logs `<name> added <what setup added>`, such as `the my-mod pane, a render of AbovePrompt and a hook on UserPromptSubmit`.
47
47
 
48
48
  When a step fails, the mod does not start. Its progress line above the prompt names the failure and the fix:
49
49
 
@@ -51,28 +51,43 @@ When a step fails, the mod does not start. Its progress line above the prompt na
51
51
  - `its state did not load: <error>` when reading the saved values or a `state.json` fails.
52
52
  - `its setup function threw: <error>` when `setup` throws or rejects.
53
53
  - `its open panes did not load: <error>` when Claude Code cannot list the open panes.
54
+ - `it decides permissions on <events>, so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod` when `setup` adds a hook on `PreToolUse` or `PermissionRequest`, or a job that decides permissions, and `register.ts` does not register the permission check.
54
55
 
55
56
  Each of these says `Fix it, then run /reload-plugins.`
56
57
 
57
- ## Connect the mod to Claude Code
58
+ ## Register the mod with Claude Code
58
59
 
59
- `hooks/hooks.json` names `hooks/register.ts` as the plugin's hooks module. `register.ts` calls `connect` and does nothing else:
60
+ `hooks/hooks.json` names `hooks/register.ts` as the plugin's hooks module. `register.ts` calls `registerMod` and does nothing else:
60
61
 
61
62
  ```ts
62
63
  import type { On } from 'claude-code'
63
- import { connect } from '../node_modules/@cmodjs/core/connect.js'
64
+ import { registerMod } from '../node_modules/@cmodjs/core/register.js'
64
65
  import { greeter } from '../src/mod.js'
65
66
 
66
- export function register(on: On): void {
67
- connect(on, greeter)
67
+ export function register(addHook: On): void {
68
+ registerMod(addHook, greeter)
68
69
  }
69
70
  ```
70
71
 
71
72
  ```ts
72
- connect<State>(on: On, definition: ModDefinition<State>): void
73
+ registerMod<State>(addHook: On, definition: ModDefinition<State>): void
74
+ registerPermissionCheck(addHook: On): void
73
75
  ```
74
76
 
75
- `connect` registers one handler per Claude Code event the mod can use, and routes each event to the mod. Keep `register.ts` this small. Claude Code checks a hooks module before it loads it, and refuses some shapes, such as a `$` passed to a function in another module. Declare `register` as a function, as here: `cmod publish` bundles the hooks module into one file, and Claude Code refuses a bundled `register` that is not one.
77
+ `registerMod` registers one handler per Claude Code event the mod can use, and routes each event to the mod. It leaves out Claude Code's permission check, the events `tool.check`, `PreToolUse`, and `PermissionRequest`.
78
+
79
+ A mod that decides permissions calls `registerPermissionCheck` after `registerMod`. That covers a hook on `PreToolUse` or `PermissionRequest`, the `permissions` job, and a job of your own on `tool.check`:
80
+
81
+ ```ts
82
+ export function register(addHook: On): void {
83
+ registerMod(addHook, guard)
84
+ registerPermissionCheck(addHook)
85
+ }
86
+ ```
87
+
88
+ A mod without it has no part in Claude Code's permission check, so the plugin directory reads it as one that never answers a permission. A mod that decides permissions without it does not start, and its progress line names the line to add.
89
+
90
+ Keep `register.ts` this small. Claude Code checks a hooks module before it loads it, and refuses some shapes, such as a `$` passed to a function in another module. The plugin directory reads the bundled module too, and flags it when the name of `register`'s first parameter is declared anywhere else in the bundle. `@cmodjs/core` declares `on` as the method `mod.on`, so name that parameter `addHook`. Declare `register` as a function, as here: `cmod publish` bundles the hooks module into one file, and Claude Code refuses a bundled `register` that is not one.
76
91
 
77
92
  ## What `mod` can call
78
93
 
@@ -100,7 +115,7 @@ type Mod<State> = {
100
115
  | --- | --- | --- |
101
116
  | `name` | The mod's name from `defineMod`. | |
102
117
  | `state` | The mod's values. Assign to a key to change it. | [state.md](state.md) |
103
- | `dataFolder` | The mod's own folder in the CMod store: `$XDG_DATA_HOME/cmod/data/<plugin name>`, or `~/.local/share/cmod/data/<plugin name>` when `XDG_DATA_HOME` is not set. The install step gets the same folder as `CMOD_DATA`. CMod deletes it when the mod is removed. | [install-steps.md](install-steps.md) |
118
+ | `dataFolder` | The mod's own folder in the cmod store: `$XDG_DATA_HOME/cmod/data/<plugin name>`, or `~/.local/share/cmod/data/<plugin name>` when `XDG_DATA_HOME` is not set. The install step gets the same folder as `CMOD_DATA`. cmod deletes it when the mod is removed. | [install-steps.md](install-steps.md) |
104
119
  | `projectRoot` | The project the session works in. It follows `/cd`. | |
105
120
  | `cwd` | The session's working folder. It follows a `cd` in a Bash or PowerShell call, and `/cd`. | |
106
121
  | `on` | Adds a hook on a Claude Code event. | [hooks.md](hooks.md) |
@@ -130,7 +145,7 @@ Both run a program without a shell. `argv[0]` is the program and the rest are it
130
145
  - `spawn` streams `{ stream: 'stdout' | 'stderr', text }` pieces as the program writes them. Its `result` resolves `{ code, signal }` once the stream is read to its end. Its first pull rejects when the program cannot start.
131
146
  - The `for await` loop over a `spawn` is the program's life. Leaving the loop, or calling `return()` on the stream, kills the program, and so does unloading the mod. Returning from the hook does not.
132
147
 
133
- Inside a job, CMod adds the job's deadline to each call ([jobs.md](jobs.md)).
148
+ Inside a job, cmod adds the job's deadline to each call ([jobs.md](jobs.md)).
134
149
 
135
150
  ```ts
136
151
  import { defineMod } from '../node_modules/@cmodjs/core/mod.js'