@nexrall/code-core 1.4.76 → 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.
Files changed (143) hide show
  1. package/dist/agent/effortCap.d.ts +24 -0
  2. package/dist/agent/effortCap.d.ts.map +1 -0
  3. package/dist/agent/effortCap.js +66 -0
  4. package/dist/agent/goal.d.ts +55 -0
  5. package/dist/agent/goal.d.ts.map +1 -0
  6. package/dist/agent/goal.js +120 -0
  7. package/dist/agent/handoffBrief.d.ts +82 -0
  8. package/dist/agent/handoffBrief.d.ts.map +1 -0
  9. package/dist/agent/handoffBrief.js +121 -0
  10. package/dist/agent/hooks.d.ts +10 -2
  11. package/dist/agent/hooks.d.ts.map +1 -1
  12. package/dist/agent/hooks.js +9 -3
  13. package/dist/agent/modelCatalogue.d.ts +23 -0
  14. package/dist/agent/modelCatalogue.d.ts.map +1 -1
  15. package/dist/agent/modelCatalogue.js +28 -0
  16. package/dist/agent/outputStyles.d.ts +27 -0
  17. package/dist/agent/outputStyles.d.ts.map +1 -0
  18. package/dist/agent/outputStyles.js +174 -0
  19. package/dist/agent/skills.d.ts.map +1 -1
  20. package/dist/agent/skills.js +21 -1
  21. package/dist/api/billingLines.d.ts +11 -0
  22. package/dist/api/billingLines.d.ts.map +1 -0
  23. package/dist/api/billingLines.js +56 -0
  24. package/dist/api/client.d.ts +2 -0
  25. package/dist/api/client.d.ts.map +1 -1
  26. package/dist/api/scheduleLines.d.ts +53 -0
  27. package/dist/api/scheduleLines.d.ts.map +1 -0
  28. package/dist/api/scheduleLines.js +150 -0
  29. package/dist/context/contextFiles.d.ts +81 -0
  30. package/dist/context/contextFiles.d.ts.map +1 -0
  31. package/dist/context/contextFiles.js +310 -0
  32. package/dist/diff/collect.d.ts +25 -0
  33. package/dist/diff/collect.d.ts.map +1 -0
  34. package/dist/diff/collect.js +169 -0
  35. package/dist/diff/diffView.d.ts +57 -0
  36. package/dist/diff/diffView.d.ts.map +1 -0
  37. package/dist/diff/diffView.js +231 -0
  38. package/dist/diff/reviewQueue.d.ts +53 -0
  39. package/dist/diff/reviewQueue.d.ts.map +1 -0
  40. package/dist/diff/reviewQueue.js +349 -0
  41. package/dist/events/recap.d.ts +54 -0
  42. package/dist/events/recap.d.ts.map +1 -0
  43. package/dist/events/recap.js +161 -0
  44. package/dist/events/sessionEvents.d.ts +54 -0
  45. package/dist/events/sessionEvents.d.ts.map +1 -0
  46. package/dist/events/sessionEvents.js +324 -0
  47. package/dist/events/sessionStats.d.ts +89 -0
  48. package/dist/events/sessionStats.d.ts.map +1 -0
  49. package/dist/events/sessionStats.js +264 -0
  50. package/dist/import/claudeImport.d.ts +44 -0
  51. package/dist/import/claudeImport.d.ts.map +1 -0
  52. package/dist/import/claudeImport.js +337 -0
  53. package/dist/index.d.ts +38 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +38 -0
  56. package/dist/mcp/manager.d.ts +8 -0
  57. package/dist/mcp/manager.d.ts.map +1 -1
  58. package/dist/mcp/manager.js +64 -57
  59. package/dist/permissions/destructiveTokens.d.ts +1 -0
  60. package/dist/permissions/destructiveTokens.d.ts.map +1 -1
  61. package/dist/permissions/destructiveTokens.js +93 -0
  62. package/dist/permissions/rules.d.ts +12 -0
  63. package/dist/permissions/rules.d.ts.map +1 -1
  64. package/dist/permissions/rules.js +89 -7
  65. package/dist/plugins/configStore.d.ts +43 -0
  66. package/dist/plugins/configStore.d.ts.map +1 -0
  67. package/dist/plugins/configStore.js +253 -0
  68. package/dist/plugins/files.d.ts +10 -0
  69. package/dist/plugins/files.d.ts.map +1 -0
  70. package/dist/plugins/files.js +85 -0
  71. package/dist/plugins/index.d.ts +3 -22
  72. package/dist/plugins/index.d.ts.map +1 -1
  73. package/dist/plugins/index.js +30 -40
  74. package/dist/plugins/resolve.d.ts +18 -0
  75. package/dist/plugins/resolve.d.ts.map +1 -0
  76. package/dist/plugins/resolve.js +152 -0
  77. package/dist/plugins/userConfig.d.ts +90 -0
  78. package/dist/plugins/userConfig.d.ts.map +1 -0
  79. package/dist/plugins/userConfig.js +332 -0
  80. package/dist/plugins/validate.d.ts +14 -0
  81. package/dist/plugins/validate.d.ts.map +1 -0
  82. package/dist/plugins/validate.js +222 -0
  83. package/dist/project/projectCommands.d.ts +33 -0
  84. package/dist/project/projectCommands.d.ts.map +1 -0
  85. package/dist/project/projectCommands.js +246 -0
  86. package/dist/search/conversationTurns.d.ts +53 -0
  87. package/dist/search/conversationTurns.d.ts.map +1 -0
  88. package/dist/search/conversationTurns.js +170 -0
  89. package/dist/search/globalSearch.d.ts +144 -0
  90. package/dist/search/globalSearch.d.ts.map +1 -0
  91. package/dist/search/globalSearch.js +350 -0
  92. package/dist/share/sessionChannel.d.ts +136 -0
  93. package/dist/share/sessionChannel.d.ts.map +1 -0
  94. package/dist/share/sessionChannel.js +373 -0
  95. package/dist/share/sessionShare.d.ts +104 -0
  96. package/dist/share/sessionShare.d.ts.map +1 -0
  97. package/dist/share/sessionShare.js +336 -0
  98. package/dist/ui/configCmd.d.ts +48 -0
  99. package/dist/ui/configCmd.d.ts.map +1 -0
  100. package/dist/ui/configCmd.js +146 -0
  101. package/dist/ui/deployPanel.d.ts +91 -0
  102. package/dist/ui/deployPanel.d.ts.map +1 -0
  103. package/dist/ui/deployPanel.js +442 -0
  104. package/dist/ui/mergeAssistant.d.ts +120 -0
  105. package/dist/ui/mergeAssistant.d.ts.map +1 -0
  106. package/dist/ui/mergeAssistant.js +391 -0
  107. package/dist/ui/notify.d.ts +114 -0
  108. package/dist/ui/notify.d.ts.map +1 -0
  109. package/dist/ui/notify.js +235 -0
  110. package/dist/ui/promptSuggest.d.ts +56 -0
  111. package/dist/ui/promptSuggest.d.ts.map +1 -0
  112. package/dist/ui/promptSuggest.js +160 -0
  113. package/dist/ui/taskGraph.d.ts +44 -0
  114. package/dist/ui/taskGraph.d.ts.map +1 -0
  115. package/dist/ui/taskGraph.js +289 -0
  116. package/dist/util/addDir.d.ts +31 -0
  117. package/dist/util/addDir.d.ts.map +1 -0
  118. package/dist/util/addDir.js +124 -0
  119. package/dist/util/batch.d.ts +16 -0
  120. package/dist/util/batch.d.ts.map +1 -0
  121. package/dist/util/batch.js +92 -0
  122. package/dist/util/cellWidth.d.ts +12 -0
  123. package/dist/util/cellWidth.d.ts.map +1 -0
  124. package/dist/util/cellWidth.js +161 -0
  125. package/dist/util/duration.d.ts +8 -0
  126. package/dist/util/duration.d.ts.map +1 -0
  127. package/dist/util/duration.js +50 -0
  128. package/dist/util/loopJobs.d.ts +67 -0
  129. package/dist/util/loopJobs.d.ts.map +1 -0
  130. package/dist/util/loopJobs.js +169 -0
  131. package/dist/util/settingsFile.d.ts +14 -0
  132. package/dist/util/settingsFile.d.ts.map +1 -0
  133. package/dist/util/settingsFile.js +99 -0
  134. package/dist/util/toolLabel.d.ts +2 -0
  135. package/dist/util/toolLabel.d.ts.map +1 -0
  136. package/dist/util/toolLabel.js +17 -0
  137. package/dist/verify/failureTimeline.d.ts +100 -0
  138. package/dist/verify/failureTimeline.d.ts.map +1 -0
  139. package/dist/verify/failureTimeline.js +253 -0
  140. package/dist/verify/verification.d.ts +75 -0
  141. package/dist/verify/verification.d.ts.map +1 -0
  142. package/dist/verify/verification.js +315 -0
  143. package/package.json +5 -4
