overlord-cli 5.11.0 → 5.12.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
@@ -118,10 +118,10 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
118
118
  - `read-context` - read shared persistent context for this ticket
119
119
  - `write-context` - write shared persistent context for future sessions
120
120
  - `deliver` - finish work, send artifacts, and move the ticket to review
121
- - `artifact-prepare-upload` - get a signed upload URL for a ticket artifact
122
- - `artifact-finalize-upload` - finalize an uploaded artifact row after storage upload
123
- - `artifact-download-url` - get a signed download URL for an existing artifact
124
- - `artifact-upload-file` - prepare, upload, and finalize a local file in one command
121
+ - `attachment-prepare-upload` - get a signed upload URL for an objective attachment
122
+ - `attachment-finalize-upload` - finalize an uploaded objective attachment row after storage upload
123
+ - `attachment-download-url` - get a signed download URL for an existing objective attachment
124
+ - `attachment-upload-file` - prepare, upload, and finalize a local objective attachment in one command
125
125
 
126
126
  ## License
127
127
 
@@ -43,7 +43,11 @@ export function resolveProtocolAgentIdentifier(flags = {}) {
43
43
  return envAgent || 'claude-code';
44
44
  }
45
45
 
46
- export function resolveProtocolTicketDelegate(flags = {}, modelIdentifier = '', agentIdentifier = '') {
46
+ export function resolveProtocolTicketDelegate(
47
+ flags = {},
48
+ modelIdentifier = '',
49
+ agentIdentifier = ''
50
+ ) {
47
51
  const explicitDelegate = typeof flags.delegate === 'string' ? flags.delegate.trim() : '';
48
52
  if (explicitDelegate) return explicitDelegate;
49
53
 
@@ -120,19 +124,20 @@ async function apiPost(
120
124
  if (error && (error.name === 'TimeoutError' || error.name === 'AbortError')) {
121
125
  throw new Error(
122
126
  `Request timed out after ${timeoutMs}ms calling ${requestUrl}.\n` +
123
- `Tip: Ensure Overlord is running and reachable from this environment. ` +
124
- `Increase the limit with --timeout <ms> or OVERLORD_TIMEOUT=<ms>.`
127
+ `Tip: Ensure Overlord is running and reachable from this environment. ` +
128
+ `Increase the limit with --timeout <ms> or OVERLORD_TIMEOUT=<ms>.`
125
129
  );
126
130
  }
127
131
 
128
- const causeCode = (
132
+ const causeCode =
129
133
  typeof error === 'object' &&
130
134
  error !== null &&
131
135
  'cause' in error &&
132
136
  typeof error.cause === 'object' &&
133
137
  error.cause !== null &&
134
138
  'code' in error.cause
135
- ) ? String(error.cause.code) : '';
139
+ ? String(error.cause.code)
140
+ : '';
136
141
 
137
142
  let hint = 'Check your network and Overlord server settings.';
138
143
  if (causeCode === 'ECONNREFUSED') {
@@ -162,10 +167,10 @@ async function apiPost(
162
167
  if (res.status === 401) {
163
168
  throw new Error(
164
169
  `Authentication failed (401): ${data.error ?? 'Invalid or missing token.'}\n` +
165
- `IMPORTANT: Stop all work immediately. Your Overlord auth session is invalid, expired, or missing required scope.\n` +
166
- `First run \`ovld auth repair\` yourself.\n` +
167
- `If repair does not fix it, ask the user to sign in again with Overlord Desktop or \`ovld auth login\` if needed.\n` +
168
- `Then ask whether they would like to proceed without submitting updates to Overlord.`
170
+ `IMPORTANT: Stop all work immediately. Your Overlord auth session is invalid, expired, or missing required scope.\n` +
171
+ `First run \`ovld auth repair\` yourself.\n` +
172
+ `If repair does not fix it, ask the user to sign in again with Overlord Desktop or \`ovld auth login\` if needed.\n` +
173
+ `Then ask whether they would like to proceed without submitting updates to Overlord.`
169
174
  );
170
175
  }
171
176
 
@@ -376,10 +381,10 @@ function createFileChangeCheckError(message, changedFiles, rationalePaths = [])
376
381
 
377
382
  return new Error(
378
383
  `${message}\n` +
379
- `Overlord persists file changes through \`changeRationales\`, not \`file_changes\` artifacts.\n` +
380
- `Re-run with --change-rationales-json or --change-rationales-file, or pass --skip-file-change-check if this was intentional.` +
381
- `${changedPreview ? `\nChanged files: ${changedPreview}${changedFiles.size > 10 ? ', ...' : ''}` : ''}` +
382
- `${rationalePreview ? `\nProvided rationale paths: ${rationalePreview}${rationalePaths.length > 10 ? ', ...' : ''}` : ''}`
384
+ `Overlord persists file changes through \`changeRationales\`, not \`file_changes\` artifacts.\n` +
385
+ `Re-run with --change-rationales-json or --change-rationales-file, or pass --skip-file-change-check if this was intentional.` +
386
+ `${changedPreview ? `\nChanged files: ${changedPreview}${changedFiles.size > 10 ? ', ...' : ''}` : ''}` +
387
+ `${rationalePreview ? `\nProvided rationale paths: ${rationalePreview}${rationalePaths.length > 10 ? ', ...' : ''}` : ''}`
383
388
  );
384
389
  }
385
390
 
@@ -427,7 +432,8 @@ function detectClaudeSessionId() {
427
432
 
428
433
  if (!fs.existsSync(sessionsDir)) return null;
429
434
 
430
- const files = fs.readdirSync(sessionsDir)
435
+ const files = fs
436
+ .readdirSync(sessionsDir)
431
437
  .filter(f => f.endsWith('.jsonl'))
432
438
  .map(f => ({
433
439
  name: f,
@@ -553,7 +559,9 @@ async function protocolUpdate(args) {
553
559
  : {}),
554
560
  ...(flags.phase ? { phase: String(flags.phase) } : {}),
555
561
  ...(flags['event-type'] ? { eventType: String(flags['event-type']) } : {}),
556
- ...(flags['payload-json'] ? { payload: parseJsonFlag('--payload-json', flags['payload-json']) } : {}),
562
+ ...(flags['payload-json']
563
+ ? { payload: parseJsonFlag('--payload-json', flags['payload-json']) }
564
+ : {}),
557
565
  ...(changeRationales.length > 0 ? { changeRationales } : {})
558
566
  };
559
567
 
@@ -634,7 +642,9 @@ async function protocolAsk(args) {
634
642
  ticketId,
635
643
  question,
636
644
  ...(flags.phase ? { phase: String(flags.phase) } : {}),
637
- ...(flags['payload-json'] ? { payload: parseJsonFlag('--payload-json', flags['payload-json']) } : {})
645
+ ...(flags['payload-json']
646
+ ? { payload: parseJsonFlag('--payload-json', flags['payload-json']) }
647
+ : {})
638
648
  };
639
649
 
640
650
  const data = await apiPost(
@@ -737,7 +747,13 @@ async function protocolWriteContext(args) {
737
747
  ticketId,
738
748
  key,
739
749
  value,
740
- ...(flags.tags ? { tags: String(flags.tags).split(',').map(t => t.trim()) } : {})
750
+ ...(flags.tags
751
+ ? {
752
+ tags: String(flags.tags)
753
+ .split(',')
754
+ .map(t => t.trim())
755
+ }
756
+ : {})
741
757
  };
742
758
 
743
759
  const data = await apiPost(
@@ -764,7 +780,8 @@ async function protocolDeliver(args) {
764
780
  const deliverPayload = flags['payload-file']
765
781
  ? await readJsonFileOrStdin(String(flags['payload-file']), '--payload-file')
766
782
  : null;
767
- const summary = deliverPayload?.summary ??
783
+ const summary =
784
+ deliverPayload?.summary ??
768
785
  (flags['summary-file']
769
786
  ? readTextFile(String(flags['summary-file']), '--summary-file')
770
787
  : requireFlag(flags, 'summary', undefined));
@@ -789,7 +806,8 @@ async function protocolDeliver(args) {
789
806
  throw new Error('Use either --payload-file or change-rationale flags, not both');
790
807
  }
791
808
 
792
- const changeRationales = deliverPayload?.changeRationales ?? await resolveChangeRationales(flags);
809
+ const changeRationales =
810
+ deliverPayload?.changeRationales ?? (await resolveChangeRationales(flags));
793
811
  validateDeliverFileChanges(flags, changeRationales);
794
812
 
795
813
  const body = {
@@ -813,14 +831,15 @@ async function protocolDeliver(args) {
813
831
  }
814
832
 
815
833
  // ---------------------------------------------------------------------------
816
- // artifacts
834
+ // objective attachments
817
835
  // ---------------------------------------------------------------------------
818
836
 
819
- async function protocolArtifactPrepareUpload(args) {
837
+ async function protocolAttachmentPrepareUpload(args) {
820
838
  const flags = parseFlags(args);
821
839
  const { sessionKey, ticketId } = resolveSessionFlags(flags);
822
840
  if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
823
841
  if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
842
+ const objectiveId = requireFlag(flags, 'objective-id', undefined);
824
843
  const fileName = requireFlag(flags, 'file-name', undefined);
825
844
 
826
845
  const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
@@ -829,12 +848,14 @@ async function protocolArtifactPrepareUpload(args) {
829
848
  const body = {
830
849
  sessionKey,
831
850
  ticketId,
851
+ objectiveId,
832
852
  fileName,
833
853
  ...(flags.label ? { label: String(flags.label) } : {}),
834
- ...(flags['artifact-type'] ? { artifactType: String(flags['artifact-type']) } : {}),
835
854
  ...(flags['content-type'] ? { contentType: String(flags['content-type']) } : {}),
836
855
  ...(flags['file-size'] ? { fileSize: parseInt(String(flags['file-size']), 10) } : {}),
837
- ...(flags['metadata-json'] ? { metadata: parseJsonFlag('--metadata-json', flags['metadata-json']) } : {})
856
+ ...(flags['metadata-json']
857
+ ? { metadata: parseJsonFlag('--metadata-json', flags['metadata-json']) }
858
+ : {})
838
859
  };
839
860
 
840
861
  const data = await apiPost(
@@ -842,18 +863,19 @@ async function protocolArtifactPrepareUpload(args) {
842
863
  bearerToken,
843
864
  localSecret,
844
865
  organizationId,
845
- '/api/protocol/artifacts/prepare-upload',
866
+ '/api/protocol/attachments/prepare-upload',
846
867
  body,
847
868
  timeoutMs
848
869
  );
849
870
  console.log(JSON.stringify(data, null, 2));
850
871
  }
851
872
 
852
- async function protocolArtifactFinalizeUpload(args) {
873
+ async function protocolAttachmentFinalizeUpload(args) {
853
874
  const flags = parseFlags(args);
854
875
  const { sessionKey, ticketId } = resolveSessionFlags(flags);
855
876
  if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
856
877
  if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
878
+ const objectiveId = requireFlag(flags, 'objective-id', undefined);
857
879
  const storagePath = requireFlag(flags, 'storage-path', undefined);
858
880
  const label = requireFlag(flags, 'label', undefined);
859
881
 
@@ -863,12 +885,14 @@ async function protocolArtifactFinalizeUpload(args) {
863
885
  const body = {
864
886
  sessionKey,
865
887
  ticketId,
888
+ objectiveId,
866
889
  storagePath,
867
890
  label,
868
- ...(flags['artifact-type'] ? { artifactType: String(flags['artifact-type']) } : {}),
869
891
  ...(flags['content-type'] ? { contentType: String(flags['content-type']) } : {}),
870
892
  ...(flags['file-size'] ? { fileSize: parseInt(String(flags['file-size']), 10) } : {}),
871
- ...(flags['metadata-json'] ? { metadata: parseJsonFlag('--metadata-json', flags['metadata-json']) } : {})
893
+ ...(flags['metadata-json']
894
+ ? { metadata: parseJsonFlag('--metadata-json', flags['metadata-json']) }
895
+ : {})
872
896
  };
873
897
 
874
898
  const data = await apiPost(
@@ -876,20 +900,23 @@ async function protocolArtifactFinalizeUpload(args) {
876
900
  bearerToken,
877
901
  localSecret,
878
902
  organizationId,
879
- '/api/protocol/artifacts/finalize-upload',
903
+ '/api/protocol/attachments/finalize-upload',
880
904
  body,
881
905
  timeoutMs
882
906
  );
883
907
  console.log(JSON.stringify(data, null, 2));
884
908
  }
885
909
 
886
- async function protocolArtifactGetDownloadUrl(args) {
910
+ async function protocolAttachmentGetDownloadUrl(args) {
887
911
  const flags = parseFlags(args);
888
912
  const { sessionKey, ticketId } = resolveSessionFlags(flags);
889
913
  if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
890
914
  if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
891
- if (!flags['artifact-id'] && !flags['storage-path']) {
892
- throw new Error('--artifact-id or --storage-path is required');
915
+ if (!flags['attachment-id'] && !flags['storage-path']) {
916
+ throw new Error('--attachment-id or --storage-path is required');
917
+ }
918
+ if (flags['storage-path'] && !flags['objective-id']) {
919
+ throw new Error('--objective-id is required when using --storage-path');
893
920
  }
894
921
 
895
922
  const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
@@ -898,7 +925,8 @@ async function protocolArtifactGetDownloadUrl(args) {
898
925
  const body = {
899
926
  sessionKey,
900
927
  ticketId,
901
- ...(flags['artifact-id'] ? { artifactId: String(flags['artifact-id']) } : {}),
928
+ ...(flags['objective-id'] ? { objectiveId: String(flags['objective-id']) } : {}),
929
+ ...(flags['attachment-id'] ? { attachmentId: String(flags['attachment-id']) } : {}),
902
930
  ...(flags['storage-path'] ? { storagePath: String(flags['storage-path']) } : {}),
903
931
  ...(flags['expires-in'] ? { expiresIn: parseInt(String(flags['expires-in']), 10) } : {})
904
932
  };
@@ -908,18 +936,19 @@ async function protocolArtifactGetDownloadUrl(args) {
908
936
  bearerToken,
909
937
  localSecret,
910
938
  organizationId,
911
- '/api/protocol/artifacts/get-download-url',
939
+ '/api/protocol/attachments/get-download-url',
912
940
  body,
913
941
  timeoutMs
914
942
  );
915
943
  console.log(JSON.stringify(data, null, 2));
916
944
  }
917
945
 
918
- async function protocolArtifactUploadFile(args) {
946
+ async function protocolAttachmentUploadFile(args) {
919
947
  const flags = parseFlags(args);
920
948
  const { sessionKey, ticketId } = resolveSessionFlags(flags);
921
949
  if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
922
950
  if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
951
+ const objectiveId = requireFlag(flags, 'objective-id', undefined);
923
952
  const filePath = requireFlag(flags, 'file', undefined);
924
953
 
925
954
  const { readFile, stat } = await import('node:fs/promises');
@@ -934,21 +963,25 @@ async function protocolArtifactUploadFile(args) {
934
963
  const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
935
964
  const timeoutMs = resolveTimeout(flags);
936
965
 
966
+ const metadata = flags['metadata-json']
967
+ ? parseJsonFlag('--metadata-json', flags['metadata-json'])
968
+ : undefined;
969
+
937
970
  const prepared = await apiPost(
938
971
  platformUrl,
939
972
  bearerToken,
940
973
  localSecret,
941
974
  organizationId,
942
- '/api/protocol/artifacts/prepare-upload',
975
+ '/api/protocol/attachments/prepare-upload',
943
976
  {
944
977
  sessionKey,
945
978
  ticketId,
979
+ objectiveId,
946
980
  fileName,
947
981
  label,
948
- artifactType: String(flags['artifact-type'] ?? 'document'),
949
982
  contentType,
950
983
  fileSize: fileStats.size,
951
- ...(flags['metadata-json'] ? { metadata: parseJsonFlag('--metadata-json', flags['metadata-json']) } : {})
984
+ ...(metadata ? { metadata } : {})
952
985
  },
953
986
  timeoutMs
954
987
  );
@@ -966,16 +999,16 @@ async function protocolArtifactUploadFile(args) {
966
999
  bearerToken,
967
1000
  localSecret,
968
1001
  organizationId,
969
- '/api/protocol/artifacts/finalize-upload',
1002
+ '/api/protocol/attachments/finalize-upload',
970
1003
  {
971
1004
  sessionKey,
972
1005
  ticketId,
1006
+ objectiveId,
973
1007
  storagePath,
974
1008
  label,
975
- artifactType: String(flags['artifact-type'] ?? 'document'),
976
1009
  contentType,
977
1010
  fileSize: fileStats.size,
978
- ...(flags['metadata-json'] ? { metadata: parseJsonFlag('--metadata-json', flags['metadata-json']) } : {})
1011
+ ...(metadata ? { metadata } : {})
979
1012
  },
980
1013
  timeoutMs
981
1014
  );
@@ -1098,12 +1131,18 @@ async function protocolPrompt(args) {
1098
1131
  ...(flags['project-id'] ? { projectId: String(flags['project-id']) } : {}),
1099
1132
  ...(personal ? { personal: true } : {}),
1100
1133
  ...(workingDirectory ? { workingDirectory: String(workingDirectory) } : {}),
1101
- ...(flags['acceptance-criteria'] ? { acceptanceCriteria: String(flags['acceptance-criteria']) } : {}),
1134
+ ...(flags['acceptance-criteria']
1135
+ ? { acceptanceCriteria: String(flags['acceptance-criteria']) }
1136
+ : {}),
1102
1137
  ...(flags['available-tools'] ? { availableTools: String(flags['available-tools']) } : {}),
1103
1138
  ...(flags['execution-target'] ? { executionTarget: String(flags['execution-target']) } : {}),
1104
1139
  delegate: resolveProtocolTicketDelegate(flags, modelIdentifier, agentIdentifier),
1105
- ...(flags['parent-session-key'] ? { parentSessionKey: String(flags['parent-session-key']) } : {}),
1106
- ...(flags['parent-ticket-id'] ? { parentTicketId: String(flags['parent-ticket-id'] ?? process.env.TICKET_ID ?? '') } : {})
1140
+ ...(flags['parent-session-key']
1141
+ ? { parentSessionKey: String(flags['parent-session-key']) }
1142
+ : {}),
1143
+ ...(flags['parent-ticket-id']
1144
+ ? { parentTicketId: String(flags['parent-ticket-id'] ?? process.env.TICKET_ID ?? '') }
1145
+ : {})
1107
1146
  };
1108
1147
 
1109
1148
  const data = await apiPost(
@@ -1151,7 +1190,9 @@ async function protocolCreateTicket(args) {
1151
1190
  objective,
1152
1191
  ...(flags.title ? { title: String(flags.title) } : {}),
1153
1192
  ...(flags.priority ? { priority: String(flags.priority) } : {}),
1154
- ...(flags['acceptance-criteria'] ? { acceptanceCriteria: String(flags['acceptance-criteria']) } : {}),
1193
+ ...(flags['acceptance-criteria']
1194
+ ? { acceptanceCriteria: String(flags['acceptance-criteria']) }
1195
+ : {}),
1155
1196
  ...(flags['available-tools'] ? { availableTools: String(flags['available-tools']) } : {}),
1156
1197
  ...(flags['execution-target'] ? { executionTarget: String(flags['execution-target']) } : {}),
1157
1198
  delegate: resolveProtocolTicketDelegate(flags, modelIdentifier, agentIdentifier)
@@ -1189,7 +1230,9 @@ async function protocolCreateTicket(args) {
1189
1230
  ...(standaloneWorkingDirectory ? { workingDirectory: standaloneWorkingDirectory } : {}),
1190
1231
  ...(flags.title ? { title: String(flags.title) } : {}),
1191
1232
  ...(flags.priority ? { priority: String(flags.priority) } : {}),
1192
- ...(flags['acceptance-criteria'] ? { acceptanceCriteria: String(flags['acceptance-criteria']) } : {}),
1233
+ ...(flags['acceptance-criteria']
1234
+ ? { acceptanceCriteria: String(flags['acceptance-criteria']) }
1235
+ : {}),
1193
1236
  ...(flags['available-tools'] ? { availableTools: String(flags['available-tools']) } : {}),
1194
1237
  ...(flags['execution-target'] ? { executionTarget: String(flags['execution-target']) } : {}),
1195
1238
  delegate: resolveProtocolTicketDelegate(flags, modelIdentifier, agentIdentifier)
@@ -1227,7 +1270,10 @@ async function protocolSearchTickets(args) {
1227
1270
  ...(flags.query ? { query: String(flags.query) } : {}),
1228
1271
  ...(statuses?.length ? { statuses } : {}),
1229
1272
  ...(flags['include-completed'] !== undefined
1230
- ? { includeCompleted: flags['include-completed'] !== false && flags['include-completed'] !== 'false' }
1273
+ ? {
1274
+ includeCompleted:
1275
+ flags['include-completed'] !== false && flags['include-completed'] !== 'false'
1276
+ }
1231
1277
  : {}),
1232
1278
  ...(flags.limit ? { limit: parseInt(String(flags.limit), 10) } : {}),
1233
1279
  ...(flags['project-id'] ? { projectId: String(flags['project-id']) } : {}),
@@ -1318,11 +1364,11 @@ Subcommands:
1318
1364
  permission-request Notify Overlord that the agent is requesting tool permission
1319
1365
  read-context Read shared persistent context for this ticket
1320
1366
  write-context Write shared persistent context for future sessions
1321
- deliver Finish work, send artifacts, and move the ticket to review
1322
- artifact-prepare-upload Get a signed upload URL for a ticket artifact
1323
- artifact-finalize-upload Finalize an uploaded artifact row after storage upload
1324
- artifact-download-url Get a signed download URL for an existing artifact
1325
- artifact-upload-file Prepare, upload, and finalize a local file in one command
1367
+ deliver Finish work, send artifacts, and move the ticket to review
1368
+ attachment-prepare-upload Get a signed upload URL for an objective attachment
1369
+ attachment-finalize-upload Finalize an uploaded attachment row after storage upload
1370
+ attachment-download-url Get a signed download URL for an existing attachment
1371
+ attachment-upload-file Prepare, upload, and finalize a local file in one command
1326
1372
 
1327
1373
  Environment fallback:
1328
1374
  --session-key <- SESSION_KEY
@@ -1551,47 +1597,48 @@ create:
1551
1597
  Standalone create auto-discovers the project from the current working directory unless --personal is set.
1552
1598
  Follow-up create requires both --session-key and --ticket-id.
1553
1599
 
1554
- artifact-prepare-upload:
1600
+ attachment-prepare-upload:
1555
1601
  Required:
1556
1602
  --session-key <key>
1557
1603
  --ticket-id <id>
1604
+ --objective-id <id>
1558
1605
  --file-name <name>
1559
1606
  Optional:
1560
1607
  --label <text>
1561
- --artifact-type <type>
1562
1608
  --content-type <mime>
1563
1609
  --file-size <bytes>
1564
1610
  --metadata-json <json>
1565
1611
 
1566
- artifact-finalize-upload:
1612
+ attachment-finalize-upload:
1567
1613
  Required:
1568
1614
  --session-key <key>
1569
1615
  --ticket-id <id>
1616
+ --objective-id <id>
1570
1617
  --storage-path <path>
1571
1618
  --label <text>
1572
1619
  Optional:
1573
- --artifact-type <type>
1574
1620
  --content-type <mime>
1575
1621
  --file-size <bytes>
1576
1622
  --metadata-json <json>
1577
1623
 
1578
- artifact-download-url:
1624
+ attachment-download-url:
1579
1625
  Required:
1580
1626
  --session-key <key>
1581
1627
  --ticket-id <id>
1582
- one of: --artifact-id <id> | --storage-path <path>
1628
+ one of: --attachment-id <id> | --storage-path <path>
1583
1629
  Optional:
1630
+ --objective-id <id> Required when using --storage-path
1584
1631
  --expires-in <seconds>
1585
1632
 
1586
- artifact-upload-file:
1633
+ attachment-upload-file:
1587
1634
  Required:
1588
1635
  --session-key <key>
1589
1636
  --ticket-id <id>
1637
+ --objective-id <id>
1590
1638
  --file <path>
1591
1639
  Optional:
1592
1640
  --file-name <name> Defaults to basename of --file
1593
1641
  --label <text> Defaults to file name
1594
- --artifact-type <type> Defaults to document
1595
1642
  --content-type <mime> Defaults to application/octet-stream
1596
1643
  --metadata-json <json>
1597
1644
 
@@ -1614,9 +1661,9 @@ Examples:
1614
1661
  ovld protocol ask --session-key <key> --ticket-id <id> --question-file ./question.txt
1615
1662
  ovld protocol read-context --session-key <key> --ticket-id <id> --query arch --limit 5
1616
1663
  ovld protocol write-context --session-key <key> --ticket-id <id> --key "arch" --value '"monorepo"' --tags repo,agent
1617
- ovld protocol artifact-prepare-upload --session-key <key> --ticket-id <id> --file-name spec.pdf --content-type application/pdf
1618
- ovld protocol artifact-upload-file --session-key <key> --ticket-id <id> --file ./spec.pdf --content-type application/pdf
1619
- ovld protocol artifact-download-url --session-key <key> --ticket-id <id> --artifact-id <artifact-id>
1664
+ ovld protocol attachment-prepare-upload --session-key <key> --ticket-id <id> --objective-id <objective-id> --file-name spec.pdf --content-type application/pdf
1665
+ ovld protocol attachment-upload-file --session-key <key> --ticket-id <id> --objective-id <objective-id> --file ./spec.pdf
1666
+ ovld protocol attachment-download-url --session-key <key> --ticket-id <id> --attachment-id <attachment-id>
1620
1667
  ovld protocol deliver --session-key <key> --ticket-id <id> --summary "Done"
1621
1668
  ovld protocol deliver --session-key <key> --ticket-id <id> --summary "Done" --artifacts-file ./artifacts.json
1622
1669
  ovld protocol deliver --session-key <key> --ticket-id <id> --payload-file ./deliver.json
@@ -1627,25 +1674,82 @@ Examples:
1627
1674
  return;
1628
1675
  }
1629
1676
 
1630
- if (subcommand === 'discover-project') { await protocolDiscoverProject(args); return; }
1631
- if (subcommand === 'auth-status') { await protocolAuthStatus(); return; }
1632
- if (subcommand === 'attach') { await protocolAttach(args); return; }
1633
- if (subcommand === 'connect') { await protocolConnect(args); return; }
1634
- if (subcommand === 'load-context') { await protocolLoadContext(args); return; }
1635
- if (subcommand === 'search-tickets') { await protocolSearchTickets(args); return; }
1636
- if (subcommand === 'create' || subcommand === 'create-ticket') { await protocolCreateTicket(args); return; }
1637
- if (subcommand === 'prompt' || subcommand === 'spawn') { await protocolPrompt(args); return; }
1638
- if (subcommand === 'artifact-prepare-upload') { await protocolArtifactPrepareUpload(args); return; }
1639
- if (subcommand === 'artifact-finalize-upload') { await protocolArtifactFinalizeUpload(args); return; }
1640
- if (subcommand === 'artifact-download-url') { await protocolArtifactGetDownloadUrl(args); return; }
1641
- if (subcommand === 'artifact-upload-file') { await protocolArtifactUploadFile(args); return; }
1642
- if (subcommand === 'update') { await protocolUpdate(args); return; }
1643
- if (subcommand === 'record-change-rationales') { await protocolRecordChangeRationales(args); return; }
1644
- if (subcommand === 'ask') { await protocolAsk(args); return; }
1645
- if (subcommand === 'permission-request') { await protocolPermissionRequest(args); return; }
1646
- if (subcommand === 'read-context') { await protocolReadContext(args); return; }
1647
- if (subcommand === 'write-context') { await protocolWriteContext(args); return; }
1648
- if (subcommand === 'deliver') { await protocolDeliver(args); return; }
1677
+ if (subcommand === 'discover-project') {
1678
+ await protocolDiscoverProject(args);
1679
+ return;
1680
+ }
1681
+ if (subcommand === 'auth-status') {
1682
+ await protocolAuthStatus();
1683
+ return;
1684
+ }
1685
+ if (subcommand === 'attach') {
1686
+ await protocolAttach(args);
1687
+ return;
1688
+ }
1689
+ if (subcommand === 'connect') {
1690
+ await protocolConnect(args);
1691
+ return;
1692
+ }
1693
+ if (subcommand === 'load-context') {
1694
+ await protocolLoadContext(args);
1695
+ return;
1696
+ }
1697
+ if (subcommand === 'search-tickets') {
1698
+ await protocolSearchTickets(args);
1699
+ return;
1700
+ }
1701
+ if (subcommand === 'create' || subcommand === 'create-ticket') {
1702
+ await protocolCreateTicket(args);
1703
+ return;
1704
+ }
1705
+ if (subcommand === 'prompt' || subcommand === 'spawn') {
1706
+ await protocolPrompt(args);
1707
+ return;
1708
+ }
1709
+ if (subcommand === 'attachment-prepare-upload') {
1710
+ await protocolAttachmentPrepareUpload(args);
1711
+ return;
1712
+ }
1713
+ if (subcommand === 'attachment-finalize-upload') {
1714
+ await protocolAttachmentFinalizeUpload(args);
1715
+ return;
1716
+ }
1717
+ if (subcommand === 'attachment-download-url') {
1718
+ await protocolAttachmentGetDownloadUrl(args);
1719
+ return;
1720
+ }
1721
+ if (subcommand === 'attachment-upload-file') {
1722
+ await protocolAttachmentUploadFile(args);
1723
+ return;
1724
+ }
1725
+ if (subcommand === 'update') {
1726
+ await protocolUpdate(args);
1727
+ return;
1728
+ }
1729
+ if (subcommand === 'record-change-rationales') {
1730
+ await protocolRecordChangeRationales(args);
1731
+ return;
1732
+ }
1733
+ if (subcommand === 'ask') {
1734
+ await protocolAsk(args);
1735
+ return;
1736
+ }
1737
+ if (subcommand === 'permission-request') {
1738
+ await protocolPermissionRequest(args);
1739
+ return;
1740
+ }
1741
+ if (subcommand === 'read-context') {
1742
+ await protocolReadContext(args);
1743
+ return;
1744
+ }
1745
+ if (subcommand === 'write-context') {
1746
+ await protocolWriteContext(args);
1747
+ return;
1748
+ }
1749
+ if (subcommand === 'deliver') {
1750
+ await protocolDeliver(args);
1751
+ return;
1752
+ }
1649
1753
 
1650
1754
  console.error(`Unknown protocol subcommand: ${subcommand}\n`);
1651
1755
  console.log('Run: ovld protocol help');
@@ -130,7 +130,7 @@ ovld protocol prompt --agent claude-code --objective "Implement feature X" --pri
130
130
  \`\`\`bash
131
131
  ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
132
132
  ovld protocol write-context --session-key <sessionKey> --ticket-id $TICKET_ID --key "key" --value '"json-value"'
133
- ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --file ./spec.pdf --content-type application/pdf
133
+ ovld protocol attachment-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --objective-id <objective-id> --file ./spec.pdf --content-type application/pdf
134
134
  \`\`\`
135
135
 
136
136
  ## Rules
@@ -209,7 +209,7 @@ ovld protocol prompt --agent opencode --objective "Implement feature X" --priori
209
209
  \`\`\`bash
210
210
  ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
211
211
  ovld protocol write-context --session-key <sessionKey> --ticket-id $TICKET_ID --key "key" --value '"json-value"'
212
- ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --file ./spec.pdf --content-type application/pdf
212
+ ovld protocol attachment-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --objective-id <objective-id> --file ./spec.pdf --content-type application/pdf
213
213
  \`\`\`
214
214
 
215
215
  ## Rules
@@ -540,7 +540,11 @@ function currentContentHashForAgent(agent) {
540
540
  return contentHashForDirectory(cursorSourcePluginDir());
541
541
  }
542
542
  if (agent === 'gemini') {
543
- return contentHash(slashCommandFiles('gemini').map(file => file.content).join('\n'));
543
+ return contentHash(
544
+ slashCommandFiles('gemini')
545
+ .map(file => file.content)
546
+ .join('\n')
547
+ );
544
548
  }
545
549
  if (agent === 'codex') return codexContentHash();
546
550
  return contentHash(
@@ -796,7 +800,14 @@ function cursorPaths() {
796
800
  const base = path.join(os.homedir(), '.cursor');
797
801
  return {
798
802
  pluginDir: path.join(base, 'plugins', 'local', 'overlord'),
799
- pluginManifest: path.join(base, 'plugins', 'local', 'overlord', '.cursor-plugin', 'plugin.json'),
803
+ pluginManifest: path.join(
804
+ base,
805
+ 'plugins',
806
+ 'local',
807
+ 'overlord',
808
+ '.cursor-plugin',
809
+ 'plugin.json'
810
+ ),
800
811
  rulesFile: path.join(base, 'rules', 'overlord-local.mdc'),
801
812
  settingsFile: path.join(base, 'settings.json')
802
813
  };
@@ -956,10 +967,7 @@ function installCursor() {
956
967
  ? existingSettings.permissions
957
968
  : {};
958
969
  const mergedAllow = Array.from(
959
- new Set([
960
- ...asStringArray(permissions.allow),
961
- 'Shell(ovld protocol:*)'
962
- ])
970
+ new Set([...asStringArray(permissions.allow), 'Shell(ovld protocol:*)'])
963
971
  );
964
972
  writeJsonFile(paths.settingsFile, {
965
973
  ...existingSettings,
@@ -1327,7 +1335,7 @@ function installClaudePermissions(platformUrl) {
1327
1335
  const entries = ['Bash(ovld protocol:*)'];
1328
1336
 
1329
1337
  const existing = new Set(settings.permissions.allow);
1330
- const toAdd = entries.filter((e) => !existing.has(e));
1338
+ const toAdd = entries.filter(e => !existing.has(e));
1331
1339
 
1332
1340
  if (toAdd.length === 0) {
1333
1341
  console.log(' All required permissions already present. Nothing to do.\n');
@@ -1466,7 +1474,9 @@ export async function runSetupCommand(args) {
1466
1474
  const selectedAgents = selectedLabels.map(label => label.split('-')[0].trim());
1467
1475
 
1468
1476
  // Step 2: Install selected agents
1469
- console.log(`\nPreparing Overlord agent plugins/connectors for: ${selectedAgents.join(', ')}...\n`);
1477
+ console.log(
1478
+ `\nPreparing Overlord agent plugins/connectors for: ${selectedAgents.join(', ')}...\n`
1479
+ );
1470
1480
 
1471
1481
  const installedAgents = [];
1472
1482
  for (const a of selectedAgents) {
@@ -1616,6 +1626,9 @@ export async function runDoctorCommand({ latestCliVersion = null } = {}) {
1616
1626
  }
1617
1627
  if (updateVersion) {
1618
1628
  console.log();
1619
- printCliUpdateNotice(updateVersion, { currentVersion: getCurrentCliVersion(), stream: process.stdout });
1629
+ printCliUpdateNotice(updateVersion, {
1630
+ currentVersion: getCurrentCliVersion(),
1631
+ stream: process.stdout
1632
+ });
1620
1633
  }
1621
1634
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "5.11.0",
3
+ "version": "5.12.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "type": "module",
6
6
  "bin": {
@@ -141,8 +141,8 @@ When in doubt, ask yourself: *can this be done entirely inside a terminal or bro
141
141
  ```bash
142
142
  ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
143
143
  ovld protocol write-context --session-key <sessionKey> --ticket-id $TICKET_ID --key "key" --value '"json-value"'
144
- ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --file ./spec.pdf --content-type application/pdf
145
- ovld protocol artifact-download-url --session-key <sessionKey> --ticket-id $TICKET_ID --artifact-id <artifact-id>
144
+ ovld protocol attachment-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --objective-id <objective-id> --file ./spec.pdf --content-type application/pdf
145
+ ovld protocol attachment-download-url --session-key <sessionKey> --ticket-id $TICKET_ID --attachment-id <attachment-id>
146
146
  ```
147
147
 
148
148
  ## Rules
@@ -158,4 +158,4 @@ ovld protocol artifact-download-url --session-key <sessionKey> --ticket-id $TICK
158
158
  - Do not add or commit changes unless the user explicitly asks you to commit.
159
159
  - Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
160
160
 
161
- <!-- version: 0.2.5 -->
161
+ <!-- version: 0.2.6 -->
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Chat-driven access to Overlord ticket protocol workflows.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -25,7 +25,7 @@ personal marketplace entry at `~/.agents/plugins/marketplace.json`.
25
25
  - Ticket session flow: `attach_ticket`, `connect_ticket`, `load_ticket_context`, `spawn_ticket`
26
26
  - Progress and review flow: `post_update`, `record_change_rationales`, `ask_blocking_question`, `deliver_ticket`
27
27
  - Shared context: `read_shared_context`, `write_shared_context`
28
- - Artifacts: `prepare_artifact_upload`, `finalize_artifact_upload`, `get_artifact_download_url`, `upload_artifact_file`
28
+ - Objective attachments: `prepare_attachment_upload`, `finalize_attachment_upload`, `get_attachment_download_url`, `upload_attachment_file`
29
29
 
30
30
  The MCP server shells into the installed `ovld` binary so the plugin stays aligned with the shipped CLI behavior instead of depending on this repository checkout.
31
31
 
@@ -296,72 +296,73 @@ const tools = [
296
296
  subcommand: 'deliver'
297
297
  },
298
298
  {
299
- name: 'prepare_artifact_upload',
300
- description: 'Prepare an Overlord artifact upload and return a signed upload URL.',
299
+ name: 'prepare_attachment_upload',
300
+ description: 'Prepare an objective attachment upload and return a signed upload URL.',
301
301
  inputSchema: {
302
302
  type: 'object',
303
303
  properties: {
304
304
  session_key: { type: 'string' },
305
305
  ticket_id: { type: 'string' },
306
+ objective_id: { type: 'string' },
306
307
  file_name: { type: 'string' },
307
308
  label: { type: 'string' },
308
- artifact_type: { type: 'string' },
309
309
  content_type: { type: 'string' },
310
310
  file_size: { type: 'number' },
311
311
  metadata: { type: 'object' }
312
312
  },
313
- required: ['session_key', 'ticket_id', 'file_name']
313
+ required: ['session_key', 'ticket_id', 'objective_id', 'file_name']
314
314
  },
315
315
  toCliFlags: args => ({
316
316
  'session-key': args.session_key,
317
317
  'ticket-id': args.ticket_id,
318
+ 'objective-id': args.objective_id,
318
319
  'file-name': args.file_name,
319
320
  label: args.label,
320
- 'artifact-type': args.artifact_type,
321
321
  'content-type': args.content_type,
322
322
  'file-size': args.file_size,
323
323
  'metadata-json': args.metadata
324
324
  }),
325
- subcommand: 'artifact-prepare-upload'
325
+ subcommand: 'attachment-prepare-upload'
326
326
  },
327
327
  {
328
- name: 'finalize_artifact_upload',
329
- description: 'Finalize an artifact after uploading bytes to the signed storage URL.',
328
+ name: 'finalize_attachment_upload',
329
+ description: 'Finalize an objective attachment after uploading bytes to the signed storage URL.',
330
330
  inputSchema: {
331
331
  type: 'object',
332
332
  properties: {
333
333
  session_key: { type: 'string' },
334
334
  ticket_id: { type: 'string' },
335
+ objective_id: { type: 'string' },
335
336
  storage_path: { type: 'string' },
336
337
  label: { type: 'string' },
337
- artifact_type: { type: 'string' },
338
338
  content_type: { type: 'string' },
339
339
  file_size: { type: 'number' },
340
340
  metadata: { type: 'object' }
341
341
  },
342
- required: ['session_key', 'ticket_id', 'storage_path', 'label']
342
+ required: ['session_key', 'ticket_id', 'objective_id', 'storage_path', 'label']
343
343
  },
344
344
  toCliFlags: args => ({
345
345
  'session-key': args.session_key,
346
346
  'ticket-id': args.ticket_id,
347
+ 'objective-id': args.objective_id,
347
348
  'storage-path': args.storage_path,
348
349
  label: args.label,
349
- 'artifact-type': args.artifact_type,
350
350
  'content-type': args.content_type,
351
351
  'file-size': args.file_size,
352
352
  'metadata-json': args.metadata
353
353
  }),
354
- subcommand: 'artifact-finalize-upload'
354
+ subcommand: 'attachment-finalize-upload'
355
355
  },
356
356
  {
357
- name: 'get_artifact_download_url',
358
- description: 'Create a signed download URL for an uploaded Overlord artifact.',
357
+ name: 'get_attachment_download_url',
358
+ description: 'Create a signed download URL for an uploaded objective attachment.',
359
359
  inputSchema: {
360
360
  type: 'object',
361
361
  properties: {
362
362
  session_key: { type: 'string' },
363
363
  ticket_id: { type: 'string' },
364
- artifact_id: { type: 'string' },
364
+ objective_id: { type: 'string' },
365
+ attachment_id: { type: 'string' },
365
366
  storage_path: { type: 'string' },
366
367
  expires_in: { type: 'number' }
367
368
  },
@@ -370,40 +371,41 @@ const tools = [
370
371
  toCliFlags: args => ({
371
372
  'session-key': args.session_key,
372
373
  'ticket-id': args.ticket_id,
373
- 'artifact-id': args.artifact_id,
374
+ 'objective-id': args.objective_id,
375
+ 'attachment-id': args.attachment_id,
374
376
  'storage-path': args.storage_path,
375
377
  'expires-in': args.expires_in
376
378
  }),
377
- subcommand: 'artifact-download-url'
379
+ subcommand: 'attachment-download-url'
378
380
  },
379
381
  {
380
- name: 'upload_artifact_file',
381
- description: 'Prepare, upload, and finalize a local file as an Overlord artifact in one step.',
382
+ name: 'upload_attachment_file',
383
+ description: 'Prepare, upload, and finalize a local file as an objective attachment in one step.',
382
384
  inputSchema: {
383
385
  type: 'object',
384
386
  properties: {
385
387
  session_key: { type: 'string' },
386
388
  ticket_id: { type: 'string' },
389
+ objective_id: { type: 'string' },
387
390
  file: { type: 'string' },
388
391
  file_name: { type: 'string' },
389
392
  label: { type: 'string' },
390
- artifact_type: { type: 'string' },
391
393
  content_type: { type: 'string' },
392
394
  metadata: { type: 'object' }
393
395
  },
394
- required: ['session_key', 'ticket_id', 'file']
396
+ required: ['session_key', 'ticket_id', 'objective_id', 'file']
395
397
  },
396
398
  toCliFlags: args => ({
397
399
  'session-key': args.session_key,
398
400
  'ticket-id': args.ticket_id,
401
+ 'objective-id': args.objective_id,
399
402
  file: args.file,
400
403
  'file-name': args.file_name,
401
404
  label: args.label,
402
- 'artifact-type': args.artifact_type,
403
405
  'content-type': args.content_type,
404
406
  'metadata-json': args.metadata
405
407
  }),
406
- subcommand: 'artifact-upload-file'
408
+ subcommand: 'attachment-upload-file'
407
409
  }
408
410
  ];
409
411
 
@@ -610,7 +612,7 @@ async function handleRequest(message) {
610
612
  },
611
613
  serverInfo: {
612
614
  name: 'overlord',
613
- version: '0.1.1'
615
+ version: '0.2.1'
614
616
  },
615
617
  instructions:
616
618
  'Use these tools to drive Overlord ticket workflows through the installed ovld CLI. Most operations expect a session key from attach or connect. If the CLI reports that OVERLORD_URL is unreachable, request permission escalation or network access before retrying.'
@@ -102,8 +102,8 @@ Record only meaningful behavioral changes. Skip formatting-only noise. Prefer 1-
102
102
  ```bash
103
103
  ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
104
104
  ovld protocol write-context --session-key <sessionKey> --ticket-id $TICKET_ID --key "key" --value '"json-value"'
105
- ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --file ./spec.pdf --content-type application/pdf
106
- ovld protocol artifact-download-url --session-key <sessionKey> --ticket-id $TICKET_ID --artifact-id <artifact-id>
105
+ ovld protocol attachment-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --objective-id <objective-id> --file ./spec.pdf --content-type application/pdf
106
+ ovld protocol attachment-download-url --session-key <sessionKey> --ticket-id $TICKET_ID --attachment-id <attachment-id>
107
107
  ```
108
108
 
109
109
  ## Project Discovery And Ticket Creation
@@ -140,4 +140,4 @@ When in doubt, ask yourself: *can this be done entirely inside a terminal or bro
140
140
  - When the ticket was launched by Overlord, the ticket prompt remains authoritative for the specific task objective and ticket-level constraints.
141
141
  - If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
142
142
 
143
- <!-- version: 0.2.4 -->
143
+ <!-- version: 0.2.5 -->