overlord-cli 3.21.0 → 3.22.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
@@ -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,7 @@ 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
34
36
  ovld setup codex
35
37
  ovld setup cursor
36
38
  ovld setup gemini
@@ -54,6 +56,7 @@ ovld doctor
54
56
  - `protocol` - run ticket lifecycle commands
55
57
  - `connect`, `restart`, `run`, `resume`, `context` - launch or resume an agent session
56
58
  - `setup` - install the Overlord connector or plugin bundle for a supported agent
59
+ - `update` - install the latest CLI release from npm
57
60
  - `doctor` - verify installed agent connectors and check whether a newer CLI version is available
58
61
 
59
62
  ## 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
+ }
@@ -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';
@@ -38,6 +39,7 @@ Usage:
38
39
  ${primaryCommand} restart <agent> Resume an agent session
39
40
  ${primaryCommand} context Print ticket context (requires TICKET_ID)
40
41
  ${primaryCommand} setup <agent|all> Install Overlord agent connector
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
 
@@ -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;
@@ -1035,7 +1009,7 @@ export async function runSetupCommand(args) {
1035
1009
  }
1036
1010
  }
1037
1011
 
1038
- export async function runDoctorCommand() {
1012
+ export async function runDoctorCommand({ latestCliVersion = null } = {}) {
1039
1013
  console.log('Overlord agent bundle status:\n');
1040
1014
  let allOk = true;
1041
1015
  const nodeMajor = currentNodeMajor();
@@ -1051,18 +1025,15 @@ export async function runDoctorCommand() {
1051
1025
  for (const agent of supportedAgents) {
1052
1026
  if (!doctorAgent(agent)) allOk = false;
1053
1027
  }
1054
- const latestCliVersion = await checkForCliUpdate();
1028
+ const updateVersion = latestCliVersion ?? (await checkForCliUpdate());
1055
1029
  console.log();
1056
1030
  if (allOk) {
1057
1031
  console.log('All bundles are up to date.');
1058
1032
  } else {
1059
1033
  console.log('Run `ovld setup <agent>` or `ovld setup all` to install/repair.');
1060
1034
  }
1061
- if (latestCliVersion) {
1035
+ if (updateVersion) {
1062
1036
  console.log();
1063
- console.log(`CLI update available: ${latestCliVersion}`);
1064
- console.log(
1065
- `Update with Node.js ${REQUIRED_NODE_MAJOR}+ and reinstall the installed CLI wrapper if needed.`
1066
- );
1037
+ printCliUpdateNotice(updateVersion, { currentVersion: getCurrentCliVersion(), stream: process.stdout });
1067
1038
  }
1068
1039
  }
@@ -1,10 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { createRequire } from 'node:module';
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 ${version}`);
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.21.0",
3
+ "version": "3.22.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "type": "module",
6
6
  "bin": {