overlord-cli 0.2605211800.0 → 0.2605211850.1

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.
@@ -9,6 +9,12 @@ import path from 'node:path';
9
9
  const OVLD_ENTRY = process.argv[1];
10
10
  const DEVICE_FILE = path.join(os.homedir(), '.ovld', 'device.json');
11
11
 
12
+ /** @internal Test-only overrides for protocol/launch subprocess calls. */
13
+ export const runnerTestHooks = {
14
+ execFileSync: null,
15
+ spawn: null
16
+ };
17
+
12
18
  function printRunnerHelp(primaryCommand = 'ovld') {
13
19
  console.log(`Usage:
14
20
  ${primaryCommand} runner once [options]
@@ -45,7 +51,7 @@ function parseFlags(args) {
45
51
  return flags;
46
52
  }
47
53
 
48
- function readOrCreateDeviceFingerprint(flags) {
54
+ export function readOrCreateDeviceFingerprint(flags) {
49
55
  const explicit =
50
56
  typeof flags['device-fingerprint'] === 'string'
51
57
  ? flags['device-fingerprint'].trim()
@@ -69,7 +75,8 @@ function readOrCreateDeviceFingerprint(flags) {
69
75
  }
70
76
 
71
77
  function runOvld(args, env = {}) {
72
- return execFileSync(process.execPath, [OVLD_ENTRY, ...args], {
78
+ const exec = runnerTestHooks.execFileSync ?? execFileSync;
79
+ return exec(process.execPath, [OVLD_ENTRY, ...args], {
73
80
  encoding: 'utf8',
74
81
  env: { ...process.env, ...env }
75
82
  });
@@ -111,7 +118,7 @@ function failLaunch(requestId, deviceFingerprint, error) {
111
118
  );
112
119
  }
113
120
 
114
- function buildLaunchArgs(launch) {
121
+ export function buildLaunchArgs(launch) {
115
122
  const args = ['launch', launch.agent, '--ticket-id', launch.ticketId];
116
123
  if (launch.workingDirectory) args.push('--working-directory', launch.workingDirectory);
117
124
  if (launch.launchMode === 'ask') args.push('--launch-mode', 'ask');
@@ -127,7 +134,7 @@ function buildLaunchArgs(launch) {
127
134
  return args;
128
135
  }
129
136
 
130
- async function launchClaimedRequest(claim, deviceFingerprint) {
137
+ export async function launchClaimedRequest(claim, deviceFingerprint) {
131
138
  const requestId = claim.request?.id;
132
139
  if (!requestId || !claim.launch?.agent || !claim.launch?.ticketId) return false;
133
140
 
@@ -137,7 +144,8 @@ async function launchClaimedRequest(claim, deviceFingerprint) {
137
144
  );
138
145
 
139
146
  await new Promise((resolve, reject) => {
140
- const child = spawn(process.execPath, [OVLD_ENTRY, ...args], {
147
+ const spawnImpl = runnerTestHooks.spawn ?? spawn;
148
+ const child = spawnImpl(process.execPath, [OVLD_ENTRY, ...args], {
141
149
  stdio: 'inherit',
142
150
  env: { ...process.env, OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
143
151
  });
@@ -170,7 +178,7 @@ async function launchClaimedRequest(claim, deviceFingerprint) {
170
178
  return true;
171
179
  }
172
180
 
173
- async function runOnce(flags, deviceFingerprint) {
181
+ export async function runOnce(flags, deviceFingerprint) {
174
182
  const claim = claimExecution(flags, deviceFingerprint);
175
183
  if (!claim.request) {
176
184
  process.stderr.write('[runner] no queued execution requests\n');
@@ -14,10 +14,86 @@ import os from 'node:os';
14
14
  import path from 'node:path';
15
15
  import { fileURLToPath } from 'node:url';
16
16
  import { checkForCliUpdate, getCurrentCliVersion, printCliUpdateNotice } from './cli-update.mjs';
17
- import {
18
- geminiLegacyCommandFiles,
19
- removeLegacyGeminiConnector as removeLegacyGeminiConnectorFiles
20
- } from '../../../../lib/overlord/legacy-gemini-connector.cjs';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Legacy Gemini connector (inlined — the lib/ directory is not published with
20
+ // the npm package, so the old relative import broke on global installs)
21
+ // ---------------------------------------------------------------------------
22
+
23
+ const GEMINI_LEGACY_COMMANDS_DIR = path.join(os.homedir(), '.gemini', 'commands');
24
+
25
+ function geminiLegacyCommandFiles() {
26
+ const base = GEMINI_LEGACY_COMMANDS_DIR;
27
+ return [
28
+ {
29
+ path: path.join(base, 'connect.toml'),
30
+ content: `description = "Connect this session to another Overlord ticket (requires: ticket-id)."\nprompt = """\nConnect this session to another Overlord ticket.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol connect --ticket-id <ticket_id>\`\n\nRules:\n- Use \`connect\`, not \`attach\`.\n- Do not load extra ticket context unless the user explicitly asks for it.\n- After the command succeeds, report the returned \`SESSION_KEY\` and confirm that future updates should use that ticket.\n"""`
31
+ },
32
+ {
33
+ path: path.join(base, 'load.toml'),
34
+ content: `description = "Load Overlord ticket context (requires: ticket-id)."\nprompt = """\nLoad Overlord ticket context without attaching to the ticket.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol load-context --ticket-id <ticket_id>\`\n\nRules:\n- Use \`load-context\`, not \`attach\`.\n- Do not create or switch sessions.\n- Summarize the returned ticket details, history, artifacts, and shared context for the user.\n"""`
35
+ },
36
+ {
37
+ path: path.join(base, 'attach.toml'),
38
+ content: `description = "Attach this session to an Overlord ticket (requires: ticket-id)."\nprompt = """\nAttach this session to an Overlord ticket.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol attach --ticket-id <ticket_id>\`\n\nRules:\n- Use \`attach\` to establish a persistent session with a ticket.\n- After the command succeeds, report the returned \`SESSION_KEY\` and confirm that future updates should use that ticket.\n"""`
39
+ },
40
+ {
41
+ path: path.join(base, 'discuss-objective.toml'),
42
+ content: `description = "Mark a ticket's draft objective as submitted (in discussion)."\nprompt = """\nMark a draft objective as "submitted", indicating the ticket is in active discussion with an agent.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol discuss-objective --ticket-id <ticket_id>\`\n\nRules:\n- This does NOT start execution. Use \`attach\` for that.\n- After the command succeeds, confirm the objective was submitted.\n"""`
43
+ },
44
+ {
45
+ path: path.join(base, 'add-objectives.toml'),
46
+ content: `description = "Append ordered objectives to an existing Overlord ticket."\nprompt = """\nAppend ordered objectives to an existing ticket.\n\nUse this when prompts are sequential steps toward the same feature or goal. Create separate tickets when prompts represent different features or goals.\n\nRun:\n\`ovld protocol add-objectives --ticket-id <ticket_id> --objectives-json '[{"objective":"Step one"},{"objective":"Step two"}]'\`\n\nIndex 0 is the first newly added objective to execute; later indexes queue after it.\n"""`
47
+ },
48
+ {
49
+ path: path.join(base, 'create.toml'),
50
+ content: `description = "Create a draft Overlord ticket from the current conversation."\nprompt = """\nCreate a draft Overlord ticket from the user's request.\n\nUse \`{{args}}\` as the input.\nIf it already contains flags such as \`--title\`, \`--priority\`, \`--project-id\`, or \`--execution-target\`, pass those flags through after \`ovld protocol create --agent gemini\`.\nOtherwise, treat \`{{args}}\` as the objective text and run:\n\`ovld protocol create --agent gemini --objective "<objective>"\`\n\nIf no objective was provided, ask the user for one and stop.\n\nAfter the command succeeds, report the new \`TICKET_ID\`.\n"""`
51
+ },
52
+ {
53
+ path: path.join(base, 'prompt.toml'),
54
+ content: `description = "Create a new Overlord ticket from the current conversation."\nprompt = """\nCreate a new Overlord ticket from the user's request.\n\nUse \`{{args}}\` as the input.\nIf it already contains flags such as \`--title\`, \`--priority\`, \`--project-id\`, or \`--execution-target\`, pass those flags through after \`ovld protocol prompt --agent gemini\`.\nOtherwise, treat \`{{args}}\` as the objective text and run:\n\`ovld protocol prompt --agent gemini --objective "<objective>"\`\n\nIf no objective was provided, ask the user for one and stop.\n\nAfter the command succeeds, report the new \`TICKET_ID\` and \`SESSION_KEY\`.\n"""`
55
+ },
56
+ {
57
+ path: path.join(base, 'record-work.toml'),
58
+ content: `description = "Record completed-from-chat work as a ticket in review + feed post (no attach)."\nprompt = """\nImmediately record the work you just completed in this chat as a new Overlord ticket via \`ovld protocol record-work\`. No agent session is opened — the work is already done.\n\nSynthesize from the current conversation:\n- \`objective\`: what was asked / what was done.\n- \`summary\`: reviewer-friendly narrative for the feed.\n- \`changeRationales\`: one entry per meaningful git-tracked file change (\`label\`, \`file_path\`, \`summary\`, \`why\`, \`impact\`, optional \`hunks\`). Use \`git status\` and \`git diff\` to enumerate changed files.\n- \`artifacts\` (optional): \`next_steps\`, \`test_results\`, \`decision\`, \`note\`, \`url\`.\n\nIf \`{{args}}\` is non-empty, treat it as additional context to weave into the summary.\n\nRun \`ovld protocol record-work --payload-file -\` and stream a JSON object \`{ "objective": "...", "summary": "...", "artifacts": [...], "changeRationales": [...] }\` on stdin via a single-quoted heredoc.\n\nAfter the command succeeds, report the new \`TICKET_ID\`.\n\nRules:\n- Do NOT use this for in-progress work. Use \`/prompt\` for that.\n- The CLI validates that every changed git-tracked file is represented in \`changeRationales\` unless \`--skip-file-change-check\` is passed.\n- If project resolution fails, re-run with \`--project-id <id>\` or \`--personal\`.\n"""`
59
+ }
60
+ ];
61
+ }
62
+
63
+ function isRemovableLegacyGeminiCommandFile({ filePath, content, manifestFiles }) {
64
+ if (manifestFiles.has(filePath)) return true;
65
+ const managed = geminiLegacyCommandFiles().find(file => file.path === filePath);
66
+ if (!managed) return false;
67
+ return content.trim() === managed.content.trim();
68
+ }
69
+
70
+ function removeLegacyGeminiConnectorFiles({ readManifest, writeManifest, readTextFile, existsSync = fs.existsSync.bind(fs), rmSync = fs.rmSync.bind(fs) }) {
71
+ const removed = [];
72
+ const manifest = readManifest();
73
+ const manifestFiles = new Set(manifest.gemini?.files ?? []);
74
+
75
+ for (const file of geminiLegacyCommandFiles()) {
76
+ if (!existsSync(file.path)) continue;
77
+ const existing = readTextFile(file.path);
78
+ if (!isRemovableLegacyGeminiCommandFile({ filePath: file.path, content: existing, manifestFiles })) continue;
79
+ rmSync(file.path, { force: true });
80
+ removed.push(file.path);
81
+ }
82
+
83
+ for (const filePath of manifestFiles) {
84
+ if (removed.includes(filePath) || !existsSync(filePath)) continue;
85
+ if (!filePath.includes('.gemini/commands') || !filePath.endsWith('.toml')) continue;
86
+ rmSync(filePath, { force: true });
87
+ removed.push(filePath);
88
+ }
89
+
90
+ if (manifest.gemini) {
91
+ delete manifest.gemini;
92
+ writeManifest(manifest);
93
+ }
94
+
95
+ return removed;
96
+ }
21
97
 
22
98
  const BUNDLE_VERSION = '1.12.0';
23
99
  const MD_MARKER_START = '<!-- overlord:managed:start -->';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605211800.0",
3
+ "version": "0.2605211850.1",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",
@@ -58,7 +58,11 @@ Always include `changeRationales` when delivering. Optionally include them on up
58
58
 
59
59
  Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact. Record only meaningful behavioral changes. Skip formatting-only noise.
60
60
 
61
- For the `record-change-rationales` command and `--change-rationales-json` payload shape see [reference/cli.md](reference/cli.md).
61
+ Each rationale entry requires these fields: `file_path`, `label`, `summary`, `why`, `impact` — all strings. Do **not** use `filePath` or `rationale`; those are a different internal shape and will cause a validation error.
62
+
63
+ For more than a handful of entries, use `--change-rationales-file` with a temp file or stdin (`-`) instead of inline `--change-rationales-json` to avoid shell quoting failures with large arrays.
64
+
65
+ For the `record-change-rationales` command and full payload shape with optional `hunks` see [reference/cli.md](reference/cli.md).
62
66
 
63
67
  ## Rules
64
68
 
@@ -64,6 +64,8 @@ ovld protocol revert --objective-id <objective-id>
64
64
 
65
65
  These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below. Use `--payload-json` for compact full delivery payloads, or `--payload-file -` when the JSON is larger or quote-sensitive so summary, artifacts, and change rationales stay in one JSON document without creating a temporary file.
66
66
 
67
+ **Required fields per entry:** `file_path`, `label`, `summary`, `why`, `impact` (all strings). Do not use `filePath` or `rationale` — those are the internal API shape and will fail CLI validation.
68
+
67
69
  ```bash
68
70
  ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
69
71
  --summary "Recorded rationale details for the latest code changes." --phase execute \
@@ -76,6 +78,19 @@ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID \
76
78
  --change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
77
79
  ```
78
80
 
81
+ For many entries (roughly 5+), pipe via stdin to avoid shell quoting failures:
82
+
83
+ ```bash
84
+ ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
85
+ --summary "Recorded rationale details for the latest code changes." --phase execute \
86
+ --change-rationales-file - <<'EOF'
87
+ [
88
+ {"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x."},
89
+ {"label":"Update config","file_path":"lib/config.ts","summary":"Added timeout.","why":"Match new defaults.","impact":"Requests time out after 30s."}
90
+ ]
91
+ EOF
92
+ ```
93
+
79
94
  ## Project Discovery And Ticket Creation
80
95
 
81
96
  When creating tickets from within a repository:
@@ -58,7 +58,11 @@ Always include `changeRationales` when delivering. Optionally include them on up
58
58
 
59
59
  Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact. Record only meaningful behavioral changes. Skip formatting-only noise.
60
60
 
61
- For the `record-change-rationales` command and `--change-rationales-json` payload shape see [reference/cli.md](reference/cli.md).
61
+ Each rationale entry requires these fields: `file_path`, `label`, `summary`, `why`, `impact` — all strings. Do **not** use `filePath` or `rationale`; those are a different internal shape and will cause a validation error.
62
+
63
+ For more than a handful of entries, use `--change-rationales-file` with a temp file or stdin (`-`) instead of inline `--change-rationales-json` to avoid shell quoting failures with large arrays.
64
+
65
+ For the `record-change-rationales` command and full payload shape with optional `hunks` see [reference/cli.md](reference/cli.md).
62
66
 
63
67
  ## Rules
64
68
 
@@ -64,6 +64,8 @@ ovld protocol revert --objective-id <objective-id>
64
64
 
65
65
  These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below. Use `--payload-json` for compact full delivery payloads, or `--payload-file -` when the JSON is larger or quote-sensitive so summary, artifacts, and change rationales stay in one JSON document without creating a temporary file.
66
66
 
67
+ **Required fields per entry:** `file_path`, `label`, `summary`, `why`, `impact` (all strings). Do not use `filePath` or `rationale` — those are the internal API shape and will fail CLI validation.
68
+
67
69
  ```bash
68
70
  ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
69
71
  --summary "Recorded rationale details for the latest code changes." --phase execute \
@@ -76,6 +78,19 @@ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID \
76
78
  --change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
77
79
  ```
78
80
 
81
+ For many entries (roughly 5+), pipe via stdin to avoid shell quoting failures:
82
+
83
+ ```bash
84
+ ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
85
+ --summary "Recorded rationale details for the latest code changes." --phase execute \
86
+ --change-rationales-file - <<'EOF'
87
+ [
88
+ {"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x."},
89
+ {"label":"Update config","file_path":"lib/config.ts","summary":"Added timeout.","why":"Match new defaults.","impact":"Requests time out after 30s."}
90
+ ]
91
+ EOF
92
+ ```
93
+
79
94
  ## Project Discovery And Ticket Creation
80
95
 
81
96
  When creating tickets from within a repository:
@@ -58,7 +58,11 @@ Always include `changeRationales` when delivering. Optionally include them on up
58
58
 
59
59
  Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact. Record only meaningful behavioral changes. Skip formatting-only noise.
60
60
 
61
- For the `record-change-rationales` command and `--change-rationales-json` payload shape see [reference/cli.md](reference/cli.md).
61
+ Each rationale entry requires these fields: `file_path`, `label`, `summary`, `why`, `impact` — all strings. Do **not** use `filePath` or `rationale`; those are a different internal shape and will cause a validation error.
62
+
63
+ For more than a handful of entries, use `--change-rationales-file` with a temp file or stdin (`-`) instead of inline `--change-rationales-json` to avoid shell quoting failures with large arrays.
64
+
65
+ For the `record-change-rationales` command and full payload shape with optional `hunks` see [reference/cli.md](reference/cli.md).
62
66
 
63
67
  ## Rules
64
68
 
@@ -64,6 +64,8 @@ ovld protocol revert --objective-id <objective-id>
64
64
 
65
65
  These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below. Use `--payload-json` for compact full delivery payloads, or `--payload-file -` when the JSON is larger or quote-sensitive so summary, artifacts, and change rationales stay in one JSON document without creating a temporary file.
66
66
 
67
+ **Required fields per entry:** `file_path`, `label`, `summary`, `why`, `impact` (all strings). Do not use `filePath` or `rationale` — those are the internal API shape and will fail CLI validation.
68
+
67
69
  ```bash
68
70
  ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
69
71
  --summary "Recorded rationale details for the latest code changes." --phase execute \
@@ -76,6 +78,19 @@ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID \
76
78
  --change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
77
79
  ```
78
80
 
81
+ For many entries (roughly 5+), pipe via stdin to avoid shell quoting failures:
82
+
83
+ ```bash
84
+ ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
85
+ --summary "Recorded rationale details for the latest code changes." --phase execute \
86
+ --change-rationales-file - <<'EOF'
87
+ [
88
+ {"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x."},
89
+ {"label":"Update config","file_path":"lib/config.ts","summary":"Added timeout.","why":"Match new defaults.","impact":"Requests time out after 30s."}
90
+ ]
91
+ EOF
92
+ ```
93
+
79
94
  ## Project Discovery And Ticket Creation
80
95
 
81
96
  When creating tickets from within a repository: