@aiwg/cli 2026.7.20 → 2026.7.21

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.
Files changed (53) hide show
  1. package/README.md +4 -4
  2. package/dist/src/api/index.d.ts +1 -0
  3. package/dist/src/api/index.js +1 -0
  4. package/dist/src/artifacts/browser-export.js +7 -0
  5. package/dist/src/artifacts/citation-parser.js +96 -35
  6. package/dist/src/artifacts/index-builder.js +54 -17
  7. package/dist/src/artifacts/state-transfer.js +27 -0
  8. package/dist/src/artifacts/stats.js +8 -0
  9. package/dist/src/cli/cli-extension-loader.js +73 -0
  10. package/dist/src/cli/handlers/index.js +3 -1
  11. package/dist/src/cli/handlers/sessions.js +966 -0
  12. package/dist/src/cli/handlers/skill-lint.js +49 -45
  13. package/dist/src/cli/handlers/use.js +143 -60
  14. package/dist/src/cli/handlers/utilities.js +22 -8
  15. package/dist/src/cli/skill-usage.js +146 -24
  16. package/dist/src/extensions/commands/definitions.js +29 -0
  17. package/dist/src/extensions/manifest.js +29 -0
  18. package/dist/src/sessions/adapters/claude.js +357 -0
  19. package/dist/src/sessions/adapters/codex.js +521 -0
  20. package/dist/src/sessions/adapters/copilot.js +226 -0
  21. package/dist/src/sessions/adapters/cursor.js +372 -0
  22. package/dist/src/sessions/adapters/factory.js +345 -0
  23. package/dist/src/sessions/adapters/generic.js +225 -0
  24. package/dist/src/sessions/adapters/hermes.js +341 -0
  25. package/dist/src/sessions/adapters/openclaw.js +381 -0
  26. package/dist/src/sessions/adapters/opencode.js +454 -0
  27. package/dist/src/sessions/adapters/openhuman.js +315 -0
  28. package/dist/src/sessions/adapters/warp.js +160 -0
  29. package/dist/src/sessions/adapters/windsurf.js +212 -0
  30. package/dist/src/sessions/candidates.js +210 -0
  31. package/dist/src/sessions/contracts.js +310 -0
  32. package/dist/src/sessions/discovery.js +51 -0
  33. package/dist/src/sessions/fixtures.js +12 -0
  34. package/dist/src/sessions/importer.js +315 -0
  35. package/dist/src/sessions/index.js +25 -0
  36. package/dist/src/sessions/knowledge-shard.js +61 -0
  37. package/dist/src/sessions/optional-backends.js +238 -0
  38. package/dist/src/sessions/policy.js +192 -0
  39. package/dist/src/sessions/ports.js +2 -0
  40. package/dist/src/sessions/promotion.js +367 -0
  41. package/dist/src/sessions/readers.js +176 -0
  42. package/dist/src/sessions/repository.js +1551 -0
  43. package/dist/src/skills/adapters/agent-skills.js +59 -0
  44. package/dist/src/skills/adapters/local.js +19 -1
  45. package/dist/src/skills/agent-skills.js +249 -0
  46. package/dist/src/skills/cli.js +463 -7
  47. package/dist/src/skills/deployer.js +554 -0
  48. package/dist/src/skills/doctor.js +105 -0
  49. package/dist/src/skills/exporter.js +382 -0
  50. package/dist/src/skills/importer.js +921 -0
  51. package/dist/src/skills/registry.js +19 -0
  52. package/dist/src/skills/validator.js +323 -0
  53. package/package.json +2 -2
