overlord-cli 0.2605151717.0 → 0.2605161310.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.
@@ -2,6 +2,9 @@
2
2
 
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { createRequire } from 'node:module';
5
+ import fs from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
5
8
 
6
9
  const require = createRequire(import.meta.url);
7
10
  const cliPackage = require('../../package.json');
@@ -50,6 +53,41 @@ export async function fetchLatestCliVersion({
50
53
  }
51
54
  }
52
55
 
56
+ function pruneOldCliVersions() {
57
+ const cliInstallRoot = path.join(os.homedir(), '.ovld', 'cli');
58
+ if (!fs.existsSync(cliInstallRoot)) return;
59
+
60
+ let entries;
61
+ try {
62
+ entries = fs.readdirSync(cliInstallRoot);
63
+ } catch {
64
+ return;
65
+ }
66
+
67
+ const versionDirs = entries
68
+ .filter(name => name.startsWith('overlord-cli-'))
69
+ .flatMap(name => {
70
+ const dirPath = path.join(cliInstallRoot, name);
71
+ try {
72
+ const stat = fs.statSync(dirPath);
73
+ if (!stat.isDirectory()) return [];
74
+ return [{ dirPath, mtime: stat.mtimeMs }];
75
+ } catch {
76
+ return [];
77
+ }
78
+ });
79
+
80
+ // Sort newest-first, keep current + 1 previous, delete the rest
81
+ versionDirs.sort((a, b) => b.mtime - a.mtime);
82
+ for (const { dirPath } of versionDirs.slice(2)) {
83
+ try {
84
+ fs.rmSync(dirPath, { recursive: true, force: true });
85
+ } catch {
86
+ // Best-effort cleanup
87
+ }
88
+ }
89
+ }
90
+
53
91
  export async function checkForCliUpdate(options = {}) {
54
92
  const currentVersion = options.currentVersion ?? CURRENT_CLI_VERSION;
55
93
  const latestVersion = await fetchLatestCliVersion(options);
@@ -115,5 +153,7 @@ export async function runCliUpdateCommand({
115
153
  logger.log('Overlord CLI update complete. Run `ovld version` to confirm the installed version.');
116
154
  }
117
155
 
156
+ pruneOldCliVersions();
157
+
118
158
  return { alreadyLatest: false, currentVersion, latestVersion, result };
119
159
  }
@@ -68,10 +68,11 @@ const agentIdentifierMap = {
68
68
  codex: 'codex',
69
69
  cursor: 'cursor',
70
70
  gemini: 'gemini',
71
- opencode: 'opencode'
71
+ opencode: 'opencode',
72
+ pi: 'pi'
72
73
  };
73
74
 
74
- const supportedAgents = ['claude', 'codex', 'cursor', 'gemini', 'opencode'];
75
+ const supportedAgents = ['claude', 'codex', 'cursor', 'gemini', 'opencode', 'pi'];
75
76
 
76
77
  function shellQuote(value) {
77
78
  return `'${String(value).replace(/'/g, `'\\''`)}'`;
@@ -98,6 +99,8 @@ function buildExtraArgs(agent, options = {}) {
98
99
  args.push('-c', `model_reasoning_effort=${toTomlString(options.thinking)}`);
99
100
  } else if (agent === 'gemini') {
100
101
  args.push('--thinking-level', options.thinking);
102
+ } else if (agent === 'pi') {
103
+ args.push('--thinking', options.thinking);
101
104
  }
102
105
  }
103
106
 
@@ -378,6 +381,17 @@ async function runAgent(agent, mode = 'run', options = {}) {
378
381
  env: childEnv
379
382
  });
380
383
  }
384
+ } else if (agent === 'pi') {
385
+ if (mode === 'resume') {
386
+ const piSessionId = process.env.PI_SESSION_ID?.trim();
387
+ const args = piSessionId
388
+ ? ['--session', piSessionId, context]
389
+ : ['--continue', context];
390
+ args.unshift(...extraArgs);
391
+ execFileSync('pi', args, { stdio: 'inherit', env: childEnv });
392
+ } else {
393
+ execFileSync('pi', [...extraArgs, context], { stdio: 'inherit', env: childEnv });
394
+ }
381
395
  } else if (agent === 'gemini') {
382
396
  // Write context to a temp file. Passing inline content as a positional arg
383
397
  // causes Gemini's @-reference parser to lstat(cwd + content) when it encounters
@@ -413,7 +427,9 @@ async function runAgent(agent, mode = 'run', options = {}) {
413
427
  ? `No prior Codex session was found. Start one with \`ovld launch codex --ticket-id <ticket_id>\` first.`
414
428
  : agent === 'opencode'
415
429
  ? `No prior OpenCode session was found. Start one with \`ovld launch opencode --ticket-id <ticket_id>\` first.`
416
- : '';
430
+ : agent === 'pi'
431
+ ? `No prior Pi session was found. Start one with \`ovld launch pi --ticket-id <ticket_id>\` first.`
432
+ : '';
417
433
  const message = error instanceof Error ? error.message : String(error);
418
434
 
419
435
  if (isResume && noSessionHint) {
@@ -1035,6 +1035,42 @@ async function protocolAsk(args) {
1035
1035
  console.log(JSON.stringify(data, null, 2));
1036
1036
  }
1037
1037
 
1038
+ // ---------------------------------------------------------------------------
1039
+ // request-approval-gate
1040
+ // ---------------------------------------------------------------------------
1041
+
1042
+ async function protocolRequestApprovalGate(args) {
1043
+ const flags = parseFlags(args);
1044
+ const { sessionKey, ticketId } = resolveSessionFlags(flags);
1045
+ if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
1046
+ if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
1047
+ const reason = flags['reason-file']
1048
+ ? await readTextFileOrStdin(String(flags['reason-file']), '--reason-file')
1049
+ : requireFlag(flags, 'reason', undefined);
1050
+
1051
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1052
+ await resolveProtocolAuthForFlags(flags, ticketId);
1053
+ const timeoutMs = resolveTimeout(flags);
1054
+
1055
+ const body = {
1056
+ sessionKey,
1057
+ ticketId,
1058
+ reason,
1059
+ ...(flags['objective-id'] ? { objectiveId: String(flags['objective-id']) } : {})
1060
+ };
1061
+
1062
+ const data = await apiPost(
1063
+ platformUrl,
1064
+ bearerToken,
1065
+ localSecret,
1066
+ organizationId,
1067
+ '/api/protocol/request-approval-gate',
1068
+ body,
1069
+ timeoutMs
1070
+ );
1071
+ console.log(JSON.stringify(data, null, 2));
1072
+ }
1073
+
1038
1074
  // ---------------------------------------------------------------------------
1039
1075
  // permission-request
1040
1076
  // ---------------------------------------------------------------------------
@@ -2238,6 +2274,7 @@ Subcommands:
2238
2274
  update Post progress, activity events, and optional change rationales
2239
2275
  record-change-rationales Persist structured change rationales without a progress update
2240
2276
  ask Post a blocking question and move the ticket to review
2277
+ request-approval-gate Flip auto_advance=false on the next queued future objective
2241
2278
  permission-request Notify Overlord that the agent is requesting tool permission
2242
2279
  hook-event Record a lifecycle hook event without a session key
2243
2280
  read-context Read shared persistent context for this ticket
@@ -2779,6 +2816,10 @@ EOF
2779
2816
  await protocolAsk(args);
2780
2817
  return;
2781
2818
  }
2819
+ if (subcommand === 'request-approval-gate') {
2820
+ await protocolRequestApprovalGate(args);
2821
+ return;
2822
+ }
2782
2823
  if (subcommand === 'permission-request') {
2783
2824
  await protocolPermissionRequest(args);
2784
2825
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605151717.0",
3
+ "version": "0.2605161310.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",
@@ -292,6 +292,31 @@ const tools = [
292
292
  }),
293
293
  subcommand: 'ask'
294
294
  },
295
+ {
296
+ name: 'request_approval_gate',
297
+ description:
298
+ 'Flip auto_advance=false on the next queued future objective so a human must approve before it runs.',
299
+ inputSchema: {
300
+ type: 'object',
301
+ properties: {
302
+ session_key: { type: 'string' },
303
+ ticket_id: {
304
+ type: 'string',
305
+ description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
306
+ },
307
+ reason: { type: 'string' },
308
+ objective_id: { type: 'string' }
309
+ },
310
+ required: ['session_key', 'ticket_id', 'reason']
311
+ },
312
+ toCliFlags: args => ({
313
+ 'session-key': args.session_key,
314
+ 'ticket-id': args.ticket_id,
315
+ reason: args.reason,
316
+ 'objective-id': args.objective_id
317
+ }),
318
+ subcommand: 'request-approval-gate'
319
+ },
295
320
  {
296
321
  name: 'read_context',
297
322
  description: 'Read persistent shared context entries for a ticket.',