@ag-eco/agentplate-cli 0.13.1 → 0.13.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ag-eco/agentplate-cli",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "Multi-agent orchestration for AI coding agents — spawn workers in git worktrees via tmux, coordinate through SQLite mail, merge with tiered conflict resolution. Pluggable runtime adapters for Claude Code, Pi, and more.",
5
5
  "author": "Jaymin West",
6
6
  "license": "MIT",
@@ -473,6 +473,23 @@ describe("initCommand: --name flag", () => {
473
473
  * Build a Spawner that returns preset responses keyed by "arg0 arg1 ..." prefix.
474
474
  * Records all calls for assertion.
475
475
  */
476
+ /**
477
+ * Normalize the bundled tool invocation `[bun, <…/tools/<tool>/entry.ts>, ...cmd]`
478
+ * back to the logical `[cli, ...cmd]` (e.g. `["lm", "init"]`) so tests can keep
479
+ * asserting on the user-facing command shape regardless of how init spawns it.
480
+ */
481
+ function normalizeToolArgs(args: string[]): string[] {
482
+ const entry = args[1] ?? "";
483
+ const cli = entry.includes("/tools/loam/")
484
+ ? "lm"
485
+ : entry.includes("/tools/sprout/")
486
+ ? "sr"
487
+ : entry.includes("/tools/trellis/")
488
+ ? "tl"
489
+ : null;
490
+ return cli ? [cli, ...args.slice(2)] : args;
491
+ }
492
+
476
493
  function createMockSpawner(
477
494
  responses: Record<string, { exitCode: number; stdout: string; stderr: string }>,
478
495
  ): {
@@ -480,7 +497,8 @@ function createMockSpawner(
480
497
  calls: string[][];
481
498
  } {
482
499
  const calls: string[][] = [];
483
- const spawner: Spawner = async (args) => {
500
+ const spawner: Spawner = async (rawArgs) => {
501
+ const args = normalizeToolArgs(rawArgs);
484
502
  calls.push(args);
485
503
  const key = args.join(" ");
486
504
  // Longest prefix match
@@ -52,17 +52,54 @@ const defaultSpawner: Spawner = async (args, opts) => {
52
52
  interface SiblingTool {
53
53
  name: string;
54
54
  cli: string;
55
+ /** Bundled entrypoint, relative to this file (src/commands/). */
56
+ entry: string;
55
57
  dotDir: string;
56
58
  initCmd: string[];
57
59
  onboardCmd: string[];
58
60
  }
59
61
 
62
+ // loam/sprout/trellis are bundled inside this package (src/tools/*), so they are
63
+ // invoked via their bundled entrypoints with the current bun runtime rather than
64
+ // looked up as `lm`/`sr`/`tl` on PATH. This makes `ap init` work even when
65
+ // ~/.npm-global/bin (or wherever the package bins live) isn't on the PATH of the
66
+ // `ap` process — the cause of spurious "loam/sprout/trellis not installed" errors.
60
67
  const SIBLING_TOOLS: SiblingTool[] = [
61
- { name: "loam", cli: "lm", dotDir: ".loam", initCmd: ["init"], onboardCmd: ["onboard"] },
62
- { name: "sprout", cli: "sr", dotDir: ".sprout", initCmd: ["init"], onboardCmd: ["onboard"] },
63
- { name: "trellis", cli: "tl", dotDir: ".trellis", initCmd: ["init"], onboardCmd: ["onboard"] },
68
+ {
69
+ name: "loam",
70
+ cli: "lm",
71
+ entry: "../tools/loam/cli.ts",
72
+ dotDir: ".loam",
73
+ initCmd: ["init"],
74
+ onboardCmd: ["onboard"],
75
+ },
76
+ {
77
+ name: "sprout",
78
+ cli: "sr",
79
+ entry: "../tools/sprout/index.ts",
80
+ dotDir: ".sprout",
81
+ initCmd: ["init"],
82
+ onboardCmd: ["onboard"],
83
+ },
84
+ {
85
+ name: "trellis",
86
+ cli: "tl",
87
+ entry: "../tools/trellis/index.ts",
88
+ dotDir: ".trellis",
89
+ initCmd: ["init"],
90
+ onboardCmd: ["onboard"],
91
+ },
64
92
  ];
65
93
 
94
+ /**
95
+ * Build the argv to run a bundled tool's CLI: the current bun runtime plus the
96
+ * tool's entrypoint resolved inside this package. PATH-independent.
97
+ */
98
+ function toolArgv(tool: SiblingTool, ...cmd: string[]): string[] {
99
+ const entryPath = new URL(tool.entry, import.meta.url).pathname;
100
+ return [process.execPath, entryPath, ...cmd];
101
+ }
102
+
66
103
  type ToolStatus = "initialized" | "already_initialized" | "skipped";
67
104
  type OnboardStatus = "appended" | "current";
68
105
 
@@ -85,9 +122,9 @@ export function resolveToolSet(opts: InitOptions): SiblingTool[] {
85
122
  });
86
123
  }
87
124
 
88
- async function isToolInstalled(cli: string, spawner: Spawner): Promise<boolean> {
125
+ async function isToolInstalled(tool: SiblingTool, spawner: Spawner): Promise<boolean> {
89
126
  try {
90
- const result = await spawner([cli, "--version"]);
127
+ const result = await spawner(toolArgv(tool, "--version"));
91
128
  return result.exitCode === 0;
92
129
  } catch {
93
130
  return false;
@@ -99,18 +136,20 @@ async function initSiblingTool(
99
136
  projectRoot: string,
100
137
  spawner: Spawner,
101
138
  ): Promise<ToolStatus> {
102
- const installed = await isToolInstalled(tool.cli, spawner);
139
+ const installed = await isToolInstalled(tool, spawner);
103
140
  if (!installed) {
141
+ // Bundled in this package — if this fires, the agentplate install itself
142
+ // is incomplete rather than a missing separate package.
104
143
  printWarning(
105
- `${tool.name} not installed — skipping`,
106
- `install: npm i -g @ag-eco/${tool.name}-cli`,
144
+ `${tool.name} unavailable — skipping`,
145
+ "bundled in @ag-eco/agentplate-cli; try reinstalling: npm i -g @ag-eco/agentplate-cli",
107
146
  );
108
147
  return "skipped";
109
148
  }
110
149
 
111
150
  let result: { exitCode: number; stdout: string; stderr: string };
112
151
  try {
113
- result = await spawner([tool.cli, ...tool.initCmd], { cwd: projectRoot });
152
+ result = await spawner(toolArgv(tool, ...tool.initCmd), { cwd: projectRoot });
114
153
  } catch (err) {
115
154
  // Spawn failure (e.g. ENOENT) — treat as not installed
116
155
  const message = err instanceof Error ? err.message : String(err);
@@ -138,11 +177,11 @@ async function onboardTool(
138
177
  projectRoot: string,
139
178
  spawner: Spawner,
140
179
  ): Promise<OnboardStatus> {
141
- const installed = await isToolInstalled(tool.cli, spawner);
180
+ const installed = await isToolInstalled(tool, spawner);
142
181
  if (!installed) return "current";
143
182
 
144
183
  try {
145
- const result = await spawner([tool.cli, ...tool.onboardCmd], { cwd: projectRoot });
184
+ const result = await spawner(toolArgv(tool, ...tool.onboardCmd), { cwd: projectRoot });
146
185
  return result.exitCode === 0 ? "appended" : "current";
147
186
  } catch {
148
187
  return "current";
@@ -49,14 +49,17 @@ describe("checkDatabases", () => {
49
49
  rmSync(tempDir, { recursive: true, force: true });
50
50
  });
51
51
 
52
- test("fails when required database files do not exist (merge-queue.db is optional)", () => {
52
+ test("fails only for sessions.db when no databases exist (mail/metrics/merge-queue are optional)", () => {
53
53
  const checks = checkDatabases(mockConfig, tempDir) as DoctorCheck[];
54
54
 
55
55
  expect(checks).toHaveLength(4);
56
- expect(checks[0]?.status).toBe("fail");
56
+ // mail.db and metrics.db are created lazily on first write (first mail /
57
+ // first session-end metric) — their absence in a fresh project is normal.
58
+ expect(checks[0]?.status).toBe("pass");
57
59
  expect(checks[0]?.name).toBe("mail.db exists");
58
- expect(checks[1]?.status).toBe("fail");
60
+ expect(checks[1]?.status).toBe("pass");
59
61
  expect(checks[1]?.name).toBe("metrics.db exists");
62
+ // sessions.db is created at `ap init` (run creation), so its absence is a failure.
60
63
  expect(checks[2]?.status).toBe("fail");
61
64
  expect(checks[2]?.name).toBe("sessions.db exists");
62
65
  // merge-queue.db is created lazily on first merge — its absence is normal.
@@ -21,6 +21,7 @@ export const checkDatabases: DoctorCheckFn = (_config, agentplateDir): DoctorChe
21
21
  }> = [
22
22
  {
23
23
  name: "mail.db",
24
+ optional: true,
24
25
  tables: ["messages"],
25
26
  requiredColumns: {
26
27
  messages: [
@@ -40,6 +41,7 @@ export const checkDatabases: DoctorCheckFn = (_config, agentplateDir): DoctorChe
40
41
  },
41
42
  {
42
43
  name: "metrics.db",
44
+ optional: true,
43
45
  tables: ["sessions"],
44
46
  requiredColumns: {
45
47
  sessions: [
@@ -246,7 +246,9 @@ describe("checkEcosystem", () => {
246
246
  const results = await check(mockConfig, "/tmp/.agentplate");
247
247
 
248
248
  const loam = results.find((r) => r.name === "loam semver");
249
- const hasHint = loam?.details?.some((d) => d.includes("@ag-eco/loam-cli"));
249
+ // loam/sprout/trellis are bundled into @ag-eco/agentplate-cli now, so the
250
+ // install hint points at the single package, not a standalone @ag-eco/loam-cli.
251
+ const hasHint = loam?.details?.some((d) => d.includes("@ag-eco/agentplate-cli"));
250
252
  expect(hasHint).toBe(true);
251
253
  });
252
254
 
package/src/version.ts CHANGED
@@ -2,4 +2,4 @@
2
2
  * Single source of truth for the package version, shared by every bundled bin
3
3
  * (ap/agentplate, lm/loam, sr, tl). Updated by scripts/version-bump.ts.
4
4
  */
5
- export const VERSION = "0.13.1";
5
+ export const VERSION = "0.13.3";