overlord-cli 4.6.0 → 4.8.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/bin/_cli/auth.mjs CHANGED
@@ -9,8 +9,10 @@ import {
9
9
  buildAuthHeaders,
10
10
  clearCredentials,
11
11
  getDefaultOverlordUrl,
12
+ getAuthStatus,
12
13
  loadCredentials,
13
14
  loadRuntime,
15
+ repairCredentials,
14
16
  saveCredentials
15
17
  } from './credentials.mjs';
16
18
 
@@ -460,7 +462,30 @@ export async function authLogin() {
460
462
  console.log('Logged in successfully!');
461
463
  }
462
464
 
463
- export function authStatus() {
465
+ function printVerboseAuthStatus() {
466
+ const status = getAuthStatus();
467
+ if (!status.isLoggedIn) {
468
+ console.log('Not logged in. Run: ovld auth login');
469
+ } else {
470
+ console.log('Logged in');
471
+ }
472
+ console.log(` Platform URL: ${status.platformUrl}`);
473
+ console.log(` Platform source: ${status.platformUrlSource}`);
474
+ console.log(` Token source: ${status.tokenSource}`);
475
+ console.log(` Token present: ${status.tokenPresent ? 'yes' : 'no'}`);
476
+ console.log(` Local secret: ${status.hasLocalSecret ? 'yes' : 'no'}`);
477
+ console.log(` credentials.json: ${status.credentialsFileExists ? 'present' : 'missing'}`);
478
+ console.log(
479
+ ` electron-credentials.json: ${status.electronCredentialsFileExists ? 'present' : 'missing'}`
480
+ );
481
+ }
482
+
483
+ export function authStatus(args = []) {
484
+ if (args.includes('--verbose') || args.includes('-v')) {
485
+ printVerboseAuthStatus();
486
+ return;
487
+ }
488
+
464
489
  const creds = loadCredentials();
465
490
  if (!creds) {
466
491
  console.log('Not logged in. Run: ovld auth login');
@@ -478,13 +503,24 @@ export function authLogout() {
478
503
  console.log('Logged out.');
479
504
  }
480
505
 
481
- export async function runAuthCommand(subcommand) {
506
+ export function authRepair() {
507
+ const result = repairCredentials();
508
+ if (result.repaired) {
509
+ console.log('Credentials repaired.');
510
+ } else {
511
+ console.log(`Credentials not repaired: ${result.reason}`);
512
+ }
513
+ printVerboseAuthStatus();
514
+ }
515
+
516
+ export async function runAuthCommand(subcommand, args = []) {
482
517
  if (!subcommand || subcommand === 'help' || subcommand === '--help') {
483
518
  console.log(`ovld auth <subcommand>
484
519
 
485
520
  Subcommands:
486
521
  login Authorize the CLI via browser (works locally or over SSH)
487
- status Show current login status
522
+ status Show current login status (use --verbose for redacted diagnostics)
523
+ repair Mirror and chmod shared Desktop/CLI credentials when possible
488
524
  logout Remove stored credentials
489
525
  `);
490
526
  return;
@@ -496,7 +532,12 @@ Subcommands:
496
532
  }
497
533
 
498
534
  if (subcommand === 'status') {
499
- authStatus();
535
+ authStatus(args);
536
+ return;
537
+ }
538
+
539
+ if (subcommand === 'repair') {
540
+ authRepair();
500
541
  return;
501
542
  }
502
543
 
@@ -9,6 +9,7 @@ import { fileURLToPath } from 'node:url';
9
9
 
10
10
  const CREDENTIALS_DIR = path.join(os.homedir(), '.ovld');
11
11
  const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
12
+ const ELECTRON_CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'electron-credentials.json');
12
13
  const RUNTIME_FILE_PATTERN = /^runtime\..+\.json$/;
13
14
  const HOSTED_OVERLORD_URL = 'https://www.ovld.ai';
14
15
  const LOCAL_DEV_OVERLORD_URL = 'http://localhost:3000';
@@ -18,30 +19,121 @@ const LOCAL_SECRET_HEADER = 'X-Overlord-Local-Secret';
18
19
  * @typedef {{ access_token: string, platform_url: string, user_email?: string }} Credentials
19
20
  */
20
21
 
21
- /** @returns {Credentials | null} */
22
- export function loadCredentials() {
22
+ function ensureCredentialsDir() {
23
+ fs.mkdirSync(CREDENTIALS_DIR, { recursive: true, mode: 0o700 });
24
+ try {
25
+ fs.chmodSync(CREDENTIALS_DIR, 0o700);
26
+ } catch {
27
+ // Best-effort hardening for existing directories.
28
+ }
29
+ }
30
+
31
+ function readJsonFile(filePath) {
23
32
  try {
24
- const raw = fs.readFileSync(CREDENTIALS_FILE, 'utf8');
33
+ const raw = fs.readFileSync(filePath, 'utf8');
25
34
  return JSON.parse(raw);
26
35
  } catch {
27
36
  return null;
28
37
  }
29
38
  }
30
39
 
40
+ function fileExists(filePath) {
41
+ try {
42
+ return fs.statSync(filePath).isFile();
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ function writeJsonFileAtomic(filePath, data) {
49
+ ensureCredentialsDir();
50
+ const tempFile = `${filePath}.tmp-${process.pid}-${Date.now()}`;
51
+ fs.writeFileSync(tempFile, JSON.stringify(data, null, 2), { mode: 0o600 });
52
+ fs.renameSync(tempFile, filePath);
53
+ fs.chmodSync(filePath, 0o600);
54
+ }
55
+
56
+ function parseStoredCredentialsData(parsed, { requireAccessToken = false } = {}) {
57
+ if (!parsed || typeof parsed !== 'object') return null;
58
+
59
+ const accessToken = normalizeAgentToken(parsed.access_token);
60
+ const platformUrl = typeof parsed.platform_url === 'string' ? parsed.platform_url.trim() : '';
61
+ if (requireAccessToken && !accessToken) return null;
62
+ if (!accessToken && !platformUrl) return null;
63
+
64
+ return {
65
+ access_token: accessToken,
66
+ platform_url: platformUrl,
67
+ ...(typeof parsed.user_email === 'string' && parsed.user_email.trim()
68
+ ? { user_email: parsed.user_email.trim() }
69
+ : {})
70
+ };
71
+ }
72
+
73
+ function normalizeCredentialsForSave(data) {
74
+ const parsed = parseStoredCredentialsData(data, { requireAccessToken: true });
75
+ if (!parsed) return null;
76
+
77
+ const platformUrl = normalizePlatformUrl(parsed.platform_url);
78
+ if (!platformUrl) return null;
79
+
80
+ return {
81
+ ...parsed,
82
+ platform_url: platformUrl
83
+ };
84
+ }
85
+
86
+ /** @returns {Credentials | null} */
87
+ export function loadCredentials() {
88
+ return (
89
+ parseStoredCredentialsData(readJsonFile(ELECTRON_CREDENTIALS_FILE), {
90
+ requireAccessToken: true
91
+ }) ??
92
+ parseStoredCredentialsData(readJsonFile(CREDENTIALS_FILE))
93
+ );
94
+ }
95
+
31
96
  /** @param {Credentials} data */
32
97
  export function saveCredentials(data) {
33
- fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
34
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
98
+ const credentials = normalizeCredentialsForSave(data);
99
+ if (!credentials) {
100
+ throw new Error('Cannot save empty Overlord credentials.');
101
+ }
102
+
103
+ writeJsonFileAtomic(CREDENTIALS_FILE, credentials);
104
+
105
+ // `electron-credentials.json` is now the shared desktop/CLI credential record.
106
+ // Preserve Electron-only encrypted fields when CLI login refreshes the agent token.
107
+ const existingElectronCredentials = readJsonFile(ELECTRON_CREDENTIALS_FILE);
108
+ const electronPayload =
109
+ existingElectronCredentials && typeof existingElectronCredentials === 'object'
110
+ ? { ...existingElectronCredentials, ...credentials }
111
+ : credentials;
112
+ writeJsonFileAtomic(ELECTRON_CREDENTIALS_FILE, electronPayload);
35
113
  }
36
114
 
37
115
  export function clearCredentials() {
38
- try {
39
- fs.unlinkSync(CREDENTIALS_FILE);
40
- } catch {
41
- // Already gone
116
+ for (const filePath of [CREDENTIALS_FILE, ELECTRON_CREDENTIALS_FILE]) {
117
+ try {
118
+ fs.unlinkSync(filePath);
119
+ } catch {
120
+ // Already gone
121
+ }
42
122
  }
43
123
  }
44
124
 
125
+ function getCredentialFileSource() {
126
+ const electronCredentials = parseStoredCredentialsData(readJsonFile(ELECTRON_CREDENTIALS_FILE), {
127
+ requireAccessToken: true
128
+ });
129
+ if (electronCredentials) return 'electron-credentials.json';
130
+
131
+ const cliCredentials = parseStoredCredentialsData(readJsonFile(CREDENTIALS_FILE));
132
+ if (cliCredentials) return 'credentials.json';
133
+
134
+ return 'none';
135
+ }
136
+
45
137
  function getRuntimeFilePath(targetUrl) {
46
138
  try {
47
139
  const parsed = new URL(targetUrl);
@@ -254,6 +346,55 @@ export function resolveAuth() {
254
346
  };
255
347
  }
256
348
 
349
+ export function getAuthStatus() {
350
+ const creds = loadCredentials();
351
+ const resolved = resolveAuth();
352
+
353
+ let tokenSource = 'fallback';
354
+ if (normalizeAgentToken(process.env.AGENT_TOKEN)) {
355
+ tokenSource = 'AGENT_TOKEN';
356
+ } else if (normalizeAgentToken(creds?.access_token)) {
357
+ tokenSource = getCredentialFileSource();
358
+ }
359
+
360
+ let platformUrlSource = 'default';
361
+ if (normalizePlatformUrl(process.env.OVERLORD_URL)) {
362
+ platformUrlSource = 'OVERLORD_URL';
363
+ } else if (normalizeStoredPlatformUrl(creds?.platform_url)) {
364
+ platformUrlSource = getCredentialFileSource();
365
+ }
366
+
367
+ return {
368
+ isLoggedIn: tokenSource !== 'fallback',
369
+ platformUrl: resolved.platformUrl,
370
+ platformUrlSource,
371
+ tokenPresent: tokenSource !== 'fallback',
372
+ tokenSource,
373
+ hasLocalSecret: Boolean(resolved.localSecret),
374
+ credentialsFileExists: fileExists(CREDENTIALS_FILE),
375
+ electronCredentialsFileExists: fileExists(ELECTRON_CREDENTIALS_FILE)
376
+ };
377
+ }
378
+
379
+ export function repairCredentials() {
380
+ const creds = loadCredentials();
381
+ if (!creds || !normalizeAgentToken(creds.access_token)) {
382
+ ensureCredentialsDir();
383
+ return {
384
+ repaired: false,
385
+ reason: 'No valid stored credentials with an access token were found.',
386
+ status: getAuthStatus()
387
+ };
388
+ }
389
+
390
+ saveCredentials(creds);
391
+
392
+ return {
393
+ repaired: true,
394
+ status: getAuthStatus()
395
+ };
396
+ }
397
+
257
398
  function normalizeAgentToken(value) {
258
399
  if (typeof value !== 'string') return '';
259
400
  return value.trim();
@@ -31,7 +31,7 @@ Usage:
31
31
  ${primaryCommand} attach [ticketId] [agent] Search tickets and launch an agent (interactive)
32
32
  ${primaryCommand} create "<objective>" Create a ticket with numbered project selection
33
33
  ${primaryCommand} prompt "<objective>" Create a ticket, then launch an agent on it
34
- ${primaryCommand} auth <subcommand> Login, logout, or check auth status
34
+ ${primaryCommand} auth <subcommand> Login, logout, repair, or check auth status
35
35
  ${primaryCommand} tickets <subcommand> Create or list tickets
36
36
  ${primaryCommand} ticket <subcommand> Work with a single ticket
37
37
  ${primaryCommand} protocol <subcommand> Agent workflow commands
@@ -46,10 +46,12 @@ Usage:
46
46
 
47
47
  Agents:
48
48
  Use ${primaryCommand} protocol help for ticket lifecycle commands.
49
+ Key protocol commands: auth-status, discover-project, spawn, attach, connect, load-context.
49
50
 
50
51
  Auth:
51
52
  ${primaryCommand} auth login Authorize CLI via browser
52
53
  ${primaryCommand} auth status Show login status
54
+ ${primaryCommand} auth repair Repair shared Desktop/CLI credentials
53
55
  ${primaryCommand} auth logout Remove stored credentials
54
56
 
55
57
  Tickets:
@@ -5,7 +5,7 @@ import fs from 'node:fs';
5
5
  import os from 'node:os';
6
6
  import path from 'node:path';
7
7
 
8
- import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
8
+ import { buildAuthHeaders, getAuthStatus, resolveAuth } from './credentials.mjs';
9
9
 
10
10
  /**
11
11
  * Parse simple CLI flags: --key value or --key=value
@@ -51,6 +51,36 @@ export function resolveProtocolTicketDelegate(flags = {}, agentIdentifier = '')
51
51
  return resolvedAgent || null;
52
52
  }
53
53
 
54
+ export function resolveProtocolModelIdentifier(flags = {}) {
55
+ const explicitModel = typeof flags.model === 'string' ? flags.model.trim() : '';
56
+ if (explicitModel) return explicitModel;
57
+
58
+ const envModel =
59
+ process.env.OVERLORD_MODEL_IDENTIFIER?.trim() ||
60
+ process.env.MODEL_IDENTIFIER?.trim() ||
61
+ process.env.AGENT_MODEL?.trim();
62
+ return envModel || null;
63
+ }
64
+
65
+ function resolveProtocolMetadata(flags = {}, base = {}) {
66
+ const metadata = { ...base };
67
+
68
+ if (flags['metadata-json']) {
69
+ const parsed = JSON.parse(String(flags['metadata-json']));
70
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
71
+ throw new Error('--metadata-json must be a JSON object');
72
+ }
73
+ Object.assign(metadata, parsed);
74
+ }
75
+
76
+ const modelIdentifier = resolveProtocolModelIdentifier(flags);
77
+ if (modelIdentifier) {
78
+ metadata.model = modelIdentifier;
79
+ }
80
+
81
+ return metadata;
82
+ }
83
+
54
84
  /**
55
85
  * Default request timeout in milliseconds. Overridable via --timeout flag or
56
86
  * OVERLORD_TIMEOUT env var. A bounded timeout prevents indefinite spinner hangs
@@ -446,9 +476,7 @@ async function protocolAttach(args) {
446
476
  agentIdentifier: resolveProtocolAgentIdentifier(flags),
447
477
  connectionMethod: String(flags.method ?? 'cli'),
448
478
  ...(externalSessionId !== undefined ? { externalSessionId } : {}),
449
- metadata: {
450
- cwd: process.cwd()
451
- }
479
+ metadata: resolveProtocolMetadata(flags, { cwd: process.cwd() })
452
480
  };
453
481
 
454
482
  const data = await apiPost(
@@ -589,6 +617,31 @@ async function protocolAsk(args) {
589
617
  console.log(JSON.stringify(data, null, 2));
590
618
  }
591
619
 
620
+ // ---------------------------------------------------------------------------
621
+ // permission-request
622
+ // ---------------------------------------------------------------------------
623
+
624
+ async function protocolPermissionRequest(args) {
625
+ const flags = parseFlags(args);
626
+ const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
627
+ const payload = flags['payload-file']
628
+ ? await readJsonFileOrStdin(String(flags['payload-file']), '--payload-file')
629
+ : {};
630
+
631
+ const { platformUrl, agentToken, localSecret } = resolveAuth();
632
+ const timeoutMs = resolveTimeout(flags);
633
+
634
+ const data = await apiPost(
635
+ platformUrl,
636
+ agentToken,
637
+ localSecret,
638
+ `/api/protocol/permission-request?ticketId=${encodeURIComponent(ticketId)}`,
639
+ payload,
640
+ timeoutMs
641
+ );
642
+ console.log(JSON.stringify(data, null, 2));
643
+ }
644
+
592
645
  // ---------------------------------------------------------------------------
593
646
  // read-context
594
647
  // ---------------------------------------------------------------------------
@@ -933,7 +986,7 @@ async function protocolConnect(args) {
933
986
  ticketId,
934
987
  agentIdentifier: resolveProtocolAgentIdentifier(flags),
935
988
  connectionMethod: String(flags.method ?? 'cli'),
936
- metadata: {}
989
+ metadata: resolveProtocolMetadata(flags, { cwd: process.cwd() })
937
990
  };
938
991
 
939
992
  const data = await apiPost(
@@ -995,7 +1048,7 @@ async function protocolSpawn(args) {
995
1048
  objective,
996
1049
  agentIdentifier,
997
1050
  connectionMethod: String(flags.method ?? 'cli'),
998
- metadata: {},
1051
+ metadata: resolveProtocolMetadata(flags, { cwd: process.cwd() }),
999
1052
  ...(flags.title ? { title: String(flags.title) } : {}),
1000
1053
  ...(flags.priority ? { priority: String(flags.priority) } : {}),
1001
1054
  ...(flags['project-id'] ? { projectId: String(flags['project-id']) } : {}),
@@ -1029,6 +1082,34 @@ async function protocolSpawn(args) {
1029
1082
  }
1030
1083
  }
1031
1084
 
1085
+ // ---------------------------------------------------------------------------
1086
+ // auth-status (agent-friendly auth diagnostics)
1087
+ // ---------------------------------------------------------------------------
1088
+
1089
+ async function protocolAuthStatus() {
1090
+ const status = getAuthStatus();
1091
+
1092
+ console.log(
1093
+ JSON.stringify(
1094
+ {
1095
+ ok: status.isLoggedIn,
1096
+ authStatus: {
1097
+ isLoggedIn: status.isLoggedIn,
1098
+ platformUrl: status.platformUrl,
1099
+ platformUrlSource: status.platformUrlSource,
1100
+ tokenSource: status.tokenSource,
1101
+ tokenPresent: status.tokenPresent,
1102
+ hasLocalSecret: status.hasLocalSecret,
1103
+ credentialsFileExists: status.credentialsFileExists,
1104
+ electronCredentialsFileExists: status.electronCredentialsFileExists
1105
+ }
1106
+ },
1107
+ null,
1108
+ 2
1109
+ )
1110
+ );
1111
+ }
1112
+
1032
1113
  // ---------------------------------------------------------------------------
1033
1114
  // Router
1034
1115
  // ---------------------------------------------------------------------------
@@ -1053,6 +1134,7 @@ Project discovery:
1053
1134
  Use --project-id to override automatic resolution on spawn or ticket creation.
1054
1135
 
1055
1136
  Subcommands:
1137
+ auth-status Return machine-readable auth status for agent runtimes
1056
1138
  discover-project Resolve a project from the current working directory
1057
1139
  attach Start a ticket session and return full working context
1058
1140
  connect Start a lightweight session without full context
@@ -1061,6 +1143,7 @@ Subcommands:
1061
1143
  update Post progress, activity events, and optional change rationales
1062
1144
  record-change-rationales Persist structured change rationales without a progress update
1063
1145
  ask Post a blocking question and move the ticket to review
1146
+ permission-request Notify Overlord that the agent is requesting tool permission
1064
1147
  read-context Read shared persistent context for this ticket
1065
1148
  write-context Write shared persistent context for future sessions
1066
1149
  deliver Finish work, send artifacts, and move the ticket to review
@@ -1072,7 +1155,7 @@ Subcommands:
1072
1155
  Environment fallback:
1073
1156
  --session-key <- SESSION_KEY
1074
1157
  --ticket-id <- TICKET_ID
1075
- auth/host <- OVERLORD_URL, AGENT_TOKEN
1158
+ auth/host <- OVERLORD_URL, AGENT_TOKEN, or shared credentials from ovld auth/Desktop login
1076
1159
  --timeout <- OVERLORD_TIMEOUT
1077
1160
 
1078
1161
  Common flags:
@@ -1080,8 +1163,15 @@ Common flags:
1080
1163
  --ticket-id <id> Ticket id when the subcommand operates on an existing ticket
1081
1164
  --session-key <key> Session key returned by attach/connect/spawn
1082
1165
  --agent <identifier> Agent identifier sent to Overlord (default: AGENT_IDENTIFIER or claude-code)
1166
+ --model <identifier> Model identifier to snapshot on executing objectives
1083
1167
  --method <connectionMethod> Connection method sent to Overlord (default: cli)
1084
1168
 
1169
+ auth-status:
1170
+ Purpose:
1171
+ Check whether the local runtime has usable Overlord credentials.
1172
+ Returns:
1173
+ JSON with ok=true|false plus authStatus fields describing token and host sources.
1174
+
1085
1175
  discover-project:
1086
1176
  Purpose:
1087
1177
  Resolve the Overlord project that corresponds to the current (or given) working directory.
@@ -1101,8 +1191,10 @@ attach:
1101
1191
  --ticket-id <id>
1102
1192
  Optional:
1103
1193
  --agent <identifier>
1194
+ --model <identifier>
1104
1195
  --method <connectionMethod>
1105
1196
  --external-session-id <id|null> Store the native agent thread/session id, or clear it with null
1197
+ --metadata-json <json> Extra session metadata object
1106
1198
  Returns:
1107
1199
  Full JSON including session.sessionKey, ticket, history, artifacts, sharedState, and promptContext
1108
1200
  Notes:
@@ -1115,7 +1207,9 @@ connect:
1115
1207
  --ticket-id <id>
1116
1208
  Optional:
1117
1209
  --agent <identifier>
1210
+ --model <identifier>
1118
1211
  --method <connectionMethod>
1212
+ --metadata-json <json> Extra session metadata object
1119
1213
  Returns:
1120
1214
  Session JSON and SESSION_KEY on stderr when available
1121
1215
 
@@ -1167,6 +1261,15 @@ ask:
1167
1261
  Notes:
1168
1262
  After ask succeeds, stop working until the human responds
1169
1263
 
1264
+ permission-request:
1265
+ Purpose:
1266
+ Notify Overlord that the local agent runtime is requesting tool permission.
1267
+ This is primarily used by installed permission hooks.
1268
+ Required:
1269
+ --ticket-id <id>
1270
+ Optional:
1271
+ --payload-file <path|-> Hook JSON payload, or stdin when "-"
1272
+
1170
1273
  read-context:
1171
1274
  Purpose:
1172
1275
  Read persistent shared context written by earlier sessions
@@ -1226,7 +1329,9 @@ spawn:
1226
1329
  --parent-session-key <key>
1227
1330
  --parent-ticket-id <id>
1228
1331
  --agent <identifier>
1332
+ --model <identifier>
1229
1333
  --method <connectionMethod>
1334
+ --metadata-json <json> Extra session metadata object
1230
1335
  Returns:
1231
1336
  New ticket/session JSON plus SESSION_KEY and TICKET_ID on stderr when available
1232
1337
 
@@ -1275,6 +1380,7 @@ artifact-upload-file:
1275
1380
  --metadata-json <json>
1276
1381
 
1277
1382
  Examples:
1383
+ ovld protocol auth-status
1278
1384
  ovld protocol discover-project
1279
1385
  ovld protocol discover-project --working-directory /path/to/repo
1280
1386
  ovld protocol spawn --agent codex --objective "Implement feature X" # auto-resolves project from cwd
@@ -1303,6 +1409,7 @@ Examples:
1303
1409
  }
1304
1410
 
1305
1411
  if (subcommand === 'discover-project') { await protocolDiscoverProject(args); return; }
1412
+ if (subcommand === 'auth-status') { await protocolAuthStatus(); return; }
1306
1413
  if (subcommand === 'attach') { await protocolAttach(args); return; }
1307
1414
  if (subcommand === 'connect') { await protocolConnect(args); return; }
1308
1415
  if (subcommand === 'load-context') { await protocolLoadContext(args); return; }
@@ -1314,6 +1421,7 @@ Examples:
1314
1421
  if (subcommand === 'update') { await protocolUpdate(args); return; }
1315
1422
  if (subcommand === 'record-change-rationales') { await protocolRecordChangeRationales(args); return; }
1316
1423
  if (subcommand === 'ask') { await protocolAsk(args); return; }
1424
+ if (subcommand === 'permission-request') { await protocolPermissionRequest(args); return; }
1317
1425
  if (subcommand === 'read-context') { await protocolReadContext(args); return; }
1318
1426
  if (subcommand === 'write-context') { await protocolWriteContext(args); return; }
1319
1427
  if (subcommand === 'deliver') { await protocolDeliver(args); return; }
@@ -195,13 +195,9 @@ ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKE
195
195
  const PERMISSION_HOOK_SCRIPT = `#!/bin/bash
196
196
  # Overlord PermissionRequest notification hook (managed by Overlord)
197
197
  BODY=$(cat -)
198
- if [ -n "$OVERLORD_URL" ] && [ -n "$AGENT_TOKEN" ] && [ -n "$TICKET_ID" ]; then
199
- curl -sf -m 5 \\
200
- -X POST "$OVERLORD_URL/api/protocol/permission-request?ticketId=$TICKET_ID" \\
201
- -H "Authorization: Bearer $AGENT_TOKEN" \\
202
- -H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \\
203
- -H "Content-Type: application/json" \\
204
- -d "$BODY" \\
198
+ if [ -n "$TICKET_ID" ] && command -v ovld >/dev/null 2>&1; then
199
+ { if [ -n "$BODY" ]; then printf '%s' "$BODY"; else printf '{}'; fi; } \\
200
+ | ovld protocol permission-request --ticket-id "$TICKET_ID" --payload-file - \\
205
201
  >/dev/null 2>&1 &
206
202
  disown
207
203
  fi
@@ -823,9 +819,7 @@ function installOpenCode() {
823
819
  bash: {
824
820
  '*': 'ask',
825
821
  ...existingBash,
826
- 'ovld protocol *': 'allow',
827
- 'curl -sS -X POST *': 'allow',
828
- 'curl -s -X POST *': 'allow'
822
+ 'ovld protocol *': 'allow'
829
823
  }
830
824
  }
831
825
  });
@@ -904,8 +898,7 @@ function installCursor() {
904
898
  const mergedAllow = Array.from(
905
899
  new Set([
906
900
  ...asStringArray(permissions.allow),
907
- 'Shell(ovld protocol:*)',
908
- 'Shell(curl -sS -X POST:*)'
901
+ 'Shell(ovld protocol:*)'
909
902
  ])
910
903
  );
911
904
  writeJsonFile(paths.settingsFile, {
@@ -939,12 +932,6 @@ function installGemini() {
939
932
  'commandPrefix = "ovld protocol"',
940
933
  'decision = "allow"',
941
934
  'priority = 900',
942
- '',
943
- '[[rule]]',
944
- 'toolName = "run_shell_command"',
945
- 'commandPrefix = "curl -sS -X POST"',
946
- 'decision = "allow"',
947
- 'priority = 900',
948
935
  ''
949
936
  ].join('\n');
950
937
  writeTextFile(paths.policyFile, policyContent);
@@ -1277,21 +1264,7 @@ function installClaudePermissions(platformUrl) {
1277
1264
  if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = [];
1278
1265
  }
1279
1266
 
1280
- const PROTOCOL_ENDPOINTS = [
1281
- 'attach', 'update', 'ask', 'read-context', 'write-context', 'deliver',
1282
- 'create-ticket', 'list-tickets', 'record-change-rationales', 'spawn',
1283
- 'discover-project', 'load-context', 'artifact-upload-file', 'artifact-download-url'
1284
- ];
1285
-
1286
- const entries = [];
1287
- for (const endpoint of PROTOCOL_ENDPOINTS) {
1288
- entries.push(`Bash(curl -s -X POST "${platformUrl}/api/protocol/${endpoint}":*)`);
1289
- }
1290
- entries.push(`Bash(curl -s -H 'Authorization::*)`);
1291
- for (const endpoint of PROTOCOL_ENDPOINTS) {
1292
- entries.push(`Bash(curl -s -X POST "$OVERLORD_URL/api/protocol/${endpoint}":*)`);
1293
- }
1294
- entries.push(`Bash(curl -s -H "Authorization::*)`);
1267
+ const entries = ['Bash(ovld protocol:*)'];
1295
1268
 
1296
1269
  const existing = new Set(settings.permissions.allow);
1297
1270
  const toAdd = entries.filter((e) => !existing.has(e));
@@ -1353,9 +1326,7 @@ function installOpenCodePermissions(_platformUrl) {
1353
1326
  bash: {
1354
1327
  '*': 'ask',
1355
1328
  ...existingBash,
1356
- 'ovld protocol *': 'allow',
1357
- 'curl -sS -X POST *': 'allow',
1358
- 'curl -s -X POST *': 'allow'
1329
+ 'ovld protocol *': 'allow'
1359
1330
  }
1360
1331
  }
1361
1332
  };
@@ -1374,21 +1345,12 @@ function installOpenCodePermissions(_platformUrl) {
1374
1345
  return true;
1375
1346
  }
1376
1347
 
1377
- function installCodexPermissions(platformUrl) {
1348
+ function installCodexPermissions() {
1378
1349
  console.log(`--- Codex ---`);
1379
1350
  console.log(' Codex does not support file-based permission configuration.');
1380
- console.log(' To warm up permissions, run the following commands once inside a Codex session:');
1381
- console.log(' (Codex will prompt for approval; approve each one to persist the prefix.)\n');
1382
-
1383
- const PROTOCOL_ENDPOINTS = [
1384
- 'attach', 'update', 'ask', 'read-context', 'write-context', 'deliver',
1385
- 'create-ticket', 'list-tickets'
1386
- ];
1387
-
1388
- for (const endpoint of PROTOCOL_ENDPOINTS) {
1389
- console.log(` curl -s -X POST "${platformUrl}/api/protocol/${endpoint}" -H "Content-Type: application/json" -H "Authorization: Bearer \\$AGENT_TOKEN" -d '{}'`);
1390
- }
1391
- console.log(` curl -s -H "Authorization: Bearer \\$AGENT_TOKEN" "${platformUrl}/api/protocol/context/test"`);
1351
+ console.log(' To warm up permissions, run the following command once inside a Codex session:');
1352
+ console.log(' (Codex will prompt for approval; approve it to persist the prefix.)\n');
1353
+ console.log(' ovld protocol help');
1392
1354
  console.log();
1393
1355
  return true;
1394
1356
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "4.6.0",
3
+ "version": "4.8.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,8 +6,8 @@ Claude Code plugin that exposes the Overlord local ticket workflow to any Claude
6
6
 
7
7
  - `skills/overlord-ticket/SKILL.md` — durable attach → update → ask → deliver workflow.
8
8
  - `commands/{connect,load,spawn}.md` — slash commands for session routing and ticket creation.
9
- - `hooks/hooks.json` + `scripts/permission-hook.sh` — PermissionRequest notifier that posts to `/api/protocol/permission-request` on the Overlord platform.
10
- - `userConfig` for `overlord_url` (non-sensitive) and `agent_token` (sensitive OS keychain) so the hook and CLI know where to talk.
9
+ - `hooks/hooks.json` + `scripts/permission-hook.sh` — PermissionRequest notifier that calls `ovld protocol permission-request`.
10
+ - `userConfig` for legacy `overlord_url` and `agent_token` installs. Current installs should authenticate with `ovld auth login` or Overlord Desktop; env vars remain optional overrides for remote shells, CI, and explicit token injection.
11
11
 
12
12
  ## Requirements
13
13
 
@@ -29,7 +29,7 @@ claude plugin marketplace add cooperativ/overlord-marketplace
29
29
  claude plugin install overlord@cooperativ
30
30
  ```
31
31
 
32
- The plugin prompts for `overlord_url` and `agent_token` at install time. The token is persisted to the OS keychain; the URL is stored in `~/.claude/settings.json` under `pluginConfigs["overlord"].options`.
32
+ Older plugin versions prompted for `overlord_url` and `agent_token` at install time. The current hook goes through `ovld protocol`, so the CLI resolves auth from env vars or the shared `~/.ovld` credentials written by CLI/Desktop login.
33
33
 
34
34
  ## Namespaced components
35
35
 
@@ -1,46 +1,82 @@
1
1
  ---
2
2
  name: overlord-ticket
3
- description: Overlord local workflow protocol attach, update, deliver lifecycle for ticket-driven work from Claude Code.
3
+ description: Overlord local workflow protocol for Claude Code, covering both Overlord-launched tickets and chat-invoked Overlord work.
4
4
  ---
5
5
 
6
6
  # Overlord Ticket
7
7
 
8
- If you receive a prompt with a specified ticket ID, adhere to the following. If the prompt does not have a ticket ID, the user may choose to add one later, but otherwise, proceed without it.
9
-
10
- ## Lifecycle
11
-
12
- 1. **Attach first** If there is a TICKET_ID, always call attach before doing any work:
13
- ```bash
14
- ovld protocol attach --ticket-id $TICKET_ID
15
- ```
16
- Store `session.sessionKey` from the response it is required for all subsequent calls.
17
-
18
- 2. **Update during work** Post at least one progress update before delivering:
19
- ```bash
20
- ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary "What you did and why." --phase execute
21
- ```
22
- Phases: `draft`, `execute`, `review`, `deliver`, `complete`, `blocked`, `cancelled`.
23
- Use `execute` while working.
24
-
25
- Pass `--event-type <type>` to publish a specific activity event (default: `update`):
26
- - `update` — standard progress update (default)
27
- - `user_follow_up` a message or question from the human user (EXCLUDING THE INITIAL TICKET)
28
- - `alert` — surface a warning or non-blocking alert
29
-
30
- 3. **Ask when blocked** — Stop working after calling:
31
- ```bash
32
- ovld protocol ask --session-key <sessionKey> --ticket-id $TICKET_ID --question "Specific question for the PM."
33
- ```
34
-
35
- 4. **Deliver last** — Always deliver when done:
36
- ```bash
37
- ovld protocol deliver --session-key <sessionKey> \
38
- --ticket-id $TICKET_ID \
39
- --summary "Narrative: what you did, next steps." \
40
- --artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' \
41
- --change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
42
- ```
43
- For larger delivery JSON, prefer `--payload-file -` and stream the full payload on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery. Do not leave delivery JSON checked into the worktree.
8
+ Use this skill whenever Claude Code needs to work with Overlord, whether the session was launched by Overlord Desktop/CLI or the user asks from chat to engage with Overlord.
9
+
10
+ ## Mode 1: Launched From Overlord Desktop Or CLI
11
+
12
+ Use this mode when the prompt already contains a ticket ID or explicitly says the session was launched by Overlord.
13
+
14
+ 1. Attach first with `ovld protocol attach --ticket-id <ticket-id>`.
15
+ 2. Keep the returned `session.sessionKey` for all follow-up calls.
16
+ 3. Treat the Overlord ticket prompt as authoritative for the objective, constraints, and delivery target.
17
+ 4. Post updates while working with `ovld protocol update --phase execute`.
18
+ 5. If the user sends a follow-up message after the initial ticket, publish it immediately with `--event-type user_follow_up`.
19
+ 6. If blocked, call `ovld protocol ask` and stop.
20
+ 7. Deliver last with `ovld protocol deliver`, including `changeRationales` for each meaningful behavioral file change.
21
+
22
+ ### Mode 1 Reference
23
+
24
+ Attach:
25
+
26
+ ```bash
27
+ ovld protocol attach --ticket-id $TICKET_ID
28
+ ```
29
+
30
+ Update:
31
+
32
+ ```bash
33
+ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary "What you did and why." --phase execute
34
+ ```
35
+
36
+ Supported `--phase` values:
37
+ - `draft`
38
+ - `execute`
39
+ - `review`
40
+ - `deliver`
41
+ - `complete`
42
+ - `blocked`
43
+ - `cancelled`
44
+
45
+ These are hardcoded CLI-supported values for the `--phase` flag. They are not user-defined phase types.
46
+
47
+ Event types:
48
+ - `update` for standard progress updates
49
+ - `user_follow_up` for human follow-up messages after the initial ticket
50
+ - `alert` for warnings or non-blocking issues
51
+
52
+ Ask when blocked:
53
+
54
+ ```bash
55
+ ovld protocol ask --session-key <sessionKey> --ticket-id $TICKET_ID --question "Specific question for the PM."
56
+ ```
57
+
58
+ Deliver:
59
+
60
+ ```bash
61
+ ovld protocol deliver --session-key <sessionKey> \
62
+ --ticket-id $TICKET_ID \
63
+ --summary "Narrative: what you did, next steps." \
64
+ --artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' \
65
+ --change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
66
+ ```
67
+
68
+ For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
69
+
70
+ ## Mode 2: Asked From Chat To Use Overlord
71
+
72
+ Use this mode when the conversation starts normally and the user asks Claude to create, inspect, connect to, or otherwise use Overlord.
73
+
74
+ 1. If the user wants a new ticket, use `/overlord:spawn` or run `ovld protocol spawn --agent claude-code --objective "..."`.
75
+ 2. If the user already has a ticket ID and only wants to inspect it, use `/overlord:load` or run `ovld protocol load-context --ticket-id <ticket-id>`.
76
+ 3. If the user wants to route the current session onto an existing ticket by ID, use `/overlord:connect` or run `ovld protocol connect --ticket-id <ticket-id>`.
77
+ 4. If the user wants to find a ticket but does not know the ID, use `ovld attach` for interactive ticket search and agent launch, or ask the user for the ticket ID if staying strictly inside chat is the better fit.
78
+ 5. If you need other lifecycle commands or flags, run `ovld protocol help` and use the real subcommand list instead of guessing.
79
+ 6. Once you attach to a ticket, switch back to Mode 1 and follow the full ticket lifecycle.
44
80
 
45
81
  ## Change Rationales
46
82
 
@@ -62,19 +98,17 @@ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID \
62
98
  --change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
63
99
  ```
64
100
 
65
- Record only meaningful behavioral changes skip formatting-only noise. Prefer 1–5 concise rationales per ticket, each tied to a specific file and diff hunk.
101
+ Record only meaningful behavioral changes. Skip formatting-only noise.
66
102
 
67
- ## Project Discovery & Ticket Spawning
103
+ ## Project Discovery And Ticket Creation
68
104
 
69
- When creating tickets from within a repository, `spawn` automatically resolves the
70
- correct project by matching your current working directory against each project's
71
- configured "Local working directory". No `--project-id` flag is needed:
105
+ When creating tickets from within a repository, `spawn` automatically resolves the correct project from the current working directory. No `--project-id` flag is needed unless you want to override resolution.
72
106
 
73
107
  ```bash
74
108
  ovld protocol spawn --agent claude-code --objective "Implement feature X" --priority medium
75
109
  ```
76
110
 
77
- To discover which project maps to the current directory:
111
+ To inspect project resolution explicitly:
78
112
 
79
113
  ```bash
80
114
  ovld protocol discover-project
@@ -82,7 +116,7 @@ ovld protocol discover-project
82
116
 
83
117
  You can override with `--project-id` or `--working-directory` if needed.
84
118
 
85
- ## Context & Artifacts
119
+ ## Context And Artifacts
86
120
 
87
121
  ```bash
88
122
  ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
@@ -93,12 +127,10 @@ ovld protocol artifact-download-url --session-key <sessionKey> --ticket-id $TICK
93
127
 
94
128
  ## Rules
95
129
 
96
- - Always attach first; always deliver when done.
97
- - Always communicate with Overlord using the ovld protocol cli commands.
98
- - Post any substantial updates about your decisions or progress.
99
- - If blocked on human-only work, call `ask` and request a follow-up human ticket.
100
- - The `summary` in deliver is what the PM reads first — write it as a narrative, not a command list.
130
+ - Always attach first and always deliver last once you are on a ticket.
131
+ - Use `ovld protocol` commands and the plugin's slash commands instead of ad hoc scripts.
132
+ - Do not invent protocol subcommands. Use `ovld protocol help` when unsure.
133
+ - The `summary` in deliver is what the PM reads first, so write it as a narrative, not a command list.
101
134
  - Use `write-context` for facts a future agent session should know.
102
- - **If the user sends you a message during your session, immediately publish a `user_follow_up` activity event with the user's message recorded verbatim in the summary before doing anything else. This DOES NOT apply to the initial ticket.**
103
- - **Do not add or commit changes (git commit) unless the user explicitly asks you to commit.**
104
- - **Delivery is the concluding step.** After delivering, stop working. Do not continue unless the user follows up or the ticket is reopened.
135
+ - Do not add or commit changes unless the user explicitly asks you to commit.
136
+ - Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
@@ -5,4 +5,6 @@ alwaysApply: true
5
5
 
6
6
  # Overlord Local Workflow
7
7
 
8
- Attach first, update during work, ask when blocked, and deliver last using `ovld protocol` commands.
8
+ - If the session was launched by Overlord Desktop or CLI, attach first with `ovld protocol attach --ticket-id $TICKET_ID`, update during work, ask when blocked, and deliver last.
9
+ - If the user asks from chat to use Overlord, use `/spawn` for new tickets, `/load` for read-only context by ticket ID, `/connect` to route the session onto an existing ticket, and `search_tickets` to find tickets by keyword or status.
10
+ - If you need other protocol commands or flags, run `ovld protocol help` instead of guessing.
@@ -1,8 +1,34 @@
1
1
  ---
2
2
  name: overlord-ticket
3
- description: Durable local workflow for Overlord tickets from Cursor.
3
+ description: Overlord local workflow protocol for Cursor, covering both Overlord-launched tickets and chat-invoked Overlord work.
4
4
  ---
5
5
 
6
6
  # Overlord Ticket
7
7
 
8
- Attach first with `ovld protocol attach --ticket-id <ticket-id>`, keep the `sessionKey`, post updates during implementation, and deliver last with change rationales.
8
+ Use this skill whenever Cursor needs to work with Overlord, whether the session was launched by Overlord Desktop/CLI or the user asks from chat to engage with Overlord.
9
+
10
+ ## Mode 1: Launched From Overlord Desktop Or CLI
11
+
12
+ 1. Attach first with `ovld protocol attach --ticket-id <ticket-id>`.
13
+ 2. Keep the returned `session.sessionKey` for all follow-up calls.
14
+ 3. Treat the Overlord ticket prompt as authoritative for the objective, constraints, and delivery target.
15
+ 4. Post updates while working with `ovld protocol update --phase execute`.
16
+ 5. If the user sends a follow-up message after the initial ticket, publish it immediately with `--event-type user_follow_up`.
17
+ 6. If blocked, call `ovld protocol ask` and stop.
18
+ 7. Deliver last with `ovld protocol deliver`, including `changeRationales` for each meaningful behavioral file change.
19
+
20
+ ## Mode 2: Asked From Chat To Use Overlord
21
+
22
+ 1. If the user wants a new ticket, use `/spawn` or run `ovld protocol spawn --agent cursor --objective "..."`.
23
+ 2. If the user already has a ticket ID and only wants to inspect it, use `/load` or run `ovld protocol load-context --ticket-id <ticket-id>`.
24
+ 3. If the user wants to route the current session onto an existing ticket by ID, use `/connect` or run `ovld protocol connect --ticket-id <ticket-id>`.
25
+ 4. If the user wants to search for tickets by keyword or status, use the `search_tickets` MCP tool.
26
+ 5. If you need other lifecycle commands or flags, run `ovld protocol help` and use the real subcommand list instead of guessing.
27
+ 6. Once you attach to a ticket, switch back to Mode 1 and follow the full ticket lifecycle.
28
+
29
+ ## Rules
30
+
31
+ - Always attach first and always deliver last once you are on a ticket.
32
+ - Use `ovld protocol` commands, the plugin commands, and the MCP tool instead of ad hoc scripts.
33
+ - Do not invent protocol subcommands. Use `ovld protocol help` when unsure.
34
+ - Include at least one progress update before delivering.
@@ -14,7 +14,7 @@ personal marketplace entry at `~/.agents/plugins/marketplace.json`.
14
14
  ## Requirements
15
15
 
16
16
  - Install the Overlord CLI so `ovld` is available on `PATH`.
17
- - Ensure `OVERLORD_URL` and `AGENT_TOKEN` are available when the target Overlord instance requires them.
17
+ - Authenticate with `ovld auth login` or Overlord Desktop. `OVERLORD_URL` and `AGENT_TOKEN` are optional overrides, mainly for remote shells, CI, or explicit token injection.
18
18
  - Optionally set `OVLD_BIN` if the CLI lives at a non-standard path.
19
19
 
20
20
  ## Tool coverage
@@ -51,16 +51,20 @@ const tools = [
51
51
  properties: {
52
52
  ticket_id: { type: 'string', description: 'Target ticket ID' },
53
53
  agent: { type: 'string' },
54
+ model: { type: 'string' },
54
55
  method: { type: 'string' },
55
- external_session_id: { type: ['string', 'null'] }
56
+ external_session_id: { type: ['string', 'null'] },
57
+ metadata: { type: 'object' }
56
58
  },
57
59
  required: ['ticket_id']
58
60
  },
59
61
  toCliFlags: args => ({
60
62
  'ticket-id': args.ticket_id,
61
63
  agent: args.agent,
64
+ model: args.model,
62
65
  method: args.method,
63
- 'external-session-id': args.external_session_id
66
+ 'external-session-id': args.external_session_id,
67
+ 'metadata-json': args.metadata
64
68
  }),
65
69
  subcommand: 'attach'
66
70
  },
@@ -116,6 +120,8 @@ const tools = [
116
120
  parent_session_key: { type: 'string' },
117
121
  parent_ticket_id: { type: 'string' },
118
122
  agent: { type: 'string' },
123
+ model: { type: 'string' },
124
+ metadata: { type: 'object' },
119
125
  method: { type: 'string' }
120
126
  },
121
127
  required: ['objective']
@@ -133,6 +139,8 @@ const tools = [
133
139
  'parent-session-key': args.parent_session_key,
134
140
  'parent-ticket-id': args.parent_ticket_id,
135
141
  agent: args.agent,
142
+ model: args.model,
143
+ 'metadata-json': args.metadata,
136
144
  method: args.method
137
145
  }),
138
146
  subcommand: 'spawn'
@@ -1,27 +1,114 @@
1
+ ---
2
+ name: overlord-ticket
3
+ description: Durable local workflow for working Overlord tickets from Codex, covering both Overlord-launched tickets and chat-invoked Overlord work.
4
+ ---
5
+
1
6
  # Overlord Ticket
2
7
 
3
- Use this skill when the user wants to work on an Overlord ticket from Codex through the local
4
- Overlord plugin.
8
+ Use this skill when Codex needs to work with Overlord, whether the session was launched by Overlord Desktop/CLI or the user asks from chat to engage with Overlord.
5
9
 
6
- ## Workflow
10
+ ## Mode 1: Launched From Overlord Desktop Or CLI
7
11
 
8
12
  1. Attach first with `ovld protocol attach --ticket-id <ticket-id>`.
9
13
  2. Store the returned `SESSION_KEY` or `session.sessionKey`.
10
- 3. While working, publish meaningful progress with:
11
- `ovld protocol update --session-key <sessionKey> --ticket-id <ticket-id> --phase execute --summary "..."`
12
- 4. If a later user message arrives during the ticket session, publish it immediately with
13
- `--event-type user_follow_up` before doing anything else.
14
- 5. If blocked on a human-only action, ask a precise blocking question with `ovld protocol ask`
15
- and stop.
16
- 6. Deliver last with `ovld protocol deliver`, including meaningful `changeRationales` for every
17
- behavioral git-tracked change.
18
- For larger delivery JSON, prefer `--payload-file -` and stream the full payload on stdin so no scratch file needs to be created or removed. If you need `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that JSON as ephemeral scratch data, not as a repository file. Remove it after delivery and never commit it.
14
+ 3. Treat the Overlord ticket prompt as authoritative for the specific objective and ticket-level constraints.
15
+ 4. While working, publish meaningful progress with `ovld protocol update --session-key <sessionKey> --ticket-id <ticket-id> --phase execute --summary "..."`.
16
+ 5. If a later user message arrives during the ticket session, publish it immediately with `--event-type user_follow_up` before doing anything else.
17
+ 6. If blocked on a human-only action, ask a precise blocking question with `ovld protocol ask` and stop.
18
+ 7. Deliver last with `ovld protocol deliver`, including meaningful `changeRationales` for every behavioral git-tracked change.
19
+
20
+ ### Mode 1 Reference
21
+
22
+ Attach:
23
+
24
+ ```bash
25
+ ovld protocol attach --ticket-id $TICKET_ID
26
+ ```
27
+
28
+ Update:
29
+
30
+ ```bash
31
+ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary "What you did and why." --phase execute
32
+ ```
33
+
34
+ Supported `--phase` values:
35
+ - `draft`
36
+ - `execute`
37
+ - `review`
38
+ - `deliver`
39
+ - `complete`
40
+ - `blocked`
41
+ - `cancelled`
42
+
43
+ These are hardcoded CLI-supported values for the `--phase` flag. They are not user-defined phase types.
44
+
45
+ Event types:
46
+ - `update` for standard progress updates
47
+ - `user_follow_up` for human follow-up messages after the initial ticket
48
+ - `alert` for warnings or non-blocking issues
49
+
50
+ Ask when blocked:
51
+
52
+ ```bash
53
+ ovld protocol ask --session-key <sessionKey> --ticket-id $TICKET_ID --question "Specific question for the PM."
54
+ ```
55
+
56
+ Deliver:
57
+
58
+ ```bash
59
+ ovld protocol deliver --session-key <sessionKey> \
60
+ --ticket-id $TICKET_ID \
61
+ --summary "Narrative: what you did, next steps." \
62
+ --artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' \
63
+ --change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
64
+ ```
65
+
66
+ For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
67
+
68
+ ## Mode 2: Asked From Chat To Use Overlord
69
+
70
+ 1. If the user wants a new ticket, use `ovld protocol spawn --objective "..."`. It creates the ticket and attaches immediately.
71
+ 2. If the user wants to inspect an existing ticket without starting work, use `ovld protocol load-context --ticket-id <ticket-id>`.
72
+ 3. If the user wants to work an existing ticket, attach with `ovld protocol attach --ticket-id <ticket-id>` and then switch to Mode 1.
73
+ 4. If the user wants to find existing tickets by keyword or workflow state, use the `search_tickets` tool.
74
+ 5. If you need to understand project routing before spawning, use `ovld protocol discover-project`.
75
+ 6. If you need other lifecycle commands or flags, run `ovld protocol help` and use the real subcommand list instead of guessing.
76
+
77
+ ## Change Rationales
78
+
79
+ Always include `changeRationales` when delivering. Optionally include them on updates during long-running work.
80
+
81
+ Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact.
82
+
83
+ These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below. For larger full delivery payloads, prefer `--payload-file -` so summary, artifacts, and change rationales stay in one JSON document without creating a temporary file. Ordinary deliver artifacts should use `next_steps`, `test_results`, `migration`, `note`, `url`, or `decision`.
84
+
85
+ ```bash
86
+ ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
87
+ --summary "Recorded rationale details for the latest code changes." --phase execute \
88
+ --change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
89
+ ```
90
+
91
+ ```bash
92
+ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID \
93
+ --summary "Added retry logic." --phase execute \
94
+ --change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
95
+ ```
96
+
97
+ Record only meaningful behavioral changes. Skip formatting-only noise. Prefer 1-5 concise rationales per ticket, each tied to a specific file and diff hunk.
98
+
99
+ ## Context And Artifacts
100
+
101
+ ```bash
102
+ ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
103
+ ovld protocol write-context --session-key <sessionKey> --ticket-id $TICKET_ID --key "key" --value '"json-value"'
104
+ ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --file ./spec.pdf --content-type application/pdf
105
+ ovld protocol artifact-download-url --session-key <sessionKey> --ticket-id $TICKET_ID --artifact-id <artifact-id>
106
+ ```
19
107
 
20
108
  ## Rules
21
109
 
22
- - The authoritative lifecycle is the `ovld protocol` CLI.
23
- - Always attach first and always deliver last.
24
- - Do not create or rely on a local Codex `AGENTS.md` bundle for Overlord.
110
+ - The authoritative lifecycle is the `ovld protocol` CLI once you are on a ticket.
111
+ - Always attach first and always deliver last once you are working a ticket.
25
112
  - Prefer the installed `ovld` CLI and the plugin's MCP tools instead of ad hoc repo scripts.
26
- - When the ticket was launched by Overlord, the ticket prompt remains authoritative for the
27
- specific task objective and any ticket-level constraints.
113
+ - Do not create or rely on a local Codex `AGENTS.md` bundle for Overlord.
114
+ - When the ticket was launched by Overlord, the ticket prompt remains authoritative for the specific task objective and ticket-level constraints.