claude-dev-env 8.51.3 → 8.52.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/bin/install-pstack-plugin.mjs +68 -33
- package/bin/install-pstack-plugin.test.mjs +77 -32
- package/bin/install.agents-home.test.mjs +1 -0
- package/bin/install.codex-rules.test.mjs +2 -0
- package/bin/install.cursor-rules.test.mjs +1 -0
- package/bin/install.mjs +18 -12
- package/bin/install.prune.test.mjs +1 -0
- package/bin/install.question-presentation.test.mjs +4 -0
- package/bin/install.settings-defaults.test.mjs +10 -2
- package/bin/install.shared-settings.test.mjs +1 -0
- package/bin/install.test.mjs +56 -0
- package/bin/install.transaction.test.mjs +8 -0
- package/bin/install.uninstall-transaction.test.mjs +2 -0
- package/bin/install.update-keeps-hooks.test.mjs +1 -0
- package/package.json +1 -1
- package/settings.json +13 -6
|
@@ -15,7 +15,7 @@ export const PSTACK_PLUGIN_SPEC = Object.freeze({
|
|
|
15
15
|
label: 'Pstack',
|
|
16
16
|
marketplaceRepository: PSTACK_MARKETPLACE_REPOSITORY,
|
|
17
17
|
marketplaceName: PSTACK_MARKETPLACE_NAME,
|
|
18
|
-
|
|
18
|
+
sparsePaths: Object.freeze([]),
|
|
19
19
|
pluginIdentifier: PSTACK_PLUGIN_IDENTIFIER,
|
|
20
20
|
hosts: PSTACK_PLUGIN_HOSTS,
|
|
21
21
|
optOutFlag: PSTACK_PLUGIN_OPT_OUT_FLAG,
|
|
@@ -27,13 +27,25 @@ export const USAGE_WRAPUP_PLUGIN_SPEC = Object.freeze({
|
|
|
27
27
|
label: 'Usage-wrapup',
|
|
28
28
|
marketplaceRepository: 'jl-cmd/claude-dev-env',
|
|
29
29
|
marketplaceName: 'claude-dev-env',
|
|
30
|
-
|
|
30
|
+
sparsePaths: Object.freeze(['.claude-plugin', 'packages/usage-wrapup']),
|
|
31
31
|
pluginIdentifier: 'usage-wrapup@claude-dev-env',
|
|
32
32
|
hosts: Object.freeze(['claude']),
|
|
33
33
|
optOutFlag: '--no-usage-wrapup',
|
|
34
34
|
optOutVariable: 'CDE_INSTALL_USAGE_WRAPUP',
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
+
export const SUBAGENT_MODELS_PLUGIN_SPEC = Object.freeze({
|
|
38
|
+
name: 'subagent-models',
|
|
39
|
+
label: 'Subagent-models',
|
|
40
|
+
marketplaceRepository: 'jl-cmd/claude-dev-env',
|
|
41
|
+
marketplaceName: 'claude-dev-env',
|
|
42
|
+
sparsePaths: Object.freeze(['.claude-plugin', 'packages/subagent-models']),
|
|
43
|
+
pluginIdentifier: 'subagent-models@claude-dev-env',
|
|
44
|
+
hosts: Object.freeze(['claude']),
|
|
45
|
+
optOutFlag: '--no-subagent-models',
|
|
46
|
+
optOutVariable: 'CDE_INSTALL_SUBAGENT_MODELS',
|
|
47
|
+
});
|
|
48
|
+
|
|
37
49
|
const HOST_DEFINITIONS = Object.freeze({
|
|
38
50
|
claude: Object.freeze({
|
|
39
51
|
executable: 'claude',
|
|
@@ -55,42 +67,60 @@ const HOST_DEFINITIONS = Object.freeze({
|
|
|
55
67
|
});
|
|
56
68
|
|
|
57
69
|
/**
|
|
58
|
-
* Read the marketplace and plugin commands one host installs a
|
|
70
|
+
* Read the marketplace and plugin commands one host installs a set of plugins with.
|
|
59
71
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
72
|
+
* Every plugin in the set comes from one marketplace. Removing a marketplace
|
|
73
|
+
* uninstalls each plugin it supplied, so the plan removes and adds that
|
|
74
|
+
* marketplace once for the whole set. The removal commands run first and take
|
|
75
|
+
* out each installed plugin, its cached versions, and the marketplace clone,
|
|
76
|
+
* so the install commands that follow fetch the newest published version into
|
|
77
|
+
* an empty slot.
|
|
63
78
|
*
|
|
64
79
|
* Claude Code and Codex publish a marketplace under different plugin verbs, so
|
|
65
80
|
* the install verb belongs to the host and the marketplace and identifier
|
|
66
81
|
* belong to the plugin spec. A spec whose marketplace is a large repository
|
|
67
|
-
*
|
|
68
|
-
* clone holds only the catalog and
|
|
82
|
+
* names its sparse paths, and the one marketplace add checks out every path
|
|
83
|
+
* the set names, so the clone holds only the catalog and those plugin folders
|
|
84
|
+
* and stays clear of Windows path-length limits.
|
|
69
85
|
*
|
|
70
|
-
* @param {typeof PSTACK_PLUGIN_SPEC}
|
|
71
|
-
* @param {string} host A host
|
|
86
|
+
* @param {ReadonlyArray<typeof PSTACK_PLUGIN_SPEC>} allSpecs The plugins to install, from one marketplace.
|
|
87
|
+
* @param {string} host A host every spec names, `claude` or `codex`.
|
|
72
88
|
* @returns {{executable: string, executableVariable: string, homeVariable: string,
|
|
73
89
|
* removalCommands: ReadonlyArray<ReadonlyArray<string>>,
|
|
74
90
|
* commands: ReadonlyArray<ReadonlyArray<string>>}} The host's install plan.
|
|
75
91
|
*/
|
|
76
|
-
export function
|
|
92
|
+
export function marketplacePluginsPlan(allSpecs, host) {
|
|
77
93
|
const hostDefinition = HOST_DEFINITIONS[host];
|
|
78
|
-
|
|
94
|
+
for (const eachSpec of allSpecs) {
|
|
95
|
+
if (!hostDefinition || !eachSpec.hosts.includes(host)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`Unsupported ${eachSpec.name} plugin host ${host}: this installer knows ${eachSpec.hosts.join(', ')}.`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const [firstSpec] = allSpecs;
|
|
102
|
+
const otherMarketplaceSpec = allSpecs.find(
|
|
103
|
+
eachSpec => eachSpec.marketplaceRepository !== firstSpec.marketplaceRepository,
|
|
104
|
+
);
|
|
105
|
+
if (otherMarketplaceSpec) {
|
|
79
106
|
throw new Error(
|
|
80
|
-
|
|
107
|
+
`${otherMarketplaceSpec.name} comes from ${otherMarketplaceSpec.marketplaceRepository}, `
|
|
108
|
+
+ `and one plan installs from ${firstSpec.marketplaceRepository} only.`,
|
|
81
109
|
);
|
|
82
110
|
}
|
|
111
|
+
const allSparsePaths = [...new Set(allSpecs.flatMap(eachSpec => eachSpec.sparsePaths))];
|
|
112
|
+
const sparseArguments = allSparsePaths.length > 0 ? ['--sparse', ...allSparsePaths] : [];
|
|
83
113
|
return {
|
|
84
114
|
executable: hostDefinition.executable,
|
|
85
115
|
executableVariable: hostDefinition.executableVariable,
|
|
86
116
|
homeVariable: hostDefinition.homeVariable,
|
|
87
117
|
removalCommands: [
|
|
88
|
-
['plugin', hostDefinition.uninstallVerb,
|
|
89
|
-
['plugin', 'marketplace', 'remove',
|
|
118
|
+
...allSpecs.map(eachSpec => ['plugin', hostDefinition.uninstallVerb, eachSpec.pluginIdentifier]),
|
|
119
|
+
['plugin', 'marketplace', 'remove', firstSpec.marketplaceName],
|
|
90
120
|
],
|
|
91
121
|
commands: [
|
|
92
|
-
['plugin', 'marketplace', 'add',
|
|
93
|
-
['plugin', hostDefinition.installVerb,
|
|
122
|
+
['plugin', 'marketplace', 'add', firstSpec.marketplaceRepository, ...sparseArguments],
|
|
123
|
+
...allSpecs.map(eachSpec => ['plugin', hostDefinition.installVerb, eachSpec.pluginIdentifier]),
|
|
94
124
|
],
|
|
95
125
|
};
|
|
96
126
|
}
|
|
@@ -198,30 +228,31 @@ export function removeOrphanedPluginVersions(versionsDirectory) {
|
|
|
198
228
|
return removedPaths;
|
|
199
229
|
}
|
|
200
230
|
|
|
201
|
-
function absentHostOutcome(
|
|
231
|
+
function absentHostOutcome(allSpecs, host, executable) {
|
|
232
|
+
const pluginNames = allSpecs.map(eachSpec => eachSpec.name).join(', ');
|
|
202
233
|
return {
|
|
203
234
|
host,
|
|
204
235
|
executable,
|
|
205
236
|
status: 'skipped',
|
|
206
|
-
warning: `${executable} is not on PATH, so ${
|
|
237
|
+
warning: `${executable} is not on PATH, so ${pluginNames} was not installed for ${host}.`,
|
|
207
238
|
};
|
|
208
239
|
}
|
|
209
240
|
|
|
210
|
-
function installForHost(
|
|
211
|
-
const plan =
|
|
241
|
+
function installForHost(allSpecs, host, homeDirectory, environment, runCommand) {
|
|
242
|
+
const plan = marketplacePluginsPlan(allSpecs, host);
|
|
212
243
|
const executable = environment[plan.executableVariable] || plan.executable;
|
|
213
244
|
const commandEnvironment = { [plan.homeVariable]: homeDirectory };
|
|
214
245
|
for (const commandArguments of plan.removalCommands) {
|
|
215
246
|
const outcome = runCommand(executable, [...commandArguments], {
|
|
216
247
|
environment: commandEnvironment,
|
|
217
248
|
});
|
|
218
|
-
if (namesAnAbsentCommand(outcome)) return absentHostOutcome(
|
|
249
|
+
if (namesAnAbsentCommand(outcome)) return absentHostOutcome(allSpecs, host, executable);
|
|
219
250
|
}
|
|
220
251
|
for (const commandArguments of plan.commands) {
|
|
221
252
|
const outcome = runCommand(executable, [...commandArguments], {
|
|
222
253
|
environment: commandEnvironment,
|
|
223
254
|
});
|
|
224
|
-
if (namesAnAbsentCommand(outcome)) return absentHostOutcome(
|
|
255
|
+
if (namesAnAbsentCommand(outcome)) return absentHostOutcome(allSpecs, host, executable);
|
|
225
256
|
if (outcome.status !== 0 || outcome.error) {
|
|
226
257
|
const detail = firstLine(outcome.stderr) || outcome.error?.message || `exit ${outcome.status}`;
|
|
227
258
|
return {
|
|
@@ -234,17 +265,19 @@ function installForHost(spec, host, homeDirectory, environment, runCommand) {
|
|
|
234
265
|
}
|
|
235
266
|
const hostDefinition = HOST_DEFINITIONS[host];
|
|
236
267
|
if (hostDefinition.orphanedVersionsDirectory && homeDirectory) {
|
|
237
|
-
|
|
268
|
+
for (const eachSpec of allSpecs) {
|
|
269
|
+
removeOrphanedPluginVersions(hostDefinition.orphanedVersionsDirectory(homeDirectory, eachSpec));
|
|
270
|
+
}
|
|
238
271
|
}
|
|
239
272
|
return { host, executable, status: 'installed', warning: null };
|
|
240
273
|
}
|
|
241
274
|
|
|
242
275
|
/**
|
|
243
|
-
* Install
|
|
276
|
+
* Install a set of plugins from one marketplace into each host's own home.
|
|
244
277
|
*
|
|
245
|
-
* Each host first removes
|
|
246
|
-
* version never survives beside the new one. A removal that finds
|
|
247
|
-
* to remove exits non-zero on a fresh home, and the install goes on.
|
|
278
|
+
* Each host first removes every plugin in the set and then the marketplace,
|
|
279
|
+
* so an older version never survives beside the new one. A removal that finds
|
|
280
|
+
* nothing to remove exits non-zero on a fresh home, and the install goes on.
|
|
248
281
|
*
|
|
249
282
|
* Each host is one member of the batch. A host without its command-line tool
|
|
250
283
|
* is skipped and a host whose command fails is reported, so the rules, hooks,
|
|
@@ -253,22 +286,24 @@ function installForHost(spec, host, homeDirectory, environment, runCommand) {
|
|
|
253
286
|
* `cmd.exe`'s command-not-found exit code on Windows, and both read as
|
|
254
287
|
* skipped.
|
|
255
288
|
*
|
|
256
|
-
* @param {typeof PSTACK_PLUGIN_SPEC}
|
|
289
|
+
* @param {ReadonlyArray<typeof PSTACK_PLUGIN_SPEC>} allSpecs The plugins to install, from one marketplace.
|
|
257
290
|
* @param {object} [options] Install targets.
|
|
258
291
|
* @param {string} [options.claudeRoot] The managed Claude root to install into.
|
|
259
292
|
* @param {string} [options.codexHome] The Codex home to install into.
|
|
260
|
-
* @param {string[]} [options.hosts] The hosts to install. Defaults to the spec
|
|
293
|
+
* @param {string[]} [options.hosts] The hosts to install. Defaults to the hosts every spec names.
|
|
261
294
|
* @param {Record<string, string|undefined>} [options.environment] The process environment.
|
|
262
295
|
* @param {object} [dependencies] Seams for the command runner.
|
|
263
296
|
* @returns {{status: string, hosts: object[], warning: string|null}} The outcome per host.
|
|
264
297
|
*/
|
|
265
|
-
export function
|
|
298
|
+
export function installMarketplacePlugins(allSpecs, options = {}, dependencies = {}) {
|
|
266
299
|
const environment = options.environment ?? process.env;
|
|
267
300
|
const runCommand = dependencies.runCommand ?? runHostCommand;
|
|
268
301
|
const homeDirectories = { claude: options.claudeRoot, codex: options.codexHome };
|
|
269
|
-
const hosts = options.hosts ??
|
|
302
|
+
const hosts = options.hosts ?? allSpecs[0].hosts.filter(
|
|
303
|
+
eachHost => allSpecs.every(eachSpec => eachSpec.hosts.includes(eachHost)),
|
|
304
|
+
);
|
|
270
305
|
const hostOutcomes = hosts.map(host => installForHost(
|
|
271
|
-
|
|
306
|
+
allSpecs,
|
|
272
307
|
host,
|
|
273
308
|
homeDirectories[host],
|
|
274
309
|
environment,
|
|
@@ -8,10 +8,11 @@ import {
|
|
|
8
8
|
PSTACK_PLUGIN_HOSTS,
|
|
9
9
|
PSTACK_PLUGIN_IDENTIFIER,
|
|
10
10
|
PSTACK_PLUGIN_SPEC,
|
|
11
|
+
SUBAGENT_MODELS_PLUGIN_SPEC,
|
|
11
12
|
USAGE_WRAPUP_PLUGIN_SPEC,
|
|
12
13
|
hostCommandInvocation,
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
installMarketplacePlugins,
|
|
15
|
+
marketplacePluginsPlan,
|
|
15
16
|
removeOrphanedPluginVersions,
|
|
16
17
|
shouldInstallMarketplacePlugin,
|
|
17
18
|
} from './install-pstack-plugin.mjs';
|
|
@@ -36,7 +37,7 @@ const ROOTS = Object.freeze({
|
|
|
36
37
|
const WINDOWS_ENVIRONMENT = Object.freeze({ ComSpec: 'C:\\Windows\\system32\\cmd.exe' });
|
|
37
38
|
|
|
38
39
|
test('the Claude plan carries the two documented plugin commands', () => {
|
|
39
|
-
const plan =
|
|
40
|
+
const plan = marketplacePluginsPlan([PSTACK_PLUGIN_SPEC], 'claude');
|
|
40
41
|
assert.equal(plan.executable, 'claude');
|
|
41
42
|
assert.deepEqual(plan.commands, [
|
|
42
43
|
['plugin', 'marketplace', 'add', PSTACK_MARKETPLACE_REPOSITORY],
|
|
@@ -45,7 +46,7 @@ test('the Claude plan carries the two documented plugin commands', () => {
|
|
|
45
46
|
});
|
|
46
47
|
|
|
47
48
|
test('the Codex plan adds the plugin with the Codex verb', () => {
|
|
48
|
-
const plan =
|
|
49
|
+
const plan = marketplacePluginsPlan([PSTACK_PLUGIN_SPEC], 'codex');
|
|
49
50
|
assert.equal(plan.executable, 'codex');
|
|
50
51
|
assert.deepEqual(plan.commands, [
|
|
51
52
|
['plugin', 'marketplace', 'add', PSTACK_MARKETPLACE_REPOSITORY],
|
|
@@ -54,13 +55,13 @@ test('the Codex plan adds the plugin with the Codex verb', () => {
|
|
|
54
55
|
});
|
|
55
56
|
|
|
56
57
|
test('an unsupported host names the hosts this installer knows', () => {
|
|
57
|
-
assert.throws(() =>
|
|
58
|
+
assert.throws(() => marketplacePluginsPlan([PSTACK_PLUGIN_SPEC], 'cursor'), /cursor/);
|
|
58
59
|
assert.deepEqual(PSTACK_PLUGIN_HOSTS, ['claude', 'codex']);
|
|
59
60
|
});
|
|
60
61
|
|
|
61
62
|
test('each host installs into the managed root this run wrote', () => {
|
|
62
63
|
const runner = recordingRunner();
|
|
63
|
-
const outcome =
|
|
64
|
+
const outcome = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], ROOTS, runner);
|
|
64
65
|
assert.equal(outcome.status, 'installed');
|
|
65
66
|
assert.deepEqual(runner.calls.map(call => [call.executable, ...call.commandArguments]), [
|
|
66
67
|
['claude', 'plugin', 'uninstall', PSTACK_PLUGIN_IDENTIFIER],
|
|
@@ -77,11 +78,11 @@ test('each host installs into the managed root this run wrote', () => {
|
|
|
77
78
|
});
|
|
78
79
|
|
|
79
80
|
test('each host removes the installed plugin and marketplace before it installs', () => {
|
|
80
|
-
assert.deepEqual(
|
|
81
|
+
assert.deepEqual(marketplacePluginsPlan([PSTACK_PLUGIN_SPEC], 'claude').removalCommands, [
|
|
81
82
|
['plugin', 'uninstall', PSTACK_PLUGIN_IDENTIFIER],
|
|
82
83
|
['plugin', 'marketplace', 'remove', 'pstack-claude'],
|
|
83
84
|
]);
|
|
84
|
-
assert.deepEqual(
|
|
85
|
+
assert.deepEqual(marketplacePluginsPlan([PSTACK_PLUGIN_SPEC], 'codex').removalCommands, [
|
|
85
86
|
['plugin', 'remove', PSTACK_PLUGIN_IDENTIFIER],
|
|
86
87
|
['plugin', 'marketplace', 'remove', 'pstack-claude'],
|
|
87
88
|
]);
|
|
@@ -92,7 +93,7 @@ test('a removal that finds nothing installed still lets the install run', () =>
|
|
|
92
93
|
commandArguments.includes('uninstall') || commandArguments.includes('remove')
|
|
93
94
|
? { status: 1, stderr: 'not installed' }
|
|
94
95
|
: { status: 0, stderr: '' }));
|
|
95
|
-
const outcome =
|
|
96
|
+
const outcome = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], { ...ROOTS, hosts: ['claude'] }, runner);
|
|
96
97
|
assert.equal(outcome.status, 'installed');
|
|
97
98
|
assert.deepEqual(runner.calls.at(-1).commandArguments, ['plugin', 'install', PSTACK_PLUGIN_IDENTIFIER]);
|
|
98
99
|
});
|
|
@@ -101,7 +102,7 @@ test('an absent host command skips that host and leaves the other installed', ()
|
|
|
101
102
|
const runner = recordingRunner((executable) => (executable === 'codex'
|
|
102
103
|
? { status: null, error: Object.assign(new Error('spawn codex ENOENT'), { code: 'ENOENT' }) }
|
|
103
104
|
: { status: 0, stderr: '' }));
|
|
104
|
-
const outcome =
|
|
105
|
+
const outcome = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], ROOTS, runner);
|
|
105
106
|
assert.equal(outcome.hosts.find(host => host.host === 'claude').status, 'installed');
|
|
106
107
|
const codex = outcome.hosts.find(host => host.host === 'codex');
|
|
107
108
|
assert.equal(codex.status, 'skipped');
|
|
@@ -114,7 +115,7 @@ test('a failing command reports that host and still installs the next one', () =
|
|
|
114
115
|
executable === 'claude' && commandArguments[2] === 'add'
|
|
115
116
|
? { status: 1, stderr: 'marketplace unreachable' }
|
|
116
117
|
: { status: 0, stderr: '' }));
|
|
117
|
-
const outcome =
|
|
118
|
+
const outcome = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], ROOTS, runner);
|
|
118
119
|
assert.equal(outcome.status, 'failed');
|
|
119
120
|
const claude = outcome.hosts.find(host => host.host === 'claude');
|
|
120
121
|
assert.equal(claude.status, 'failed');
|
|
@@ -126,8 +127,8 @@ test('a failing command reports that host and still installs the next one', () =
|
|
|
126
127
|
|
|
127
128
|
test('an executable override replaces the host command for that host only', () => {
|
|
128
129
|
const runner = recordingRunner();
|
|
129
|
-
|
|
130
|
-
PSTACK_PLUGIN_SPEC,
|
|
130
|
+
installMarketplacePlugins(
|
|
131
|
+
[PSTACK_PLUGIN_SPEC],
|
|
131
132
|
{ ...ROOTS, environment: { CDE_CODEX_EXECUTABLE: '/opt/codex/bin/codex' } },
|
|
132
133
|
runner,
|
|
133
134
|
);
|
|
@@ -146,7 +147,7 @@ test('the opt-out flag and variable both turn the step off', () => {
|
|
|
146
147
|
|
|
147
148
|
test('a selected host list installs only that host', () => {
|
|
148
149
|
const runner = recordingRunner();
|
|
149
|
-
const outcome =
|
|
150
|
+
const outcome = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], { ...ROOTS, hosts: ['codex'] }, runner);
|
|
150
151
|
assert.deepEqual(outcome.hosts.map(host => host.host), ['codex']);
|
|
151
152
|
assert.deepEqual([...new Set(runner.calls.map(call => call.executable))], ['codex']);
|
|
152
153
|
});
|
|
@@ -199,7 +200,7 @@ test("cmd.exe's command-not-found exit code reads as an absent host, not a failu
|
|
|
199
200
|
stderr: "'codex' is not recognized as an internal or external command,\noperable program or batch file.",
|
|
200
201
|
}
|
|
201
202
|
: { status: 0, stderr: '' }));
|
|
202
|
-
const outcome =
|
|
203
|
+
const outcome = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], ROOTS, runner);
|
|
203
204
|
const codex = outcome.hosts.find(host => host.host === 'codex');
|
|
204
205
|
assert.equal(codex.status, 'skipped');
|
|
205
206
|
assert.equal(outcome.status, 'installed');
|
|
@@ -208,13 +209,13 @@ test("cmd.exe's command-not-found exit code reads as an absent host, not a failu
|
|
|
208
209
|
|
|
209
210
|
test('a non-zero exit that is not command-not-found still reads as a failure', () => {
|
|
210
211
|
const runner = recordingRunner(() => ({ status: 9009, stderr: 'the marketplace rejected the catalog' }));
|
|
211
|
-
const outcome =
|
|
212
|
+
const outcome = installMarketplacePlugins([PSTACK_PLUGIN_SPEC], { ...ROOTS, hosts: ['claude'] }, runner);
|
|
212
213
|
assert.equal(outcome.hosts[0].status, 'failed');
|
|
213
214
|
assert.match(outcome.hosts[0].warning, /rejected the catalog/);
|
|
214
215
|
});
|
|
215
216
|
|
|
216
217
|
test('the usage-wrapup plan adds this repository marketplace and installs the plugin on Claude', () => {
|
|
217
|
-
const plan =
|
|
218
|
+
const plan = marketplacePluginsPlan([USAGE_WRAPUP_PLUGIN_SPEC], 'claude');
|
|
218
219
|
assert.equal(plan.executable, 'claude');
|
|
219
220
|
assert.equal(plan.homeVariable, 'CLAUDE_CONFIG_DIR');
|
|
220
221
|
assert.deepEqual(plan.commands, [
|
|
@@ -225,12 +226,12 @@ test('the usage-wrapup plan adds this repository marketplace and installs the pl
|
|
|
225
226
|
|
|
226
227
|
test('usage-wrapup is a Claude-only plugin and refuses a Codex plan', () => {
|
|
227
228
|
assert.deepEqual(USAGE_WRAPUP_PLUGIN_SPEC.hosts, ['claude']);
|
|
228
|
-
assert.throws(() =>
|
|
229
|
+
assert.throws(() => marketplacePluginsPlan([USAGE_WRAPUP_PLUGIN_SPEC], 'codex'), /usage-wrapup plugin host codex/);
|
|
229
230
|
});
|
|
230
231
|
|
|
231
232
|
test('installing usage-wrapup runs only the Claude commands, inside the managed Claude root', () => {
|
|
232
233
|
const runner = recordingRunner();
|
|
233
|
-
const outcome =
|
|
234
|
+
const outcome = installMarketplacePlugins([USAGE_WRAPUP_PLUGIN_SPEC], ROOTS, runner);
|
|
234
235
|
assert.equal(outcome.status, 'installed');
|
|
235
236
|
assert.deepEqual(outcome.hosts.map(host => host.host), ['claude']);
|
|
236
237
|
assert.deepEqual(runner.calls.map(call => [call.executable, ...call.commandArguments]), [
|
|
@@ -249,7 +250,7 @@ test('an absent claude command skips usage-wrapup and names the plugin in the wa
|
|
|
249
250
|
status: null,
|
|
250
251
|
error: Object.assign(new Error('spawn claude ENOENT'), { code: 'ENOENT' }),
|
|
251
252
|
}));
|
|
252
|
-
const outcome =
|
|
253
|
+
const outcome = installMarketplacePlugins([USAGE_WRAPUP_PLUGIN_SPEC], ROOTS, runner);
|
|
253
254
|
assert.equal(outcome.status, 'skipped');
|
|
254
255
|
assert.match(outcome.warning, /usage-wrapup was not installed for claude/);
|
|
255
256
|
});
|
|
@@ -268,16 +269,60 @@ test('the usage-wrapup opt-out flag and variable turn off only usage-wrapup', ()
|
|
|
268
269
|
);
|
|
269
270
|
});
|
|
270
271
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
272
|
+
for (const [spec, packageFolder] of [
|
|
273
|
+
[USAGE_WRAPUP_PLUGIN_SPEC, './packages/usage-wrapup'],
|
|
274
|
+
[SUBAGENT_MODELS_PLUGIN_SPEC, './packages/subagent-models'],
|
|
275
|
+
]) {
|
|
276
|
+
test(`the repository marketplace lists ${spec.name} from its package folder in this repository`, () => {
|
|
277
|
+
const marketplace = JSON.parse(readFileSync(
|
|
278
|
+
new URL('../../../.claude-plugin/marketplace.json', import.meta.url),
|
|
279
|
+
'utf8',
|
|
280
|
+
));
|
|
281
|
+
const [pluginName, marketplaceName] = spec.pluginIdentifier.split('@');
|
|
282
|
+
assert.equal(marketplace.name, marketplaceName);
|
|
283
|
+
const entry = marketplace.plugins.find(plugin => plugin.name === pluginName);
|
|
284
|
+
assert.ok(entry, `the marketplace lists ${pluginName}`);
|
|
285
|
+
assert.equal(entry.source, packageFolder);
|
|
286
|
+
assert.ok(spec.sparsePaths.includes(packageFolder.slice(2)), `the sparse checkout holds ${packageFolder}`);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
test('should remove and add the shared marketplace once when installing usage-wrapup and subagent-models together', () => {
|
|
291
|
+
const runner = recordingRunner();
|
|
292
|
+
const outcome = installMarketplacePlugins([USAGE_WRAPUP_PLUGIN_SPEC, SUBAGENT_MODELS_PLUGIN_SPEC], ROOTS, runner);
|
|
293
|
+
assert.equal(outcome.status, 'installed');
|
|
294
|
+
assert.deepEqual(outcome.hosts.map(host => host.host), ['claude']);
|
|
295
|
+
assert.deepEqual(runner.calls.map(call => [call.executable, ...call.commandArguments]), [
|
|
296
|
+
['claude', 'plugin', 'uninstall', 'usage-wrapup@claude-dev-env'],
|
|
297
|
+
['claude', 'plugin', 'uninstall', 'subagent-models@claude-dev-env'],
|
|
298
|
+
['claude', 'plugin', 'marketplace', 'remove', 'claude-dev-env'],
|
|
299
|
+
[
|
|
300
|
+
'claude', 'plugin', 'marketplace', 'add', 'jl-cmd/claude-dev-env',
|
|
301
|
+
'--sparse', '.claude-plugin', 'packages/usage-wrapup', 'packages/subagent-models',
|
|
302
|
+
],
|
|
303
|
+
['claude', 'plugin', 'install', 'usage-wrapup@claude-dev-env'],
|
|
304
|
+
['claude', 'plugin', 'install', 'subagent-models@claude-dev-env'],
|
|
305
|
+
]);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('should refuse one plan for plugins from two marketplaces', () => {
|
|
309
|
+
assert.throws(
|
|
310
|
+
() => marketplacePluginsPlan([PSTACK_PLUGIN_SPEC, SUBAGENT_MODELS_PLUGIN_SPEC], 'claude'),
|
|
311
|
+
/subagent-models comes from jl-cmd\/claude-dev-env/,
|
|
312
|
+
);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
test('should turn off only subagent-models with its opt-out flag or variable', () => {
|
|
316
|
+
assert.equal(shouldInstallMarketplacePlugin(SUBAGENT_MODELS_PLUGIN_SPEC, [], {}), true);
|
|
317
|
+
assert.equal(shouldInstallMarketplacePlugin(SUBAGENT_MODELS_PLUGIN_SPEC, ['--no-subagent-models'], {}), false);
|
|
318
|
+
assert.equal(
|
|
319
|
+
shouldInstallMarketplacePlugin(SUBAGENT_MODELS_PLUGIN_SPEC, [], { CDE_INSTALL_SUBAGENT_MODELS: '0' }),
|
|
320
|
+
false,
|
|
321
|
+
);
|
|
322
|
+
assert.equal(
|
|
323
|
+
shouldInstallMarketplacePlugin(USAGE_WRAPUP_PLUGIN_SPEC, ['--no-subagent-models'], { CDE_INSTALL_SUBAGENT_MODELS: '0' }),
|
|
324
|
+
true,
|
|
325
|
+
);
|
|
281
326
|
});
|
|
282
327
|
|
|
283
328
|
test('a clean install deletes the orphaned pstack versions and keeps the installed one', () => {
|
|
@@ -286,8 +331,8 @@ test('a clean install deletes the orphaned pstack versions and keeps the install
|
|
|
286
331
|
mkdirSync(join(versionsDirectory, '0.9.55'), { recursive: true });
|
|
287
332
|
writeFileSync(join(versionsDirectory, '0.9.55', '.orphaned_at'), '1');
|
|
288
333
|
mkdirSync(join(versionsDirectory, '0.9.64'), { recursive: true });
|
|
289
|
-
const outcome =
|
|
290
|
-
PSTACK_PLUGIN_SPEC,
|
|
334
|
+
const outcome = installMarketplacePlugins(
|
|
335
|
+
[PSTACK_PLUGIN_SPEC],
|
|
291
336
|
{ claudeRoot, hosts: ['claude'], environment: {} },
|
|
292
337
|
recordingRunner(),
|
|
293
338
|
);
|
|
@@ -52,6 +52,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
|
|
|
52
52
|
...process.env,
|
|
53
53
|
CDE_INSTALL_PSTACK: '0',
|
|
54
54
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
55
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
55
56
|
HOME: homeDirectory,
|
|
56
57
|
USERPROFILE: homeDirectory,
|
|
57
58
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
@@ -58,6 +58,7 @@ function runInstaller(homeDirectory, extraArguments) {
|
|
|
58
58
|
...process.env,
|
|
59
59
|
CDE_INSTALL_PSTACK: '0',
|
|
60
60
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
61
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
61
62
|
HOME: homeDirectory,
|
|
62
63
|
USERPROFILE: homeDirectory,
|
|
63
64
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
@@ -87,6 +88,7 @@ function scanTextWithProductionPiiScanner(scannedText) {
|
|
|
87
88
|
...process.env,
|
|
88
89
|
CDE_INSTALL_PSTACK: '0',
|
|
89
90
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
91
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
90
92
|
PYTHONPATH: BLOCKING_HOOKS_DIRECTORY,
|
|
91
93
|
CLAUDE_LOCAL_IDENTITY_PATH: join(
|
|
92
94
|
temporaryDirectory,
|
|
@@ -33,6 +33,7 @@ function runInstaller(homeDirectory, extraArguments, environmentOverrides = {})
|
|
|
33
33
|
...process.env,
|
|
34
34
|
CDE_INSTALL_PSTACK: '0',
|
|
35
35
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
36
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
36
37
|
HOME: homeDirectory,
|
|
37
38
|
USERPROFILE: homeDirectory,
|
|
38
39
|
CODEX_HOME: join(homeDirectory, '.codex'),
|
package/bin/install.mjs
CHANGED
|
@@ -49,8 +49,9 @@ import {
|
|
|
49
49
|
} from './install-constants.mjs';
|
|
50
50
|
import {
|
|
51
51
|
PSTACK_PLUGIN_SPEC,
|
|
52
|
+
SUBAGENT_MODELS_PLUGIN_SPEC,
|
|
52
53
|
USAGE_WRAPUP_PLUGIN_SPEC,
|
|
53
|
-
|
|
54
|
+
installMarketplacePlugins,
|
|
54
55
|
shouldInstallMarketplacePlugin,
|
|
55
56
|
} from './install-pstack-plugin.mjs';
|
|
56
57
|
import { seedCodexPstackModels } from './seed-codex-pstack-models.mjs';
|
|
@@ -407,17 +408,19 @@ export function pythonFileAndPrefixArguments(pythonCommand) {
|
|
|
407
408
|
* @returns {{status: string, hosts: object[], warning: string|null}} The outcome.
|
|
408
409
|
*/
|
|
409
410
|
export function installPstackPluginForHosts() {
|
|
410
|
-
return
|
|
411
|
+
return installMarketplacePlugins([PSTACK_PLUGIN_SPEC], {
|
|
411
412
|
claudeRoot: CLAUDE_HOME,
|
|
412
413
|
codexHome: INSTALL_ROOT_RESOLUTION.codexHomeDirectory,
|
|
413
414
|
});
|
|
414
415
|
}
|
|
415
416
|
|
|
416
|
-
function printPluginHostOutcomes(
|
|
417
|
-
for (const
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
417
|
+
function printPluginHostOutcomes(allSpecs, pluginOutcome) {
|
|
418
|
+
for (const eachSpec of allSpecs) {
|
|
419
|
+
for (const eachHost of pluginOutcome.hosts) {
|
|
420
|
+
console.log(eachHost.warning
|
|
421
|
+
? ` ${eachSpec.label} (${eachHost.host}): ${eachHost.status} — ${eachHost.warning}`
|
|
422
|
+
: ` ${eachSpec.label} (${eachHost.host}): ${eachHost.status}`);
|
|
423
|
+
}
|
|
421
424
|
}
|
|
422
425
|
}
|
|
423
426
|
|
|
@@ -2890,14 +2893,16 @@ function executeInstallPlanMutations(plan, transactionHelpers) {
|
|
|
2890
2893
|
console.log(` \u2713 ${skillLoadGuidancePath} (Codex skill-load line)`);
|
|
2891
2894
|
}
|
|
2892
2895
|
}
|
|
2893
|
-
printPluginHostOutcomes(PSTACK_PLUGIN_SPEC, pstackPlugin);
|
|
2896
|
+
printPluginHostOutcomes([PSTACK_PLUGIN_SPEC], pstackPlugin);
|
|
2894
2897
|
}
|
|
2895
|
-
|
|
2896
|
-
|
|
2898
|
+
const allRepositoryPluginSpecs = [USAGE_WRAPUP_PLUGIN_SPEC, SUBAGENT_MODELS_PLUGIN_SPEC]
|
|
2899
|
+
.filter(eachSpec => shouldInstallMarketplacePlugin(eachSpec));
|
|
2900
|
+
if (!selectedGroups && allRepositoryPluginSpecs.length > 0) {
|
|
2901
|
+
const repositoryPlugins = installMarketplacePlugins(allRepositoryPluginSpecs, {
|
|
2897
2902
|
claudeRoot: CLAUDE_HOME,
|
|
2898
2903
|
});
|
|
2899
|
-
summary.
|
|
2900
|
-
printPluginHostOutcomes(
|
|
2904
|
+
summary.repositoryPlugins = repositoryPlugins;
|
|
2905
|
+
printPluginHostOutcomes(allRepositoryPluginSpecs, repositoryPlugins);
|
|
2901
2906
|
}
|
|
2902
2907
|
if (!selectedGroups) {
|
|
2903
2908
|
const packageGuidancePath = writeCodexPackageGuidance(
|
|
@@ -3412,6 +3417,7 @@ Usage:
|
|
|
3412
3417
|
npx ${PACKAGE_NAME} --profiles A,B Install into each selected profile (one ownership manifest per target)
|
|
3413
3418
|
npx ${PACKAGE_NAME} --no-pstack Full install without the pstack plugin (also CDE_INSTALL_PSTACK=0)
|
|
3414
3419
|
npx ${PACKAGE_NAME} --no-usage-wrapup Full install without the usage-wrapup plugin (also CDE_INSTALL_USAGE_WRAPUP=0)
|
|
3420
|
+
npx ${PACKAGE_NAME} --no-subagent-models Full install without the subagent-models plugin (also CDE_INSTALL_SUBAGENT_MODELS=0)
|
|
3415
3421
|
npx ${PACKAGE_NAME} --uninstall Remove installed files from the selected root
|
|
3416
3422
|
npx ${PACKAGE_NAME} --help Show this help
|
|
3417
3423
|
|
|
@@ -261,6 +261,7 @@ function resolveInstallerInvocation(homeDirectory) {
|
|
|
261
261
|
...process.env,
|
|
262
262
|
CDE_INSTALL_PSTACK: '0',
|
|
263
263
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
264
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
264
265
|
HOME: homeDirectory,
|
|
265
266
|
USERPROFILE: homeDirectory,
|
|
266
267
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
@@ -24,6 +24,7 @@ function installInScratchHome(homeDirectory, argumentsList = [], extraEnvironmen
|
|
|
24
24
|
...process.env,
|
|
25
25
|
CDE_INSTALL_PSTACK: '0',
|
|
26
26
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
27
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
27
28
|
HOME: homeDirectory,
|
|
28
29
|
USERPROFILE: homeDirectory,
|
|
29
30
|
CODEX_HOME: join(homeDirectory, '.codex'),
|
|
@@ -179,6 +180,7 @@ test('a successful pstack install retains its skill block and gains the question
|
|
|
179
180
|
const installation = installInScratchHome(homeDirectory, [], {
|
|
180
181
|
CDE_INSTALL_PSTACK: '1',
|
|
181
182
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
183
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
182
184
|
CDE_CODEX_EXECUTABLE: commandPath,
|
|
183
185
|
CDE_CLAUDE_EXECUTABLE: commandPath,
|
|
184
186
|
});
|
|
@@ -206,6 +208,7 @@ test('enabling pstack after a pstack-off install preserves question guidance', (
|
|
|
206
208
|
const secondInstallation = installInScratchHome(homeDirectory, [], {
|
|
207
209
|
CDE_INSTALL_PSTACK: '1',
|
|
208
210
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
211
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
209
212
|
CDE_CODEX_EXECUTABLE: commandPath,
|
|
210
213
|
CDE_CLAUDE_EXECUTABLE: commandPath,
|
|
211
214
|
});
|
|
@@ -234,6 +237,7 @@ test('a failed pstack transition restores question-only guidance and removes its
|
|
|
234
237
|
const failedInstallation = installInScratchHome(homeDirectory, [], {
|
|
235
238
|
CDE_INSTALL_PSTACK: '1',
|
|
236
239
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
240
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
237
241
|
CDE_CODEX_EXECUTABLE: commandPath,
|
|
238
242
|
CDE_CLAUDE_EXECUTABLE: commandPath,
|
|
239
243
|
CLAUDE_DEV_ENV_INSTALL_FAULT: 'after_file_staging',
|
|
@@ -58,6 +58,7 @@ function runInstallerInSandbox(sandboxHome, installerArguments = []) {
|
|
|
58
58
|
...process.env,
|
|
59
59
|
CDE_INSTALL_PSTACK: '0',
|
|
60
60
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
61
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
61
62
|
HOME: sandboxHome,
|
|
62
63
|
USERPROFILE: sandboxHome,
|
|
63
64
|
GIT_CONFIG_GLOBAL: gitConfigGlobal,
|
|
@@ -257,9 +258,16 @@ test('sandbox uninstall removes only package-owned permission entries and keeps
|
|
|
257
258
|
}
|
|
258
259
|
});
|
|
259
260
|
|
|
260
|
-
test('package settings.json publishes the advisor model
|
|
261
|
+
test('package settings.json publishes the advisor model and auto mode defaults', () => {
|
|
261
262
|
const packageSettings = JSON.parse(readFileSync(PACKAGE_SETTINGS_PATH, 'utf8'));
|
|
262
|
-
|
|
263
|
+
const settingsDefaults = settingsDefaultsFromPackageSettings(packageSettings);
|
|
264
|
+
assert.deepEqual(Object.keys(settingsDefaults).sort(), ['advisorModel', 'autoMode']);
|
|
265
|
+
assert.equal(settingsDefaults.advisorModel, 'fable');
|
|
266
|
+
const [firstAllowRule, ...allCustomAllowRules] = settingsDefaults.autoMode.allow;
|
|
267
|
+
assert.equal(firstAllowRule, '$defaults');
|
|
268
|
+
assert.equal(allCustomAllowRules.length, 2);
|
|
269
|
+
assert.match(allCustomAllowRules[0], /npx claude-dev-env@latest --update/);
|
|
270
|
+
assert.match(allCustomAllowRules[1], /claude-dev-env repository/);
|
|
263
271
|
});
|
|
264
272
|
|
|
265
273
|
test('a missing default is added and a user-set value is kept', () => {
|
package/bin/install.test.mjs
CHANGED
|
@@ -3026,6 +3026,7 @@ test('a failed update preserves an existing skills lookup pointer', () => {
|
|
|
3026
3026
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
3027
3027
|
CDE_INSTALL_PSTACK: '0',
|
|
3028
3028
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
3029
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
3029
3030
|
};
|
|
3030
3031
|
const argumentsForCoreInstall = [PSTACK_TEST_INSTALLER_PATH, '--only', 'core'];
|
|
3031
3032
|
const firstRun = spawnSync(process.execPath, argumentsForCoreInstall, {
|
|
@@ -3077,6 +3078,7 @@ function runPstackInstaller(homeDirectory, extraArguments, environmentOverrides
|
|
|
3077
3078
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
3078
3079
|
CDE_INSTALL_PSTACK: '1',
|
|
3079
3080
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
3081
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
3080
3082
|
...environmentOverrides,
|
|
3081
3083
|
},
|
|
3082
3084
|
});
|
|
@@ -3391,6 +3393,60 @@ test('the help output names the usage-wrapup opt-out beside the pstack one', ()
|
|
|
3391
3393
|
assert.match(helpRun.stdout, /--no-pstack .*\n.*--no-usage-wrapup .*CDE_INSTALL_USAGE_WRAPUP=0/);
|
|
3392
3394
|
});
|
|
3393
3395
|
|
|
3396
|
+
const REPOSITORY_PLUGIN_CLAUDE_COMMANDS = Object.freeze([
|
|
3397
|
+
'claude plugin uninstall usage-wrapup@claude-dev-env',
|
|
3398
|
+
'claude plugin uninstall subagent-models@claude-dev-env',
|
|
3399
|
+
'claude plugin marketplace remove claude-dev-env',
|
|
3400
|
+
'claude plugin marketplace add jl-cmd/claude-dev-env --sparse .claude-plugin packages/usage-wrapup packages/subagent-models',
|
|
3401
|
+
'claude plugin install usage-wrapup@claude-dev-env',
|
|
3402
|
+
'claude plugin install subagent-models@claude-dev-env',
|
|
3403
|
+
]);
|
|
3404
|
+
|
|
3405
|
+
test('should install subagent-models beside usage-wrapup with one marketplace add on a full install', t => {
|
|
3406
|
+
const sandbox = pstackPluginSandbox(t);
|
|
3407
|
+
|
|
3408
|
+
const installerOutput = runPstackInstaller(sandbox.homeDirectory, ['--no-pstack'], {
|
|
3409
|
+
...sandbox.environment,
|
|
3410
|
+
CDE_INSTALL_USAGE_WRAPUP: '1',
|
|
3411
|
+
CDE_INSTALL_SUBAGENT_MODELS: '1',
|
|
3412
|
+
});
|
|
3413
|
+
|
|
3414
|
+
assert.deepEqual(usageWrapupCommands(sandbox.recordedCommands()), REPOSITORY_PLUGIN_CLAUDE_COMMANDS);
|
|
3415
|
+
assert.match(installerOutput, /Usage-wrapup \(claude\): installed/);
|
|
3416
|
+
assert.match(installerOutput, /Subagent-models \(claude\): installed/);
|
|
3417
|
+
});
|
|
3418
|
+
|
|
3419
|
+
for (const [caseName, extraArguments, environmentOverrides] of [
|
|
3420
|
+
['--no-subagent-models', ['--no-subagent-models'], { CDE_INSTALL_SUBAGENT_MODELS: '1' }],
|
|
3421
|
+
['CDE_INSTALL_SUBAGENT_MODELS=0', [], { CDE_INSTALL_SUBAGENT_MODELS: '0' }],
|
|
3422
|
+
['an --only run', ['--only', 'core'], { CDE_INSTALL_SUBAGENT_MODELS: '1' }],
|
|
3423
|
+
]) {
|
|
3424
|
+
test(`should skip subagent-models on ${caseName}`, t => {
|
|
3425
|
+
const sandbox = pstackPluginSandbox(t);
|
|
3426
|
+
|
|
3427
|
+
const installerOutput = runPstackInstaller(sandbox.homeDirectory, extraArguments, {
|
|
3428
|
+
...sandbox.environment,
|
|
3429
|
+
...environmentOverrides,
|
|
3430
|
+
});
|
|
3431
|
+
|
|
3432
|
+
assert.equal(
|
|
3433
|
+
sandbox.recordedCommands().some(eachCommand => eachCommand.includes('subagent-models')),
|
|
3434
|
+
false,
|
|
3435
|
+
);
|
|
3436
|
+
assert.doesNotMatch(installerOutput, /Subagent-models \(/);
|
|
3437
|
+
});
|
|
3438
|
+
}
|
|
3439
|
+
|
|
3440
|
+
test('should name the subagent-models opt-out in the help output', () => {
|
|
3441
|
+
const helpRun = spawnSync(process.execPath, [PSTACK_TEST_INSTALLER_PATH, '--help'], {
|
|
3442
|
+
encoding: 'utf8',
|
|
3443
|
+
env: process.env,
|
|
3444
|
+
});
|
|
3445
|
+
|
|
3446
|
+
assert.equal(helpRun.status, 0, helpRun.stderr);
|
|
3447
|
+
assert.match(helpRun.stdout, /--no-subagent-models .*CDE_INSTALL_SUBAGENT_MODELS=0/);
|
|
3448
|
+
});
|
|
3449
|
+
|
|
3394
3450
|
function continuityCommandCount(configurationPath) {
|
|
3395
3451
|
if (!existsSync(configurationPath)) return 0;
|
|
3396
3452
|
const configuration = JSON.parse(readFileSync(configurationPath, 'utf8'));
|
|
@@ -87,6 +87,7 @@ test('capture and restore recover settings, manifest, files, and hooksPath', ()
|
|
|
87
87
|
...process.env,
|
|
88
88
|
CDE_INSTALL_PSTACK: '0',
|
|
89
89
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
90
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
90
91
|
HOME: box.root,
|
|
91
92
|
USERPROFILE: box.root,
|
|
92
93
|
GIT_CONFIG_GLOBAL: gitConfigPath,
|
|
@@ -149,6 +150,7 @@ test('capture and restore recover an additional host settings file', () => {
|
|
|
149
150
|
...process.env,
|
|
150
151
|
CDE_INSTALL_PSTACK: '0',
|
|
151
152
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
153
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
152
154
|
HOME: box.root,
|
|
153
155
|
USERPROFILE: box.root,
|
|
154
156
|
GIT_CONFIG_GLOBAL: gitConfigPath,
|
|
@@ -184,6 +186,7 @@ test('runWithInstallTransaction restores prior state on injected fault', () => {
|
|
|
184
186
|
...process.env,
|
|
185
187
|
CDE_INSTALL_PSTACK: '0',
|
|
186
188
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
189
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
187
190
|
HOME: box.root,
|
|
188
191
|
USERPROFILE: box.root,
|
|
189
192
|
GIT_CONFIG_GLOBAL: gitConfigPath,
|
|
@@ -238,6 +241,7 @@ test('runWithInstallTransaction commits and discards journal on success', () =>
|
|
|
238
241
|
...process.env,
|
|
239
242
|
CDE_INSTALL_PSTACK: '0',
|
|
240
243
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
244
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
241
245
|
HOME: box.root,
|
|
242
246
|
USERPROFILE: box.root,
|
|
243
247
|
GIT_CONFIG_GLOBAL: gitConfigPath,
|
|
@@ -281,6 +285,7 @@ function runInstaller(homeDirectory, extraArguments, options = {}) {
|
|
|
281
285
|
...process.env,
|
|
282
286
|
CDE_INSTALL_PSTACK: '0',
|
|
283
287
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
288
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
284
289
|
HOME: homeDirectory,
|
|
285
290
|
USERPROFILE: homeDirectory,
|
|
286
291
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
@@ -316,6 +321,7 @@ test('a later install fault removes newly seeded Codex pstack files', () => {
|
|
|
316
321
|
environment: {
|
|
317
322
|
CDE_INSTALL_PSTACK: '1',
|
|
318
323
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
324
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
319
325
|
CDE_CODEX_EXECUTABLE: codexCommandPath,
|
|
320
326
|
},
|
|
321
327
|
});
|
|
@@ -459,6 +465,7 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
|
|
|
459
465
|
...process.env,
|
|
460
466
|
CDE_INSTALL_PSTACK: '0',
|
|
461
467
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
468
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
462
469
|
HOME: homeDirectory,
|
|
463
470
|
USERPROFILE: homeDirectory,
|
|
464
471
|
GIT_CONFIG_GLOBAL: gitConfigPath,
|
|
@@ -476,6 +483,7 @@ test('installer fault after_git_config restores prior core.hooksPath', () => {
|
|
|
476
483
|
...process.env,
|
|
477
484
|
CDE_INSTALL_PSTACK: '0',
|
|
478
485
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
486
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
479
487
|
HOME: homeDirectory,
|
|
480
488
|
USERPROFILE: homeDirectory,
|
|
481
489
|
GIT_CONFIG_GLOBAL: gitConfigPath,
|
|
@@ -73,6 +73,7 @@ function runInstaller(homeDirectory, extraArguments, options = {}) {
|
|
|
73
73
|
...process.env,
|
|
74
74
|
CDE_INSTALL_PSTACK: '0',
|
|
75
75
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
76
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
76
77
|
HOME: homeDirectory,
|
|
77
78
|
USERPROFILE: homeDirectory,
|
|
78
79
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
|
@@ -307,6 +308,7 @@ test('uninstall fault after_git_config restores prior core.hooksPath', () => {
|
|
|
307
308
|
...process.env,
|
|
308
309
|
CDE_INSTALL_PSTACK: '0',
|
|
309
310
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
311
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
310
312
|
HOME: homeDirectory,
|
|
311
313
|
USERPROFILE: homeDirectory,
|
|
312
314
|
GIT_CONFIG_GLOBAL: gitConfigPath,
|
|
@@ -24,6 +24,7 @@ function installerEnvironment(homeDirectory) {
|
|
|
24
24
|
...process.env,
|
|
25
25
|
CDE_INSTALL_PSTACK: '0',
|
|
26
26
|
CDE_INSTALL_USAGE_WRAPUP: '0',
|
|
27
|
+
CDE_INSTALL_SUBAGENT_MODELS: '0',
|
|
27
28
|
HOME: homeDirectory,
|
|
28
29
|
USERPROFILE: homeDirectory,
|
|
29
30
|
GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
|
package/package.json
CHANGED
package/settings.json
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"WebFetch(domain:docs.github.com)"
|
|
5
|
+
]
|
|
6
|
+
},
|
|
7
|
+
"advisorModel": "fable",
|
|
8
|
+
"autoMode": {
|
|
9
|
+
"allow": [
|
|
10
|
+
"$defaults",
|
|
11
|
+
"Updating or installing the claude-dev-env package is allowed: running `npx claude-dev-env@latest --update` or `npx --yes claude-dev-env@latest --update --target <claude config dir>` is the user's own daily environment sync from the user's own public package, and its writes to the Claude config directory are the intended result. This covers only that package and command.",
|
|
12
|
+
"Developing the claude-dev-env repository is allowed: editing, testing, committing and pushing its settings, hooks, scripts and tests is the user's requested work on the user's own public package. It changes the package source in the repository; it does not change the running session's own configuration."
|
|
13
|
+
]
|
|
14
|
+
}
|
|
8
15
|
}
|