@@ -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
@@ -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":"AAuCA,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;AAED;;;;;;;;;;;;GAYG;AACH,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;AA+CD;;;;;;;;;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,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CAoBtE;AAeD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkBzE"}
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"}
@@ -33,10 +33,9 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.FILE_CANDIDATES = void 0;
36
+ exports.readPluginManifest = exports.resolvePluginFile = exports.FILE_CANDIDATES = void 0;
37
37
  exports.isPluginDisabled = isPluginDisabled;
38
38
  exports.setPluginDisabled = setPluginDisabled;
39
- exports.resolvePluginFile = resolvePluginFile;
40
39
  exports.loadPlugins = loadPlugins;
41
40
  exports.findPlugin = findPlugin;
42
41
  exports.pluginAssetDirs = pluginAssetDirs;
@@ -46,6 +45,11 @@ const fs = __importStar(require("fs"));
46
45
  const path = __importStar(require("path"));
47
46
  const os = __importStar(require("os"));
48
47
  const data_1 = require("./data");
48
+ const files_1 = require("./files");
49
+ var files_2 = require("./files");
50
+ Object.defineProperty(exports, "FILE_CANDIDATES", { enumerable: true, get: function () { return files_2.FILE_CANDIDATES; } });
51
+ Object.defineProperty(exports, "resolvePluginFile", { enumerable: true, get: function () { return files_2.resolvePluginFile; } });
52
+ Object.defineProperty(exports, "readPluginManifest", { enumerable: true, get: function () { return files_2.readPluginManifest; } });
49
53
  // ─── Enable / disable ──────────────────────────────────────────────────────────
