overlord-cli 3.21.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 +9 -2
- package/bin/_cli/cli-update.mjs +119 -0
- package/bin/_cli/index.mjs +23 -2
- package/bin/_cli/postinstall.mjs +42 -0
- package/bin/_cli/setup.mjs +509 -35
- package/bin/_cli/version.mjs +2 -5
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -13,6 +13,7 @@ npm install -g overlord-cli
|
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Use Node.js 20 or newer for every CLI install or update.
|
|
16
|
+
Run `ovld update` any time you want to refresh the global npm install to the latest release.
|
|
16
17
|
|
|
17
18
|
## Usage
|
|
18
19
|
|
|
@@ -31,6 +32,10 @@ ovld auth login
|
|
|
31
32
|
ovld attach
|
|
32
33
|
ovld create "Investigate the failing build"
|
|
33
34
|
ovld prompt "Draft a fix for the onboarding flow"
|
|
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
|
|
34
39
|
ovld setup codex
|
|
35
40
|
ovld setup cursor
|
|
36
41
|
ovld setup gemini
|
|
@@ -51,9 +56,11 @@ ovld doctor
|
|
|
51
56
|
- `auth` - log in, log out, or check auth status
|
|
52
57
|
- `tickets` - list or create tickets
|
|
53
58
|
- `ticket` - work with a single ticket
|
|
54
|
-
- `protocol` - run ticket lifecycle commands
|
|
55
|
-
- `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`
|
|
56
62
|
- `setup` - install the Overlord connector or plugin bundle for a supported agent
|
|
63
|
+
- `update` - install the latest CLI release from npm
|
|
57
64
|
- `doctor` - verify installed agent connectors and check whether a newer CLI version is available
|
|
58
65
|
|
|
59
66
|
## License
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const cliPackage = require('../../package.json');
|
|
8
|
+
|
|
9
|
+
const CURRENT_CLI_VERSION = typeof cliPackage.version === 'string' ? cliPackage.version : '0.0.0';
|
|
10
|
+
const CLI_PACKAGE_NAME =
|
|
11
|
+
typeof cliPackage.name === 'string' && cliPackage.name ? cliPackage.name : 'overlord-cli';
|
|
12
|
+
|
|
13
|
+
const ORANGE = '\x1b[38;5;208m';
|
|
14
|
+
const RESET = '\x1b[0m';
|
|
15
|
+
|
|
16
|
+
function colorizeOrange(text) {
|
|
17
|
+
return `${ORANGE}${text}${RESET}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getCurrentCliVersion() {
|
|
21
|
+
return CURRENT_CLI_VERSION;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getCliPackageName() {
|
|
25
|
+
return CLI_PACKAGE_NAME;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function fetchLatestCliVersion({
|
|
29
|
+
fetchImpl = fetch,
|
|
30
|
+
packageName = CLI_PACKAGE_NAME,
|
|
31
|
+
timeoutMs = 2500
|
|
32
|
+
} = {}) {
|
|
33
|
+
const controller = new AbortController();
|
|
34
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const response = await fetchImpl(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
38
|
+
signal: controller.signal,
|
|
39
|
+
headers: { Accept: 'application/json' }
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!response.ok) return null;
|
|
43
|
+
|
|
44
|
+
const payload = await response.json();
|
|
45
|
+
return typeof payload?.version === 'string' ? payload.version : null;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
} finally {
|
|
49
|
+
clearTimeout(timeout);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function checkForCliUpdate(options = {}) {
|
|
54
|
+
const currentVersion = options.currentVersion ?? CURRENT_CLI_VERSION;
|
|
55
|
+
const latestVersion = await fetchLatestCliVersion(options);
|
|
56
|
+
if (!latestVersion || latestVersion === currentVersion) return null;
|
|
57
|
+
return latestVersion;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function formatCliUpdateNotice(latestVersion, { currentVersion = CURRENT_CLI_VERSION } = {}) {
|
|
61
|
+
return `New Overlord CLI version available: v${latestVersion} (installed v${currentVersion}). Run \`ovld update\` to update via npm.`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function printCliUpdateNotice(
|
|
65
|
+
latestVersion,
|
|
66
|
+
{ currentVersion = CURRENT_CLI_VERSION, stream = process.stderr } = {}
|
|
67
|
+
) {
|
|
68
|
+
if (!latestVersion) return false;
|
|
69
|
+
stream.write(`${colorizeOrange(formatCliUpdateNotice(latestVersion, { currentVersion }))}\n`);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function runCliUpdateCommand({
|
|
74
|
+
currentVersion = CURRENT_CLI_VERSION,
|
|
75
|
+
fetchLatestVersionFn = fetchLatestCliVersion,
|
|
76
|
+
logger = console,
|
|
77
|
+
npmCommand = 'npm',
|
|
78
|
+
packageName = CLI_PACKAGE_NAME,
|
|
79
|
+
spawnSyncImpl = spawnSync
|
|
80
|
+
} = {}) {
|
|
81
|
+
const latestVersion = await fetchLatestVersionFn({ currentVersion, packageName });
|
|
82
|
+
|
|
83
|
+
if (latestVersion && latestVersion === currentVersion) {
|
|
84
|
+
logger.log(`Overlord CLI ${currentVersion} is already the latest version.`);
|
|
85
|
+
return { alreadyLatest: true, currentVersion, latestVersion };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const target = `${packageName}@latest`;
|
|
89
|
+
if (latestVersion) {
|
|
90
|
+
logger.log(`Updating Overlord CLI ${currentVersion} -> ${latestVersion} via npm...`);
|
|
91
|
+
} else {
|
|
92
|
+
logger.log(`Updating Overlord CLI via npm...`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = spawnSyncImpl(npmCommand, ['install', '-g', target], {
|
|
96
|
+
env: process.env,
|
|
97
|
+
stdio: 'inherit'
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (result.error) {
|
|
101
|
+
throw result.error;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof result.status === 'number' && result.status !== 0) {
|
|
105
|
+
throw new Error(`\`${npmCommand} install -g ${target}\` exited with status ${result.status}.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof result.signal === 'string') {
|
|
109
|
+
throw new Error(`\`${npmCommand} install -g ${target}\` was terminated by ${result.signal}.`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (latestVersion) {
|
|
113
|
+
logger.log(`Overlord CLI updated to v${latestVersion}.`);
|
|
114
|
+
} else {
|
|
115
|
+
logger.log('Overlord CLI update complete. Run `ovld version` to confirm the installed version.');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return { alreadyLatest: false, currentVersion, latestVersion, result };
|
|
119
|
+
}
|
package/bin/_cli/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { runAttachCommand } from './attach.mjs';
|
|
4
4
|
import { runAuthCommand } from './auth.mjs';
|
|
5
|
+
import { checkForCliUpdate, printCliUpdateNotice, runCliUpdateCommand } from './cli-update.mjs';
|
|
5
6
|
import { runLauncherCommand } from './launcher.mjs';
|
|
6
7
|
import { runProtocolCommand } from './protocol.mjs';
|
|
7
8
|
import { runDoctorCommand, runSetupCommand } from './setup.mjs';
|
|
@@ -37,7 +38,8 @@ Usage:
|
|
|
37
38
|
${primaryCommand} connect <agent> Launch an agent on a ticket
|
|
38
39
|
${primaryCommand} restart <agent> Resume an agent session
|
|
39
40
|
${primaryCommand} context Print ticket context (requires TICKET_ID)
|
|
40
|
-
${primaryCommand} setup
|
|
41
|
+
${primaryCommand} setup [agent|all] Install Overlord agent connector (interactive if no args)
|
|
42
|
+
${primaryCommand} update Install the latest CLI version from npm
|
|
41
43
|
${primaryCommand} doctor Validate installed agent connectors and check for CLI updates
|
|
42
44
|
${primaryCommand} version Show the installed CLI version
|
|
43
45
|
${primaryCommand} help Show this help message
|
|
@@ -66,6 +68,15 @@ Run a subcommand with --help for more detail.
|
|
|
66
68
|
export async function runCli({ primaryCommand }) {
|
|
67
69
|
assertSupportedNodeVersion();
|
|
68
70
|
const [command, ...rest] = process.argv.slice(2);
|
|
71
|
+
const shouldCheckForUpdate =
|
|
72
|
+
Boolean(process.stdout.isTTY || process.stderr.isTTY) &&
|
|
73
|
+
command !== 'doctor' &&
|
|
74
|
+
command !== 'update';
|
|
75
|
+
const latestCliVersion = shouldCheckForUpdate ? await checkForCliUpdate() : null;
|
|
76
|
+
|
|
77
|
+
if (latestCliVersion && command !== 'doctor' && command !== 'update') {
|
|
78
|
+
printCliUpdateNotice(latestCliVersion);
|
|
79
|
+
}
|
|
69
80
|
|
|
70
81
|
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
71
82
|
printHelp(primaryCommand);
|
|
@@ -119,8 +130,18 @@ export async function runCli({ primaryCommand }) {
|
|
|
119
130
|
return;
|
|
120
131
|
}
|
|
121
132
|
|
|
133
|
+
if (command === 'update') {
|
|
134
|
+
if (rest[0] === '--help' || rest[0] === '-h' || rest[0] === 'help') {
|
|
135
|
+
console.log(`Usage:
|
|
136
|
+
${primaryCommand} update Install the latest CLI version from npm`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await runCliUpdateCommand();
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
122
143
|
if (command === 'doctor') {
|
|
123
|
-
await runDoctorCommand();
|
|
144
|
+
await runDoctorCommand({ latestCliVersion });
|
|
124
145
|
return;
|
|
125
146
|
}
|
|
126
147
|
|
|
@@ -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
|
@@ -12,6 +12,7 @@ import fs from 'node:fs';
|
|
|
12
12
|
import os from 'node:os';
|
|
13
13
|
import path from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { checkForCliUpdate, getCurrentCliVersion, printCliUpdateNotice } from './cli-update.mjs';
|
|
15
16
|
|
|
16
17
|
const BUNDLE_VERSION = '1.8.0';
|
|
17
18
|
const MD_MARKER_START = '<!-- overlord:managed:start -->';
|
|
@@ -280,11 +281,6 @@ function readJsonFileOrNull(filePath) {
|
|
|
280
281
|
}
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
function localCliVersion() {
|
|
284
|
-
const cliPackage = readJsonFileOrNull(path.resolve(__dirname, '..', '..', 'package.json'));
|
|
285
|
-
return typeof cliPackage?.version === 'string' ? cliPackage.version : null;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
284
|
function writeJsonFile(filePath, data) {
|
|
289
285
|
const dir = path.dirname(filePath);
|
|
290
286
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
@@ -483,28 +479,6 @@ function currentContentHashForAgent(agent) {
|
|
|
483
479
|
);
|
|
484
480
|
}
|
|
485
481
|
|
|
486
|
-
async function checkForCliUpdate() {
|
|
487
|
-
const currentVersion = localCliVersion();
|
|
488
|
-
if (!currentVersion) return null;
|
|
489
|
-
const controller = new AbortController();
|
|
490
|
-
const timeout = setTimeout(() => controller.abort(), 2500);
|
|
491
|
-
try {
|
|
492
|
-
const response = await fetch('https://registry.npmjs.org/overlord-cli/latest', {
|
|
493
|
-
signal: controller.signal,
|
|
494
|
-
headers: { Accept: 'application/json' }
|
|
495
|
-
});
|
|
496
|
-
if (!response.ok) return null;
|
|
497
|
-
const payload = await response.json();
|
|
498
|
-
const latestVersion = typeof payload?.version === 'string' ? payload.version : null;
|
|
499
|
-
if (!latestVersion) return null;
|
|
500
|
-
return latestVersion === currentVersion ? null : latestVersion;
|
|
501
|
-
} catch {
|
|
502
|
-
return null;
|
|
503
|
-
} finally {
|
|
504
|
-
clearTimeout(timeout);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
482
|
function codexSourcePluginDir() {
|
|
509
483
|
if (fs.existsSync(PACKAGE_PLUGIN_DIR)) return PACKAGE_PLUGIN_DIR;
|
|
510
484
|
if (fs.existsSync(REPO_PLUGIN_DIR)) return REPO_PLUGIN_DIR;
|
|
@@ -976,6 +950,392 @@ function currentNodeMajor() {
|
|
|
976
950
|
return Number.parseInt(process.versions.node.split('.')[0] ?? '', 10);
|
|
977
951
|
}
|
|
978
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
|
+
|
|
979
1339
|
// ---------------------------------------------------------------------------
|
|
980
1340
|
// Public API
|
|
981
1341
|
// ---------------------------------------------------------------------------
|
|
@@ -985,18 +1345,103 @@ export async function runSetupCommand(args) {
|
|
|
985
1345
|
|
|
986
1346
|
if (agent === '--help' || agent === '-h' || agent === 'help') {
|
|
987
1347
|
console.log(`Usage:
|
|
1348
|
+
ovld setup Interactive setup (select agents and configure permissions)
|
|
988
1349
|
ovld setup claude Install Overlord bundle for Claude Code
|
|
989
1350
|
ovld setup codex Install Overlord Codex plugin bundle
|
|
990
1351
|
ovld setup cursor Install Overlord rules, slash commands, and permissions for Cursor
|
|
991
1352
|
ovld setup gemini Install Overlord slash commands and policy rules for Gemini CLI
|
|
992
1353
|
ovld setup opencode Install Overlord connector for OpenCode
|
|
993
1354
|
ovld setup all Install for all supported agents
|
|
994
|
-
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.');
|
|
995
1438
|
return;
|
|
996
1439
|
}
|
|
997
1440
|
|
|
998
1441
|
if (agent === 'all') {
|
|
999
1442
|
console.log('Installing Overlord agent bundle for all supported agents...\n');
|
|
1443
|
+
const installedAgents = [];
|
|
1444
|
+
|
|
1000
1445
|
for (const a of supportedAgents) {
|
|
1001
1446
|
console.log(`[${a}]`);
|
|
1002
1447
|
try {
|
|
@@ -1005,11 +1450,30 @@ export async function runSetupCommand(args) {
|
|
|
1005
1450
|
else if (a === 'cursor') installCursor();
|
|
1006
1451
|
else if (a === 'gemini') installGemini();
|
|
1007
1452
|
else installOpenCode();
|
|
1453
|
+
installedAgents.push(a);
|
|
1008
1454
|
} catch (err) {
|
|
1009
1455
|
console.error(` ✗ Failed: ${err.message}`);
|
|
1010
1456
|
}
|
|
1011
1457
|
console.log();
|
|
1012
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
|
+
|
|
1013
1477
|
console.log('Done.');
|
|
1014
1478
|
return;
|
|
1015
1479
|
}
|
|
@@ -1029,13 +1493,26 @@ export async function runSetupCommand(args) {
|
|
|
1029
1493
|
else if (agent === 'gemini') installGemini();
|
|
1030
1494
|
else installOpenCode();
|
|
1031
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
|
+
}
|
|
1032
1509
|
} catch (err) {
|
|
1033
1510
|
console.error(`\nFailed: ${err.message}`);
|
|
1034
1511
|
process.exit(1);
|
|
1035
1512
|
}
|
|
1036
1513
|
}
|
|
1037
1514
|
|
|
1038
|
-
export async function runDoctorCommand() {
|
|
1515
|
+
export async function runDoctorCommand({ latestCliVersion = null } = {}) {
|
|
1039
1516
|
console.log('Overlord agent bundle status:\n');
|
|
1040
1517
|
let allOk = true;
|
|
1041
1518
|
const nodeMajor = currentNodeMajor();
|
|
@@ -1051,18 +1528,15 @@ export async function runDoctorCommand() {
|
|
|
1051
1528
|
for (const agent of supportedAgents) {
|
|
1052
1529
|
if (!doctorAgent(agent)) allOk = false;
|
|
1053
1530
|
}
|
|
1054
|
-
const
|
|
1531
|
+
const updateVersion = latestCliVersion ?? (await checkForCliUpdate());
|
|
1055
1532
|
console.log();
|
|
1056
1533
|
if (allOk) {
|
|
1057
1534
|
console.log('All bundles are up to date.');
|
|
1058
1535
|
} else {
|
|
1059
1536
|
console.log('Run `ovld setup <agent>` or `ovld setup all` to install/repair.');
|
|
1060
1537
|
}
|
|
1061
|
-
if (
|
|
1538
|
+
if (updateVersion) {
|
|
1062
1539
|
console.log();
|
|
1063
|
-
|
|
1064
|
-
console.log(
|
|
1065
|
-
`Update with Node.js ${REQUIRED_NODE_MAJOR}+ and reinstall the installed CLI wrapper if needed.`
|
|
1066
|
-
);
|
|
1540
|
+
printCliUpdateNotice(updateVersion, { currentVersion: getCurrentCliVersion(), stream: process.stdout });
|
|
1067
1541
|
}
|
|
1068
1542
|
}
|
package/bin/_cli/version.mjs
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
const require = createRequire(import.meta.url);
|
|
6
|
-
const { version } = require('../../package.json');
|
|
3
|
+
import { getCurrentCliVersion } from './cli-update.mjs';
|
|
7
4
|
|
|
8
5
|
export function runVersionCommand() {
|
|
9
|
-
console.log(`Overlord CLI ${
|
|
6
|
+
console.log(`Overlord CLI ${getCurrentCliVersion()}`);
|
|
10
7
|
}
|
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
|
},
|