agent-trellis 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -38
- 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/cli.js +121 -21
- package/dist/commands/doctor.d.ts +7 -2
- package/dist/commands/doctor.js +38 -2
- package/dist/commands/init.d.ts +4 -2
- package/dist/commands/init.js +26 -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 +22 -1
- package/dist/commands/mcp.js +65 -2
- package/dist/commands/mcpGateway.d.ts +6 -0
- package/dist/commands/mcpGateway.js +34 -14
- package/dist/commands/mcpImport.d.ts +27 -0
- package/dist/commands/mcpImport.js +336 -0
- package/dist/commands/memory.d.ts +5 -3
- package/dist/commands/memory.js +12 -8
- package/dist/commands/migrate.d.ts +16 -5
- package/dist/commands/migrate.js +90 -17
- package/dist/commands/onboard.d.ts +74 -2
- package/dist/commands/onboard.js +458 -71
- package/dist/commands/rollback.js +4 -1
- package/dist/commands/skill.d.ts +17 -0
- package/dist/commands/skill.js +47 -2
- package/dist/commands/sync.js +2 -0
- 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 +7 -0
- package/dist/lib/backup.d.ts +2 -0
- package/dist/lib/backup.js +4 -0
- package/dist/lib/builtinSkills.d.ts +2 -0
- package/dist/lib/builtinSkills.js +4 -0
- package/dist/lib/cliMetadata.d.ts +1 -0
- package/dist/lib/cliMetadata.js +14 -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 +1 -0
- package/dist/lib/mcpMigrateRead.js +3 -0
- 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/runtimeControlProvider.d.ts +16 -0
- package/dist/lib/runtimeControlProvider.js +168 -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 +53 -218
- package/dist/pi-bridge/bundle.js +475 -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/kimi-code.d.ts +12 -0
- package/dist/probes/kimi-code.js +55 -0
- package/dist/sdk.d.ts +1 -1
- package/docs/architecture.md +224 -26
- package/docs/assets/agent-trellis-architecture-horizontal.svg +80 -0
- package/docs/assets/agent-trellis-architecture-vertical.svg +82 -0
- package/docs/assets/trellis-mark.svg +12 -0
- package/docs/getting-started.md +328 -35
- package/docs/overview.md +131 -0
- package/docs/overview.zh-CN.md +100 -0
- package/docs/research.md +59 -0
- package/docs/roadmap.md +9 -5
- package/package.json +3 -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
package/README.md
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
1
1
|
# Trellis
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/assets/trellis-mark.svg" width="88" alt="Trellis logo" />
|
|
5
|
+
</p>
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
defined once, adapted natively into Claude Code, Codex, Kiro, and the [pi coding
|
|
7
|
-
agent](https://github.com/earendil-works/pi), without drift and without a second
|
|
8
|
-
copy of anything.
|
|
7
|
+
<p align="center"><strong>Unified Code Agent Runtime</strong></p>
|
|
9
8
|
|
|
10
|
-
Trellis
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
Trellis gives Claude Code, Codex, Kiro, pi, and Kimi Code one Runtime for
|
|
10
|
+
Skills, MCP, Memory, and shared instructions.
|
|
11
|
+
|
|
12
|
+
> 中文:Trellis 是统一的 Code Agent Runtime,为多个 Code Agent 提供一致的
|
|
13
|
+
> Skill、MCP、Memory 与共享指令能力。
|
|
14
|
+
|
|
15
|
+
Trellis does not replace an Agent's native runtime. It manages one canonical
|
|
16
|
+
source and generates verified native or Runtime projections from it.
|
|
17
|
+
|
|
18
|
+
[Read the product overview →](docs/overview.md)
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g agent-trellis
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
trellis onboard --dry-run
|
|
32
|
+
trellis onboard
|
|
33
|
+
trellis doctor
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Use the dry-run to review migration source, managed Agents, MCP mode, and
|
|
37
|
+
Memory before applying. See [`docs/overview.md`](docs/overview.md) for the
|
|
38
|
+
short product explanation and [`docs/getting-started.md`](docs/getting-started.md)
|
|
39
|
+
for the complete workflow.
|
|
13
40
|
|
|
14
41
|
## Why
|
|
15
42
|
|
|
@@ -26,14 +53,6 @@ Trellis aligns with the emerging [`.agents Protocol`](https://dotagentsprotocol.
|
|
|
26
53
|
draft rather than inventing a sixth competing standard, and is likely its first
|
|
27
54
|
working implementation.
|
|
28
55
|
|
|
29
|
-
## Install
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
npm install -g agent-trellis
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Quick start
|
|
36
|
-
|
|
37
56
|
See [`docs/getting-started.md`](docs/getting-started.md) for the detailed
|
|
38
57
|
walkthrough — example output for each command, what each `migrate`/`sync`
|
|
39
58
|
conflict action means, and troubleshooting.
|
|
@@ -44,7 +63,7 @@ conflict action means, and troubleshooting.
|
|
|
44
63
|
trellis onboard
|
|
45
64
|
```
|
|
46
65
|
|
|
47
|
-
Creates canonical source, detects which of Claude Code/Codex/Kiro/pi are on
|
|
66
|
+
Creates canonical source, detects which of Claude Code/Codex/Kiro/pi/Kimi Code are on
|
|
48
67
|
this machine, then resolves two independent choices: a **migration source**
|
|
49
68
|
(read from — auto-selected if only one present agent has real content,
|
|
50
69
|
prompted with a numbered choice if more than one, skippable if starting
|
|
@@ -62,19 +81,63 @@ type by hand. If no agent is detected, it prints each supported agent's
|
|
|
62
81
|
real install command/URL and stops — it never installs anything on its own
|
|
63
82
|
initiative. Add `--dry-run` to preview the whole thing with zero writes.
|
|
64
83
|
|
|
84
|
+
Onboarding only adds to the existing managed set. To deliberately narrow or
|
|
85
|
+
replace Trellis's future write boundary without touching an agent's current
|
|
86
|
+
native state, use `trellis manage list|set|add|remove`; these mutations are
|
|
87
|
+
dry-runnable and backed by `trellis rollback`.
|
|
88
|
+
|
|
65
89
|
```
|
|
66
90
|
trellis onboard --agent claude-code --manage pi
|
|
67
91
|
```
|
|
68
92
|
|
|
93
|
+
## Trellis itself as a Skill
|
|
94
|
+
|
|
95
|
+
Trellis ships one package-owned Skill, `trellis-runtime`, with two layers:
|
|
96
|
+
|
|
97
|
+
| Layer | Who uses it | What it covers |
|
|
98
|
+
| --- | --- | --- |
|
|
99
|
+
| Takeover and migration | the operator, with Agent assistance | inspect, dry-run, choose a migration source, choose the managed boundary, select Skills/MCP/Memory, authorize OAuth, sync, audit, verify, and rollback |
|
|
100
|
+
| Runtime consumption | every managed Agent | discover and read canonical Skills, inspect shared Memory and global instructions, diagnose MCP, see managed Agents, and inspect or explicitly confirm task handoffs |
|
|
101
|
+
|
|
102
|
+
`trellis init` bootstraps this Skill into
|
|
103
|
+
`~/.trellis/skills/trellis-runtime/SKILL.md`. After an Agent is managed,
|
|
104
|
+
`trellis sync` delivers it in the Agent's native Skill location for native or
|
|
105
|
+
`both` delivery. For Runtime-only delivery, `SkillProvider` exposes the same
|
|
106
|
+
canonical Skill through the `trellis` MCP Runtime; Kimi Code uses
|
|
107
|
+
`trellis kimi` to prevent a second native Skill discovery path. This is one
|
|
108
|
+
canonical Skill, not a copied per-Agent variant.
|
|
109
|
+
|
|
110
|
+
Before takeover, the Skill cannot be consumed from an Agent that has not been
|
|
111
|
+
connected to Trellis yet; use the CLI and
|
|
112
|
+
[`docs/getting-started.md`](docs/getting-started.md#trellis-runtime-skill)
|
|
113
|
+
for the first-run path. After takeover, the Skill is the Agent's operating
|
|
114
|
+
guide for Trellis-managed capabilities. The MCP client may display names such
|
|
115
|
+
as `mcp__trellis__skills_search`; `mcp__` is client-generated, while Trellis's
|
|
116
|
+
portable names are `skills_search`, `memory_search`, `runtime_status`, and so
|
|
117
|
+
on.
|
|
118
|
+
|
|
69
119
|
**Or step by step** (what `onboard` is actually doing under the hood, if you
|
|
70
120
|
want to run any one stage on its own):
|
|
71
121
|
|
|
122
|
+
For standard MCP Router/client exports, use `trellis mcp import <json-file>`.
|
|
123
|
+
It reads `mcpServers` without changing the source, de-duplicates existing
|
|
124
|
+
servers, extracts credential-like environment values into the ignored local
|
|
125
|
+
secret file, converts supported `mcp-remote` Basic Auth entries to native HTTP,
|
|
126
|
+
and reports unsupported inline credentials or unavailable paths.
|
|
127
|
+
Always run it with `--dry-run` first; see the detailed importer rules in
|
|
128
|
+
[`docs/getting-started.md`](docs/getting-started.md).
|
|
129
|
+
|
|
130
|
+
After upgrading Trellis, refresh the package-owned Runtime Skill with
|
|
131
|
+
`trellis skill update-builtin --dry-run` followed by
|
|
132
|
+
`trellis skill update-builtin`; the operation is backed up and does not require
|
|
133
|
+
editing Agent-native files.
|
|
134
|
+
|
|
72
135
|
**Already using Claude Code, Codex, Kiro, or pi and want to migrate what you
|
|
73
136
|
already have?**
|
|
74
137
|
|
|
75
138
|
1. `trellis init` — creates `~/.trellis/` with a minimal skeleton (only what's
|
|
76
139
|
missing; never overwrites a file you already have) and tells you which of
|
|
77
|
-
the
|
|
140
|
+
the five supported agents it found on this machine.
|
|
78
141
|
2. `trellis migrate --from <agent>` — once per agent you already use. Copies
|
|
79
142
|
that agent's real skills and instructions into canonical source. Never
|
|
80
143
|
overwrites: an already-identical skill is reported and skipped, a genuine
|
|
@@ -86,8 +149,7 @@ already have?**
|
|
|
86
149
|
but not managed is never touched. Add `--dry-run` to preview first.
|
|
87
150
|
4. `trellis mcp sync` — distributes `~/.trellis/mcp/servers.yaml` (see
|
|
88
151
|
[`schema/servers.example.yaml`](schema/servers.example.yaml)) to every
|
|
89
|
-
managed agent's native MCP config
|
|
90
|
-
limitations).
|
|
152
|
+
managed agent's native MCP config, including ownership-safe removal.
|
|
91
153
|
5. `trellis secrets audit` — fails non-zero if any managed agent's real
|
|
92
154
|
config holds a literal credential or an unexpected env var name.
|
|
93
155
|
6. `trellis doctor` — read-only scan of every present agent's current state
|
|
@@ -118,15 +180,16 @@ restore with zero writes.
|
|
|
118
180
|
|
|
119
181
|
## Status
|
|
120
182
|
|
|
121
|
-
**Early, pre-1.0.**
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
183
|
+
**Early, pre-1.0.** The CLI commands above are implemented, unit-tested, and
|
|
184
|
+
verified against isolated Linux Docker scenarios (never a developer's own
|
|
185
|
+
dotfiles during development — see
|
|
186
|
+
[`docs/architecture.md`](docs/architecture.md)'s testing philosophy). The
|
|
187
|
+
sandbox matrix covers migration, steady-state management, runtime routing,
|
|
188
|
+
failure isolation, and expected conflicts. A separate agent image installs
|
|
189
|
+
real Codex, Claude Code, Kiro CLI, and pi binaries to verify their isolated
|
|
190
|
+
configuration behavior. `onboard` picks one agent as the migration base when
|
|
191
|
+
more than one is present; merging differing content across multiple agents
|
|
192
|
+
into one result is named future work, not built yet.
|
|
130
193
|
|
|
131
194
|
**Known limitations, honestly stated rather than discovered the hard way:**
|
|
132
195
|
- MCP servers are never spawned/handshake-tested by `trellis mcp sync` or
|
|
@@ -136,16 +199,12 @@ result is named future work, not built yet.
|
|
|
136
199
|
- The pi bridge extension (`trellis-pi-mcp-bridge`) has been verified to
|
|
137
200
|
load and register tools without erroring, never against a real LLM tool
|
|
138
201
|
call in production.
|
|
139
|
-
- Verification has run against real Docker containers and
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
issue.
|
|
144
|
-
- Automatic removal of an MCP server is deliberately unsupported (create/
|
|
145
|
-
repair only) until an ownership-tracking mechanism exists — see
|
|
146
|
-
`docs/roadmap.md`'s P2 note.
|
|
202
|
+
- Verification has run against real Docker containers and real isolated
|
|
203
|
+
Codex, Claude Code, Kiro CLI, and pi installs — not against a developer's
|
|
204
|
+
actual daily-use agent directories. If you hit something a clean-room
|
|
205
|
+
sandbox wouldn't have caught, please open an issue.
|
|
147
206
|
- `~/.trellis/backups/` has no automatic pruning yet — every `sync`/
|
|
148
|
-
`mcp sync`/`onboard` run that performs a real write adds one more run
|
|
207
|
+
`mcp sync`/`onboard`/`mcp import` run that performs a real write adds one more run
|
|
149
208
|
directory, with no cap. Delete old ones by hand for now.
|
|
150
209
|
|
|
151
210
|
A canonical MCP server can also declare `enabled: false` (kept defined,
|
|
@@ -13,6 +13,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
13
13
|
import { dirname, join } from "node:path";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { isInScope } from "../core/adapter.js";
|
|
16
|
+
import { usesNativeCapabilityDelivery } from "../core/types.js";
|
|
16
17
|
import * as claudeCodeProbe from "../probes/claude-code.js";
|
|
17
18
|
import { applySymlinkPlan, planSymlinks } from "./symlinkPlan.js";
|
|
18
19
|
import { applyJsonMcp, planJsonMcp, renderJsonServerEntry } from "./jsonMcp.js";
|
|
@@ -32,7 +33,7 @@ export class ClaudeCodeAdapter {
|
|
|
32
33
|
const canonicalRoot = dirname(canonical.instructionsFile);
|
|
33
34
|
const skillsRoot = join(this.homeDir, ".claude", "skills");
|
|
34
35
|
const desiredSkills = canonical.skills
|
|
35
|
-
.filter((skill) => isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
36
|
+
.filter((skill) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
36
37
|
.map((skill) => ({ name: skill.name, target: skill.dir }));
|
|
37
38
|
const skillItems = planSymlinks({
|
|
38
39
|
rootDir: skillsRoot,
|
|
@@ -82,7 +83,7 @@ export class ClaudeCodeAdapter {
|
|
|
82
83
|
return { ok: false, mismatches: ["claude-code is not present on this machine"] };
|
|
83
84
|
}
|
|
84
85
|
const mismatches = [];
|
|
85
|
-
const desiredNames = new Set(canonical.skills.filter((s) => isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
86
|
+
const desiredNames = new Set(canonical.skills.filter((s) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
86
87
|
const actualSkills = new Set(snapshot.skillRoots.flatMap((root) => root.skills).map((s) => s.name));
|
|
87
88
|
for (const name of desiredNames) {
|
|
88
89
|
if (!actualSkills.has(name)) {
|
package/dist/adapters/codex.js
CHANGED
|
@@ -19,6 +19,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
19
19
|
import { basename, dirname, join } from "node:path";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { isInScope } from "../core/adapter.js";
|
|
22
|
+
import { usesNativeCapabilityDelivery } from "../core/types.js";
|
|
22
23
|
import * as codexProbe from "../probes/codex.js";
|
|
23
24
|
import { readInstructionsPath } from "../probes/codex.js";
|
|
24
25
|
import { applySymlinkPlan, planSymlinks } from "./symlinkPlan.js";
|
|
@@ -40,7 +41,7 @@ export class CodexAdapter {
|
|
|
40
41
|
const canonicalRoot = dirname(canonical.instructionsFile);
|
|
41
42
|
const skillsRoot = join(this.homeDir, ".agents", "skills");
|
|
42
43
|
const desiredSkills = canonical.skills
|
|
43
|
-
.filter((skill) => isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
44
|
+
.filter((skill) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
44
45
|
.map((skill) => ({ name: skill.name, target: skill.dir }));
|
|
45
46
|
const skillItems = planSymlinks({
|
|
46
47
|
rootDir: skillsRoot,
|
|
@@ -136,7 +137,7 @@ export class CodexAdapter {
|
|
|
136
137
|
return { ok: false, mismatches: ["codex is not present on this machine"] };
|
|
137
138
|
}
|
|
138
139
|
const mismatches = [];
|
|
139
|
-
const desiredNames = new Set(canonical.skills.filter((s) => isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
140
|
+
const desiredNames = new Set(canonical.skills.filter((s) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
140
141
|
const actualSkills = new Set(snapshot.skillRoots.flatMap((root) => root.skills).map((s) => s.name));
|
|
141
142
|
for (const name of desiredNames) {
|
|
142
143
|
if (!actualSkills.has(name)) {
|
|
@@ -15,6 +15,7 @@ import type { AdapterPlanItem } from "../core/adapter.js";
|
|
|
15
15
|
* Codex's TOML does — see tomlSection.ts's two-table rendering)
|
|
16
16
|
* (trellis-mcp-static-env-and-disabled-servers design.md D4). */
|
|
17
17
|
export declare function renderJsonServerEntry(def: McpServerDef): Record<string, unknown>;
|
|
18
|
+
export type JsonMcpEntryRenderer = (def: McpServerDef, name: string) => Record<string, unknown>;
|
|
18
19
|
export declare function planJsonMcp(opts: {
|
|
19
20
|
configPath: string;
|
|
20
21
|
parsed: Record<string, unknown> | undefined;
|
|
@@ -27,9 +28,10 @@ export declare function planJsonMcp(opts: {
|
|
|
27
28
|
* pass `{}`) to get today's create/repair/conflict-only behavior with
|
|
28
29
|
* zero removal candidates. */
|
|
29
30
|
ownership?: Record<string, unknown>;
|
|
31
|
+
render?: JsonMcpEntryRenderer;
|
|
30
32
|
}): AdapterPlanItem[];
|
|
31
33
|
/** Merges every `"create"` MCP item into `parsed` (or a fresh `{}` if the
|
|
32
34
|
* file didn't exist) and deletes every `"remove"` item's key, returning
|
|
33
35
|
* the object to stringify — every sibling top-level key on `parsed` is
|
|
34
36
|
* spread through untouched. */
|
|
35
|
-
export declare function applyJsonMcp(parsed: Record<string, unknown> | undefined, items: AdapterPlanItem[]): Record<string, unknown>;
|
|
37
|
+
export declare function applyJsonMcp(parsed: Record<string, unknown> | undefined, items: AdapterPlanItem[], render?: JsonMcpEntryRenderer): Record<string, unknown>;
|
package/dist/adapters/jsonMcp.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { deepEqual } from "../lib/deepEqual.js";
|
|
10
10
|
import { resolvedEnvTextMap } from "../lib/mcpMigrateRead.js";
|
|
11
|
-
import { resolveMcpPlan } from "./mcpPlan.js";
|
|
11
|
+
import { LEGACY_GATEWAY_ENTRY_NAME, LEGACY_RUNTIME_ENTRY_NAME, resolveMcpPlan } from "./mcpPlan.js";
|
|
12
12
|
/** Renders the native JSON shape Claude Code/Kiro's `mcpServers` map
|
|
13
13
|
* expects — `env` names render as `${VAR}` references, `staticEnv`
|
|
14
14
|
* entries render as their literal values, both in the same map (JSON's
|
|
@@ -32,11 +32,12 @@ export function renderJsonServerEntry(def) {
|
|
|
32
32
|
}
|
|
33
33
|
export function planJsonMcp(opts) {
|
|
34
34
|
const { configPath, parsed, mcp, agentId, managedAgents, policy, ownership } = opts;
|
|
35
|
+
const render = opts.render ?? ((def, _name) => renderJsonServerEntry(def));
|
|
35
36
|
const { desired, conflicts } = resolveMcpPlan(agentId, mcp, managedAgents, policy);
|
|
36
37
|
const existingServers = parsed?.mcpServers ?? {};
|
|
37
38
|
const items = [];
|
|
38
39
|
for (const { name, def } of desired) {
|
|
39
|
-
const rendered =
|
|
40
|
+
const rendered = render(def, name);
|
|
40
41
|
const current = existingServers[name];
|
|
41
42
|
if (deepEqual(current, rendered)) {
|
|
42
43
|
continue; // already correct — no-op
|
|
@@ -59,6 +60,19 @@ export function planJsonMcp(opts) {
|
|
|
59
60
|
for (const [name, expected] of Object.entries(ownership ?? {})) {
|
|
60
61
|
if (desiredNames.has(name))
|
|
61
62
|
continue; // still wanted — not a removal candidate at all
|
|
63
|
+
if ((name === LEGACY_GATEWAY_ENTRY_NAME || name === LEGACY_RUNTIME_ENTRY_NAME) && desiredNames.has("trellis")) {
|
|
64
|
+
const current = existingServers[name];
|
|
65
|
+
if (current !== undefined && deepEqual(current, expected)) {
|
|
66
|
+
items.push({
|
|
67
|
+
action: "remove",
|
|
68
|
+
kind: "mcp",
|
|
69
|
+
target: configPath,
|
|
70
|
+
mcpRemove: { name },
|
|
71
|
+
description: `legacy MCP server "${name}" removed from ${configPath} after Trellis Runtime key migration`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
62
76
|
const current = existingServers[name];
|
|
63
77
|
if (current === undefined)
|
|
64
78
|
continue; // already gone — nothing to remove, ledger is pruned separately
|
|
@@ -78,7 +92,7 @@ export function planJsonMcp(opts) {
|
|
|
78
92
|
* file didn't exist) and deletes every `"remove"` item's key, returning
|
|
79
93
|
* the object to stringify — every sibling top-level key on `parsed` is
|
|
80
94
|
* spread through untouched. */
|
|
81
|
-
export function applyJsonMcp(parsed, items) {
|
|
95
|
+
export function applyJsonMcp(parsed, items, render = (def, _name) => renderJsonServerEntry(def)) {
|
|
82
96
|
const base = parsed ?? {};
|
|
83
97
|
const existingServers = base.mcpServers ?? {};
|
|
84
98
|
const mergedServers = { ...existingServers };
|
|
@@ -86,7 +100,7 @@ export function applyJsonMcp(parsed, items) {
|
|
|
86
100
|
if (item.kind !== "mcp")
|
|
87
101
|
continue;
|
|
88
102
|
if (item.action === "create" && item.mcpWrite) {
|
|
89
|
-
mergedServers[item.mcpWrite.name] =
|
|
103
|
+
mergedServers[item.mcpWrite.name] = render(item.mcpWrite.def, item.mcpWrite.name);
|
|
90
104
|
}
|
|
91
105
|
else if (item.action === "remove" && item.mcpRemove) {
|
|
92
106
|
delete mergedServers[item.mcpRemove.name];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi Code Runtime-first adapter.
|
|
3
|
+
*
|
|
4
|
+
* Runtime delivery owns only ~/.kimi-code/mcp.json. Native Kimi Skills and
|
|
5
|
+
* AGENTS.md remain opt-in for `native`/`both`; Runtime-only delivery uses
|
|
6
|
+
* `trellis kimi`, which launches Kimi with an empty --skills-dir root so the
|
|
7
|
+
* shared ~/.agents/skills tree is not discovered a second time.
|
|
8
|
+
*/
|
|
9
|
+
import type { AdapterPlanItem, AdapterProbeResult, AdapterVerifyResult, TrellisAdapter } from "../core/adapter.js";
|
|
10
|
+
import type { CanonicalSource } from "../core/types.js";
|
|
11
|
+
import type { BackupSession } from "../lib/backup.js";
|
|
12
|
+
export declare class KimiCodeAdapter implements TrellisAdapter {
|
|
13
|
+
private readonly homeDir;
|
|
14
|
+
readonly name = "Kimi Code";
|
|
15
|
+
readonly id: "kimi-code";
|
|
16
|
+
constructor(homeDir?: string);
|
|
17
|
+
probe(): Promise<AdapterProbeResult>;
|
|
18
|
+
plan(canonical: CanonicalSource): Promise<AdapterPlanItem[]>;
|
|
19
|
+
apply(plan: AdapterPlanItem[], backup: BackupSession): Promise<void>;
|
|
20
|
+
verify(canonical: CanonicalSource): Promise<AdapterVerifyResult>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kimi Code Runtime-first adapter.
|
|
3
|
+
*
|
|
4
|
+
* Runtime delivery owns only ~/.kimi-code/mcp.json. Native Kimi Skills and
|
|
5
|
+
* AGENTS.md remain opt-in for `native`/`both`; Runtime-only delivery uses
|
|
6
|
+
* `trellis kimi`, which launches Kimi with an empty --skills-dir root so the
|
|
7
|
+
* shared ~/.agents/skills tree is not discovered a second time.
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { dirname, join } from "node:path";
|
|
12
|
+
import { isInScope } from "../core/adapter.js";
|
|
13
|
+
import { usesNativeCapabilityDelivery } from "../core/types.js";
|
|
14
|
+
import * as kimiProbe from "../probes/kimi-code.js";
|
|
15
|
+
import { applyJsonMcp, planJsonMcp, renderJsonServerEntry } from "./jsonMcp.js";
|
|
16
|
+
import { RUNTIME_COMMAND, RUNTIME_ENTRY_NAME } from "./mcpPlan.js";
|
|
17
|
+
import { applySymlinkPlan, planSymlinks } from "./symlinkPlan.js";
|
|
18
|
+
import { loadMcpOwnership, ownedByAgent, recordOwned, forgetOwned, saveMcpOwnership } from "../lib/mcpOwnership.js";
|
|
19
|
+
const KIMI_RUNTIME_STARTUP_TIMEOUT_MS = 30_000;
|
|
20
|
+
function renderKimiServerEntry(def, name) {
|
|
21
|
+
const entry = renderJsonServerEntry(def);
|
|
22
|
+
if (def.transport === "stdio")
|
|
23
|
+
delete entry.type;
|
|
24
|
+
if (name === RUNTIME_ENTRY_NAME && def.command === RUNTIME_COMMAND) {
|
|
25
|
+
entry.deferred = true;
|
|
26
|
+
entry.startupTimeoutMs = KIMI_RUNTIME_STARTUP_TIMEOUT_MS;
|
|
27
|
+
}
|
|
28
|
+
return entry;
|
|
29
|
+
}
|
|
30
|
+
export class KimiCodeAdapter {
|
|
31
|
+
homeDir;
|
|
32
|
+
name = "Kimi Code";
|
|
33
|
+
id = "kimi-code";
|
|
34
|
+
constructor(homeDir = homedir()) {
|
|
35
|
+
this.homeDir = homeDir;
|
|
36
|
+
}
|
|
37
|
+
async probe() {
|
|
38
|
+
const snapshot = await kimiProbe.probe(this.homeDir);
|
|
39
|
+
return { present: snapshot.present, version: snapshot.version };
|
|
40
|
+
}
|
|
41
|
+
async plan(canonical) {
|
|
42
|
+
const native = usesNativeCapabilityDelivery(this.id, canonical.mcp);
|
|
43
|
+
const items = [];
|
|
44
|
+
const root = join(this.homeDir, ".kimi-code");
|
|
45
|
+
const canonicalRoot = dirname(canonical.instructionsFile);
|
|
46
|
+
items.push(...planSymlinks({
|
|
47
|
+
rootDir: join(root, "skills"),
|
|
48
|
+
desired: native
|
|
49
|
+
? canonical.skills
|
|
50
|
+
.filter((skill) => isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
51
|
+
.map((skill) => ({ name: skill.name, target: skill.dir }))
|
|
52
|
+
: [],
|
|
53
|
+
canonicalRoot: join(canonicalRoot, "skills"),
|
|
54
|
+
kind: "skill",
|
|
55
|
+
}));
|
|
56
|
+
items.push(...planSymlinks({
|
|
57
|
+
rootDir: root,
|
|
58
|
+
desired: native ? [{ name: "AGENTS.md", target: canonical.instructionsFile }] : [],
|
|
59
|
+
canonicalRoot,
|
|
60
|
+
kind: "instructions",
|
|
61
|
+
}));
|
|
62
|
+
const configPath = join(this.homeDir, ".kimi-code", "mcp.json");
|
|
63
|
+
const parsed = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : undefined;
|
|
64
|
+
const ownership = ownedByAgent(loadMcpOwnership(this.homeDir), this.id);
|
|
65
|
+
items.push(...planJsonMcp({
|
|
66
|
+
configPath,
|
|
67
|
+
parsed,
|
|
68
|
+
mcp: canonical.mcp,
|
|
69
|
+
agentId: this.id,
|
|
70
|
+
managedAgents: canonical.managedAgents,
|
|
71
|
+
policy: canonical.secretsPolicy,
|
|
72
|
+
ownership,
|
|
73
|
+
render: renderKimiServerEntry,
|
|
74
|
+
}));
|
|
75
|
+
return items;
|
|
76
|
+
}
|
|
77
|
+
async apply(plan, backup) {
|
|
78
|
+
await applySymlinkPlan(plan.filter((item) => item.kind === "skill" || item.kind === "instructions"), backup);
|
|
79
|
+
const mcpWrites = plan.filter((item) => item.kind === "mcp" && (item.action === "create" || item.action === "remove"));
|
|
80
|
+
if (mcpWrites.length === 0)
|
|
81
|
+
return;
|
|
82
|
+
const configPath = mcpWrites[0].target;
|
|
83
|
+
const parsed = existsSync(configPath) ? JSON.parse(readFileSync(configPath, "utf-8")) : undefined;
|
|
84
|
+
const merged = applyJsonMcp(parsed, mcpWrites, renderKimiServerEntry);
|
|
85
|
+
backup.writeFile(configPath, `${JSON.stringify(merged, null, 2)}\n`);
|
|
86
|
+
let ownership = loadMcpOwnership(this.homeDir);
|
|
87
|
+
for (const item of mcpWrites) {
|
|
88
|
+
if (item.action === "create" && item.mcpWrite)
|
|
89
|
+
ownership = recordOwned(ownership, this.id, item.mcpWrite.name, renderKimiServerEntry(item.mcpWrite.def, item.mcpWrite.name));
|
|
90
|
+
if (item.action === "remove" && item.mcpRemove)
|
|
91
|
+
ownership = forgetOwned(ownership, this.id, item.mcpRemove.name);
|
|
92
|
+
}
|
|
93
|
+
saveMcpOwnership(this.homeDir, ownership);
|
|
94
|
+
}
|
|
95
|
+
async verify(canonical) {
|
|
96
|
+
const snapshot = await kimiProbe.probe(this.homeDir);
|
|
97
|
+
if (!snapshot.present)
|
|
98
|
+
return { ok: false, mismatches: ["kimi-code is not present on this machine"] };
|
|
99
|
+
const desired = new Set(canonical.skills.filter((skill) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, skill.scope, canonical.managedAgents)).map((skill) => skill.name));
|
|
100
|
+
const actual = new Set(snapshot.skillRoots.flatMap((root) => root.skills).filter((skill) => skill.dir.includes("/.kimi-code/skills/")).map((skill) => skill.name));
|
|
101
|
+
const mismatches = [
|
|
102
|
+
...[...desired].filter((name) => !actual.has(name)).map((name) => `skill "${name}" is missing on Kimi Code`),
|
|
103
|
+
...[...actual].filter((name) => !desired.has(name)).map((name) => `skill "${name}" is not in Kimi Code's canonical scope`),
|
|
104
|
+
];
|
|
105
|
+
return mismatches.length === 0 ? { ok: true } : { ok: false, mismatches };
|
|
106
|
+
}
|
|
107
|
+
}
|
package/dist/adapters/kiro.js
CHANGED
|
@@ -19,6 +19,7 @@ import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
|
19
19
|
import { dirname, join } from "node:path";
|
|
20
20
|
import { homedir } from "node:os";
|
|
21
21
|
import { isInScope } from "../core/adapter.js";
|
|
22
|
+
import { usesNativeCapabilityDelivery } from "../core/types.js";
|
|
22
23
|
import * as kiroProbe from "../probes/kiro.js";
|
|
23
24
|
import { applySymlinkPlan, planSymlinks } from "./symlinkPlan.js";
|
|
24
25
|
import { applyJsonMcp, planJsonMcp, renderJsonServerEntry } from "./jsonMcp.js";
|
|
@@ -48,7 +49,7 @@ export class KiroAdapter {
|
|
|
48
49
|
const canonicalRoot = dirname(canonical.instructionsFile);
|
|
49
50
|
const skillsRoot = join(this.homeDir, ".kiro", "skills");
|
|
50
51
|
const desiredSkills = canonical.skills
|
|
51
|
-
.filter((skill) => isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
52
|
+
.filter((skill) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
52
53
|
.map((skill) => ({ name: skill.name, target: skill.dir }));
|
|
53
54
|
const skillItems = planSymlinks({
|
|
54
55
|
rootDir: skillsRoot,
|
|
@@ -161,7 +162,7 @@ export class KiroAdapter {
|
|
|
161
162
|
return { ok: false, mismatches: ["kiro is not present on this machine"] };
|
|
162
163
|
}
|
|
163
164
|
const mismatches = [];
|
|
164
|
-
const desiredNames = new Set(canonical.skills.filter((s) => isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
165
|
+
const desiredNames = new Set(canonical.skills.filter((s) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
165
166
|
const actualSkills = new Set(snapshot.skillRoots.flatMap((root) => root.skills).map((s) => s.name));
|
|
166
167
|
for (const name of desiredNames) {
|
|
167
168
|
if (!actualSkills.has(name)) {
|
|
@@ -18,12 +18,17 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import type { AgentId, McpConfig, McpServerDef, SecretsPolicy } from "../core/types.js";
|
|
20
20
|
export declare const HUB_ENTRY_NAME = "trellis-hub";
|
|
21
|
-
export declare const GATEWAY_ENTRY_NAME = "trellis
|
|
21
|
+
export declare const GATEWAY_ENTRY_NAME = "trellis";
|
|
22
|
+
export declare const LEGACY_GATEWAY_ENTRY_NAME = "trellis-gateway";
|
|
23
|
+
export declare const RUNTIME_ENTRY_NAME = "trellis";
|
|
24
|
+
export declare const LEGACY_RUNTIME_ENTRY_NAME = "trellis-runtime";
|
|
22
25
|
/** The command an agent spawns in gateway mode. Bare `trellis` rather than
|
|
23
26
|
* an absolute path: the entry has to keep working across reinstalls and
|
|
24
27
|
* version bumps, and every agent resolves it through the same PATH the
|
|
25
28
|
* user installed the CLI onto. */
|
|
26
29
|
export declare const GATEWAY_COMMAND = "trellis";
|
|
30
|
+
export declare const RUNTIME_COMMAND = "trellis";
|
|
31
|
+
export declare function isOAuthMcpServer(def: McpServerDef): boolean;
|
|
27
32
|
/**
|
|
28
33
|
* Gateway mode applies to every managed agent when `agents` is omitted —
|
|
29
34
|
* the expected normal case (design.md D14). An explicit list narrows it,
|