agent-trellis 0.6.3 → 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 +26 -2
- package/dist/adapters/mcpPlan.js +135 -15
- 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 +129 -20
- 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 +37 -7
- package/dist/commands/migrate.js +253 -27
- package/dist/commands/onboard.d.ts +74 -2
- package/dist/commands/onboard.js +463 -72
- package/dist/commands/onboardVerdict.js +20 -7
- package/dist/commands/rollback.js +4 -1
- package/dist/commands/secretsAudit.d.ts +21 -12
- package/dist/commands/secretsAudit.js +39 -10
- 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 +58 -4
- package/dist/core/canonical.js +183 -11
- 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 +17 -0
- package/dist/lib/secretEnv.js +31 -1
- 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 -28
- package/dist/lib/terminalPicker.js +54 -164
- package/dist/pi-bridge/bundle.js +493 -51
- 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 +403 -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 +10 -5
- package/package.json +5 -3
- 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/dist/adapters/mcpPlan.js
CHANGED
|
@@ -17,21 +17,32 @@
|
|
|
17
17
|
* ever returns create/repair + refuse.
|
|
18
18
|
*/
|
|
19
19
|
import { isInScope } from "../core/adapter.js";
|
|
20
|
+
import { capabilityDeliveryForAgent } from "../core/types.js";
|
|
20
21
|
import { codexBearerTokenEnvVar } from "../lib/tomlSection.js";
|
|
21
22
|
import { resolveSecretEnv } from "../lib/secretEnv.js";
|
|
22
23
|
export const HUB_ENTRY_NAME = "trellis-hub";
|
|
23
|
-
export const GATEWAY_ENTRY_NAME = "trellis
|
|
24
|
+
export const GATEWAY_ENTRY_NAME = "trellis";
|
|
25
|
+
export const LEGACY_GATEWAY_ENTRY_NAME = "trellis-gateway";
|
|
26
|
+
export const RUNTIME_ENTRY_NAME = "trellis";
|
|
27
|
+
export const LEGACY_RUNTIME_ENTRY_NAME = "trellis-runtime";
|
|
24
28
|
/** The command an agent spawns in gateway mode. Bare `trellis` rather than
|
|
25
29
|
* an absolute path: the entry has to keep working across reinstalls and
|
|
26
30
|
* version bumps, and every agent resolves it through the same PATH the
|
|
27
31
|
* user installed the CLI onto. */
|
|
28
32
|
export const GATEWAY_COMMAND = "trellis";
|
|
33
|
+
export const RUNTIME_COMMAND = "trellis";
|
|
34
|
+
export function isOAuthMcpServer(def) {
|
|
35
|
+
return def.auth === "oauth";
|
|
36
|
+
}
|
|
29
37
|
/**
|
|
30
38
|
* Gateway mode applies to every managed agent when `agents` is omitted —
|
|
31
39
|
* the expected normal case (design.md D14). An explicit list narrows it,
|
|
32
40
|
* letting some agents stay in direct or hub mode.
|
|
33
41
|
*/
|
|
34
42
|
export function isGatewayAgent(agentId, mcp, managedAgents) {
|
|
43
|
+
const route = mcp.routes?.[agentId];
|
|
44
|
+
if (route)
|
|
45
|
+
return route.mode === "gateway";
|
|
35
46
|
const gateway = mcp.gateway;
|
|
36
47
|
if (!gateway?.enabled)
|
|
37
48
|
return false;
|
|
@@ -39,6 +50,16 @@ export function isGatewayAgent(agentId, mcp, managedAgents) {
|
|
|
39
50
|
return gateway.agents.includes(agentId);
|
|
40
51
|
return managedAgents.includes(agentId);
|
|
41
52
|
}
|
|
53
|
+
function routeForAgent(agentId, mcp, managedAgents) {
|
|
54
|
+
const explicit = mcp.routes?.[agentId];
|
|
55
|
+
if (explicit)
|
|
56
|
+
return explicit;
|
|
57
|
+
if (isGatewayAgent(agentId, mcp, managedAgents))
|
|
58
|
+
return { mode: "gateway" };
|
|
59
|
+
if (mcp.hub)
|
|
60
|
+
return { mode: "hub" };
|
|
61
|
+
return { mode: "direct" };
|
|
62
|
+
}
|
|
42
63
|
const CODEX_STDIO_URL_CRASH_NOTE = ' On Codex specifically, this crashes the entire process at startup ("url is not supported for stdio"), not just this one server — see docs/research.md.';
|
|
43
64
|
function collisionMessage(name, agentId) {
|
|
44
65
|
return `refusing to write MCP server "${name}": also appears in known_host_injected.${agentId === "codex" ? CODEX_STDIO_URL_CRASH_NOTE : ""}`;
|
|
@@ -71,13 +92,23 @@ const DANGEROUS_LITERAL_PATTERNS = [
|
|
|
71
92
|
* read, diffed, and committed as plain text. One shared implementation,
|
|
72
93
|
* not two that can drift, for the same reason `mcpConnect.ts`/
|
|
73
94
|
* `mcpToolRegistry.ts` are shared between pi-bridge and the gateway.
|
|
95
|
+
*
|
|
96
|
+
* Scanning order is `command, url, args, headers, staticEnv` — a def
|
|
97
|
+
* matching in more than one field returns whichever is found first, same
|
|
98
|
+
* as before this function reported a field at all.
|
|
74
99
|
*/
|
|
75
100
|
export function findLiteralSecret(def) {
|
|
76
|
-
const candidates = [
|
|
77
|
-
|
|
101
|
+
const candidates = [
|
|
102
|
+
...(def.command !== undefined ? [{ value: def.command, field: "command" }] : []),
|
|
103
|
+
...(def.url !== undefined ? [{ value: def.url, field: "url" }] : []),
|
|
104
|
+
...(def.args ?? []).map((value) => ({ value, field: "args" })),
|
|
105
|
+
...Object.entries(def.headers ?? {}).map(([key, value]) => ({ value, field: "headers", key })),
|
|
106
|
+
...Object.entries(def.staticEnv ?? {}).map(([key, value]) => ({ value, field: "staticEnv", key })),
|
|
107
|
+
];
|
|
108
|
+
for (const { value, field, key } of candidates) {
|
|
78
109
|
for (const { label, pattern } of DANGEROUS_LITERAL_PATTERNS) {
|
|
79
|
-
if (pattern.test(
|
|
80
|
-
return label;
|
|
110
|
+
if (pattern.test(value)) {
|
|
111
|
+
return { label, field, key };
|
|
81
112
|
}
|
|
82
113
|
}
|
|
83
114
|
}
|
|
@@ -106,54 +137,143 @@ function findUnresolvedEnvName(def, policy) {
|
|
|
106
137
|
return names.find((name) => !resolved[name]);
|
|
107
138
|
}
|
|
108
139
|
export function resolveMcpPlan(agentId, mcp, managedAgents, policy) {
|
|
140
|
+
const route = routeForAgent(agentId, mcp, managedAgents);
|
|
141
|
+
const runtimeEnabled = capabilityDeliveryForAgent(agentId, mcp) !== "native";
|
|
109
142
|
// Checked before `hub`: only one entry can be "the" MCP entry for an
|
|
110
143
|
// agent, and gateway is the newer, purpose-built path (design.md D6).
|
|
111
144
|
// `hub` stays available, unchanged, for anyone pointing an agent at an
|
|
112
145
|
// externally-operated endpoint — including a different agent in the
|
|
113
146
|
// same canonical source.
|
|
114
|
-
if (
|
|
115
|
-
|
|
147
|
+
if (route.mode === "gateway") {
|
|
148
|
+
const entryName = runtimeEnabled ? RUNTIME_ENTRY_NAME : GATEWAY_ENTRY_NAME;
|
|
149
|
+
if (mcp.knownHostInjected.includes(entryName)) {
|
|
116
150
|
return {
|
|
117
151
|
desired: [],
|
|
118
|
-
conflicts: [{ name:
|
|
152
|
+
conflicts: [{ name: entryName, message: collisionMessage(entryName, agentId), remediation: collisionRemediation(entryName) }],
|
|
119
153
|
};
|
|
120
154
|
}
|
|
155
|
+
const oauth = planDirectServers(agentId, mcp, managedAgents, policy, route, isOAuthMcpServer);
|
|
156
|
+
// Every ordinary per-server check (enabled, scope, literal secrets,
|
|
157
|
+
// unresolved env names, Codex's header shape) still runs at gateway
|
|
158
|
+
// startup. OAuth servers are the deliberate exception: they are written
|
|
159
|
+
// as direct entries so the gateway never owns their client connection.
|
|
121
160
|
// Every per-server check (enabled, scope, literal secrets, unresolved
|
|
122
161
|
// env names, Codex's header shape) still runs — but at gateway
|
|
123
162
|
// *startup*, inside the subcommand, since no adapter sees an
|
|
124
163
|
// individual server in this mode (design.md D6).
|
|
125
164
|
return {
|
|
126
|
-
desired: [{
|
|
127
|
-
|
|
165
|
+
desired: [{
|
|
166
|
+
name: entryName,
|
|
167
|
+
def: {
|
|
168
|
+
transport: "stdio",
|
|
169
|
+
command: runtimeEnabled ? RUNTIME_COMMAND : GATEWAY_COMMAND,
|
|
170
|
+
args: [runtimeEnabled ? "mcp-runtime" : "mcp-gateway", "--agent", agentId],
|
|
171
|
+
},
|
|
172
|
+
}, ...oauth.desired],
|
|
173
|
+
conflicts: oauth.conflicts,
|
|
128
174
|
};
|
|
129
175
|
}
|
|
130
|
-
if (
|
|
176
|
+
if (route.mode === "hub") {
|
|
177
|
+
if (!mcp.hub) {
|
|
178
|
+
return {
|
|
179
|
+
desired: [],
|
|
180
|
+
conflicts: [{
|
|
181
|
+
name: HUB_ENTRY_NAME,
|
|
182
|
+
message: `cannot resolve hub route "${agentId}": canonical mcp.hub.url is not configured`,
|
|
183
|
+
remediation: `configure mcp.hub.url or choose direct/gateway mode for ${agentId}`,
|
|
184
|
+
}],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
131
187
|
if (mcp.knownHostInjected.includes(HUB_ENTRY_NAME)) {
|
|
132
188
|
return {
|
|
133
189
|
desired: [],
|
|
134
190
|
conflicts: [{ name: HUB_ENTRY_NAME, message: collisionMessage(HUB_ENTRY_NAME, agentId), remediation: collisionRemediation(HUB_ENTRY_NAME) }],
|
|
135
191
|
};
|
|
136
192
|
}
|
|
137
|
-
|
|
193
|
+
if (route.servers) {
|
|
194
|
+
return {
|
|
195
|
+
desired: [],
|
|
196
|
+
conflicts: [{
|
|
197
|
+
name: HUB_ENTRY_NAME,
|
|
198
|
+
message: `cannot restrict external hub route "${agentId}" to individual MCP servers: the hub owns its upstream set`,
|
|
199
|
+
remediation: `remove the servers list from the ${agentId} hub route, or use direct/gateway mode for per-server selection`,
|
|
200
|
+
}],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const desired = [{ name: HUB_ENTRY_NAME, def: { transport: "http", url: mcp.hub.url } }];
|
|
204
|
+
if (runtimeEnabled) {
|
|
205
|
+
if (mcp.knownHostInjected.includes(RUNTIME_ENTRY_NAME)) {
|
|
206
|
+
return {
|
|
207
|
+
desired: [],
|
|
208
|
+
conflicts: [{ name: RUNTIME_ENTRY_NAME, message: collisionMessage(RUNTIME_ENTRY_NAME, agentId), remediation: collisionRemediation(RUNTIME_ENTRY_NAME) }],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// The runtime provides Trellis-owned capabilities here. The external
|
|
212
|
+
// hub remains the upstream MCP edge; the runtime does not connect to
|
|
213
|
+
// canonical upstreams a second time for this route.
|
|
214
|
+
desired.push({
|
|
215
|
+
name: RUNTIME_ENTRY_NAME,
|
|
216
|
+
def: { transport: "stdio", command: RUNTIME_COMMAND, args: ["mcp-runtime", "--agent", agentId] },
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
return { desired, conflicts: [] };
|
|
138
220
|
}
|
|
221
|
+
if (runtimeEnabled) {
|
|
222
|
+
if (mcp.knownHostInjected.includes(RUNTIME_ENTRY_NAME)) {
|
|
223
|
+
return {
|
|
224
|
+
desired: [],
|
|
225
|
+
conflicts: [{ name: RUNTIME_ENTRY_NAME, message: collisionMessage(RUNTIME_ENTRY_NAME, agentId), remediation: collisionRemediation(RUNTIME_ENTRY_NAME) }],
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// Runtime mode owns the agent-facing MCP edge. It resolves the same
|
|
229
|
+
// direct/gateway-compatible upstream route at startup, so native
|
|
230
|
+
// per-server entries are not duplicated here.
|
|
231
|
+
return {
|
|
232
|
+
desired: [{
|
|
233
|
+
name: RUNTIME_ENTRY_NAME,
|
|
234
|
+
def: { transport: "stdio", command: RUNTIME_COMMAND, args: ["mcp-runtime", "--agent", agentId] },
|
|
235
|
+
}],
|
|
236
|
+
conflicts: [],
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return planDirectServers(agentId, mcp, managedAgents, policy, route);
|
|
240
|
+
}
|
|
241
|
+
function planDirectServers(agentId, mcp, managedAgents, policy, route, filter) {
|
|
139
242
|
const desired = [];
|
|
140
243
|
const conflicts = [];
|
|
141
244
|
for (const [name, def] of Object.entries(mcp.servers)) {
|
|
142
245
|
if (def.enabled === false) {
|
|
143
246
|
continue;
|
|
144
247
|
}
|
|
248
|
+
if (filter && !filter(def))
|
|
249
|
+
continue;
|
|
145
250
|
if (!isInScope(agentId, def.agents, managedAgents)) {
|
|
146
251
|
continue;
|
|
147
252
|
}
|
|
253
|
+
if (route.servers && !route.servers.includes(name)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
148
256
|
if (mcp.knownHostInjected.includes(name)) {
|
|
149
257
|
conflicts.push({ name, message: collisionMessage(name, agentId), remediation: collisionRemediation(name) });
|
|
150
258
|
continue;
|
|
151
259
|
}
|
|
152
|
-
|
|
153
|
-
|
|
260
|
+
// Only `staticEnv` is refused here: it's the one shape a real
|
|
261
|
+
// credential is meant to leave via `env`/`env_vars`, proven to
|
|
262
|
+
// resolve back to a real value for every consumer (pi-bridge,
|
|
263
|
+
// claude-code, codex, kiro) — a literal surviving there means
|
|
264
|
+
// something bypassed `migrate`'s own extraction (a hand-edited
|
|
265
|
+
// servers.yaml, most likely) and must still be refused outright. A
|
|
266
|
+
// match in `command`/`url`/`args`/`headers` is accepted and written
|
|
267
|
+
// as ordinary literal config instead (trellis-migrate-extract-static-env-secrets
|
|
268
|
+
// design.md D11) — none of those fields has a `${VAR}` resolution
|
|
269
|
+
// mechanism proven across every consumer, so a fake reference there
|
|
270
|
+
// would be strictly worse than the literal it replaced; `secrets
|
|
271
|
+
// audit` keeps flagging canonical itself so this isn't silent.
|
|
272
|
+
const secretMatch = findLiteralSecret(def);
|
|
273
|
+
if (secretMatch?.field === "staticEnv") {
|
|
154
274
|
conflicts.push({
|
|
155
275
|
name,
|
|
156
|
-
message: `refusing to write MCP server "${name}": a value matches a known-dangerous literal pattern (${
|
|
276
|
+
message: `refusing to write MCP server "${name}": a value matches a known-dangerous literal pattern (${secretMatch.label}) — configs must hold variable NAMES only, never real values (docs/research.md "Secrets")`,
|
|
157
277
|
remediation: `replace the literal value in servers.yaml with a \`\${VAR_NAME}\` reference and put the real value wherever secrets.policy.yaml resolves it from`,
|
|
158
278
|
});
|
|
159
279
|
continue;
|
package/dist/adapters/pi.js
CHANGED
|
@@ -9,13 +9,31 @@
|
|
|
9
9
|
* mechanism from every other agent's config-generation adapter, but the
|
|
10
10
|
* same symlink create/repair/remove machinery as skills/instructions.
|
|
11
11
|
*/
|
|
12
|
-
import { dirname, join } from "node:path";
|
|
12
|
+
import { dirname, join, sep } from "node:path";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
|
+
import { readFileSync } from "node:fs";
|
|
15
16
|
import { isInScope } from "../core/adapter.js";
|
|
17
|
+
import { usesNativeCapabilityDelivery } from "../core/types.js";
|
|
16
18
|
import * as piProbe from "../probes/pi.js";
|
|
17
19
|
import { applySymlinkPlan, planSymlinks } from "./symlinkPlan.js";
|
|
18
20
|
const BRIDGE_SYMLINK_NAME = "trellis-mcp-bridge.js";
|
|
21
|
+
/**
|
|
22
|
+
* A source-checkout bridge can survive a switch to the packaged CLI. Adopt it
|
|
23
|
+
* only when it has the Trellis bridge artifact shape and is byte-identical
|
|
24
|
+
* to the bundle this installation would write. This is intentionally not a
|
|
25
|
+
* general symlink ownership rule.
|
|
26
|
+
*/
|
|
27
|
+
function isEquivalentTrellisBridge(existingTarget, desiredTarget) {
|
|
28
|
+
if (!existingTarget.endsWith(`${sep}${join("dist", "pi-bridge", "bundle.js")}`))
|
|
29
|
+
return false;
|
|
30
|
+
try {
|
|
31
|
+
return readFileSync(existingTarget).equals(readFileSync(desiredTarget));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
19
37
|
/**
|
|
20
38
|
* The bundled bridge file's location — always `<repo-root>/dist/pi-bridge/
|
|
21
39
|
* bundle.js`, never `~/.trellis/` (it's Trellis's own packaged code, not a
|
|
@@ -56,7 +74,7 @@ export class PiAdapter {
|
|
|
56
74
|
const canonicalRoot = dirname(canonical.instructionsFile);
|
|
57
75
|
const agentDir = join(this.homeDir, ".pi", "agent");
|
|
58
76
|
const desiredSkills = canonical.skills
|
|
59
|
-
.filter((skill) => isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
77
|
+
.filter((skill) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, skill.scope, canonical.managedAgents))
|
|
60
78
|
.map((skill) => ({ name: skill.name, target: skill.dir }));
|
|
61
79
|
const skillItems = planSymlinks({
|
|
62
80
|
rootDir: join(agentDir, "skills"),
|
|
@@ -79,6 +97,7 @@ export class PiAdapter {
|
|
|
79
97
|
// (design.md D2), same role `canonicalRoot` plays for skills.
|
|
80
98
|
canonicalRoot: dirname(bridgeFile),
|
|
81
99
|
kind: "extension",
|
|
100
|
+
adoptExistingSymlink: isEquivalentTrellisBridge,
|
|
82
101
|
});
|
|
83
102
|
return [...skillItems, ...instructionsItems, ...extensionItems];
|
|
84
103
|
}
|
|
@@ -91,7 +110,7 @@ export class PiAdapter {
|
|
|
91
110
|
return { ok: false, mismatches: ["pi is not present on this machine"] };
|
|
92
111
|
}
|
|
93
112
|
const mismatches = [];
|
|
94
|
-
const desiredNames = new Set(canonical.skills.filter((s) => isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
113
|
+
const desiredNames = new Set(canonical.skills.filter((s) => usesNativeCapabilityDelivery(this.id, canonical.mcp) && isInScope(this.id, s.scope, canonical.managedAgents)).map((s) => s.name));
|
|
95
114
|
const actualSkills = new Set(snapshot.skillRoots.flatMap((root) => root.skills).map((s) => s.name));
|
|
96
115
|
for (const name of desiredNames) {
|
|
97
116
|
if (!actualSkills.has(name)) {
|
|
@@ -12,6 +12,12 @@ export interface DesiredSymlink {
|
|
|
12
12
|
/** Absolute path the symlink should point at (inside `canonicalRoot`). */
|
|
13
13
|
target: string;
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* A narrowly-scoped escape hatch for artifacts that Trellis can identify
|
|
17
|
+
* across installation roots. The default ownership rule remains path-based;
|
|
18
|
+
* callers must opt in explicitly for a particular artifact kind.
|
|
19
|
+
*/
|
|
20
|
+
export type ExistingSymlinkAdoption = (existingTarget: string, desiredTarget: string) => boolean;
|
|
15
21
|
/**
|
|
16
22
|
* `rootDir` is the directory entries are named directly under (e.g.
|
|
17
23
|
* `~/.claude/skills`, or the instructions file's own parent directory when
|
|
@@ -28,6 +34,7 @@ export declare function planSymlinks(opts: {
|
|
|
28
34
|
desired: DesiredSymlink[];
|
|
29
35
|
canonicalRoot: string;
|
|
30
36
|
kind: "skill" | "instructions" | "extension";
|
|
37
|
+
adoptExistingSymlink?: ExistingSymlinkAdoption;
|
|
31
38
|
}): AdapterPlanItem[];
|
|
32
39
|
/** Executes a `planSymlinks` result through the run's backup session
|
|
33
40
|
* (trellis-backup-rollback) — every create/repair/remove is recorded
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* trellis-sync-p1/specs/skill-instructions-sync/spec.md) — built once,
|
|
5
5
|
* reused by every adapter rather than reimplemented per agent.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { lstatSync, readdirSync, readlinkSync } from "node:fs";
|
|
8
8
|
import { mkdir } from "node:fs/promises";
|
|
9
|
-
import { join, resolve, sep } from "node:path";
|
|
9
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
10
10
|
import { isSymlinkTo } from "../lib/fsIdentity.js";
|
|
11
11
|
import { currentLinkTarget } from "../lib/backup.js";
|
|
12
12
|
function isUnderRoot(path, root) {
|
|
@@ -29,7 +29,7 @@ function isUnderRoot(path, root) {
|
|
|
29
29
|
* case this removal path exists to catch.
|
|
30
30
|
*/
|
|
31
31
|
export function planSymlinks(opts) {
|
|
32
|
-
const { rootDir, desired, kind } = opts;
|
|
32
|
+
const { rootDir, desired, kind, adoptExistingSymlink } = opts;
|
|
33
33
|
const items = [];
|
|
34
34
|
const desiredNames = new Set(desired.map((d) => d.name));
|
|
35
35
|
const canonicalRootResolved = resolve(opts.canonicalRoot);
|
|
@@ -38,8 +38,15 @@ export function planSymlinks(opts) {
|
|
|
38
38
|
if (isSymlinkTo(path, target)) {
|
|
39
39
|
continue; // already correct — no-op
|
|
40
40
|
}
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// lstat sees dangling links too; existsSync would treat them as absent
|
|
42
|
+
// and bypass the foreign-link protection below.
|
|
43
|
+
const existing = lstatSync(path, { throwIfNoEntry: false });
|
|
44
|
+
if (existing) {
|
|
45
|
+
const isSymlink = existing.isSymbolicLink();
|
|
46
|
+
const rawExistingTarget = isSymlink ? readlinkSync(path) : undefined;
|
|
47
|
+
const resolvedExistingTarget = rawExistingTarget === undefined
|
|
48
|
+
? undefined
|
|
49
|
+
: resolve(dirname(path), rawExistingTarget);
|
|
43
50
|
// A symlink whose stored target resolves outside canonicalRoot is
|
|
44
51
|
// owned by something else (e.g. a user's own dotfile-management
|
|
45
52
|
// setup) — repairing it as if it were a stale Trellis entry would
|
|
@@ -47,14 +54,16 @@ export function planSymlinks(opts) {
|
|
|
47
54
|
// symlink Trellis itself left pointing at a since-removed canonical
|
|
48
55
|
// entry is broken by construction and must still be treated as
|
|
49
56
|
// ours to repair, not thrown out to a conflict by a realpath error.
|
|
50
|
-
const isForeign =
|
|
57
|
+
const isForeign = resolvedExistingTarget !== undefined
|
|
58
|
+
&& !isUnderRoot(resolvedExistingTarget, canonicalRootResolved)
|
|
59
|
+
&& adoptExistingSymlink?.(resolvedExistingTarget, target) !== true;
|
|
51
60
|
if (!isSymlink || isForeign) {
|
|
52
61
|
items.push({
|
|
53
62
|
action: "conflict",
|
|
54
63
|
kind,
|
|
55
64
|
target: path,
|
|
56
65
|
description: isForeign
|
|
57
|
-
? `${path} exists as a symlink to ${
|
|
66
|
+
? `${path} exists as a symlink to ${rawExistingTarget}, not owned by Trellis — left untouched`
|
|
58
67
|
: `${path} exists and is not a Trellis-managed symlink — left untouched`,
|
|
59
68
|
remediation: isForeign
|
|
60
69
|
? `remove the existing symlink at ${path} if you want Trellis to manage it, then re-run sync — otherwise leave it, Trellis will not touch it`
|
package/dist/cli.js
CHANGED
|
@@ -9,22 +9,33 @@ import { runInit } from "./commands/init.js";
|
|
|
9
9
|
import { runMigrate } from "./commands/migrate.js";
|
|
10
10
|
import { runOnboard } from "./commands/onboard.js";
|
|
11
11
|
import { runSync } from "./commands/sync.js";
|
|
12
|
-
import { runMcpSync, runMcpList, runMcpAdd, runMcpRemove, parseMcpAddArgs } from "./commands/mcp.js";
|
|
13
|
-
import {
|
|
12
|
+
import { runMcpSync, runMcpList, runMcpAdd, runMcpRemove, runMcpSetAuth, parseMcpAddArgs } from "./commands/mcp.js";
|
|
13
|
+
import { runMcpImport } from "./commands/mcpImport.js";
|
|
14
|
+
import { parseMcpGatewayArgs, runMcpGateway, runMcpRuntime } from "./commands/mcpGateway.js";
|
|
14
15
|
import { runMcpAuth } from "./commands/mcpAuth.js";
|
|
15
16
|
import { runSecretsAudit } from "./commands/secretsAudit.js";
|
|
16
17
|
import { runRollback } from "./commands/rollback.js";
|
|
17
|
-
import { runSkillList, runSkillAdd, runSkillRemove } from "./commands/skill.js";
|
|
18
|
+
import { runSkillList, runSkillAdd, runSkillRemove, runSkillUpdateBuiltin } from "./commands/skill.js";
|
|
18
19
|
import { runMemoryExtraction, runMemorySync } from "./commands/memory.js";
|
|
20
|
+
import { parseManageArgs, runManage } from "./commands/manage.js";
|
|
21
|
+
import { runKimi } from "./commands/kimi.js";
|
|
22
|
+
import { TRELLIS_VERSION } from "./lib/cliMetadata.js";
|
|
19
23
|
import { parseSyncArgs } from "./lib/syncArgs.js";
|
|
20
|
-
const KNOWN_COMMANDS = ["onboard", "init", "migrate", "doctor", "sync", "mcp", "mcp-gateway", "skill", "memory", "secrets", "rollback"];
|
|
24
|
+
const KNOWN_COMMANDS = ["onboard", "init", "migrate", "doctor", "sync", "mcp", "mcp-gateway", "mcp-runtime", "skill", "memory", "manage", "kimi", "secrets", "rollback"];
|
|
21
25
|
function printUsage() {
|
|
22
26
|
console.log(`trellis - a single source of capability for every coding agent
|
|
23
27
|
|
|
24
28
|
Usage:
|
|
25
29
|
trellis <command>
|
|
30
|
+
trellis --version
|
|
31
|
+
trellis --help
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
-v, --version print the installed Trellis version
|
|
35
|
+
-h, --help show this help
|
|
26
36
|
|
|
27
37
|
Commands:
|
|
38
|
+
help Show this help
|
|
28
39
|
onboard Guided flow: init -> detect agents -> pick a migration source ->
|
|
29
40
|
pick which agents to manage -> migrate -> sync -> mcp sync ->
|
|
30
41
|
secrets audit, in one command. Source (read from) and managed
|
|
@@ -39,15 +50,21 @@ Commands:
|
|
|
39
50
|
(required with no terminal to prompt
|
|
40
51
|
in, e.g. --json)
|
|
41
52
|
--mcp-mode <direct|hub|gateway>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
--hub-url <url> required with --mcp-mode hub
|
|
53
|
+
direct = Agent 分别直连;gateway =
|
|
54
|
+
Trellis 本机统一托管(推荐);hub =
|
|
55
|
+
连接已有的外部 MCP Hub
|
|
56
|
+
--hub-url <url> required with --mcp-mode hub; Trellis
|
|
57
|
+
does not deploy an external Hub
|
|
46
58
|
--gateway-agents <ids>
|
|
47
59
|
optional with --mcp-mode gateway;
|
|
48
60
|
omit for every managed agent
|
|
49
61
|
--memory <on|off> enable/disable the shared memory MCP
|
|
50
62
|
server; omit to leave it untouched
|
|
63
|
+
--memory-migrate <on|off>
|
|
64
|
+
import supported native Agent memory into
|
|
65
|
+
canonical; omit to leave it untouched
|
|
66
|
+
--selection <file> item-level YAML/JSON selection for skills,
|
|
67
|
+
MCP servers, memories, and per-agent routes
|
|
51
68
|
--dry-run preview the whole flow, write nothing
|
|
52
69
|
--json machine-readable output, no report text
|
|
53
70
|
init Create ~/.trellis/ with a minimal valid skeleton if missing
|
|
@@ -55,12 +72,13 @@ Commands:
|
|
|
55
72
|
missing) and prints which agents are present
|
|
56
73
|
--json machine-readable output, no report text
|
|
57
74
|
migrate --from <agent>
|
|
58
|
-
Import an existing agent's real skills/instructions/MCP
|
|
75
|
+
Import an existing agent's real skills/instructions/MCP/memory
|
|
59
76
|
servers into canonical source (claude-code | codex | kiro |
|
|
60
77
|
pi — pi has no static MCP config, mcp migrate-in is a no-op
|
|
61
78
|
for it). Never overwrites differing content — reports a
|
|
62
79
|
conflict instead.
|
|
63
|
-
|
|
80
|
+
--only skills|instructions|mcp|memory
|
|
81
|
+
restrict to one category
|
|
64
82
|
--dry-run preview the plan, write nothing
|
|
65
83
|
--json machine-readable output, no report text
|
|
66
84
|
doctor Scan Claude Code / Codex / Kiro / pi for drift
|
|
@@ -83,12 +101,22 @@ Commands:
|
|
|
83
101
|
--json machine-readable output, no report text
|
|
84
102
|
mcp add <name> --transport stdio|http|sse ...
|
|
85
103
|
Add a canonical MCP server (refuses on an existing name,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
no overwrite): --command <cmd> [--args a,b] (stdio) or
|
|
105
|
+
--url <url> (http/sse); [--headers k=v,...]
|
|
106
|
+
[--auth oauth] [--env NAME,...] [--static-env k=v,...] [--agents id,...]
|
|
107
|
+
[--enabled true|false]
|
|
108
|
+
--dry-run preview the plan, write nothing
|
|
109
|
+
--json machine-readable output, no report text
|
|
110
|
+
mcp import <json-file>
|
|
111
|
+
Import a standard mcpServers JSON export without changing the
|
|
112
|
+
source; extracts credential env values into the ignored local
|
|
113
|
+
secret file, de-duplicates, and reports conflicts
|
|
114
|
+
--dry-run preview without writing
|
|
115
|
+
--json machine-readable output, no report text
|
|
116
|
+
mcp set <name> --auth oauth|none
|
|
117
|
+
Set or clear explicit OAuth classification; canonical only
|
|
118
|
+
--dry-run preview the plan, write nothing
|
|
119
|
+
--json machine-readable output, no report text
|
|
92
120
|
mcp auth <server-name>
|
|
93
121
|
Authorize a remote (http/sse) MCP server that uses OAuth —
|
|
94
122
|
opens a browser once, stores the result 0600 under
|
|
@@ -97,6 +125,10 @@ Commands:
|
|
|
97
125
|
refreshes silently on its own and never prompts.
|
|
98
126
|
--force re-authorize even if the stored token is valid
|
|
99
127
|
--json machine-readable output, no report text
|
|
128
|
+
mcp-runtime --agent <agent>
|
|
129
|
+
Internal MCP edge exposing Trellis providers and selected
|
|
130
|
+
upstream capabilities; existing mcp-gateway configs remain valid
|
|
131
|
+
as a compatibility alias
|
|
100
132
|
mcp remove <name>
|
|
101
133
|
Remove a canonical MCP server (canonical-side only — does
|
|
102
134
|
not touch any agent's already-synced native config)
|
|
@@ -111,6 +143,11 @@ Commands:
|
|
|
111
143
|
--dry-run preview the plan, write nothing
|
|
112
144
|
--json machine-readable output, no report text
|
|
113
145
|
skill remove <name>
|
|
146
|
+
skill update-builtin
|
|
147
|
+
Refresh the package-owned trellis-runtime Skill in canonical
|
|
148
|
+
source; existing native symlinks see the update
|
|
149
|
+
--dry-run preview without writing
|
|
150
|
+
--json machine-readable output, no report text
|
|
114
151
|
Remove a canonical skill (the next sync auto-removes the
|
|
115
152
|
now-stale symlink on every managed agent)
|
|
116
153
|
--dry-run preview the plan, write nothing
|
|
@@ -132,6 +169,20 @@ Commands:
|
|
|
132
169
|
round-trip byte-for-byte back through memory sync.
|
|
133
170
|
--dry-run preview the plan, write nothing
|
|
134
171
|
--json machine-readable output, no report text
|
|
172
|
+
manage list
|
|
173
|
+
Show the canonical managed-agent write boundary.
|
|
174
|
+
manage set <ids|none>
|
|
175
|
+
manage add <ids>
|
|
176
|
+
manage remove <ids>
|
|
177
|
+
Explicitly replace, extend, or narrow the managed set. Removing
|
|
178
|
+
an agent preserves its existing native state; future Trellis
|
|
179
|
+
writes stop reaching it. Mutations are backup/rollback-enabled.
|
|
180
|
+
--dry-run preview current -> desired, write nothing
|
|
181
|
+
--json machine-readable output
|
|
182
|
+
kimi [args...]
|
|
183
|
+
Launch Kimi Code. When kimi-code Runtime delivery is \`mcp\`,
|
|
184
|
+
starts Kimi with an empty --skills-dir so Skills come from
|
|
185
|
+
trellis-runtime instead of native discovery.
|
|
135
186
|
secrets audit
|
|
136
187
|
Scan each present agent's real MCP config for leaked
|
|
137
188
|
credentials and unexpected env var names
|
|
@@ -148,18 +199,38 @@ Commands:
|
|
|
148
199
|
|
|
149
200
|
See docs/roadmap.md for what's built vs. planned.`);
|
|
150
201
|
}
|
|
202
|
+
function printVersion() {
|
|
203
|
+
console.log(TRELLIS_VERSION);
|
|
204
|
+
}
|
|
151
205
|
async function main(argv) {
|
|
152
206
|
const [command, ...rest] = argv;
|
|
153
207
|
if (!command || command === "--help" || command === "-h") {
|
|
154
208
|
printUsage();
|
|
155
209
|
return;
|
|
156
210
|
}
|
|
211
|
+
if (command === "--version" || command === "-v") {
|
|
212
|
+
printVersion();
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (command === "help") {
|
|
216
|
+
printUsage();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
157
219
|
if (!KNOWN_COMMANDS.includes(command)) {
|
|
158
220
|
console.error(`Unknown command: ${command}\n`);
|
|
159
221
|
printUsage();
|
|
160
222
|
process.exitCode = 1;
|
|
161
223
|
return;
|
|
162
224
|
}
|
|
225
|
+
// `--help`/`-h` anywhere in a subcommand's own args short-circuits before
|
|
226
|
+
// any parsing or real action — a typo'd or exploratory flag (e.g. `mcp
|
|
227
|
+
// sync --help`) must never fall through to actually running the command
|
|
228
|
+
// it was asking about (real incident: `--help` isn't a flag any branch
|
|
229
|
+
// recognizes, so it silently ran a real, non-dry-run `mcp sync`).
|
|
230
|
+
if (command !== "kimi" && (rest.includes("--help") || rest.includes("-h"))) {
|
|
231
|
+
printUsage();
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
163
234
|
if (command === "onboard") {
|
|
164
235
|
const agentIndex = rest.indexOf("--agent");
|
|
165
236
|
const agent = agentIndex >= 0 ? rest[agentIndex + 1] : undefined;
|
|
@@ -173,6 +244,10 @@ async function main(argv) {
|
|
|
173
244
|
const gatewayAgents = gatewayAgentsIndex >= 0 ? rest[gatewayAgentsIndex + 1] : undefined;
|
|
174
245
|
const memoryIndex = rest.indexOf("--memory");
|
|
175
246
|
const memory = memoryIndex >= 0 ? rest[memoryIndex + 1] : undefined;
|
|
247
|
+
const memoryMigrateIndex = rest.indexOf("--memory-migrate");
|
|
248
|
+
const memoryMigrate = memoryMigrateIndex >= 0 ? rest[memoryMigrateIndex + 1] : undefined;
|
|
249
|
+
const selectionIndex = rest.indexOf("--selection");
|
|
250
|
+
const selectionFile = selectionIndex >= 0 ? rest[selectionIndex + 1] : undefined;
|
|
176
251
|
const { exitCode } = await runOnboard({
|
|
177
252
|
agent,
|
|
178
253
|
manage,
|
|
@@ -180,6 +255,8 @@ async function main(argv) {
|
|
|
180
255
|
hubUrl,
|
|
181
256
|
gatewayAgents,
|
|
182
257
|
memory,
|
|
258
|
+
memoryMigrate,
|
|
259
|
+
selectionFile,
|
|
183
260
|
dryRun: rest.includes("--dry-run"),
|
|
184
261
|
json: rest.includes("--json"),
|
|
185
262
|
});
|
|
@@ -208,14 +285,16 @@ async function main(argv) {
|
|
|
208
285
|
// Spawned by an agent, never typed by a person: stdout is the MCP
|
|
209
286
|
// protocol stream for the rest of this process's life, so nothing here
|
|
210
287
|
// may print to it (trellis-mcp-gateway-hosting design.md D2).
|
|
211
|
-
if (command === "mcp-gateway") {
|
|
288
|
+
if (command === "mcp-gateway" || command === "mcp-runtime") {
|
|
212
289
|
const parsed = parseMcpGatewayArgs(rest);
|
|
213
290
|
if ("error" in parsed) {
|
|
214
291
|
console.error(parsed.error);
|
|
215
292
|
process.exitCode = 1;
|
|
216
293
|
return;
|
|
217
294
|
}
|
|
218
|
-
const { exitCode } =
|
|
295
|
+
const { exitCode } = command === "mcp-runtime"
|
|
296
|
+
? await runMcpRuntime({ agentId: parsed.agentId })
|
|
297
|
+
: await runMcpGateway({ agentId: parsed.agentId });
|
|
219
298
|
process.exitCode = exitCode;
|
|
220
299
|
return;
|
|
221
300
|
}
|
|
@@ -249,6 +328,18 @@ async function main(argv) {
|
|
|
249
328
|
process.exitCode = runMcpAdd(name, parseMcpAddArgs(mcpRest), { json, dryRun }).exitCode;
|
|
250
329
|
return;
|
|
251
330
|
}
|
|
331
|
+
if (subcommand === "import") {
|
|
332
|
+
const [file] = mcpRest;
|
|
333
|
+
process.exitCode = runMcpImport(file, { json, dryRun }).exitCode;
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
if (subcommand === "set") {
|
|
337
|
+
const [name] = mcpRest;
|
|
338
|
+
const authIndex = mcpRest.indexOf("--auth");
|
|
339
|
+
const auth = authIndex >= 0 ? mcpRest[authIndex + 1] : undefined;
|
|
340
|
+
process.exitCode = runMcpSetAuth(name, auth, { json, dryRun }).exitCode;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
252
343
|
if (subcommand === "remove") {
|
|
253
344
|
const [name] = mcpRest;
|
|
254
345
|
if (!name) {
|
|
@@ -270,7 +361,7 @@ async function main(argv) {
|
|
|
270
361
|
process.exitCode = exitCode;
|
|
271
362
|
return;
|
|
272
363
|
}
|
|
273
|
-
console.error(`Unknown mcp subcommand: ${subcommand ?? "(none)"}\nUsage: trellis mcp sync|list|add <name>|remove <name>|auth <name>\n`);
|
|
364
|
+
console.error(`Unknown mcp subcommand: ${subcommand ?? "(none)"}\nUsage: trellis mcp sync|list|add <name>|set <name>|remove <name>|auth <name>\n`);
|
|
274
365
|
process.exitCode = 1;
|
|
275
366
|
return;
|
|
276
367
|
}
|
|
@@ -304,7 +395,11 @@ async function main(argv) {
|
|
|
304
395
|
process.exitCode = runSkillRemove(name, { json, dryRun }).exitCode;
|
|
305
396
|
return;
|
|
306
397
|
}
|
|
307
|
-
|
|
398
|
+
if (subcommand === "update-builtin") {
|
|
399
|
+
process.exitCode = runSkillUpdateBuiltin({ json, dryRun }).exitCode;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
console.error(`Unknown skill subcommand: ${subcommand ?? "(none)"}\nUsage: trellis skill list|add <name> --from <path>|remove <name>|update-builtin\n`);
|
|
308
403
|
process.exitCode = 1;
|
|
309
404
|
return;
|
|
310
405
|
}
|
|
@@ -324,6 +419,20 @@ async function main(argv) {
|
|
|
324
419
|
process.exitCode = 1;
|
|
325
420
|
return;
|
|
326
421
|
}
|
|
422
|
+
if (command === "manage") {
|
|
423
|
+
const parsed = parseManageArgs(rest);
|
|
424
|
+
if ("error" in parsed) {
|
|
425
|
+
console.error(`${parsed.error}\nUsage: trellis manage list|set <ids|none>|add <ids>|remove <ids> [--dry-run] [--json]\n`);
|
|
426
|
+
process.exitCode = 1;
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
process.exitCode = runManage(parsed).exitCode;
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
if (command === "kimi") {
|
|
433
|
+
process.exitCode = (await runKimi(rest)).exitCode;
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
327
436
|
if (command === "secrets") {
|
|
328
437
|
const [subcommand] = rest;
|
|
329
438
|
if (subcommand !== "audit") {
|