@cmodjs/core 0.1.14 → 0.2.1

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.
Files changed (96) hide show
  1. package/docs/commands.md +32 -21
  2. package/docs/dependencies.md +8 -0
  3. package/docs/index.md +12 -4
  4. package/docs/install-steps.md +36 -4
  5. package/docs/jobs.md +25 -9
  6. package/docs/mod.md +103 -37
  7. package/docs/options.md +93 -0
  8. package/docs/permissions.md +88 -0
  9. package/docs/state.md +4 -31
  10. package/docs/testing.md +13 -2
  11. package/installer.d.ts +10 -0
  12. package/installer.d.ts.map +1 -0
  13. package/installer.js +8 -0
  14. package/jobs/context.d.ts +2 -2
  15. package/jobs/context.d.ts.map +1 -1
  16. package/jobs/context.js +5 -3
  17. package/jobs/permissions.js +1 -1
  18. package/jobs/program.d.ts.map +1 -1
  19. package/jobs/program.js +5 -1
  20. package/jobs/prompt.js +5 -5
  21. package/jobs/schedule.d.ts.map +1 -1
  22. package/jobs/schedule.js +2 -1
  23. package/jobs/slash-command.d.ts.map +1 -1
  24. package/jobs/slash-command.js +3 -2
  25. package/jobs/status-line.d.ts.map +1 -1
  26. package/jobs/status-line.js +3 -2
  27. package/jobs/tool.d.ts.map +1 -1
  28. package/jobs/tool.js +3 -2
  29. package/mod.d.ts +48 -10
  30. package/mod.d.ts.map +1 -1
  31. package/mod.js +3 -0
  32. package/options.d.ts +58 -0
  33. package/options.d.ts.map +1 -0
  34. package/options.js +66 -0
  35. package/package.json +1 -1
  36. package/records.d.ts +16 -1
  37. package/records.d.ts.map +1 -1
  38. package/records.js +119 -7
  39. package/register.d.ts +4 -2
  40. package/register.d.ts.map +1 -1
  41. package/register.js +8 -2
  42. package/runtime/claude.d.ts +5 -1
  43. package/runtime/claude.d.ts.map +1 -1
  44. package/runtime/grants.d.ts +28 -0
  45. package/runtime/grants.d.ts.map +1 -0
  46. package/runtime/grants.js +286 -0
  47. package/runtime/installer.d.ts +24 -0
  48. package/runtime/installer.d.ts.map +1 -0
  49. package/runtime/installer.js +118 -0
  50. package/runtime/lifecycle.d.ts +6 -3
  51. package/runtime/lifecycle.d.ts.map +1 -1
  52. package/runtime/lifecycle.js +226 -36
  53. package/runtime/metadata.d.ts +20 -0
  54. package/runtime/metadata.d.ts.map +1 -0
  55. package/runtime/metadata.js +143 -0
  56. package/runtime/options.d.ts +21 -0
  57. package/runtime/options.d.ts.map +1 -0
  58. package/runtime/options.js +100 -0
  59. package/runtime/state.d.ts +1 -0
  60. package/runtime/state.d.ts.map +1 -1
  61. package/runtime/state.js +5 -75
  62. package/src/installer.ts +17 -0
  63. package/src/jobs/context.ts +5 -3
  64. package/src/jobs/permissions.ts +1 -1
  65. package/src/jobs/program.ts +4 -1
  66. package/src/jobs/prompt.ts +5 -5
  67. package/src/jobs/schedule.ts +2 -1
  68. package/src/jobs/slash-command.ts +3 -2
  69. package/src/jobs/status-line.ts +3 -2
  70. package/src/jobs/tool.ts +3 -2
  71. package/src/mod.ts +46 -11
  72. package/src/options.ts +91 -0
  73. package/src/records.ts +131 -8
  74. package/src/register.ts +12 -3
  75. package/src/runtime/claude.ts +2 -0
  76. package/src/runtime/grants.ts +306 -0
  77. package/src/runtime/installer.ts +169 -0
  78. package/src/runtime/lifecycle.ts +227 -45
  79. package/src/runtime/metadata.ts +153 -0
  80. package/src/runtime/options.ts +102 -0
  81. package/src/runtime/state.ts +7 -70
  82. package/src/testing/fake-claude.ts +6 -1
  83. package/src/testing/fake-files.ts +9 -3
  84. package/src/testing.ts +40 -15
  85. package/src/utils/metadata.ts +242 -0
  86. package/testing/fake-claude.d.ts +4 -0
  87. package/testing/fake-claude.d.ts.map +1 -1
  88. package/testing/fake-claude.js +5 -1
  89. package/testing/fake-files.d.ts.map +1 -1
  90. package/testing/fake-files.js +9 -3
  91. package/testing.d.ts +6 -2
  92. package/testing.d.ts.map +1 -1
  93. package/testing.js +29 -13
  94. package/utils/metadata.d.ts +21 -0
  95. package/utils/metadata.d.ts.map +1 -0
  96. package/utils/metadata.js +250 -0
package/docs/mod.md CHANGED
@@ -5,21 +5,25 @@
5
5
  ## defineMod
6
6
 
7
7
  ```ts
8
- defineMod<State, Name>(definition: ModDefinition<State, Name>): ModDefinition<State, Name>
8
+ defineMod<State, Name, Options>(definition: ModDefinition<State, Name, Options>): ModDefinition<State, Name, Options>
9
9
  ```
10
10
 
11
11
  ```ts
12
- type ModDefinition<State, Name> = {
12
+ type ModDefinition<State, Name, Options> = {
13
13
  readonly name: Name
14
14
  readonly state?: State
15
+ readonly options?: Options
15
16
  readonly api?: { … }
16
- setup(mod: Mod<State>): void | Promise<void>
17
+ readonly installer?: readonly Step<State>[]
18
+ setup(mod: Mod<State, Options>): void | Promise<void>
17
19
  }
18
20
  ```
