@open-agent-toolkit/cli 0.0.35 → 0.0.36

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.
@@ -73,10 +73,14 @@ Key behavior:
73
73
 
74
74
  - Same pack selection and install flow as `oat init tools`
75
75
  - Pack-oriented install subcommands: `core`, `docs`, `ideas`, `workflows`, `utility`, `project-management`, `research`
76
+ - Interactive installs show each pack's current install location in the picker so already-installed packs are visible before you submit
77
+ - User-scope follow-up choices are prepopulated from the current install state for user-eligible packs (`ideas`, `docs`, `utility`, `research`)
78
+ - If a user-eligible pack is already installed in both project and user scope, the installer asks whether to keep both installs or normalize the pack to user scope before it makes any cleanup changes
79
+ - Changing a user-eligible pack from project scope to user scope, or back again, is treated as a migration: the old canonical copy is removed so the pack ends in the selected scope instead of accumulating duplicate installs
76
80
  - Tracks installed vs bundled skill versions and reports outdated skills
77
81
  - Records installed pack state in shared repo config as `tools.<pack>: true` so other OAT workflows can detect installed capabilities without relying on filesystem heuristics
78
82
  - Interactive runs can prompt to update selected outdated skills
79
- - Auto-sync runs automatically after successful install (provider views are updated)
83
+ - Successful installs report the final scope chosen for each pack, including `project + user` when both installs are preserved, and auto-sync every scope touched by the install or migration
80
84
  - Install-triggered auto-sync limits removal planning to the canonical entries from the pack that was just installed, so stale manifest drift in unrelated packs does not delete other provider views
81
85
  - Use `--no-sync` to skip auto-sync
82
86
 
@@ -1,6 +1,6 @@
1
1
  {
2
- "cli": "0.0.35",
3
- "docs-config": "0.0.35",
4
- "docs-theme": "0.0.35",
5
- "docs-transforms": "0.0.35"
2
+ "cli": "0.0.36",
3
+ "docs-config": "0.0.36",
4
+ "docs-theme": "0.0.36",
5
+ "docs-transforms": "0.0.36"
6
6
  }
@@ -1,7 +1,10 @@
1
1
  import { type CommandContext, type GlobalOptions } from '../../../app/command-context.js';
2
2
  import { type UpsertSectionResult } from '../../shared/agents-md.js';
3
3
  import { type MultiSelectChoice, type PromptContext, type SelectChoice } from '../../shared/shared.prompts.js';
4
+ import type { ScanToolsOptions } from '../../tools/shared/scan-tools.js';
5
+ import type { ToolInfo } from '../../tools/shared/types.js';
4
6
  import { type OatConfig } from '../../../config/oat-config.js';
7
+ import type { ConcreteScope } from '../../../shared/types.js';
5
8
  import { Command } from 'commander';
6
9
  import { type InstallCoreOptions, type InstallCoreResult } from './core/install-core.js';
7
10
  import { type InstallDocsOptions, type InstallDocsResult } from './docs/install-docs.js';
@@ -11,12 +14,14 @@ import { type InstallResearchOptions, type InstallResearchResult } from './resea
11
14
  import { type InstallUtilityOptions, type InstallUtilityResult } from './utility/install-utility.js';
12
15
  import { type InstallWorkflowsOptions, type InstallWorkflowsResult } from './workflows/install-workflows.js';
13
16
  type InstallScope = 'project' | 'user';
17
+ type PackInstallTarget = InstallScope | 'both';
14
18
  export type ToolPack = 'core' | 'ideas' | 'docs' | 'workflows' | 'utility' | 'project-management' | 'research';