50
54
  //
51
55
  // A marker file rather than a registry list, so the on/off state travels WITH
@@ -80,39 +84,13 @@ function setPluginDisabled(dir, disabled) {
80
84
  }
81
85
  }
82
86
  /**
83
- * Where each single-file component may live, in priority order.
84
- *
85
- * Our own layout is checked first so a plugin shipping both is unambiguous, and
86
- * so this stays additive: nothing that worked before changes behaviour.
87
- *
88
- * SECURITY: `hooks` and `mcp` here are code-execution surfaces. Every reader
89
- * below and `inspectPluginDir` in installer.ts MUST resolve through this same
90
- * table — if the loader reads a path the inspector does not check, a plugin can
91
- * run commands that the install-time warning never mentioned. Adding a candidate
92
- * without updating the inspector is exactly that bug, so the two are pinned
93
- * together by a test.
87
+ * Where each single-file component may live, in priority order — moved to
88
+ * ./files.ts (S24) so parsing-only modules (validate, the config store) can
89
+ * resolve paths without importing this loader (which imports them back).
90
+ * Re-exported above for existing callers.
94
91
  */
95
- exports.FILE_CANDIDATES = {
96
- manifest: ['plugin.json', path.join('.claude-plugin', 'plugin.json')],
97
- hooks: ['hooks.json', path.join('hooks', 'hooks.json')],
98
- mcp: ['mcp.json', '.mcp.json'],
99
- };
100
- /** First existing candidate path for a component, or null. */
101
- function resolvePluginFile(dir, kind) {
102
- for (const rel of exports.FILE_CANDIDATES[kind]) {
103
- const full = path.join(dir, rel);
104
- try {
105
- if (fs.statSync(full).isFile())
106
- return full;
107
- }
108
- catch {
109
- /* try the next candidate */
110
- }
111
- }
112
- return null;
113
- }
114
92
  function readMeta(dir) {
115
- const file = resolvePluginFile(dir, 'manifest');
93
+ const file = (0, files_1.resolvePluginFile)(dir, 'manifest');
116
94
  if (!file)
117
95
  return {};
118
96
  try {
@@ -215,12 +193,12 @@ function pluginAssetDirs(workDir, kind) {
215
193
  * processes their NEXRALL_PLUGIN_DATA (see runHookHandler). Both entry forms are stamped:
216
194
  * the flat one (the entry IS the handler) and the nested one (each item under `hooks`).
217
195
  */
218
- function pluginHooks(workDir) {
196
+ function pluginHooks(workDir, transform) {
219
197
  const merged = {};
220
198
  for (const p of loadPlugins(workDir)) {
221
199
  if (p.disabled)
222
200
  continue;
223
- const file = resolvePluginFile(p.dir, 'hooks');
201
+ const file = (0, files_1.resolvePluginFile)(p.dir, 'hooks');
224
202
  if (!file)
225
203
  continue;
226
204
  try {
@@ -231,7 +209,15 @@ function pluginHooks(workDir) {
231
209
  for (const [phase, entries] of Object.entries(hooks)) {
232
210
  if (!Array.isArray(entries))
233
211
  continue;
234
- merged[phase] = [...(merged[phase] ?? []), ...entries.map((e) => stampPlugin(e, p.name))];
212
+ // `transform` (S24) lets plugins/resolve.ts bake each handler's resolved
213
+ // userConfig env map in, keeping THIS loader free of value resolution.
214
+ merged[phase] = [
215
+ ...(merged[phase] ?? []),
216
+ ...entries.map((e) => {
217
+ const stamped = stampPlugin(e, p.name);
218
+ return transform ? transform(stamped, p.name) : stamped;
219
+ }),
220
+ ];
235
221
  }
236
222
  }
237
223
  catch {
@@ -260,12 +246,12 @@ function stampPlugin(entry, plugin) {
260
246
  * this is the only place a server can keep state. A plugin that set the variable itself keeps
261
247
  * its own value: an explicit config value is never overridden here.
262
248
  */
263
- function pluginMcpServers(workDir) {
249
+ function pluginMcpServers(workDir, transform) {
264
250
  const merged = {};
265
251
  for (const p of loadPlugins(workDir)) {
266
252
  if (p.disabled)
267
253
  continue;
268
- const file = resolvePluginFile(p.dir, 'mcp');
254
+ const file = (0, files_1.resolvePluginFile)(p.dir, 'mcp');
269
255
  if (!file)
270
256
  continue;
271
257
  try {
@@ -273,8 +259,12 @@ function pluginMcpServers(workDir) {
273
259
  const j = JSON.parse(raw);
274
260
  const servers = (j.mcpServers ?? j);
275
261
  for (const [name, cfg] of Object.entries(servers)) {
276
- if (!(name in merged))
277
- merged[name] = withPluginDataEnv(cfg, p.name);
262
+ if (!(name in merged)) {
263
+ // `transform` (S24) is where plugins/resolve.ts substitutes
264
+ // `${user_config.KEY}`; the data-dir env is added after it.
265
+ const resolved = transform ? transform(cfg, p.name) : cfg;
266
+ merged[name] = withPluginDataEnv(resolved, p.name);
267
+ }
278
268
  }
279
269
  }
280
270
  catch {
@@ -0,0 +1,18 @@
1
+ import { type UserConfigOption } from './userConfig';
2
+ export interface PluginResolvedConfig {
3
+ options: UserConfigOption[];
4
+ values: Record<string, unknown>;
5
+ }
6
+ /** pluginHooks + each handler's resolved NEXRALL_PLUGIN_OPTION_* env map. */
7
+ export declare function resolvePluginHooks(workDir: string): Record<string, unknown[]>;
8
+ /** pluginMcpServers + `${user_config.KEY}` substitution (sensitive included). */
9
+ export declare function resolvePluginMcpServers(workDir: string): Record<string, unknown>;
10
+ /**
11
+ * The resolved config of the plugin a skill DIRECTORY belongs to, or null.
12
+ * Used by skills.ts's expandSkill: a skill under `<plugin>/skills/<name>/` gets
13
+ * its body substituted with that plugin's NON-SENSITIVE values.
14
+ */
15
+ export declare function pluginConfigForSkillDir(workDir: string, skillDir: string): PluginResolvedConfig | null;
16
+ /** Substitute one text with a plugin's values, non-sensitive only (skill bodies). */
17
+ export declare function substituteSkillBody(text: string, cfg: PluginResolvedConfig): string;
18
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/plugins/resolve.ts"],"names":[],"mappings":"AAsBA,OAAO,EAKL,KAAK,gBAAgB,EACtB,MAAM,cAAc,CAAC;AAGtB,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AAcD,6EAA6E;AAC7E,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,CA2B7E;AAED,iFAAiF;AACjF,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQhF;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAgBtG;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,GAAG,MAAM,CAEnF"}