@aiwg/cli 2026.7.21 → 2026.7.23

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
@@ -10,10 +10,15 @@ AIWG skills and agents use this CLI to perform common operations with
10
10
  predictable, structured calls instead of spending context on shell discovery,
11
11
  filesystem traversal, command reconstruction, and repeated tool output.
12
12
 
13
- ```bash
14
- npm install --global @aiwg/cli
13
+ New to AIWG? Install the full `aiwg` package and let the agentic installer
14
+ connect the complete system. `@aiwg/cli` is the smaller execution layer for
15
+ agents, CI, and web-backed installations.
15
16
 
16
- aiwg doctor
17
+ ```text
18
+ Install or repair AIWG for this project by following
19
+ https://raw.githubusercontent.com/jmagly/aiwg/main/setup.aiwg.yaml
20
+ Explain the plan before changing anything, preserve my existing work, and ask
21
+ me only for choices you cannot safely determine.
17
22
  ```
18
23
 
19
24
  [![npm version](https://img.shields.io/npm/v/%40aiwg%2Fcli/latest?label=%40aiwg%2Fcli&color=CB3837&logo=npm&style=flat-square)](https://www.npmjs.com/package/@aiwg/cli)
@@ -209,6 +214,12 @@ execution layer and can obtain resources from the signed web release. Choose
209
214
  `aiwg` when local authoring, the full bundled corpus, or completely cold
210
215
  offline operation is required.
211
216
 
217
+ For a first installation, an uncertain environment, or a machine with an old
218
+ or broken AIWG setup, use the full `aiwg` package and the
219
+ [agentic installer manifest](https://raw.githubusercontent.com/jmagly/aiwg/main/setup.aiwg.yaml).
220
+ The flow detects development checkouts and preserves development mode unless
221
+ the user explicitly approves switching to the published package.
222
+
212
223
  Both CLI packages expose the same `aiwg` executable name. Install one globally
213
224
  at a time unless you deliberately manage separate npm prefixes.
214
225
 
@@ -8,4 +8,5 @@
8
8
  export { run } from '../cli/router.js';
9
9
  export * from '../resources/index.js';
10
10
  export * from '../sessions/index.js';
11
+ export * from '../security/threat-assessment-config.js';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -8,4 +8,5 @@
8
8
  export { run } from '../cli/router.js';
9
9
  export * from '../resources/index.js';
10
10
  export * from '../sessions/index.js';
11
+ export * from '../security/threat-assessment-config.js';
11
12
  //# sourceMappingURL=index.js.map
@@ -1,16 +1,20 @@
1
- import { mkdirSync } from 'node:fs';
2
- import { dirname, resolve } from 'node:path';
3
- import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, redactSourceLocator, } from '../../sessions/index.js';
1
+ import { existsSync, mkdirSync, realpathSync, statSync, } from 'node:fs';
2
+ import { dirname, isAbsolute, resolve, } from 'node:path';
3
+ import { CLAUDE_ADAPTER_VERSION, ClaudeSessionAdapter, CODEX_ADAPTER_VERSION, CodexSessionAdapter, COPILOT_ADAPTER_VERSION, CopilotSessionAdapter, CURSOR_ADAPTER_VERSION, CursorSessionAdapter, FACTORY_ADAPTER_VERSION, FactorySessionAdapter, HERMES_ADAPTER_VERSION, HermesSessionAdapter, OPENCODE_ADAPTER_VERSION, OpenCodeSessionAdapter, OPENCLAW_ADAPTER_VERSION, OpenClawSessionAdapter, OPENHUMAN_ADAPTER_VERSION, OpenHumanSessionAdapter, WARP_ADAPTER_VERSION, WarpSessionAdapter, DEVIN_DESKTOP_ADAPTER_VERSION, DevinDesktopSessionAdapter, CandidateExtractionService, GENERIC_ADAPTER_VERSION, GenericSessionInterchangeAdapter, IncrementalSessionImporter, ImportLeaseContentionError, FilesystemMemoryDestination, FilesystemPromotionDispositionCoordinator, MemoryPromotionGateway, SESSION_CONTRACT_VERSION, SESSION_PROVIDER_IDS, SessionContractError, SessionRepository, SessionSourceSchema, StructuralCandidateExtractor, resolveMemoryConsumerManifest, assertSessionProviderId, acquireImportLease, defaultDiscoveryManifestPath, discoverWorkspaceHistories, deriveSessionTimeline, importDiscoveryManifest, previewDiscoveryImport, publicDiscoveryManifest, readDiscoveryManifest, redactSourceLocator, sha256, parseTimelineGap, writeDiscoveryManifest, } from '../../sessions/index.js';
4
4
  const JSON_CONTRACT_VERSION = '1.0.0';
5
5
  const EXIT = {
6
6
  ok: 0, usage: 2, unsupported: 3, unavailable: 4, contract: 5, storage: 6,
7
+ locked: 7, coverage: 8,
7
8
  };
8
9
  const HELP = `Usage: aiwg sessions <command> [options]
