overlord-cli 0.2605151257.0 → 0.2605160931.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
@@ -111,9 +111,10 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
111
111
 
112
112
  - `auth-status` - return machine-readable auth status for agent runtimes
113
113
  - `discover-project` - resolve a project from the current (or given) working directory
114
- - `attach` - start a ticket session and return full working context
114
+ - `attach` - start a ticket session, create a local git checkpoint per executing objective, and return full working context
115
115
  - `connect` - start a lightweight session without full context
116
116
  - `load-context` - read ticket context without creating a session
117
+ - `revert` - restore the local working tree to an objective checkpoint after fetching its checkpoint row
117
118
  - `search-tickets` - find tickets by keyword, status, project, creator, or update date
118
119
  - `create` - create a draft ticket without attaching (standalone or follow-up)
119
120
  - `prompt` - create a ticket and attach to it immediately (`spawn` is a backward-compatible alias)
@@ -125,11 +126,15 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
125
126
  - `hook-event` - record lifecycle hook events such as `UserPromptSubmit` / Cursor `beforeSubmitPrompt` without a session key
126
127
  - `read-context` - read shared persistent context for this ticket
127
128
  - `write-context` - write shared persistent context for future sessions
128
- - `deliver` - create a local JJ/Git checkpoint when available, send artifacts/rationales, and move the ticket to review
129
+ - `deliver` - send artifacts/rationales and move the ticket to review
129
130
  - `attachment-prepare-upload` - get a signed upload URL for an objective attachment
130
131
  - `attachment-finalize-upload` - finalize an uploaded objective attachment row after storage upload
131
132
  - `attachment-download-url` - get a signed download URL for an existing objective attachment
132
133
  - `attachment-upload-file` - prepare, upload, and finalize a local objective attachment in one command
134
+ - `get-device` / `update-device` - register the caller machine (per organization + user + fingerprint) and rename its label
135
+ - `list-project-resources` / `add-project-resource` / `update-project-resource` - manage filesystem directories tied to a project device
136
+
137
+ Devices are keyed by **(organization, user, fingerprint)** so the same physical workstation can appear once per org session.
133
138
 
134
139
  Use `create` for future work you want to track, `prompt` for work that should start immediately, and `record-work` for work that was already completed in chat and now needs a review ticket plus feed post.
135
140
 
