agent-trellis 0.7.0 → 0.8.2
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/README.md +48 -153
- package/dist/adapters/claude-code.js +3 -2
- package/dist/adapters/codex.js +3 -2
- package/dist/adapters/jsonMcp.d.ts +3 -1
- package/dist/adapters/jsonMcp.js +18 -4
- package/dist/adapters/kimi-code.d.ts +21 -0
- package/dist/adapters/kimi-code.js +107 -0
- package/dist/adapters/kiro.js +3 -2
- package/dist/adapters/mcpPlan.d.ts +6 -1
- package/dist/adapters/mcpPlan.js +106 -8
- package/dist/adapters/pi.js +22 -3
- package/dist/adapters/symlinkPlan.d.ts +7 -0
- package/dist/adapters/symlinkPlan.js +16 -7
- package/dist/adapters/zcode.d.ts +21 -0
- package/dist/adapters/zcode.js +234 -0
- package/dist/cli.js +145 -29
- package/dist/commands/doctor.d.ts +7 -2
- package/dist/commands/doctor.js +40 -2
- package/dist/commands/init.d.ts +4 -2
- package/dist/commands/init.js +29 -13
- package/dist/commands/kimi.d.ts +16 -0
- package/dist/commands/kimi.js +64 -0
- package/dist/commands/manage.d.ts +39 -0
- package/dist/commands/manage.js +126 -0
- package/dist/commands/mcp.d.ts +77 -14
- package/dist/commands/mcp.js +169 -29
- package/dist/commands/mcpGateway.d.ts +6 -0
- package/dist/commands/mcpGateway.js +35 -14
- package/dist/commands/mcpImport.d.ts +46 -0
- package/dist/commands/mcpImport.js +359 -0
- package/dist/commands/memory.d.ts +17 -3
- package/dist/commands/memory.js +27 -8
- package/dist/commands/migrate.d.ts +16 -5
- package/dist/commands/migrate.js +93 -17
- package/dist/commands/onboard.d.ts +74 -2
- package/dist/commands/onboard.js +469 -71
- package/dist/commands/rollback.js +28 -2
- package/dist/commands/secretsAudit.js +4 -1
- package/dist/commands/skill.d.ts +52 -6
- package/dist/commands/skill.js +114 -15
- package/dist/commands/sync.js +5 -1
- package/dist/core/adapter.d.ts +11 -1
- package/dist/core/canonical.d.ts +15 -7
- package/dist/core/canonical.js +101 -13
- package/dist/core/capabilitySelection.d.ts +45 -0
- package/dist/core/capabilitySelection.js +124 -0
- package/dist/core/types.d.ts +25 -2
- package/dist/core/types.js +8 -0
- package/dist/lib/agentBridge.d.ts +169 -0
- package/dist/lib/agentBridge.js +367 -0
- package/dist/lib/agentBridgeProvider.d.ts +14 -0
- package/dist/lib/agentBridgeProvider.js +95 -0
- package/dist/lib/backup.d.ts +32 -0
- package/dist/lib/backup.js +20 -1
- package/dist/lib/builtinSkills.d.ts +2 -0
- package/dist/lib/builtinSkills.js +4 -0
- package/dist/lib/chatAgents.d.ts +42 -0
- package/dist/lib/chatAgents.js +59 -0
- package/dist/lib/cliMetadata.d.ts +1 -0
- package/dist/lib/cliMetadata.js +14 -0
- package/dist/lib/envVarNames.d.ts +3 -0
- package/dist/lib/envVarNames.js +19 -0
- package/dist/lib/gatewayBackend.d.ts +28 -0
- package/dist/lib/gatewayBackend.js +72 -1
- package/dist/lib/installAgent.d.ts +7 -5
- package/dist/lib/installAgent.js +4 -3
- package/dist/lib/mcpConnect.js +5 -1
- package/dist/lib/mcpMigrateRead.d.ts +2 -0
- package/dist/lib/mcpMigrateRead.js +22 -1
- package/dist/lib/mcpRuntime.d.ts +46 -0
- package/dist/lib/mcpRuntime.js +185 -0
- package/dist/lib/mcpStatusProvider.d.ts +10 -0
- package/dist/lib/mcpStatusProvider.js +25 -0
- package/dist/lib/mcpToolRegistry.d.ts +18 -0
- package/dist/lib/mcpToolRegistry.js +107 -14
- package/dist/lib/memoryGraph.d.ts +1 -1
- package/dist/lib/memoryGraph.js +9 -3
- package/dist/lib/memoryProvider.d.ts +58 -0
- package/dist/lib/memoryProvider.js +175 -0
- package/dist/lib/memoryStatus.d.ts +17 -0
- package/dist/lib/memoryStatus.js +88 -0
- package/dist/lib/nativeMemory.d.ts +19 -0
- package/dist/lib/nativeMemory.js +129 -0
- package/dist/lib/realHomeSnapshot.js +5 -0
- package/dist/lib/runtimeControlProvider.d.ts +16 -0
- package/dist/lib/runtimeControlProvider.js +170 -0
- package/dist/lib/secretEnv.d.ts +2 -1
- package/dist/lib/secretEnv.js +6 -2
- package/dist/lib/skillProvider.d.ts +13 -0
- package/dist/lib/skillProvider.js +210 -0
- package/dist/lib/taskProvider.d.ts +7 -0
- package/dist/lib/taskProvider.js +43 -0
- package/dist/lib/taskStore.d.ts +46 -0
- package/dist/lib/taskStore.js +156 -0
- package/dist/lib/terminalPicker.d.ts +23 -49
- package/dist/lib/terminalPicker.js +80 -218
- package/dist/lib/zcodeOwnership.d.ts +17 -0
- package/dist/lib/zcodeOwnership.js +37 -0
- package/dist/pi-bridge/bundle.js +476 -37
- package/dist/pi-bridge/index.js +42 -21
- package/dist/pi-bridge/schemaTranslate.d.ts +4 -8
- package/dist/pi-bridge/schemaTranslate.js +4 -8
- package/dist/probes/codex.d.ts +7 -0
- package/dist/probes/codex.js +37 -5
- package/dist/probes/kimi-code.d.ts +12 -0
- package/dist/probes/kimi-code.js +55 -0
- package/dist/probes/zcode.d.ts +32 -0
- package/dist/probes/zcode.js +140 -0
- package/dist/sdk.d.ts +1 -1
- package/docs/architecture.md +230 -28
- package/docs/assets/brand-v3/trellis-mark-v3-a.png +0 -0
- package/docs/assets/brand-v3/trellis-mark-v3-a.svg +10 -0
- package/docs/assets/poster-series-v3/README.md +60 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-en.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-horizontal-zh.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-en.svg +48 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-memory-zh.svg +48 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-en.svg +56 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-migrate-zh.svg +56 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-en.svg +47 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-switch-zh.svg +47 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-en.svg +51 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.png +0 -0
- package/docs/assets/poster-series-v3/agent-trellis-poster-v3-unified-zh.svg +51 -0
- package/docs/getting-started.md +496 -42
- package/docs/overview.md +134 -0
- package/docs/overview.zh-CN.md +102 -0
- package/docs/research.md +59 -0
- package/docs/roadmap.md +11 -6
- package/docs/video/README.md +76 -0
- package/docs/video/build/audio/01-opening.aiff +0 -0
- package/docs/video/build/audio/01-opening.mp3 +0 -0
- package/docs/video/build/audio/02-pain.aiff +0 -0
- package/docs/video/build/audio/02-pain.mp3 +0 -0
- package/docs/video/build/audio/03-solution.aiff +0 -0
- package/docs/video/build/audio/03-solution.mp3 +0 -0
- package/docs/video/build/audio/04-install.aiff +0 -0
- package/docs/video/build/audio/04-install.mp3 +0 -0
- package/docs/video/build/audio/04-setup.mp3 +0 -0
- package/docs/video/build/audio/05-dryrun.mp3 +0 -0
- package/docs/video/build/audio/05-init.aiff +0 -0
- package/docs/video/build/audio/05-init.mp3 +0 -0
- package/docs/video/build/audio/05-interactive.mp3 +0 -0
- package/docs/video/build/audio/06-dryrun.aiff +0 -0
- package/docs/video/build/audio/06-dryrun.mp3 +0 -0
- package/docs/video/build/audio/06-onboard.mp3 +0 -0
- package/docs/video/build/audio/06-run.mp3 +0 -0
- package/docs/video/build/audio/07-onboard.aiff +0 -0
- package/docs/video/build/audio/07-onboard.mp3 +0 -0
- package/docs/video/build/audio/07-verify.mp3 +0 -0
- package/docs/video/build/audio/08-closing.mp3 +0 -0
- package/docs/video/build/audio/08-doctor.aiff +0 -0
- package/docs/video/build/audio/08-doctor.mp3 +0 -0
- package/docs/video/build/audio/09-rollback.aiff +0 -0
- package/docs/video/build/audio/09-rollback.mp3 +0 -0
- package/docs/video/build/audio/10-principles.aiff +0 -0
- package/docs/video/build/audio/10-principles.mp3 +0 -0
- package/docs/video/build/audio/11-closing.aiff +0 -0
- package/docs/video/build/audio/11-closing.mp3 +0 -0
- package/docs/video/build/frames/01-opening-0.png +0 -0
- package/docs/video/build/frames/02-pain-1.png +0 -0
- package/docs/video/build/frames/02-pain-2.png +0 -0
- package/docs/video/build/frames/03-solution-3.png +0 -0
- package/docs/video/build/frames/04-install-4.png +0 -0
- package/docs/video/build/frames/04-setup-4.png +0 -0
- package/docs/video/build/frames/04-setup-5.png +0 -0
- package/docs/video/build/frames/05-dryrun-6.png +0 -0
- package/docs/video/build/frames/05-init-5.png +0 -0
- package/docs/video/build/frames/05-interactive-5.png +0 -0
- package/docs/video/build/frames/05-interactive-6.png +0 -0
- package/docs/video/build/frames/06-dryrun-6.png +0 -0
- package/docs/video/build/frames/06-dryrun-7.png +0 -0
- package/docs/video/build/frames/06-onboard-7.png +0 -0
- package/docs/video/build/frames/06-onboard-8.png +0 -0
- package/docs/video/build/frames/06-run-7.png +0 -0
- package/docs/video/build/frames/06-run-8.png +0 -0
- package/docs/video/build/frames/07-onboard-10.png +0 -0
- package/docs/video/build/frames/07-onboard-7.png +0 -0
- package/docs/video/build/frames/07-onboard-8.png +0 -0
- package/docs/video/build/frames/07-onboard-9.png +0 -0
- package/docs/video/build/frames/07-verify-10.png +0 -0
- package/docs/video/build/frames/07-verify-8.png +0 -0
- package/docs/video/build/frames/07-verify-9.png +0 -0
- package/docs/video/build/frames/08-closing-10.png +0 -0
- package/docs/video/build/frames/08-closing-11.png +0 -0
- package/docs/video/build/frames/08-doctor-11.png +0 -0
- package/docs/video/build/frames/08-doctor-9.png +0 -0
- package/docs/video/build/frames/09-rollback-10.png +0 -0
- package/docs/video/build/frames/09-rollback-12.png +0 -0
- package/docs/video/build/frames/10-principles-11.png +0 -0
- package/docs/video/build/frames/10-principles-13.png +0 -0
- package/docs/video/build/frames/11-closing-12.png +0 -0
- package/docs/video/build/frames/11-closing-14.png +0 -0
- package/docs/video/build/segments/01-opening.mp4 +0 -0
- package/docs/video/build/segments/01-opening.txt +3 -0
- package/docs/video/build/segments/02-pain.mp4 +0 -0
- package/docs/video/build/segments/02-pain.txt +5 -0
- package/docs/video/build/segments/03-solution.mp4 +0 -0
- package/docs/video/build/segments/03-solution.txt +3 -0
- package/docs/video/build/segments/04-install.mp4 +0 -0
- package/docs/video/build/segments/04-install.txt +3 -0
- package/docs/video/build/segments/04-setup.mp4 +0 -0
- package/docs/video/build/segments/04-setup.txt +3 -0
- package/docs/video/build/segments/05-dryrun.mp4 +0 -0
- package/docs/video/build/segments/05-dryrun.txt +3 -0
- package/docs/video/build/segments/05-init.mp4 +0 -0
- package/docs/video/build/segments/05-init.txt +3 -0
- package/docs/video/build/segments/05-interactive.mp4 +0 -0
- package/docs/video/build/segments/05-interactive.txt +5 -0
- package/docs/video/build/segments/06-dryrun.mp4 +0 -0
- package/docs/video/build/segments/06-dryrun.txt +3 -0
- package/docs/video/build/segments/06-onboard.mp4 +0 -0
- package/docs/video/build/segments/06-onboard.txt +5 -0
- package/docs/video/build/segments/06-run.mp4 +0 -0
- package/docs/video/build/segments/06-run.txt +3 -0
- package/docs/video/build/segments/07-onboard.mp4 +0 -0
- package/docs/video/build/segments/07-onboard.txt +5 -0
- package/docs/video/build/segments/07-verify.mp4 +0 -0
- package/docs/video/build/segments/07-verify.txt +5 -0
- package/docs/video/build/segments/08-closing.mp4 +0 -0
- package/docs/video/build/segments/08-closing.txt +3 -0
- package/docs/video/build/segments/08-doctor.mp4 +0 -0
- package/docs/video/build/segments/08-doctor.txt +3 -0
- package/docs/video/build/segments/09-rollback.mp4 +0 -0
- package/docs/video/build/segments/09-rollback.txt +3 -0
- package/docs/video/build/segments/10-principles.mp4 +0 -0
- package/docs/video/build/segments/10-principles.txt +3 -0
- package/docs/video/build/segments/11-closing.mp4 +0 -0
- package/docs/video/build/segments/11-closing.txt +3 -0
- package/docs/video/build/segments/final.txt +8 -0
- package/docs/video/build-ai/audio/01-hook.mp3 +0 -0
- package/docs/video/build-ai/audio/02-pain.mp3 +0 -0
- package/docs/video/build-ai/audio/03-solution.mp3 +0 -0
- package/docs/video/build-ai/audio/04-how.mp3 +0 -0
- package/docs/video/build-ai/audio/05-safe.mp3 +0 -0
- package/docs/video/build-ai/audio/06-closing.mp3 +0 -0
- package/docs/video/build-ai/clips/01-hook.mp4 +0 -0
- package/docs/video/build-ai/clips/02-pain.mp4 +0 -0
- package/docs/video/build-ai/clips/03-solution.mp4 +0 -0
- package/docs/video/build-ai/clips/04-how.mp4 +0 -0
- package/docs/video/build-ai/clips/05-safe.mp4 +0 -0
- package/docs/video/build-ai/clips/06-closing.mp4 +0 -0
- package/docs/video/build-ai/first-frame-02-pain.png +0 -0
- package/docs/video/build-ai/first-frame-03-solution.png +0 -0
- package/docs/video/build-ai/first-frame-04-how.png +0 -0
- package/docs/video/build-ai/first-frame-05-safe.png +0 -0
- package/docs/video/build-ai/segments/01-hook.caption.txt +2 -0
- package/docs/video/build-ai/segments/01-hook.mp4 +0 -0
- package/docs/video/build-ai/segments/02-pain.mp4 +0 -0
- package/docs/video/build-ai/segments/03-solution.mp4 +0 -0
- package/docs/video/build-ai/segments/04-how.mp4 +0 -0
- package/docs/video/build-ai/segments/05-safe.mp4 +0 -0
- package/docs/video/build-ai/segments/06-closing.mp4 +0 -0
- package/docs/video/build-ai/segments/cap-01-hook.png +0 -0
- package/docs/video/build-ai/segments/cap-02-pain.png +0 -0
- package/docs/video/build-ai/segments/cap-03-solution.png +0 -0
- package/docs/video/build-ai/segments/cap-04-how.png +0 -0
- package/docs/video/build-ai/segments/cap-05-safe.png +0 -0
- package/docs/video/build-ai/segments/cap-06-closing.png +0 -0
- package/docs/video/build-ai/segments/final.txt +6 -0
- package/docs/video/build-posters/audio/01-hook.mp3 +0 -0
- package/docs/video/build-posters/audio/02-pain.mp3 +0 -0
- package/docs/video/build-posters/audio/03-solution.mp3 +0 -0
- package/docs/video/build-posters/audio/04-how.mp3 +0 -0
- package/docs/video/build-posters/audio/05-safe.mp3 +0 -0
- package/docs/video/build-posters/audio/06-closing.mp3 +0 -0
- package/docs/video/build-posters/cap-01-hook.png +0 -0
- package/docs/video/build-posters/cap-02-pain.png +0 -0
- package/docs/video/build-posters/cap-03-solution.png +0 -0
- package/docs/video/build-posters/cap-04-how.png +0 -0
- package/docs/video/build-posters/cap-05-safe.png +0 -0
- package/docs/video/build-posters/cap-06-closing.png +0 -0
- package/docs/video/build-posters/card-01-hook.png +0 -0
- package/docs/video/build-posters/card-06-closing.png +0 -0
- package/docs/video/build-posters/segments/01-hook.mp4 +0 -0
- package/docs/video/build-posters/segments/02-pain.mp4 +0 -0
- package/docs/video/build-posters/segments/03-solution.mp4 +0 -0
- package/docs/video/build-posters/segments/04-how.mp4 +0 -0
- package/docs/video/build-posters/segments/05-safe.mp4 +0 -0
- package/docs/video/build-posters/segments/06-closing.mp4 +0 -0
- package/docs/video/build-posters/segments/final.txt +6 -0
- package/docs/video/generate-video-ai.mjs +334 -0
- package/docs/video/generate-video-posters.mjs +204 -0
- package/docs/video/generate-video.mjs +426 -0
- package/docs/video/outline.zh-CN.md +102 -0
- package/docs/video/trellis-demo-video-ai.mp4 +0 -0
- package/docs/video/trellis-demo-video.mp4 +0 -0
- package/docs/video/voiceover.zh-CN.md +49 -0
- package/package.json +6 -2
- package/schema/builtin-skills/trellis-runtime/SKILL.md +117 -0
- package/schema/capability-selection.example.yaml +23 -0
- package/schema/scope.example.yaml +1 -1
- package/schema/servers.example.yaml +15 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit lifecycle management for `~/.trellis/managed.yaml`.
|
|
3
|
+
*
|
|
4
|
+
* Onboarding stays additive: omission must never silently revoke Trellis's
|
|
5
|
+
* write authorization. Exact replacement and subtraction live here so they
|
|
6
|
+
* are deliberate, visible in shell history, dry-runnable, and recoverable.
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentId } from "../core/types.js";
|
|
9
|
+
export type ManageOperation = "list" | "set" | "add" | "remove";
|
|
10
|
+
export interface ManagePlan {
|
|
11
|
+
operation: ManageOperation;
|
|
12
|
+
current: AgentId[];
|
|
13
|
+
desired: AgentId[];
|
|
14
|
+
added: AgentId[];
|
|
15
|
+
removed: AgentId[];
|
|
16
|
+
changed: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface ParsedManageArgs {
|
|
19
|
+
operation: ManageOperation;
|
|
20
|
+
ids?: string;
|
|
21
|
+
json: boolean;
|
|
22
|
+
dryRun: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type ManagePlanResult = {
|
|
25
|
+
plan: ManagePlan;
|
|
26
|
+
} | {
|
|
27
|
+
error: string;
|
|
28
|
+
};
|
|
29
|
+
export declare function parseManageArgs(argv: readonly string[]): ParsedManageArgs | {
|
|
30
|
+
error: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function collectManagePlan(operation: ManageOperation, rawIds: string | undefined, homeDir?: string): ManagePlanResult;
|
|
33
|
+
export declare function applyManagePlan(plan: ManagePlan, homeDir?: string): void;
|
|
34
|
+
export declare function runManage(parsed: ParsedManageArgs, opts?: {
|
|
35
|
+
homeDir?: string;
|
|
36
|
+
}): {
|
|
37
|
+
exitCode: number;
|
|
38
|
+
plan?: ManagePlan;
|
|
39
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit lifecycle management for `~/.trellis/managed.yaml`.
|
|
3
|
+
*
|
|
4
|
+
* Onboarding stays additive: omission must never silently revoke Trellis's
|
|
5
|
+
* write authorization. Exact replacement and subtraction live here so they
|
|
6
|
+
* are deliberate, visible in shell history, dry-runnable, and recoverable.
|
|
7
|
+
*/
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { loadCanonicalSource } from "../core/canonical.js";
|
|
11
|
+
import { ALL_AGENTS } from "../core/types.js";
|
|
12
|
+
import { openBackupSession } from "../lib/backup.js";
|
|
13
|
+
function stableAgents(ids) {
|
|
14
|
+
const selected = new Set(ids);
|
|
15
|
+
return ALL_AGENTS.filter((id) => selected.has(id));
|
|
16
|
+
}
|
|
17
|
+
function parseAgentList(raw, allowNone) {
|
|
18
|
+
if (raw === undefined || raw.trim() === "") {
|
|
19
|
+
return { error: `agent list is required — use a comma-separated subset of ${ALL_AGENTS.join(", ")}${allowNone ? ", or none" : ""}` };
|
|
20
|
+
}
|
|
21
|
+
if (raw.trim().toLowerCase() === "none") {
|
|
22
|
+
return allowNone ? { agents: [] } : { error: '"none" is valid only for `trellis manage set none`' };
|
|
23
|
+
}
|
|
24
|
+
const tokens = raw.split(",").map((token) => token.trim()).filter(Boolean);
|
|
25
|
+
const invalid = tokens.filter((token) => !ALL_AGENTS.includes(token));
|
|
26
|
+
if (invalid.length > 0) {
|
|
27
|
+
return { error: `unknown agent id${invalid.length === 1 ? "" : "s"}: ${invalid.join(", ")} — expected ${ALL_AGENTS.join(", ")}` };
|
|
28
|
+
}
|
|
29
|
+
return { agents: stableAgents(tokens) };
|
|
30
|
+
}
|
|
31
|
+
export function parseManageArgs(argv) {
|
|
32
|
+
const [rawOperation, ...rest] = argv;
|
|
33
|
+
if (rawOperation !== "list" && rawOperation !== "set" && rawOperation !== "add" && rawOperation !== "remove") {
|
|
34
|
+
return { error: `unknown manage operation "${rawOperation ?? "(none)"}" — expected list, set, add, or remove` };
|
|
35
|
+
}
|
|
36
|
+
const unknownFlags = rest.filter((arg) => arg.startsWith("--") && arg !== "--json" && arg !== "--dry-run");
|
|
37
|
+
if (unknownFlags.length > 0)
|
|
38
|
+
return { error: `unknown manage flag "${unknownFlags[0]}"` };
|
|
39
|
+
const positional = rest.filter((arg) => !arg.startsWith("--"));
|
|
40
|
+
if (rawOperation === "list") {
|
|
41
|
+
if (positional.length > 0)
|
|
42
|
+
return { error: "manage list accepts no agent list" };
|
|
43
|
+
}
|
|
44
|
+
else if (positional.length !== 1) {
|
|
45
|
+
return { error: `manage ${rawOperation} requires exactly one comma-separated agent list` };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
operation: rawOperation,
|
|
49
|
+
ids: positional[0],
|
|
50
|
+
json: rest.includes("--json"),
|
|
51
|
+
dryRun: rest.includes("--dry-run"),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function collectManagePlan(operation, rawIds, homeDir = homedir()) {
|
|
55
|
+
const current = stableAgents(loadCanonicalSource(homeDir).managedAgents);
|
|
56
|
+
if (operation === "list") {
|
|
57
|
+
return { plan: { operation, current, desired: current, added: [], removed: [], changed: false } };
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseAgentList(rawIds, operation === "set");
|
|
60
|
+
if ("error" in parsed)
|
|
61
|
+
return parsed;
|
|
62
|
+
const requested = new Set(parsed.agents);
|
|
63
|
+
const desired = operation === "set"
|
|
64
|
+
? stableAgents(requested)
|
|
65
|
+
: operation === "add"
|
|
66
|
+
? stableAgents([...current, ...requested])
|
|
67
|
+
: stableAgents(current.filter((id) => !requested.has(id)));
|
|
68
|
+
const currentSet = new Set(current);
|
|
69
|
+
const desiredSet = new Set(desired);
|
|
70
|
+
const added = desired.filter((id) => !currentSet.has(id));
|
|
71
|
+
const removed = current.filter((id) => !desiredSet.has(id));
|
|
72
|
+
return {
|
|
73
|
+
plan: {
|
|
74
|
+
operation,
|
|
75
|
+
current,
|
|
76
|
+
desired,
|
|
77
|
+
added,
|
|
78
|
+
removed,
|
|
79
|
+
changed: added.length > 0 || removed.length > 0,
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function applyManagePlan(plan, homeDir = homedir()) {
|
|
84
|
+
if (!plan.changed || plan.operation === "list")
|
|
85
|
+
return;
|
|
86
|
+
const session = openBackupSession(homeDir, `manage-${plan.operation}`);
|
|
87
|
+
session.writeFile(join(homeDir, ".trellis", "managed.yaml"), `agents: [${plan.desired.join(", ")}]\n`);
|
|
88
|
+
session.finalize();
|
|
89
|
+
}
|
|
90
|
+
function displayAgents(agents) {
|
|
91
|
+
return agents.length > 0 ? agents.join(", ") : "(none)";
|
|
92
|
+
}
|
|
93
|
+
export function runManage(parsed, opts = {}) {
|
|
94
|
+
const homeDir = opts.homeDir ?? homedir();
|
|
95
|
+
let result;
|
|
96
|
+
try {
|
|
97
|
+
result = collectManagePlan(parsed.operation, parsed.ids, homeDir);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
101
|
+
return { exitCode: 1 };
|
|
102
|
+
}
|
|
103
|
+
if ("error" in result) {
|
|
104
|
+
console.error(result.error);
|
|
105
|
+
return { exitCode: 1 };
|
|
106
|
+
}
|
|
107
|
+
const { plan } = result;
|
|
108
|
+
if (!parsed.dryRun)
|
|
109
|
+
applyManagePlan(plan, homeDir);
|
|
110
|
+
if (parsed.json) {
|
|
111
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
112
|
+
}
|
|
113
|
+
else if (parsed.operation === "list") {
|
|
114
|
+
console.log(`managed agents: ${displayAgents(plan.current)}`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.log(`${parsed.dryRun ? "[dry run] " : ""}managed agents: ${displayAgents(plan.current)} -> ${displayAgents(plan.desired)}`);
|
|
118
|
+
if (!plan.changed)
|
|
119
|
+
console.log(" no change");
|
|
120
|
+
if (plan.added.length > 0)
|
|
121
|
+
console.log(` added: ${plan.added.join(", ")}`);
|
|
122
|
+
if (plan.removed.length > 0)
|
|
123
|
+
console.log(` detached: ${plan.removed.join(", ")} (native state preserved)`);
|
|
124
|
+
}
|
|
125
|
+
return { exitCode: 0, plan };
|
|
126
|
+
}
|
package/dist/commands/mcp.d.ts
CHANGED
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* gated — remove), and reports what happened. Kept as a separate
|
|
6
6
|
* command from bare `trellis sync` (skills/instructions) since the two
|
|
7
7
|
* have different write mechanisms entirely, not because MCP removal is
|
|
8
|
-
* unsafe (see `src/lib/mcpOwnership.ts`). `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* unsafe (see `src/lib/mcpOwnership.ts`). `add`/`remove` mutate the
|
|
9
|
+
* canonical source and then run the same sync `mcp sync` performs, so
|
|
10
|
+
* one command both edits the list and maps it onto the managed agents
|
|
11
|
+
* (removal propagates through the ownership ledger). `list` is
|
|
12
|
+
* read-only. All three are an alternative to hand-editing
|
|
13
|
+
* `~/.trellis/mcp/servers.yaml`.
|
|
12
14
|
*/
|
|
13
15
|
import { type ServersYamlWriteResult } from "../core/canonical.js";
|
|
14
16
|
import type { AdapterPlanItem } from "../core/adapter.js";
|
|
15
|
-
import type { AgentId, McpServerDef, Transport } from "../core/types.js";
|
|
17
|
+
import type { AgentId, McpAuthMode, McpConfig, McpServerDef, Transport } from "../core/types.js";
|
|
16
18
|
import { type BackupSession } from "../lib/backup.js";
|
|
17
19
|
export interface RunMcpSyncOptions {
|
|
18
20
|
json?: boolean;
|
|
@@ -24,6 +26,9 @@ export interface RunMcpSyncOptions {
|
|
|
24
26
|
/** Onboard-only seam — see RunSyncOptions.managedAgents. Never a CLI
|
|
25
27
|
* flag. */
|
|
26
28
|
managedAgents?: readonly AgentId[];
|
|
29
|
+
/** Onboard-only canonical MCP override used to preview a route selection
|
|
30
|
+
* before writing it to servers.yaml. */
|
|
31
|
+
mcp?: McpConfig;
|
|
27
32
|
/** Onboard-only seam — see RunSyncOptions.backupSession. Never a CLI
|
|
28
33
|
* flag. */
|
|
29
34
|
backupSession?: BackupSession;
|
|
@@ -47,6 +52,7 @@ export declare function printReport(report: McpSyncReport, dryRun: boolean): voi
|
|
|
47
52
|
export interface McpListEntry {
|
|
48
53
|
name: string;
|
|
49
54
|
transport: Transport;
|
|
55
|
+
auth?: McpAuthMode;
|
|
50
56
|
enabled: boolean;
|
|
51
57
|
agents: readonly AgentId[];
|
|
52
58
|
command?: string;
|
|
@@ -75,6 +81,7 @@ export declare function runMcpList(opts?: {
|
|
|
75
81
|
* command's own inline flag parsing in src/cli.ts. */
|
|
76
82
|
export interface McpAddRawArgs {
|
|
77
83
|
transport?: string;
|
|
84
|
+
auth?: string;
|
|
78
85
|
command?: string;
|
|
79
86
|
/** Comma-separated. */
|
|
80
87
|
args?: string;
|
|
@@ -107,29 +114,85 @@ export interface McpAddPlan {
|
|
|
107
114
|
* always refuses, ever (design.md D4).
|
|
108
115
|
*/
|
|
109
116
|
export declare function collectMcpAddPlan(name: string | undefined, raw: McpAddRawArgs, homeDir?: string): McpAddPlan;
|
|
110
|
-
export
|
|
111
|
-
export
|
|
117
|
+
export type McpSetAuthAction = "updated" | "already-set" | "not-found" | "invalid-input";
|
|
118
|
+
export interface McpSetAuthPlan {
|
|
119
|
+
name: string;
|
|
120
|
+
auth: "oauth" | "none";
|
|
121
|
+
action: McpSetAuthAction;
|
|
122
|
+
detail: string;
|
|
123
|
+
def?: McpServerDef;
|
|
124
|
+
}
|
|
125
|
+
export declare function collectMcpSetAuthPlan(name: string | undefined, auth: string | undefined, homeDir?: string): McpSetAuthPlan;
|
|
126
|
+
export declare function runMcpSetAuth(name: string | undefined, auth: string | undefined, opts?: {
|
|
112
127
|
homeDir?: string;
|
|
113
128
|
json?: boolean;
|
|
114
129
|
dryRun?: boolean;
|
|
115
130
|
}): {
|
|
116
131
|
exitCode: number;
|
|
117
132
|
};
|
|
133
|
+
export declare function applyMcpAddPlan(plan: McpAddPlan, homeDir?: string, backup?: BackupSession): ServersYamlWriteResult;
|
|
134
|
+
export interface McpAddOutcome {
|
|
135
|
+
plan: McpAddPlan;
|
|
136
|
+
writeError?: string;
|
|
137
|
+
sync?: McpSyncReport;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The apply-and-cascade-sync orchestration `runMcpAdd` used to do inline
|
|
141
|
+
* and only expose via a console.log'd payload — split out so a
|
|
142
|
+
* non-CLI caller (trellis-gui's sidecar) can get the same structured
|
|
143
|
+
* result a JSON-mode CLI run prints, without scraping stdout or
|
|
144
|
+
* re-deriving the same sequencing itself (trellis-gui design.md
|
|
145
|
+
* Decision 1). Behavior-preserving: `runMcpAdd` below is now a thin
|
|
146
|
+
* wrapper over this plus its own printing, same as every other command
|
|
147
|
+
* in this file already splits compute from CLI I/O.
|
|
148
|
+
*
|
|
149
|
+
* Opens its own backup session around the direct `servers.yaml` write
|
|
150
|
+
* when the caller doesn't already have one open — this canonical write
|
|
151
|
+
* used to be the one path in this file that bypassed backup/rollback
|
|
152
|
+
* entirely (trellis-gui tasks.md 3.5 found this empirically: a real `mcp
|
|
153
|
+
* add` left zero trace in `trellis rollback --list`). Mirrors the
|
|
154
|
+
* `ownSession` pattern `collectMcpSyncReport` below already uses for
|
|
155
|
+
* exactly the same reason.
|
|
156
|
+
*/
|
|
157
|
+
export declare function applyMcpAddWithSync(plan: McpAddPlan, opts?: {
|
|
158
|
+
homeDir?: string;
|
|
159
|
+
dryRun?: boolean;
|
|
160
|
+
backupSession?: BackupSession;
|
|
161
|
+
}): Promise<McpAddOutcome>;
|
|
162
|
+
export declare function runMcpAdd(name: string | undefined, raw: McpAddRawArgs, opts?: {
|
|
163
|
+
homeDir?: string;
|
|
164
|
+
json?: boolean;
|
|
165
|
+
dryRun?: boolean;
|
|
166
|
+
}): Promise<{
|
|
167
|
+
exitCode: number;
|
|
168
|
+
}>;
|
|
118
169
|
export type McpRemoveAction = "removed" | "not-found";
|
|
119
170
|
export interface McpRemovePlan {
|
|
120
171
|
name: string;
|
|
121
172
|
action: McpRemoveAction;
|
|
122
173
|
}
|
|
123
|
-
/** Canonical
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
174
|
+
/** Canonical plan only — `runMcpRemove` follows a real removal with the
|
|
175
|
+
* same sync `mcp sync` performs, which propagates the deletion to every
|
|
176
|
+
* agent whose entry the ownership ledger proves Trellis wrote
|
|
177
|
+
* (trellis-mcp-lifecycle-parity, roadmap P14). */
|
|
127
178
|
export declare function collectMcpRemovePlan(name: string, homeDir?: string): McpRemovePlan;
|
|
128
|
-
export declare function applyMcpRemovePlan(plan: McpRemovePlan, homeDir?: string): ServersYamlWriteResult;
|
|
179
|
+
export declare function applyMcpRemovePlan(plan: McpRemovePlan, homeDir?: string, backup?: BackupSession): ServersYamlWriteResult;
|
|
180
|
+
export interface McpRemoveOutcome {
|
|
181
|
+
plan: McpRemovePlan;
|
|
182
|
+
writeError?: string;
|
|
183
|
+
sync?: McpSyncReport;
|
|
184
|
+
}
|
|
185
|
+
/** See `applyMcpAddWithSync`'s doc comment — same split, same reason, same
|
|
186
|
+
* own-backup-session fix. */
|
|
187
|
+
export declare function applyMcpRemoveWithSync(plan: McpRemovePlan, opts?: {
|
|
188
|
+
homeDir?: string;
|
|
189
|
+
dryRun?: boolean;
|
|
190
|
+
backupSession?: BackupSession;
|
|
191
|
+
}): Promise<McpRemoveOutcome>;
|
|
129
192
|
export declare function runMcpRemove(name: string, opts?: {
|
|
130
193
|
homeDir?: string;
|
|
131
194
|
json?: boolean;
|
|
132
195
|
dryRun?: boolean;
|
|
133
|
-
}): {
|
|
196
|
+
}): Promise<{
|
|
134
197
|
exitCode: number;
|
|
135
|
-
}
|
|
198
|
+
}>;
|
package/dist/commands/mcp.js
CHANGED
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* gated — remove), and reports what happened. Kept as a separate
|
|
6
6
|
* command from bare `trellis sync` (skills/instructions) since the two
|
|
7
7
|
* have different write mechanisms entirely, not because MCP removal is
|
|
8
|
-
* unsafe (see `src/lib/mcpOwnership.ts`). `
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* unsafe (see `src/lib/mcpOwnership.ts`). `add`/`remove` mutate the
|
|
9
|
+
* canonical source and then run the same sync `mcp sync` performs, so
|
|
10
|
+
* one command both edits the list and maps it onto the managed agents
|
|
11
|
+
* (removal propagates through the ownership ledger). `list` is
|
|
12
|
+
* read-only. All three are an alternative to hand-editing
|
|
13
|
+
* `~/.trellis/mcp/servers.yaml`.
|
|
12
14
|
*/
|
|
13
15
|
import { homedir } from "node:os";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
14
17
|
import { join } from "node:path";
|
|
15
18
|
import { loadCanonicalSource, upsertServerYaml, removeServerYaml } from "../core/canonical.js";
|
|
16
19
|
import { ALL_AGENTS, resolveScope } from "../core/types.js";
|
|
@@ -18,12 +21,16 @@ import { ClaudeCodeAdapter } from "../adapters/claude-code.js";
|
|
|
18
21
|
import { CodexAdapter } from "../adapters/codex.js";
|
|
19
22
|
import { KiroAdapter } from "../adapters/kiro.js";
|
|
20
23
|
import { PiAdapter } from "../adapters/pi.js";
|
|
24
|
+
import { KimiCodeAdapter } from "../adapters/kimi-code.js";
|
|
25
|
+
import { ZcodeAdapter } from "../adapters/zcode.js";
|
|
21
26
|
import { openBackupSession } from "../lib/backup.js";
|
|
22
27
|
const ADAPTER_FACTORY = {
|
|
23
28
|
"claude-code": (homeDir) => new ClaudeCodeAdapter(homeDir),
|
|
24
29
|
codex: (homeDir) => new CodexAdapter(homeDir),
|
|
25
30
|
kiro: (homeDir) => new KiroAdapter(homeDir),
|
|
26
31
|
pi: (homeDir) => new PiAdapter(homeDir),
|
|
32
|
+
"kimi-code": (homeDir) => new KimiCodeAdapter(homeDir),
|
|
33
|
+
zcode: (homeDir) => new ZcodeAdapter(homeDir),
|
|
27
34
|
};
|
|
28
35
|
/** Only agents in `canonical.managedAgents` — see src/commands/sync.ts's
|
|
29
36
|
* own copy of this same restriction (trellis-managed-agents). */
|
|
@@ -33,7 +40,11 @@ function buildAdapters(homeDir, managedAgents) {
|
|
|
33
40
|
export async function collectMcpSyncReport(opts = {}) {
|
|
34
41
|
const homeDir = opts.homeDir ?? homedir();
|
|
35
42
|
const loaded = loadCanonicalSource(homeDir);
|
|
36
|
-
const canonical =
|
|
43
|
+
const canonical = {
|
|
44
|
+
...loaded,
|
|
45
|
+
...(opts.managedAgents ? { managedAgents: opts.managedAgents } : {}),
|
|
46
|
+
...(opts.mcp ? { mcp: opts.mcp } : {}),
|
|
47
|
+
};
|
|
37
48
|
const reports = [];
|
|
38
49
|
const ownSession = !opts.dryRun && !opts.backupSession ? openBackupSession(homeDir, "mcp-sync") : undefined;
|
|
39
50
|
const backup = opts.backupSession ?? ownSession;
|
|
@@ -107,6 +118,7 @@ export function collectMcpListPlan(homeDir = homedir()) {
|
|
|
107
118
|
return Object.entries(canonical.mcp.servers).map(([name, def]) => ({
|
|
108
119
|
name,
|
|
109
120
|
transport: def.transport,
|
|
121
|
+
auth: def.auth,
|
|
110
122
|
enabled: def.enabled ?? true,
|
|
111
123
|
agents: resolveScope(def.agents, canonical.managedAgents),
|
|
112
124
|
command: def.command,
|
|
@@ -136,7 +148,7 @@ export function runMcpList(opts = {}) {
|
|
|
136
148
|
else {
|
|
137
149
|
for (const e of entries) {
|
|
138
150
|
const scope = e.agents.length > 0 ? e.agents.join(", ") : "(no managed agent reaches it)";
|
|
139
|
-
console.log(`${e.name} (${e.transport})${e.enabled ? "" : " [disabled]"} — ${scope}`);
|
|
151
|
+
console.log(`${e.name} (${e.transport}${e.auth === "oauth" ? ", oauth" : ""})${e.enabled ? "" : " [disabled]"} — ${scope}`);
|
|
140
152
|
if (e.env && e.env.length > 0)
|
|
141
153
|
console.log(` env: ${e.env.join(", ")} (values never read/printed)`);
|
|
142
154
|
if (e.staticEnv)
|
|
@@ -152,6 +164,7 @@ export function parseMcpAddArgs(argv) {
|
|
|
152
164
|
};
|
|
153
165
|
return {
|
|
154
166
|
transport: flag("--transport"),
|
|
167
|
+
auth: flag("--auth"),
|
|
155
168
|
command: flag("--command"),
|
|
156
169
|
args: flag("--args"),
|
|
157
170
|
url: flag("--url"),
|
|
@@ -202,6 +215,12 @@ export function collectMcpAddPlan(name, raw, homeDir = homedir()) {
|
|
|
202
215
|
if ((transport === "http" || transport === "sse") && !raw.url) {
|
|
203
216
|
return { name, action: "invalid-input", detail: `--transport ${transport} requires --url` };
|
|
204
217
|
}
|
|
218
|
+
if (raw.auth !== undefined && raw.auth !== "oauth") {
|
|
219
|
+
return { name, action: "invalid-input", detail: `--auth must be "oauth" when provided (got ${raw.auth})` };
|
|
220
|
+
}
|
|
221
|
+
if (raw.auth === "oauth" && transport === "stdio") {
|
|
222
|
+
return { name, action: "invalid-input", detail: "--auth oauth requires an http or sse MCP server" };
|
|
223
|
+
}
|
|
205
224
|
const agentsList = parseCsv(raw.agents);
|
|
206
225
|
if (agentsList) {
|
|
207
226
|
for (const id of agentsList) {
|
|
@@ -215,6 +234,7 @@ export function collectMcpAddPlan(name, raw, homeDir = homedir()) {
|
|
|
215
234
|
}
|
|
216
235
|
const def = {
|
|
217
236
|
transport,
|
|
237
|
+
...(raw.auth === "oauth" ? { auth: "oauth" } : {}),
|
|
218
238
|
...(raw.command ? { command: raw.command } : {}),
|
|
219
239
|
...(parseCsv(raw.args) ? { args: parseCsv(raw.args) } : {}),
|
|
220
240
|
...(raw.url ? { url: raw.url } : {}),
|
|
@@ -234,52 +254,164 @@ export function collectMcpAddPlan(name, raw, homeDir = homedir()) {
|
|
|
234
254
|
}
|
|
235
255
|
return { name, action: "create", detail: "will add to servers.yaml", def };
|
|
236
256
|
}
|
|
237
|
-
export function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
257
|
+
export function collectMcpSetAuthPlan(name, auth, homeDir = homedir()) {
|
|
258
|
+
const mode = auth === "oauth" || auth === "none" ? auth : undefined;
|
|
259
|
+
if (!name)
|
|
260
|
+
return { name: "(none)", auth: (mode ?? "none"), action: "invalid-input", detail: "usage: trellis mcp set <name> --auth oauth|none" };
|
|
261
|
+
if (!mode)
|
|
262
|
+
return { name, auth: "none", action: "invalid-input", detail: `--auth must be "oauth" or "none" (got ${auth ?? "(missing)"})` };
|
|
263
|
+
const canonical = loadCanonicalSource(homeDir);
|
|
264
|
+
const current = canonical.mcp.servers[name];
|
|
265
|
+
if (!current)
|
|
266
|
+
return { name, auth: mode, action: "not-found", detail: `no canonical MCP server named "${name}"` };
|
|
267
|
+
if (mode === "oauth" && !current.url) {
|
|
268
|
+
return { name, auth: mode, action: "invalid-input", detail: "auth: oauth requires an http or sse MCP server" };
|
|
269
|
+
}
|
|
270
|
+
const next = mode === "oauth" ? { ...current, auth: "oauth" } : (() => {
|
|
271
|
+
const { auth: _auth, ...withoutAuth } = current;
|
|
272
|
+
return withoutAuth;
|
|
273
|
+
})();
|
|
274
|
+
if (JSON.stringify(current) === JSON.stringify(next))
|
|
275
|
+
return { name, auth: mode, action: "already-set", detail: `auth is already ${mode}`, def: next };
|
|
276
|
+
return { name, auth: mode, action: "updated", detail: `set auth to ${mode}`, def: next };
|
|
241
277
|
}
|
|
242
|
-
export function
|
|
278
|
+
export function runMcpSetAuth(name, auth, opts = {}) {
|
|
243
279
|
const homeDir = opts.homeDir ?? homedir();
|
|
244
280
|
let plan;
|
|
245
281
|
try {
|
|
246
|
-
plan =
|
|
282
|
+
plan = collectMcpSetAuthPlan(name, auth, homeDir);
|
|
247
283
|
}
|
|
248
284
|
catch (err) {
|
|
249
285
|
console.error(err instanceof Error ? err.message : String(err));
|
|
250
286
|
return { exitCode: 1 };
|
|
251
287
|
}
|
|
252
288
|
let writeError;
|
|
253
|
-
if (!opts.dryRun && plan.action === "
|
|
254
|
-
const result =
|
|
289
|
+
if (!opts.dryRun && plan.action === "updated" && plan.def) {
|
|
290
|
+
const result = upsertServerYaml(serversYamlPath(homeDir), plan.name, plan.def);
|
|
255
291
|
if (!result.ok)
|
|
256
292
|
writeError = result.error;
|
|
257
293
|
}
|
|
258
294
|
if (opts.json) {
|
|
259
295
|
console.log(JSON.stringify(writeError ? { ...plan, writeError } : plan, null, 2));
|
|
260
296
|
}
|
|
297
|
+
else {
|
|
298
|
+
console.log(`${opts.dryRun ? "[dry run] " : ""}mcp set ${plan.name}`);
|
|
299
|
+
console.log(` [${plan.action}] ${plan.detail}`);
|
|
300
|
+
if (writeError)
|
|
301
|
+
console.error(` write failed: ${writeError}`);
|
|
302
|
+
}
|
|
303
|
+
return { exitCode: ["invalid-input", "not-found"].includes(plan.action) || Boolean(writeError) ? 1 : 0 };
|
|
304
|
+
}
|
|
305
|
+
export function applyMcpAddPlan(plan, homeDir = homedir(), backup) {
|
|
306
|
+
if (plan.action !== "create" || !plan.def)
|
|
307
|
+
return { ok: true };
|
|
308
|
+
return upsertServerYaml(serversYamlPath(homeDir), plan.name, plan.def, backup);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* The apply-and-cascade-sync orchestration `runMcpAdd` used to do inline
|
|
312
|
+
* and only expose via a console.log'd payload — split out so a
|
|
313
|
+
* non-CLI caller (trellis-gui's sidecar) can get the same structured
|
|
314
|
+
* result a JSON-mode CLI run prints, without scraping stdout or
|
|
315
|
+
* re-deriving the same sequencing itself (trellis-gui design.md
|
|
316
|
+
* Decision 1). Behavior-preserving: `runMcpAdd` below is now a thin
|
|
317
|
+
* wrapper over this plus its own printing, same as every other command
|
|
318
|
+
* in this file already splits compute from CLI I/O.
|
|
319
|
+
*
|
|
320
|
+
* Opens its own backup session around the direct `servers.yaml` write
|
|
321
|
+
* when the caller doesn't already have one open — this canonical write
|
|
322
|
+
* used to be the one path in this file that bypassed backup/rollback
|
|
323
|
+
* entirely (trellis-gui tasks.md 3.5 found this empirically: a real `mcp
|
|
324
|
+
* add` left zero trace in `trellis rollback --list`). Mirrors the
|
|
325
|
+
* `ownSession` pattern `collectMcpSyncReport` below already uses for
|
|
326
|
+
* exactly the same reason.
|
|
327
|
+
*/
|
|
328
|
+
export async function applyMcpAddWithSync(plan, opts = {}) {
|
|
329
|
+
const homeDir = opts.homeDir ?? homedir();
|
|
330
|
+
const ownSession = !opts.dryRun && !opts.backupSession && plan.action === "create" ? openBackupSession(homeDir, "mcp-add") : undefined;
|
|
331
|
+
const session = opts.backupSession ?? ownSession;
|
|
332
|
+
let writeError;
|
|
333
|
+
if (!opts.dryRun && plan.action === "create") {
|
|
334
|
+
const result = applyMcpAddPlan(plan, homeDir, session);
|
|
335
|
+
if (!result.ok)
|
|
336
|
+
writeError = result.error;
|
|
337
|
+
}
|
|
338
|
+
ownSession?.finalize();
|
|
339
|
+
const planFailed = plan.action === "conflict" || plan.action === "invalid-input" || Boolean(writeError);
|
|
340
|
+
let syncReport;
|
|
341
|
+
if (!planFailed && plan.action === "create" && existsSync(join(homeDir, ".trellis"))) {
|
|
342
|
+
syncReport = await collectMcpSyncReport({ homeDir, dryRun: opts.dryRun });
|
|
343
|
+
}
|
|
344
|
+
return { plan, ...(writeError ? { writeError } : {}), ...(syncReport ? { sync: syncReport } : {}) };
|
|
345
|
+
}
|
|
346
|
+
export async function runMcpAdd(name, raw, opts = {}) {
|
|
347
|
+
const homeDir = opts.homeDir ?? homedir();
|
|
348
|
+
let plan;
|
|
349
|
+
try {
|
|
350
|
+
plan = collectMcpAddPlan(name, raw, homeDir);
|
|
351
|
+
}
|
|
352
|
+
catch (err) {
|
|
353
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
354
|
+
return { exitCode: 1 };
|
|
355
|
+
}
|
|
356
|
+
let outcome;
|
|
357
|
+
try {
|
|
358
|
+
outcome = await applyMcpAddWithSync(plan, { homeDir, dryRun: opts.dryRun });
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
362
|
+
return { exitCode: 1 };
|
|
363
|
+
}
|
|
364
|
+
const { writeError, sync: syncReport } = outcome;
|
|
365
|
+
if (opts.json) {
|
|
366
|
+
const payload = writeError ? { ...plan, writeError } : plan;
|
|
367
|
+
console.log(JSON.stringify(syncReport ? { ...payload, sync: syncReport } : payload, null, 2));
|
|
368
|
+
}
|
|
261
369
|
else {
|
|
262
370
|
console.log(`${opts.dryRun ? "[dry run] " : ""}mcp add ${plan.name}`);
|
|
263
371
|
console.log(` [${plan.action}] ${plan.detail}`);
|
|
264
372
|
if (writeError)
|
|
265
373
|
console.error(` write failed: ${writeError}`);
|
|
374
|
+
if (syncReport)
|
|
375
|
+
printReport(syncReport, opts.dryRun ?? false);
|
|
266
376
|
}
|
|
267
|
-
|
|
377
|
+
const planFailed = plan.action === "conflict" || plan.action === "invalid-input" || Boolean(writeError);
|
|
378
|
+
const syncConflict = syncReport?.reports.some((r) => r.items.some((i) => i.action === "conflict")) ?? false;
|
|
379
|
+
return { exitCode: planFailed || syncConflict ? 1 : 0 };
|
|
268
380
|
}
|
|
269
|
-
/** Canonical
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
381
|
+
/** Canonical plan only — `runMcpRemove` follows a real removal with the
|
|
382
|
+
* same sync `mcp sync` performs, which propagates the deletion to every
|
|
383
|
+
* agent whose entry the ownership ledger proves Trellis wrote
|
|
384
|
+
* (trellis-mcp-lifecycle-parity, roadmap P14). */
|
|
273
385
|
export function collectMcpRemovePlan(name, homeDir = homedir()) {
|
|
274
386
|
const canonical = loadCanonicalSource(homeDir);
|
|
275
387
|
return { name, action: canonical.mcp.servers[name] ? "removed" : "not-found" };
|
|
276
388
|
}
|
|
277
|
-
export function applyMcpRemovePlan(plan, homeDir = homedir()) {
|
|
389
|
+
export function applyMcpRemovePlan(plan, homeDir = homedir(), backup) {
|
|
278
390
|
if (plan.action !== "removed")
|
|
279
391
|
return { ok: true };
|
|
280
|
-
return removeServerYaml(serversYamlPath(homeDir), plan.name);
|
|
392
|
+
return removeServerYaml(serversYamlPath(homeDir), plan.name, backup);
|
|
393
|
+
}
|
|
394
|
+
/** See `applyMcpAddWithSync`'s doc comment — same split, same reason, same
|
|
395
|
+
* own-backup-session fix. */
|
|
396
|
+
export async function applyMcpRemoveWithSync(plan, opts = {}) {
|
|
397
|
+
const homeDir = opts.homeDir ?? homedir();
|
|
398
|
+
const ownSession = !opts.dryRun && !opts.backupSession && plan.action === "removed" ? openBackupSession(homeDir, "mcp-remove") : undefined;
|
|
399
|
+
const session = opts.backupSession ?? ownSession;
|
|
400
|
+
let writeError;
|
|
401
|
+
if (!opts.dryRun && plan.action === "removed") {
|
|
402
|
+
const result = applyMcpRemovePlan(plan, homeDir, session);
|
|
403
|
+
if (!result.ok)
|
|
404
|
+
writeError = result.error;
|
|
405
|
+
}
|
|
406
|
+
ownSession?.finalize();
|
|
407
|
+
const planFailed = plan.action === "not-found" || Boolean(writeError);
|
|
408
|
+
let syncReport;
|
|
409
|
+
if (!planFailed && existsSync(join(homeDir, ".trellis"))) {
|
|
410
|
+
syncReport = await collectMcpSyncReport({ homeDir, dryRun: opts.dryRun });
|
|
411
|
+
}
|
|
412
|
+
return { plan, ...(writeError ? { writeError } : {}), ...(syncReport ? { sync: syncReport } : {}) };
|
|
281
413
|
}
|
|
282
|
-
export function runMcpRemove(name, opts = {}) {
|
|
414
|
+
export async function runMcpRemove(name, opts = {}) {
|
|
283
415
|
const homeDir = opts.homeDir ?? homedir();
|
|
284
416
|
let plan;
|
|
285
417
|
try {
|
|
@@ -289,14 +421,19 @@ export function runMcpRemove(name, opts = {}) {
|
|
|
289
421
|
console.error(err instanceof Error ? err.message : String(err));
|
|
290
422
|
return { exitCode: 1 };
|
|
291
423
|
}
|
|
292
|
-
let
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
424
|
+
let outcome;
|
|
425
|
+
try {
|
|
426
|
+
outcome = await applyMcpRemoveWithSync(plan, { homeDir, dryRun: opts.dryRun });
|
|
427
|
+
}
|
|
428
|
+
catch (err) {
|
|
429
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
430
|
+
return { exitCode: 1 };
|
|
297
431
|
}
|
|
432
|
+
const { writeError, sync: syncReport } = outcome;
|
|
433
|
+
const planFailed = plan.action === "not-found" || Boolean(writeError);
|
|
298
434
|
if (opts.json) {
|
|
299
|
-
|
|
435
|
+
const payload = writeError ? { ...plan, writeError } : plan;
|
|
436
|
+
console.log(JSON.stringify(syncReport ? { ...payload, sync: syncReport } : payload, null, 2));
|
|
300
437
|
}
|
|
301
438
|
else if (plan.action === "not-found") {
|
|
302
439
|
console.error(`"${name}" is not a canonical MCP server — nothing to remove.`);
|
|
@@ -306,6 +443,9 @@ export function runMcpRemove(name, opts = {}) {
|
|
|
306
443
|
}
|
|
307
444
|
else {
|
|
308
445
|
console.log(`${opts.dryRun ? "[dry run] " : ""}removed MCP server "${name}" from canonical source.`);
|
|
446
|
+
if (syncReport)
|
|
447
|
+
printReport(syncReport, opts.dryRun ?? false);
|
|
309
448
|
}
|
|
310
|
-
|
|
449
|
+
const syncConflict = syncReport?.reports.some((r) => r.items.some((i) => i.action === "conflict")) ?? false;
|
|
450
|
+
return { exitCode: planFailed || syncConflict ? 1 : 0 };
|
|
311
451
|
}
|
|
@@ -51,3 +51,9 @@ export declare function resolveGatewayUpstreams(agentId: AgentId, canonical: Pic
|
|
|
51
51
|
export declare function runMcpGateway(opts: RunMcpGatewayOptions): Promise<{
|
|
52
52
|
exitCode: number;
|
|
53
53
|
}>;
|
|
54
|
+
/** The first-class runtime entrypoint. mcp-gateway remains a compatibility
|
|
55
|
+
* alias for existing native agent config, but both edges use the same
|
|
56
|
+
* provider registry and backend construction path. */
|
|
57
|
+
export declare function runMcpRuntime(opts: RunMcpGatewayOptions): Promise<{
|
|
58
|
+
exitCode: number;
|
|
59
|
+
}>;
|