@open-agent-toolkit/cli 0.0.35 → 0.0.37

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
 
@@ -27,6 +27,11 @@ Install the workflow skills with `oat tools install docs` (preferred) or
27
27
  contributor guidance, and docs-app contract issues
28
28
  - `oat-docs-apply` consumes the analysis artifact and applies only approved,
29
29
  evidence-backed recommendations
30
+ - `oat-project-document` performs post-implementation docs sync for a tracked project,
31
+ including finding missing coverage for newly shipped capability areas and
32
+ recommending new docs pages or directories when existing docs do not provide
33
+ a natural home. See the [project lifecycle post-implementation flow](../workflows/projects/lifecycle.md#post-implementation-flow)
34
+ for where this runs in the tracked project flow.
30
35
 
31
36
  ## Contract model
32
37
 
@@ -18,7 +18,7 @@ OAT lifecycle order:
18
18
  7. Summary (`oat-project-summary`) — generates `summary.md` as institutional memory; `oat-project-pr-final` and `oat-project-complete` auto-refresh it when missing or stale
19
19
  8. PR (`oat-project-pr-progress` / `oat-project-pr-final`) — sets `pr_open` status
20
20
  9. Revision loop (`oat-project-revise`) — optional; accepts post-PR feedback
21
- 10. Documentation sync (`oat-project-document`) — optional; reads project artifacts to identify docs needing updates, checks `tools.project-management`, and auto-runs `oat-pjm-update-repo-reference` before scanning docs when the project-management pack is installed
21
+ 10. Documentation sync (`oat-project-document`) — optional; reads project artifacts and code evidence to identify docs needing updates, checks for missing coverage of newly shipped capability areas, checks `tools.project-management`, and auto-runs `oat-pjm-update-repo-reference` before scanning docs when the project-management pack is installed
22
22
  11. Complete (`oat-project-complete`)
23
23
 
24
24
  **Shortcut:** `oat-project-next` reads project state and invokes the correct next skill automatically — use it instead of remembering which skill comes next. Complements `oat-project-progress` (which is read-only diagnostic).
@@ -49,7 +49,7 @@ flowchart LR
49
49
  After implementation and final review pass:
50
50
 
51
51
  1. **Summary** (`oat-project-summary`) — generates `summary.md` as institutional memory from project artifacts; PR-final and completion will auto-refresh it if you have not already run it or if it is stale
52
- 2. **Documentation** (`oat-project-document`) — optional sync of project docs; now uses the shared `tools.project-management` config signal to decide whether repo-reference refresh should run before docs analysis
52
+ 2. **Documentation** (`oat-project-document`) — optional sync of project docs; now uses the shared `tools.project-management` config signal to decide whether repo-reference refresh should run before docs analysis, and should recommend new docs pages/directories when the shipped work introduces a capability area that the docs app does not already cover
53
53
  3. **PR** (`oat-project-pr-final`) — creates PR description (auto-refreshes `summary.md` first when needed, then uses it as source), sets `oat_phase_status: pr_open`, and tracks actual PR existence with `oat_pr_status` / `oat_pr_url`
54
54
  4. **Revision loop** (`oat-project-revise`) — accepts post-PR feedback:
55
55
  - Inline feedback creates `p-revN` revision phases with `prevN-tNN` task IDs
@@ -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.37",
3
+ "docs-config": "0.0.37",
4
+ "docs-theme": "0.0.37",
5
+ "docs-transforms": "0.0.37"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: oat-project-document
3
- version: 1.2.0
3
+ version: 1.3.0
4
4
  description: Run when implementation is complete and documentation needs updating. Analyzes project artifacts to produce documentation update recommendations, then applies approved changes before project completion.
5
5
  argument-hint: '[project-path] [--auto]'
6
6
  disable-model-invocation: true
@@ -195,9 +195,9 @@ These will be verified against actual code in Step 3.
195
195
  - Quick-mode projects may lack spec.md and design.md — extract what's available
196
196
  - If only plan.md exists (no implementation.md), the project may not have started implementation yet — still proceed, but note that documentation recommendations will be based on planned work rather than verified implementation
197
197
 
198
- ### Step 3: Verify Against Code
198
+ ### Step 3: Verify Against Code and Build a Capability Inventory
199
199
 
200
- Read source files referenced in artifacts to confirm what actually shipped.
200
+ Read source files referenced in artifacts to confirm what actually shipped, then do a targeted capability-discovery pass so the skill can catch newly introduced documentation surfaces rather than only updating already-documented ones.
201
201
 
202
202
  **For each referenced source file:**
203
203
 
@@ -211,11 +211,31 @@ Read source files referenced in artifacts to confirm what actually shipped.
211
211
 
212
212
  - Add code-verified details that artifacts didn't capture
213
213
  - Note any discrepancies between artifacts and code (informational — include in delta plan as context, not as blocking issues)
214
+ - Organize the verified implementation into **capability areas** (for example: new app surface, CI/CD pipeline, release automation flow, deployment target, integration surface, CLI/config workflow)
215
+
216
+ **Targeted capability discovery pass:**
217
+
218
+ After verifying artifact-referenced files, inspect the strongest adjacent signals for newly shipped capability surfaces even when the exact docs target does not already exist.
219
+
220
+ Prioritize evidence from:
221
+
222
+ - new apps/packages/directories called out by the artifacts
223
+ - workflow/config files tied to shipped behavior (`.github/workflows`, release configs, deploy configs, mobile build/release files, etc.)
224
+ - entrypoints, route registration files, service modules, and schemas that define user- or operator-facing behavior
225
+ - package manifests and scripts that expose new setup, release, or operational workflows
226
+
227
+ For each significant capability area, capture:
228
+
229
+ - capability name
230
+ - concrete repo evidence proving it shipped
231
+ - likely audience (`developer`, `operator`, `integrator`, `end user`)
232
+ - whether the capability represents a new docs surface versus an addition to an existing surface
214
233
 
215
234
  **Scope control:**
216
235
 
217
- - Focus on files directly referenced in artifacts don't scan the entire codebase
218
- - If artifacts reference many files (>20), prioritize: new files first, then modified files with the most changes
236
+ - Start with files directly referenced in artifacts, then inspect only the highest-signal adjacent files needed to understand the shipped capability areas
237
+ - Do not scan the entire codebase blindly; stay anchored to the implementation areas surfaced by the project artifacts
238
+ - If artifacts reference many files (>20), prioritize: new files first, then modified files with the most changes, then only the adjacent files needed to confirm docs impact
219
239
  - Read file contents, not just check existence — the skill needs to understand what the code does to make good documentation recommendations
220
240
 
221
241
  ### Step 4: Discover Documentation Surfaces
@@ -228,6 +248,8 @@ Scan the repository for all documentation and instruction surfaces.
228
248
  - Read the docs tooling config (e.g., `$DOCS_CONFIG`) to understand nav structure
229
249
  - List all files in `$DOCS_ROOT` recursively
230
250
  - Read existing docs files that could be affected by the project
251
+ - Identify the parent section or directory where each uncovered capability area would naturally live
252
+ - Note when no existing page or directory is a good fit — this is a strong signal for a `CREATE` recommendation, not a reason to force the content into an unrelated existing page
231
253
 
232
254
  2. **Root README.md:**
233
255
  - Always check — read current content
@@ -270,7 +292,38 @@ Scan the repository for all documentation and instruction surfaces.
270
292
 
271
293
  Compare "what was built" (from Steps 2-3) against "what's documented" (from Step 4) to produce recommendations.
272
294
 
273
- **5a. Documentation surface assessment:**
295
+ **5a. Capability coverage assessment (required):**
296
+
297
+ Before recommending file-level edits, evaluate coverage for each significant capability area in the "what was built" model.
298
+
299
+ For each capability area, classify the documentation state as:
300
+
301
+ - **adequately covered** — existing docs already explain the shipped behavior accurately
302
+ - **thin coverage** — the area is mentioned, but important setup, workflow, or usage details are missing
303
+ - **no coverage** — no existing docs surface meaningfully covers the capability
304
+
305
+ For each capability with **thin coverage** or **no coverage**, determine the best docs home:
306
+
307
+ - **Expand an existing page** when a clear, discoverable page already owns the topic
308
+ - **Create a new page** when the topic is distinct enough to deserve its own entrypoint
309
+ - **Create a new docs directory** (with `index.md` entrypoint, where that is the local convention) when the shipped work introduces a new top-level capability area with multiple likely subtopics
310
+
311
+ Bias rules:
312
+
313
+ - If a docs app exists and the shipped work represents a new major capability area, do **not** default to stuffing the information into `README.md`, `roadmap.md`, or `current-state.md`
314
+ - Examples that often warrant `CREATE` recommendations: new mobile apps, CI/CD and release automation, new deployment targets, new integration surfaces, or new operator workflows
315
+ - If no existing docs section is a natural fit, prefer recommending a new page or directory rather than forcing an `UPDATE` on a loosely related page
316
+
317
+ Each coverage-gap finding should capture:
318
+
319
+ - capability area
320
+ - current docs state (`thin coverage` or `no coverage`)
321
+ - evidence proving the capability shipped
322
+ - likely audience
323
+ - suggested docs location (`existing page`, `new page`, or `new directory`)
324
+ - why that location is the right home
325
+
326
+ **5b. Documentation surface assessment:**
274
327
 
275
328
  For each documentation surface relevant to the project, determine one of:
276
329
 
@@ -283,6 +336,7 @@ For each documentation surface relevant to the project, determine one of:
283
336
  - Proposed file path
284
337
  - Proposed content outline (section headings, key points)
285
338
  - Why this warrants a new file (evidence)
339
+ - If inside a docs app, note any parent index/nav follow-on work that will be needed
286
340
 
287
341
  - **SPLIT:** Existing doc would become too large with additions. Specify:
288
342
  - Current file path and approximate size
@@ -291,7 +345,13 @@ For each documentation surface relevant to the project, determine one of:
291
345
 
292
346
  - **No change:** Surface is already accurate — skip from delta plan.
293
347
 
294
- **5b. Instruction surface assessment (strong signals only):**
348
+ When deciding between `UPDATE` and `CREATE`, prefer `CREATE` if:
349
+
350
+ - the capability currently has **no coverage**
351
+ - the docs app has no discoverable home for that topic
352
+ - the proposed addition would otherwise bury a new major workflow inside an unrelated page
353
+
354
+ **5c. Instruction surface assessment (strong signals only):**
295
355
 
296
356
  Only recommend instruction changes when there is a clear trigger:
297
357
 
@@ -305,14 +365,17 @@ Only recommend instruction changes when there is a clear trigger:
305
365
 
306
366
  If no strong signal is present for an instruction surface, skip it.
307
367
 
308
- **5c. Per recommendation, capture:**
368
+ **5d. Per recommendation, capture:**
309
369
 
310
370
  ```
311
371
  - Target: {file path — existing or proposed}
312
372
  - Action: {UPDATE | CREATE | SPLIT}
313
373
  - Summary: {1-2 sentences on what changes and why}
314
- - Evidence: {artifact reference — e.g., "spec.md §3", "plan.md p02-t03", "implementation.md p01-t01 outcome"}
374
+ - Evidence: {artifact/code reference — e.g., "spec.md §3", "plan.md p02-t03", "implementation.md p01-t01 outcome", "workflow file X proves release automation shipped"}
375
+ - Audience: {developer | operator | integrator | end user}
315
376
  - Content guidance: {specific content to add or outline for new files}
377
+ - Coverage state: {adequately covered | thin coverage | no coverage}
378
+ - Parent docs impact: {parent index/nav updates needed, or "none"}
316
379
  ```
317
380
 
318
381
  ### Step 6: Present Delta Plan
@@ -378,7 +441,8 @@ Execute the approved documentation updates.
378
441
  2. **CREATE:**
379
442
  - Create parent directories if needed (`mkdir -p`)
380
443
  - Write the new file with the content outlined in the recommendation
381
- - For docs directory files: follow the existing docs conventions (e.g., index.md structure for MkDocs)
444
+ - For docs directory files: follow the existing docs conventions for entrypoints and local navigation (for example, `index.md` entrypoints and `## Contents` sections where that is the local pattern)
445
+ - If the recommendation created a new docs directory, add the required entrypoint file for that directory as part of the same change
382
446
 
383
447
  3. **SPLIT:**
384
448
  - Create the new file with the content being moved
@@ -475,7 +539,9 @@ Next steps:
475
539
  ## Success Criteria
476
540
 
477
541
  - All documentation surfaces relevant to the project are scanned
542
+ - Significant capability areas from the "what was built" model are classified as `adequately covered`, `thin coverage`, or `no coverage` before file-level recommendations are chosen
478
543
  - Recommendations are evidence-based (every recommendation cites artifact/code sources)
544
+ - `CREATE` actions are recommended when no existing docs surface is a natural fit, including new docs pages or directories when the shipped work introduces a new capability area
479
545
  - Interactive approval flow works correctly (all/individual/skip)
480
546
  - `--auto` mode applies all changes without user interaction
481
547
  - New files and splits are handled correctly
@@ -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.37",
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.37"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@types/node": "^22.10.0",