@@ -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) {
@@ -548,6 +548,80 @@ function createLocalCheckpoint({ flags, kind = 'objective', objectiveId, snapsho
548
548
  }
549
549
  }
550
550
 
551
+ function snapshotLocalWorkingTree(repoRoot, parentSha, message) {
552
+ const tempIndex = path.join(repoRoot, '.git', `overlord-snap-index-${Date.now()}`);
553
+ const env = { ...process.env, NO_COLOR: '1', GIT_INDEX_FILE: tempIndex };
554
+ try {
555
+ execFileSync('git', ['-C', repoRoot, 'read-tree', 'HEAD'], { cwd: repoRoot, env });
556
+ execFileSync('git', ['-C', repoRoot, 'add', '-A'], { cwd: repoRoot, env });
557
+ const tree = execFileSync('git', ['-C', repoRoot, 'write-tree'], {
558
+ cwd: repoRoot,
559
+ env,
560
+ encoding: 'utf8'
561
+ }).trim();
562
+ if (!tree) throw new Error('git write-tree produced no output.');
563
+ return execFileSync(
564
+ 'git',
565
+ ['-C', repoRoot, 'commit-tree', tree, '-p', parentSha, '-m', message],
566
+ {
567
+ cwd: repoRoot,
568
+ env: { ...process.env, NO_COLOR: '1' },
569
+ encoding: 'utf8'
570
+ }
571
+ ).trim();
572
+ } finally {
573
+ try {
574
+ fs.unlinkSync(tempIndex);
575
+ } catch {
576
+ /* ignore */
577
+ }
578
+ }
579
+ }
580
+
581
+ function restoreLocalCheckpoint({ workspacePath, objectiveId, gitCommitId }) {
582
+ const repoRoot = resolveGitRepoRoot(workspacePath);
583
+ if (!repoRoot) throw new Error(`No git repository found at ${workspacePath}.`);
584
+
585
+ const ref = `refs/overlord/checkpoints/${objectiveId}`;
586
+ const target = gitCommitId
587
+ ? runLocalCommand('git', ['-C', repoRoot, 'rev-parse', '--verify', `${gitCommitId}^{commit}`], {
588
+ cwd: repoRoot
589
+ }).trim()
590
+ : runLocalCommand('git', ['-C', repoRoot, 'rev-parse', '--verify', ref], {
591
+ cwd: repoRoot
592
+ }).trim();
593
+ if (!target) throw new Error(`No checkpoint exists for objective ${objectiveId}.`);
594
+
595
+ let safetyRef = null;
596
+ let safetySha = null;
597
+ try {
598
+ const headSha = runLocalCommand('git', ['-C', repoRoot, 'rev-parse', 'HEAD'], {
599
+ cwd: repoRoot
600
+ }).trim();
601
+ const stamp = new Date().toISOString().replace(/[^0-9A-Za-z]/g, '');
602
+ safetyRef = `refs/overlord/safety/${stamp}`;
603
+ safetySha = snapshotLocalWorkingTree(
604
+ repoRoot,
605
+ headSha,
606
+ `overlord pre-revert safety ${new Date().toISOString()}`
607
+ );
608
+ if (safetySha) {
609
+ runLocalCommand('git', ['-C', repoRoot, 'update-ref', safetyRef, safetySha], {
610
+ cwd: repoRoot
611
+ });
612
+ }
613
+ } catch {
614
+ safetyRef = null;
615
+ safetySha = null;
616
+ }
617
+
618
+ runLocalCommand('git', ['-C', repoRoot, 'read-tree', '--reset', '-u', target], {
619
+ cwd: repoRoot
620
+ });
621
+
622
+ return { ref, gitCommitId: target, safetyRef, safetySha };
623
+ }
624
+
551
625
  async function createAndRecordPendingCheckpoints({
552
626
  attachData,
553
627
  bearerToken,
@@ -1441,6 +1515,223 @@ async function protocolDiscoverProject(args) {
1441
1515
  }
1442
1516
  }
1443
1517
 
1518
+ // ---------------------------------------------------------------------------
1519
+ // get-device (identify / register the current device)
1520
+ // ---------------------------------------------------------------------------
1521
+
1522
+ async function protocolGetDevice(args) {
1523
+ const flags = parseFlags(args);
1524
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1525
+ await resolveProtocolAuthForFlags(flags);
1526
+ const timeoutMs = resolveTimeout(flags);
1527
+
1528
+ const deviceFingerprint = String(flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? '');
1529
+ if (!deviceFingerprint) {
1530
+ console.error('Error: --device-fingerprint is required (or set OVERLORD_DEVICE_FINGERPRINT)');
1531
+ process.exit(1);
1532
+ }
1533
+
1534
+ const body = {
1535
+ deviceFingerprint,
1536
+ ...(flags['device-hostname'] ? { deviceHostname: String(flags['device-hostname']) } : {}),
1537
+ ...(flags['device-platform'] ? { devicePlatform: String(flags['device-platform']) } : {})
1538
+ };
1539
+
1540
+ const data = await apiPost(
1541
+ platformUrl,
1542
+ bearerToken,
1543
+ localSecret,
1544
+ organizationId,
1545
+ '/api/protocol/get-device',
1546
+ body,
1547
+ timeoutMs
1548
+ );
1549
+ console.log(JSON.stringify(data, null, 2));
1550
+
1551
+ if (data.device?.label) {
1552
+ process.stderr.write(`\nDEVICE_LABEL=${data.device.label}\n`);
1553
+ }
1554
+ if (data.device?.id) {
1555
+ process.stderr.write(`DEVICE_ID=${data.device.id}\n`);
1556
+ }
1557
+ }
1558
+
1559
+ // ---------------------------------------------------------------------------
1560
+ // update-device (rename the device label)
1561
+ // ---------------------------------------------------------------------------
1562
+
1563
+ async function protocolUpdateDevice(args) {
1564
+ const flags = parseFlags(args);
1565
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1566
+ await resolveProtocolAuthForFlags(flags);
1567
+ const timeoutMs = resolveTimeout(flags);
1568
+
1569
+ const deviceFingerprint = String(flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? '');
1570
+ if (!deviceFingerprint) {
1571
+ console.error('Error: --device-fingerprint is required (or set OVERLORD_DEVICE_FINGERPRINT)');
1572
+ process.exit(1);
1573
+ }
1574
+ const label = typeof flags.label === 'string' ? flags.label.trim() : '';
1575
+ if (!label) {
1576
+ console.error('Error: --label is required');
1577
+ process.exit(1);
1578
+ }
1579
+
1580
+ const data = await apiPost(
1581
+ platformUrl,
1582
+ bearerToken,
1583
+ localSecret,
1584
+ organizationId,
1585
+ '/api/protocol/update-device',
1586
+ { deviceFingerprint, label },
1587
+ timeoutMs
1588
+ );
1589
+ console.log(JSON.stringify(data, null, 2));
1590
+ }
1591
+
1592
+ // ---------------------------------------------------------------------------
1593
+ // list-project-resources (list resource directories for a project)
1594
+ // ---------------------------------------------------------------------------
1595
+
1596
+ async function protocolListProjectResources(args) {
1597
+ const flags = parseFlags(args);
1598
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1599
+ await resolveProtocolAuthForFlags(flags);
1600
+ const timeoutMs = resolveTimeout(flags);
1601
+
1602
+ const projectId = typeof flags['project-id'] === 'string' ? flags['project-id'].trim() : '';
1603
+ if (!projectId) {
1604
+ console.error('Error: --project-id is required');
1605
+ process.exit(1);
1606
+ }
1607
+
1608
+ const deviceFingerprint = typeof flags['device-fingerprint'] === 'string'
1609
+ ? flags['device-fingerprint'].trim()
1610
+ : (process.env.OVERLORD_DEVICE_FINGERPRINT?.trim() ?? '');
1611
+
1612
+ const body = {
1613
+ projectId,
1614
+ ...(deviceFingerprint ? { deviceFingerprint } : {})
1615
+ };
1616
+
1617
+ const data = await apiPost(
1618
+ platformUrl,
1619
+ bearerToken,
1620
+ localSecret,
1621
+ organizationId,
1622
+ '/api/protocol/list-project-resources',
1623
+ body,
1624
+ timeoutMs
1625
+ );
1626
+ console.log(JSON.stringify(data, null, 2));
1627
+ }
1628
+
1629
+ // ---------------------------------------------------------------------------
1630
+ // add-project-resource (register a directory for a project on this device)
1631
+ // ---------------------------------------------------------------------------
1632
+
1633
+ async function protocolAddProjectResource(args) {
1634
+ const flags = parseFlags(args);
1635
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1636
+ await resolveProtocolAuthForFlags(flags);
1637
+ const timeoutMs = resolveTimeout(flags);
1638
+
1639
+ const projectId = typeof flags['project-id'] === 'string' ? flags['project-id'].trim() : '';
1640
+ const directoryPath = typeof flags['directory'] === 'string'
1641
+ ? flags['directory'].trim()
1642
+ : process.cwd();
1643
+ const deviceFingerprint = String(
1644
+ flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? ''
1645
+ );
1646
+
1647
+ if (!projectId) {
1648
+ console.error('Error: --project-id is required');
1649
+ process.exit(1);
1650
+ }
1651
+ if (!deviceFingerprint) {
1652
+ console.error('Error: --device-fingerprint is required (or set OVERLORD_DEVICE_FINGERPRINT)');
1653
+ process.exit(1);
1654
+ }
1655
+
1656
+ // Verify directory exists on this device before sending to server
1657
+ const fs = await import('node:fs');
1658
+ if (!fs.existsSync(directoryPath)) {
1659
+ console.error(`Error: Directory does not exist: ${directoryPath}`);
1660
+ process.exit(1);
1661
+ }
1662
+
1663
+ const body = {
1664
+ projectId,
1665
+ directoryPath,
1666
+ deviceFingerprint,
1667
+ ...(typeof flags.label === 'string' ? { label: flags.label } : {}),
1668
+ ...(flags['is-primary'] === true || flags['is-primary'] === 'true' ? { isPrimary: true } : {}),
1669
+ ...(flags['device-hostname'] ? { deviceHostname: String(flags['device-hostname']) } : {}),
1670
+ ...(flags['device-platform'] ? { devicePlatform: String(flags['device-platform']) } : {})
1671
+ };
1672
+
1673
+ const data = await apiPost(
1674
+ platformUrl,
1675
+ bearerToken,
1676
+ localSecret,
1677
+ organizationId,
1678
+ '/api/protocol/add-project-resource',
1679
+ body,
1680
+ timeoutMs
1681
+ );
1682
+ console.log(JSON.stringify(data, null, 2));
1683
+
1684
+ if (data.resource?.id) {
1685
+ process.stderr.write(`\nRESOURCE_ID=${data.resource.id}\n`);
1686
+ }
1687
+ }
1688
+
1689
+ // ---------------------------------------------------------------------------
1690
+ // update-project-resource (update path, label, or primary status)
1691
+ // ---------------------------------------------------------------------------
1692
+
1693
+ async function protocolUpdateProjectResource(args) {
1694
+ const flags = parseFlags(args);
1695
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1696
+ await resolveProtocolAuthForFlags(flags);
1697
+ const timeoutMs = resolveTimeout(flags);
1698
+
1699
+ const resourceId = typeof flags['resource-id'] === 'string' ? flags['resource-id'].trim() : '';
1700
+ const deviceFingerprint = String(
1701
+ flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? ''
1702
+ );
1703
+
1704
+ if (!resourceId) {
1705
+ console.error('Error: --resource-id is required');
1706
+ process.exit(1);
1707
+ }
1708
+ if (!deviceFingerprint) {
1709
+ console.error('Error: --device-fingerprint is required (or set OVERLORD_DEVICE_FINGERPRINT)');
1710
+ process.exit(1);
1711
+ }
1712
+
1713
+ const body = {
1714
+ resourceId,
1715
+ deviceFingerprint,
1716
+ ...(typeof flags.directory === 'string' ? { directoryPath: flags.directory.trim() } : {}),
1717
+ ...('label' in flags ? { label: flags.label === 'null' ? null : String(flags.label) } : {}),
1718
+ ...(flags['is-primary'] !== undefined
1719
+ ? { isPrimary: flags['is-primary'] === true || flags['is-primary'] === 'true' }
1720
+ : {})
1721
+ };
1722
+
1723
+ const data = await apiPost(
1724
+ platformUrl,
1725
+ bearerToken,
1726
+ localSecret,
1727
+ organizationId,
1728
+ '/api/protocol/update-project-resource',
1729
+ body,
1730
+ timeoutMs
1731
+ );
1732
+ console.log(JSON.stringify(data, null, 2));
1733
+ }
1734
+
1444
1735
  // ---------------------------------------------------------------------------
1445
1736
  // connect (lightweight session, no context returned)
1446
1737
  // ---------------------------------------------------------------------------
@@ -1502,6 +1793,42 @@ async function protocolLoadContext(args) {
1502
1793
  console.log(JSON.stringify(data, null, 2));
1503
1794
  }
1504
1795
 
1796
+ // ---------------------------------------------------------------------------
1797
+ // revert (restore local working tree to an objective checkpoint)
1798
+ // ---------------------------------------------------------------------------
1799
+
1800
+ async function protocolRevert(args) {
1801
+ const flags = parseFlags(args);
1802
+ const objectiveId = requireFlag(flags, 'objective-id', 'OVERLORD_OBJECTIVE_ID');
1803
+ const workspacePath = path.resolve(
1804
+ String(flags['working-directory'] ?? process.env.OVERLORD_WORKSPACE_PATH ?? process.cwd())
1805
+ );
1806
+ const { platformUrl, bearerToken, localSecret, organizationId } =
1807
+ await resolveProtocolAuthForFlags(flags);
1808
+ const timeoutMs = resolveTimeout(flags);
1809
+
1810
+ const data = await apiPost(
1811
+ platformUrl,
1812
+ bearerToken,
1813
+ localSecret,
1814
+ organizationId,
1815
+ '/api/protocol/revert',
1816
+ { objectiveId },
1817
+ timeoutMs
1818
+ );
1819
+ const checkpoint = data?.checkpoint;
1820
+ if (!checkpoint?.git_commit_id) {
1821
+ throw new Error('API response did not include a checkpoint git_commit_id.');
1822
+ }
1823
+
1824
+ const result = restoreLocalCheckpoint({
1825
+ workspacePath,
1826
+ objectiveId,
1827
+ gitCommitId: checkpoint.git_commit_id
1828
+ });
1829
+ console.log(JSON.stringify({ ok: true, checkpoint, restore: result }, null, 2));
1830
+ }
1831
+
1505
1832
  // ---------------------------------------------------------------------------
1506
1833
  // prompt (create ticket + connect in one call)
1507
1834
  // ---------------------------------------------------------------------------
@@ -1902,6 +2229,7 @@ Subcommands:
1902
2229
  attach Start a ticket session and return full working context
1903
2230
  connect Start a lightweight session without full context
1904
2231
  load-context Read ticket context without creating a session
2232
+ revert Restore the local working tree to an objective checkpoint
1905
2233
  search-tickets Find tickets by keyword, status, project, creator, or update date
1906
2234
  discuss-objective Mark a draft objective as submitted (ready for review/execution)
1907
2235
  create Create a draft ticket without attaching (follow-up or standalone)
@@ -1920,6 +2248,11 @@ Subcommands:
1920
2248
  attachment-finalize-upload Finalize an uploaded attachment row after storage upload
1921
2249
  attachment-download-url Get a signed download URL for an existing attachment
1922
2250
  attachment-upload-file Prepare, upload, and finalize a local file in one command
2251
+ get-device Identify / register the current device and return its label
2252
+ update-device Rename this device's label (must be lowercase kebab-case)
2253
+ list-project-resources List resource directories registered for a project
2254
+ add-project-resource Register a local directory as a project resource on this device
2255
+ update-project-resource Update a resource directory's path, label, or primary status
1923
2256
 
1924
2257
  Environment fallback:
1925
2258
  --session-key <- SESSION_KEY
@@ -1993,6 +2326,16 @@ load-context:
1993
2326
  Required:
1994
2327
  --ticket-id <ticket_id>
1995
2328
 
2329
+ revert:
2330
+ Purpose:
2331
+ Fetch an objective checkpoint from Overlord and restore the local git working tree to it.
2332
+ Required:
2333
+ --objective-id <objective-uuid>
2334
+ Optional:
2335
+ --working-directory <path> Repository to restore (default: current working directory)
2336
+ Notes:
2337
+ A safety snapshot of the current working tree is saved under refs/overlord/safety/ first.
2338
+
1996
2339
  search-tickets:
1997
2340
  Purpose:
1998
2341
  Find tickets in your organization by keyword, status, project, creator, or update window.
@@ -2272,6 +2615,44 @@ attachment-upload-file:
2272
2615
  --content-type <mime> Defaults to application/octet-stream
2273
2616
  --metadata-json <json>
2274
2617
 
2618
+ get-device:
2619
+ Required:
2620
+ --device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
2621
+ Optional:
2622
+ --device-hostname <name>
2623
+ --device-platform <platform> (darwin|linux|windows)
2624
+
2625
+ update-device:
2626
+ Required:
2627
+ --device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
2628
+ --label <label> Lowercase kebab-case, unique within org
2629
+
2630
+ list-project-resources:
2631
+ Required:
2632
+ --project-id <uuid>
2633
+ Optional:
2634
+ --device-fingerprint <fp> Filter to current device only
2635
+
2636
+ add-project-resource:
2637
+ Required:
2638
+ --project-id <uuid>
2639
+ --device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
2640
+ Optional:
2641
+ --directory <path> Defaults to current working directory
2642
+ --label <text>
2643
+ --is-primary Mark as primary directory for this device+project
2644
+ --device-hostname <name>
2645
+ --device-platform <platform>
2646
+
2647
+ update-project-resource:
2648
+ Required:
2649
+ --resource-id <uuid> From list-project-resources output
2650
+ --device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
2651
+ Optional:
2652
+ --directory <path> New absolute path
2653
+ --label <text> New label (pass 'null' to clear)
2654
+ --is-primary <true|false>
2655
+
2275
2656
  Examples:
2276
2657
  ovld protocol auth-status
2277
2658
  ovld protocol discover-project
@@ -2281,6 +2662,7 @@ Examples:
2281
2662
  ovld protocol attach --ticket-id 1:899 --external-session-id null
2282
2663
  ovld protocol connect --ticket-id 1:899
2283
2664
  ovld protocol load-context --ticket-id 1:899
2665
+ ovld protocol revert --objective-id <objective-uuid>
2284
2666
  ovld protocol search-tickets --query "auth refactor" --status next-up,execute --limit 10
2285
2667
  ovld protocol discuss-objective --ticket-id 1:899
2286
2668
  ovld protocol discuss-objective --ticket-id 1:899 --objective-id <objective-uuid>
@@ -2309,6 +2691,14 @@ Examples:
2309
2691
  ovld protocol record-work --payload-file - <<'EOF'
2310
2692
  {"objective":"...","summary":"...","artifacts":[...],"changeRationales":[...]}
2311
2693
  EOF
2694
+ ovld protocol get-device --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
2695
+ ovld protocol update-device --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT --label work-macbook
2696
+ ovld protocol list-project-resources --project-id <project-uuid>
2697
+ ovld protocol list-project-resources --project-id <project-uuid> --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
2698
+ ovld protocol add-project-resource --project-id <project-uuid> --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
2699
+ ovld protocol add-project-resource --project-id <project-uuid> --directory /path/to/repo --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT --label "main checkout" --is-primary
2700
+ ovld protocol update-project-resource --resource-id <resource-uuid> --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT --label "docs branch"
2701
+ ovld protocol update-project-resource --resource-id <resource-uuid> --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT --is-primary true
2312
2702
  `);
2313
2703
  return;
2314
2704
  }
@@ -2333,6 +2723,10 @@ EOF
2333
2723
  await protocolLoadContext(args);
2334
2724
  return;
2335
2725
  }
2726
+ if (subcommand === 'revert') {
2727
+ await protocolRevert(args);
2728
+ return;
2729
+ }
2336
2730
  if (subcommand === 'search-tickets') {
2337
2731
  await protocolSearchTickets(args);
2338
2732
  return;
@@ -2405,6 +2799,26 @@ EOF
2405
2799
  await protocolDeliver(args);
2406
2800
  return;
2407
2801
  }
2802
+ if (subcommand === 'get-device') {
2803
+ await protocolGetDevice(args);
2804
+ return;
2805
+ }
2806
+ if (subcommand === 'update-device') {
2807
+ await protocolUpdateDevice(args);
2808
+ return;
2809
+ }
2810
+ if (subcommand === 'list-project-resources') {
2811
+ await protocolListProjectResources(args);
2812
+ return;
2813
+ }
2814
+ if (subcommand === 'add-project-resource') {
2815
+ await protocolAddProjectResource(args);
2816
+ return;
2817
+ }
2818
+ if (subcommand === 'update-project-resource') {
2819
+ await protocolUpdateProjectResource(args);
2820
+ return;
2821
+ }
2408
2822
 
2409
2823
  console.error(`Unknown protocol subcommand: ${subcommand}\n`);
2410
2824
  console.log('Run: ovld protocol help');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605151257.0",
3
+ "version": "0.2605160931.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",
@@ -27,6 +27,8 @@ Attach:
27
27
  ovld protocol attach --ticket-id $TICKET_ID
28
28
  ```
29
29
 
30
+ In a git workspace, `attach` automatically creates a local git checkpoint for each executing objective before work begins, stored under `refs/overlord/checkpoints/<objectiveId>`. Pass `--skip-checkpoint` only when intentionally bypassing local provenance.
31
+
30
32
  Update:
31
33
 
32
34
  ```bash
@@ -75,7 +77,15 @@ ovld protocol deliver --session-key <sessionKey> \
75
77
  --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 @@"}]}]'
76
78
  ```
77
79
 
78
- `ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. Use `--payload-json` when the full delivery object fits comfortably inline. 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 the summary contains special characters, use `--summary-file -` and pipe via a single-quoted heredoc (`<<'EOF'`) to prevent shell expansion. 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.
80
+ Use `--payload-json` when the full delivery object fits comfortably inline. 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 the summary contains special characters, use `--summary-file -` and pipe via a single-quoted heredoc (`<<'EOF'`) to prevent shell expansion. 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.
81
+
82
+ Revert an objective:
83
+
84
+ ```bash
85
+ ovld protocol revert --objective-id <objective-id>
86
+ ```
87
+
88
+ `revert` restores the local working tree to the recorded objective checkpoint and saves a safety ref under `refs/overlord/safety/` first.
79
89
 
80
90
  ## Mode 2: Asked From Chat To Use Overlord
81
91
 
@@ -25,6 +25,8 @@ Attach:
25
25
  ovld protocol attach --ticket-id $TICKET_ID
26
26
  ```
27
27
 
28
+ In a git workspace, `attach` automatically creates a local git checkpoint for each executing objective before work begins, stored under `refs/overlord/checkpoints/<objectiveId>`. Pass `--skip-checkpoint` only when intentionally bypassing local provenance.
29
+
28
30
  Update:
29
31
 
30
32
  ```bash
@@ -61,7 +63,15 @@ ovld protocol deliver --session-key <sessionKey> \
61
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 @@"}]}]'
62
64
  ```
63
65
 
64
- `ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. Use `--payload-json` when the full delivery object fits comfortably inline. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed.
66
+ Use `--payload-json` when the full delivery object fits comfortably inline. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed.
67
+
68
+ Revert an objective:
69
+
70
+ ```bash
71
+ ovld protocol revert --objective-id <objective-id>
72
+ ```
73
+
74
+ `revert` restores the local working tree to the recorded objective checkpoint and saves a safety ref under `refs/overlord/safety/` first.
65
75
 
66
76
  ## Mode 2: Asked From Chat To Use Overlord
67
77
 
@@ -365,9 +365,8 @@ const tools = [
365
365
  checkpoint: {
366
366
  type: 'object',
367
367
  description:
368
- 'Optional checkpoint metadata. The local CLI creates this automatically for JJ/Git workspaces during deliver.'
368
+ 'Optional checkpoint metadata. Local git checkpoints are created on `attach`, not deliver; this field is only for callers forwarding pre-recorded checkpoint provenance.'
369
369
  },
370
- skip_checkpoint: { type: 'boolean' },
371
370
  skip_file_change_check: { type: 'boolean' }
372
371
  },
373
372
  required: ['session_key', 'ticket_id', 'summary']
@@ -376,7 +375,6 @@ const tools = [
376
375
  'session-key': args.session_key,
377
376
  'ticket-id': args.ticket_id,
378
377
  'payload-file': '-',
379
- 'skip-checkpoint': args.skip_checkpoint,
380
378
  'skip-file-change-check': args.skip_file_change_check
381
379
  }),
382
380
  toCliStdin: args =>
@@ -25,6 +25,8 @@ Attach:
25
25
  ovld protocol attach --ticket-id $TICKET_ID
26
26
  ```
27
27
 
28
+ In a git workspace, `attach` automatically creates a local git checkpoint for each executing objective before work begins, stored under `refs/overlord/checkpoints/<objectiveId>`. Pass `--skip-checkpoint` only when intentionally bypassing local provenance.
29
+
28
30
  Update:
29
31
 
30
32
  ```bash
@@ -65,7 +67,15 @@ ovld protocol deliver --session-key <sessionKey> \
65
67
  --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
68
  ```
67
69
 
68
- `ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. Use `--payload-json` when the full delivery object fits comfortably inline. 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.
70
+ Use `--payload-json` when the full delivery object fits comfortably inline. 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.
71
+
72
+ Revert an objective:
73
+
74
+ ```bash
75
+ ovld protocol revert --objective-id <objective-id>
76
+ ```
77
+
78
+ `revert` restores the local working tree to the recorded objective checkpoint and saves a safety ref under `refs/overlord/safety/` first.
69
79
 
70
80
  ## Mode 2: Asked From Chat To Use Overlord
71
81