19
21
 
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.
22
+ - `name` is the mod's name. Use the `name` in `.claude-plugin/plugin.json`. Claude Mod Manager (cmod) keys the saved state and the options 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
23
  - `state` holds the starting values, grouped by how long they last. [state.md](state.md) explains the groups.
24
+ - `options` declares what a person sets, such as a token, and types `mod.options`. [options.md](options.md) explains them.
22
25
  - `api` holds the methods other mods call. [dependencies.md](dependencies.md) explains it.
26
+ - `installer` lists the steps the person finishes in Claude Code after the permissions and the options, such as signing in. [install-steps.md](install-steps.md#steps-in-claude-code) explains them.
23
27
  - `setup` runs once when the mod starts. It adds hooks, panes, slot renders, and jobs. It may be `async`.
24
28
 
25
29
  ```tsx
@@ -41,14 +45,15 @@ export const greeter = defineMod({
41
45
 
42
46
  ## When setup runs
43
47
 
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.
48
+ Claude Code loads the mod at the start of each session. cmod then checks that the mod is set up. A mod the person has not accepted yet opens the installer pane, `Install <name>`, which lists its permissions and the changes it makes to the computer, and waits for Accept ([install-steps.md](install-steps.md)). An option with no default and no value is asked on the next page of the same pane. Then cmod loads the saved state, runs `setup`, marks as open each of the mod's panes that Claude Code already shows, and opens the pane again for each `installer` step the mod declares that is not done.
45
49
 
46
50
  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
51
 
48
52
  When a step fails, the mod does not start. Its progress line above the prompt names the failure and the fix:
49
53
 
50
54
  - `<mod>: state.<group> is not a lifetime. …` or `<mod>: state.<group> is not an object of values. …` when the declared state is not valid ([state.md](state.md)).
51
- - `its state did not load: <error>` when reading the saved values or a `state.json` fails.
55
+ - `its state did not load: <error>` when reading the saved values fails.
56
+ - `it needs <titles>` when the person chose Not now on an option with no default, or no one can answer, as in `claude -p` ([options.md](options.md)). This one says `Set it in /config.` instead, and Claude Code reloads the mod once the value is set.
52
57
  - `its setup function threw: <error>` when `setup` throws or rejects.
53
58
  - `its open panes did not load: <error>` when Claude Code cannot list the open panes.
54
59
  - `it decides permissions on <events>, so hooks/register.ts must call registerPermissionCheck(addHook) after registerMod` when `setup` adds a hook on `PermissionRequest`, or a job that decides permissions, and `register.ts` does not register the permission check.
@@ -60,43 +65,45 @@ Each of these says `Fix it, then run /reload-plugins.`
60
65
  `hooks/hooks.json` names `hooks/register.ts` as the plugin's hooks module. `register.ts` calls `registerMod` and does nothing else:
61
66
 
62
67
  ```ts
63
- import type { On } from 'claude-code'
68
+ import type { On, PluginOptions } from 'claude-code'
64
69
  import { registerMod } from '../node_modules/@cmodjs/core/register.js'
65
70
  import { greeter } from '../src/mod.js'
66
71
 
67
- export function register(addHook: On): void {
68
- registerMod(addHook, greeter)
72
+ export function register(addHook: On, options: PluginOptions): void {
73
+ registerMod(addHook, greeter, options)
69
74
  }
70
75
  ```
71
76
 
72
77
  ```ts
73
- registerMod<State>(addHook: On, definition: ModDefinition<State>): void
78
+ registerMod<State, Options>(addHook: On, definition: ModDefinition<State, string, Options>, options: PluginOptions): void
74
79
  registerPermissionCheck(addHook: On): void
80
+ registeredMod(): ModDefinition | undefined
75
81
  ```
76
82
 
77
- `registerMod` registers one handler per Claude Code event the mod can use, and routes each event to the mod. A `PreToolUse` hook runs on `tool.call`, which `registerMod` registers ([hooks.md](hooks.md)). It leaves out Claude Code's permission check, the events `tool.check` and `PermissionRequest`.
83
+ `registerMod` registers one handler per Claude Code event the mod can use, and routes each event to the mod. `options` is what Claude Code passes `register`: the values of the mod's options ([options.md](options.md)). `registeredMod` returns the definition `registerMod` got, for `cmod check` to read the mod's options without starting it. A `PreToolUse` hook runs on `tool.call`, which `registerMod` registers ([hooks.md](hooks.md)). It leaves out Claude Code's permission check, the events `tool.check` and `PermissionRequest`.
78
84
 
79
85
  A mod that decides permissions calls `registerPermissionCheck` after `registerMod`. That covers a `PreToolUse` hook that answers `permissionDecision: 'allow'` or `'ask'`, a hook on `PermissionRequest`, the `permissions` job, and a job of your own on `tool.check`:
80
86
 
81
87
  ```ts
82
- export function register(addHook: On): void {
83
- registerMod(addHook, guard)
88
+ export function register(addHook: On, options: PluginOptions): void {
89
+ registerMod(addHook, guard, options)
84
90
  registerPermissionCheck(addHook)
85
91
  }
86
92
  ```
87
93
 
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.
94
+ Such a mod also declares `"approve": true` in its `package.json` `"cmod".permissions`, so the person grants it at install. `cmod check` adds the line when it is missing, and without the grant cmod drops every allow or ask the mod answers, so Claude Code decides those calls itself ([permissions.md](permissions.md)). A mod without `registerPermissionCheck` 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
95
 
90
96
  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.
91
97
 
92
98
  ## What `mod` can call
93
99
 
94
- `setup` gets `mod`, of type `Mod<State>`. Every hook, render, and job gets the same `mod`.
100
+ `setup` gets `mod`, of type `Mod<State, Options>`. Every hook, render, and job gets the same `mod`.
95
101
 
96
102
  ```ts
97
- type Mod<State> = {
103
+ type Mod<State, Options> = {
98
104
  readonly name: string
99
105
  readonly state: Readonly<State>
106
+ readonly options: OptionValues<Options>
100
107
  readonly dataFolder: string
101
108
  readonly projectRoot: string
102
109
  readonly cwd: string
@@ -105,13 +112,16 @@ type Mod<State> = {
105
112
  every(ms, hook): Timer
106
113
  readonly ui: { pane, render, toast, progress, ask, scroll }
107
114
  readonly process: { run, spawn }
108
- readonly fs: { read, write, list, exists, stat }
115
+ readonly fs: { read, write, list, exists, stat, find }
116
+ readonly metadata: { read, update }
109
117
  readonly http: { fetch }
110
- readonly settings: { read }
118
+ readonly settings: { page, open }
111
119
  readonly session: { messages, append, submit }
112
120
  readonly agent: { spawn }
113
121
  readonly model: { complete }
122
+ readonly permissions: { has }
114
123
  readonly dependencies: CmodDependencies
124
+ readonly claude: ModClaude
115
125
  }
116
126
  ```
117
127
 
@@ -119,6 +129,7 @@ type Mod<State> = {
119
129
  | --- | --- | --- |
120
130
  | `name` | The mod's name from `defineMod`. | |
121
131
  | `state` | The mod's values. Assign to a key to change it. | [state.md](state.md) |
132
+ | `options` | The values a person set for the mod's options, typed from their declaration. Read-only. | [options.md](options.md) |
122
133
  | `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) |
123
134
  | `projectRoot` | The project the session works in. It follows `/cd`. | |
124
135
  | `cwd` | The session's working folder. It follows a `cd` in a Bash or PowerShell call, and `/cd`. | |
@@ -127,13 +138,16 @@ type Mod<State> = {
127
138
  | `every` | Runs a function every so many milliseconds while the session runs. | below |
128
139
  | `ui` | Panes, slot renders, toasts, progress lines, questions, and scrolling. | [ui.md](ui.md) |
129
140
  | `process` | Runs a program. | below |
130
- | `fs` | Reads, writes, and looks up files. | below |
141
+ | `fs` | Reads, writes, looks up, and finds files. | below |
142
+ | `metadata` | Reads and changes the mod's keys in one file's metadata. | below |
131
143
  | `http` | Fetches a URL. | below |
132
- | `settings` | Reads Claude Code's settings. | below |
144
+ | `settings` | Adds the mod's own pages to `/mods`, and opens them. | below |
133
145
  | `session` | Reads the conversation or a subagent's, adds a note Claude reads, and asks Claude for a turn. | below |
134
146
  | `agent` | Starts a subagent. | below |
135
147
  | `model` | Asks a model one question, outside the conversation. | below |
148
+ | `permissions` | Says whether the person has a permission turned on. `process`, `fs.write`, `metadata.update`, `http`, `session`, `agent`, `model`, and `claude` each check theirs. | [permissions.md](permissions.md) |
136
149
  | `dependencies` | Calls the methods of other mods. | [dependencies.md](dependencies.md) |
150
+ | `claude` | Claude Code's own calls and events, for what `mod` has no member for. | [jobs.md](jobs.md#the-claude-members) |
137
151
 
138
152
  ### mod.every
139
153
 
@@ -195,6 +209,7 @@ mod.fs.write(path: string, text: string): Promise<void>
195
209
  mod.fs.list(path?: string): Promise<FsEntry[]>
196
210
  mod.fs.exists(path: string): Promise<boolean>
197
211
  mod.fs.stat(path: string, options?: { resolve: boolean }): Promise<FsStat>
212
+ mod.fs.find(glob: string | readonly string[]): Promise<readonly FileMatch[]>
198
213
  ```
199
214
 
200
215
  - A relative path is under the session's working folder. An absolute path is used as given. Text is UTF-8.
@@ -204,7 +219,64 @@ mod.fs.stat(path: string, options?: { resolve: boolean }): Promise<FsStat>
204
219
  - `exists` answers whether the path leads to a file or a folder.
205
220
  - `stat` answers `{ kind, size, mtimeMs, isLink }` of what the path leads to, following a symbolic link, and rejects a missing path. With `{ resolve: true }` it also answers `realPath`: the absolute path, every symbolic link followed.
206
221
 
207
- A file the mod ships is under the plugin's folder. `mod` has no member that names that folder, so a job of your own reads it from `claude.plugin.root` ([jobs.md](jobs.md#the-claude-members)). A file the mod keeps for itself belongs in `mod.dataFolder`.
222
+ A file the mod ships is under the plugin's folder, `mod.claude.plugin.root` ([jobs.md](jobs.md#the-claude-members)). A file the mod keeps for itself belongs in `mod.dataFolder`.
223
+
224
+ `find` answers every file a glob matches, each with its metadata, so a mod finds the Skills, agents, scripts, or notes it acts on in one call:
225
+
226
+ ```ts
227
+ type FileMatch = { readonly path: string; readonly name?: string; readonly metadata: Readonly<Record<string, string>>; readonly error?: string }
228
+ ```
229
+
230
+ - A glob starting with `~/.claude` starts at Claude Code's config folder, `CLAUDE_CONFIG_DIR` when it is set. One starting with `~/` starts at home, one starting with `/` is absolute, and any other starts at the project root. Several globs answer each file once.
231
+ - `path` is absolute. `name` is a Markdown file's frontmatter `name`. `metadata` holds the mod's own keys, bare, as `mod.metadata.read` answers them.
232
+ - `find` follows symbolic links into the folders they point at, once each. It skips `.git` and `node_modules` unless the glob names them.
233
+ - A file whose metadata cmod cannot read comes back with empty `metadata` and an `error` naming the line, and cmod logs it once.
234
+ - cmod keeps what it read of each file until the file's modified time or size changes.
235
+
236
+ ```ts
237
+ const skills = await mod.fs.find(['~/.claude/skills/*/SKILL.md', '.claude/skills/*/SKILL.md'])
238
+ const planning = skills.filter((skill) => skill.metadata['modes']?.split(' ').includes('plan'))
239
+ ```
240
+
241
+ ### mod.metadata
242
+
243
+ ```ts
244
+ mod.metadata.read(path: string): Promise<Readonly<Record<string, string>>>
245
+ mod.metadata.update(path: string, change: (metadata: Record<string, string>) => void): Promise<void>
246
+ ```
247
+
248
+ Metadata is a file's own set of string keys, kept in the file, so it moves and is committed with the file. Each mod reads and writes its own keys only: the file stores them as `<mod>.<key>`, and the mod sees them bare. A value is one line of text, and a list is one space-separated string.
249
+
250
+ Where a file keeps its metadata depends on its kind:
251
+
252
+ ```markdown
253
+ ---
254
+ name: plan
255
+ metadata:
256
+ modes.modes: plan build
257
+ ---
258
+ ```
259
+
260
+ ```sh
261
+ #!/bin/sh
262
+ # /// metadata
263
+ # modes.modes: plan build
264
+ # ///
265
+ ```
266
+
267
+ - A Markdown file keeps it in its frontmatter, under `metadata:`.
268
+ - A `.sh`, `.py`, `.ts`, `.js`, `.yaml`, or `.toml` file keeps it in a `/// metadata` comment block after any shebang, in the file's own comment, `#` or `//`, the way PEP 723 keeps a Python script's dependencies.
269
+ - Any other file keeps it in a sidecar, `<file>.meta`.
270
+
271
+ `update` hands `change` the mod's current keys, and writes what `change` leaves. It changes only the metadata block, keeps other mods' keys, writes nothing when nothing changed, and removes an empty block. It writes through a symbolic link to the file it points at. Writing a file outside the project and the mod's data folder needs the `files` permission, as `mod.fs.write` does ([permissions.md](permissions.md)). A key is letters, digits, `_`, `.`, and `-`, and `update` rejects any other key and a value with a line break.
272
+
273
+ ```ts
274
+ await mod.metadata.update('.claude/skills/plan/SKILL.md', (metadata) => {
275
+ metadata['modes'] = 'plan build'
276
+ })
277
+ ```
278
+
279
+ `cmod check` reads the metadata of every file the mod ships, and fails one cmod cannot read.
208
280
 
209
281
  ### mod.http
210
282
 
@@ -218,35 +290,29 @@ mod.http.fetch(url: string, init?: HttpInit): Promise<HttpResponse>
218
290
  ### mod.settings
219
291
 
220
292
  ```ts
221
- mod.settings.read(args?: { source?: 'user' | 'project' | 'local' | 'flag' | 'policy' }): Promise<Settings>
293
+ mod.settings.page(page: Pane<State>): void
294
+ mod.settings.open(pageId?: string): Promise<void>
222
295
  ```
223
296
 
224
- Without a `source` it answers the settings Claude Code runs under, every source merged. With a `source` it answers that one source: `user` is `~/.claude/settings.json`, `project` is `.claude/settings.json`, and `local` is `.claude/settings.local.json`. The answer is keyed as a `settings.json` is, such as `permissions`, `env`, and `enabledPlugins`.
297
+ `/mods` shows each mod's options, permissions, and keys. `page` adds a page of the mod's own to its entry there, drawn as any pane is ([ui.md](ui.md)). `open` opens that page, or with no id opens the mod's entry in `/mods`. `open` rejects for an id `page` did not add.
225
298
 
226
- ```ts
227
- import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
228
-
229
- export const pluginCount = defineMod({
230
- name: 'plugin-count',
231
- setup(mod) {
232
- mod.on('SessionStart', async () => {
233
- const enabled = (await mod.settings.read({ source: 'user' }))['enabledPlugins'] as Record<string, unknown> | undefined
234
- mod.ui.toast(`${Object.keys(enabled ?? {}).length} plugins are enabled`)
235
- })
236
- },
237
- })
299
+ ```tsx
300
+ mod.settings.page(definePane({ id: 'runs', title: 'Runs', render: (current) => <Text>{current.state.session.lastRun}</Text> }))
301
+ mod.use(slashCommand({ name: 'ci-runs', description: 'Show the CI runs', reply: () => void mod.settings.open('runs') }))
238
302
  ```
239
303
 
304
+ Claude Code's own `settings.json` files are read with `mod.claude.settings.read`, such as `(await mod.claude.settings.read({ source: 'user' }))['enabledPlugins']` ([jobs.md](jobs.md#the-claude-members)).
305
+
240
306
  ### mod.session
241
307
 
242
308
  ```ts
243
309
  mod.session.messages(): Promise<SessionMessage[]>
244
- mod.session.messages(args: { agentId?: string; as?: 'api' }): Promise<…>
310
+ mod.session.messages(args: { agentId: string }): Promise<SessionMessage[] | { deny: string }>
245
311
  mod.session.append(text: string): Promise<void>
246
312
  mod.session.submit(text: string): Promise<void>
247
313
  ```
248
314
 
249
- `messages` reads the main conversation, one `{ role, text, toolUses, toolResults }` entry per message. A message has no id of its own, and each tool use carries its `tool_use_id`. `{ agentId }` reads that subagent's conversation instead, the id a `SubagentStart` or `SubagentStop` hook gets as `agent_id`, and answers `{ deny }` when the session cannot read it, so `Array.isArray` tells the two apart. `{ as: 'api' }` reads it in the Messages API form, `{ role, content }` with the content blocks whole.
315
+ `messages` reads the main conversation, one `{ role, text, toolUses, toolResults }` entry per message. A message has no id of its own, and each tool use carries its `tool_use_id`. `{ agentId }` reads that subagent's conversation instead, the id a `SubagentStart` or `SubagentStop` hook gets as `agent_id`, and answers `{ deny }` when the session cannot read it, so `Array.isArray` tells the two apart. The Messages API form, `{ role, content }` with the content blocks whole, is `mod.claude.session.messages({ as: 'api' })`.
250
316
 
251
317
  ```ts
252
318
  mod.on('SubagentStop', async ({ agent_id }) => {
@@ -0,0 +1,93 @@
1
+ # Options
2
+
3
+ An option is a value a person sets for a mod and the mod reads, such as a token, a URL, or how often to check. The mod declares its options in `defineMod`, and `mod.options` holds their values, typed from the declaration. Claude Code stores them: it shows each one in `/config`, keeps a secret in the system keychain, and reloads the mod when one changes.
4
+
5
+ `mod.state` is the other kind of value: what the mod owns and changes as it runs ([state.md](state.md)).
6
+
7
+ ## Declare the options
8
+
9
+ ```ts
10
+ import { defineMod } from '../node_modules/@cmodjs/core/mod.js'
11
+ import { option } from '../node_modules/@cmodjs/core/options.js'
12
+
13
+ export const ciWatch = defineMod({
14
+ name: 'ci-watch',
15
+ options: {
16
+ githubToken: option.secret({ title: 'GitHub token', description: 'Reads your CI runs' }),
17
+ every: option.number({ title: 'Check every', description: 'Minutes between checks', default: 10, min: 1 }),
18
+ branch: option.text({ title: 'Branch', description: 'The branch to watch', default: 'main' }),
19
+ },
20
+ setup(mod) {
21
+ mod.ui.toast(`Watching ${mod.options.branch} every ${mod.options.every} minutes`)
22
+ },
23
+ })
24
+ ```
25
+
26
+ `mod.options.every` is a `number` and `mod.options.branch` a `string`, so `tsc` catches a typo or a wrong type.
27
+
28
+ | Helper | The value | Claude Code shows it as |
29
+ | --- | --- | --- |
30
+ | `option.text({ title, description, default? })` | `string` | a text field |
31
+ | `option.secret({ title, description })` | `string` | a masked field, kept in the keychain |
32
+ | `option.number({ title, description, default?, min?, max? })` | `number` | a number field |
33
+ | `option.toggle({ title, description, default? })` | `boolean`, `false` unless `default` says otherwise | a toggle |
34
+ | `option.choice(choices, { title, description, default? })` | one of `choices` | a picker |
35
+ | `option.folder({ title, description, default? })` | `string`, a folder path | a folder field |
36
+ | `option.file({ title, description, default? })` | `string`, a file path | a file field |
37
+ | `option.list({ title, description, default? })` | `readonly string[]` | a list of text |
38
+
39
+ - An option name is letters, digits, and `_`, starting with a letter, because Claude Code passes each option on as `CLAUDE_PLUGIN_OPTION_<NAME>`. `defineMod` throws for another name.
40
+ - `title` is the label `/config` shows. `description` is the help text under it.
41
+ - A secret has no default, so a token never ships in the mod.
42
+
43
+ ## Where the values come from
44
+
45
+ Each value is, in order of precedence:
46
+
47
+ 1. A value the person's organization set in managed settings. `/config` shows it locked.
48
+ 2. The value in the project's `options.json`.
49
+ 3. The value the person set in `/config`.
50
+ 4. The `default` in the declaration.
51
+
52
+ An option with no default and no value is asked before `setup` runs, on a page of the installer pane: one field per option, each saved to `/config` as the person submits it. A secret is set in `/config` itself, where Claude Code keeps it in the keychain, and the page says so. When the person chooses Not now, or no one can answer, as in `claude -p`, the mod does not start: its progress line says `it needs <title>` and `Set it in /config.`, and Claude Code reloads the mod once the value is set.
53
+
54
+ `cmod install`, `cmod link`, and `cmod try` set options from the terminal with `--option key=value`, once per option ([commands.md](commands.md)).
55
+
56
+ `mod.options` follows the values while the session runs. Read an option where you use it, as in `mod.options.branch`, so a change applies at once. A change in `/config` reloads the mod, and a `/cd` reads the new project's `options.json`.
57
+
58
+ ## Set options for a team
59
+
60
+ A project sets options for everyone who works in it with an `options.json` in its config folder, committed with the repository:
61
+
62
+ ```text
63
+ <project>/.claude/cmods/<mod>/options.json
64
+ ```
65
+
66
+ ```json
67
+ { "branch": "release", "every": 5 }
68
+ ```
69
+
70
+ cmod ignores each entry it cannot use and logs one line naming the file, the entry, and the fix:
71
+
72
+ - a file that is not JSON, or not a JSON object
73
+ - an option the mod does not declare
74
+ - a secret, because the file is committed for everyone to read
75
+ - a value the option does not take, such as `0` for an option with `min: 1`
76
+
77
+ ## plugin.json userConfig
78
+
79
+ Claude Code reads a plugin's options from `userConfig` in `.claude-plugin/plugin.json` before it loads the mod. `cmod check` writes `userConfig` from the options `defineMod` declares, so the declaration is the one place to change them. `cmod publish` refuses a `plugin.json` whose `userConfig` differs: run `cmod check`, then commit `.claude-plugin/plugin.json`.
80
+
81
+ `cmod check` never writes `required`, because Claude Code refuses to load a mod while a required option has no value. cmod asks for an option with no default instead.
82
+
83
+ `userConfigOf(options)`, exported from `options.js`, returns the `userConfig` for a declaration.
84
+
85
+ ## Test options
86
+
87
+ `testMod(mod, { options: { githubToken: 'ghp_test' } })` starts the tested mod with those values, as Claude Code passes them. Each option left out gets its default. A test that leaves out an option with no default fails with `<mod> needs <title>, which a person sets in /config. Give it to the tested mod: testMod(mod, { options: { <key>: … } }).` `fakes.config.list` answers the `/config` rows, so a test can lock a value as managed settings do ([testing.md](testing.md)).
88
+
89
+ ```ts
90
+ const tested = testMod(ciWatch, { options: { githubToken: 'ghp_test', branch: 'develop' } })
91
+ await tested.start()
92
+ expect(tested.shown.toasts).toContain('Watching develop every 10 minutes')
93
+ ```
@@ -0,0 +1,88 @@
1
+ # Permissions
2
+
3
+ A mod declares what it does beyond drawing and answering hooks: the hosts it connects to, the programs it runs, the files it changes outside its project, and whether it reads the conversation, adds text Claude reads, asks a model, starts agents, uses Claude's tools, changes Claude Code's settings, or approves tool calls. The person sees each permission as one sentence when they install the mod, turns each one on or off in `/mods`, and cmod checks every call and every answer against what they granted.
4
+
5
+ Permissions bind every mod built on `@cmodjs/core`, and the plugin directory reads them from `package.json`. They are a contract, not a sandbox: a Claude Code plugin can skip cmod and call Claude Code directly.
6
+
7
+ ## Declare the permissions
8
+
9
+ They sit in the `package.json` `cmod` key, beside `install` and `keys`, because the person reads them before any of the mod's code runs:
10
+
11
+ ```json
12
+ "cmod": {
13
+ "permissions": { "network": ["api.github.com"], "run": ["gh"], "prompt": true, "model": true }
14
+ }
15
+ ```
16
+
17
+ | Permission | The person reads | cmod checks |
18
+ | --- | --- | --- |
19
+ | `"network": ["api.github.com"]` | Connect to api.github.com | `mod.http.fetch` to that host. A path starting with `/` or `~/` names a socket, such as `"~/.docker/run/docker.sock"` |
20
+ | `"run": ["gh"]`, or `"run": "*"` | Run gh on your computer | `mod.process.run`, `mod.process.spawn`, and the `program` and `check` jobs. A program matches by its file name, so `"mermaid-ascii"` covers `${mod.dataFolder}/bin/mermaid-ascii` |
21
+ | `"files": ["~/.zshrc"]` | Change ~/.zshrc | `mod.fs.write` and `mod.metadata.update` outside the project and the mod's data folder. A folder covers every file below it |
22
+ | `"conversation": true` | Read this conversation | `mod.session.messages`, and reading Claude Code's transcript files |
23
+ | `"prompt": true` | Add text Claude reads and start turns | `mod.session.append`, `mod.session.submit`, the `prompt` and `schedule` jobs, and the hook answers listed below |
24
+ | `"model": true` | Ask a model, which uses your plan | `mod.model.complete` |
25
+ | `"agents": true` | Start agents | `mod.agent.spawn` |
26
+ | `"tools": true` | Use and change Claude's tool calls | `mod.claude.tool.call`, and a hook that changes a tool call's input |
27
+ | `"config": true` | Change your Claude Code settings | `mod.claude.config.set`, and writes to `.claude/settings.json`, `.claude/settings.local.json`, and `.mcp.json` |
28
+ | `"approve": true` | Approve Claude's tool calls for you | an allow or an ask from the mod's permission rules or hooks, and a `retry`. A deny needs no permission |
29
+
30
+ Reading files, drawing panes and slot renders, toasts, slash commands, tools of the mod's own, and hooks need no permission.
31
+
32
+ `cmod check` adds `"approve": true` to `package.json` when `hooks/register.ts` calls `registerPermissionCheck` and the line is missing, and says so.
33
+
34
+ ## What the person sees
35
+
36
+ The install opens the installer pane in Claude Code and lists every permission in `permissions`, with the install step, the program, and the keys. An update asks again only when it adds a permission, and lists only the new ones. Removing the mod revokes everything it was granted.
37
+
38
+ `/mods <mod>` lists the same permissions as toggles. A toggle runs `cmod permission <mod> <name> [value] on|off`, and the mod sees the change before its next call, with no reload.
39
+
40
+ `cmod check` prints the sentences, so you read what the person will see:
41
+
42
+ ```text
43
+ ✔ At install the person grants: Connect to api.github.com; Run gh on your computer; Ask a model, which uses your plan
44
+ ```
45
+
46
+ ## Check a permission before a feature uses it
47
+
48
+ ```ts
49
+ mod.permissions.has(name: 'network' | 'run' | 'files', value: string): boolean
50
+ mod.permissions.has(name: 'conversation' | 'prompt' | 'model' | 'agents' | 'tools' | 'config' | 'approve'): boolean
51
+ ```
52
+
53
+ `has` says whether the person has the permission turned on, so a mod hides a feature instead of failing it:
54
+
55
+ ```ts
56
+ if (mod.permissions.has('model')) {
57
+ const verdict = await mod.model.complete({ prompt: 'Is this reply a waste of time?' })
58
+ }
59
+ ```
60
+
61
+ ## When a call has no grant
62
+
63
+ The call rejects, and the error names the fix:
64
+
65
+ - An undeclared call names the line to add: `ci-watch calls http.fetch(https://api.github.com/repos), which needs "permissions": { "network": ["api.github.com"] } in package.json "cmod".`
66
+ - A declared call the person turned off names where to turn it on: `ci-watch calls model.complete without your grant to "Ask a model, which uses your plan". Turn it on in /mods ci-watch.`
67
+ - A `program` job without its grant stops at once with the same line, with no restarts.
68
+
69
+ ## When a hook answer has no grant
70
+
71
+ cmod checks the answer each hook returns, part by part. A part without its grant is dropped, and cmod logs once per permission which part it dropped and how to fix it:
72
+
73
+ ```text
74
+ ci-watch answered additionalContext without your grant to "Add text Claude reads and start turns", so cmod dropped it. Turn it on in /mods ci-watch.
75
+ ```
76
+
77
+ | Answer part | Permission |
78
+ | --- | --- |
79
+ | `additionalContext`, `initialUserMessage`, `suppressOriginalPrompt`, `updatedToolOutput`, a `prompt` job's text | `prompt` |
80
+ | a Stop or PostToolUse block | `prompt` |
81
+ | `updatedInput`, and a changed tool input passed down the chain | `tools` |
82
+ | an allow or an ask, and `retry` | `approve` |
83
+
84
+ A deny needs no permission. A tool of the mod's own, `mcp__<mod>__*`, answers freely.
85
+
86
+ ## Test permissions
87
+
88
+ `testMod` grants what the mod's `package.json` declares, so tests run as an installed mod does. An undeclared call fails the test with the line to add. `testMod(mod, { permissions: ['network:api.github.com'] })` grants exactly the list instead, so a test checks what happens with a permission turned off ([testing.md](testing.md)).
package/docs/state.md CHANGED
@@ -80,16 +80,11 @@ export const reads = defineMod({
80
80
 
81
81
  ## Where the values come from
82
82
 
83
- When the mod starts, each value is, in order of precedence:
83
+ When the mod starts, each value is the value the mod saved for this session, this project, or globally, or else the starting value in `defineMod`. After a `/cd` to another project, cmod loads that project's `project` values.
84
84
 
85
- 1. The value the mod saved for this session, this project, or globally.
86
- 2. The value in the project's `state.json`.
87
- 3. The value in the person's own `state.json`.
88
- 4. The starting value in `defineMod`.
85
+ `mod.state` holds what the mod owns and changes as it runs. What a person sets, such as a token or a branch to watch, is an option ([options.md](options.md)).
89
86
 
90
- After a `/cd` to another project, cmod loads that project's `project` values and reads its `state.json` again.
91
-
92
- ## Let a person change the starting values
87
+ ## Let a person replace a Skill's text
93
88
 
94
89
  Each mod has two config folders, outside its code:
95
90
 
@@ -98,29 +93,7 @@ Each mod has two config folders, outside its code:
98
93
  <project>/.claude/cmods/<mod>/ the team's, committed with the repository
99
94
  ```
100
95
 
101
- `CLAUDE_CONFIG_DIR` moves `~/.claude`. Removing the mod keeps both folders.
102
-
103
- A `state.json` in either folder lists only the values to change:
104
-
105
- ```json
106
- { "global": { "defaultCommitPolicy": "never" }, "session": { "prompts": 10 } }
107
- ```
108
-
109
- - A `session` value is what each new conversation starts with. A `project` value is what each project starts with.
110
- - A file in the project folder wins over the same value in the person's folder.
111
- - A project `state.json` cannot set `global` values, because one repository cannot change a value for every project.
112
- - A file cannot set `memory` values.
113
-
114
- cmod ignores each entry it cannot use and logs one line naming the file, the entry, and the fix:
115
-
116
- - a file that is not JSON, or not a JSON object
117
- - a group the mod does not declare
118
- - a key the mod does not declare
119
- - a value whose type differs from the starting value's
120
- - a `global` value in a project file
121
- - a `memory` value
122
-
123
- ## Let a person replace a Skill's text
96
+ `CLAUDE_CONFIG_DIR` moves `~/.claude`. Removing the mod keeps both folders. The project folder also holds the team's options, in `options.json` ([options.md](options.md)).
124
97
 
125
98
  A person replaces the text of a Skill the mod ships by putting a file at the same path in a config folder:
126
99
 
package/docs/testing.md CHANGED
@@ -3,7 +3,7 @@
3
3
  `testMod` runs a mod against a fake Claude Code in `bun test`, so a test drives the mod the way a person and Claude do and reads what they would see. It needs no running Claude Code.
4
4
 
5
5
  ```ts
6
- testMod<State>(definition: ModDefinition<State>, options?: TestOptions<State>): TestedMod<State>
6
+ testMod<State, Options>(definition: ModDefinition<State, string, Options>, options?: TestOptions<State, Options>): TestedMod<State>
7
7
  ```
8
8
 
9
9
  ```ts
@@ -24,24 +24,30 @@ Assert what a person or Claude sees: `tested.lines(...)`, `tested.shown`, `teste
24
24
  ## TestOptions
25
25
 
26
26
  ```ts
27
- type TestOptions<State> = {
27
+ type TestOptions<State, Options> = {
28
28
  readonly state?: { [Lifetime in keyof State]?: Partial<State[Lifetime]> }
29
+ readonly options?: Partial<OptionValues<Options>>
30
+ readonly permissions?: readonly string[]
29
31
  readonly scope?: 'user' | 'project'
30
32
  readonly projectRoot?: string
31
33
  readonly cwd?: string
32
34
  readonly dependencies?: { [Name in keyof CmodDependencies]?: CmodDependencies[Name] }
33
35
  readonly files?: Readonly<Record<string, string>>
36
+ readonly links?: Readonly<Record<string, string>>
34
37
  }
35
38
  ```
36
39
 
37
40
  | Option | What it sets | Default |
38
41
  | --- | --- | --- |
39
42
  | `state` | Values over the declared starting state | The declared state |
43
+ | `options` | The option values Claude Code passes, as a person set them ([options.md](options.md)) | none: each option gets its default |
44
+ | `permissions` | Exactly the permissions the person granted, such as `['network:api.github.com', 'model']` ([permissions.md](permissions.md)) | every permission the mod's `package.json` declares |
40
45
  | `scope` | `'project'` makes the mod a project plugin, in `<projectRoot>/.claude/skills/<name>` | `'user'` |
41
46
  | `projectRoot` | `mod.projectRoot` | `/test/plugins/<name>`, or `/test/project` for a project plugin |
42
47
  | `cwd` | `mod.cwd` | `projectRoot` |
43
48
  | `dependencies` | The methods other mods answer with | none: every call fails with `<name> is not installed. Run cmod install <name>.` |
44
49
  | `files` | The fake file system, absolute path to text | no files |
50
+ | `links` | Symbolic links in the fake file system, absolute link path to its target | no links |
45
51
 
46
52
  A project plugin finds its repository through a `.git` entry at `projectRoot`, so a test of one puts it in `files`, such as `files: { '/test/project/.git': '' }`. Without it the project plugin has no repository, and its rules match calls in every folder, as a user mod's do.
47
53
 
@@ -213,6 +219,7 @@ type Fakes = {
213
219
  fs: { read?, write?, list?, exists?, stat? }
214
220
  http: { fetch? }
215
221
  settings: { read? }
222
+ config: { list?, set? }
216
223
  ui: { ask?, open?, scroll? }
217
224
  session: { messages?, append? }
218
225
  prompt: { submit? }
@@ -234,7 +241,11 @@ Set a fake to answer a call:
234
241
  - `clock.after(ms, fn)` and `clock.every(ms, fn)` each return `{ cancel() }`, and so must a fake of either. `clock.after` runs a real timer by default. `clock.every` never fires unless a fake keeps `fn` for the test to call, so a test of `mod.every` keeps it and calls it once per tick.
235
242
  - `session.append` and `prompt.submit` take every note and prompt by default, into `shown.notes` and `shown.prompts`. A fake that answers `{ deny }` or `{ drop }` refuses one.
236
243
  - `model.complete` has no default: set it to the answer the model gives, such as `async () => ({ isAnswered: true, text: 'no', usage })`.
244
+ - `config.list` answers no `/config` rows by default, and `config.set` answers every change with `{ value }`. A `config.list` fake that answers a row with `isLocked: true` locks that option as managed settings do.
237
245
  - `tool.call` answers `CronCreate`, `CronDelete`, and `CronList` by default, keeping the crons a `schedule` makes in `shown.schedules`. A call to any other tool needs a fake.
246
+ - `files` takes symbolic links too: `testMod(mod, { files, links: { '/work/skills': '/shared/skills' } })` makes `/work/skills` a link to `/shared/skills`, so a test of `mod.fs.find` or `mod.metadata.update` follows it as a real file system does.
247
+ - `testMod` runs no installer pane: an option with no default and no value fails `start()` with its fix, and the `installer` steps are not shown. Test a step's `render` and `isDone` by calling them with the started `mod`.
248
+ - The `committer` below runs `git`, so its `package.json` declares `"permissions": { "run": ["git"] }`. A gated call the mod does not declare fails before it reaches a fake ([permissions.md](permissions.md)).
238
249
 
239
250
  ```ts
240
251
  import { expect, test } from 'bun:test'
package/installer.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { Frozen, RenderElement, RenderPropsOf } from 'claude-code';
2
+ import type { Mod } from './mod.js';
3
+ export type Step<State extends object = Record<never, never>> = {
4
+ readonly id: string;
5
+ readonly title: string;
6
+ render(mod: Mod<State>, props: Frozen<RenderPropsOf['Pane']>): RenderElement;
7
+ isDone(mod: Mod<State>): boolean | Promise<boolean>;
8
+ };
9
+ export declare function defineStep<State extends object = Record<never, never>>(step: Step<State>): Step<State>;
10
+ //# sourceMappingURL=installer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installer.d.ts","sourceRoot":"","sources":["src/installer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAEnC,MAAM,MAAM,IAAI,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI;IAC9D,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,aAAa,CAAA;IAC5E,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACpD,CAAA;AAID,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAItG"}
package/installer.js ADDED
@@ -0,0 +1,8 @@
1
+ const stepId = /^[A-Za-z0-9_-]{1,64}$/;
2
+ export function defineStep(step) {
3
+ if (!stepId.test(step.id))
4
+ throw new Error(`defineStep: "${step.id}" is not a step id. Use 1 to 64 letters, digits, "_", or "-".`);
5
+ if (step.title.trim() === '')
6
+ throw new Error(`defineStep: the step "${step.id}" needs a title, the line the installer shows above it.`);
7
+ return step;
8
+ }
package/jobs/context.d.ts CHANGED
@@ -3,12 +3,12 @@ import { type Deadline } from '../runtime/deadline.js';
3
3
  import type { ToolCall } from '../utils/call-effects.js';
4
4
  import { type Workspace } from './permissions/find-project-scope.js';
5
5
  import { type Target } from './permissions/match-target.js';
6
- export declare function workspaceReader({ claude }: JobContext): () => Promise<Workspace>;
6
+ export declare function workspaceReader({ mod: { claude } }: JobContext): () => Promise<Workspace>;
7
7
  export declare function afterCall<State extends object>(job: JobContext<State>, contextAfter: (call: ToolCall, workspace: Workspace) => Promise<readonly string[]>, failed: (error: unknown) => readonly string[]): void;
8
8
  export declare function modOf<State extends object>(job: JobContext<State>, { call, folder }: {
9
9
  readonly call?: ToolCall;
10
10
  readonly folder: string;
11
11
  }, workspace: Workspace, deadline: Deadline): Mod<State>;
12
- export declare function modWithin<State extends object>({ mod, claude }: JobContext<State>, deadline: Deadline, workTree?: () => Promise<string | undefined>): Mod<State>;
12
+ export declare function modWithin<State extends object>({ mod }: JobContext<State>, deadline: Deadline, workTree?: () => Promise<string | undefined>): Mod<State>;
13
13
  export declare function targetWords(target: Target): string;
14
14
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/jobs/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAEhD,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAItE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAA;AACtF,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAOrE,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAOhF;AAaD,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAC5C,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,EAClF,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,MAAM,EAAE,GAC5C,IAAI,CAUN;AAMD,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAEjM;AAED,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAyB,GAAG,GAAG,CAAC,KAAK,CAAC,CA0BvL;AAwBD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASlD"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/jobs/context.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAEhD,OAAO,EAAkB,KAAK,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AAItE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AACxD,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,qCAAqC,CAAA;AACtF,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,+BAA+B,CAAA;AAOrE,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAOzF;AAaD,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAC5C,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,YAAY,EAAE,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,EAClF,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,MAAM,EAAE,GAC5C,IAAI,CAUN;AAMD,wBAAgB,KAAK,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,CAEjM;AAED,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAAE,EAAE,GAAG,EAAE,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAyB,GAAG,GAAG,CAAC,KAAK,CAAC,CA4B/K;AAwBD,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CASlD"}