@cmodjs/core 0.1.13 → 0.2.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 +31 -19
- package/docs/index.md +14 -4
- package/docs/install-steps.md +30 -4
- package/docs/jobs.md +25 -9
- package/docs/mod.md +103 -37
- package/docs/options.md +93 -0
- package/docs/permissions.md +88 -0
- package/docs/state.md +4 -31
- package/docs/testing.md +13 -2
- package/installer.d.ts +10 -0
- package/installer.d.ts.map +1 -0
- package/installer.js +8 -0
- package/jobs/context.d.ts +2 -2
- package/jobs/context.d.ts.map +1 -1
- package/jobs/context.js +5 -3
- package/jobs/permissions.js +1 -1
- package/jobs/program.d.ts.map +1 -1
- package/jobs/program.js +5 -1
- package/jobs/prompt.js +5 -5
- package/jobs/schedule.d.ts.map +1 -1
- package/jobs/schedule.js +2 -1
- package/jobs/slash-command.d.ts.map +1 -1
- package/jobs/slash-command.js +3 -2
- package/jobs/status-line.d.ts.map +1 -1
- package/jobs/status-line.js +3 -2
- package/jobs/tool.d.ts.map +1 -1
- package/jobs/tool.js +3 -2
- package/mod.d.ts +48 -10
- package/mod.d.ts.map +1 -1
- package/mod.js +3 -0
- package/options.d.ts +58 -0
- package/options.d.ts.map +1 -0
- package/options.js +66 -0
- package/package.json +1 -1
- package/records.d.ts +14 -1
- package/records.d.ts.map +1 -1
- package/records.js +106 -7
- package/register.d.ts +4 -2
- package/register.d.ts.map +1 -1
- package/register.js +8 -2
- package/runtime/claude.d.ts +5 -1
- package/runtime/claude.d.ts.map +1 -1
- package/runtime/grants.d.ts +28 -0
- package/runtime/grants.d.ts.map +1 -0
- package/runtime/grants.js +286 -0
- package/runtime/installer.d.ts +23 -0
- package/runtime/installer.d.ts.map +1 -0
- package/runtime/installer.js +113 -0
- package/runtime/lifecycle.d.ts +6 -3
- package/runtime/lifecycle.d.ts.map +1 -1
- package/runtime/lifecycle.js +220 -36
- package/runtime/metadata.d.ts +20 -0
- package/runtime/metadata.d.ts.map +1 -0
- package/runtime/metadata.js +143 -0
- package/runtime/options.d.ts +21 -0
- package/runtime/options.d.ts.map +1 -0
- package/runtime/options.js +100 -0
- package/runtime/state.d.ts +1 -0
- package/runtime/state.d.ts.map +1 -1
- package/runtime/state.js +5 -75
- package/src/installer.ts +17 -0
- package/src/jobs/context.ts +5 -3
- package/src/jobs/permissions.ts +1 -1
- package/src/jobs/program.ts +4 -1
- package/src/jobs/prompt.ts +5 -5
- package/src/jobs/schedule.ts +2 -1
- package/src/jobs/slash-command.ts +3 -2
- package/src/jobs/status-line.ts +3 -2
- package/src/jobs/tool.ts +3 -2
- package/src/mod.ts +46 -11
- package/src/options.ts +91 -0
- package/src/records.ts +117 -8
- package/src/register.ts +12 -3
- package/src/runtime/claude.ts +2 -0
- package/src/runtime/grants.ts +306 -0
- package/src/runtime/installer.ts +163 -0
- package/src/runtime/lifecycle.ts +220 -45
- package/src/runtime/metadata.ts +153 -0
- package/src/runtime/options.ts +102 -0
- package/src/runtime/state.ts +7 -70
- package/src/testing/fake-claude.ts +6 -1
- package/src/testing/fake-files.ts +9 -3
- package/src/testing.ts +40 -15
- package/src/utils/metadata.ts +242 -0
- package/testing/fake-claude.d.ts +4 -0
- package/testing/fake-claude.d.ts.map +1 -1
- package/testing/fake-claude.js +5 -1
- package/testing/fake-files.d.ts.map +1 -1
- package/testing/fake-files.js +9 -3
- package/testing.d.ts +6 -2
- package/testing.d.ts.map +1 -1
- package/testing.js +29 -13
- package/utils/metadata.d.ts +21 -0
- package/utils/metadata.d.ts.map +1 -0
- package/utils/metadata.js +250 -0
package/runtime/lifecycle.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { dataFolder, isAtLeast, oldestCmodFor, parseEvent, readRecord, readSteps, scriptsSha256, storeFolder } from '../records.js';
|
|
1
|
+
import { consentPath, dataFolder, finishStepsMethod, isAtLeast, oldestCmodFor, openPageMethod, parseConsent, pendingStepsMethod, parseEvent, permissionWords, readRecord, readSteps, scriptsSha256, settingsPagesMethod, storeFolder } from '../records.js';
|
|
2
2
|
import { relativePath } from '../utils/paths.js';
|
|
3
3
|
import { formatExit, listed, messageOf } from '../utils/text.js';
|
|
4
4
|
import { beforeDeadline } from './deadline.js';
|
|
5
5
|
import { answerCall, dependencyCalls, notInstalled } from './dependencies.js';
|
|
6
6
|
import { classicHook, heldDecisionHook, permissionEvents, preToolUseHook, userSkillHook } from './hooks.js';
|
|
7
|
+
import { checkedAnswer, checkingGrants, checksAnswers, checkWrite, isCovered, itemOf, passedDown } from './grants.js';
|
|
8
|
+
import { createModFiles } from './metadata.js';
|
|
9
|
+
import { fitsOption } from '../options.js';
|
|
10
|
+
import { createInstaller } from './installer.js';
|
|
11
|
+
import { createOptions, MissingOptions } from './options.js';
|
|
7
12
|
import { createRouter } from './router.js';
|
|
8
13
|
import { createState } from './state.js';
|
|
9
14
|
import { toolCalls } from './tool-calls.js';
|
|
@@ -30,12 +35,14 @@ export async function readPlugin(claude) {
|
|
|
30
35
|
const manifest = (await readJson(read, `${root}/.claude-plugin/plugin.json`));
|
|
31
36
|
const version = typeof manifest?.version === 'string' ? manifest.version : undefined;
|
|
32
37
|
const store = storeFolder({ HOME: await claude.env.home(), XDG_DATA_HOME: await claude.env.dataHome() });
|
|
38
|
+
const steps = readSteps(await readJson(read, `${root}/package.json`)) ?? {};
|
|
39
|
+
const declared = steps.permissions ?? [];
|
|
33
40
|
if (name === cmodPluginName) {
|
|
34
41
|
const installed = await cmodVersion(claude);
|
|
35
|
-
return { name, root, version, store,
|
|
42
|
+
return { name, root, version, store, steps, granted: declared, isInstalled: version !== undefined && installed !== undefined && isAtLeast(installed, version), shouldRecord: false };
|
|
36
43
|
}
|
|
37
|
-
const
|
|
38
|
-
const plugin = { name, root, version, store,
|
|
44
|
+
const approved = parseConsent(await readJson(read, consentPath(store)), consentPath(store))[name] ?? [];
|
|
45
|
+
const plugin = { name, root, version, store, steps, granted: declared.filter((item) => approved.includes(item)) };
|
|
39
46
|
const record = await readRecord(read, store, name);
|
|
40
47
|
if (Object.keys(steps).length === 0)
|
|
41
48
|
return { ...plugin, isInstalled: true, shouldRecord: record?.version !== version };
|
|
@@ -79,15 +86,18 @@ async function cmodVersion(claude) {
|
|
|
79
86
|
throw new Error(`cmod --version ${formatExit(result.exitCode, lastLineOf(result.stderr) ?? '')}`);
|
|
80
87
|
return result.stdout.trim().split(/\s+/).at(-1);
|
|
81
88
|
}
|
|
82
|
-
export function createLifecycle(definition, checksPermissions = () => true) {
|
|
89
|
+
export function createLifecycle(definition, checksPermissions = () => true, options = {}, asksPerson = true) {
|
|
83
90
|
let router = createRouter();
|
|
84
91
|
let phase = 'starting';
|
|
85
92
|
let runtime;
|
|
86
93
|
let plugin;
|
|
94
|
+
const granted = new Set();
|
|
87
95
|
let line;
|
|
88
96
|
let activation;
|
|
89
97
|
let mod;
|
|
90
98
|
let failure;
|
|
99
|
+
let pluginOptions = options;
|
|
100
|
+
const installer = createInstaller(definition.name, () => claude());
|
|
91
101
|
let shouldRecord = false;
|
|
92
102
|
let recording;
|
|
93
103
|
let missedSessionStart;
|
|
@@ -95,7 +105,7 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
95
105
|
const startSettled = new Promise((resolve) => (settleStart = resolve));
|
|
96
106
|
const claude = () => {
|
|
97
107
|
if (runtime === undefined)
|
|
98
|
-
throw new Error(`${definition.name}: the lifecycle has not started. registerMod(addHook, mod) starts it at session.start.`);
|
|
108
|
+
throw new Error(`${definition.name}: the lifecycle has not started. registerMod(addHook, mod, options) starts it at session.start.`);
|
|
99
109
|
return runtime.claude;
|
|
100
110
|
};
|
|
101
111
|
const showLine = () => {
|
|
@@ -132,20 +142,71 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
132
142
|
recording = undefined;
|
|
133
143
|
});
|
|
134
144
|
};
|
|
145
|
+
const saveOption = async (key, text) => {
|
|
146
|
+
const option = definition.options?.[key];
|
|
147
|
+
if (option === undefined || plugin === undefined)
|
|
148
|
+
return `${definition.name} has no option ${key}.`;
|
|
149
|
+
const value = option.kind === 'number' ? Number(text) : option.kind === 'toggle' ? text.trim() === 'true' : option.kind === 'list' ? text.split(',').map((item) => item.trim()).filter((item) => item !== '') : text;
|
|
150
|
+
const misfit = option.kind === 'toggle' && !['true', 'false'].includes(text.trim()) ? 'takes true or false' : fitsOption(option, value);
|
|
151
|
+
if (misfit !== undefined)
|
|
152
|
+
return `${option.title} ${misfit}.`;
|
|
153
|
+
const saved = await claude().config.set({ key: `${plugin.name}.${key}`, value });
|
|
154
|
+
return saved.deny;
|
|
155
|
+
};
|
|
156
|
+
const askOptions = async (missing) => {
|
|
157
|
+
if (!asksPerson || plugin === undefined || (await claude().session.surfaces()).length === 0)
|
|
158
|
+
return false;
|
|
159
|
+
const declared = definition.options ?? {};
|
|
160
|
+
const asked = missing.keys.flatMap((key) => {
|
|
161
|
+
const option = declared[key];
|
|
162
|
+
return option === undefined ? [] : [[key, option]];
|
|
163
|
+
});
|
|
164
|
+
if (!(await installer.options(asked, saveOption)))
|
|
165
|
+
return false;
|
|
166
|
+
const prefix = `${plugin.name}.`;
|
|
167
|
+
const rows = await claude().config.list();
|
|
168
|
+
pluginOptions = { ...pluginOptions, ...Object.fromEntries(rows.filter((row) => row.key.startsWith(prefix)).map((row) => [row.key.slice(prefix.length), row.value])) };
|
|
169
|
+
return true;
|
|
170
|
+
};
|
|
171
|
+
const pendingSteps = async (active) => {
|
|
172
|
+
const pending = [];
|
|
173
|
+
for (const step of definition.installer ?? [])
|
|
174
|
+
if (!(await Promise.resolve(step.isDone(active)).catch(() => false)))
|
|
175
|
+
pending.push(step.title);
|
|
176
|
+
return pending;
|
|
177
|
+
};
|
|
178
|
+
const runSteps = async (active) => {
|
|
179
|
+
const steps = asksPerson ? (definition.installer ?? []) : [];
|
|
180
|
+
for (const [index, step] of steps.entries()) {
|
|
181
|
+
if (await Promise.resolve(step.isDone(active)).catch(() => false))
|
|
182
|
+
continue;
|
|
183
|
+
const isAnswered = (await claude().session.surfaces()).length > 0 && (await installer.step(active, step, index + 1, steps.length));
|
|
184
|
+
if (!isAnswered) {
|
|
185
|
+
claude().ui.log(`${definition.name} needs ${steps.length - index === 1 ? 'one more step' : `${steps.length - index} more steps`}, starting with ${step.title}. Run /mods ${definition.name} to finish.`);
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
await installer.close();
|
|
190
|
+
};
|
|
135
191
|
const activate = async () => {
|
|
136
192
|
if (runtime === undefined || plugin === undefined)
|
|
137
193
|
return;
|
|
138
194
|
const activeRouter = createRouter();
|
|
139
|
-
const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name),
|
|
140
|
-
|
|
195
|
+
const active = await createMod(definition, { ...runtime, router: activeRouter, dataFolder: dataFolder(plugin.store, plugin.name), steps: plugin.steps, granted, refreshGrants, checksPermissions, options: pluginOptions }).catch(async (error) => {
|
|
196
|
+
if (error instanceof MissingOptions && (await askOptions(error)))
|
|
197
|
+
return undefined;
|
|
198
|
+
fail(messageOf(error), error instanceof MissingOptions ? `Set ${error.keys.length === 1 ? 'it' : 'them'} in /config.` : 'Fix it, then run /reload-plugins.');
|
|
141
199
|
throw error;
|
|
142
200
|
});
|
|
201
|
+
if (active === undefined)
|
|
202
|
+
return activate();
|
|
143
203
|
const { added } = active;
|
|
144
204
|
mod = active.mod;
|
|
145
205
|
router = activeRouter;
|
|
146
206
|
phase = 'active';
|
|
147
207
|
runtime.claude.ui.invalidate('ui.render');
|
|
148
208
|
endLine();
|
|
209
|
+
void runSteps(active.mod).catch((error) => claude().ui.log(`${definition.name}: its installer stopped: ${messageOf(error)}`));
|
|
149
210
|
const missed = missedSessionStart;
|
|
150
211
|
missedSessionStart = undefined;
|
|
151
212
|
const lateAnswer = missed === undefined ? {} : await activeRouter.dispatch('classic.SessionStart', missed, async () => ({})).catch((error) => {
|
|
@@ -171,14 +232,9 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
171
232
|
return;
|
|
172
233
|
}
|
|
173
234
|
showLine().wait('Waiting for your answer');
|
|
174
|
-
|
|
175
|
-
...(event.install === '' ? [] : [`runs ${event.install} to install`]),
|
|
176
|
-
...(event.uninstall === '' ? [] : [`runs ${event.uninstall} when you remove it`]),
|
|
177
|
-
...(event.keys === '' ? [] : [`binds ${event.keys}`]),
|
|
178
|
-
];
|
|
179
|
-
const answer = await claude().ui.ask(`${name} ${listed(actions)}. Install it now?`, { options: ['Install', 'Not now'], header: 'Install' });
|
|
180
|
-
if (answer === 'Install')
|
|
235
|
+
if (await installer.consent(event))
|
|
181
236
|
return install(event.sha256);
|
|
237
|
+
await installer.close();
|
|
182
238
|
phase = 'declined';
|
|
183
239
|
endLine();
|
|
184
240
|
claude().ui.log(`${name} is not installed. Run cmod install ${name} to install it.`);
|
|
@@ -203,14 +259,17 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
203
259
|
if (event.kind === 'done' || event.kind === 'failed' || event.kind === 'needs-consent')
|
|
204
260
|
outcome = event;
|
|
205
261
|
});
|
|
206
|
-
if (outcome?.kind === 'done')
|
|
262
|
+
if (outcome?.kind === 'done') {
|
|
263
|
+
for (const item of plugin.steps.permissions ?? [])
|
|
264
|
+
granted.add(item);
|
|
207
265
|
return finish();
|
|
266
|
+
}
|
|
208
267
|
if (outcome?.kind === 'needs-consent')
|
|
209
268
|
return askConsent(outcome);
|
|
210
269
|
const reason = outcome?.kind === 'failed' ? outcome.message : `cmod setup ${formatExit(code, lastError)}`;
|
|
211
270
|
fail(reason, `Fix the cause, then run: cmod install ${definition.name}`);
|
|
212
271
|
};
|
|
213
|
-
const fits = (found) => found !== undefined && isAtLeast(found, oldestCmodFor(plugin?.
|
|
272
|
+
const fits = (found) => found !== undefined && isAtLeast(found, oldestCmodFor(plugin?.steps ?? {}));
|
|
214
273
|
const waitForcmod = (found) => {
|
|
215
274
|
phase = 'waiting';
|
|
216
275
|
showLine().wait('Waiting for cmod');
|
|
@@ -237,7 +296,7 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
237
296
|
});
|
|
238
297
|
const longWait = claude().clock.after(cmodWaitMs, () => showLine().wait(latest === undefined
|
|
239
298
|
? "Still waiting for cmod to download cmod. See cmod's own line."
|
|
240
|
-
: `PATH finds cmod ${latest}, and ${definition.name} needs cmod ${oldestCmodFor(plugin?.
|
|
299
|
+
: `PATH finds cmod ${latest}, and ${definition.name} needs cmod ${oldestCmodFor(plugin?.steps ?? {})} or later. Run npm i -g @cmodjs/cli, or put ~/.local/bin ahead of the old cmod on PATH.`));
|
|
241
300
|
};
|
|
242
301
|
const bootstrap = async (root) => {
|
|
243
302
|
const progress = showLine();
|
|
@@ -256,6 +315,47 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
256
315
|
if (phase !== 'failed')
|
|
257
316
|
fail(messageOf(error), `Run cmod install ${definition.name} in a terminal to see the whole log.`);
|
|
258
317
|
};
|
|
318
|
+
let consentMs = 0;
|
|
319
|
+
const consentStamp = async (path) => {
|
|
320
|
+
const stat = await claude().fs.stat(path).catch(() => undefined);
|
|
321
|
+
return stat?.kind === 'file' ? stat.mtimeMs : 0;
|
|
322
|
+
};
|
|
323
|
+
const refreshGrants = async () => {
|
|
324
|
+
if (plugin === undefined || plugin.name === cmodPluginName)
|
|
325
|
+
return;
|
|
326
|
+
const path = consentPath(plugin.store);
|
|
327
|
+
const modifiedMs = await consentStamp(path);
|
|
328
|
+
if (modifiedMs === consentMs)
|
|
329
|
+
return;
|
|
330
|
+
consentMs = modifiedMs;
|
|
331
|
+
const approved = modifiedMs === 0 ? [] : (parseConsent(JSON.parse(await claude().fs.read(path)), path)[plugin.name] ?? []);
|
|
332
|
+
granted.clear();
|
|
333
|
+
for (const item of plugin.steps.permissions ?? []) {
|
|
334
|
+
if (approved.includes(item))
|
|
335
|
+
granted.add(item);
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
const droppedParts = new Set();
|
|
339
|
+
const answerCheck = {
|
|
340
|
+
get plugin() {
|
|
341
|
+
return plugin?.name ?? definition.name;
|
|
342
|
+
},
|
|
343
|
+
isGranted: (item) => isCovered(granted, item, undefined),
|
|
344
|
+
dropped(item, part) {
|
|
345
|
+
if (droppedParts.has(item))
|
|
346
|
+
return;
|
|
347
|
+
droppedParts.add(item);
|
|
348
|
+
const declared = plugin?.steps.permissions ?? [];
|
|
349
|
+
const fix = declared.includes(item) ? `Turn it on in /mods ${definition.name}.` : `It needs "permissions": { "${item}": true } in package.json "cmod".`;
|
|
350
|
+
claude().ui.log(`${definition.name} answered ${part} without your grant to "${permissionWords(item)}", so cmod dropped it. ${fix}`);
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
const checkedRoute = async (event, e, next) => {
|
|
354
|
+
let below;
|
|
355
|
+
const passOn = (passed) => (below ??= next(passedDown(event, e, passed, answerCheck)));
|
|
356
|
+
const answer = await router.dispatch(event, e, passOn);
|
|
357
|
+
return (await checkedAnswer(event, e, answer, () => passOn(e), answerCheck));
|
|
358
|
+
};
|
|
259
359
|
const whenActive = async () => {
|
|
260
360
|
if (phase === 'ready')
|
|
261
361
|
activation ??= activate().catch(report);
|
|
@@ -291,6 +391,9 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
291
391
|
if (started === undefined)
|
|
292
392
|
return;
|
|
293
393
|
plugin = started;
|
|
394
|
+
for (const item of started.granted)
|
|
395
|
+
granted.add(item);
|
|
396
|
+
consentMs = await consentStamp(consentPath(started.store));
|
|
294
397
|
if (started.isInstalled) {
|
|
295
398
|
shouldRecord = started.shouldRecord;
|
|
296
399
|
if (shouldRecord)
|
|
@@ -306,6 +409,10 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
306
409
|
}
|
|
307
410
|
},
|
|
308
411
|
async route(event, e, next) {
|
|
412
|
+
if (event === 'ui.render' && installer.draws(e))
|
|
413
|
+
return installer.draw(e);
|
|
414
|
+
if (event === 'ui.close')
|
|
415
|
+
installer.closed(e.id);
|
|
309
416
|
if (event === 'classic.SessionStart')
|
|
310
417
|
missedSessionStart = undefined;
|
|
311
418
|
if (event === 'classic.SessionStart' && runtime === undefined) {
|
|
@@ -321,6 +428,17 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
321
428
|
const isStopped = phase === 'declined' || phase === 'failed';
|
|
322
429
|
return { deny: isStopped ? notInstalled(definition.name) : `${definition.name} is installing. Try again when it's ready.` };
|
|
323
430
|
}
|
|
431
|
+
const call = e;
|
|
432
|
+
if (event === 'cmod.call' && call.to === definition.name && mod !== undefined) {
|
|
433
|
+
if (call.method === pendingStepsMethod)
|
|
434
|
+
return { value: await pendingSteps(mod) };
|
|
435
|
+
if (call.method === finishStepsMethod)
|
|
436
|
+
return (await runSteps(mod), { value: null });
|
|
437
|
+
}
|
|
438
|
+
if (event === 'classic.UserPromptSubmit' || event === 'classic.SessionStart')
|
|
439
|
+
await refreshGrants().catch((error) => claude().ui.log(`${definition.name} keeps its grants from before: ${messageOf(error)}`, { to: 'debug' }));
|
|
440
|
+
if (checksAnswers(event) && phase === 'active')
|
|
441
|
+
return checkedRoute(event, e, next);
|
|
324
442
|
const answer = router.dispatch(event, e, next);
|
|
325
443
|
const render = e;
|
|
326
444
|
if (event !== 'ui.render' || render.component !== 'AbovePrompt' || runtime === undefined || !runtime.progress.isShown)
|
|
@@ -331,22 +449,63 @@ export function createLifecycle(definition, checksPermissions = () => true) {
|
|
|
331
449
|
}
|
|
332
450
|
async function createMod(definition, runtime) {
|
|
333
451
|
const { router, progress } = runtime;
|
|
334
|
-
const claude = checkingKeys(runtime.claude, definition.name, runtime.keys);
|
|
452
|
+
const claude = checkingKeys(runtime.claude, definition.name, runtime.steps.keys ?? {});
|
|
335
453
|
const added = [];
|
|
336
454
|
const hookEvents = [];
|
|
337
455
|
const announce = (feature) => {
|
|
338
456
|
added.push(feature);
|
|
339
457
|
};
|
|
340
|
-
const [session, root, startCwd] = await Promise.all([claude.session.id(), claude.session.root(), claude.session.cwd()]);
|
|
458
|
+
const [session, root, startCwd, home, configHome] = await Promise.all([claude.session.id(), claude.session.root(), claude.session.cwd(), claude.env.home(), claude.env.configHome()]);
|
|
459
|
+
const { granted } = runtime;
|
|
460
|
+
const configRoot = configHome ?? (home === undefined ? undefined : `${home}/.claude`);
|
|
461
|
+
const grants = {
|
|
462
|
+
name: definition.name,
|
|
463
|
+
declared: runtime.steps.permissions ?? [],
|
|
464
|
+
granted: () => granted,
|
|
465
|
+
refresh: runtime.refreshGrants,
|
|
466
|
+
home,
|
|
467
|
+
configRoot,
|
|
468
|
+
projectRoot: () => modState.root,
|
|
469
|
+
freeFolders: () => [modState.root, runtime.dataFolder],
|
|
470
|
+
};
|
|
471
|
+
const checked = checkingGrants(claude, grants);
|
|
472
|
+
const files = createModFiles({
|
|
473
|
+
name: definition.name,
|
|
474
|
+
claude,
|
|
475
|
+
places: { home, configRoot, projectRoot: () => modState.root },
|
|
476
|
+
checkWrite: (path) => checkWrite(grants, `metadata.update(${path})`, path),
|
|
477
|
+
});
|
|
478
|
+
const pages = new Map();
|
|
479
|
+
const settingsPages = {
|
|
480
|
+
page(page) {
|
|
481
|
+
if (pages.has(page.id))
|
|
482
|
+
throw new Error(`${definition.name}: the settings page "${page.id}" is already added. Give each page its own id.`);
|
|
483
|
+
pages.set(page.id, { title: page.title, handle: area.ui.pane(page) });
|
|
484
|
+
},
|
|
485
|
+
async open(pageId) {
|
|
486
|
+
if (pageId === undefined) {
|
|
487
|
+
await claude.cmod.call({ to: cmodPluginName, method: 'openSettings', input: { mod: definition.name } });
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const page = pages.get(pageId);
|
|
491
|
+
if (page === undefined)
|
|
492
|
+
throw new Error(`${definition.name} has no settings page "${pageId}". It has: ${[...pages.keys()].join(', ') || 'none'}.`);
|
|
493
|
+
await page.handle.open({ focus: true });
|
|
494
|
+
},
|
|
495
|
+
};
|
|
341
496
|
let cwd = startCwd;
|
|
342
497
|
let loadedCwd = startCwd;
|
|
343
498
|
let staleCwd;
|
|
344
499
|
const area = createUi({ name: definition.name, claude, router, progress, announce, mod: () => mod });
|
|
345
500
|
const modState = createState({ name: definition.name, initial: definition.state ?? {}, session, root, claude, changed: area.changed });
|
|
501
|
+
const modOptions = createOptions({ name: definition.name, declared: definition.options ?? {}, fromClaude: runtime.options, claude, changed: modState.changed });
|
|
346
502
|
const held = runtime.checksPermissions() ? new Map() : undefined;
|
|
347
503
|
const mod = {
|
|
348
504
|
name: definition.name,
|
|
349
505
|
state: modState.state,
|
|
506
|
+
get options() {
|
|
507
|
+
return modOptions.values;
|
|
508
|
+
},
|
|
350
509
|
dataFolder: runtime.dataFolder,
|
|
351
510
|
on(event, hook, options) {
|
|
352
511
|
const bounded = options?.timeoutMs === undefined ? hook : timedHook(definition.name, event, hook, checkedMs(definition.name, `the ${event} hook's timeoutMs`, options.timeoutMs), claude);
|
|
@@ -357,7 +516,7 @@ async function createMod(definition, runtime) {
|
|
|
357
516
|
else
|
|
358
517
|
router.add(`classic.${event}`, classicHook(definition.name, event, bounded, claude, agents));
|
|
359
518
|
},
|
|
360
|
-
use: (job) => job({ mod,
|
|
519
|
+
use: (job) => job({ mod, announce, reserveName, toolCalls: agents }),
|
|
361
520
|
every(ms, hook) {
|
|
362
521
|
let isRunning = false;
|
|
363
522
|
return claude.clock.every(checkedMs(definition.name, 'mod.every', ms), () => {
|
|
@@ -374,33 +533,42 @@ async function createMod(definition, runtime) {
|
|
|
374
533
|
},
|
|
375
534
|
ui: area.ui,
|
|
376
535
|
process: {
|
|
377
|
-
run: (argv, init) =>
|
|
378
|
-
spawn: (argv, init) =>
|
|
536
|
+
run: (argv, init) => checked.process.run(argv, init),
|
|
537
|
+
spawn: (argv, init) => checked.process.spawn({ ...init, argv }),
|
|
379
538
|
},
|
|
380
539
|
fs: {
|
|
381
|
-
read: (path) =>
|
|
382
|
-
write: (path, text) =>
|
|
383
|
-
list: (path) =>
|
|
384
|
-
exists: (path) =>
|
|
385
|
-
stat: (path, options) =>
|
|
540
|
+
read: (path) => checked.fs.read(path),
|
|
541
|
+
write: (path, text) => checked.fs.write(path, text),
|
|
542
|
+
list: (path) => checked.fs.list(path),
|
|
543
|
+
exists: (path) => checked.fs.exists(path),
|
|
544
|
+
stat: (path, options) => checked.fs.stat(path, options),
|
|
545
|
+
find: (glob) => files.find(glob),
|
|
546
|
+
},
|
|
547
|
+
metadata: {
|
|
548
|
+
read: (path) => files.read(path),
|
|
549
|
+
update: (path, change) => files.update(path, change),
|
|
386
550
|
},
|
|
387
|
-
http: { fetch: (url, init) =>
|
|
388
|
-
settings:
|
|
551
|
+
http: { fetch: (url, init) => checked.http.fetch(url, init) },
|
|
552
|
+
settings: settingsPages,
|
|
389
553
|
session: {
|
|
390
|
-
messages:
|
|
554
|
+
messages: ((args) => (args === undefined ? checked.session.messages() : checked.session.messages({ agentId: args.agentId }))),
|
|
391
555
|
async append(text) {
|
|
392
|
-
const added = await
|
|
556
|
+
const added = await checked.session.append({ message: { type: 'user', content: [{ type: 'text', text }] } });
|
|
393
557
|
if (added.deny !== undefined)
|
|
394
558
|
throw new Error(`${definition.name}: Claude Code refused the note: ${added.deny}`);
|
|
395
559
|
},
|
|
396
560
|
async submit(text) {
|
|
397
|
-
const submitted = await
|
|
561
|
+
const submitted = await checked.prompt.submit({ text });
|
|
398
562
|
if ('drop' in submitted && typeof submitted.drop === 'string')
|
|
399
563
|
throw new Error(`${definition.name}: Claude Code dropped the prompt: ${submitted.drop}`);
|
|
400
564
|
},
|
|
401
565
|
},
|
|
402
|
-
agent: { spawn: (args) =>
|
|
403
|
-
model: { complete: (
|
|
566
|
+
agent: { spawn: (args) => checked.agent.spawn(args) },
|
|
567
|
+
model: { complete: (completion, options) => checked.model.complete(completion, options) },
|
|
568
|
+
permissions: {
|
|
569
|
+
has: (name, value) => isCovered(granted, itemOf(name, value), home),
|
|
570
|
+
},
|
|
571
|
+
claude: { ...checked, on: (event, hook) => router.add(event, hook) },
|
|
404
572
|
get projectRoot() {
|
|
405
573
|
return modState.root;
|
|
406
574
|
},
|
|
@@ -427,6 +595,8 @@ async function createMod(definition, runtime) {
|
|
|
427
595
|
cwd = loadedCwd;
|
|
428
596
|
throw error;
|
|
429
597
|
});
|
|
598
|
+
if (hasMovedRoot)
|
|
599
|
+
await modOptions.load(nextRoot);
|
|
430
600
|
loadedCwd = nextCwd;
|
|
431
601
|
if (!hasMovedRoot)
|
|
432
602
|
area.changed();
|
|
@@ -465,7 +635,17 @@ async function createMod(definition, runtime) {
|
|
|
465
635
|
});
|
|
466
636
|
router.add('skill.prompt', userSkillHook(claude));
|
|
467
637
|
const api = Object.fromEntries(Object.entries(definition.api ?? {}).map(([method, run]) => [method, (input) => run(input, mod)]));
|
|
468
|
-
router.add('cmod.call', (e, next) =>
|
|
638
|
+
router.add('cmod.call', async (e, next) => {
|
|
639
|
+
if (e.to !== definition.name)
|
|
640
|
+
return next(e);
|
|
641
|
+
if (e.method === settingsPagesMethod)
|
|
642
|
+
return { value: [...pages].map(([id, { title }]) => ({ id, title })) };
|
|
643
|
+
if (e.method === openPageMethod) {
|
|
644
|
+
await settingsPages.open(String(e.input));
|
|
645
|
+
return { value: null };
|
|
646
|
+
}
|
|
647
|
+
return answerCall(definition.name, api, e);
|
|
648
|
+
});
|
|
469
649
|
const agents = toolCalls(claude, router);
|
|
470
650
|
const names = new Set();
|
|
471
651
|
const reserveName = (kind, name, taken) => {
|
|
@@ -475,6 +655,10 @@ async function createMod(definition, runtime) {
|
|
|
475
655
|
names.add(key);
|
|
476
656
|
};
|
|
477
657
|
await failsAs('its state did not load', () => modState.load());
|
|
658
|
+
await failsAs('its options did not load', () => modOptions.load(root));
|
|
659
|
+
const missing = modOptions.missing;
|
|
660
|
+
if (missing.length > 0)
|
|
661
|
+
throw new MissingOptions(missing, missing.map((key) => definition.options?.[key]?.title ?? key));
|
|
478
662
|
await failsAs('its setup function threw', () => definition.setup(mod));
|
|
479
663
|
const permissionHooks = permissionEvents.filter((event) => router.has(event));
|
|
480
664
|
if (permissionHooks.length > 0 && !runtime.checksPermissions()) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FileMatch, Metadata } from '../mod.js';
|
|
2
|
+
import type { Claude } from './claude.js';
|
|
3
|
+
export type Places = {
|
|
4
|
+
readonly home: string | undefined;
|
|
5
|
+
readonly configRoot: string | undefined;
|
|
6
|
+
readonly projectRoot: () => string;
|
|
7
|
+
};
|
|
8
|
+
export type ModFiles = {
|
|
9
|
+
find(glob: string | readonly string[]): Promise<readonly FileMatch[]>;
|
|
10
|
+
read(path: string): Promise<Metadata>;
|
|
11
|
+
update(path: string, change: (metadata: Record<string, string>) => void): Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolvedPath(path: string, places: Places): string;
|
|
14
|
+
export declare function createModFiles({ name, claude, places, checkWrite }: {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly claude: Claude;
|
|
17
|
+
readonly places: Places;
|
|
18
|
+
readonly checkWrite: (path: string) => Promise<void>;
|
|
19
|
+
}): ModFiles;
|
|
20
|
+
//# sourceMappingURL=metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/runtime/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AAIpD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,MAAM,MAAM,MAAM,GAAG;IACnB,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;CACnC,CAAA;AAED,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC,CAAA;IACrE,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACrC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACxF,CAAA;AAUD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAQjE;AAED,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,GAAG,QAAQ,CAoHhN"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { formatOf, MetadataError, readMetadata, sidecarSuffix, writeMetadata } from '../utils/metadata.js';
|
|
2
|
+
import { messageOf } from '../utils/text.js';
|
|
3
|
+
import { picomatch, resolve } from '../vendor.js';
|
|
4
|
+
const skippedFolders = ['.git', 'node_modules'];
|
|
5
|
+
const ownKey = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
|
|
6
|
+
export function resolvedPath(path, places) {
|
|
7
|
+
const configured = /^~\/\.claude(?=\/|$)/.exec(path);
|
|
8
|
+
if (configured !== null && places.configRoot !== undefined)
|
|
9
|
+
return resolve(`${places.configRoot}${path.slice(configured[0].length)}`);
|
|
10
|
+
if (path === '~' || path.startsWith('~/')) {
|
|
11
|
+
if (places.home === undefined)
|
|
12
|
+
throw new Error(`${path} starts with ~, and HOME is not set. Start Claude Code with HOME set.`);
|
|
13
|
+
return resolve(`${places.home}${path.slice(1)}`);
|
|
14
|
+
}
|
|
15
|
+
return path.startsWith('/') ? resolve(path) : resolve(places.projectRoot(), path);
|
|
16
|
+
}
|
|
17
|
+
export function createModFiles({ name, claude, places, checkWrite }) {
|
|
18
|
+
const prefix = `${name}.`;
|
|
19
|
+
const cache = new Map();
|
|
20
|
+
const logged = new Set();
|
|
21
|
+
const own = (all) => Object.freeze(Object.fromEntries(Object.entries(all).flatMap(([key, value]) => (key.startsWith(prefix) ? [[key.slice(prefix.length), value]] : []))));
|
|
22
|
+
const locate = async (path) => {
|
|
23
|
+
const stat = await claude.fs.stat(path, { resolve: true });
|
|
24
|
+
if (stat.kind !== 'file')
|
|
25
|
+
throw new Error(`${path} is not a file, so it holds no metadata.`);
|
|
26
|
+
const realPath = stat.realPath ?? path;
|
|
27
|
+
const extensionless = !realPath.slice(realPath.lastIndexOf('/') + 1).includes('.');
|
|
28
|
+
const firstLine = extensionless ? (await claude.fs.read(realPath)).split('\n', 1)[0] : undefined;
|
|
29
|
+
const format = formatOf(realPath, firstLine);
|
|
30
|
+
const holder = format.kind === 'sidecar' ? `${realPath}${sidecarSuffix}` : realPath;
|
|
31
|
+
const sidecar = format.kind === 'sidecar' ? await claude.fs.stat(holder).catch(() => undefined) : undefined;
|
|
32
|
+
return { realPath, format, holder, signature: `${stat.mtimeMs}:${stat.size}:${sidecar?.kind === 'file' ? `${sidecar.mtimeMs}:${sidecar.size}` : ''}` };
|
|
33
|
+
};
|
|
34
|
+
const textOf = async (holder) => ((await claude.fs.stat(holder).catch(() => undefined))?.kind === 'file' ? claude.fs.read(holder) : '');
|
|
35
|
+
const readEntry = async (path) => {
|
|
36
|
+
const located = await locate(path);
|
|
37
|
+
const cached = cache.get(path);
|
|
38
|
+
if (cached?.signature === located.signature)
|
|
39
|
+
return cached.read;
|
|
40
|
+
let read;
|
|
41
|
+
try {
|
|
42
|
+
const { metadata, name: declared } = readMetadata(await textOf(located.holder), located.format);
|
|
43
|
+
read = { metadata, ...(declared === undefined ? {} : { name: declared }) };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (!(error instanceof MetadataError))
|
|
47
|
+
throw error;
|
|
48
|
+
const failure = `${located.holder} ${error.message}`;
|
|
49
|
+
read = { metadata: {}, error: failure };
|
|
50
|
+
if (!logged.has(failure))
|
|
51
|
+
claude.ui.log(`${name}: ${failure}`);
|
|
52
|
+
logged.add(failure);
|
|
53
|
+
}
|
|
54
|
+
cache.set(path, { signature: located.signature, read });
|
|
55
|
+
return read;
|
|
56
|
+
};
|
|
57
|
+
const matchesOf = async (pattern) => {
|
|
58
|
+
const absolute = resolvedPath(pattern, places);
|
|
59
|
+
const scanned = picomatch.scan(absolute);
|
|
60
|
+
if (!scanned.isGlob)
|
|
61
|
+
return (await claude.fs.stat(absolute).catch(() => undefined))?.kind === 'file' ? [absolute] : [];
|
|
62
|
+
const isMatch = picomatch(absolute, { dot: true });
|
|
63
|
+
const entersSkipped = skippedFolders.filter((folder) => scanned.glob.split('/').includes(folder));
|
|
64
|
+
const maxDepth = scanned.glob.includes('**') ? Number.POSITIVE_INFINITY : scanned.glob.split('/').length;
|
|
65
|
+
const found = [];
|
|
66
|
+
const visited = new Set();
|
|
67
|
+
const walk = async (folder, depth) => {
|
|
68
|
+
const real = (await claude.fs.stat(folder, { resolve: true }).catch(() => undefined))?.realPath ?? folder;
|
|
69
|
+
if (visited.has(real))
|
|
70
|
+
return;
|
|
71
|
+
visited.add(real);
|
|
72
|
+
const entries = await claude.fs.list(folder).catch(() => []);
|
|
73
|
+
await Promise.all(entries.map(async (entry) => {
|
|
74
|
+
const path = `${folder}/${entry.name}`;
|
|
75
|
+
const kind = entry.isLink === true ? (await claude.fs.stat(path).catch(() => undefined))?.kind : entry.kind;
|
|
76
|
+
if (kind === 'file' && isMatch(path))
|
|
77
|
+
found.push(path);
|
|
78
|
+
if (kind !== 'dir' || depth + 1 >= maxDepth)
|
|
79
|
+
return;
|
|
80
|
+
if (skippedFolders.includes(entry.name) && !entersSkipped.includes(entry.name))
|
|
81
|
+
return;
|
|
82
|
+
await walk(path, depth + 1);
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
const base = scanned.base === '' ? '/' : scanned.base;
|
|
86
|
+
if ((await claude.fs.stat(base).catch(() => undefined))?.kind === 'dir')
|
|
87
|
+
await walk(base, 0);
|
|
88
|
+
return found;
|
|
89
|
+
};
|
|
90
|
+
return {
|
|
91
|
+
async find(glob) {
|
|
92
|
+
const patterns = typeof glob === 'string' ? [glob] : glob;
|
|
93
|
+
const paths = [...new Set((await Promise.all(patterns.map(matchesOf))).flat())].sort();
|
|
94
|
+
const kept = paths.filter((path) => !(path.endsWith(sidecarSuffix) && paths.includes(path.slice(0, -sidecarSuffix.length))));
|
|
95
|
+
return Promise.all(kept.map(async (path) => {
|
|
96
|
+
const read = await readEntry(path);
|
|
97
|
+
return { path, ...(read.name === undefined ? {} : { name: read.name }), metadata: own(read.metadata), ...(read.error === undefined ? {} : { error: read.error }) };
|
|
98
|
+
}));
|
|
99
|
+
},
|
|
100
|
+
async read(path) {
|
|
101
|
+
const read = await readEntry(resolvedPath(path, places));
|
|
102
|
+
if (read.error !== undefined)
|
|
103
|
+
throw new Error(`${name}: ${read.error}`);
|
|
104
|
+
return own(read.metadata);
|
|
105
|
+
},
|
|
106
|
+
async update(path, change) {
|
|
107
|
+
const given = resolvedPath(path, places);
|
|
108
|
+
await checkWrite(given);
|
|
109
|
+
const located = await locate(given);
|
|
110
|
+
const text = await textOf(located.holder);
|
|
111
|
+
let all;
|
|
112
|
+
try {
|
|
113
|
+
all = readMetadata(text, located.format).metadata;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw new Error(`${name}: ${located.holder} ${messageOf(error)}`, { cause: error });
|
|
117
|
+
}
|
|
118
|
+
const edited = { ...own(all) };
|
|
119
|
+
change(edited);
|
|
120
|
+
for (const [key, value] of Object.entries(edited)) {
|
|
121
|
+
if (!ownKey.test(key))
|
|
122
|
+
throw new Error(`${name}: the metadata key "${key}" is not a name. Use letters, digits, "_", ".", and "-".`);
|
|
123
|
+
if (typeof value !== 'string')
|
|
124
|
+
throw new Error(`${name}: the metadata key "${key}" holds ${JSON.stringify(value)}. Each value is text, and a list is one space-separated text.`);
|
|
125
|
+
if (value.includes('\n'))
|
|
126
|
+
throw new Error(`${name}: the metadata key "${key}" holds a line break. Each value is one line of text.`);
|
|
127
|
+
}
|
|
128
|
+
const next = {};
|
|
129
|
+
for (const [key, value] of Object.entries(all)) {
|
|
130
|
+
if (!key.startsWith(prefix))
|
|
131
|
+
next[key] = value;
|
|
132
|
+
else if (Object.hasOwn(edited, key.slice(prefix.length)))
|
|
133
|
+
next[key] = edited[key.slice(prefix.length)];
|
|
134
|
+
}
|
|
135
|
+
for (const [key, value] of Object.entries(edited))
|
|
136
|
+
next[`${prefix}${key}`] = value;
|
|
137
|
+
if (JSON.stringify(next) === JSON.stringify(all))
|
|
138
|
+
return;
|
|
139
|
+
await claude.fs.write(located.holder, writeMetadata(text, located.format, next));
|
|
140
|
+
cache.delete(given);
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PluginOptions } from 'claude-code';
|
|
2
|
+
import { type Options } from '../options.js';
|
|
3
|
+
import type { Claude } from './claude.js';
|
|
4
|
+
export declare class MissingOptions extends Error {
|
|
5
|
+
readonly keys: readonly string[];
|
|
6
|
+
readonly titles: readonly string[];
|
|
7
|
+
constructor(keys: readonly string[], titles: readonly string[]);
|
|
8
|
+
}
|
|
9
|
+
export type ModOptions = {
|
|
10
|
+
readonly values: Readonly<Record<string, unknown>>;
|
|
11
|
+
readonly missing: readonly string[];
|
|
12
|
+
load(root: string): Promise<void>;
|
|
13
|
+
};
|
|
14
|
+
export declare function createOptions({ name, declared, fromClaude, claude, changed }: {
|
|
15
|
+
readonly name: string;
|
|
16
|
+
readonly declared: Options;
|
|
17
|
+
readonly fromClaude: PluginOptions;
|
|
18
|
+
readonly claude: Claude;
|
|
19
|
+
readonly changed: () => void;
|
|
20
|
+
}): ModOptions;
|
|
21
|
+
//# sourceMappingURL=options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"options.d.ts","sourceRoot":"","sources":["../src/runtime/options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAoC,KAAK,OAAO,EAAE,MAAM,eAAe,CAAA;AAG9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAEzC,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAElC,YAAY,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,MAAM,EAAE,SAAS,MAAM,EAAE,EAI7D;CACF;AAED,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAClD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC,CAAA;AAED,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,GAAG,UAAU,CAmE3O"}
|