@cmodjs/core 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/commands.md CHANGED
@@ -251,7 +251,7 @@ Turns one permission of a set-up mod on or off, such as `cmod permission ci-watc
251
251
  cmod settings <mod> <user|project|local> <json> [--project <folder>]
252
252
  ```
253
253
 
254
- Changes Claude Code settings for a set-up mod, as its `settings.user.update`, `settings.project.update`, and `settings.local.update` do ([mod.md](mod.md#settings-claude-code-s-settings-files)), such as `cmod settings keeper user '{"cleanupPeriodDays":365}'`. It writes each top-level key of the JSON object into the user, project, or local settings, and `null` removes the key. The project and local settings are those of `--project`, the current folder when it is left out, and `mcpServers` in the project settings goes to `.mcp.json`. Each key needs the mod's `config` permission for it. cmod keeps every other key and the file's formatting, and `cmod teardown` takes out each value the mod set that is still the same.
254
+ Changes Claude Code settings for a set-up mod, as its `settings.user.update`, `settings.project.update`, and `settings.local.update` do ([mod.md](mod.md#settings-claude-code-s-settings-files)), such as `cmod settings oneclaude user '{"env":{"ANTHROPIC_BASE_URL":"http://127.0.0.1:8317"}}'`. It merges the JSON object into the user, project, or local settings as a JSON Merge Patch: an object merges into the object already there, `null` removes the key, and an array or any other value replaces it. The project and local settings are those of `--project`, the current folder when it is left out, and `mcpServers` in the project settings goes to `.mcp.json`. Each value needs the mod's `config` permission for its key path or a path above it. cmod keeps every other key and the file's formatting, and `cmod teardown` takes out each value the mod set that is still the same, then each object that leaves empty.
255
255
 
256
256
  ### cmod rules
257
257
 
package/docs/mod.md CHANGED
@@ -272,14 +272,17 @@ type Setting = { key: string; label: string; description?: string; kind: 'boolea
272
272
  `settings.user` is the person's own `~/.claude/settings.json`, `settings.project` is the project's `.claude/settings.json`, and `settings.local` is the project's `.claude/settings.local.json`.
273
273
 
274
274
  - `read` answers the values in that one file, such as `(await settings.user.read())['enabledPlugins']`.
275
- - `update` writes each top-level key of `values` into that file. `mcpServers` in `settings.project` goes to the project's `.mcp.json`, where Claude Code reads a project's servers.
276
- - A key replaces its whole value, and `null` removes the key. Every other key, and the file's formatting, stays as it was.
277
- - Each key needs its own `config` permission, such as `"config": ["cleanupPeriodDays"]` ([permissions.md](permissions.md)), and `update` rejects with the line to add for a key without one.
278
- - Removing the mod takes out each value it set that still equals what it set, so a value the person changed since stays.
275
+ - `update` merges `values` into that file as a JSON Merge Patch ([RFC 7396](https://www.rfc-editor.org/rfc/rfc7396)). `mcpServers` in `settings.project` goes to the project's `.mcp.json`, where Claude Code reads a project's servers.
276
+ - An object merges into the object already at that key, at every depth, so every key it does not name stays as it was. `null` removes the key at its depth. An array or any other value replaces the value. To replace a whole object, read it and set each key you drop to `null`.
277
+ - The file's comments and formatting stay as they were.
278
+ - Each value needs a `config` permission for its key path or a path above it ([permissions.md](permissions.md)): `"config": ["env.ANTHROPIC_BASE_URL"]` covers that one value, and `"config": ["env"]` covers every key in `env`. `update` rejects with the line to add for a value no grant covers, and changes nothing.
279
+ - Removing the mod takes out each value it set that still equals what it set, then each object that leaves empty. A value the person changed since stays.
279
280
  - `fs.write` to a Claude Code settings file rejects and names `update`, because a whole-file write loses the person's own edits and leaves the mod's values behind when it is removed.
280
281
 
282
+ A mod that points Claude Code at its local proxy sets one variable in `env`, and the person's other variables stay:
283
+
281
284
  ```ts
282
- await settings.user.update({ cleanupPeriodDays: 365 })
285
+ await settings.user.update({ env: { ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317' } })
283
286
  ```
284
287
 
285
288
  ## model: one question outside the conversation
@@ -24,7 +24,7 @@ They sit in the `package.json` `cmod` key, beside `install` and `keys`, because
24
24
  | `"model": true` | Ask a model, which uses your plan | `model.complete` |
25
25
  | `"tasks": true` | Start tasks that work beside Claude | A session's `startTask`, and a `before(TaskStart)` or `before(TaskEnd)` answer |
26
26
  | `"tools": true` | Use and change Claude's tool calls | A session's `callTool`, a `before(ToolCall)` answer, and a listener or a job that changes a tool call's input |
27
- | `"config": ["cleanupPeriodDays"]` | Change cleanupPeriodDays in your Claude Code settings | `settings.user.update`, `settings.project.update`, and `settings.local.update` of that setting. `mcpServers` covers the project's `.mcp.json` |
27
+ | `"config": ["env.ANTHROPIC_BASE_URL"]` | Change env.ANTHROPIC_BASE_URL in your Claude Code settings | Each value `settings.user.update`, `settings.project.update`, and `settings.local.update` change. A key path covers its own value and every key below it, so `"env"` covers every key in `env`, and `await settings.user.update({ env: { ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317' } })` needs `"env.ANTHROPIC_BASE_URL"` or `"env"`. `mcpServers` covers the project's `.mcp.json` |
28
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
29
 
30
30
  Reading files, drawing panes and renders, toasts, slash commands, tools of the mod's own, and listeners need no permission. Writing a Claude Code settings file with `fs.write` is refused with every permission, and names the `settings` call to use instead ([mod.md](mod.md#settings-claude-code-s-settings-files)).
@@ -56,11 +56,11 @@ type Permissions = {
56
56
  readonly network: { allows(host: string): boolean }
57
57
  readonly run: { allows(program: string): boolean }
58
58
  readonly files: { allows(path: string): boolean }
59
- readonly config: { allows(key: string): boolean }
59
+ readonly config: { allows(keyPath: string): boolean }
60
60
  }
61
61
  ```
62
62
 
63
- `mod.permissions` says whether the person has each permission turned on, so a mod hides a feature instead of failing it. A flag is `true` while it is granted, and `allows` answers for one host, program, path, or settings key:
63
+ `mod.permissions` says whether the person has each permission turned on, so a mod hides a feature instead of failing it. A flag is `true` while it is granted, and `allows` answers for one host, program, path, or settings key path, such as `env.ANTHROPIC_BASE_URL`:
64
64
 
65
65
  ```ts
66
66
  if (mod.permissions.model) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cmodjs/core",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Build Claude Code mods: hooks, panes, permissions, checks, and an install that just works.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/records.d.ts CHANGED
@@ -63,6 +63,8 @@ export declare function readRecord(read: ReadFile, store: string, name: string):
63
63
  export declare function writeRecord(write: (path: string, text: string) => Promise<void>, store: string, record: InstallRecord): Promise<void>;
64
64
  export declare function readSteps(pkg: unknown): Steps | undefined;
65
65
  export declare function permissionWords(item: string): string;
66
+ export declare function settingPaths(values: Readonly<Record<string, unknown>>): string[][];
67
+ export declare function isSettingCovered(granted: Iterable<string>, path: string): boolean;
66
68
  export declare const settingsPagesMethod = "cmod:settingsPages";
67
69
  export declare const changelogPage = "changelog";
68
70
  export declare const openPageMethod = "cmod:openPage";
@@ -72,7 +74,7 @@ export declare function marketplaceOf(root: string, name: string): string | unde
72
74
  export declare function updatesToTurnOn(marketplace: string | undefined, settings: unknown, known: unknown): string | undefined;
73
75
  export declare function consentPath(store: string): string;
74
76
  export declare function parseConsent(value: unknown, path: string): Record<string, string[]>;
75
- export declare const coreVersion = "1.5.1";
77
+ export declare const coreVersion = "1.6.0";
76
78
  export declare function oldestCmodFor(steps: Steps): string;
77
79
  export declare function keyWords(keys: Readonly<Record<string, string>> | undefined): string;
78
80
  export declare function scriptPaths(command: string): string[];
package/records.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["src/records.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"records.d.ts","sourceRoot":"","sources":["src/records.ts"],"names":[],"mappings":"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"}
package/records.js CHANGED
@@ -4,12 +4,12 @@ export const flagPermissions = ['conversation', 'prompt', 'model', 'tasks', 'too
4
4
  const permissionNames = [...listPermissions, ...flagPermissions];
5
5
  const hostName = /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$/;
6
6
  const programName = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
7
- const settingName = /^[A-Za-z][A-Za-z0-9]*$/;
7
+ const settingPath = /^[A-Za-z][A-Za-z0-9]*(\.[A-Za-z0-9_@-]+)*$/;
8
8
  const listItemHint = {
9
9
  network: 'a host alone, such as "api.github.com", or a socket path, such as "/var/run/docker.sock"',
10
10
  run: 'a program\'s command, such as "gh"',
11
11
  files: 'a path that starts with ~/ or /, such as "~/.zshrc"',
12
- config: 'a Claude Code setting\'s name, such as "cleanupPeriodDays"',
12
+ config: 'a Claude Code setting\'s name, or a key path inside one, such as "cleanupPeriodDays" or "env.ANTHROPIC_BASE_URL"',
13
13
  };
14
14
  export const pluginName = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
15
15
  export function storeFolder(env) {
@@ -141,7 +141,7 @@ function readPermissions(steps) {
141
141
  throw new Error(`${at} sets "${name}" to ${JSON.stringify(value)}. Write a list, such as "${name}": ${example}.`);
142
142
  for (const each of value) {
143
143
  const isPath = /^(~\/|\/)/.test(each) && !each.split('/').includes('..');
144
- const isValid = { network: hostName.test(each) || isPath, run: programName.test(each), files: isPath, config: settingName.test(each) }[name];
144
+ const isValid = { network: hostName.test(each) || isPath, run: programName.test(each), files: isPath, config: settingPath.test(each) }[name];
145
145
  if (isValid !== true)
146
146
  throw new Error(`${at} lists "${each}" under "${name}". Write ${listItemHint[name]}.`);
147
147
  items.push(`${name}:${each}`);
@@ -177,6 +177,13 @@ export function permissionWords(item) {
177
177
  return item;
178
178
  }
179
179
  }
180
+ export function settingPaths(values) {
181
+ return Object.entries(values).flatMap(([key, value]) => (isObject(value) && Object.keys(value).length > 0 ? settingPaths(value).map((path) => [key, ...path]) : [[key]]));
182
+ }
183
+ export function isSettingCovered(granted, path) {
184
+ const item = `config:${path}`;
185
+ return [...granted].some((each) => item === each || item.startsWith(`${each}.`));
186
+ }
180
187
  export const settingsPagesMethod = 'cmod:settingsPages';
181
188
  export const changelogPage = 'changelog';
182
189
  export const openPageMethod = 'cmod:openPage';
@@ -220,9 +227,11 @@ function readKeys(keys) {
220
227
  }
221
228
  return { keys: commands };
222
229
  }
223
- export const coreVersion = '1.5.1';
230
+ export const coreVersion = '1.6.0';
224
231
  export function oldestCmodFor(steps) {
225
- if (steps.permissions?.some((item) => item === 'tasks' || item.startsWith('config:')) === true)
232
+ if (steps.permissions?.some((item) => item.startsWith('config:')) === true)
233
+ return '1.6.0';
234
+ if (steps.permissions?.includes('tasks') === true)
226
235
  return '1.0.0';
227
236
  if (steps.permissions !== undefined)
228
237
  return '0.2.0';
@@ -16,7 +16,7 @@ export declare class PermissionRefused extends Error {
16
16
  export declare function programAddresses(mod: object): Set<string>;
17
17
  export declare function checkGrant(grants: Grants, call: string, item: string | undefined): Promise<void>;
18
18
  export declare function checkWrite(grants: Grants, call: string, path: string): Promise<void>;
19
- export declare function checkSettings(grants: Grants, source: 'user' | 'project' | 'local', keys: readonly string[]): Promise<void>;
19
+ export declare function checkSettings(grants: Grants, source: 'user' | 'project' | 'local', values: Readonly<Record<string, unknown>>): Promise<void>;
20
20
  export declare function checkingGrants(claude: Claude, grants: Grants): Claude;
21
21
  export declare function itemOf(name: string, value?: string): string;
22
22
  export declare function isCovered(granted: ReadonlySet<string>, item: string, home: string | undefined): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/runtime/grants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;CAClD,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAI/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAIzD;AAkBD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAItG;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhI;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAkFrE;AAmBD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAUvG;AAiBD,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9G;AA4BD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1C,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAa9G;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BxJ"}
1
+ {"version":3,"file":"grants.d.ts","sourceRoot":"","sources":["../src/runtime/grants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAG7C,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,MAAM,CAAA;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,CAAA;IAC7C,QAAQ,CAAC,aAAa,EAAE,MAAM,WAAW,CAAC,MAAM,CAAC,CAAA;CAClD,CAAA;AAED,qBAAa,iBAAkB,SAAQ,KAAK;CAAG;AAI/C,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAIzD;AAkBD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAItG;AAED,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1F;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAElJ;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAkFrE;AAmBD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAWvG;AAiBD,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAI9G;AA4BD,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;IAChC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAC1C,CAAA;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAEzD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAa9G;AAED,wBAAsB,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CA+BxJ"}
package/runtime/grants.js CHANGED
@@ -1,4 +1,4 @@
1
- import { isObject, permissionWords } from '../records.js';
1
+ import { isObject, isSettingCovered, permissionWords, settingPaths } from '../records.js';
2
2
  import { relativePath } from '../utils/paths.js';
3
3
  import { cmodPluginName } from './dependencies.js';
4
4
  import { actPermission, conversationMethods, isAct, toolPermission } from './sessions.js';
@@ -34,9 +34,9 @@ export async function checkGrant(grants, call, item) {
34
34
  export async function checkWrite(grants, call, path) {
35
35
  await checkGrant(grants, call, writeItem(grants, path));
36
36
  }
37
- export async function checkSettings(grants, source, keys) {
38
- for (const key of keys)
39
- await checkGrant(grants, `settings.${source}.update(${key})`, `config:${key}`);
37
+ export async function checkSettings(grants, source, values) {
38
+ for (const path of settingPaths(values).map((each) => each.join('.')))
39
+ await checkGrant(grants, `settings.${source}.update(${path})`, `config:${path}`);
40
40
  }
41
41
  export function checkingGrants(claude, grants) {
42
42
  const gate = (call, item) => checkGrant(grants, call, item);
@@ -147,6 +147,8 @@ export function isCovered(granted, item, home) {
147
147
  if (split === -1)
148
148
  return false;
149
149
  const name = item.slice(0, split);
150
+ if (name === 'config')
151
+ return isSettingCovered(granted, item.slice(split + 1));
150
152
  const target = expanded(item.slice(split + 1), home);
151
153
  const values = [...granted].filter((each) => each.startsWith(`${name}:`)).map((each) => expanded(each.slice(name.length + 1), home));
152
154
  if (name === 'run')
@@ -645,7 +645,7 @@ async function createMod(definition, runtime) {
645
645
  const settingsFile = (source) => ({
646
646
  read: () => checked.settings.read({ source }),
647
647
  async update(values) {
648
- await checkSettings(grants, source, Object.keys(values));
648
+ await checkSettings(grants, source, values);
649
649
  await claude.cmod.call({ to: cmodPluginName, method: 'updateSettings', input: { mod: definition.name, source, project: modState.root, values } });
650
650
  },
651
651
  });
package/src/records.ts CHANGED
@@ -26,12 +26,12 @@ export type FlagPermission = (typeof flagPermissions)[number]
26
26
  const permissionNames: readonly string[] = [...listPermissions, ...flagPermissions]
27
27
  const hostName = /^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$/
28
28
  const programName = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/
29
- const settingName = /^[A-Za-z][A-Za-z0-9]*$/
29
+ const settingPath = /^[A-Za-z][A-Za-z0-9]*(\.[A-Za-z0-9_@-]+)*$/
30
30
  const listItemHint: Readonly<Record<ListPermission, string>> = {
31
31
  network: 'a host alone, such as "api.github.com", or a socket path, such as "/var/run/docker.sock"',
32
32
  run: 'a program\'s command, such as "gh"',
33
33
  files: 'a path that starts with ~/ or /, such as "~/.zshrc"',
34
- config: 'a Claude Code setting\'s name, such as "cleanupPeriodDays"',
34
+ config: 'a Claude Code setting\'s name, or a key path inside one, such as "cleanupPeriodDays" or "env.ANTHROPIC_BASE_URL"',
35
35
  }
36
36
 
37
37
  export type ReadFile = (path: string) => Promise<string | undefined>
@@ -165,7 +165,7 @@ function readPermissions(steps: Record<string, unknown>): string[] {
165
165
  if (!Array.isArray(value) || value.length === 0 || !value.every((each) => typeof each === 'string')) throw new Error(`${at} sets "${name}" to ${JSON.stringify(value)}. Write a list, such as "${name}": ${example}.`)
166
166
  for (const each of value) {
167
167
  const isPath = /^(~\/|\/)/.test(each) && !each.split('/').includes('..')
168
- const isValid = { network: hostName.test(each) || isPath, run: programName.test(each), files: isPath, config: settingName.test(each) }[name]
168
+ const isValid = { network: hostName.test(each) || isPath, run: programName.test(each), files: isPath, config: settingPath.test(each) }[name]
169
169
  if (isValid !== true) throw new Error(`${at} lists "${each}" under "${name}". Write ${listItemHint[name as ListPermission]}.`)
170
170
  items.push(`${name}:${each}`)
171
171
  }
@@ -202,6 +202,15 @@ export function permissionWords(item: string): string {
202
202
  }
203
203
  }
204
204
 
205
+ export function settingPaths(values: Readonly<Record<string, unknown>>): string[][] {
206
+ return Object.entries(values).flatMap(([key, value]) => (isObject(value) && Object.keys(value).length > 0 ? settingPaths(value).map((path) => [key, ...path]) : [[key]]))
207
+ }
208
+
209
+ export function isSettingCovered(granted: Iterable<string>, path: string): boolean {
210
+ const item = `config:${path}`
211
+ return [...granted].some((each) => item === each || item.startsWith(`${each}.`))
212
+ }
213
+
205
214
  export const settingsPagesMethod = 'cmod:settingsPages'
206
215
 
207
216
  export const changelogPage = 'changelog'
@@ -250,10 +259,11 @@ function readKeys(keys: unknown): Steps {
250
259
  return { keys: commands }
251
260
  }
252
261
 
253
- export const coreVersion = '1.5.1'
262
+ export const coreVersion = '1.6.0'
254
263
 
255
264
  export function oldestCmodFor(steps: Steps): string {
256
- if (steps.permissions?.some((item) => item === 'tasks' || item.startsWith('config:')) === true) return '1.0.0'
265
+ if (steps.permissions?.some((item) => item.startsWith('config:')) === true) return '1.6.0'
266
+ if (steps.permissions?.includes('tasks') === true) return '1.0.0'
257
267
  if (steps.permissions !== undefined) return '0.2.0'
258
268
  return Object.keys(steps.keys ?? {}).length > 0 ? '0.1.12' : '0.0.0'
259
269
  }
@@ -1,4 +1,4 @@
1
- import { isObject, permissionWords } from '../records.js'
1
+ import { isObject, isSettingCovered, permissionWords, settingPaths } from '../records.js'
2
2
  import { relativePath } from '../utils/paths.js'
3
3
  import type { Claude } from './claude.js'
4
4
  import { cmodPluginName } from './dependencies.js'
@@ -53,8 +53,8 @@ export async function checkWrite(grants: Grants, call: string, path: string): Pr
53
53
  await checkGrant(grants, call, writeItem(grants, path))
54
54
  }
55
55
 
56
- export async function checkSettings(grants: Grants, source: 'user' | 'project' | 'local', keys: readonly string[]): Promise<void> {
57
- for (const key of keys) await checkGrant(grants, `settings.${source}.update(${key})`, `config:${key}`)
56
+ export async function checkSettings(grants: Grants, source: 'user' | 'project' | 'local', values: Readonly<Record<string, unknown>>): Promise<void> {
57
+ for (const path of settingPaths(values).map((each) => each.join('.'))) await checkGrant(grants, `settings.${source}.update(${path})`, `config:${path}`)
58
58
  }
59
59
 
60
60
  export function checkingGrants(claude: Claude, grants: Grants): Claude {
@@ -167,6 +167,7 @@ export function isCovered(granted: ReadonlySet<string>, item: string, home: stri
167
167
  const split = item.indexOf(':')
168
168
  if (split === -1) return false
169
169
  const name = item.slice(0, split)
170
+ if (name === 'config') return isSettingCovered(granted, item.slice(split + 1))
170
171
  const target = expanded(item.slice(split + 1), home)
171
172
  const values = [...granted].filter((each) => each.startsWith(`${name}:`)).map((each) => expanded(each.slice(name.length + 1), home))
172
173
  if (name === 'run') return values.includes('*') || values.includes(target)
@@ -685,7 +685,7 @@ async function createMod<State extends object, Declared extends Options>(definit
685
685
  const settingsFile = (source: 'user' | 'project' | 'local'): SettingsFile => ({
686
686
  read: () => checked.settings.read({ source }),
687
687
  async update(values) {
688
- await checkSettings(grants, source, Object.keys(values))
688
+ await checkSettings(grants, source, values)
689
689
  await claude.cmod.call({ to: cmodPluginName, method: 'updateSettings', input: { mod: definition.name, source, project: modState.root, values } })
690
690
  },
691
691
  })