overlord-cli 3.22.0 → 3.23.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/setup.mjs +504 -1
- package/package.json +4 -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/setup.mjs
CHANGED
|
@@ -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.23.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
|
},
|