agent-trellis 0.15.1 → 0.17.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.
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { loadCanonicalSource } from "../core/canonical.js";
|
|
8
8
|
import type { AgentId, AgentSnapshot, CanonicalSource } from "../core/types.js";
|
|
9
|
+
import { type VersionExecutor } from "../lib/cliEntrypoints.js";
|
|
9
10
|
import { type WorkspaceDoctorSection } from "../lib/workspaceDoctor.js";
|
|
10
11
|
/**
|
|
11
12
|
* Interim default until P1 wires this from `.trellis/mcp/servers.yaml`'s
|
|
@@ -15,7 +16,7 @@ import { type WorkspaceDoctorSection } from "../lib/workspaceDoctor.js";
|
|
|
15
16
|
* machine.
|
|
16
17
|
*/
|
|
17
18
|
export declare const DEFAULT_KNOWN_HOST_INJECTED: readonly string[];
|
|
18
|
-
export type FindingKind = "probe-failed" | "duplication" | "collision" | "drift" | "case-mismatch" | "parse-diagnostic" | "runtime-drift" | "oauth-expired" | "mcp-unreachable";
|
|
19
|
+
export type FindingKind = "probe-failed" | "duplication" | "collision" | "drift" | "case-mismatch" | "parse-diagnostic" | "runtime-drift" | "oauth-expired" | "mcp-unreachable" | "workspace-toml-invalid" | "cli-version-split";
|
|
19
20
|
export interface Finding {
|
|
20
21
|
kind: FindingKind;
|
|
21
22
|
/** Omitted for a finding that names more than one agent (drift). */
|
|
@@ -52,6 +53,10 @@ export interface RunDoctorOptions {
|
|
|
52
53
|
* seam. CLI discovery walks up from cwd via `findWorkspaceRoot`;
|
|
53
54
|
* null = no workspace layer. */
|
|
54
55
|
workspaceDir?: string | null;
|
|
56
|
+
/** PATH to scan for `trellis` entrypoints; defaults to the real one.
|
|
57
|
+
* Test seam, with `execVersion`. */
|
|
58
|
+
pathEnv?: string;
|
|
59
|
+
execVersion?: VersionExecutor;
|
|
55
60
|
}
|
|
56
61
|
export interface OAuthHealthEntry {
|
|
57
62
|
server: string;
|
package/dist/commands/doctor.js
CHANGED
|
@@ -17,6 +17,7 @@ import { ALL_AGENTS, isOAuthAuth, oauthOwner } from "../core/types.js";
|
|
|
17
17
|
import { classifyStoredToken, remediationFor } from "../lib/oauth/renew.js";
|
|
18
18
|
import { readToken } from "../lib/oauth/store.js";
|
|
19
19
|
import { findWorkspaceRoot } from "../lib/workspace.js";
|
|
20
|
+
import { detectCliVersionSplit, findCliEntrypoints } from "../lib/cliEntrypoints.js";
|
|
20
21
|
import { collectWorkspaceDoctorSection } from "../lib/workspaceDoctor.js";
|
|
21
22
|
/**
|
|
22
23
|
* Interim default until P1 wires this from `.trellis/mcp/servers.yaml`'s
|
|
@@ -44,6 +45,13 @@ const EXIT_NONZERO_KINDS = new Set([
|
|
|
44
45
|
// exists to catch *before* the session does (trellis-mcp-oauth-renew
|
|
45
46
|
// design.md D4).
|
|
46
47
|
"oauth-expired",
|
|
48
|
+
// A project-level Codex config that fails TOML parsing will fail the
|
|
49
|
+
// *agent*'s own startup regardless of ownership — catch it here before
|
|
50
|
+
// the next session does (trellis-doctor-workspace-toml-validation).
|
|
51
|
+
"workspace-toml-invalid",
|
|
52
|
+
// A stale `trellis` that wins on PATH silently changes behavior (e.g.
|
|
53
|
+
// rejecting config keys a newer copy accepts) — trellis-doctor-cli-version-split.
|
|
54
|
+
"cli-version-split",
|
|
47
55
|
]);
|
|
48
56
|
/** Exported so tests can assert on this resolution directly, without
|
|
49
57
|
* routing through real per-agent probes `runDoctor` also runs. */
|
|
@@ -62,6 +70,12 @@ export function resolveKnownHostInjected(opts) {
|
|
|
62
70
|
export async function runDoctor(opts = {}) {
|
|
63
71
|
const workspaceDir = opts.workspaceDir !== undefined ? opts.workspaceDir : findWorkspaceRoot(process.cwd());
|
|
64
72
|
const report = await collectDoctorReport(opts.homeDir, resolveKnownHostInjected(opts), opts.probeMcp, workspaceDir);
|
|
73
|
+
// CLI boundary only: spawning `trellis --version` belongs here, not in
|
|
74
|
+
// collectDoctorReport, so library callers and their tests never depend
|
|
75
|
+
// on the developer's real PATH.
|
|
76
|
+
const split = detectCliVersionSplit(findCliEntrypoints({ pathEnv: opts.pathEnv, execVersion: opts.execVersion }));
|
|
77
|
+
if (split)
|
|
78
|
+
report.findings.push({ kind: "cli-version-split", message: split });
|
|
65
79
|
const exitCode = report.findings.some((f) => EXIT_NONZERO_KINDS.has(f.kind)) ? 1 : 0;
|
|
66
80
|
if (opts.json) {
|
|
67
81
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -120,7 +134,53 @@ export async function collectDoctorReport(homeDir = homedir(), knownHostInjected
|
|
|
120
134
|
// canonical has not been initialized yet.
|
|
121
135
|
}
|
|
122
136
|
}
|
|
123
|
-
|
|
137
|
+
// Workspace attribution runs whenever a workspace root is in scope —
|
|
138
|
+
// even when `~/.trellis/` itself failed to load or has never been
|
|
139
|
+
// initialized. The spec's third scenario: a user managing only project
|
|
140
|
+
// skills in `.agents/skills/` without a global Trellis install still
|
|
141
|
+
// gets project-level config validation
|
|
142
|
+
// (trellis-doctor-workspace-toml-validation design.md D5). When the
|
|
143
|
+
// canonical source is absent we synthesize the minimal workspace layer
|
|
144
|
+
// with every map empty — provenance is moot without a global layer, but
|
|
145
|
+
// `collectWorkspaceDoctorSection`'s per-file inspection is what we want.
|
|
146
|
+
let workspace;
|
|
147
|
+
if (canonical?.workspace) {
|
|
148
|
+
workspace = collectWorkspaceDoctorSection(canonical);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const workspaceRoot = workspaceDir != null ? findWorkspaceRoot(workspaceDir) : null;
|
|
152
|
+
if (workspaceRoot) {
|
|
153
|
+
// `managedAgents` is driven by `~/.trellis/managed.yaml`, which is
|
|
154
|
+
// absent in this branch — sweep every known agent so codex's TOML
|
|
155
|
+
// file is still inspected (otherwise an empty list would skip the
|
|
156
|
+
// file entirely and the spec's scenario-3 finding would never fire).
|
|
157
|
+
const minimal = {
|
|
158
|
+
instructionsFile: "",
|
|
159
|
+
managedAgents: ALL_AGENTS,
|
|
160
|
+
skills: [],
|
|
161
|
+
agents: [],
|
|
162
|
+
memories: [],
|
|
163
|
+
mcp: { servers: {}, knownHostInjected: [] },
|
|
164
|
+
secretsPolicy: { allowedVars: [], rejectPatterns: [] },
|
|
165
|
+
diagnostics: [],
|
|
166
|
+
workspace: { root: workspaceRoot, mcpProvenance: {}, mcpGlobalServers: {}, skillProvenance: {}, globalSkills: [], shadowed: [] },
|
|
167
|
+
};
|
|
168
|
+
workspace = collectWorkspaceDoctorSection(minimal);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Workspace TOML findings become top-level findings so the exit code
|
|
172
|
+
// and `--json` consumers see them alongside cross-agent findings, not
|
|
173
|
+
// only inside the workspace section. They keep `agent: "codex"` — the
|
|
174
|
+
// file layout tells us which agent the file breaks.
|
|
175
|
+
if (workspace) {
|
|
176
|
+
for (const finding of workspace.tomlFindings) {
|
|
177
|
+
findings.push({
|
|
178
|
+
kind: "workspace-toml-invalid",
|
|
179
|
+
agent: finding.agent,
|
|
180
|
+
message: `${finding.path}${finding.line !== undefined ? `:${finding.line}${finding.column !== undefined ? `:${finding.column}` : ""}` : ""} — ${finding.reason} (not rendered by Trellis; fix by hand)`,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
124
184
|
const oauth = canonical ? collectOAuthHealth(homeDir, canonical) : [];
|
|
125
185
|
findings.push(...detectDuplication(snapshots), ...detectCollisions(snapshots, knownHostInjected), ...detectCrossAgentDrift(snapshots), ...detectCaseMismatches(snapshots), ...detectParseDiagnostics(snapshots), ...(canonical ? detectRuntimeDrift(snapshots, canonical) : []), ...detectMcpUnreachable(snapshots), ...oauth.flatMap((entry) => (entry.status === "valid" ? [] : [oauthFinding(entry)])));
|
|
126
186
|
return { snapshots, findings, ...(oauth.length > 0 ? { oauth } : {}), ...(workspace ? { workspace } : {}) };
|
|
@@ -395,6 +455,10 @@ export function printReport(report) {
|
|
|
395
455
|
for (const hint of ws.unmigrated) {
|
|
396
456
|
console.log(` ⚠ ${hint.path} holds ${hint.entries.length} entr${hint.entries.length === 1 ? "y" : "ies"} Trellis didn't render (${hint.entries.join(", ")}) — run \`trellis migrate --workspace\``);
|
|
397
457
|
}
|
|
458
|
+
for (const finding of ws.tomlFindings) {
|
|
459
|
+
const location = finding.line !== undefined ? `:${finding.line}${finding.column !== undefined ? `:${finding.column}` : ""}` : "";
|
|
460
|
+
console.log(` ⚠ ${finding.path}${location} — ${finding.reason} (not rendered by Trellis — fix by hand and re-run \`trellis doctor\`)`);
|
|
461
|
+
}
|
|
398
462
|
for (const warning of ws.precedenceWarnings) {
|
|
399
463
|
console.log(` ⚠ ${warning}`);
|
|
400
464
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locates every `trellis` executable on PATH and compares their versions
|
|
3
|
+
* (trellis-doctor-cli-version-split). Read-only: it never edits, removes
|
|
4
|
+
* or upgrades an entrypoint — a wrapper script may be deliberately
|
|
5
|
+
* maintained by the user, so Trellis only reports.
|
|
6
|
+
*/
|
|
7
|
+
export interface CliEntrypoint {
|
|
8
|
+
path: string;
|
|
9
|
+
version: string;
|
|
10
|
+
}
|
|
11
|
+
export type VersionExecutor = (path: string) => string | undefined;
|
|
12
|
+
/** Runs `<path> --version` via argv (no shell). `undefined` on any
|
|
13
|
+
* failure or unrecognizable output, so a broken entry is skipped rather
|
|
14
|
+
* than reported as a split. */
|
|
15
|
+
export declare const defaultExecVersion: VersionExecutor;
|
|
16
|
+
export declare function findCliEntrypoints(opts?: {
|
|
17
|
+
pathEnv?: string;
|
|
18
|
+
execVersion?: VersionExecutor;
|
|
19
|
+
name?: string;
|
|
20
|
+
}): CliEntrypoint[];
|
|
21
|
+
/** A finding message when two or more entries disagree; `undefined`
|
|
22
|
+
* otherwise. The first entry is the one a bare `trellis` runs. */
|
|
23
|
+
export declare function detectCliVersionSplit(entries: readonly CliEntrypoint[]): string | undefined;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locates every `trellis` executable on PATH and compares their versions
|
|
3
|
+
* (trellis-doctor-cli-version-split). Read-only: it never edits, removes
|
|
4
|
+
* or upgrades an entrypoint — a wrapper script may be deliberately
|
|
5
|
+
* maintained by the user, so Trellis only reports.
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from "node:child_process";
|
|
8
|
+
import { accessSync, constants, realpathSync, statSync } from "node:fs";
|
|
9
|
+
import { delimiter, join } from "node:path";
|
|
10
|
+
const VERSION_RE = /\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/;
|
|
11
|
+
/** Runs `<path> --version` via argv (no shell). `undefined` on any
|
|
12
|
+
* failure or unrecognizable output, so a broken entry is skipped rather
|
|
13
|
+
* than reported as a split. */
|
|
14
|
+
export const defaultExecVersion = (path) => {
|
|
15
|
+
try {
|
|
16
|
+
const out = execFileSync(path, ["--version"], { encoding: "utf-8", timeout: 5_000, stdio: ["ignore", "pipe", "ignore"] });
|
|
17
|
+
return VERSION_RE.exec(out)?.[0];
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
function isExecutableFile(path) {
|
|
24
|
+
try {
|
|
25
|
+
if (!statSync(path).isFile())
|
|
26
|
+
return false;
|
|
27
|
+
accessSync(path, constants.X_OK);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function findCliEntrypoints(opts = {}) {
|
|
35
|
+
const pathEnv = opts.pathEnv ?? process.env.PATH ?? "";
|
|
36
|
+
const execVersion = opts.execVersion ?? defaultExecVersion;
|
|
37
|
+
const name = opts.name ?? "trellis";
|
|
38
|
+
const seenDirs = new Set();
|
|
39
|
+
const seenReal = new Set();
|
|
40
|
+
const entries = [];
|
|
41
|
+
for (const dir of pathEnv.split(delimiter)) {
|
|
42
|
+
if (!dir || seenDirs.has(dir))
|
|
43
|
+
continue;
|
|
44
|
+
seenDirs.add(dir);
|
|
45
|
+
const candidate = join(dir, name);
|
|
46
|
+
if (!isExecutableFile(candidate))
|
|
47
|
+
continue;
|
|
48
|
+
let real;
|
|
49
|
+
try {
|
|
50
|
+
real = realpathSync(candidate);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (seenReal.has(real))
|
|
56
|
+
continue;
|
|
57
|
+
seenReal.add(real);
|
|
58
|
+
// Spawn the PATH-visible path, not the resolved target: a shim's
|
|
59
|
+
// behavior depends on the name it is invoked by.
|
|
60
|
+
const version = execVersion(candidate);
|
|
61
|
+
if (version)
|
|
62
|
+
entries.push({ path: candidate, version });
|
|
63
|
+
}
|
|
64
|
+
return entries;
|
|
65
|
+
}
|
|
66
|
+
/** A finding message when two or more entries disagree; `undefined`
|
|
67
|
+
* otherwise. The first entry is the one a bare `trellis` runs. */
|
|
68
|
+
export function detectCliVersionSplit(entries) {
|
|
69
|
+
if (entries.length < 2)
|
|
70
|
+
return undefined;
|
|
71
|
+
if (new Set(entries.map((e) => e.version)).size < 2)
|
|
72
|
+
return undefined;
|
|
73
|
+
const lines = entries.map((e, i) => ` ${i === 0 ? "▶ runs" : " also"} ${e.path} ${e.version}`);
|
|
74
|
+
return [
|
|
75
|
+
`${entries.length} \`trellis\` entrypoints on PATH report different versions — a bare \`trellis\` runs ${entries[0].path} (${entries[0].version}), so upgrading another copy has no effect:`,
|
|
76
|
+
...lines,
|
|
77
|
+
"Reconcile them (upgrade or remove the stale one, or reorder PATH) — Trellis does not modify entrypoints.",
|
|
78
|
+
].join("\n");
|
|
79
|
+
}
|
|
@@ -20,6 +20,19 @@ export interface WorkspaceUnmigratedHint {
|
|
|
20
20
|
/** Entry names present in the file with no Trellis manifest record. */
|
|
21
21
|
entries: string[];
|
|
22
22
|
}
|
|
23
|
+
/** One parse-level failure when inspecting an agent's project MCP file.
|
|
24
|
+
* Distinct from `parse-diagnostic` (agent-global probes). Even when Trellis
|
|
25
|
+
* has never rendered into this file, a project file that breaks the agent's
|
|
26
|
+
* own startup is exactly what doctor exists to surface
|
|
27
|
+
* (trellis-doctor-workspace-toml-validation). */
|
|
28
|
+
export interface WorkspaceTomlFinding {
|
|
29
|
+
agent: AgentId;
|
|
30
|
+
path: string;
|
|
31
|
+
/** Parser's 1-based line and column of the failure, when known. */
|
|
32
|
+
line?: number;
|
|
33
|
+
column?: number;
|
|
34
|
+
reason: string;
|
|
35
|
+
}
|
|
23
36
|
export interface WorkspaceDoctorSection {
|
|
24
37
|
root: string;
|
|
25
38
|
mcp: WorkspaceLayerEntry[];
|
|
@@ -38,6 +51,8 @@ export interface WorkspaceDoctorSection {
|
|
|
38
51
|
name: string;
|
|
39
52
|
}[];
|
|
40
53
|
unmigrated: WorkspaceUnmigratedHint[];
|
|
54
|
+
/** Parse-level failures on workspace agent MCP files (usually empty). */
|
|
55
|
+
tomlFindings: WorkspaceTomlFinding[];
|
|
41
56
|
/** zcode's user-level-first deviation notices (empty otherwise). */
|
|
42
57
|
precedenceWarnings: string[];
|
|
43
58
|
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* from the already-loaded canonical source and file reads only.
|
|
11
11
|
*/
|
|
12
12
|
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { parse as parseToml } from "smol-toml";
|
|
13
14
|
import { resolveSecretEnv } from "./secretEnv.js";
|
|
14
15
|
import { loadWorkspaceRendered, workspaceMcpConfigPath, workspacePrecedenceWarnings } from "./workspaceMcp.js";
|
|
15
16
|
const VAR_REF_RE = /^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/;
|
|
@@ -36,15 +37,36 @@ function collectVarRefs(canonical) {
|
|
|
36
37
|
return refs;
|
|
37
38
|
}
|
|
38
39
|
/** Entry names a JSON `mcpServers` file / Codex TOML / zcode nested
|
|
39
|
-
* config holds beyond what the in-repo manifest records Trellis
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* config holds beyond what the in-repo manifest records Trellis rendering.
|
|
41
|
+
*
|
|
42
|
+
* The Codex branch TOML-parses the file first — syntactically invalid TOML
|
|
43
|
+
* is indistinguishable from valid TOML to the section-header regex, but is
|
|
44
|
+
* fatal to Codex's own startup, so it is surfaced as `invalid` and never
|
|
45
|
+
* turned into an unmigrated hint. The parsed AST is discarded on success:
|
|
46
|
+
* header extraction keeps using the existing regex to avoid changing edge
|
|
47
|
+
* semantics (dotted quoted keys, single-quoted keys) that the regex was
|
|
48
|
+
* already validated against — smol-toml is used parse-only, never
|
|
49
|
+
* round-tripped, respecting lib/tomlSection.ts's write-path constraint. */
|
|
42
50
|
function unmarkedEntries(agentId, configPath, recorded) {
|
|
43
51
|
if (!existsSync(configPath))
|
|
44
|
-
return [];
|
|
52
|
+
return { kind: "ok", names: [] };
|
|
45
53
|
let names = [];
|
|
46
54
|
if (agentId === "codex") {
|
|
47
55
|
const content = readFileSync(configPath, "utf-8");
|
|
56
|
+
try {
|
|
57
|
+
parseToml(content);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const tomLError = err;
|
|
61
|
+
const reason = tomLError.message?.split("\n")[0] ?? String(err);
|
|
62
|
+
return {
|
|
63
|
+
kind: "invalid",
|
|
64
|
+
path: configPath,
|
|
65
|
+
...(typeof tomLError.line === "number" ? { line: tomLError.line } : {}),
|
|
66
|
+
...(typeof tomLError.column === "number" ? { column: tomLError.column + 1 } : {}),
|
|
67
|
+
reason,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
48
70
|
const re = /^\[mcp_servers\.([^\]]+)\]\s*$/gm;
|
|
49
71
|
let match;
|
|
50
72
|
while ((match = re.exec(content)) !== null)
|
|
@@ -59,10 +81,10 @@ function unmarkedEntries(agentId, configPath, recorded) {
|
|
|
59
81
|
names = Object.keys(servers);
|
|
60
82
|
}
|
|
61
83
|
catch {
|
|
62
|
-
return []; // unparsable file is the sync guard's finding, not doctor's
|
|
84
|
+
return { kind: "ok", names: [] }; // unparsable file is the sync guard's finding, not doctor's
|
|
63
85
|
}
|
|
64
86
|
}
|
|
65
|
-
return names.filter((name) => recorded[name] === undefined);
|
|
87
|
+
return { kind: "ok", names: names.filter((name) => recorded[name] === undefined) };
|
|
66
88
|
}
|
|
67
89
|
export function collectWorkspaceDoctorSection(canonical) {
|
|
68
90
|
const workspace = canonical.workspace;
|
|
@@ -83,12 +105,17 @@ export function collectWorkspaceDoctorSection(canonical) {
|
|
|
83
105
|
const unresolvedVars = refs.filter((r) => resolved[r.name] === undefined);
|
|
84
106
|
const manifest = loadWorkspaceRendered(workspace.root);
|
|
85
107
|
const unmigrated = [];
|
|
108
|
+
const tomlFindings = [];
|
|
86
109
|
for (const agentId of canonical.managedAgents) {
|
|
87
110
|
const configPath = workspaceMcpConfigPath(agentId, workspace.root);
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
|
|
111
|
+
const result = unmarkedEntries(agentId, configPath, manifest.mcp?.[agentId] ?? {});
|
|
112
|
+
if (result.kind === "invalid") {
|
|
113
|
+
tomlFindings.push({ agent: agentId, path: result.path, ...(result.line !== undefined ? { line: result.line } : {}), ...(result.column !== undefined ? { column: result.column } : {}), reason: result.reason });
|
|
114
|
+
continue; // never report an unparseable file's entries as unmigrated — spec req 2
|
|
115
|
+
}
|
|
116
|
+
if (result.names.length > 0)
|
|
117
|
+
unmigrated.push({ agent: agentId, path: configPath, entries: result.names });
|
|
91
118
|
}
|
|
92
119
|
const precedenceWarnings = canonical.managedAgents.flatMap((id) => workspacePrecedenceWarnings(id, canonical));
|
|
93
|
-
return { root: workspace.root, mcp, skills, shadowed: workspace.shadowed, tombstoned, unresolvedVars, unmigrated, precedenceWarnings };
|
|
120
|
+
return { root: workspace.root, mcp, skills, shadowed: workspace.shadowed, tombstoned, unresolvedVars, unmigrated, tomlFindings, precedenceWarnings };
|
|
94
121
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-trellis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "A unified Code Agent Runtime for Skills, MCP, Memory, and shared instructions across Claude Code, Codex, Kiro, pi, Kimi Code, and ZCode.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Paul Leo",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"@modelcontextprotocol/client": "^2.3.1",
|
|
65
65
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
66
66
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
67
|
+
"smol-toml": "^1.9.1",
|
|
67
68
|
"yaml": "^2.9.1",
|
|
68
69
|
"zod": "^4.6.5"
|
|
69
70
|
}
|