@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.
- package/assets/docs/cli-utilities/tool-packs.md +5 -1
- package/assets/docs/docs-tooling/workflows.md +5 -0
- package/assets/docs/workflows/projects/lifecycle.md +2 -2
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-document/SKILL.md +76 -10
- package/dist/commands/init/tools/index.d.ts +13 -2
- package/dist/commands/init/tools/index.d.ts.map +1 -1
- package/dist/commands/init/tools/index.js +318 -82
- package/dist/commands/init/tools/install-state.d.ts +10 -0
- package/dist/commands/init/tools/install-state.d.ts.map +1 -0
- package/dist/commands/init/tools/install-state.js +36 -0
- package/dist/commands/tools/install/index.d.ts +2 -1
- package/dist/commands/tools/install/index.d.ts.map +1 -1
- package/dist/commands/tools/install/index.js +11 -5
- package/package.json +2 -2
|
@@ -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
|
-
-
|
|
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
2
|
name: oat-project-document
|
|
3
|
-
version: 1.
|
|
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
|
-
-
|
|
218
|
-
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
**
|
|
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 (
|
|
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:
|
|
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":"
|
|
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 {
|
|
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,
|
|
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,
|
|
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
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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) =>
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
126
|
-
|
|
286
|
+
installedPacks: packs,
|
|
287
|
+
syncScopes,
|
|
127
288
|
});
|
|
128
289
|
return;
|
|
129
290
|
}
|
|
130
|
-
context.logger.info(`Installed tool packs: ${
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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'
|
|
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',
|
|
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
|
|
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
|
-
|
|
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({
|
|
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
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
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
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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({
|
|
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
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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({
|
|
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
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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.
|
|
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.
|
|
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 =
|
|
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
|
|
358
|
-
|
|
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":"
|
|
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,
|
|
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
|
|
41
|
-
const
|
|
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.
|
|
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.
|
|
36
|
+
"@open-agent-toolkit/control-plane": "0.0.37"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.10.0",
|