@@ -0,0 +1,454 @@
1
+ import { z } from 'zod';
2
+ import { SessionContractError, assertSupportedSchemaMajor, } from '../contracts.js';
3
+ import { readBoundedJson, readBoundedJsonLines, streamBoundedJsonLines, } from '../readers.js';
4
+ export const OPENCODE_ADAPTER_VERSION = '1.0.0';
5
+ export const OPENCODE_EXPORT_SCHEMA_VERSION = '1.0.0';
6
+ const SessionInfoSchema = z.object({
7
+ id: z.string().min(1),
8
+ projectID: z.string().optional(),
9
+ directory: z.string().optional(),
10
+ parentID: z.string().optional(),
11
+ title: z.string().optional(),
12
+ version: z.string().optional(),
13
+ time: z.object({
14
+ created: z.number().optional(),
15
+ updated: z.number().optional(),
16
+ compacting: z.number().nullable().optional(),
17
+ archived: z.number().nullable().optional(),
18
+ }).passthrough().optional(),
19
+ share: z.object({ url: z.string().optional() }).passthrough().optional(),
20
+ }).passthrough();
21
+ const MessageInfoSchema = z.object({
22
+ id: z.string().min(1),
23
+ sessionID: z.string().min(1),
24
+ role: z.string().min(1),
25
+ parentID: z.string().optional(),
26
+ modelID: z.string().optional(),
27
+ providerID: z.string().optional(),
28
+ model: z.object({ providerID: z.string(), modelID: z.string() }).optional(),
29
+ cost: z.number().optional(),
30
+ tokens: z.record(z.unknown()).optional(),
31
+ time: z.object({ created: z.number().optional(), completed: z.number().optional() })
32
+ .passthrough().optional(),
33
+ error: z.unknown().optional(),
34
+ }).passthrough();
35
+ const PartSchema = z.object({
36
+ id: z.string().min(1),
37
+ sessionID: z.string().min(1),
38
+ messageID: z.string().min(1),
39
+ type: z.string().min(1),
40
+ text: z.string().optional(),
41
+ tool: z.string().optional(),
42
+ callID: z.string().optional(),
43
+ state: z.unknown().optional(),
44
+ mime: z.string().optional(),
45
+ filename: z.string().optional(),
46
+ url: z.string().optional(),
47
+ time: z.unknown().optional(),
48
+ }).passthrough();
49
+ const MessageSchema = z.object({
50
+ info: MessageInfoSchema,
51
+ parts: z.array(PartSchema),
52
+ }).passthrough();
53
+ const ExportSchema = z.object({
54
+ schemaVersion: z.string().optional(),
55
+ info: SessionInfoSchema,
56
+ messages: z.array(MessageSchema),
57
+ sanitized: z.boolean().optional(),
58
+ sanitization: z.record(z.unknown()).optional(),
59
+ shared: z.boolean().optional(),
60
+ providerDeletedAt: z.number().nullable().optional(),
61
+ }).passthrough();
62
+ const EventSchema = z.object({
63
+ schemaVersion: z.string().optional(),
64
+ type: z.string().min(1),
65
+ properties: z.record(z.unknown()),
66
+ sanitized: z.boolean().optional(),
67
+ shared: z.boolean().optional(),
68
+ }).passthrough();
69
+ export class OpenCodeSessionAdapter {
70
+ limits;
71
+ transports;
72
+ provider = 'opencode';
73
+ adapterVersion = OPENCODE_ADAPTER_VERSION;
74
+ disposition = 'implemented';
75
+ supportedOperations = ['inspect', 'stream'];
76
+ acquisitionModes = ['manual-export', 'api', 'jsonl'];
77
+ constructor(limits, transports = []) {
78
+ this.limits = limits;
79
+ this.transports = transports;
80
+ }
81
+ async *discover(_scope) {
82
+ // Exports and loopback API/SSE endpoints require explicit selection.
83
+ }
84
+ async inspect(source) {
85
+ const parsed = await this.readSource(source);
86
+ return {
87
+ sourceSchemaVersion: parsed.schemaVersion,
88
+ consistency: parsed.consistency,
89
+ operationalState: 'available',
90
+ };
91
+ }
92
+ async *stream(source, cursor) {
93
+ if (source.locatorClass === 'opencode-sse-jsonl') {
94
+ const input = await streamBoundedJsonLines({
95
+ selectedPath: source.locator,
96
+ allowedRoots: source.authorizedScope.allowedRoots,
97
+ }, { consistency: 'provisional', limits: this.limits });
98
+ const start = parseCursor(cursor?.value);
99
+ const sessions = new Map();
100
+ const messages = new Map();
101
+ const maxJoinStates = Math.min(this.limits?.maxRecords ?? 1_000_000, 10_000);
102
+ let schemaVersion = null;
103
+ let outputIndex = 0;
104
+ let sawSession = false;
105
+ for await (const line of input) {
106
+ const parsed = EventSchema.safeParse(line.value);
107
+ if (!parsed.success) {
108
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE event is malformed');
109
+ }
110
+ const event = parsed.data;
111
+ const currentSchema = event.schemaVersion ?? OPENCODE_EXPORT_SCHEMA_VERSION;
112
+ if (schemaVersion && schemaVersion !== currentSchema) {
113
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenCode event schemas');
114
+ }
115
+ schemaVersion = currentSchema;
116
+ assertSupportedSchemaMajor(schemaVersion);
117
+ const properties = event.properties;
118
+ const emitted = [];
119
+ if (event.type === 'session.created' || event.type === 'session.updated') {
120
+ const candidate = SessionInfoSchema.safeParse(properties.info);
121
+ if (candidate.success) {
122
+ sawSession = true;
123
+ sessions.set(candidate.data.id, candidate.data);
124
+ emitted.push(...normalizeExport({
125
+ info: candidate.data,
126
+ messages: [],
127
+ sanitized: event.sanitized !== false,
128
+ shared: event.shared === true,
129
+ }, source.locatorClass, true).records);
130
+ }
131
+ }
132
+ else if (event.type === 'message.updated') {
133
+ const candidate = MessageInfoSchema.safeParse(properties.info);
134
+ if (candidate.success) {
135
+ messages.set(candidate.data.id, candidate.data);
136
+ const session = sessions.get(candidate.data.sessionID);
137
+ if (!session) {
138
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode message event precedes its session identity');
139
+ }
140
+ const common = sessionExtensions({
141
+ info: session, messages: [], sanitized: event.sanitized !== false,
142
+ shared: event.shared === true,
143
+ }, source.locatorClass);
144
+ emitted.push(...normalizeMessage({ info: candidate.data, parts: [] }, line.sequence * 1_000 + 1, source.locatorClass, common));
145
+ }
146
+ }
147
+ else if (event.type === 'message.part.updated') {
148
+ const candidate = PartSchema.safeParse(properties.part);
149
+ if (candidate.success) {
150
+ const message = messages.get(candidate.data.messageID);
151
+ const session = sessions.get(candidate.data.sessionID);
152
+ if (!message || !session) {
153
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode part event precedes its message or session identity');
154
+ }
155
+ const common = sessionExtensions({
156
+ info: session, messages: [], sanitized: event.sanitized !== false,
157
+ shared: event.shared === true,
158
+ }, source.locatorClass);
159
+ const normalized = normalizeMessage({ info: message, parts: [candidate.data] }, line.sequence * 1_000, source.locatorClass, common);
160
+ emitted.push(...normalized.slice(1));
161
+ }
162
+ }
163
+ if (sessions.size + messages.size > maxJoinStates) {
164
+ throw new SessionContractError('RESOURCE_LIMIT_EXCEEDED', 'OpenCode SSE join state exceeds the bounded streaming limit');
165
+ }
166
+ for (const record of emitted) {
167
+ if (outputIndex++ >= start)
168
+ yield record;
169
+ }
170
+ }
171
+ if (!sawSession) {
172
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE stream lacks a session identity');
173
+ }
174
+ return;
175
+ }
176
+ const parsed = await this.readSource(source);
177
+ const start = parseCursor(cursor?.value);
178
+ for (const record of parsed.records.slice(start))
179
+ yield record;
180
+ }
181
+ async readSource(source) {
182
+ if (source.locatorClass === 'opencode-sqlite') {
183
+ throw new SessionContractError('UNSUPPORTED_OPERATION', 'direct OpenCode SQLite access is not a supported acquisition boundary; use export or API/SSE');
184
+ }
185
+ if (source.locatorClass === 'opencode-export-json') {
186
+ const { value } = await readBoundedJson({
187
+ selectedPath: source.locator,
188
+ allowedRoots: source.authorizedScope.allowedRoots,
189
+ }, this.limits);
190
+ return normalizeExport(value, source.locatorClass);
191
+ }
192
+ if (source.locatorClass === 'opencode-sse-jsonl') {
193
+ const result = await readBoundedJsonLines({
194
+ selectedPath: source.locator,
195
+ allowedRoots: source.authorizedScope.allowedRoots,
196
+ }, { consistency: 'provisional', limits: this.limits });
197
+ if (result.records.length === 0) {
198
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE source is empty');
199
+ }
200
+ return normalizeEvents(result.records, source.locatorClass);
201
+ }
202
+ const kind = source.locatorClass === 'opencode-local-api'
203
+ ? 'api'
204
+ : source.locatorClass === 'opencode-live-sse' ? 'sse' : undefined;
205
+ if (!kind)
206
+ throw new SessionContractError('UNSUPPORTED_OPERATION', 'unsupported OpenCode source class');
207
+ const operation = kind === 'api' ? 'opencode.local.sessions.read' : 'opencode.local.events.read';
208
+ if (source.authorizedScope.networkOperation !== operation) {
209
+ throw new SessionContractError('OPERATION_NOT_AUTHORIZED', `OpenCode ${kind.toUpperCase()} requires explicit ${operation} authorization`);
210
+ }
211
+ const transport = this.transports.find((candidate) => candidate.kind === kind);
212
+ if (!transport) {
213
+ throw new SessionContractError('UNSUPPORTED_OPERATION', `OpenCode ${kind.toUpperCase()} was not negotiated`);
214
+ }
215
+ const value = await transport.snapshot(source);
216
+ if (kind === 'api')
217
+ return normalizeExport(value, source.locatorClass, true);
218
+ const events = Array.isArray(value) ? value : [value];
219
+ return normalizeEvents(events.map(toRecord), source.locatorClass);
220
+ }
221
+ }
222
+ function normalizeExport(input, locatorClass, provisional = false) {
223
+ const parsed = ExportSchema.safeParse(input);
224
+ if (!parsed.success)
225
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode export is malformed');
226
+ const value = parsed.data;
227
+ const schema = value.schemaVersion ?? OPENCODE_EXPORT_SCHEMA_VERSION;
228
+ assertSupportedSchemaMajor(schema);
229
+ const common = sessionExtensions(value, locatorClass);
230
+ const header = {
231
+ nativeSessionId: value.info.id,
232
+ nativeEventId: `session:${value.info.id}`,
233
+ sequence: 0,
234
+ kind: 'opencode.session',
235
+ role: 'system',
236
+ occurredAt: timestamp(value.info.time?.created),
237
+ text: value.info.title ?? '',
238
+ rawReference: { locatorClass, sequence: 0 },
239
+ extensions: common,
240
+ };
241
+ return {
242
+ schemaVersion: OPENCODE_EXPORT_SCHEMA_VERSION,
243
+ consistency: provisional || !isClosed(value) ? 'provisional' : 'complete',
244
+ records: [
245
+ header,
246
+ ...value.messages.flatMap((message, index) => normalizeMessage(message, index * 1_000 + 1, locatorClass, common)),
247
+ ],
248
+ };
249
+ }
250
+ function normalizeEvents(input, locatorClass) {
251
+ const parsed = input.map(({ value, ...line }) => {
252
+ const result = EventSchema.safeParse(value);
253
+ if (!result.success)
254
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE event is malformed');
255
+ return { value: result.data, ...line };
256
+ });
257
+ const versions = new Set(parsed.map((item) => item.value.schemaVersion ?? OPENCODE_EXPORT_SCHEMA_VERSION));
258
+ if (versions.size !== 1)
259
+ throw new SessionContractError('SCHEMA_DRIFT', 'mixed OpenCode event schemas');
260
+ assertSupportedSchemaMajor([...versions][0]);
261
+ const sessions = new Map();
262
+ const messages = new Map();
263
+ const parts = [];
264
+ for (const event of parsed) {
265
+ const properties = event.value.properties;
266
+ if (event.value.type === 'session.created' || event.value.type === 'session.updated') {
267
+ const candidate = SessionInfoSchema.safeParse(properties.info);
268
+ if (candidate.success)
269
+ sessions.set(candidate.data.id, candidate.data);
270
+ }
271
+ else if (event.value.type === 'message.updated') {
272
+ const candidate = MessageInfoSchema.safeParse(properties.info);
273
+ if (candidate.success)
274
+ messages.set(candidate.data.id, candidate.data);
275
+ }
276
+ else if (event.value.type === 'message.part.updated') {
277
+ const candidate = PartSchema.safeParse(properties.part);
278
+ if (candidate.success)
279
+ parts.push(candidate.data);
280
+ }
281
+ }
282
+ if (sessions.size === 0) {
283
+ throw new SessionContractError('MALFORMED_SOURCE', 'OpenCode SSE stream lacks a session identity');
284
+ }
285
+ const records = [];
286
+ for (const session of sessions.values()) {
287
+ const exportValue = {
288
+ info: session,
289
+ messages: [...messages.values()]
290
+ .filter((message) => message.sessionID === session.id)
291
+ .map((info) => ({ info, parts: parts.filter((part) => part.messageID === info.id) })),
292
+ sanitized: parsed.every((item) => item.value.sanitized !== false),
293
+ shared: parsed.some((item) => item.value.shared === true),
294
+ };
295
+ const normalized = normalizeExport(exportValue, locatorClass, true);
296
+ records.push(...normalized.records);
297
+ }
298
+ return { schemaVersion: OPENCODE_EXPORT_SCHEMA_VERSION, consistency: 'provisional', records };
299
+ }
300
+ function normalizeMessage(message, sequence, locatorClass, common) {
301
+ const header = {
302
+ nativeSessionId: message.info.sessionID,
303
+ nativeEventId: `message:${message.info.id}`,
304
+ sequence,
305
+ kind: 'message',
306
+ role: message.info.role,
307
+ participant: message.info.role,
308
+ model: message.info.model?.modelID ?? message.info.modelID,
309
+ occurredAt: timestamp(message.info.time?.created),
310
+ text: '',
311
+ rawReference: { locatorClass, sequence },
312
+ extensions: {
313
+ ...common,
314
+ parentMessageId: message.info.parentID,
315
+ model: message.info.model ?? {
316
+ providerID: message.info.providerID,
317
+ modelID: message.info.modelID,
318
+ },
319
+ usage: { cost: message.info.cost, tokens: message.info.tokens },
320
+ error: message.info.error,
321
+ messageUnknownFields: unknownFields(message.info, MESSAGE_KEYS),
322
+ },
323
+ };
324
+ return [
325
+ header,
326
+ ...message.parts.map((part, index) => ({
327
+ nativeSessionId: part.sessionID,
328
+ nativeEventId: `part:${part.id}`,
329
+ sequence: sequence + index + 1,
330
+ kind: partKind(part),
331
+ role: message.info.role,
332
+ participant: message.info.role,
333
+ toolName: part.tool,
334
+ toolCallId: part.callID,
335
+ model: message.info.model?.modelID ?? message.info.modelID,
336
+ occurredAt: timestamp(asObject(part.time).start),
337
+ text: partText(part),
338
+ rawReference: { locatorClass, sequence: sequence + index + 1 },
339
+ extensions: {
340
+ ...common,
341
+ messageId: part.messageID,
342
+ tool: part.tool,
343
+ toolCallId: part.callID,
344
+ toolState: part.state,
345
+ attachment: part.type === 'file'
346
+ ? { mime: part.mime, filename: part.filename, urlPresent: Boolean(part.url) }
347
+ : undefined,
348
+ opaqueContent: !['text', 'reasoning', 'tool', 'file'].includes(part.type),
349
+ unknownFields: unknownFields(part, PART_KEYS),
350
+ },
351
+ })),
352
+ ];
353
+ }
354
+ function sessionExtensions(value, locatorClass) {
355
+ return {
356
+ lifecycle: value.providerDeletedAt
357
+ ? 'deleted'
358
+ : value.info.time?.archived ? 'archived' : isClosed(value) ? 'complete' : 'active',
359
+ workspace: {
360
+ projectId: value.info.projectID,
361
+ directoryClass: value.info.directory ? '<workspace>' : undefined,
362
+ },
363
+ lineage: { parentSessionId: value.info.parentID },
364
+ sharing: {
365
+ shared: value.shared ?? Boolean(value.info.share?.url),
366
+ publicUrlPresent: Boolean(value.info.share?.url),
367
+ unshareRequiredForProviderDeletion: Boolean(value.shared ?? value.info.share?.url),
368
+ },
369
+ sanitization: {
370
+ sanitized: value.sanitized === true,
371
+ evidence: value.sanitization,
372
+ exportMayOmitSensitiveNativeFields: true,
373
+ },
374
+ provenance: {
375
+ acquisition: locatorClass,
376
+ productVersion: value.info.version ?? 'not-reported',
377
+ directSqlite: false,
378
+ },
379
+ deletion: {
380
+ providerDeletedAt: timestamp(value.providerDeletedAt),
381
+ aiwgDeletionDoesNotUnshare: true,
382
+ aiwgDeletionDoesNotDeleteProviderSession: true,
383
+ },
384
+ sessionUnknownFields: {
385
+ ...unknownFields(value.info, SESSION_INFO_KEYS),
386
+ ...unknownFields(value, EXPORT_KEYS),
387
+ },
388
+ };
389
+ }
390
+ function isClosed(value) {
391
+ return Boolean(value.info.time?.archived || value.providerDeletedAt
392
+ || value.messages.some((message) => message.info.time?.completed));
393
+ }
394
+ function partKind(part) {
395
+ if (part.type === 'tool') {
396
+ const status = String(asObject(part.state).status ?? '');
397
+ return status === 'completed' || status === 'error' ? 'tool-result' : 'tool-call';
398
+ }
399
+ if (part.type === 'text')
400
+ return 'message-part';
401
+ if (part.type === 'reasoning')
402
+ return 'reasoning';
403
+ if (part.type === 'file')
404
+ return 'attachment';
405
+ return `opencode.${part.type}`;
406
+ }
407
+ function partText(part) {
408
+ if (part.text)
409
+ return part.text;
410
+ if (part.type === 'tool') {
411
+ const state = asObject(part.state);
412
+ return typeof state.output === 'string' ? state.output : part.tool ?? '';
413
+ }
414
+ return '';
415
+ }
416
+ function toRecord(value, sequence) {
417
+ return { value, sequence, byteOffset: sequence, byteLength: Buffer.byteLength(JSON.stringify(value)) };
418
+ }
419
+ function timestamp(value) {
420
+ if (typeof value !== 'number')
421
+ return undefined;
422
+ const date = new Date(value < 10_000_000_000 ? value * 1_000 : value);
423
+ return Number.isNaN(date.getTime()) ? undefined : date.toISOString();
424
+ }
425
+ function asObject(value) {
426
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
427
+ ? value : {};
428
+ }
429
+ function unknownFields(value, keys) {
430
+ return Object.fromEntries(Object.entries(value).filter(([key]) => !keys.has(key)).sort(([a], [b]) => a.localeCompare(b)));
431
+ }
432
+ function parseCursor(value) {
433
+ if (!value)
434
+ return 0;
435
+ if (!/^\d+$/.test(value))
436
+ throw new SessionContractError('SCHEMA_DRIFT', 'invalid OpenCode cursor');
437
+ return Number(value);
438
+ }
439
+ const SESSION_INFO_KEYS = new Set([
440
+ 'id', 'projectID', 'directory', 'parentID', 'title', 'version', 'time', 'share',
441
+ ]);
442
+ const MESSAGE_KEYS = new Set([
443
+ 'id', 'sessionID', 'role', 'parentID', 'modelID', 'providerID', 'model', 'cost',
444
+ 'tokens', 'time', 'error',
445
+ ]);
446
+ const PART_KEYS = new Set([
447
+ 'id', 'sessionID', 'messageID', 'type', 'text', 'tool', 'callID', 'state',
448
+ 'mime', 'filename', 'url', 'time',
449
+ ]);
450
+ const EXPORT_KEYS = new Set([
451
+ 'schemaVersion', 'info', 'messages', 'sanitized', 'sanitization', 'shared',
452
+ 'providerDeletedAt',
453
+ ]);
454
+ //# sourceMappingURL=opencode.js.map