claude-dev-env 8.51.4 → 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.
@@ -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
- marketplaceAddArguments: Object.freeze([]),
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
- marketplaceAddArguments: Object.freeze(['--sparse', '.claude-plugin', 'packages/usage-wrapup']),
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 plugin with.
70
+ * Read the marketplace and plugin commands one host installs a set of plugins with.
59
71
  *
60
- * The removal commands run first and take out the installed plugin, its
61
- * cached versions, and the marketplace clone, so the install commands that
62
- * follow fetch the newest published version into an empty slot.
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
- * passes `--sparse .claude-plugin` among its marketplace add arguments, so the
68
- * clone holds only the catalog and stays clear of Windows path-length limits.
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} spec The plugin to install.
71
- * @param {string} host A host the spec names, `claude` or `codex`.
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 marketplacePluginPlan(spec, host) {
92
+ export function marketplacePluginsPlan(allSpecs, host) {
77
93
  const hostDefinition = HOST_DEFINITIONS[host];
78
- if (!hostDefinition || !spec.hosts.includes(host)) {
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
- `Unsupported ${spec.name} plugin host ${host}: this installer knows ${spec.hosts.join(', ')}.`,
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, spec.pluginIdentifier],
89
- ['plugin', 'marketplace', 'remove', spec.marketplaceName],
118
+ ...allSpecs.map(eachSpec => ['plugin', hostDefinition.uninstallVerb, eachSpec.pluginIdentifier]),
119
+ ['plugin', 'marketplace', 'remove', firstSpec.marketplaceName],
90
120
  ],
91
121
  commands: [
92
- ['plugin', 'marketplace', 'add', spec.marketplaceRepository, ...spec.marketplaceAddArguments],
93
- ['plugin', hostDefinition.installVerb, spec.pluginIdentifier],
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(spec, host, executable) {
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 ${spec.name} was not installed for ${host}.`,
237
+ warning: `${executable} is not on PATH, so ${pluginNames} was not installed for ${host}.`,
207
238
  };
208
239
  }
209
240
 
210
- function installForHost(spec, host, homeDirectory, environment, runCommand) {
211
- const plan = marketplacePluginPlan(spec, host);
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(spec, host, executable);
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(spec, host, executable);
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
- removeOrphanedPluginVersions(hostDefinition.orphanedVersionsDirectory(homeDirectory, spec));
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 one plugin from its marketplace into each host's own home.
276
+ * Install a set of plugins from one marketplace into each host's own home.
244
277
  *
245
- * Each host first removes the plugin and its marketplace, so an older
246
- * version never survives beside the new one. A removal that finds nothing
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} spec The plugin to install.
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's hosts.
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 installMarketplacePlugin(spec, options = {}, dependencies = {}) {
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 ?? spec.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
- spec,
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
- installMarketplacePlugin,
14
- marketplacePluginPlan,
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 = marketplacePluginPlan(PSTACK_PLUGIN_SPEC, 'claude');
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 = marketplacePluginPlan(PSTACK_PLUGIN_SPEC, 'codex');
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(() => marketplacePluginPlan(PSTACK_PLUGIN_SPEC, 'cursor'), /cursor/);
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 = installMarketplacePlugin(PSTACK_PLUGIN_SPEC, ROOTS, runner);
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(marketplacePluginPlan(PSTACK_PLUGIN_SPEC, 'claude').removalCommands, [
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(marketplacePluginPlan(PSTACK_PLUGIN_SPEC, 'codex').removalCommands, [
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 = installMarketplacePlugin(PSTACK_PLUGIN_SPEC, { ...ROOTS, hosts: ['claude'] }, runner);
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 = installMarketplacePlugin(PSTACK_PLUGIN_SPEC, ROOTS, runner);
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 = installMarketplacePlugin(PSTACK_PLUGIN_SPEC, ROOTS, runner);
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
- installMarketplacePlugin(
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 = installMarketplacePlugin(PSTACK_PLUGIN_SPEC, { ...ROOTS, hosts: ['codex'] }, runner);
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 = installMarketplacePlugin(PSTACK_PLUGIN_SPEC, ROOTS, runner);
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 = installMarketplacePlugin(PSTACK_PLUGIN_SPEC, { ...ROOTS, hosts: ['claude'] }, runner);
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 = marketplacePluginPlan(USAGE_WRAPUP_PLUGIN_SPEC, 'claude');
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(() => marketplacePluginPlan(USAGE_WRAPUP_PLUGIN_SPEC, 'codex'), /usage-wrapup plugin host codex/);
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 = installMarketplacePlugin(USAGE_WRAPUP_PLUGIN_SPEC, ROOTS, runner);
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 = installMarketplacePlugin(USAGE_WRAPUP_PLUGIN_SPEC, ROOTS, runner);
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
- test('the repository marketplace lists usage-wrapup from its package folder in this repository', () => {
272
- const marketplace = JSON.parse(readFileSync(
273
- new URL('../../../.claude-plugin/marketplace.json', import.meta.url),
274
- 'utf8',
275
- ));
276
- const [pluginName, marketplaceName] = USAGE_WRAPUP_PLUGIN_SPEC.pluginIdentifier.split('@');
277
- assert.equal(marketplace.name, marketplaceName);
278
- const entry = marketplace.plugins.find(plugin => plugin.name === pluginName);
279
- assert.ok(entry, `the marketplace lists ${pluginName}`);
280
- assert.equal(entry.source, './packages/usage-wrapup');
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 = installMarketplacePlugin(
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
- installMarketplacePlugin,
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 installMarketplacePlugin(PSTACK_PLUGIN_SPEC, {
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(spec, pluginOutcome) {
417
- for (const eachHost of pluginOutcome.hosts) {
418
- console.log(eachHost.warning
419
- ? ` ${spec.label} (${eachHost.host}): ${eachHost.status} — ${eachHost.warning}`
420
- : ` ${spec.label} (${eachHost.host}): ${eachHost.status}`);
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
- if (!selectedGroups && shouldInstallMarketplacePlugin(USAGE_WRAPUP_PLUGIN_SPEC)) {
2896
- const usageWrapupPlugin = installMarketplacePlugin(USAGE_WRAPUP_PLUGIN_SPEC, {
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.usageWrapupPlugin = usageWrapupPlugin;
2900
- printPluginHostOutcomes(USAGE_WRAPUP_PLUGIN_SPEC, usageWrapupPlugin);
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,
@@ -31,6 +31,7 @@ function installEnvironment(homeDirectory) {
31
31
  GIT_CONFIG_GLOBAL: join(homeDirectory, '.gitconfig'),
32
32
  CDE_INSTALL_PSTACK: '0',
33
33
  CDE_INSTALL_USAGE_WRAPUP: '0',
34
+ CDE_INSTALL_SUBAGENT_MODELS: '0',
34
35
  };
35
36
  }
36
37
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "8.51.4",
3
+ "version": "8.52.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {