@nexrall/code-core 1.4.77 → 1.4.78
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/dist/agent/effortCap.d.ts +24 -0
- package/dist/agent/effortCap.d.ts.map +1 -0
- package/dist/agent/effortCap.js +66 -0
- package/dist/agent/hooks.d.ts +10 -2
- package/dist/agent/hooks.d.ts.map +1 -1
- package/dist/agent/hooks.js +9 -3
- package/dist/agent/skills.d.ts.map +1 -1
- package/dist/agent/skills.js +14 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/mcp/manager.d.ts +8 -0
- package/dist/mcp/manager.d.ts.map +1 -1
- package/dist/mcp/manager.js +64 -57
- package/dist/permissions/destructiveTokens.d.ts +1 -0
- package/dist/permissions/destructiveTokens.d.ts.map +1 -1
- package/dist/permissions/destructiveTokens.js +93 -0
- package/dist/permissions/rules.d.ts +12 -0
- package/dist/permissions/rules.d.ts.map +1 -1
- package/dist/permissions/rules.js +89 -7
- package/dist/plugins/configStore.d.ts +43 -0
- package/dist/plugins/configStore.d.ts.map +1 -0
- package/dist/plugins/configStore.js +253 -0
- package/dist/plugins/files.d.ts +10 -0
- package/dist/plugins/files.d.ts.map +1 -0
- package/dist/plugins/files.js +85 -0
- package/dist/plugins/index.d.ts +3 -22
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +30 -40
- package/dist/plugins/resolve.d.ts +18 -0
- package/dist/plugins/resolve.d.ts.map +1 -0
- package/dist/plugins/resolve.js +152 -0
- package/dist/plugins/userConfig.d.ts +90 -0
- package/dist/plugins/userConfig.d.ts.map +1 -0
- package/dist/plugins/userConfig.js +332 -0
- package/dist/plugins/validate.d.ts +14 -0
- package/dist/plugins/validate.d.ts.map +1 -0
- package/dist/plugins/validate.js +222 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/permissions/rules.ts"],"names":[],"mappings":"AA0BA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,eAAe,CAAC;IAC7B,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,wBAAgB,mBAAmB,IAAI,MAAM,CAQ5C;AAsBD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI,CAEjE;AAQD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,CAE5E;AACD,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAEnE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CA8B5D;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,CAcvG;AAsMD;;oEAEoE;AACpE,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,MAAM,GAAG,IAAI,CAEf;AAYD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOtD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,eAAe,EACtB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,EAAE,MAAM,GACd,kBAAkB,GAAG,IAAI,CAK3B"}
|
|
@@ -38,12 +38,15 @@ exports.setSessionRules = setSessionRules;
|
|
|
38
38
|
exports.setSessionSettings = setSessionSettings;
|
|
39
39
|
exports.getSessionSettings = getSessionSettings;
|
|
40
40
|
exports.loadSettings = loadSettings;
|
|
41
|
+
exports.settingsTiersRaw = settingsTiersRaw;
|
|
41
42
|
exports.matchedAllowRule = matchedAllowRule;
|
|
42
43
|
exports.isBroadAllowRule = isBroadAllowRule;
|
|
43
44
|
exports.evaluatePermission = evaluatePermission;
|
|
44
45
|
const fs = __importStar(require("fs"));
|
|
45
46
|
const path = __importStar(require("path"));
|
|
46
47
|
const os = __importStar(require("os"));
|
|
48
|
+
const modePolicy_1 = require("./modePolicy");
|
|
49
|
+
const destructiveTokens_1 = require("./destructiveTokens");
|
|
47
50
|
function managedSettingsPath() {
|
|
48
51
|
// Tests (and a sandboxed doctor run) point this at a fixture. Checked first so it
|
|
49
52
|
// can never be shadowed by a real machine-level file.
|
|
@@ -120,6 +123,31 @@ function loadSettings(workDir) {
|
|
|
120
123
|
merged.deny.push(...sessionRules.deny);
|
|
121
124
|
return { permissions: merged, raw };
|
|
122
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* S25b: the raw object of every settings tier, in precedence order (managed →
|
|
128
|
+
* user → project → local → `--settings` session settings). For resolvers where
|
|
129
|
+
* the STRICTEST value must win regardless of tier — `maxEffortLevel` (a cap can
|
|
130
|
+
* only be tightened by a lower-precedence file, never loosened), and anything
|
|
131
|
+
* with the same one-way semantics added later. `loadSettings().raw` cannot serve
|
|
132
|
+
* those: it merges with LAST-wins, which is the opposite direction.
|
|
133
|
+
*/
|
|
134
|
+
function settingsTiersRaw(workDir) {
|
|
135
|
+
const files = [
|
|
136
|
+
['managed', managedSettingsPath()],
|
|
137
|
+
['user', path.join(os.homedir(), '.nexrall', 'settings.json')],
|
|
138
|
+
['project', path.join(workDir, '.nexrall', 'settings.json')],
|
|
139
|
+
['local', path.join(workDir, '.nexrall', 'settings.local.json')],
|
|
140
|
+
];
|
|
141
|
+
const out = [];
|
|
142
|
+
for (const [tier, file] of files) {
|
|
143
|
+
const obj = readJson(file);
|
|
144
|
+
if (obj)
|
|
145
|
+
out.push({ tier, raw: obj });
|
|
146
|
+
}
|
|
147
|
+
if (sessionSettings)
|
|
148
|
+
out.push({ tier: 'session', raw: sessionSettings });
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
123
151
|
// ─── Rule matching ──────────────────────────────────────────────────────────
|
|
124
152
|
function parseRule(rule) {
|
|
125
153
|
const m = /^([A-Za-z0-9_]+)\((.*)\)$/.exec(rule.trim());
|
|
@@ -180,8 +208,21 @@ function matchPattern(pattern, value) {
|
|
|
180
208
|
}
|
|
181
209
|
return value === pattern;
|
|
182
210
|
}
|
|
183
|
-
/**
|
|
184
|
-
|
|
211
|
+
/** The command line of a bash-family call: `command`, falling back to the `cmd`
|
|
212
|
+
* alias every other reader accepts (destructive guard, CLI handler). */
|
|
213
|
+
function bashCommand(input) {
|
|
214
|
+
const v = input.command ?? input.cmd;
|
|
215
|
+
return typeof v === 'string' ? v : '';
|
|
216
|
+
}
|
|
217
|
+
/** Map a tool's input to the string(s) a rule pattern should match against.
|
|
218
|
+
*
|
|
219
|
+
* S25f: deny/ask matching ALSO sees a path's real identity behind symlinks —
|
|
220
|
+
* Claude Code 2.1.289 fixed exactly this class ("Read deny rules not applying
|
|
221
|
+
* to files @-mentioned, changed, or selected in the IDE through a symlink").
|
|
222
|
+
* The allow path skips those views (`followSymlinks: false`): an exception
|
|
223
|
+
* stays as NARROW as it was written — the same asymmetry the unwrapped bash
|
|
224
|
+
* views in destructiveTokens use. */
|
|
225
|
+
function matchValues(tool, input, workDir, followSymlinks = false) {
|
|
185
226
|
const str = (v) => (typeof v === 'string' ? v : '');
|
|
186
227
|
const vals = [];
|
|
187
228
|
const pushPath = (p) => {
|
|
@@ -192,10 +233,36 @@ function matchValues(tool, input, workDir) {
|
|
|
192
233
|
vals.push(path.relative(workDir, p));
|
|
193
234
|
else
|
|
194
235
|
vals.push(path.resolve(workDir, p));
|
|
236
|
+
if (!followSymlinks)
|
|
237
|
+
return;
|
|
238
|
+
const abs = path.resolve(workDir, p);
|
|
239
|
+
try {
|
|
240
|
+
const real = fs.realpathSync(abs);
|
|
241
|
+
if (real !== abs)
|
|
242
|
+
vals.push(real, path.relative(workDir, real));
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// A path being CREATED through a symlinked directory: resolve the parent.
|
|
246
|
+
try {
|
|
247
|
+
const realDir = fs.realpathSync(path.dirname(abs));
|
|
248
|
+
const realAbs = path.join(realDir, path.basename(abs));
|
|
249
|
+
vals.push(realAbs, path.relative(workDir, realAbs));
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
/* neither exists: nothing to resolve */
|
|
253
|
+
}
|
|
254
|
+
}
|
|
195
255
|
};
|
|
196
256
|
switch (tool) {
|
|
257
|
+
// The whole bash family, matching modePolicy.BASH_TOOLS: the executor emits
|
|
258
|
+
// `bash`, and run_command / execute_command are the same tool accepted
|
|
259
|
+
// everywhere else (destructive guard, CLI handler). A `Bash(…)` rule must
|
|
260
|
+
// not silently skip a call that arrives under one of those names — the
|
|
261
|
+
// "looks configured and isn't" failure class the task rules had.
|
|
197
262
|
case 'bash':
|
|
198
|
-
|
|
263
|
+
case 'run_command':
|
|
264
|
+
case 'execute_command':
|
|
265
|
+
vals.push(bashCommand(input));
|
|
199
266
|
break;
|
|
200
267
|
case 'move_file':
|
|
201
268
|
pushPath(str(input.source));
|
|
@@ -241,7 +308,7 @@ function ruleMatches(rule, tool, input, workDir, opts) {
|
|
|
241
308
|
Read: ['read_file', 'list_directory', 'search_files', 'glob', 'notebook_read'],
|
|
242
309
|
Edit: ['edit_file', 'multi_edit', 'write_file', 'notebook_edit'],
|
|
243
310
|
Write: ['write_file', 'write_docx', 'write_xlsx', 'write_pptx'],
|
|
244
|
-
Bash: ['bash'],
|
|
311
|
+
Bash: ['bash', 'run_command', 'execute_command'],
|
|
245
312
|
WebFetch: ['fetch_url'],
|
|
246
313
|
Image: ['generate_image', 'stock_photo'],
|
|
247
314
|
};
|
|
@@ -255,15 +322,30 @@ function ruleMatches(rule, tool, input, workDir, opts) {
|
|
|
255
322
|
// "Bash(npm run test:*)" allow rule straight past the user prompt.
|
|
256
323
|
// Deny rules stay aggressive (they SHOULD match chained commands), and a
|
|
257
324
|
// bare "Bash" allow (pattern === null) is an explicit "allow everything".
|
|
258
|
-
if (opts?.forAllow && tool
|
|
259
|
-
const cmd =
|
|
325
|
+
if (opts?.forAllow && modePolicy_1.BASH_TOOLS.has(tool) && pattern !== null && pattern !== '' && pattern !== '*') {
|
|
326
|
+
const cmd = bashCommand(input);
|
|
260
327
|
// newline / ; / && / || / | / trailing & / command-substitution / backtick
|
|
261
328
|
if (/[\r\n;`]|&&|\|\||\||&\s*$|\$\(/.test(cmd))
|
|
262
329
|
return false;
|
|
263
330
|
}
|
|
264
331
|
if (pattern === null || pattern === '' || pattern === '*')
|
|
265
332
|
return true;
|
|
266
|
-
|
|
333
|
+
if (matchValues(tool, input, workDir, !opts?.forAllow).some((v) => matchPattern(pattern, v)))
|
|
334
|
+
return true;
|
|
335
|
+
// Deny / ask rules must also see the command BEHIND a costume: env
|
|
336
|
+
// assignments (`TZ="$HOME" rm -rf build` — Claude Code's own fix class,
|
|
337
|
+
// 2.1.288/289), wrappers (`sudo`, `env`, `nice`, `timeout`, `xargs`, shell
|
|
338
|
+
// `-c`, `eval`), compound separators (`;` `&&` `||` `|`) and `$(…)` /
|
|
339
|
+
// backtick bodies. The destructive guard has always read these spellings
|
|
340
|
+
// (destructiveTokens); a user's deny rule must not be the one thing a
|
|
341
|
+
// prefix can skip.
|
|
342
|
+
//
|
|
343
|
+
// ALLOW deliberately stays exact — an exception is only as WIDE as it was
|
|
344
|
+
// written, and env vars can execute code (NODE_OPTIONS, LD_PRELOAD…), so
|
|
345
|
+
// `FOO=x npm run test` must not ride `Bash(npm run test:*)` without asking.
|
|
346
|
+
if (opts?.forAllow || !modePolicy_1.BASH_TOOLS.has(tool))
|
|
347
|
+
return false;
|
|
348
|
+
return (0, destructiveTokens_1.unwrappedViews)(bashCommand(input)).some((v) => matchPattern(pattern, v));
|
|
267
349
|
}
|
|
268
350
|
/** The first ALLOW rule that matches this call, or null. S20's classifier gate
|
|
269
351
|
* reads this to tell a NARROW pre-approval (wins, no review) from a BROAD one
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type UserConfigOption } from './userConfig';
|
|
2
|
+
type SecretMap = Record<string, Record<string, unknown>>;
|
|
3
|
+
/** The whole secrets file ({} when absent/unreadable/malformed). */
|
|
4
|
+
export declare function readPluginSecrets(): SecretMap;
|
|
5
|
+
/**
|
|
6
|
+
* `pluginConfigs` from every settings tier, merged per plugin per key
|
|
7
|
+
* (last tier wins — the same direction loadSettings uses for `raw`).
|
|
8
|
+
*/
|
|
9
|
+
export declare function pluginConfigsFromSettings(workDir: string): Record<string, Record<string, unknown>>;
|
|
10
|
+
export interface ResolvedPluginConfig {
|
|
11
|
+
/** key → effective value (valid sources only, default included). */
|
|
12
|
+
values: Record<string, unknown>;
|
|
13
|
+
/** key → where the value came from. */
|
|
14
|
+
sources: Record<string, 'secret' | 'settings' | 'default'>;
|
|
15
|
+
/** required options with no value anywhere (not even a default). */
|
|
16
|
+
missing: string[];
|
|
17
|
+
/** Values that failed validation, with their source — reported, not used. */
|
|
18
|
+
invalid: Array<{
|
|
19
|
+
key: string;
|
|
20
|
+
source: 'secret' | 'settings';
|
|
21
|
+
error: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
/** Resolve every declared option to its effective value. Pure reads. */
|
|
25
|
+
export declare function resolvePluginConfig(workDir: string, pluginName: string, options: UserConfigOption[]): ResolvedPluginConfig;
|
|
26
|
+
export interface SavePluginConfigResult {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
/** Nothing was written when this is non-empty. */
|
|
29
|
+
errors: string[];
|
|
30
|
+
/** Paths actually written. */
|
|
31
|
+
wroteSettings?: string;
|
|
32
|
+
wroteSecrets?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Validate + save values. Unknown keys are errors (a typo must not create a
|
|
36
|
+
* silent no-op entry), as are values that fail their option's validation. The
|
|
37
|
+
* two halves land in different stores (see the file header).
|
|
38
|
+
*/
|
|
39
|
+
export declare function savePluginConfig(workDir: string, pluginName: string, options: UserConfigOption[], values: Record<string, unknown>, opts?: {
|
|
40
|
+
scope?: 'user' | 'project';
|
|
41
|
+
}): SavePluginConfigResult;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=configStore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configStore.d.ts","sourceRoot":"","sources":["../../src/plugins/configStore.ts"],"names":[],"mappings":"AA2BA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAmD9E,KAAK,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEzD,oEAAoE;AACpE,wBAAgB,iBAAiB,IAAI,SAAS,CAQ7C;AAgBD;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAWlG;AAED,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC,CAAC;IAC3D,oEAAoE;IACpE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,6EAA6E;IAC7E,OAAO,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC/E;AAED,wEAAwE;AACxE,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,EAAE,GAC1B,oBAAoB,CA+BtB;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,OAAO,CAAC;IACZ,kDAAkD;IAClD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,8BAA8B;IAC9B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,EAAE,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAO,GACxC,sBAAsB,CAkDxB"}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Plugin config values store (S24) ────────────────────────────────────────
|
|
3
|
+
//
|
|
4
|
+
// Where a plugin's userConfig VALUES live:
|
|
5
|
+
//
|
|
6
|
+
// • non-sensitive → `pluginConfigs.<plugin>.<key>` in a settings file
|
|
7
|
+
// (managed → user → project → local → `--settings`, read merged; the LAST
|
|
8
|
+
// tier wins per key, matching every other settings read in the codebase).
|
|
9
|
+
// Written to the tier you choose — default: the scope the plugin is
|
|
10
|
+
// installed in, so a project plugin configures itself in the project.
|
|
11
|
+
// • sensitive → `~/.nexrall/plugin-secrets.json` (0600), keyed the same way.
|
|
12
|
+
// Claude Code uses the OS credential store here; the CLI has no such
|
|
13
|
+
// integration, so this is a dedicated 0600 file instead — stated plainly
|
|
14
|
+
// rather than implied to be a keychain.
|
|
15
|
+
// • nothing set → the manifest default (resolvePluginConfig reports which).
|
|
16
|
+
//
|
|
17
|
+
// Resolution order per key: secrets → settings → default. An INVALID value at
|
|
18
|
+
// a higher precedence does not win: it is reported (resolvePluginConfig's
|
|
19
|
+
// `invalid`) and resolution continues to the next source, so a typo in
|
|
20
|
+
// settings cannot silently break a working default.
|
|
21
|
+
//
|
|
22
|
+
// This module reads/writes the disk; the schema and substitution half is pure
|
|
23
|
+
// (userConfig.ts).
|
|
24
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
27
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
28
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
29
|
+
}
|
|
30
|
+
Object.defineProperty(o, k2, desc);
|
|
31
|
+
}) : (function(o, m, k, k2) {
|
|
32
|
+
if (k2 === undefined) k2 = k;
|
|
33
|
+
o[k2] = m[k];
|
|
34
|
+
}));
|
|
35
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
36
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
37
|
+
}) : function(o, v) {
|
|
38
|
+
o["default"] = v;
|
|
39
|
+
});
|
|
40
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
41
|
+
var ownKeys = function(o) {
|
|
42
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
43
|
+
var ar = [];
|
|
44
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
45
|
+
return ar;
|
|
46
|
+
};
|
|
47
|
+
return ownKeys(o);
|
|
48
|
+
};
|
|
49
|
+
return function (mod) {
|
|
50
|
+
if (mod && mod.__esModule) return mod;
|
|
51
|
+
var result = {};
|
|
52
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
53
|
+
__setModuleDefault(result, mod);
|
|
54
|
+
return result;
|
|
55
|
+
};
|
|
56
|
+
})();
|
|
57
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
|
+
exports.readPluginSecrets = readPluginSecrets;
|
|
59
|
+
exports.pluginConfigsFromSettings = pluginConfigsFromSettings;
|
|
60
|
+
exports.resolvePluginConfig = resolvePluginConfig;
|
|
61
|
+
exports.savePluginConfig = savePluginConfig;
|
|
62
|
+
const fs = __importStar(require("fs"));
|
|
63
|
+
const os = __importStar(require("os"));
|
|
64
|
+
const path = __importStar(require("path"));
|
|
65
|
+
const rules_1 = require("../permissions/rules");
|
|
66
|
+
const userConfig_1 = require("./userConfig");
|
|
67
|
+
// NOTE (import graph): this module deliberately does NOT use
|
|
68
|
+
// util/settingsFile's writeSetting — that module imports agent/hooks, and
|
|
69
|
+
// hooks imports plugins/resolve → this file; using it would close a cycle.
|
|
70
|
+
// The atomic write below is the same temp-file+rename shape, minus the
|
|
71
|
+
// ConfigChange observer hook (pluginConfig edits do not fire it yet).
|
|
72
|
+
function secretsPath() {
|
|
73
|
+
return path.join(os.homedir(), '.nexrall', 'plugin-secrets.json');
|
|
74
|
+
}
|
|
75
|
+
/** The settings file a scoped write targets (mirror of util/settingsFile's settingsPath). */
|
|
76
|
+
function tierFile(workDir, scope) {
|
|
77
|
+
return scope === 'project'
|
|
78
|
+
? path.join(workDir, '.nexrall', 'settings.json')
|
|
79
|
+
: path.join(os.homedir(), '.nexrall', 'settings.json');
|
|
80
|
+
}
|
|
81
|
+
/** Atomic top-level-key write: temp file + rename, so an interrupted write cannot half-JSON a settings file. */
|
|
82
|
+
function writeTierSetting(workDir, scope, key, value) {
|
|
83
|
+
const file = tierFile(workDir, scope);
|
|
84
|
+
let existing = {};
|
|
85
|
+
if (fs.existsSync(file)) {
|
|
86
|
+
try {
|
|
87
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
88
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
|
|
89
|
+
existing = parsed;
|
|
90
|
+
else
|
|
91
|
+
return { ok: false, path: file, error: 'the file is valid JSON but not an object' };
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
return { ok: false, path: file, error: `could not parse it: ${e.message}` };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const next = { ...existing };
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
delete next[key];
|
|
100
|
+
else
|
|
101
|
+
next[key] = value;
|
|
102
|
+
try {
|
|
103
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
104
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
105
|
+
fs.writeFileSync(tmp, JSON.stringify(next, null, 2) + '\n', 'utf-8');
|
|
106
|
+
fs.renameSync(tmp, file);
|
|
107
|
+
return { ok: true, path: file };
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return { ok: false, path: file, error: e.message };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** The whole secrets file ({} when absent/unreadable/malformed). */
|
|
114
|
+
function readPluginSecrets() {
|
|
115
|
+
try {
|
|
116
|
+
const j = JSON.parse(fs.readFileSync(secretsPath(), 'utf-8'));
|
|
117
|
+
if (!j || typeof j !== 'object' || Array.isArray(j))
|
|
118
|
+
return {};
|
|
119
|
+
return j;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Write the secrets file 0600. Best-effort: a read-only home must not crash a save that the settings half may still complete. */
|
|
126
|
+
function writePluginSecrets(map) {
|
|
127
|
+
const file = secretsPath();
|
|
128
|
+
try {
|
|
129
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
130
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
131
|
+
fs.writeFileSync(tmp, JSON.stringify(map, null, 2) + '\n', { encoding: 'utf-8', mode: 0o600 });
|
|
132
|
+
fs.renameSync(tmp, file);
|
|
133
|
+
return { ok: true };
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
return { ok: false, error: e.message };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* `pluginConfigs` from every settings tier, merged per plugin per key
|
|
141
|
+
* (last tier wins — the same direction loadSettings uses for `raw`).
|
|
142
|
+
*/
|
|
143
|
+
function pluginConfigsFromSettings(workDir) {
|
|
144
|
+
const out = {};
|
|
145
|
+
for (const { raw } of (0, rules_1.settingsTiersRaw)(workDir)) {
|
|
146
|
+
const pc = raw.pluginConfigs;
|
|
147
|
+
if (!pc || typeof pc !== 'object' || Array.isArray(pc))
|
|
148
|
+
continue;
|
|
149
|
+
for (const [plugin, bag] of Object.entries(pc)) {
|
|
150
|
+
if (!bag || typeof bag !== 'object' || Array.isArray(bag))
|
|
151
|
+
continue;
|
|
152
|
+
out[plugin] = { ...(out[plugin] ?? {}), ...bag };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
/** Resolve every declared option to its effective value. Pure reads. */
|
|
158
|
+
function resolvePluginConfig(workDir, pluginName, options) {
|
|
159
|
+
const secrets = readPluginSecrets()[pluginName] ?? {};
|
|
160
|
+
const fromSettings = pluginConfigsFromSettings(workDir)[pluginName] ?? {};
|
|
161
|
+
const values = {};
|
|
162
|
+
const sources = {};
|
|
163
|
+
const invalid = [];
|
|
164
|
+
for (const option of options) {
|
|
165
|
+
let found = false;
|
|
166
|
+
for (const [source, bag] of [
|
|
167
|
+
['secret', secrets],
|
|
168
|
+
['settings', fromSettings],
|
|
169
|
+
]) {
|
|
170
|
+
if (!(option.key in bag))
|
|
171
|
+
continue;
|
|
172
|
+
const err = (0, userConfig_1.validateUserConfigValue)(option, bag[option.key]);
|
|
173
|
+
if (err) {
|
|
174
|
+
invalid.push({ key: option.key, source, error: err });
|
|
175
|
+
continue; // a broken higher-precedence value must not mask a working lower one
|
|
176
|
+
}
|
|
177
|
+
values[option.key] = bag[option.key];
|
|
178
|
+
sources[option.key] = source;
|
|
179
|
+
found = true;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
if (found)
|
|
183
|
+
continue;
|
|
184
|
+
if (option.default !== undefined) {
|
|
185
|
+
values[option.key] = option.default;
|
|
186
|
+
sources[option.key] = 'default';
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
const missing = options.filter((o) => o.required === true && !(o.key in values)).map((o) => o.key);
|
|
190
|
+
return { values, sources, missing, invalid };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Validate + save values. Unknown keys are errors (a typo must not create a
|
|
194
|
+
* silent no-op entry), as are values that fail their option's validation. The
|
|
195
|
+
* two halves land in different stores (see the file header).
|
|
196
|
+
*/
|
|
197
|
+
function savePluginConfig(workDir, pluginName, options, values, opts = {}) {
|
|
198
|
+
const byKey = new Map(options.map((o) => [o.key, o]));
|
|
199
|
+
const errors = [];
|
|
200
|
+
const plain = {};
|
|
201
|
+
const secret = {};
|
|
202
|
+
for (const [key, value] of Object.entries(values)) {
|
|
203
|
+
const option = byKey.get(key);
|
|
204
|
+
if (!option) {
|
|
205
|
+
errors.push(`"${key}" is not a declared option of plugin "${pluginName}"`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const bad = (0, userConfig_1.validateUserConfigValue)(option, value);
|
|
209
|
+
if (bad) {
|
|
210
|
+
errors.push(`${key}: ${bad}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (option.sensitive)
|
|
214
|
+
secret[key] = value;
|
|
215
|
+
else
|
|
216
|
+
plain[key] = value;
|
|
217
|
+
}
|
|
218
|
+
if (errors.length)
|
|
219
|
+
return { ok: false, errors };
|
|
220
|
+
const result = { ok: true, errors: [] };
|
|
221
|
+
if (Object.keys(secret).length) {
|
|
222
|
+
const map = readPluginSecrets();
|
|
223
|
+
map[pluginName] = { ...(map[pluginName] ?? {}), ...secret };
|
|
224
|
+
const w = writePluginSecrets(map);
|
|
225
|
+
if (!w.ok)
|
|
226
|
+
return { ok: false, errors: [`could not write the secrets file: ${w.error}`] };
|
|
227
|
+
result.wroteSecrets = true;
|
|
228
|
+
}
|
|
229
|
+
if (Object.keys(plain).length) {
|
|
230
|
+
const scope = opts.scope ?? 'user';
|
|
231
|
+
// Read the TARGET tier file's pluginConfigs and merge — the write replaces the
|
|
232
|
+
// whole top-level key, so passing a partial object would drop other plugins.
|
|
233
|
+
const file = tierFile(workDir, scope);
|
|
234
|
+
let tierPc = {};
|
|
235
|
+
try {
|
|
236
|
+
const j = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
237
|
+
const pc = j?.pluginConfigs;
|
|
238
|
+
if (pc && typeof pc === 'object' && !Array.isArray(pc)) {
|
|
239
|
+
tierPc = pc;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
/* no file yet, or unreadable — start from an empty map; the writer refuses a corrupt file itself */
|
|
244
|
+
}
|
|
245
|
+
const merged = { ...tierPc, [pluginName]: { ...(tierPc[pluginName] ?? {}), ...plain } };
|
|
246
|
+
const w = writeTierSetting(workDir, scope, 'pluginConfigs', merged);
|
|
247
|
+
if (!w.ok)
|
|
248
|
+
return { ok: false, errors: [`could not write settings: ${w.error}`] };
|
|
249
|
+
result.wroteSettings = w.path;
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=configStore.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const FILE_CANDIDATES: {
|
|
2
|
+
readonly manifest: readonly ["plugin.json", string];
|
|
3
|
+
readonly hooks: readonly ["hooks.json", string];
|
|
4
|
+
readonly mcp: readonly ["mcp.json", ".mcp.json"];
|
|
5
|
+
};
|
|
6
|
+
/** First existing candidate path for a component, or null. */
|
|
7
|
+
export declare function resolvePluginFile(dir: string, kind: keyof typeof FILE_CANDIDATES): string | null;
|
|
8
|
+
/** The plugin's manifest as a parsed object (null when absent/malformed/not an object). */
|
|
9
|
+
export declare function readPluginManifest(dir: string): Record<string, unknown> | null;
|
|
10
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/plugins/files.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,eAAe;;;;CAIlB,CAAC;AAEX,8DAA8D;AAC9D,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,OAAO,eAAe,GAAG,MAAM,GAAG,IAAI,CAUhG;AAED,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAS9E"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FILE_CANDIDATES = void 0;
|
|
37
|
+
exports.resolvePluginFile = resolvePluginFile;
|
|
38
|
+
exports.readPluginManifest = readPluginManifest;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
// ─── Component file locations ────────────────────────────────────────────────
|
|
42
|
+
//
|
|
43
|
+
// Extracted from plugins/index.ts (S24) so the modules that PARSE a plugin's
|
|
44
|
+
// files without loading it — userConfig schema reading, `nex plugin validate`,
|
|
45
|
+
// the config store — can resolve paths without importing the loader (which
|
|
46
|
+
// imports them back: a cycle).
|
|
47
|
+
//
|
|
48
|
+
// SECURITY: `hooks` and `mcp` here are code-execution surfaces. Every reader
|
|
49
|
+
// (plugins/index.ts, installer.ts's inspectPluginDir, validate.ts) MUST resolve
|
|
50
|
+
// through this same table - if the loader reads a path the inspector does not
|
|
51
|
+
// check, a plugin can run commands that the install-time warning never
|
|
52
|
+
// mentioned.
|
|
53
|
+
exports.FILE_CANDIDATES = {
|
|
54
|
+
manifest: ['plugin.json', path.join('.claude-plugin', 'plugin.json')],
|
|
55
|
+
hooks: ['hooks.json', path.join('hooks', 'hooks.json')],
|
|
56
|
+
mcp: ['mcp.json', '.mcp.json'],
|
|
57
|
+
};
|
|
58
|
+
/** First existing candidate path for a component, or null. */
|
|
59
|
+
function resolvePluginFile(dir, kind) {
|
|
60
|
+
for (const rel of exports.FILE_CANDIDATES[kind]) {
|
|
61
|
+
const full = path.join(dir, rel);
|
|
62
|
+
try {
|
|
63
|
+
if (fs.statSync(full).isFile())
|
|
64
|
+
return full;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
/* try the next candidate */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
/** The plugin's manifest as a parsed object (null when absent/malformed/not an object). */
|
|
73
|
+
function readPluginManifest(dir) {
|
|
74
|
+
const file = resolvePluginFile(dir, 'manifest');
|
|
75
|
+
if (!file)
|
|
76
|
+
return null;
|
|
77
|
+
try {
|
|
78
|
+
const j = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
79
|
+
return j && typeof j === 'object' && !Array.isArray(j) ? j : null;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=files.js.map
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { FILE_CANDIDATES, resolvePluginFile, readPluginManifest } from './files';
|
|
1
2
|
export interface PluginInfo {
|
|
2
3
|
/** Directory name (or plugin.json "name" when present). */
|
|
3
4
|
name: string;
|
|
@@ -13,26 +14,6 @@ export interface PluginInfo {
|
|
|
13
14
|
export declare function isPluginDisabled(dir: string): boolean;
|
|
14
15
|
/** Toggle a plugin on/off. Idempotent — enabling an already-enabled plugin is a no-op. */
|
|
15
16
|
export declare function setPluginDisabled(dir: string, disabled: boolean): void;
|
|
16
|
-
/**
|
|
17
|
-
* Where each single-file component may live, in priority order.
|
|
18
|
-
*
|
|
19
|
-
* Our own layout is checked first so a plugin shipping both is unambiguous, and
|
|
20
|
-
* so this stays additive: nothing that worked before changes behaviour.
|
|
21
|
-
*
|
|
22
|
-
* SECURITY: `hooks` and `mcp` here are code-execution surfaces. Every reader
|
|
23
|
-
* below and `inspectPluginDir` in installer.ts MUST resolve through this same
|
|
24
|
-
* table — if the loader reads a path the inspector does not check, a plugin can
|
|
25
|
-
* run commands that the install-time warning never mentioned. Adding a candidate
|
|
26
|
-
* without updating the inspector is exactly that bug, so the two are pinned
|
|
27
|
-
* together by a test.
|
|
28
|
-
*/
|
|
29
|
-
export declare const FILE_CANDIDATES: {
|
|
30
|
-
readonly manifest: readonly ["plugin.json", string];
|
|
31
|
-
readonly hooks: readonly ["hooks.json", string];
|
|
32
|
-
readonly mcp: readonly ["mcp.json", ".mcp.json"];
|
|
33
|
-
};
|
|
34
|
-
/** First existing candidate path for a component, or null. */
|
|
35
|
-
export declare function resolvePluginFile(dir: string, kind: keyof typeof FILE_CANDIDATES): string | null;
|
|
36
17
|
/**
|
|
37
18
|
* Discover installed plugins (project scope shadows global on name clash).
|
|
38
19
|
*
|
|
@@ -65,7 +46,7 @@ export declare function pluginAssetDirs(workDir: string, kind: 'commands' | 'age
|
|
|
65
46
|
* processes their NEXRALL_PLUGIN_DATA (see runHookHandler). Both entry forms are stamped:
|
|
66
47
|
* the flat one (the entry IS the handler) and the nested one (each item under `hooks`).
|
|
67
48
|
*/
|
|
68
|
-
export declare function pluginHooks(workDir: string): Record<string, unknown[]>;
|
|
49
|
+
export declare function pluginHooks(workDir: string, transform?: (entry: unknown, plugin: string) => unknown): Record<string, unknown[]>;
|
|
69
50
|
/** MCP server maps declared by ENABLED plugins: { serverName → config } (first plugin wins on clash).
|
|
70
51
|
*
|
|
71
52
|
* A stdio server from a plugin gets NEXRALL_PLUGIN_DATA in its environment, pointing at the
|
|
@@ -73,5 +54,5 @@ export declare function pluginHooks(workDir: string): Record<string, unknown[]>;
|
|
|
73
54
|
* this is the only place a server can keep state. A plugin that set the variable itself keeps
|
|
74
55
|
* its own value: an explicit config value is never overridden here.
|
|
75
56
|
*/
|
|
76
|
-
export declare function pluginMcpServers(workDir: string): Record<string, unknown>;
|
|
57
|
+
export declare function pluginMcpServers(workDir: string, transform?: (cfg: unknown, plugin: string) => unknown): Record<string, unknown>;
|
|
77
58
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/plugins/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAoCjF,MAAM,WAAW,UAAU;IACzB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAYD,oEAAoE;AACpE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAMrD;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,CAWtE;AAsDD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAKzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAM7G;AAED,gGAAgG;AAChG,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,EAAE,CAWjG;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,GACtD,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CA4B3B;AAeD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,GACpD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAuBzB"}
|