15
- interface InitToolsDependencies {
19
+ export interface InitToolsDependencies {
16
20
  buildCommandContext: (options: GlobalOptions) => CommandContext;
17
21
  resolveProjectRoot: (cwd: string) => Promise<string>;
18
22
  resolveScopeRoot: (scope: InstallScope, cwd: string, home: string) => string;
19
23
  resolveAssetsRoot: () => Promise<string>;
24
+ scanTools: (options: ScanToolsOptions) => Promise<ToolInfo[]>;
20
25
  selectManyWithAbort: <T extends string>(message: string, choices: MultiSelectChoice<T>[], ctx: PromptContext) => Promise<T[] | null>;
21
26
  selectWithAbort: <T extends string>(message: string, choices: SelectChoice<T>[], ctx: PromptContext) => Promise<T | null>;
22
27
  installCore: (options: InstallCoreOptions) => Promise<InstallCoreResult>;
@@ -27,6 +32,8 @@ interface InitToolsDependencies {
27
32
  installProjectManagement: (options: InstallProjectManagementOptions) => Promise<InstallProjectManagementResult>;
28
33
  installResearch: (options: InstallResearchOptions) => Promise<InstallResearchResult>;
29
34
  copyDirWithStatus: (source: string, destination: string, force: boolean) => Promise<'copied' | 'updated' | 'skipped'>;
35
+ removeDirectory: (target: string) => Promise<void>;
36
+ removeFile: (target: string) => Promise<void>;
30
37
  addLocalPaths: (repoRoot: string, paths: string[]) => Promise<{
31
38
  added: string[];
32
39
  all: string[];
@@ -40,9 +47,13 @@ interface InitToolsDependencies {
40
47
  upsertAgentsMdSection: (repoRoot: string, key: string, body: string) => Promise<UpsertSectionResult>;
41
48
  removeAgentsMdSection: (repoRoot: string, key: string) => Promise<boolean>;
42
49
  }
50
+ interface InitToolsRunMetadata {
51
+ affectedScopes: ConcreteScope[];
52
+ }
53
+ export declare function consumeInitToolsRunMetadata(): InitToolsRunMetadata | null;
43
54
  interface PackScopeInfo {
44
55
  pack: ToolPack;
45
- scope: InstallScope;
56
+ scope: PackInstallTarget;
46
57
  }
47
58
  export declare function buildToolPacksSectionBody(packs: PackScopeInfo[]): string;
48
59
  export declare function runInitTools(context: CommandContext, dependencies: InitToolsDependencies): Promise<ToolPack[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/init/tools/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/init/tools/index.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { rm, unlink } from 'node:fs/promises';
1
2
  import { join } from 'node:path';
2
3
  import { buildCommandContext, } from '../../../app/command-context.js';
3
4
  import { copyDirWithStatus } from '../../init/tools/shared/copy-helpers.js';
@@ -7,6 +8,7 @@ import { removeAgentsMdSection, upsertAgentsMdSection, } from '../../shared/agen
7
8
  import { selectManyWithAbort, selectWithAbort, } from '../../shared/shared.prompts.js';
8
9
  import { readGlobalOptions } from '../../shared/shared.utils.js';
9
10
  import { canonicalPathsForPacks, setInstalledCanonicalPaths, } from '../../tools/shared/install-sync-context.js';
11
+ import { scanTools } from '../../tools/shared/scan-tools.js';
10
12
  import { readOatConfig, resolveLocalPaths, writeOatConfig, } from '../../../config/oat-config.js';
11
13
  import { resolveAssetsRoot } from '../../../fs/assets.js';
12
14
  import { resolveProjectRoot, resolveScopeRoot } from '../../../fs/paths.js';
@@ -14,38 +16,56 @@ import { Command } from 'commander';
14
16
  import { createInitToolsCoreCommand } from './core/index.js';
15
17
  import { installCore as defaultInstallCore, } from './core/install-core.js';
16
18
  import { createInitToolsDocsCommand } from './docs/index.js';
17
- import { DOCS_SKILLS, installDocs as defaultInstallDocs, } from './docs/install-docs.js';
19
+ import { installDocs as defaultInstallDocs, } from './docs/install-docs.js';
18
20
  import { createInitToolsIdeasCommand } from './ideas/index.js';
19
21
  import { installIdeas as defaultInstallIdeas, } from './ideas/install-ideas.js';
22
+ import { buildPackInstallStateMap, } from './install-state.js';
20
23
  import { createInitToolsProjectManagementCommand } from './project-management/index.js';
21
24
  import { installProjectManagement as defaultInstallProjectManagement, } from './project-management/install-project-management.js';
22
25
  import { createInitToolsResearchCommand } from './research/index.js';
23
- import { installResearch as defaultInstallResearch, RESEARCH_SKILLS, } from './research/install-research.js';
26
+ import { installResearch as defaultInstallResearch, } from './research/install-research.js';
27
+ import { DOCS_SKILLS, IDEA_SKILLS, RESEARCH_AGENTS, RESEARCH_SKILLS, UTILITY_SKILLS, } from './shared/skill-manifest.js';
24
28
  import { createInitToolsUtilityCommand } from './utility/index.js';
25
- import { installUtility as defaultInstallUtility, UTILITY_SKILLS, } from './utility/install-utility.js';
29
+ import { installUtility as defaultInstallUtility, } from './utility/install-utility.js';
26
30
  import { createInitToolsWorkflowsCommand } from './workflows/index.js';
27
31
  import { installWorkflows as defaultInstallWorkflows, } from './workflows/install-workflows.js';
28
32
  function formatVersionForDisplay(version) {
29
33
  return version ?? '(unversioned)';
30
34
  }
31
- const PACK_CHOICES = [
32
- { label: 'Core [user]', value: 'core', checked: true },
33
- { label: 'Ideas [project|user]', value: 'ideas', checked: true },
34
- { label: 'Docs [project|user]', value: 'docs', checked: true },
35
- {
36
- label: 'Project Management [project]',
37
- value: 'project-management',
38
- checked: false,
39
- },
40
- { label: 'Workflows [project]', value: 'workflows', checked: true },
41
- { label: 'Utility [project|user]', value: 'utility', checked: true },
42
- { label: 'Research [project|user]', value: 'research', checked: true },
35
+ const ALL_TOOL_PACKS = [
36
+ 'core',
37
+ 'ideas',
38
+ 'docs',
39
+ 'workflows',
40
+ 'utility',
41
+ 'project-management',
42
+ 'research',
43
43
  ];
44
+ const USER_ELIGIBLE_PACK_MEMBERS = {
45
+ ideas: {
46
+ skills: IDEA_SKILLS,
47
+ agents: [],
48
+ },
49
+ docs: {
50
+ skills: DOCS_SKILLS,
51
+ agents: [],
52
+ },
53
+ utility: {
54
+ skills: UTILITY_SKILLS,
55
+ agents: [],
56
+ },
57
+ research: {
58
+ skills: RESEARCH_SKILLS,
59
+ agents: RESEARCH_AGENTS,
60
+ },
61
+ };
62
+ let lastRunInitToolsMetadata = null;
44
63
  const DEFAULT_DEPENDENCIES = {
45
64
  buildCommandContext,
46
65
  resolveProjectRoot,
47
66
  resolveScopeRoot,
48
67
  resolveAssetsRoot,
68
+ scanTools,
49
69
  selectManyWithAbort,
50
70
  selectWithAbort,
51
71
  installCore: defaultInstallCore,
@@ -56,6 +76,21 @@ const DEFAULT_DEPENDENCIES = {
56
76
  installProjectManagement: defaultInstallProjectManagement,
57
77
  installResearch: defaultInstallResearch,
58
78
  copyDirWithStatus,
79
+ removeDirectory: async (target) => {
80
+ await rm(target, { recursive: true, force: true });
81
+ },
82
+ removeFile: async (target) => {
83
+ try {
84
+ await unlink(target);
85
+ }
86
+ catch (error) {
87
+ if (!(error instanceof Error) ||
88
+ !('code' in error) ||
89
+ error.code !== 'ENOENT') {
90
+ throw error;
91
+ }
92
+ }
93
+ },
59
94
  addLocalPaths,
60
95
  applyGitignore,
61
96
  readOatConfig,
@@ -70,7 +105,120 @@ const USER_ELIGIBLE_PACKS = new Set([
70
105
  'utility',
71
106
  'research',
72
107
  ]);
73
- async function resolvePackScopes(context, selections, dependencies) {
108
+ function isUserEligiblePack(pack) {
109
+ return USER_ELIGIBLE_PACKS.has(pack);
110
+ }
111
+ async function loadInstalledPackStates(projectRoot, userRoot, assetsRoot, dependencies) {
112
+ const [projectTools, userTools] = await Promise.all([
113
+ dependencies.scanTools({
114
+ scope: 'project',
115
+ scopeRoot: projectRoot,
116
+ assetsRoot,
117
+ }),
118
+ dependencies.scanTools({
119
+ scope: 'user',
120
+ scopeRoot: userRoot,
121
+ assetsRoot,
122
+ }),
123
+ ]);
124
+ return buildPackInstallStateMap(ALL_TOOL_PACKS, [
125
+ ...projectTools,
126
+ ...userTools,
127
+ ]);
128
+ }
129
+ function formatInstalledLocation(location) {
130
+ switch (location) {
131
+ case 'project':
132
+ return 'project';
133
+ case 'user':
134
+ return 'user';
135
+ case 'both':
136
+ return 'project + user';
137
+ default:
138
+ return 'not installed';
139
+ }
140
+ }
141
+ function buildPackChoices(installedPackStates) {
142
+ return [
143
+ {
144
+ label: `Core [user]${installedPackStates.core.location === 'not-installed' ? '' : ` (installed: ${formatInstalledLocation(installedPackStates.core.location)})`}`,
145
+ value: 'core',
146
+ checked: true,
147
+ },
148
+ {
149
+ label: `Ideas [project|user]${installedPackStates.ideas.location === 'not-installed' ? '' : ` (installed: ${formatInstalledLocation(installedPackStates.ideas.location)})`}`,
150
+ value: 'ideas',
151
+ checked: true,
152
+ },
153
+ {
154
+ label: `Docs [project|user]${installedPackStates.docs.location === 'not-installed' ? '' : ` (installed: ${formatInstalledLocation(installedPackStates.docs.location)})`}`,
155
+ value: 'docs',
156
+ checked: true,
157
+ },
158
+ {
159
+ label: `Project Management [project]${installedPackStates['project-management'].location === 'not-installed' ? '' : ` (installed: ${formatInstalledLocation(installedPackStates['project-management'].location)})`}`,
160
+ value: 'project-management',
161
+ checked: false,
162
+ },
163
+ {
164
+ label: `Workflows [project]${installedPackStates.workflows.location === 'not-installed' ? '' : ` (installed: ${formatInstalledLocation(installedPackStates.workflows.location)})`}`,
165
+ value: 'workflows',
166
+ checked: true,
167
+ },
168
+ {
169
+ label: `Utility [project|user]${installedPackStates.utility.location === 'not-installed' ? '' : ` (installed: ${formatInstalledLocation(installedPackStates.utility.location)})`}`,
170
+ value: 'utility',
171
+ checked: true,
172
+ },
173
+ {
174
+ label: `Research [project|user]${installedPackStates.research.location === 'not-installed' ? '' : ` (installed: ${formatInstalledLocation(installedPackStates.research.location)})`}`,
175
+ value: 'research',
176
+ checked: true,
177
+ },
178
+ ];
179
+ }
180
+ function buildUserScopeChoices(packs, installedPackStates) {
181
+ return packs.map((pack) => {
182
+ const location = installedPackStates[pack].location;
183
+ return {
184
+ label: location === 'not-installed'
185
+ ? pack
186
+ : `${pack} (current: ${formatInstalledLocation(location)})`,
187
+ value: pack,
188
+ checked: location === 'user' || location === 'both',
189
+ };
190
+ });
191
+ }
192
+ async function resolveBothScopeTarget(pack, dependencies, interactive) {
193
+ const selection = await dependencies.selectWithAbort(`${pack} is currently installed in project and user scope. Keep both installs or normalize to user scope?`, [
194
+ {
195
+ label: 'Keep project + user (recommended)',
196
+ value: 'both',
197
+ description: 'Preserve both installed copies',
198
+ },
199
+ {
200
+ label: 'User only',
201
+ value: 'user',
202
+ description: 'Remove the project-scoped copy',
203
+ },
204
+ ], { interactive });
205
+ return selection ?? 'both';
206
+ }
207
+ export function consumeInitToolsRunMetadata() {
208
+ const metadata = lastRunInitToolsMetadata;
209
+ lastRunInitToolsMetadata = null;
210
+ return metadata;
211
+ }
212
+ async function removePackFromScope(pack, root, dependencies) {
213
+ const members = USER_ELIGIBLE_PACK_MEMBERS[pack];
214
+ for (const skill of members.skills) {
215
+ await dependencies.removeDirectory(join(root, '.agents', 'skills', skill));
216
+ }
217
+ for (const agent of members.agents) {
218
+ await dependencies.removeFile(join(root, '.agents', 'agents', agent));
219
+ }
220
+ }
221
+ async function resolvePackScopes(context, selections, installedPackStates, dependencies) {
74
222
  const scopes = {};
75
223
  // Workflows is always project-only
76
224
  for (const pack of selections) {
@@ -82,7 +230,7 @@ async function resolvePackScopes(context, selections, dependencies) {
82
230
  if (selections.includes('core')) {
83
231
  scopes.core = 'user';
84
232
  }
85
- const eligiblePacks = selections.filter((pack) => USER_ELIGIBLE_PACKS.has(pack));
233
+ const eligiblePacks = selections.filter((pack) => isUserEligiblePack(pack));
86
234
  if (eligiblePacks.length === 0) {
87
235
  return scopes;
88
236
  }
@@ -107,31 +255,45 @@ async function resolvePackScopes(context, selections, dependencies) {
107
255
  return scopes;
108
256
  }
109
257
  // Interactive: let user pick which packs go to user scope
110
- const userScopePacks = (await dependencies.selectManyWithAbort('Which packs should install at user scope? (unselected go to project scope)', eligiblePacks.map((pack) => ({
111
- label: pack,
112
- value: pack,
113
- checked: false,
114
- })), { interactive: context.interactive })) ?? [];
258
+ const userScopePacks = (await dependencies.selectManyWithAbort('Which packs should install at user scope? (unselected go to project scope)', buildUserScopeChoices(eligiblePacks, installedPackStates), { interactive: context.interactive })) ?? [];
115
259
  const userScopeSet = new Set(userScopePacks);
116
260
  for (const pack of eligiblePacks) {
117
- scopes[pack] = userScopeSet.has(pack) ? 'user' : 'project';
261
+ if (!userScopeSet.has(pack)) {
262
+ scopes[pack] = 'project';
263
+ continue;
264
+ }
265
+ scopes[pack] =
266
+ installedPackStates[pack].location === 'both'
267
+ ? await resolveBothScopeTarget(pack, dependencies, context.interactive)
268
+ : 'user';
118
269
  }
119
270
  return scopes;
120
271
  }
121
- function reportSuccess(context, selectedPacks, utilityScope) {
272
+ function buildInstalledToolsConfig(selectedPacks, installedPackStates, existingTools) {
273
+ const selectedPackSet = new Set(selectedPacks);
274
+ const tools = { ...existingTools };
275
+ for (const pack of ALL_TOOL_PACKS) {
276
+ tools[pack] =
277
+ selectedPackSet.has(pack) ||
278
+ installedPackStates[pack].location !== 'not-installed';
279
+ }
280
+ return tools;
281
+ }
282
+ function reportSuccess(context, packs, syncScopes) {
122
283
  if (context.json) {
123
284
  context.logger.json({
124
285
  status: 'ok',
125
- selectedPacks,
126
- utilityScope,
286
+ installedPacks: packs,
287
+ syncScopes,
127
288
  });
128
289
  return;
129
290
  }
130
- context.logger.info(`Installed tool packs: ${selectedPacks.join(', ')}`);
131
- context.logger.info(`User-eligible pack scope: ${utilityScope}`);
132
- context.logger.info('Run: oat sync --scope project');
133
- if (utilityScope === 'user') {
134
- context.logger.info('Also run: oat sync --scope user');
291
+ context.logger.info(`Installed tool packs: ${packs.map(({ pack, scope }) => `${pack} (${formatInstalledLocation(scope)})`).join(', ')}`);
292
+ syncScopes.forEach((scope, index) => {
293
+ context.logger.info(`${index === 0 ? 'Run' : 'Also run'}: oat sync --scope ${scope}`);
294
+ });
295
+ if (syncScopes.length === 0) {
296
+ context.logger.info('No sync needed.');
135
297
  }
136
298
  }
137
299
  function reportOutdatedSkills(context, outdatedSkills) {
@@ -140,7 +302,7 @@ function reportOutdatedSkills(context, outdatedSkills) {
140
302
  }
141
303
  context.logger.info('Outdated skills:');
142
304
  for (const skill of outdatedSkills) {
143
- context.logger.info(` ${skill.name} ${formatVersionForDisplay(skill.installed)} -> ${formatVersionForDisplay(skill.bundled)}`);
305
+ context.logger.info(` ${skill.name} (${skill.targetRoot}) ${formatVersionForDisplay(skill.installed)} -> ${formatVersionForDisplay(skill.bundled)}`);
144
306
  }
145
307
  }
146
308
  async function updateOutdatedSkills(outdatedSkills, assetsRoot, dependencies) {
@@ -163,7 +325,7 @@ const PACK_DESCRIPTIONS = {
163
325
  research: 'Research, analysis, verification, and synthesis',
164
326
  };
165
327
  export function buildToolPacksSectionBody(packs) {
166
- const userPacks = packs.filter((p) => p.scope === 'user');
328
+ const userPacks = packs.filter((p) => p.scope === 'user' || p.scope === 'both');
167
329
  const hasWorkflows = packs.some((p) => p.pack === 'workflows');
168
330
  const lines = [
169
331
  '## Tool Packs',
@@ -179,7 +341,11 @@ export function buildToolPacksSectionBody(packs) {
179
341
  }
180
342
  lines.push('', '### Installed Packs', '');
181
343
  for (const { pack, scope } of packs) {
182
- const suffix = scope === 'user' ? ' _(user scope)_' : '';
344
+ const suffix = scope === 'user'
345
+ ? ' _(user scope)_'
346
+ : scope === 'both'
347
+ ? ' _(project + user scope)_'
348
+ : '';
183
349
  lines.push(`- **${pack}** — ${PACK_DESCRIPTIONS[pack]}${suffix}`);
184
350
  }
185
351
  if (hasWorkflows) {
@@ -188,66 +354,118 @@ export function buildToolPacksSectionBody(packs) {
188
354
  return lines.join('\n');
189
355
  }
190
356
  export async function runInitTools(context, dependencies) {
357
+ lastRunInitToolsMetadata = null;
191
358
  try {
359
+ const projectRoot = await dependencies.resolveProjectRoot(context.cwd);
360
+ const userRoot = dependencies.resolveScopeRoot('user', context.cwd, context.home);
361
+ const assetsRoot = await dependencies.resolveAssetsRoot();
362
+ const initialPackStates = await loadInstalledPackStates(projectRoot, userRoot, assetsRoot, dependencies);
192
363
  const selectedPacks = context.interactive
193
- ? ((await dependencies.selectManyWithAbort('Select tool packs to install', PACK_CHOICES, { interactive: context.interactive })) ?? [])
364
+ ? ((await dependencies.selectManyWithAbort('Select tool packs to install', buildPackChoices(initialPackStates), { interactive: context.interactive })) ?? [])
194
365
  : ['core', 'ideas', 'docs', 'workflows', 'utility', 'research'];
195
366
  if (!context.interactive) {
196
367
  selectedPacks.push('project-management');
197
368
  }
198
369
  if (selectedPacks.length === 0) {
370
+ lastRunInitToolsMetadata = { affectedScopes: [] };
199
371
  if (!context.json) {
200
372
  context.logger.info('No tool packs selected.');
201
373
  }
202
374
  process.exitCode = 0;
203
375
  return [];
204
376
  }
205
- const projectRoot = await dependencies.resolveProjectRoot(context.cwd);
206
- const userRoot = dependencies.resolveScopeRoot('user', context.cwd, context.home);
207
- const packScopes = await resolvePackScopes(context, selectedPacks, dependencies);
377
+ const packScopes = await resolvePackScopes(context, selectedPacks, initialPackStates, dependencies);
208
378
  function packRoot(pack) {
209
379
  return packScopes[pack] === 'user' ? userRoot : projectRoot;
210
380
  }
211
- const assetsRoot = await dependencies.resolveAssetsRoot();
381
+ function packTargets(pack) {
382
+ return packScopes[pack] === 'both'
383
+ ? [projectRoot, userRoot]
384
+ : [packRoot(pack)];
385
+ }
212
386
  const outdatedSkills = [];
387
+ const affectedScopes = new Set();
388
+ for (const pack of selectedPacks) {
389
+ if (!isUserEligiblePack(pack)) {
390
+ continue;
391
+ }
392
+ const desiredScope = packScopes[pack];
393
+ const currentLocation = initialPackStates[pack].location;
394
+ if (desiredScope === 'both') {
395
+ continue;
396
+ }
397
+ if (desiredScope === 'user') {
398
+ if (currentLocation === 'project' || currentLocation === 'both') {
399
+ await removePackFromScope(pack, projectRoot, dependencies);
400
+ affectedScopes.add('project');
401
+ }
402
+ continue;
403
+ }
404
+ if (currentLocation === 'user' || currentLocation === 'both') {
405
+ await removePackFromScope(pack, userRoot, dependencies);
406
+ affectedScopes.add('user');
407
+ }
408
+ }
213
409
  if (selectedPacks.includes('core')) {
214
410
  // Core pack always installs at user scope, regardless of userEligibleScope
411
+ affectedScopes.add('user');
215
412
  const coreResult = await dependencies.installCore({
216
413
  assetsRoot,
217
414
  targetRoot: userRoot,
218
415
  });
219
416
  for (const skill of coreResult.outdatedSkills) {
220
- outdatedSkills.push({ ...skill, targetRoot: userRoot });
417
+ outdatedSkills.push({
418
+ ...skill,
419
+ targetRoot: userRoot,
420
+ selectionKey: `${skill.name}:${userRoot}`,
421
+ });
221
422
  }
222
423
  }
223
424
  if (selectedPacks.includes('ideas')) {
224
- const targetRoot = packRoot('ideas');
225
- const ideasResult = await dependencies.installIdeas({
226
- assetsRoot,
227
- targetRoot,
228
- });
229
- for (const skill of ideasResult.outdatedSkills) {
230
- outdatedSkills.push({ ...skill, targetRoot });
425
+ for (const targetRoot of packTargets('ideas')) {
426
+ affectedScopes.add(targetRoot === userRoot ? 'user' : 'project');
427
+ const ideasResult = await dependencies.installIdeas({
428
+ assetsRoot,
429
+ targetRoot,
430
+ });
431
+ for (const skill of ideasResult.outdatedSkills) {
432
+ outdatedSkills.push({
433
+ ...skill,
434
+ targetRoot,
435
+ selectionKey: `${skill.name}:${targetRoot}`,
436
+ });
437
+ }
231
438
  }
232
439
  }
233
440
  if (selectedPacks.includes('docs')) {
234
- const targetRoot = packRoot('docs');
235
- const docsResult = await dependencies.installDocs({
236
- assetsRoot,
237
- targetRoot,
238
- skills: [...DOCS_SKILLS],
239
- });
240
- for (const skill of docsResult.outdatedSkills) {
241
- outdatedSkills.push({ ...skill, targetRoot });
441
+ for (const targetRoot of packTargets('docs')) {
442
+ affectedScopes.add(targetRoot === userRoot ? 'user' : 'project');
443
+ const docsResult = await dependencies.installDocs({
444
+ assetsRoot,
445
+ targetRoot,
446
+ skills: [...DOCS_SKILLS],
447
+ });
448
+ for (const skill of docsResult.outdatedSkills) {
449
+ outdatedSkills.push({
450
+ ...skill,
451
+ targetRoot,
452
+ selectionKey: `${skill.name}:${targetRoot}`,
453
+ });
454
+ }
242
455
  }
243
456
  }
244
457
  if (selectedPacks.includes('workflows')) {
458
+ affectedScopes.add('project');
245
459
  const workflowsResult = await dependencies.installWorkflows({
246
460
  assetsRoot,
247
461
  targetRoot: projectRoot,
248
462
  });
249
463
  for (const skill of workflowsResult.outdatedSkills) {
250
- outdatedSkills.push({ ...skill, targetRoot: projectRoot });
464
+ outdatedSkills.push({
465
+ ...skill,
466
+ targetRoot: projectRoot,
467
+ selectionKey: `${skill.name}:${projectRoot}`,
468
+ });
251
469
  }
252
470
  const resolvedRoot = workflowsResult.resolvedProjectsRoot || '.oat/projects/shared';
253
471
  const projectsBase = resolvedRoot.replace(/\/[^/]+$/, '');
@@ -286,47 +504,64 @@ export async function runInitTools(context, dependencies) {
286
504
  }
287
505
  }
288
506
  if (selectedPacks.includes('utility')) {
289
- const targetRoot = packRoot('utility');
290
- const utilityResult = await dependencies.installUtility({
291
- assetsRoot,
292
- targetRoot,
293
- skills: [...UTILITY_SKILLS],
294
- });
295
- for (const skill of utilityResult.outdatedSkills) {
296
- outdatedSkills.push({ ...skill, targetRoot });
507
+ for (const targetRoot of packTargets('utility')) {
508
+ affectedScopes.add(targetRoot === userRoot ? 'user' : 'project');
509
+ const utilityResult = await dependencies.installUtility({
510
+ assetsRoot,
511
+ targetRoot,
512
+ skills: [...UTILITY_SKILLS],
513
+ });
514
+ for (const skill of utilityResult.outdatedSkills) {
515
+ outdatedSkills.push({
516
+ ...skill,
517
+ targetRoot,
518
+ selectionKey: `${skill.name}:${targetRoot}`,
519
+ });
520
+ }
297
521
  }
298
522
  }
299
523
  if (selectedPacks.includes('project-management')) {
300
524
  const targetRoot = projectRoot;
525
+ affectedScopes.add('project');
301
526
  const projectManagementResult = await dependencies.installProjectManagement({
302
527
  assetsRoot,
303
528
  targetRoot,
304
529
  });
305
530
  for (const skill of projectManagementResult.outdatedSkills) {
306
- outdatedSkills.push({ ...skill, targetRoot });
531
+ outdatedSkills.push({
532
+ ...skill,
533
+ targetRoot,
534
+ selectionKey: `${skill.name}:${targetRoot}`,
535
+ });
307
536
  }
308
537
  }
309
538
  if (selectedPacks.includes('research')) {
310
- const targetRoot = packRoot('research');
311
- const researchResult = await dependencies.installResearch({
312
- assetsRoot,
313
- targetRoot,
314
- skills: [...RESEARCH_SKILLS],
315
- });
316
- for (const skill of researchResult.outdatedSkills) {
317
- outdatedSkills.push({ ...skill, targetRoot });
539
+ for (const targetRoot of packTargets('research')) {
540
+ affectedScopes.add(targetRoot === userRoot ? 'user' : 'project');
541
+ const researchResult = await dependencies.installResearch({
542
+ assetsRoot,
543
+ targetRoot,
544
+ skills: [...RESEARCH_SKILLS],
545
+ });
546
+ for (const skill of researchResult.outdatedSkills) {
547
+ outdatedSkills.push({
548
+ ...skill,
549
+ targetRoot,
550
+ selectionKey: `${skill.name}:${targetRoot}`,
551
+ });
552
+ }
318
553
  }
319
554
  }
320
555
  if (outdatedSkills.length > 0) {
321
556
  reportOutdatedSkills(context, outdatedSkills);
322
557
  if (context.interactive) {
323
558
  const selectedNames = (await dependencies.selectManyWithAbort('Update outdated skills?', outdatedSkills.map((skill) => ({
324
- label: `${skill.name} (${skill.installed} -> ${skill.bundled})`,
325
- value: skill.name,
559
+ label: `${skill.name} (${skill.targetRoot}) (${skill.installed} -> ${skill.bundled})`,
560
+ value: skill.selectionKey,
326
561
  checked: true,
327
562
  })), { interactive: context.interactive })) ?? [];
328
563
  const selectedSet = new Set(selectedNames);
329
- const selectedOutdated = outdatedSkills.filter((skill) => selectedSet.has(skill.name));
564
+ const selectedOutdated = outdatedSkills.filter((skill) => selectedSet.has(skill.selectionKey));
330
565
  const updatedNames = await updateOutdatedSkills(selectedOutdated, assetsRoot, dependencies);
331
566
  if (updatedNames.length > 0) {
332
567
  context.logger.info(`Updated outdated skills: ${updatedNames.join(', ')}`);
@@ -349,17 +584,18 @@ export async function runInitTools(context, dependencies) {
349
584
  context.logger.info(`AGENTS.md tool packs section ${sectionResult.action}.`);
350
585
  }
351
586
  const config = await dependencies.readOatConfig(projectRoot);
352
- const tools = { ...config.tools };
353
- for (const pack of selectedPacks) {
354
- tools[pack] = true;
355
- }
587
+ const tools = buildInstalledToolsConfig(selectedPacks, initialPackStates, config.tools);
356
588
  await dependencies.writeOatConfig(projectRoot, { ...config, tools });
357
- const hasUserScope = selectedPacks.some((pack) => packScopes[pack] === 'user');
358
- reportSuccess(context, selectedPacks, hasUserScope ? 'user' : 'project');
589
+ const affectedScopesList = [...affectedScopes];
590
+ lastRunInitToolsMetadata = {
591
+ affectedScopes: affectedScopesList,
592
+ };
593
+ reportSuccess(context, packScopeInfo, affectedScopesList);
359
594
  process.exitCode = 0;
360
595
  return selectedPacks;
361
596
  }
362
597
  catch (error) {
598
+ lastRunInitToolsMetadata = null;
363
599
  const message = error instanceof Error ? error.message : String(error);
364
600
  if (context.json) {
365
601
  context.logger.json({ status: 'error', message });
@@ -0,0 +1,10 @@
1
+ import type { PackName, ToolInfo } from '../../tools/shared/types.js';
2
+ export type PackInstallLocation = 'not-installed' | 'project' | 'user' | 'both';
3
+ export interface PackInstallState {
4
+ project: boolean;
5
+ user: boolean;
6
+ location: PackInstallLocation;
7
+ }
8
+ export declare function resolvePackInstallLocation(project: boolean, user: boolean): PackInstallLocation;
9
+ export declare function buildPackInstallStateMap<TPack extends PackName>(packs: readonly TPack[], tools: ToolInfo[]): Record<TPack, PackInstallState>;
10
+ //# sourceMappingURL=install-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-state.d.ts","sourceRoot":"","sources":["../../../../src/commands/init/tools/install-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAEvE,MAAM,MAAM,mBAAmB,GAAG,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,mBAAmB,CAAC;CAC/B;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,EAChB,IAAI,EAAE,OAAO,GACZ,mBAAmB,CAcrB;AAED,wBAAgB,wBAAwB,CAAC,KAAK,SAAS,QAAQ,EAC7D,KAAK,EAAE,SAAS,KAAK,EAAE,EACvB,KAAK,EAAE,QAAQ,EAAE,GAChB,MAAM,CAAC,KAAK,EAAE,gBAAgB,CAAC,CA8BjC"}
@@ -0,0 +1,36 @@
1
+ export function resolvePackInstallLocation(project, user) {
2
+ if (project && user) {
3
+ return 'both';
4
+ }
5
+ if (project) {
6
+ return 'project';
7
+ }
8
+ if (user) {
9
+ return 'user';
10
+ }
11
+ return 'not-installed';
12
+ }
13
+ export function buildPackInstallStateMap(packs, tools) {
14
+ const state = Object.fromEntries(packs.map((pack) => [
15
+ pack,
16
+ {
17
+ project: false,
18
+ user: false,
19
+ location: 'not-installed',
20
+ },
21
+ ]));
22
+ for (const tool of tools) {
23
+ if (tool.pack === 'custom' || !(tool.pack in state)) {
24
+ continue;
25
+ }
26
+ const packState = state[tool.pack];
27
+ if (tool.scope === 'project') {
28
+ packState.project = true;
29
+ }
30
+ else {
31
+ packState.user = true;
32
+ }
33
+ packState.location = resolvePackInstallLocation(packState.project, packState.user);
34
+ }
35
+ return state;
36
+ }
@@ -1,4 +1,5 @@
1
+ import { type InitToolsDependencies } from '../../init/tools/index.js';
1
2
  import { type AutoSyncDependencies } from '../../tools/shared/auto-sync.js';
2
3
  import type { Command } from 'commander';
3
- export declare function createToolsInstallCommand(syncDependencies?: AutoSyncDependencies, createBaseCommand?: () => Command): Command;
4
+ export declare function createToolsInstallCommand(syncDependencies?: AutoSyncDependencies, initOverrides?: Partial<InitToolsDependencies>, createBaseCommand?: () => Command): Command;
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/install/index.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBzC,wBAAgB,yBAAyB,CACvC,gBAAgB,GAAE,oBAA8C,EAChE,iBAAiB,GAAE,MAAM,OAAgC,GACxD,OAAO,CA2BT"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/commands/tools/install/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,sBAAsB,CAAC;AAK9B,OAAO,EACL,KAAK,oBAAoB,EAE1B,MAAM,kCAAkC,CAAC;AAE1C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAwBzC,wBAAgB,yBAAyB,CACvC,gBAAgB,GAAE,oBAA8C,EAChE,aAAa,GAAE,OAAO,CAAC,qBAAqB,CAAM,EAClD,iBAAiB,CAAC,EAAE,MAAM,OAAO,GAChC,OAAO,CAoCT"}
@@ -1,6 +1,6 @@
1
1
  import { execFile } from 'node:child_process';
2
2
  import { buildCommandContext } from '../../../app/command-context.js';
3
- import { createInitToolsCommand } from '../../init/tools/index.js';
3
+ import { consumeInitToolsRunMetadata, createInitToolsCommand, } from '../../init/tools/index.js';
4
4
  import { readGlobalOptions, resolveConcreteScopes, } from '../../shared/shared.utils.js';
5
5
  import { autoSync, } from '../../tools/shared/auto-sync.js';
6
6
  import { getInstalledCanonicalPaths as getInstallSyncCanonicalPaths } from '../../tools/shared/install-sync-context.js';
@@ -26,8 +26,10 @@ const defaultSyncDependencies = {
26
26
  });
27
27
  },
28
28
  };
29
- export function createToolsInstallCommand(syncDependencies = defaultSyncDependencies, createBaseCommand = createInitToolsCommand) {
30
- const cmd = createBaseCommand();
29
+ export function createToolsInstallCommand(syncDependencies = defaultSyncDependencies, initOverrides = {}, createBaseCommand) {
30
+ const cmd = createBaseCommand === undefined
31
+ ? createInitToolsCommand(initOverrides)
32
+ : createBaseCommand();
31
33
  cmd.name('install');
32
34
  cmd.option('--no-sync', 'Skip auto-sync after install');
33
35
  cmd.hook('postAction', async (thisCommand, actionCommand) => {
@@ -37,8 +39,12 @@ export function createToolsInstallCommand(syncDependencies = defaultSyncDependen
37
39
  if (opts.sync === false)
38
40
  return;
39
41
  const globalOptions = readGlobalOptions(actionCommand);
40
- const context = buildCommandContext(globalOptions);
41
- const scopes = resolveConcreteScopes(context.scope);
42
+ const buildContext = initOverrides.buildCommandContext ?? buildCommandContext;
43
+ const context = buildContext(globalOptions);
44
+ const metadata = consumeInitToolsRunMetadata();
45
+ const scopes = metadata === null
46
+ ? resolveConcreteScopes(context.scope)
47
+ : metadata.affectedScopes;
42
48
  const installedCanonicalPaths = getInstallSyncCanonicalPaths(actionCommand);
43
49
  await autoSync(scopes, context.cwd, context.home, context.logger, syncDependencies, { installedCanonicalPaths });
44
50
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-agent-toolkit/cli",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "private": false,
5
5
  "description": "Open Agent Toolkit CLI",
6
6
  "homepage": "https://github.com/voxmedia/open-agent-toolkit/tree/main/packages/cli",
@@ -33,7 +33,7 @@
33
33
  "ora": "^9.0.0",
34
34
  "yaml": "2.8.2",
35
35
  "zod": "^3.25.76",
36
- "@open-agent-toolkit/control-plane": "0.0.35"
36
+ "@open-agent-toolkit/control-plane": "0.0.36"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.10.0",