9
10
 
10
11
  Commands:
11
12
  sources Show all canonical provider dispositions
13
+ discover --workspace <path> Inventory authorized workspace histories
14
+ import-discovered --workspace <path> Import the exact saved manifest
12
15
  import <file> --source-id <id> Import a supported provider JSONL source
13
16
  list [--limit N] [--cursor N] List normalized sessions
17
+ timeline [--gap 30m] Report chronological activity segments
14
18
  show <session-id> Show a session with events and tags
15
19
  search <query> --workspace <id> Search authorized normalized content
16
20
  extract [session-id] --workspace <id> Extract structural candidates
@@ -30,6 +34,13 @@ Options:
30
34
  --json Emit the versioned JSON contract
31
35
  --dry-run Preview a mutation without changing state
32
36
  --db <path> Override .aiwg/sessions/catalog.sqlite
37
+ --manifest <path> Override the discovery manifest path
38
+ --provider-home <path> Override the provider home root (testing/portable homes)
39
+ --codex-root <path> Explicitly authorize a shared Codex sessions/export root
40
+ --confirm, --yes Confirm a persistent discovered batch import
41
+ --lock-wait-ms <n> Maximum import-lease wait (default 5000)
42
+ --inactivity-threshold <duration> Historical inactivity threshold (default 24h)
43
+ --min-coverage <0..1> Fail list/report commands below a coverage ratio
33
44
  --provider <id> Filter list or select an import adapter
34
45
  --consumer <id> Select a named memory consumer for promotion
35
46
  --workspace <id>, --tag <tag>, --limit <n>, --cursor <n>
@@ -90,11 +101,15 @@ async function executeCommand(ctx, args) {
90
101
  }
91
102
  if (command === 'import')
92
103
  return importSource(ctx, args);
104
+ if (command === 'discover')
105
+ return discoverWorkspace(ctx, args);
106
+ if (command === 'import-discovered')
107
+ return importDiscovered(ctx, args);
93
108
  const repository = openRepository(ctx, args);
