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 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`, `run`, `resume`, `context` - launch or resume an agent session
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
@@ -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 <agent|all> Install Overlord agent connector
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
+ `);
@@ -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.22.0",
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
  },