@relay-baton/cli 1.0.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.
Files changed (82) hide show
  1. package/LICENSE +21 -0
  2. package/dist/commands/agentFor.d.ts +8 -0
  3. package/dist/commands/agentFor.js +28 -0
  4. package/dist/commands/auditApiKeyEnv.d.ts +6 -0
  5. package/dist/commands/auditApiKeyEnv.js +27 -0
  6. package/dist/commands/budget.d.ts +5 -0
  7. package/dist/commands/budget.js +86 -0
  8. package/dist/commands/chat.d.ts +5 -0
  9. package/dist/commands/chat.js +218 -0
  10. package/dist/commands/checkpoint.d.ts +10 -0
  11. package/dist/commands/checkpoint.js +78 -0
  12. package/dist/commands/compact.d.ts +4 -0
  13. package/dist/commands/compact.js +22 -0
  14. package/dist/commands/compress.d.ts +4 -0
  15. package/dist/commands/compress.js +61 -0
  16. package/dist/commands/compressContext.d.ts +8 -0
  17. package/dist/commands/compressContext.js +51 -0
  18. package/dist/commands/conversation.d.ts +8 -0
  19. package/dist/commands/conversation.js +90 -0
  20. package/dist/commands/diagnostics.d.ts +23 -0
  21. package/dist/commands/diagnostics.js +254 -0
  22. package/dist/commands/doctor.d.ts +5 -0
  23. package/dist/commands/doctor.js +104 -0
  24. package/dist/commands/execute.d.ts +9 -0
  25. package/dist/commands/execute.js +183 -0
  26. package/dist/commands/git.d.ts +6 -0
  27. package/dist/commands/git.js +82 -0
  28. package/dist/commands/guard.d.ts +7 -0
  29. package/dist/commands/guard.js +30 -0
  30. package/dist/commands/handoff.d.ts +10 -0
  31. package/dist/commands/handoff.js +133 -0
  32. package/dist/commands/handoffBundle.d.ts +12 -0
  33. package/dist/commands/handoffBundle.js +64 -0
  34. package/dist/commands/handoffHistory.d.ts +23 -0
  35. package/dist/commands/handoffHistory.js +129 -0
  36. package/dist/commands/handoffShow.d.ts +12 -0
  37. package/dist/commands/handoffShow.js +73 -0
  38. package/dist/commands/init.d.ts +2 -0
  39. package/dist/commands/init.js +19 -0
  40. package/dist/commands/inventory.d.ts +5 -0
  41. package/dist/commands/inventory.js +23 -0
  42. package/dist/commands/login.d.ts +3 -0
  43. package/dist/commands/login.js +80 -0
  44. package/dist/commands/migrate.d.ts +8 -0
  45. package/dist/commands/migrate.js +55 -0
  46. package/dist/commands/plan.d.ts +13 -0
  47. package/dist/commands/plan.js +159 -0
  48. package/dist/commands/profile.d.ts +5 -0
  49. package/dist/commands/profile.js +23 -0
  50. package/dist/commands/project.d.ts +18 -0
  51. package/dist/commands/project.js +173 -0
  52. package/dist/commands/projectOptions.d.ts +7 -0
  53. package/dist/commands/projectOptions.js +21 -0
  54. package/dist/commands/receipt.d.ts +8 -0
  55. package/dist/commands/receipt.js +48 -0
  56. package/dist/commands/replay.d.ts +8 -0
  57. package/dist/commands/replay.js +35 -0
  58. package/dist/commands/report.d.ts +6 -0
  59. package/dist/commands/report.js +54 -0
  60. package/dist/commands/review.d.ts +8 -0
  61. package/dist/commands/review.js +63 -0
  62. package/dist/commands/risk.d.ts +5 -0
  63. package/dist/commands/risk.js +25 -0
  64. package/dist/commands/run.d.ts +31 -0
  65. package/dist/commands/run.js +323 -0
  66. package/dist/commands/session.d.ts +40 -0
  67. package/dist/commands/session.js +158 -0
  68. package/dist/commands/sessionWorkspace.d.ts +25 -0
  69. package/dist/commands/sessionWorkspace.js +193 -0
  70. package/dist/commands/status.d.ts +5 -0
  71. package/dist/commands/status.js +116 -0
  72. package/dist/commands/tui.d.ts +4 -0
  73. package/dist/commands/tui.js +46 -0
  74. package/dist/commands/usage.d.ts +11 -0
  75. package/dist/commands/usage.js +40 -0
  76. package/dist/commands/verify.d.ts +15 -0
  77. package/dist/commands/verify.js +197 -0
  78. package/dist/commands/workspace.d.ts +5 -0
  79. package/dist/commands/workspace.js +27 -0
  80. package/dist/index.d.ts +2 -0
  81. package/dist/index.js +394 -0
  82. package/package.json +57 -0
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.firstNonEmptyLine = firstNonEmptyLine;
37
+ exports.collectHandoffHistory = collectHandoffHistory;
38
+ exports.handoffHistoryCommand = handoffHistoryCommand;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const core_1 = require("@relay-baton/core");
42
+ const shared_1 = require("@relay-baton/shared");
43
+ const projectOptions_1 = require("./projectOptions");
44
+ /**
45
+ * Returns the first non-empty line of `content`, trimmed and clipped to
46
+ * `maxLen` characters. Skips the leading `#` heading markers so the label
47
+ * is more descriptive (e.g. "Relay Baton Handoff" instead of "#").
48
+ */
49
+ function firstNonEmptyLine(content, maxLen = 80) {
50
+ for (const raw of content.split(/\r?\n/)) {
51
+ const trimmed = raw.replace(/^#+\s*/, "").trim();
52
+ if (trimmed.length === 0)
53
+ continue;
54
+ return trimmed.length > maxLen ? trimmed.slice(0, maxLen - 1) + "…" : trimmed;
55
+ }
56
+ return "(empty)";
57
+ }
58
+ // SessionManager.backupHandoffIfExists writes:
59
+ // handoff.<ISO-with-:-and-.-replaced-by-->.md
60
+ // e.g. handoff.2026-05-27T05-58-13-213Z.md
61
+ // So the timestamp piece can contain digits, dashes, and a trailing 'Z'.
62
+ const BACKUP_NAME_RE = /^handoff\.\d{4}-\d{2}-\d{2}T[\dZ-]+\.md$/;
63
+ const CURRENT_NAME = "handoff.md";
64
+ function collectHandoffHistory(repoRoot) {
65
+ const files = new core_1.SessionFiles(repoRoot);
66
+ if (!fs.existsSync(files.dir))
67
+ return [];
68
+ const entries = [];
69
+ for (const name of fs.readdirSync(files.dir)) {
70
+ const isCurrent = name === CURRENT_NAME;
71
+ const isBackup = BACKUP_NAME_RE.test(name);
72
+ if (!isCurrent && !isBackup)
73
+ continue;
74
+ const full = path.join(files.dir, name);
75
+ let stat;
76
+ try {
77
+ stat = fs.statSync(full);
78
+ }
79
+ catch {
80
+ continue;
81
+ }
82
+ if (!stat.isFile())
83
+ continue;
84
+ let label = "(unreadable)";
85
+ try {
86
+ label = firstNonEmptyLine(fs.readFileSync(full, "utf8"));
87
+ }
88
+ catch { /* keep default */ }
89
+ entries.push({
90
+ name,
91
+ path: full,
92
+ mtimeMs: stat.mtimeMs,
93
+ size: stat.size,
94
+ label,
95
+ current: isCurrent,
96
+ });
97
+ }
98
+ // newest first
99
+ entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
100
+ return entries;
101
+ }
102
+ function fmtSize(bytes) {
103
+ if (bytes < 1024)
104
+ return `${bytes}B`;
105
+ if (bytes < 1024 * 1024)
106
+ return `${(bytes / 1024).toFixed(1)}K`;
107
+ return `${(bytes / 1024 / 1024).toFixed(1)}M`;
108
+ }
109
+ function fmtTimestamp(mtimeMs) {
110
+ // ISO-like compact form, no milliseconds.
111
+ return new Date(mtimeMs).toISOString().replace(/\.\d+Z$/, "Z");
112
+ }
113
+ async function handoffHistoryCommand(opts = {}) {
114
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
115
+ const entries = collectHandoffHistory(repoRoot);
116
+ if (entries.length === 0) {
117
+ console.log(`[relay-baton] no handoff history under ${shared_1.SESSION_DIR}/`);
118
+ return;
119
+ }
120
+ // Print a small header so the columns are obvious.
121
+ console.log(" TIMESTAMP SIZE FILE LABEL");
122
+ for (const e of entries) {
123
+ const marker = e.current ? "*" : " ";
124
+ const ts = fmtTimestamp(e.mtimeMs).padEnd(20);
125
+ const sz = fmtSize(e.size).padStart(6);
126
+ const name = e.name.length > 36 ? e.name.slice(0, 35) + "…" : e.name.padEnd(36);
127
+ console.log(`${marker} ${ts} ${sz} ${name} ${e.label}`);
128
+ }
129
+ }
@@ -0,0 +1,12 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface HandoffShowOpts extends ProjectOpts {
3
+ json?: boolean;
4
+ /** Show a specific history file (name from `handoff history`) instead of the current handoff.md. */
5
+ file?: string;
6
+ }
7
+ /**
8
+ * Read-only view of a handoff document. This exists so display surfaces
9
+ * (desktop webview, scripts) can render the handoff through the CLI instead
10
+ * of reaching into `.ai-session/` themselves — the UI never touches files.
11
+ */
12
+ export declare function handoffShowCommand(opts?: HandoffShowOpts): Promise<void>;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handoffShowCommand = handoffShowCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const core_1 = require("@relay-baton/core");
39
+ const shared_1 = require("@relay-baton/shared");
40
+ const projectOptions_1 = require("./projectOptions");
41
+ const handoffHistory_1 = require("./handoffHistory");
42
+ /**
43
+ * Read-only view of a handoff document. This exists so display surfaces
44
+ * (desktop webview, scripts) can render the handoff through the CLI instead
45
+ * of reaching into `.ai-session/` themselves — the UI never touches files.
46
+ */
47
+ async function handoffShowCommand(opts = {}) {
48
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
49
+ const files = new core_1.SessionFiles(repoRoot);
50
+ let target = files.p("handoff");
51
+ let name = "handoff.md";
52
+ if (opts.file) {
53
+ // Only allow names that `handoff history` itself lists — no path traversal.
54
+ const entry = (0, handoffHistory_1.collectHandoffHistory)(repoRoot).find((e) => e.name === opts.file);
55
+ if (!entry) {
56
+ console.error(`[relay-baton] unknown handoff file: ${opts.file} (see \`handoff history\`)`);
57
+ process.exit(2);
58
+ }
59
+ target = entry.path;
60
+ name = entry.name;
61
+ }
62
+ const exists = fs.existsSync(target);
63
+ const content = exists ? fs.readFileSync(target, "utf8") : null;
64
+ if (opts.json) {
65
+ console.log(JSON.stringify({ name, path: target, exists, content }, null, 2));
66
+ return;
67
+ }
68
+ if (!exists) {
69
+ console.log(`[relay-baton] no handoff found at ${shared_1.SESSION_DIR}/${name}`);
70
+ return;
71
+ }
72
+ console.log(content);
73
+ }
@@ -0,0 +1,2 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export declare function initCommand(opts?: ProjectOpts): Promise<void>;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initCommand = initCommand;
4
+ const core_1 = require("@relay-baton/core");
5
+ const projectOptions_1 = require("./projectOptions");
6
+ async function initCommand(opts = {}) {
7
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
8
+ const { config } = core_1.ConfigLoader.load(repoRoot);
9
+ const sm = new core_1.SessionManager(repoRoot, config);
10
+ const r = sm.init("");
11
+ if (r.alreadyExisted && r.createdFiles.length === 0) {
12
+ console.log(`[relay-baton] .ai-session already exists at ${r.dir}. Nothing to do.`);
13
+ }
14
+ else {
15
+ console.log(`[relay-baton] initialized ${r.dir}`);
16
+ for (const f of r.createdFiles)
17
+ console.log(` + ${f}`);
18
+ }
19
+ }
@@ -0,0 +1,5 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface InventoryOpts extends ProjectOpts {
3
+ json?: boolean;
4
+ }
5
+ export declare function inventoryCommand(opts?: InventoryOpts): Promise<void>;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.inventoryCommand = inventoryCommand;
4
+ const core_1 = require("@relay-baton/core");
5
+ const projectOptions_1 = require("./projectOptions");
6
+ async function inventoryCommand(opts = {}) {
7
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
8
+ const inv = new core_1.DependencyInventory(repoRoot).generate();
9
+ if (opts.json) {
10
+ console.log(JSON.stringify(inv, null, 2));
11
+ return;
12
+ }
13
+ console.log(`[relay-baton] inventory: ${inv.repoRoot}`);
14
+ console.log(`dependency manifests: ${inv.dependencyManifests.join(", ") || "-"}`);
15
+ console.log(`root scripts: ${inv.rootScripts.join(", ") || "-"}`);
16
+ if (inv.packages.length) {
17
+ console.log(`workspace packages (${inv.packages.length}):`);
18
+ for (const p of inv.packages)
19
+ console.log(` - ${p.name}: ${p.scripts.join(", ") || "(no scripts)"}`);
20
+ }
21
+ console.log(`CI workflows: ${inv.ciWorkflows.join(", ") || "-"}`);
22
+ console.log(`release files: ${inv.releaseFiles.join(", ") || "-"}`);
23
+ }
@@ -0,0 +1,3 @@
1
+ export declare function loginCommand(agent: string | undefined, opts: {
2
+ allowApiKeyEnv?: boolean;
3
+ }): Promise<void>;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loginCommand = loginCommand;
4
+ const core_1 = require("@relay-baton/core");
5
+ function bin(cmd) {
6
+ const r = (0, core_1.safeSpawnSync)(cmd, ["--version"], { encoding: "utf8" });
7
+ return r.error == null;
8
+ }
9
+ function header(title) {
10
+ const line = "─".repeat(Math.max(8, title.length + 4));
11
+ process.stdout.write(`\n\x1b[36m┌${line}┐\n│ \x1b[1m${title}\x1b[22m\x1b[36m │\n└${line}┘\x1b[0m\n`);
12
+ }
13
+ async function spawnInteractive(command, args, env) {
14
+ return new Promise((resolve) => {
15
+ const child = (0, core_1.safeSpawn)(command, args, { stdio: "inherit", env, shell: false });
16
+ child.on("error", (e) => {
17
+ console.error(`\x1b[31m[relay-baton] failed to spawn ${command}: ${e?.message ?? e}\x1b[0m`);
18
+ if (e?.code === "ENOENT") {
19
+ console.error(`\x1b[33mInstall the ${command} CLI and ensure it is on PATH.\x1b[0m`);
20
+ }
21
+ resolve(127);
22
+ });
23
+ child.on("close", (code) => resolve(code ?? 0));
24
+ });
25
+ }
26
+ /** Run the registry-described login flow for a single agent; returns 0 on success. */
27
+ async function loginAgent(id, command, env) {
28
+ const desc = (0, core_1.getAgentDescriptor)(id);
29
+ header(`${desc.displayName} login [${desc.tier}]`);
30
+ if (!bin(command)) {
31
+ console.error(`\x1b[31m✗ ${command} command not found on PATH.\x1b[0m`);
32
+ console.error(` Install ${desc.displayName} first: ${desc.installUrl}`);
33
+ return 1;
34
+ }
35
+ for (const line of desc.login.instructions)
36
+ console.log(`\x1b[2m${line}\x1b[0m`);
37
+ // No login subcommand (e.g. Aider uses provider API keys via env) — instructions only.
38
+ if (desc.login.kind === "env") {
39
+ console.log(`\x1b[32m✓ ${desc.displayName}: nothing to run here (see above).\x1b[0m`);
40
+ return 0;
41
+ }
42
+ const args = desc.login.kind === "subcommand" ? desc.login.subcommand ?? [] : [];
43
+ const code = await spawnInteractive(command, args, env);
44
+ // Interactive sessions exit non-zero on Ctrl+C (130) — treat as benign.
45
+ if (code !== 0 && !(desc.login.kind === "interactive" && code === 130)) {
46
+ console.error(`\x1b[31m✗ ${command} login exited with ${code}\x1b[0m`);
47
+ return 1;
48
+ }
49
+ console.log(`\x1b[32m✓ ${desc.displayName} login finished. \`relay-baton doctor\`로 상태를 확인하세요.\x1b[0m`);
50
+ return 0;
51
+ }
52
+ async function loginCommand(agent, opts) {
53
+ const which = agent ?? "all";
54
+ if (which !== "all" && !(0, core_1.isAgentId)(which)) {
55
+ console.error(`unknown agent: ${which}. Use one of: ${core_1.ALL_AGENT_IDS.join(", ")} | all`);
56
+ process.exit(2);
57
+ }
58
+ const repoRoot = process.cwd();
59
+ const { config } = core_1.ConfigLoader.load(repoRoot);
60
+ const { env, removed } = (0, core_1.createAgentEnv)(process.env, config.authPolicy, opts.allowApiKeyEnv);
61
+ if (removed.length > 0) {
62
+ console.log(`\x1b[2m[relay-baton] blocked env from child: ${removed.join(", ")}\x1b[0m`);
63
+ }
64
+ // Default `all` logs in just the first-class pair (codex + claude); explicit
65
+ // agent ids reach the supported adapters (opencode/gemini/aider/cursor).
66
+ const targets = which === "all"
67
+ ? core_1.ALL_AGENT_IDS.filter((id) => core_1.AGENT_REGISTRY[id].tier === "first-class")
68
+ : [which];
69
+ let totalFail = 0;
70
+ for (const id of targets) {
71
+ const command = config.agents[id]?.command ?? core_1.AGENT_REGISTRY[id].command;
72
+ totalFail += await loginAgent(id, command, env);
73
+ }
74
+ if (which === "all") {
75
+ const supported = core_1.ALL_AGENT_IDS.filter((id) => core_1.AGENT_REGISTRY[id].tier === "supported");
76
+ console.log(`\n\x1b[2m지원 어댑터 로그인: ${supported.map((s) => `relay-baton login ${s}`).join(" · ")}\x1b[0m`);
77
+ }
78
+ if (totalFail > 0)
79
+ process.exit(1);
80
+ }
@@ -0,0 +1,8 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface MigrateOpts extends ProjectOpts {
3
+ check?: boolean;
4
+ apply?: boolean;
5
+ dryRun?: boolean;
6
+ json?: boolean;
7
+ }
8
+ export declare function migrateCommand(opts?: MigrateOpts): Promise<void>;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.migrateCommand = migrateCommand;
4
+ const core_1 = require("@relay-baton/core");
5
+ const projectOptions_1 = require("./projectOptions");
6
+ async function migrateCommand(opts = {}) {
7
+ const repoRoot = (0, projectOptions_1.resolveRepoRoot)(opts);
8
+ core_1.ConfigLoader.load(repoRoot);
9
+ // Apply path is opt-in (--apply). Default and --check are read-only detection.
10
+ if (opts.apply) {
11
+ const dryRun = opts.dryRun === true;
12
+ const result = new core_1.SchemaMigrator(repoRoot).migrate({ dryRun });
13
+ if (opts.json) {
14
+ console.log(JSON.stringify(result, null, 2));
15
+ return;
16
+ }
17
+ if (!result.exists) {
18
+ console.log("[relay-baton] no .ai-session to migrate.");
19
+ return;
20
+ }
21
+ const verb = dryRun ? "would migrate" : "migrated";
22
+ const acted = result.actions.filter(a => a.kind !== "skip");
23
+ if (acted.length === 0) {
24
+ console.log("[relay-baton] nothing to migrate; all artifacts are current.");
25
+ }
26
+ else {
27
+ console.log(`[relay-baton] ${verb} ${acted.length} artifact(s)${dryRun ? " (dry-run)" : ""}`);
28
+ for (const a of result.actions)
29
+ console.log(`- ${a.file}: ${a.kind} — ${a.note}`);
30
+ if (result.backups.length)
31
+ console.log(`backups: ${result.backups.length} written (.bak.<ts>)`);
32
+ }
33
+ return;
34
+ }
35
+ const report = new core_1.SchemaInspector(repoRoot).inspect();
36
+ if (opts.json) {
37
+ console.log(JSON.stringify(report, null, 2));
38
+ return;
39
+ }
40
+ if (!report.exists) {
41
+ console.log("[relay-baton] no .ai-session to check.");
42
+ return;
43
+ }
44
+ console.log(`[relay-baton] artifact schemas: ${report.ok ? "ok" : "ATTENTION"}`);
45
+ for (const c of report.checks) {
46
+ const found = c.foundVersion == null ? "-" : `v${c.foundVersion}`;
47
+ console.log(`- ${c.file}: ${c.status} (${found}/v${c.currentVersion}) — ${c.guidance}`);
48
+ }
49
+ if (report.migratable) {
50
+ console.log("run `relay-baton migrate --apply --dry-run` to preview, then `--apply` to normalize (writes a .bak backup).");
51
+ }
52
+ else {
53
+ console.log("nothing to migrate.");
54
+ }
55
+ }
@@ -0,0 +1,13 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface PlanOpts extends ProjectOpts {
3
+ diet?: string;
4
+ with?: string;
5
+ planner?: string;
6
+ executor?: string;
7
+ force?: boolean;
8
+ run?: boolean;
9
+ noRun?: boolean;
10
+ thenExecute?: boolean;
11
+ allowApiKeyEnv?: boolean;
12
+ }
13
+ export declare function planCommand(task: string, opts: PlanOpts): Promise<void>;
@@ -0,0 +1,159 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.planCommand = planCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const core_1 = require("@relay-baton/core");
39
+ const projectOptions_1 = require("./projectOptions");
40
+ const agentFor_1 = require("./agentFor");
41
+ const execute_1 = require("./execute");
42
+ async function planCommand(task, opts) {
43
+ const projectContext = (0, projectOptions_1.resolveProjectContext)(opts, true);
44
+ const repoRoot = projectContext.repoRoot;
45
+ const { config } = core_1.ConfigLoader.load(repoRoot);
46
+ const sm = new core_1.SessionManager(repoRoot, config);
47
+ if (!sm.getMeta())
48
+ sm.init(task);
49
+ sm.writeTask(task);
50
+ const git = new core_1.GitService(repoRoot);
51
+ if (!git.isGitRepo()) {
52
+ console.error("[relay-baton] not a git repository. aborting.");
53
+ process.exit(2);
54
+ }
55
+ const profileName = (opts.diet ?? projectContext.project?.defaultDiet ?? config.tokenDiet.profile);
56
+ if (!config.tokenDiet.profiles[profileName]) {
57
+ console.error(`unknown diet profile: ${profileName}`);
58
+ process.exit(2);
59
+ }
60
+ const planner = (opts.planner ?? opts.with ?? config.planExecute?.defaultPlanner ?? "claude");
61
+ // Refresh artifacts so the planner can read repo-map / compact-state by reference.
62
+ new core_1.BatonWorkflow(sm, config).refreshArtifacts(profileName);
63
+ sm.updateMeta({
64
+ task,
65
+ workflowMode: "plan-execute",
66
+ status: "planning",
67
+ planAuthor: planner,
68
+ activeAgent: planner,
69
+ tokenDietProfile: profileName,
70
+ });
71
+ // --no-run: scaffold an empty plan template for manual authoring, then stop.
72
+ if (opts.run === false || opts.noRun) {
73
+ if (!fs.existsSync(sm.files.p("plan"))) {
74
+ fs.writeFileSync(sm.files.p("plan"), (0, core_1.planTemplate)(task), "utf8");
75
+ console.log(`[relay-baton] wrote plan template: ${sm.files.p("plan")}`);
76
+ console.log("[relay-baton] fill it in, then run 'relay-baton execute'.");
77
+ }
78
+ else {
79
+ console.log("[relay-baton] plan.md already exists; left untouched (--no-run).");
80
+ }
81
+ sm.updateMeta({ status: "plan_ready", activeAgent: "none" });
82
+ return;
83
+ }
84
+ // Plan diffing (v0.7): back up the existing plan so we can report a
85
+ // deterministic section-level delta after the planner rewrites it.
86
+ const prevPlan = fs.existsSync(sm.files.p("plan")) ? fs.readFileSync(sm.files.p("plan"), "utf8") : "";
87
+ const backupPath = (0, core_1.backupPlan)(repoRoot);
88
+ if (backupPath)
89
+ console.log(`[relay-baton] backed up previous plan: ${backupPath}`);
90
+ // Launch the planner agent with the planner prompt.
91
+ const adapter = (0, agentFor_1.adapterFor)(planner, config);
92
+ const prompt = core_1.PromptBuilder.planner(task);
93
+ const cmd = adapter.buildCommand({ task: prompt, prompt, repoRoot, sessionDir: sm.files.dir, dietProfile: profileName });
94
+ console.log(`[relay-baton] planning with ${cmd.command} (${planner}) ...`);
95
+ const r = await (0, core_1.runAgent)({
96
+ command: cmd,
97
+ logFile: sm.files.p("commandsLog"),
98
+ authPolicy: config.authPolicy,
99
+ allowApiKeyEnv: opts.allowApiKeyEnv,
100
+ onStdout: l => process.stdout.write(l + "\n"),
101
+ onStderr: l => process.stderr.write(l + "\n"),
102
+ });
103
+ if (r.error) {
104
+ console.error(r.error);
105
+ sm.updateMeta({ status: "failed", lastError: r.error, lastAgent: planner, activeAgent: "none" });
106
+ process.exit(1);
107
+ }
108
+ // Validate the plan the planner wrote.
109
+ const gate = new core_1.PlanQualityGate(repoRoot, config.tokenDiet.profiles[profileName]).check();
110
+ if (!gate.ok) {
111
+ console.error("[relay-baton] Plan Quality Gate failed:");
112
+ for (const f of gate.failures)
113
+ console.error(" - " + f);
114
+ if (!opts.force) {
115
+ sm.updateMeta({ status: "failed", lastAgent: planner, activeAgent: "none", lastError: "plan quality gate failed" });
116
+ console.error("[relay-baton] aborting. Fix plan.md or use --force.");
117
+ process.exit(3);
118
+ }
119
+ }
120
+ for (const w of gate.warnings)
121
+ console.error("[relay-baton] warn: " + w);
122
+ sm.updateMeta({
123
+ status: "plan_ready",
124
+ lastAgent: planner,
125
+ activeAgent: "none",
126
+ planFinalizedAt: new Date().toISOString(),
127
+ lastError: null,
128
+ });
129
+ console.log(`[relay-baton] plan ready: ${sm.files.p("plan")}`);
130
+ // Report the section-level delta vs the previous plan, if there was one.
131
+ if (prevPlan.trim()) {
132
+ const newPlan = fs.readFileSync(sm.files.p("plan"), "utf8");
133
+ const delta = (0, core_1.diffPlans)(prevPlan, newPlan);
134
+ if (delta.changed) {
135
+ console.log("[relay-baton] plan delta vs previous:");
136
+ for (const s of delta.sections) {
137
+ if (s.change !== "unchanged")
138
+ console.log(` - ${s.section}: ${s.change}`);
139
+ }
140
+ }
141
+ else {
142
+ console.log("[relay-baton] plan unchanged vs previous.");
143
+ }
144
+ }
145
+ if (opts.thenExecute) {
146
+ console.log("[relay-baton] --then-execute: starting execute phase...");
147
+ await (0, execute_1.executeCommand)({
148
+ diet: opts.diet,
149
+ with: opts.executor,
150
+ force: opts.force,
151
+ allowApiKeyEnv: opts.allowApiKeyEnv,
152
+ project: opts.project,
153
+ path: opts.path,
154
+ });
155
+ }
156
+ else {
157
+ console.log("[relay-baton] run 'relay-baton execute' to implement it.");
158
+ }
159
+ }
@@ -0,0 +1,5 @@
1
+ import { ProjectOpts } from "./projectOptions";
2
+ export interface ProfileOpts extends ProjectOpts {
3
+ json?: boolean;
4
+ }
5
+ export declare function profileCommand(opts?: ProfileOpts): Promise<void>;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.profileCommand = profileCommand;
4
+ const core_1 = require("@relay-baton/core");
5
+ const projectOptions_1 = require("./projectOptions");
6
+ async function profileCommand(opts = {}) {
7
+ const ctx = (0, projectOptions_1.resolveProjectContext)(opts);
8
+ const { config } = core_1.ConfigLoader.load(ctx.repoRoot);
9
+ const profile = new core_1.ProjectProfile(ctx.repoRoot, config, ctx.project).generate();
10
+ if (opts.json) {
11
+ console.log(JSON.stringify(profile, null, 2));
12
+ return;
13
+ }
14
+ console.log(`[relay-baton] project profile: ${profile.repoRoot}`);
15
+ console.log(`tags: ${profile.tags.join(", ") || "-"}`);
16
+ console.log(`frameworks: ${profile.frameworks.join(", ") || "-"}`);
17
+ console.log(`monorepo: ${profile.monorepo ? `yes (${profile.packageCount})` : "no"}`);
18
+ const r = profile.recommended;
19
+ console.log(`recommended build: ${r.build ?? "-"}`);
20
+ console.log(`recommended test: ${r.test ?? "-"}`);
21
+ console.log(`diet: ${r.diet} · agents: ${r.primaryAgent} -> ${r.fallbackAgent}`);
22
+ console.log(`entry points: ${profile.entryPoints.join(", ") || "-"}`);
23
+ }
@@ -0,0 +1,18 @@
1
+ import type { AgentId, DietProfileName } from "@relay-baton/shared";
2
+ interface AddOpts {
3
+ name?: string;
4
+ diet?: DietProfileName;
5
+ primary?: AgentId;
6
+ fallback?: AgentId;
7
+ json?: boolean;
8
+ }
9
+ export declare function projectAddCommand(projectPath: string, opts: AddOpts): Promise<void>;
10
+ export interface JsonOpts {
11
+ json?: boolean;
12
+ }
13
+ export declare function projectListCommand(opts?: JsonOpts): Promise<void>;
14
+ export declare function projectSwitchCommand(nameOrId: string): Promise<void>;
15
+ export declare function projectCurrentCommand(opts?: JsonOpts): Promise<void>;
16
+ export declare function projectRemoveCommand(nameOrId: string, opts?: JsonOpts): Promise<void>;
17
+ export declare function projectDoctorCommand(): Promise<void>;
18
+ export {};