94
109
  try {
95
110
  switch (command) {
96
111
  case 'list': {
97
- const { workspaceId } = authorizationContext(args, command);
112
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
98
113
  const limit = boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit');
99
114
  const cursor = args.values.get('--cursor');
100
115
  const providerInput = args.values.get('--provider');
@@ -106,7 +121,8 @@ async function executeCommand(ctx, args) {
106
121
  limit,
107
122
  cursor,
108
123
  });
109
- return ok(command, {
124
+ const coverage = repository.getCoverage(workspaceId);
125
+ const response = {
110
126
  items: result.items,
111
127
  page: {
112
128
  limit,
@@ -115,11 +131,56 @@ async function executeCommand(ctx, args) {
115
131
  snapshotRowid: result.snapshotRowid,
116
132
  total: result.total,
117
133
  },
118
- });
134
+ coverage,
135
+ };
136
+ const minimum = args.values.has('--min-coverage')
137
+ ? boundedNumber(args.values.get('--min-coverage'), 0, 0, 1, '--min-coverage')
138
+ : null;
139
+ if (minimum !== null
140
+ && (coverage.coverageRatio === null || coverage.coverageRatio < minimum)) {
141
+ return {
142
+ envelope: envelope(`sessions.${command}`, 'error', response, {
143
+ code: 'COVERAGE_BELOW_THRESHOLD',
144
+ message: `session coverage is below the requested threshold ${minimum}`,
145
+ details: coverage,
146
+ }),
147
+ exitCode: EXIT.coverage,
148
+ };
149
+ }
150
+ return ok(command, response);
151
+ }
152
+ case 'timeline': {
153
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
154
+ const gapMs = timelineGap(args.values.get('--gap'));
155
+ const items = deriveSessionTimeline(repository.listTimelineInputs(workspaceId), gapMs);
156
+ const coverage = repository.getCoverage(workspaceId);
157
+ const response = {
158
+ schemaVersion: '1.0.0',
159
+ workspaceId,
160
+ gapMs,
161
+ items,
162
+ count: items.length,
163
+ coverage,
164
+ };
165
+ const minimum = args.values.has('--min-coverage')
166
+ ? boundedNumber(args.values.get('--min-coverage'), 0, 0, 1, '--min-coverage')
167
+ : null;
168
+ if (minimum !== null
169
+ && (coverage.coverageRatio === null || coverage.coverageRatio < minimum)) {
170
+ return {
171
+ envelope: envelope(`sessions.${command}`, 'error', response, {
172
+ code: 'COVERAGE_BELOW_THRESHOLD',
173
+ message: `session coverage is below the requested threshold ${minimum}`,
174
+ details: coverage,
175
+ }),
176
+ exitCode: EXIT.coverage,
177
+ };
178
+ }
179
+ return ok(command, response);
119
180
  }
120
181
  case 'show': {
121
182
  const id = requiredPositional(args, 0, 'session-id');
122
- const { workspaceId } = authorizationContext(args, command);
183
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
123
184
  const session = repository.getSession(id, workspaceId);
124
185
  if (!session)
125
186
  throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
@@ -131,7 +192,7 @@ async function executeCommand(ctx, args) {
131
192
  }
132
193
  case 'search': {
133
194
  const query = requiredPositional(args, 0, 'query');
134
- const { workspaceId } = authorizationContext(args, command);
195
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
135
196
  const providerInput = args.values.get('--provider');
136
197
  const provider = providerInput ? assertSessionProviderId(providerInput) : undefined;
137
198
  const result = repository.search({
@@ -148,6 +209,7 @@ async function executeCommand(ctx, args) {
148
209
  entity: args.values.get('--entity'),
149
210
  sensitivity: args.values.get('--sensitivity'),
150
211
  extractionState: args.values.get('--extraction-state'),
212
+ controlEvents: controlEventMode(args.values.get('--control-events')),
151
213
  limit: boundedInteger(args.values.get('--limit'), 50, 1, 500, '--limit'),
152
214
  cursor: args.values.get('--cursor'),
153
215
  });
@@ -158,7 +220,7 @@ async function executeCommand(ctx, args) {
158
220
  });
159
221
  }
160
222
  case 'extract': {
161
- const { workspaceId } = authorizationContext(args, command);
223
+ const { workspaceId } = authorizationContext(ctx, args, command);
162
224
  const sessionId = args.positionals[0];
163
225
  const pageSize = boundedInteger(args.values.get('--page-size'), 250, 1, 500, '--page-size');
164
226
  const maxDocuments = args.values.has('--max-documents')
@@ -217,14 +279,14 @@ async function executeCommand(ctx, args) {
217
279
  : ok(command, { items, count: items.length, durableMemoryWrites: 0, scan });
218
280
  }
219
281
  case 'candidates': {
220
- const { workspaceId } = authorizationContext(args, command);
282
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
221
283
  const state = candidateState(args.values.get('--state'));
222
284
  return ok(command, {
223
285
  items: repository.listCandidates(state, workspaceId),
224
286
  });
225
287
  }
226
288
  case 'review': {
227
- const { workspaceId } = authorizationContext(args, command);
289
+ const { workspaceId } = authorizationContext(ctx, args, command);
228
290
  const candidateId = requiredPositional(args, 0, 'candidate-id');
229
291
  const version = boundedInteger(requiredPositional(args, 1, 'version'), 1, 1, Number.MAX_SAFE_INTEGER, 'version');
230
292
  const toState = candidateState(requiredPositional(args, 2, 'state'));
@@ -249,7 +311,7 @@ async function executeCommand(ctx, args) {
249
311
  }));
250
312
  }
251
313
  case 'promote': {
252
- const { workspaceId } = authorizationContext(args, command);
314
+ const { workspaceId } = authorizationContext(ctx, args, command);
253
315
  const candidateId = requiredPositional(args, 0, 'candidate-id');
254
316
  const version = boundedInteger(requiredPositional(args, 1, 'version'), 1, 1, Number.MAX_SAFE_INTEGER, 'version');
255
317
  const consumer = requiredValue(args, '--consumer');
@@ -286,7 +348,7 @@ async function executeCommand(ctx, args) {
286
348
  }
287
349
  case 'tag': {
288
350
  const id = requiredPositional(args, 0, 'session-id');
289
- const { workspaceId } = authorizationContext(args, command);
351
+ const { workspaceId } = authorizationContext(ctx, args, command);
290
352
  const tag = requiredPositional(args, 1, 'tag');
291
353
  if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(tag)) {
292
354
  throw new CliError('INVALID_TAG', 'tag must be 1-64 safe identifier characters', EXIT.usage);
@@ -302,7 +364,7 @@ async function executeCommand(ctx, args) {
302
364
  }
303
365
  case 'relocate': {
304
366
  const sourceId = requiredPositional(args, 0, 'source-id');
305
- const { workspaceId } = authorizationContext(args, command);
367
+ const { workspaceId } = authorizationContext(ctx, args, command);
306
368
  const locator = requiredPositional(args, 1, 'file');
307
369
  if (!repository.getSource(sourceId, workspaceId)) {
308
370
  throw new CliError('SOURCE_NOT_FOUND', `source not found: ${sourceId}`, EXIT.unavailable);
@@ -314,14 +376,14 @@ async function executeCommand(ctx, args) {
314
376
  return ok(command, { sourceId, redactedLocator });
315
377
  }
316
378
  case 'reindex': {
317
- const { workspaceId } = authorizationContext(args, command);
379
+ const { workspaceId } = authorizationContext(ctx, args, command);
318
380
  if (isDryRun(ctx, args))
319
381
  return preview(command, { operation: 'reindex' });
320
382
  repository.reindex(workspaceId);
321
383
  return ok(command, { operation: 'reindex' });
322
384
  }
323
385
  case 'audit': {
324
- const { workspaceId } = authorizationContext(args, command);
386
+ const { workspaceId } = readAuthorizationContext(ctx, args, command, repository);
325
387
  const limit = boundedInteger(args.values.get('--limit'), 100, 1, 500, '--limit');
326
388
  const cursor = args.values.get('--cursor');
327
389
  if (args.flags.has('--otel')) {
@@ -339,7 +401,7 @@ async function executeCommand(ctx, args) {
339
401
  }
340
402
  case 'delete': {
341
403
  const id = requiredPositional(args, 0, 'session-id');
342
- const { workspaceId } = authorizationContext(args, command);
404
+ const { workspaceId } = authorizationContext(ctx, args, command);
343
405
  const counts = repository.deletionPreview(id, workspaceId);
344
406
  if (counts.sessions === 0) {
345
407
  throw new CliError('SESSION_NOT_FOUND', `session not found: ${id}`, EXIT.unavailable);
@@ -357,7 +419,7 @@ async function executeCommand(ctx, args) {
357
419
  }
358
420
  case 'restore': {
359
421
  const id = requiredPositional(args, 0, 'session-id');
360
- const { workspaceId } = authorizationContext(args, command);
422
+ const { workspaceId } = authorizationContext(ctx, args, command);
361
423
  if (isDryRun(ctx, args))
362
424
  return preview(command, { sessionId: id, wouldRestore: true });
363
425
  if (!repository.restoreSession(id, workspaceId)) {
@@ -367,7 +429,7 @@ async function executeCommand(ctx, args) {
367
429
  }
368
430
  case 'purge': {
369
431
  const id = requiredPositional(args, 0, 'session-id');
370
- const { workspaceId } = authorizationContext(args, command);
432
+ const { workspaceId } = authorizationContext(ctx, args, command);
371
433
  const completed = repository.getCompletedPurge(id, workspaceId);
372
434
  if (completed) {
373
435
  return ok(command, {
@@ -455,7 +517,9 @@ async function importSource(ctx, args) {
455
517
  throw new CliError('UNSUPPORTED_OPERATION', `session import is not implemented for ${provider}`, EXIT.unsupported);
456
518
  }
457
519
  const sourceId = requiredValue(args, '--source-id');
458
- const workspaceId = args.values.get('--workspace') ?? 'default';
520
+ const workspaceId = args.values.has('--workspace')
521
+ ? normalizeWorkspaceId(ctx.cwd, args.values.get('--workspace'))
522
+ : 'default';
459
523
  const isClaude = provider === 'claude';
460
524
  const isCodex = provider === 'codex';
461
525
  const isCopilot = provider === 'copilot';
@@ -605,22 +669,106 @@ async function importSource(ctx, args) {
605
669
  if (isDryRun(ctx, args)) {
606
670
  return preview('import', { source, wouldInspect: true, wouldPersist: false });
607
671
  }
608
- const repository = openRepository(ctx, args);
672
+ const lease = await acquireImportLease(databasePath(ctx, args), sha256(['single-source-import', sourceId, workspaceId].join('\0')), {
673
+ waitMs: boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms'),
674
+ });
609
675
  try {
610
- const receipts = await new IncrementalSessionImporter(repository).import({
611
- source, selectedSource, adapter, workspaceId, policyVersion: '1.0.0',
612
- });
613
- return ok('import', {
614
- sourceId,
615
- receipts,
616
- totals: {
617
- sessionsInserted: receipts.reduce((sum, item) => sum + item.sessionsInserted, 0),
618
- eventsInserted: receipts.reduce((sum, item) => sum + item.eventsInserted, 0),
619
- },
676
+ const repository = openRepository(ctx, args);
677
+ try {
678
+ const receipts = await new IncrementalSessionImporter(repository).import({
679
+ source, selectedSource, adapter, workspaceId, policyVersion: '1.0.0',
680
+ inactivityThresholdMs: inactivityThreshold(args.values.get('--inactivity-threshold')),
681
+ });
682
+ return ok('import', {
683
+ sourceId,
684
+ receipts,
685
+ totals: {
686
+ sessionsInserted: receipts.reduce((sum, item) => sum + item.sessionsInserted, 0),
687
+ eventsInserted: receipts.reduce((sum, item) => sum + item.eventsInserted, 0),
688
+ },
689
+ });
690
+ }
691
+ finally {
692
+ repository.close();
693
+ }
694
+ }
695
+ finally {
696
+ await lease.release();
697
+ }
698
+ }
699
+ async function discoverWorkspace(ctx, args) {
700
+ const workspace = resolve(ctx.cwd, requiredValue(args, '--workspace'));
701
+ const manifest = await discoverWorkspaceHistories({
702
+ workspace,
703
+ providerHome: args.values.has('--provider-home')
704
+ ? resolve(ctx.cwd, args.values.get('--provider-home'))
705
+ : undefined,
706
+ codexRoot: args.values.has('--codex-root')
707
+ ? resolve(ctx.cwd, args.values.get('--codex-root'))
708
+ : undefined,
709
+ });
710
+ const manifestPath = resolve(ctx.cwd, args.values.get('--manifest') ?? defaultDiscoveryManifestPath(manifest.workspacePath));
711
+ const data = {
712
+ manifest: publicDiscoveryManifest(manifest),
713
+ manifestPath: redactSourceLocator(manifestPath),
714
+ exactManifestRequiredForImport: true,
715
+ };
716
+ if (isDryRun(ctx, args)) {
717
+ return preview('discover', { ...data, wouldPersistManifest: false });
718
+ }
719
+ await writeDiscoveryManifest(manifestPath, manifest);
720
+ return ok('discover', { ...data, manifestPersisted: true });
721
+ }
722
+ async function importDiscovered(ctx, args) {
723
+ const workspace = resolve(ctx.cwd, requiredValue(args, '--workspace'));
724
+ const canonicalWorkspace = realpathSync(workspace);
725
+ const manifestPath = resolve(ctx.cwd, args.values.get('--manifest') ?? defaultDiscoveryManifestPath(canonicalWorkspace));
726
+ const manifest = await readDiscoveryManifest(manifestPath);
727
+ if (manifest.workspaceId !== canonicalWorkspace) {
728
+ throw new CliError('MANIFEST_WORKSPACE_MISMATCH', 'discovery manifest does not belong to the explicitly authorized workspace', EXIT.contract);
729
+ }
730
+ const confirmed = args.flags.has('--confirm') || args.flags.has('--yes');
731
+ if (isDryRun(ctx, args) || !confirmed) {
732
+ let existing = null;
733
+ try {
734
+ const repository = openRepository(ctx, args);
735
+ existing = repository.getBatchImportRunForManifest(manifest.manifestId, manifest.workspaceId);
736
+ repository.close();
737
+ }
738
+ catch {
739
+ // A preview can still describe the exact manifest before a catalog exists.
740
+ }
741
+ return preview('import-discovered', {
742
+ manifest: publicDiscoveryManifest(manifest),
743
+ receipt: previewDiscoveryImport(manifest, existing),
744
+ confirmationRequired: !isDryRun(ctx, args),
745
+ wouldPersist: false,
620
746
  });
621
747
  }
748
+ const lease = await acquireImportLease(databasePath(ctx, args), sha256(['discovered-import', manifest.manifestId, manifest.workspaceId].join('\0')), {
749
+ waitMs: boundedInteger(args.values.get('--lock-wait-ms'), 5_000, 0, 300_000, '--lock-wait-ms'),
750
+ });
751
+ try {
752
+ const repository = openRepository(ctx, args);
753
+ try {
754
+ const receipt = await importDiscoveryManifest({
755
+ manifest,
756
+ repository,
757
+ signal: ctx.signal,
758
+ inactivityThresholdMs: inactivityThreshold(args.values.get('--inactivity-threshold')),
759
+ });
760
+ return ok('import-discovered', {
761
+ manifestId: manifest.manifestId,
762
+ receipt,
763
+ resumable: receipt.run.status !== 'complete',
764
+ });
765
+ }
766
+ finally {
767
+ repository.close();
768
+ }
769
+ }
622
770
  finally {
623
- repository.close();
771
+ await lease.release();
624
772
  }
625
773
  }
626
774
  function providerDisposition(provider) {
@@ -669,10 +817,10 @@ function providerDisposition(provider) {
669
817
  if (provider === 'cursor') {
670
818
  return {
671
819
  provider, disposition: 'implemented', operationalState: 'available',
672
- supportedOperations: ['inspect', 'stream'],
820
+ supportedOperations: ['discover', 'inspect', 'stream'],
673
821
  acquisitionModes: ['api', 'jsonl', 'manual-export'],
674
822
  reasonCode: null,
675
- remediation: 'Import Cursor CLI stream-json, captured Cloud Agent v1 events, or an editor Markdown export.',
823
+ remediation: 'Authorize a workspace agent-transcripts root, or import Cursor CLI/Cloud Agent exports explicitly.',
676
824
  evidence: {
677
825
  adapterVersion: CURSOR_ADAPTER_VERSION,
678
826
  verifiedAt: '2026-07-27',
@@ -804,6 +952,8 @@ function cursorLocatorClass(input) {
804
952
  return 'cursor-editor-markdown';
805
953
  if (input.endsWith('.cloud.jsonl'))
806
954
  return 'cursor-cloud-events-jsonl';
955
+ if (input.split(/[\\/]+/).includes('agent-transcripts'))
956
+ return 'cursor-agent-transcript-jsonl';
807
957
  return 'cursor-cli-stream-json';
808
958
  }
809
959
  function cursorProviderProfile(locatorClass) {
@@ -811,6 +961,8 @@ function cursorProviderProfile(locatorClass) {
811
961
  return 'editor-markdown-lossy';
812
962
  if (locatorClass === 'cursor-cloud-events-jsonl')
813
963
  return 'cloud-agents-api-v1';
964
+ if (locatorClass === 'cursor-agent-transcript-jsonl')
965
+ return 'agent-transcript-jsonl';
814
966
  return 'cli-stream-json';
815
967
  }
816
968
  function openRepository(ctx, args) {
@@ -820,11 +972,17 @@ function openRepository(ctx, args) {
820
972
  return new SessionRepository(path);
821
973
  }
822
974
  catch (error) {
823
- throw new CliError('CATALOG_UNAVAILABLE', error instanceof Error ? error.message : String(error), EXIT.storage);
975
+ const message = error instanceof Error ? error.message : String(error);
976
+ if (/SQLITE_BUSY|database is locked/i.test(message)) {
977
+ throw new CliError('IMPORT_LOCKED', 'the session catalog is busy; retry after the active writer finishes', EXIT.locked, { owner: null, waitedMs: 0 });
978
+ }
979
+ throw new CliError('CATALOG_UNAVAILABLE', message, EXIT.storage);
824
980
  }
825
981
  }
826
982
  function databasePath(ctx, args) {
827
- return resolve(ctx.cwd, args.values.get('--db') ?? '.aiwg/sessions/catalog.sqlite');
983
+ if (args.values.has('--db'))
984
+ return resolve(ctx.cwd, args.values.get('--db'));
985
+ return resolve(projectRootCandidate(ctx.cwd) ?? ctx.cwd, '.aiwg/sessions/catalog.sqlite');
828
986
  }
829
987
  function parseArgs(argv) {
830
988
  const flags = new Set();
@@ -836,6 +994,9 @@ function parseArgs(argv) {
836
994
  '--entity', '--sensitivity', '--extraction-state', '--page-size', '--max-documents',
837
995
  '--state', '--reviewer', '--reason', '--policy-version', '--min-confidence',
838
996
  '--consumer', '--actor-class', '--reason-code', '--dependent-action', '--basis',
997
+ '--manifest', '--provider-home', '--codex-root', '--lock-wait-ms', '--min-coverage', '--gap',
998
+ '--inactivity-threshold',
999
+ '--control-events',
839
1000
  ]);
840
1001
  let command;
841
1002
  for (let index = 0; index < argv.length; index += 1) {
@@ -872,15 +1033,94 @@ function requiredValue(args, flag) {
872
1033
  throw new CliError('MISSING_ARGUMENT', `missing required ${flag}`, EXIT.usage);
873
1034
  return value;
874
1035
  }
875
- function authorizationContext(args, operation) {
1036
+ function authorizationContext(ctx, args, operation) {
876
1037
  return {
877
1038
  actorId: 'local-catalog-owner',
878
- workspaceId: requiredValue(args, '--workspace'),
1039
+ workspaceId: normalizeWorkspaceId(ctx.cwd, requiredValue(args, '--workspace')),
879
1040
  operation,
880
1041
  catalogScope: 'workspace',
881
1042
  mode: 'local-owner',
882
1043
  };
883
1044
  }
1045
+ function readAuthorizationContext(ctx, args, operation, repository) {
1046
+ const explicit = args.values.get('--workspace');
1047
+ if (explicit) {
1048
+ return {
1049
+ actorId: 'local-catalog-owner',
1050
+ workspaceId: normalizeWorkspaceId(ctx.cwd, explicit),
1051
+ operation,
1052
+ catalogScope: 'workspace',
1053
+ mode: 'local-owner',
1054
+ };
1055
+ }
1056
+ const project = projectRootCandidate(ctx.cwd);
1057
+ if (project) {
1058
+ return {
1059
+ actorId: 'local-catalog-owner',
1060
+ workspaceId: project,
1061
+ operation,
1062
+ catalogScope: 'workspace',
1063
+ mode: 'local-owner',
1064
+ };
1065
+ }
1066
+ const candidates = repository.listWorkspaceIds();
1067
+ if (candidates.length === 1) {
1068
+ return {
1069
+ actorId: 'local-catalog-owner',
1070
+ workspaceId: candidates[0],
1071
+ operation,
1072
+ catalogScope: 'workspace',
1073
+ mode: 'local-owner',
1074
+ };
1075
+ }
1076
+ if (candidates.length > 1) {
1077
+ throw new CliError('WORKSPACE_AMBIGUOUS', 'current workspace cannot be inferred safely; pass --workspace explicitly', EXIT.usage, {
1078
+ candidates,
1079
+ example: `aiwg sessions ${operation} --workspace ${JSON.stringify(candidates[0])}`,
1080
+ });
1081
+ }
1082
+ throw new CliError('WORKSPACE_REQUIRED', 'current workspace cannot be inferred; pass --workspace explicitly', EXIT.usage, {
1083
+ candidates: [],
1084
+ example: `aiwg sessions ${operation} --workspace <path-or-id>`,
1085
+ });
1086
+ }
1087
+ function projectRootCandidate(start) {
1088
+ let current;
1089
+ try {
1090
+ const resolved = realpathSync(resolve(start));
1091
+ current = statSync(resolved).isDirectory() ? resolved : dirname(resolved);
1092
+ }
1093
+ catch {
1094
+ current = resolve(start);
1095
+ }
1096
+ let gitRoot = null;
1097
+ while (true) {
1098
+ if (existsSync(resolve(current, '.aiwg', 'aiwg.config')))
1099
+ return current;
1100
+ if (!gitRoot && existsSync(resolve(current, '.git')))
1101
+ gitRoot = current;
1102
+ const parent = dirname(current);
1103
+ if (parent === current)
1104
+ return gitRoot;
1105
+ current = parent;
1106
+ }
1107
+ }
1108
+ function normalizeWorkspaceId(cwd, value) {
1109
+ const pathLike = isAbsolute(value)
1110
+ || value === '.'
1111
+ || value === '..'
1112
+ || value.startsWith('./')
1113
+ || value.startsWith('../');
1114
+ if (!pathLike)
1115
+ return value;
1116
+ const candidate = resolve(cwd, value);
1117
+ try {
1118
+ return realpathSync(candidate);
1119
+ }
1120
+ catch {
1121
+ return candidate;
1122
+ }
1123
+ }
884
1124
  function boundedInteger(value, fallback, min, max, name) {
885
1125
  if (value === undefined)
886
1126
  return fallback;
@@ -901,6 +1141,29 @@ function boundedNumber(value, fallback, min, max, name) {
901
1141
  }
902
1142
  return parsed;
903
1143
  }
1144
+ function timelineGap(value) {
1145
+ try {
1146
+ return parseTimelineGap(value);
1147
+ }
1148
+ catch (error) {
1149
+ throw new CliError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'invalid timeline gap', EXIT.usage);
1150
+ }
1151
+ }
1152
+ function inactivityThreshold(value) {
1153
+ try {
1154
+ return parseTimelineGap(value ?? '24h');
1155
+ }
1156
+ catch (error) {
1157
+ throw new CliError('INVALID_ARGUMENT', error instanceof Error ? error.message : 'invalid inactivity threshold', EXIT.usage);
1158
+ }
1159
+ }
1160
+ function controlEventMode(value) {
1161
+ if (value === undefined)
1162
+ return 'exclude';
1163
+ if (value === 'exclude' || value === 'include' || value === 'only')
1164
+ return value;
1165
+ throw new CliError('INVALID_ARGUMENT', '--control-events must be exclude, include, or only', EXIT.usage);
1166
+ }
904
1167
  function candidateState(value) {
905
1168
  if (value === undefined)
906
1169
  return undefined;
@@ -939,25 +1202,61 @@ function emit(value) {
939
1202
  function printHuman(value) {
940
1203
  if (value.status === 'preview')
941
1204
  console.log('Preview (no changes applied)');
1205
+ if (value.command === 'sessions.timeline' && value.data
1206
+ && typeof value.data === 'object' && 'items' in value.data) {
1207
+ const data = value.data;
1208
+ for (const item of data.items) {
1209
+ console.log([
1210
+ item.startAt ?? '<unknown-time>',
1211
+ item.endAt ?? '<unknown-time>',
1212
+ item.provider,
1213
+ item.sessionId,
1214
+ `segment=${item.segmentIndex}`,
1215
+ `events=${item.eventCount}`,
1216
+ `boundary=${item.boundaryBasis}`,
1217
+ `confidence=${item.confidence}`,
1218
+ ].join(' '));
1219
+ }
1220
+ console.log(`Coverage: ${data.coverage?.status ?? 'unknown'}`);
1221
+ return;
1222
+ }
942
1223
  console.log(JSON.stringify(value.data, null, 2));
943
1224
  }
944
1225
  class CliError extends Error {
945
1226
  code;
946
1227
  exitCode;
947
- constructor(code, message, exitCode) {
1228
+ details;
1229
+ constructor(code, message, exitCode, details) {
948
1230
  super(message);
949
1231
  this.code = code;
950
1232
  this.exitCode = exitCode;
1233
+ this.details = details;
951
1234
  }
952
1235
  }
953
1236
  function normalizeError(error) {
954
1237
  if (error instanceof CliError) {
955
- return { error: { code: error.code, message: error.message }, exitCode: error.exitCode };
1238
+ return {
1239
+ error: { code: error.code, message: error.message, details: error.details },
1240
+ exitCode: error.exitCode,
1241
+ };
956
1242
  }
957
1243
  if (error instanceof SessionContractError) {
958
1244
  const exitCode = error.code === 'UNSUPPORTED_OPERATION' ? EXIT.unsupported : EXIT.contract;
959
1245
  return { error: { code: error.code, message: error.message }, exitCode };
960
1246
  }
1247
+ if (error instanceof ImportLeaseContentionError) {
1248
+ return {
1249
+ error: {
1250
+ code: error.code,
1251
+ message: error.message,
1252
+ details: {
1253
+ owner: error.owner,
1254
+ waitedMs: error.waitMs,
1255
+ },
1256
+ },
1257
+ exitCode: EXIT.locked,
1258
+ };
1259
+ }
961
1260
  return {
962
1261
  error: { code: 'INTERNAL_ERROR', message: error instanceof Error ? error.message : String(error) },
963
1262
  exitCode: EXIT.storage,