overlord-cli 3.22.0 → 3.24.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 +6 -2
- package/bin/_cli/index.mjs +1 -1
- package/bin/_cli/postinstall.mjs +42 -0
- package/bin/_cli/protocol.mjs +42 -6
- package/bin/_cli/setup.mjs +511 -8
- package/package.json +4 -1
- package/plugins/overlord/scripts/overlord-mcp.mjs +44 -12
- package/plugins/overlord/skills/overlord-ticket-workflow/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -33,6 +33,9 @@ ovld attach
|
|
|
33
33
|
ovld create "Investigate the failing build"
|
|
34
34
|
ovld prompt "Draft a fix for the onboarding flow"
|
|
35
35
|
ovld update
|
|
36
|
+
ovld protocol discover-project
|
|
37
|
+
ovld protocol attach --ticket-id <ticket-id>
|
|
38
|
+
ovld protocol update --session-key <session-key> --ticket-id <ticket-id> --summary "Working on it" --phase execute
|
|
36
39
|
ovld setup codex
|
|
37
40
|
ovld setup cursor
|
|
38
41
|
ovld setup gemini
|
|
@@ -53,8 +56,9 @@ ovld doctor
|
|
|
53
56
|
- `auth` - log in, log out, or check auth status
|
|
54
57
|
- `tickets` - list or create tickets
|
|
55
58
|
- `ticket` - work with a single ticket
|
|
56
|
-
- `protocol` - run ticket lifecycle commands
|
|
57
|
-
- `connect`, `restart`, `
|
|
59
|
+
- `protocol` - run ticket lifecycle commands such as `discover-project`, `attach`, `connect`, `load-context`, `spawn`, `update`, `record-change-rationales`, `ask`, `read-context`, `write-context`, `deliver`, and artifact upload/download helpers
|
|
60
|
+
- `connect`, `restart`, `context` - launch or resume an agent session or print ticket context
|
|
61
|
+
- `run`, `resume` - legacy aliases for `connect` and `restart`
|
|
58
62
|
- `setup` - install the Overlord connector or plugin bundle for a supported agent
|
|
59
63
|
- `update` - install the latest CLI release from npm
|
|
60
64
|
- `doctor` - verify installed agent connectors and check whether a newer CLI version is available
|
package/bin/_cli/index.mjs
CHANGED
|
@@ -38,7 +38,7 @@ Usage:
|
|
|
38
38
|
${primaryCommand} connect <agent> Launch an agent on a ticket
|
|
39
39
|
${primaryCommand} restart <agent> Resume an agent session
|
|
40
40
|
${primaryCommand} context Print ticket context (requires TICKET_ID)
|
|
41
|
-
${primaryCommand} setup
|
|
41
|
+
${primaryCommand} setup [agent|all] Install Overlord agent connector (interactive if no args)
|
|
42
42
|
${primaryCommand} update Install the latest CLI version from npm
|
|
43
43
|
${primaryCommand} doctor Validate installed agent connectors and check for CLI updates
|
|
44
44
|
${primaryCommand} version Show the installed CLI version
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install message for Overlord CLI
|
|
5
|
+
* Shown after `npm install -g overlord-cli` or `yarn global add overlord-cli`
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { dirname } from 'node:path';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Only show message if this is a global install (not local dev)
|
|
15
|
+
const isGlobalInstall = process.env.npm_config_global === 'true' ||
|
|
16
|
+
process.env.npm_execpath?.includes('yarn') ||
|
|
17
|
+
!__dirname.includes('node_modules');
|
|
18
|
+
|
|
19
|
+
if (!isGlobalInstall) {
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const green = s => `\x1b[32m${s}\x1b[0m`;
|
|
24
|
+
const cyan = s => `\x1b[36m${s}\x1b[0m`;
|
|
25
|
+
const bold = s => `\x1b[1m${s}\x1b[0m`;
|
|
26
|
+
|
|
27
|
+
console.log(`
|
|
28
|
+
${green('✓')} Overlord CLI installed successfully!
|
|
29
|
+
|
|
30
|
+
${bold('Next step:')} Configure agent connectors
|
|
31
|
+
|
|
32
|
+
${cyan('ovld setup')}
|
|
33
|
+
|
|
34
|
+
This will guide you through:
|
|
35
|
+
• Selecting which agent connectors to install (Claude, Cursor, etc.)
|
|
36
|
+
• Configuring agent permissions for Overlord protocol access
|
|
37
|
+
|
|
38
|
+
You can also run ${cyan('ovld setup <agent>')} to install a specific agent connector,
|
|
39
|
+
or ${cyan('ovld doctor')} to check your installation status.
|
|
40
|
+
|
|
41
|
+
Run ${cyan('ovld help')} to see all available commands.
|
|
42
|
+
`);
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -189,6 +189,37 @@ function readJsonFile(filePath, label) {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
async function readTextFromStdin(label) {
|
|
193
|
+
const chunks = [];
|
|
194
|
+
try {
|
|
195
|
+
for await (const chunk of process.stdin) {
|
|
196
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
197
|
+
}
|
|
198
|
+
} catch (err) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`${label}: could not read stdin: ${err instanceof Error ? err.message : String(err)}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function readJsonFileOrStdin(filePath, label) {
|
|
207
|
+
if (filePath !== '-') {
|
|
208
|
+
return readJsonFile(filePath, label);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
return JSON.parse(await readTextFromStdin(label));
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (err instanceof Error && err.message.startsWith(`${label}: could not read stdin`)) {
|
|
215
|
+
throw err;
|
|
216
|
+
}
|
|
217
|
+
throw new Error(
|
|
218
|
+
`${label}: could not parse stdin: ${err instanceof Error ? err.message : String(err)}`
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
192
223
|
// ---------------------------------------------------------------------------
|
|
193
224
|
// changeRationales helper
|
|
194
225
|
// ---------------------------------------------------------------------------
|
|
@@ -200,7 +231,10 @@ function readJsonFile(filePath, label) {
|
|
|
200
231
|
*/
|
|
201
232
|
async function resolveChangeRationales(flags) {
|
|
202
233
|
if (flags['change-rationales-file']) {
|
|
203
|
-
return
|
|
234
|
+
return await readJsonFileOrStdin(
|
|
235
|
+
String(flags['change-rationales-file']),
|
|
236
|
+
'--change-rationales-file'
|
|
237
|
+
);
|
|
204
238
|
}
|
|
205
239
|
if (flags['change-rationales-json']) {
|
|
206
240
|
try {
|
|
@@ -624,7 +658,7 @@ async function protocolDeliver(args) {
|
|
|
624
658
|
if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
|
|
625
659
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
626
660
|
const deliverPayload = flags['payload-file']
|
|
627
|
-
?
|
|
661
|
+
? await readJsonFileOrStdin(String(flags['payload-file']), '--payload-file')
|
|
628
662
|
: null;
|
|
629
663
|
const summary = deliverPayload?.summary ??
|
|
630
664
|
(flags['summary-file']
|
|
@@ -642,7 +676,7 @@ async function protocolDeliver(args) {
|
|
|
642
676
|
throw new Error('Use either --payload-file or --artifacts-json, not both');
|
|
643
677
|
}
|
|
644
678
|
if (flags['artifacts-file']) {
|
|
645
|
-
artifacts =
|
|
679
|
+
artifacts = await readJsonFileOrStdin(String(flags['artifacts-file']), '--artifacts-file');
|
|
646
680
|
} else if (flags['artifacts-json']) {
|
|
647
681
|
try {
|
|
648
682
|
artifacts = JSON.parse(String(flags['artifacts-json']));
|
|
@@ -1144,14 +1178,15 @@ deliver:
|
|
|
1144
1178
|
--session-key <key>
|
|
1145
1179
|
--ticket-id <id>
|
|
1146
1180
|
--summary <text> or --summary-file <path>
|
|
1147
|
-
or: --payload-file <path
|
|
1181
|
+
or: --payload-file <path|-> containing { summary, artifacts, changeRationales }
|
|
1148
1182
|
Optional:
|
|
1149
1183
|
--artifacts-json <json>
|
|
1150
|
-
--artifacts-file <path
|
|
1184
|
+
--artifacts-file <path|->
|
|
1151
1185
|
--change-rationales-json <json>
|
|
1152
|
-
--change-rationales-file <path
|
|
1186
|
+
--change-rationales-file <path|->
|
|
1153
1187
|
--skip-file-change-check Bypass local git vs changeRationales validation
|
|
1154
1188
|
Notes:
|
|
1189
|
+
Use --payload-file - to read the full delivery JSON from stdin without creating a scratch file.
|
|
1155
1190
|
Do not combine --payload-file with --artifacts-json/--artifacts-file or change-rationale flags.
|
|
1156
1191
|
In a git workspace, deliver validates that changed files are represented by changeRationales unless skipped.
|
|
1157
1192
|
|
|
@@ -1243,6 +1278,7 @@ Examples:
|
|
|
1243
1278
|
ovld protocol deliver --session-key <key> --ticket-id <id> --summary "Done"
|
|
1244
1279
|
ovld protocol deliver --session-key <key> --ticket-id <id> --summary "Done" --artifacts-file ./artifacts.json
|
|
1245
1280
|
ovld protocol deliver --session-key <key> --ticket-id <id> --payload-file ./deliver.json
|
|
1281
|
+
ovld protocol deliver --session-key <key> --ticket-id <id> --payload-file -
|
|
1246
1282
|
ovld protocol deliver --session-key <key> --ticket-id <id> --summary "Done" --skip-file-change-check
|
|
1247
1283
|
ovld protocol deliver --session-key <key> --ticket-id <id> --summary "Done" --timeout 60000
|
|
1248
1284
|
`);
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -84,11 +84,11 @@ If you receive a prompt with a specified ticket ID, adhere to the following. If
|
|
|
84
84
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
85
85
|
\`\`\`
|
|
86
86
|
|
|
87
|
-
For larger or quote-sensitive deliveries, prefer a single JSON
|
|
87
|
+
For larger or quote-sensitive deliveries, prefer a single JSON payload on stdin:
|
|
88
88
|
\`\`\`bash
|
|
89
|
-
ovld protocol deliver --session-key <sessionKey> --ticket-id $TICKET_ID --payload-file
|
|
89
|
+
ovld protocol deliver --session-key <sessionKey> --ticket-id $TICKET_ID --payload-file -
|
|
90
90
|
\`\`\`
|
|
91
|
-
|
|
91
|
+
This avoids creating a scratch delivery file that needs cleanup. If your runtime cannot provide stdin directly, \`--payload-file ./deliver.json\` remains supported; treat that file as ephemeral scratch data, never commit it, and remove it after delivery.
|
|
92
92
|
|
|
93
93
|
## Change Rationales
|
|
94
94
|
|
|
@@ -96,7 +96,7 @@ Always include \`changeRationales\` when delivering. Optionally include them on
|
|
|
96
96
|
|
|
97
97
|
Before delivering, make sure every meaningful git-tracked file change is represented in \`changeRationales\`; do not send \`file_changes\` as an artifact.
|
|
98
98
|
|
|
99
|
-
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. For
|
|
99
|
+
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. For larger full delivery payloads, prefer \`--payload-file -\` so summary, artifacts, and change rationales stay in one JSON document without creating a temporary file. Ordinary deliver artifacts should use \`next_steps\`, \`test_results\`, \`migration\`, \`note\`, \`url\`, or \`decision\`.
|
|
100
100
|
|
|
101
101
|
\`\`\`bash
|
|
102
102
|
ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \\\\
|
|
@@ -158,11 +158,11 @@ If you receive a prompt with a specified ticket ID, adhere to the following. If
|
|
|
158
158
|
--artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' \\\\
|
|
159
159
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
160
160
|
\`\`\`
|
|
161
|
-
If you use \`--payload-file\`, \`--artifacts-file\`, or \`--change-rationales-file\`
|
|
161
|
+
For larger delivery JSON, prefer \`--payload-file -\` and stream the full payload on stdin so no scratch file needs to be created or removed. If you use \`--payload-file\`, \`--artifacts-file\`, or \`--change-rationales-file\` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery. Do not leave delivery JSON checked into the worktree.
|
|
162
162
|
|
|
163
163
|
## Change Rationales
|
|
164
164
|
|
|
165
|
-
Always include \`changeRationales\` when delivering. 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. Overlord stores these as structured rows in the \`file_changes\` table. If you need a JSON file for transport, keep it ephemeral and out of the repository.
|
|
165
|
+
Always include \`changeRationales\` when delivering. 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. Overlord stores these as structured rows in the \`file_changes\` table. For larger delivery payloads, prefer \`--payload-file -\` with stdin. If you need a JSON file for transport, keep it ephemeral and out of the repository.
|
|
166
166
|
|
|
167
167
|
\`\`\`bash
|
|
168
168
|
ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \\\\
|
|
@@ -231,7 +231,7 @@ When done, deliver with artifacts and change rationales:
|
|
|
231
231
|
\`\`\`bash
|
|
232
232
|
ovld protocol deliver --session-key <sessionKey> --ticket-id $TICKET_ID --summary "Narrative: what you did, next steps." --artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' --change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
233
233
|
\`\`\`
|
|
234
|
-
If you use a JSON file for delivery transport, keep it ephemeral scratch data outside the repository and remove it after the protocol call.
|
|
234
|
+
For larger delivery JSON, prefer \`--payload-file -\` with stdin so no scratch file needs to be created or removed. If you use a JSON file for delivery transport, keep it ephemeral scratch data outside the repository and remove it after the protocol call.
|
|
235
235
|
|
|
236
236
|
Rules:
|
|
237
237
|
- Always attach first and deliver last.
|
|
@@ -950,6 +950,392 @@ function currentNodeMajor() {
|
|
|
950
950
|
return Number.parseInt(process.versions.node.split('.')[0] ?? '', 10);
|
|
951
951
|
}
|
|
952
952
|
|
|
953
|
+
// ---------------------------------------------------------------------------
|
|
954
|
+
// Interactive checkbox prompt
|
|
955
|
+
// ---------------------------------------------------------------------------
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* Run an interactive checkbox list (multiselect with spacebar).
|
|
959
|
+
*
|
|
960
|
+
* @param {object} opts
|
|
961
|
+
* @param {string} opts.message - Prompt message shown above the list
|
|
962
|
+
* @param {string[]} opts.choices - List of choice labels
|
|
963
|
+
* @param {string[]} [opts.defaults] - Initially selected choices
|
|
964
|
+
* @returns {Promise<string[]>} - Array of selected choice labels
|
|
965
|
+
*/
|
|
966
|
+
function runCheckboxPrompt({ message, choices, defaults = [] }) {
|
|
967
|
+
return new Promise(resolve => {
|
|
968
|
+
const hide = '\x1b[?25l';
|
|
969
|
+
const show = '\x1b[?25h';
|
|
970
|
+
const saveCursor = '\x1b7';
|
|
971
|
+
const restoreCursor = '\x1b8';
|
|
972
|
+
const eraseBelow = '\x1b[J';
|
|
973
|
+
const cyan = s => `\x1b[36m${s}\x1b[0m`;
|
|
974
|
+
const bold = s => `\x1b[1m${s}\x1b[0m`;
|
|
975
|
+
const dim = s => `\x1b[2m${s}\x1b[0m`;
|
|
976
|
+
|
|
977
|
+
let cursorIdx = 0;
|
|
978
|
+
let selected = new Set(defaults);
|
|
979
|
+
let hasRendered = false;
|
|
980
|
+
|
|
981
|
+
function render() {
|
|
982
|
+
const lines = [];
|
|
983
|
+
lines.push(bold(message));
|
|
984
|
+
lines.push(dim(' ↑↓ navigate · Space toggle · Enter confirm · Esc cancel'));
|
|
985
|
+
lines.push('');
|
|
986
|
+
|
|
987
|
+
for (let i = 0; i < choices.length; i++) {
|
|
988
|
+
const choice = choices[i];
|
|
989
|
+
const isSelected = selected.has(choice);
|
|
990
|
+
const isCursor = i === cursorIdx;
|
|
991
|
+
const checkbox = isSelected ? '[✓]' : '[ ]';
|
|
992
|
+
const marker = isCursor ? cyan('▶') : ' ';
|
|
993
|
+
const label = isCursor ? bold(choice) : choice;
|
|
994
|
+
lines.push(` ${marker} ${checkbox} ${label}`);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (hasRendered) {
|
|
998
|
+
process.stdout.write(restoreCursor + eraseBelow);
|
|
999
|
+
}
|
|
1000
|
+
process.stdout.write(saveCursor + lines.join('\n'));
|
|
1001
|
+
hasRendered = true;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function cleanup() {
|
|
1005
|
+
if (hasRendered) {
|
|
1006
|
+
process.stdout.write(restoreCursor + eraseBelow);
|
|
1007
|
+
}
|
|
1008
|
+
process.stdin.setRawMode(false);
|
|
1009
|
+
process.stdin.removeAllListeners('data');
|
|
1010
|
+
process.stdout.write(show);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
process.stdin.setRawMode(true);
|
|
1014
|
+
process.stdin.resume();
|
|
1015
|
+
process.stdin.setEncoding('utf8');
|
|
1016
|
+
process.stdout.write(hide);
|
|
1017
|
+
render();
|
|
1018
|
+
|
|
1019
|
+
process.stdin.on('data', key => {
|
|
1020
|
+
// Ctrl-C / Ctrl-D → exit
|
|
1021
|
+
if (key === '\x03' || key === '\x04') {
|
|
1022
|
+
cleanup();
|
|
1023
|
+
process.exit(0);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Escape → cancel
|
|
1027
|
+
if (key === '\x1b') {
|
|
1028
|
+
cleanup();
|
|
1029
|
+
resolve([]);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
// Enter → confirm selection
|
|
1034
|
+
if (key === '\r' || key === '\n') {
|
|
1035
|
+
cleanup();
|
|
1036
|
+
resolve(Array.from(selected));
|
|
1037
|
+
return;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Arrow up
|
|
1041
|
+
if (key === '\x1b[A') {
|
|
1042
|
+
cursorIdx = (cursorIdx - 1 + choices.length) % choices.length;
|
|
1043
|
+
render();
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// Arrow down
|
|
1048
|
+
if (key === '\x1b[B') {
|
|
1049
|
+
cursorIdx = (cursorIdx + 1) % choices.length;
|
|
1050
|
+
render();
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// Spacebar → toggle selection
|
|
1055
|
+
if (key === ' ') {
|
|
1056
|
+
const choice = choices[cursorIdx];
|
|
1057
|
+
if (selected.has(choice)) {
|
|
1058
|
+
selected.delete(choice);
|
|
1059
|
+
} else {
|
|
1060
|
+
selected.add(choice);
|
|
1061
|
+
}
|
|
1062
|
+
render();
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
});
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* Ask a yes/no question interactively.
|
|
1071
|
+
*
|
|
1072
|
+
* @param {string} question - The question to ask
|
|
1073
|
+
* @param {boolean} defaultYes - Default answer if user just presses Enter
|
|
1074
|
+
* @returns {Promise<boolean>} - true if yes, false if no
|
|
1075
|
+
*/
|
|
1076
|
+
function askYesNo(question, defaultYes = true) {
|
|
1077
|
+
return new Promise(resolve => {
|
|
1078
|
+
const hide = '\x1b[?25l';
|
|
1079
|
+
const show = '\x1b[?25h';
|
|
1080
|
+
const saveCursor = '\x1b7';
|
|
1081
|
+
const restoreCursor = '\x1b8';
|
|
1082
|
+
const eraseBelow = '\x1b[J';
|
|
1083
|
+
const cyan = s => `\x1b[36m${s}\x1b[0m`;
|
|
1084
|
+
const bold = s => `\x1b[1m${s}\x1b[0m`;
|
|
1085
|
+
const dim = s => `\x1b[2m${s}\x1b[0m`;
|
|
1086
|
+
|
|
1087
|
+
const choices = ['Yes', 'No'];
|
|
1088
|
+
let cursorIdx = defaultYes ? 0 : 1;
|
|
1089
|
+
let hasRendered = false;
|
|
1090
|
+
|
|
1091
|
+
function render() {
|
|
1092
|
+
const lines = [];
|
|
1093
|
+
lines.push(bold(question));
|
|
1094
|
+
lines.push('');
|
|
1095
|
+
|
|
1096
|
+
for (let i = 0; i < choices.length; i++) {
|
|
1097
|
+
const choice = choices[i];
|
|
1098
|
+
const isCursor = i === cursorIdx;
|
|
1099
|
+
const marker = isCursor ? cyan('▶') : ' ';
|
|
1100
|
+
const label = isCursor ? bold(choice) : choice;
|
|
1101
|
+
lines.push(` ${marker} ${label}`);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
lines.push('');
|
|
1105
|
+
lines.push(dim(' ↑↓ navigate · Enter confirm · Esc cancel'));
|
|
1106
|
+
|
|
1107
|
+
if (hasRendered) {
|
|
1108
|
+
process.stdout.write(restoreCursor + eraseBelow);
|
|
1109
|
+
}
|
|
1110
|
+
process.stdout.write(saveCursor + lines.join('\n'));
|
|
1111
|
+
hasRendered = true;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
function cleanup() {
|
|
1115
|
+
if (hasRendered) {
|
|
1116
|
+
process.stdout.write(restoreCursor + eraseBelow);
|
|
1117
|
+
}
|
|
1118
|
+
process.stdin.setRawMode(false);
|
|
1119
|
+
process.stdin.removeAllListeners('data');
|
|
1120
|
+
process.stdout.write(show);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
process.stdin.setRawMode(true);
|
|
1124
|
+
process.stdin.resume();
|
|
1125
|
+
process.stdin.setEncoding('utf8');
|
|
1126
|
+
process.stdout.write(hide);
|
|
1127
|
+
render();
|
|
1128
|
+
|
|
1129
|
+
process.stdin.on('data', key => {
|
|
1130
|
+
// Ctrl-C / Ctrl-D → exit
|
|
1131
|
+
if (key === '\x03' || key === '\x04') {
|
|
1132
|
+
cleanup();
|
|
1133
|
+
process.exit(0);
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Escape → cancel (default to No)
|
|
1137
|
+
if (key === '\x1b') {
|
|
1138
|
+
cleanup();
|
|
1139
|
+
resolve(false);
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// Enter → confirm selection
|
|
1144
|
+
if (key === '\r' || key === '\n') {
|
|
1145
|
+
cleanup();
|
|
1146
|
+
resolve(cursorIdx === 0);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// Arrow up
|
|
1151
|
+
if (key === '\x1b[A') {
|
|
1152
|
+
cursorIdx = (cursorIdx - 1 + choices.length) % choices.length;
|
|
1153
|
+
render();
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
// Arrow down
|
|
1158
|
+
if (key === '\x1b[B') {
|
|
1159
|
+
cursorIdx = (cursorIdx + 1) % choices.length;
|
|
1160
|
+
render();
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// y/Y → yes
|
|
1165
|
+
if (key === 'y' || key === 'Y') {
|
|
1166
|
+
cleanup();
|
|
1167
|
+
resolve(true);
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// n/N → no
|
|
1172
|
+
if (key === 'n' || key === 'N') {
|
|
1173
|
+
cleanup();
|
|
1174
|
+
resolve(false);
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// ---------------------------------------------------------------------------
|
|
1182
|
+
// Agent permissions installation
|
|
1183
|
+
// ---------------------------------------------------------------------------
|
|
1184
|
+
|
|
1185
|
+
function getPlatformUrl() {
|
|
1186
|
+
// Check for OVERLORD_URL env var first, otherwise default to localhost
|
|
1187
|
+
return process.env.OVERLORD_URL || 'http://localhost:3000';
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
function installAgentPermissions(agents, platformUrl) {
|
|
1191
|
+
console.log(`\nInstalling agent permissions for: ${agents.join(', ')}`);
|
|
1192
|
+
console.log(`Platform URL: ${platformUrl}\n`);
|
|
1193
|
+
|
|
1194
|
+
for (const agent of agents) {
|
|
1195
|
+
if (agent === 'claude') {
|
|
1196
|
+
installClaudePermissions(platformUrl);
|
|
1197
|
+
} else if (agent === 'opencode') {
|
|
1198
|
+
installOpenCodePermissions(platformUrl);
|
|
1199
|
+
} else if (agent === 'codex') {
|
|
1200
|
+
installCodexPermissions(platformUrl);
|
|
1201
|
+
}
|
|
1202
|
+
// cursor and gemini don't have permission configuration
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function installClaudePermissions(platformUrl) {
|
|
1207
|
+
const settingsPath = path.join(process.cwd(), '.claude', 'settings.local.json');
|
|
1208
|
+
console.log(`--- Claude Code ---`);
|
|
1209
|
+
console.log(`Settings file: ${settingsPath}`);
|
|
1210
|
+
|
|
1211
|
+
let settings = { permissions: { allow: [] } };
|
|
1212
|
+
if (fs.existsSync(settingsPath)) {
|
|
1213
|
+
try {
|
|
1214
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
1215
|
+
} catch (e) {
|
|
1216
|
+
console.error(` ERROR: Could not parse ${settingsPath}: ${e.message}`);
|
|
1217
|
+
return false;
|
|
1218
|
+
}
|
|
1219
|
+
if (!settings.permissions) settings.permissions = {};
|
|
1220
|
+
if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = [];
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const PROTOCOL_ENDPOINTS = [
|
|
1224
|
+
'attach', 'update', 'ask', 'read-context', 'write-context', 'deliver',
|
|
1225
|
+
'create-ticket', 'list-tickets', 'record-change-rationales', 'spawn',
|
|
1226
|
+
'discover-project', 'load-context', 'artifact-upload-file', 'artifact-download-url'
|
|
1227
|
+
];
|
|
1228
|
+
|
|
1229
|
+
const entries = [];
|
|
1230
|
+
for (const endpoint of PROTOCOL_ENDPOINTS) {
|
|
1231
|
+
entries.push(`Bash(curl -s -X POST "${platformUrl}/api/protocol/${endpoint}":*)`);
|
|
1232
|
+
}
|
|
1233
|
+
entries.push(`Bash(curl -s -H 'Authorization::*)`);
|
|
1234
|
+
for (const endpoint of PROTOCOL_ENDPOINTS) {
|
|
1235
|
+
entries.push(`Bash(curl -s -X POST "$OVERLORD_URL/api/protocol/${endpoint}":*)`);
|
|
1236
|
+
}
|
|
1237
|
+
entries.push(`Bash(curl -s -H "Authorization::*)`);
|
|
1238
|
+
|
|
1239
|
+
const existing = new Set(settings.permissions.allow);
|
|
1240
|
+
const toAdd = entries.filter((e) => !existing.has(e));
|
|
1241
|
+
|
|
1242
|
+
if (toAdd.length === 0) {
|
|
1243
|
+
console.log(' All required permissions already present. Nothing to do.\n');
|
|
1244
|
+
return true;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
console.log(` Adding ${toAdd.length} permission entries:`);
|
|
1248
|
+
for (const entry of toAdd) {
|
|
1249
|
+
console.log(` + ${entry}`);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Backup
|
|
1253
|
+
if (fs.existsSync(settingsPath)) {
|
|
1254
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1255
|
+
const backupPath = `${settingsPath}.backup-${ts}`;
|
|
1256
|
+
fs.copyFileSync(settingsPath, backupPath);
|
|
1257
|
+
console.log(` Backup: ${backupPath}`);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
settings.permissions.allow = [...settings.permissions.allow, ...toAdd];
|
|
1261
|
+
|
|
1262
|
+
const dir = path.dirname(settingsPath);
|
|
1263
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1264
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
1265
|
+
console.log(' Settings updated.\n');
|
|
1266
|
+
return true;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function installOpenCodePermissions(_platformUrl) {
|
|
1270
|
+
console.log(`--- OpenCode ---`);
|
|
1271
|
+
const configPath = path.join(os.homedir(), '.config', 'opencode', 'opencode.json');
|
|
1272
|
+
console.log(`Config file: ${configPath}`);
|
|
1273
|
+
|
|
1274
|
+
let config = {};
|
|
1275
|
+
if (fs.existsSync(configPath)) {
|
|
1276
|
+
try {
|
|
1277
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
1278
|
+
} catch (e) {
|
|
1279
|
+
console.error(` ERROR: Could not parse ${configPath}: ${e.message}`);
|
|
1280
|
+
return false;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
const existingPermission =
|
|
1285
|
+
config.permission && typeof config.permission === 'object' ? config.permission : {};
|
|
1286
|
+
const existingBash =
|
|
1287
|
+
existingPermission.bash && typeof existingPermission.bash === 'object'
|
|
1288
|
+
? existingPermission.bash
|
|
1289
|
+
: {};
|
|
1290
|
+
|
|
1291
|
+
const next = {
|
|
1292
|
+
...config,
|
|
1293
|
+
$schema: 'https://opencode.ai/config.json',
|
|
1294
|
+
permission: {
|
|
1295
|
+
...existingPermission,
|
|
1296
|
+
bash: {
|
|
1297
|
+
'*': 'ask',
|
|
1298
|
+
...existingBash,
|
|
1299
|
+
'ovld protocol *': 'allow',
|
|
1300
|
+
'curl -sS -X POST *': 'allow',
|
|
1301
|
+
'curl -s -X POST *': 'allow'
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
};
|
|
1305
|
+
|
|
1306
|
+
if (fs.existsSync(configPath)) {
|
|
1307
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
1308
|
+
const backupPath = `${configPath}.backup-${ts}`;
|
|
1309
|
+
fs.copyFileSync(configPath, backupPath);
|
|
1310
|
+
console.log(` Backup: ${backupPath}`);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const dir = path.dirname(configPath);
|
|
1314
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
1315
|
+
fs.writeFileSync(configPath, JSON.stringify(next, null, 2) + '\n');
|
|
1316
|
+
console.log(' Config updated.\n');
|
|
1317
|
+
return true;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
function installCodexPermissions(platformUrl) {
|
|
1321
|
+
console.log(`--- Codex ---`);
|
|
1322
|
+
console.log(' Codex does not support file-based permission configuration.');
|
|
1323
|
+
console.log(' To warm up permissions, run the following commands once inside a Codex session:');
|
|
1324
|
+
console.log(' (Codex will prompt for approval; approve each one to persist the prefix.)\n');
|
|
1325
|
+
|
|
1326
|
+
const PROTOCOL_ENDPOINTS = [
|
|
1327
|
+
'attach', 'update', 'ask', 'read-context', 'write-context', 'deliver',
|
|
1328
|
+
'create-ticket', 'list-tickets'
|
|
1329
|
+
];
|
|
1330
|
+
|
|
1331
|
+
for (const endpoint of PROTOCOL_ENDPOINTS) {
|
|
1332
|
+
console.log(` curl -s -X POST "${platformUrl}/api/protocol/${endpoint}" -H "Content-Type: application/json" -H "Authorization: Bearer \\$AGENT_TOKEN" -d '{}'`);
|
|
1333
|
+
}
|
|
1334
|
+
console.log(` curl -s -H "Authorization: Bearer \\$AGENT_TOKEN" "${platformUrl}/api/protocol/context/test"`);
|
|
1335
|
+
console.log();
|
|
1336
|
+
return true;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
953
1339
|
// ---------------------------------------------------------------------------
|
|
954
1340
|
// Public API
|
|
955
1341
|
// ---------------------------------------------------------------------------
|
|
@@ -959,18 +1345,103 @@ export async function runSetupCommand(args) {
|
|
|
959
1345
|
|
|
960
1346
|
if (agent === '--help' || agent === '-h' || agent === 'help') {
|
|
961
1347
|
console.log(`Usage:
|
|
1348
|
+
ovld setup Interactive setup (select agents and configure permissions)
|
|
962
1349
|
ovld setup claude Install Overlord bundle for Claude Code
|
|
963
1350
|
ovld setup codex Install Overlord Codex plugin bundle
|
|
964
1351
|
ovld setup cursor Install Overlord rules, slash commands, and permissions for Cursor
|
|
965
1352
|
ovld setup gemini Install Overlord slash commands and policy rules for Gemini CLI
|
|
966
1353
|
ovld setup opencode Install Overlord connector for OpenCode
|
|
967
1354
|
ovld setup all Install for all supported agents
|
|
968
|
-
ovld doctor Validate installed connectors`);
|
|
1355
|
+
ovld doctor Validate installed connectors and check for CLI updates`);
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Interactive mode when called without arguments
|
|
1360
|
+
if (!agent) {
|
|
1361
|
+
console.log('Welcome to Overlord agent setup!\n');
|
|
1362
|
+
|
|
1363
|
+
// Step 1: Select agents to install
|
|
1364
|
+
const agentLabels = supportedAgents.map(a => {
|
|
1365
|
+
const descriptions = {
|
|
1366
|
+
claude: 'Claude Code',
|
|
1367
|
+
codex: 'Codex',
|
|
1368
|
+
cursor: 'Cursor',
|
|
1369
|
+
gemini: 'Gemini CLI',
|
|
1370
|
+
opencode: 'OpenCode'
|
|
1371
|
+
};
|
|
1372
|
+
return `${a.padEnd(10)} - ${descriptions[a] || a}`;
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
const selectedLabels = await runCheckboxPrompt({
|
|
1376
|
+
message: 'Select agent connectors to install (Space to toggle, Enter to confirm):',
|
|
1377
|
+
choices: agentLabels,
|
|
1378
|
+
defaults: []
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
if (selectedLabels.length === 0) {
|
|
1382
|
+
console.log('\nNo agents selected. Setup cancelled.');
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// Extract agent names from selected labels
|
|
1387
|
+
const selectedAgents = selectedLabels.map(label => label.split('-')[0].trim());
|
|
1388
|
+
|
|
1389
|
+
// Step 2: Install selected agents
|
|
1390
|
+
console.log(`\nInstalling Overlord agent bundles for: ${selectedAgents.join(', ')}...\n`);
|
|
1391
|
+
|
|
1392
|
+
const installedAgents = [];
|
|
1393
|
+
for (const a of selectedAgents) {
|
|
1394
|
+
console.log(`[${a}]`);
|
|
1395
|
+
try {
|
|
1396
|
+
if (a === 'claude') installClaude();
|
|
1397
|
+
else if (a === 'codex') installCodex();
|
|
1398
|
+
else if (a === 'cursor') installCursor();
|
|
1399
|
+
else if (a === 'gemini') installGemini();
|
|
1400
|
+
else installOpenCode();
|
|
1401
|
+
installedAgents.push(a);
|
|
1402
|
+
} catch (err) {
|
|
1403
|
+
console.error(` ✗ Failed: ${err.message}`);
|
|
1404
|
+
}
|
|
1405
|
+
console.log();
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
if (installedAgents.length === 0) {
|
|
1409
|
+
console.log('No agents were successfully installed.');
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
// Step 3: Offer to configure agent permissions
|
|
1414
|
+
const agentsThatNeedPermissions = installedAgents.filter(a =>
|
|
1415
|
+
['claude', 'codex', 'opencode'].includes(a)
|
|
1416
|
+
);
|
|
1417
|
+
|
|
1418
|
+
if (agentsThatNeedPermissions.length > 0) {
|
|
1419
|
+
console.log('Agent connectors installed successfully!\n');
|
|
1420
|
+
|
|
1421
|
+
const shouldInstallPermissions = await askYesNo(
|
|
1422
|
+
'Would you like to configure agent permissions for Overlord protocol access?',
|
|
1423
|
+
true
|
|
1424
|
+
);
|
|
1425
|
+
|
|
1426
|
+
if (shouldInstallPermissions) {
|
|
1427
|
+
const platformUrl = getPlatformUrl();
|
|
1428
|
+
installAgentPermissions(agentsThatNeedPermissions, platformUrl);
|
|
1429
|
+
console.log('✓ Agent permissions configured.\n');
|
|
1430
|
+
} else {
|
|
1431
|
+
console.log('\nSkipped agent permissions configuration.');
|
|
1432
|
+
console.log('You can run the permission installer later with:');
|
|
1433
|
+
console.log(' node scripts/install-agent-permissions.mjs\n');
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
console.log('Setup complete! Run `ovld doctor` to verify your installation.');
|
|
969
1438
|
return;
|
|
970
1439
|
}
|
|
971
1440
|
|
|
972
1441
|
if (agent === 'all') {
|
|
973
1442
|
console.log('Installing Overlord agent bundle for all supported agents...\n');
|
|
1443
|
+
const installedAgents = [];
|
|
1444
|
+
|
|
974
1445
|
for (const a of supportedAgents) {
|
|
975
1446
|
console.log(`[${a}]`);
|
|
976
1447
|
try {
|
|
@@ -979,11 +1450,30 @@ export async function runSetupCommand(args) {
|
|
|
979
1450
|
else if (a === 'cursor') installCursor();
|
|
980
1451
|
else if (a === 'gemini') installGemini();
|
|
981
1452
|
else installOpenCode();
|
|
1453
|
+
installedAgents.push(a);
|
|
982
1454
|
} catch (err) {
|
|
983
1455
|
console.error(` ✗ Failed: ${err.message}`);
|
|
984
1456
|
}
|
|
985
1457
|
console.log();
|
|
986
1458
|
}
|
|
1459
|
+
|
|
1460
|
+
// Offer permissions setup for 'all' command too
|
|
1461
|
+
const agentsThatNeedPermissions = installedAgents.filter(a =>
|
|
1462
|
+
['claude', 'codex', 'opencode'].includes(a)
|
|
1463
|
+
);
|
|
1464
|
+
|
|
1465
|
+
if (agentsThatNeedPermissions.length > 0) {
|
|
1466
|
+
const shouldInstallPermissions = await askYesNo(
|
|
1467
|
+
'\nWould you like to configure agent permissions for Overlord protocol access?',
|
|
1468
|
+
true
|
|
1469
|
+
);
|
|
1470
|
+
|
|
1471
|
+
if (shouldInstallPermissions) {
|
|
1472
|
+
const platformUrl = getPlatformUrl();
|
|
1473
|
+
installAgentPermissions(agentsThatNeedPermissions, platformUrl);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
|
|
987
1477
|
console.log('Done.');
|
|
988
1478
|
return;
|
|
989
1479
|
}
|
|
@@ -1003,6 +1493,19 @@ export async function runSetupCommand(args) {
|
|
|
1003
1493
|
else if (agent === 'gemini') installGemini();
|
|
1004
1494
|
else installOpenCode();
|
|
1005
1495
|
console.log('\nDone.');
|
|
1496
|
+
|
|
1497
|
+
// Offer permissions setup for single agent install too
|
|
1498
|
+
if (['claude', 'codex', 'opencode'].includes(agent)) {
|
|
1499
|
+
const shouldInstallPermissions = await askYesNo(
|
|
1500
|
+
'\nWould you like to configure agent permissions for Overlord protocol access?',
|
|
1501
|
+
true
|
|
1502
|
+
);
|
|
1503
|
+
|
|
1504
|
+
if (shouldInstallPermissions) {
|
|
1505
|
+
const platformUrl = getPlatformUrl();
|
|
1506
|
+
installAgentPermissions([agent], platformUrl);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1006
1509
|
} catch (err) {
|
|
1007
1510
|
console.error(`\nFailed: ${err.message}`);
|
|
1008
1511
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overlord-cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.24.0",
|
|
4
4
|
"description": "Overlord CLI — launch AI agents on tickets from anywhere",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"bin/",
|
|
15
15
|
"plugins/"
|
|
16
16
|
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node bin/_cli/postinstall.mjs || true"
|
|
19
|
+
},
|
|
17
20
|
"engines": {
|
|
18
21
|
"node": ">=20"
|
|
19
22
|
},
|
|
@@ -7,6 +7,27 @@ const execFileAsync = promisify(execFile);
|
|
|
7
7
|
const OVLD_BIN = process.env.OVLD_BIN?.trim() || 'ovld';
|
|
8
8
|
const PROTOCOL_VERSION = '2025-06-18';
|
|
9
9
|
|
|
10
|
+
function execFileWithOptionalInput(file, args, options, input) {
|
|
11
|
+
if (input === undefined) {
|
|
12
|
+
return execFileAsync(file, args, options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const child = execFile(file, args, options, (error, stdout, stderr) => {
|
|
17
|
+
if (error) {
|
|
18
|
+
error.stdout = stdout;
|
|
19
|
+
error.stderr = stderr;
|
|
20
|
+
reject(error);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
resolve({ stdout, stderr });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
child.stdin?.end(input);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
10
31
|
const tools = [
|
|
11
32
|
{
|
|
12
33
|
name: 'discover_project',
|
|
@@ -239,7 +260,8 @@ const tools = [
|
|
|
239
260
|
},
|
|
240
261
|
{
|
|
241
262
|
name: 'deliver_ticket',
|
|
242
|
-
description:
|
|
263
|
+
description:
|
|
264
|
+
'Deliver final work back into Overlord with summary, artifacts, and change rationales. Large payloads are streamed to the CLI through stdin, so this tool does not create delivery scratch files.',
|
|
243
265
|
inputSchema: {
|
|
244
266
|
type: 'object',
|
|
245
267
|
properties: {
|
|
@@ -255,11 +277,14 @@ const tools = [
|
|
|
255
277
|
toCliFlags: args => ({
|
|
256
278
|
'session-key': args.session_key,
|
|
257
279
|
'ticket-id': args.ticket_id,
|
|
258
|
-
|
|
259
|
-
'artifacts-json': args.artifacts,
|
|
260
|
-
'change-rationales-json': args.change_rationales,
|
|
280
|
+
'payload-file': '-',
|
|
261
281
|
'skip-file-change-check': args.skip_file_change_check
|
|
262
282
|
}),
|
|
283
|
+
toCliStdin: args => JSON.stringify({
|
|
284
|
+
summary: args.summary,
|
|
285
|
+
...(Array.isArray(args.artifacts) ? { artifacts: args.artifacts } : {}),
|
|
286
|
+
...(Array.isArray(args.change_rationales) ? { changeRationales: args.change_rationales } : {})
|
|
287
|
+
}),
|
|
263
288
|
subcommand: 'deliver'
|
|
264
289
|
},
|
|
265
290
|
{
|
|
@@ -490,17 +515,24 @@ function cliArgsFromFlags(flags) {
|
|
|
490
515
|
}
|
|
491
516
|
|
|
492
517
|
async function runProtocol(tool, args) {
|
|
493
|
-
const
|
|
518
|
+
const toolArgs = args ?? {};
|
|
519
|
+
const cliArgs = ['protocol', tool.subcommand, ...cliArgsFromFlags(tool.toCliFlags(toolArgs))];
|
|
520
|
+
const stdin = typeof tool.toCliStdin === 'function' ? tool.toCliStdin(toolArgs) : undefined;
|
|
494
521
|
|
|
495
522
|
try {
|
|
496
|
-
const { stdout, stderr } = await
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
523
|
+
const { stdout, stderr } = await execFileWithOptionalInput(
|
|
524
|
+
OVLD_BIN,
|
|
525
|
+
cliArgs,
|
|
526
|
+
{
|
|
527
|
+
cwd: process.cwd(),
|
|
528
|
+
env: {
|
|
529
|
+
...process.env,
|
|
530
|
+
AGENT_IDENTIFIER: process.env.AGENT_IDENTIFIER ?? 'codex-overlord-plugin'
|
|
531
|
+
},
|
|
532
|
+
maxBuffer: 20 * 1024 * 1024
|
|
501
533
|
},
|
|
502
|
-
|
|
503
|
-
|
|
534
|
+
stdin
|
|
535
|
+
);
|
|
504
536
|
|
|
505
537
|
const trimmed = stdout.trim();
|
|
506
538
|
const data = trimmed ? JSON.parse(trimmed) : {};
|
|
@@ -15,7 +15,7 @@ Overlord plugin.
|
|
|
15
15
|
and stop.
|
|
16
16
|
6. Deliver last with `ovld protocol deliver`, including meaningful `changeRationales` for every
|
|
17
17
|
behavioral git-tracked change.
|
|
18
|
-
If you need `--payload-file`, `--artifacts-file`, or `--change-rationales-file
|
|
18
|
+
For larger delivery JSON, prefer `--payload-file -` and stream the full payload on stdin so no scratch file needs to be created or removed. If you need `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that JSON as ephemeral scratch data, not as a repository file. Remove it after delivery and never commit it.
|
|
19
19
|
|
|
20
20
|
## Rules
|
|
21
21
|
|