sentinelayer-cli 0.46.4 → 0.48.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.
Files changed (137) hide show
  1. package/README.md +63 -3
  2. package/package.json +12 -7
  3. package/skills/omar-gate-setup/SKILL.md +103 -0
  4. package/skills/senti-agent-identity/SKILL.md +80 -0
  5. package/skills/senti-judge/SKILL.md +139 -0
  6. package/skills/senti-room-notifications/SKILL.md +73 -0
  7. package/skills/senti-room-notifications/listen-wrapper.cjs +150 -0
  8. package/skills/senti-room-notifications/package.json +1 -0
  9. package/skills/senti-room-notifications/test-classifier.js +109 -0
  10. package/skills/senti-room-notifications/test-receipt.js +54 -0
  11. package/skills/sentinelayer-cli/SKILL.md +222 -0
  12. package/src/agents/devtestbot/runner.js +22 -11
  13. package/src/agents/devtestbot/tool.js +24 -11
  14. package/src/agents/jules/fix-cycle.js +7 -1
  15. package/src/agents/jules/tools/runtime-audit.js +17 -12
  16. package/src/agents/shared-tools/dispatch-core.js +10 -5
  17. package/src/ai/aidenid.js +4 -2
  18. package/src/ai/domain-target-store.js +11 -5
  19. package/src/ai/identity-store.js +11 -5
  20. package/src/ai/site-store.js +8 -4
  21. package/src/audit/orchestrator.js +21 -14
  22. package/src/audit/package.js +6 -4
  23. package/src/audit/persona-loop.js +7 -0
  24. package/src/audit/replay.js +4 -2
  25. package/src/auth/gate.js +46 -8
  26. package/src/auth/service.js +34 -15
  27. package/src/auth/session-store.js +105 -47
  28. package/src/board/evidence-resolvers.js +3 -4
  29. package/src/cli.js +23 -3
  30. package/src/commands/ai/identity-lifecycle.js +30 -26
  31. package/src/commands/ai/provision-governance.js +32 -24
  32. package/src/commands/ai/shared.js +6 -4
  33. package/src/commands/audit.js +14 -12
  34. package/src/commands/auth.js +24 -4
  35. package/src/commands/board.js +2 -2
  36. package/src/commands/chat.js +8 -6
  37. package/src/commands/judge.js +125 -0
  38. package/src/commands/legacy-args.js +1 -0
  39. package/src/commands/mcp.js +45 -52
  40. package/src/commands/omargate.js +2 -1
  41. package/src/commands/plugin.js +11 -3
  42. package/src/commands/review.js +9 -4
  43. package/src/commands/scan.js +7 -7
  44. package/src/commands/session.js +607 -57
  45. package/src/commands/skills.js +45 -0
  46. package/src/commands/watch.js +9 -7
  47. package/src/config/output-filesystem.js +156 -0
  48. package/src/config/schema.js +4 -2
  49. package/src/config/service.js +40 -4
  50. package/src/cost/history.js +15 -9
  51. package/src/daemon/artifact-lineage.js +15 -12
  52. package/src/daemon/assignment-ledger.js +31 -25
  53. package/src/daemon/ast-drift.js +7 -3
  54. package/src/daemon/budget-governor.js +26 -20
  55. package/src/daemon/error-worker.js +28 -22
  56. package/src/daemon/fix-cycle.js +5 -4
  57. package/src/daemon/hybrid-mapper.js +19 -16
  58. package/src/daemon/ingest-refresh.js +4 -0
  59. package/src/daemon/jira-lifecycle.js +21 -17
  60. package/src/daemon/operator-control.js +18 -15
  61. package/src/daemon/reliability-lane.js +18 -15
  62. package/src/daemon/scope-engine.js +12 -8
  63. package/src/daemon/watchdog.js +15 -12
  64. package/src/engram/build-state.js +24 -3
  65. package/src/engram/capability-index.js +367 -0
  66. package/src/engram/compile-graph.js +1063 -0
  67. package/src/engram/contracts/action-receipt.js +82 -0
  68. package/src/engram/contracts/capability-index.js +259 -0
  69. package/src/engram/contracts/capability.js +377 -0
  70. package/src/engram/contracts/content-hash.js +8 -0
  71. package/src/engram/contracts/evidence.js +258 -0
  72. package/src/engram/contracts/graph.js +594 -0
  73. package/src/engram/contracts/identity.js +8 -0
  74. package/src/engram/contracts/index.js +149 -0
  75. package/src/engram/contracts/media-asset.js +85 -0
  76. package/src/engram/contracts/money.js +249 -0
  77. package/src/engram/contracts/source-snapshot.js +57 -0
  78. package/src/engram/contracts/task-context-pack.js +114 -0
  79. package/src/engram/contracts/temporal-segment.js +72 -0
  80. package/src/engram/contracts/versions.js +96 -0
  81. package/src/engram/extract-html.js +692 -0
  82. package/src/engram/graph-store.js +877 -0
  83. package/src/engram/index.js +98 -14
  84. package/src/engram/ingest-site.js +132 -0
  85. package/src/engram/live-state.js +1180 -0
  86. package/src/engram/namespace.js +34 -1
  87. package/src/engram/retrieve-site.js +753 -0
  88. package/src/engram/safe-reader.js +597 -0
  89. package/src/engram/source-intake.js +251 -0
  90. package/src/engram/store.js +227 -9
  91. package/src/ingest/engine.js +21 -28
  92. package/src/judge/verdict.js +305 -0
  93. package/src/legacy-cli.js +96 -19
  94. package/src/mcp/bridge-inputs.js +28 -4
  95. package/src/mcp/cli-command-tools.js +70 -0
  96. package/src/mcp/registry.js +17 -13
  97. package/src/mcp/session-stdio-server.js +297 -19
  98. package/src/memory/blackboard.js +5 -4
  99. package/src/net/gh-api-path.js +35 -9
  100. package/src/net/test-egress-guard.js +16 -4
  101. package/src/plugin/manifest.js +12 -8
  102. package/src/review/ai-review.js +110 -10
  103. package/src/review/investor-dd-devtestbot.js +17 -6
  104. package/src/review/investor-dd-orchestrator.js +21 -13
  105. package/src/review/local-review.js +26 -16
  106. package/src/review/omargate-cache.js +5 -5
  107. package/src/review/omargate-orchestrator.js +89 -17
  108. package/src/review/replay.js +9 -4
  109. package/src/review/report.js +22 -13
  110. package/src/review/typesafe-severity.js +201 -0
  111. package/src/session/admission-auth.js +34 -7
  112. package/src/session/admission.js +322 -71
  113. package/src/session/agent-access.js +137 -0
  114. package/src/session/agent-registry.js +2 -1
  115. package/src/session/attachment-summary.js +77 -0
  116. package/src/session/edit-summary.js +19 -0
  117. package/src/session/invitations.js +65 -1
  118. package/src/session/mentions.js +17 -9
  119. package/src/session/message-edits.js +5 -4
  120. package/src/session/preview.js +10 -2
  121. package/src/session/recap.js +3 -2
  122. package/src/session/room-files.js +519 -0
  123. package/src/session/route-classes.js +22 -8
  124. package/src/session/setup-guides.js +4 -0
  125. package/src/session/standup.js +192 -0
  126. package/src/session/sync.js +229 -10
  127. package/src/session/tickets.js +41 -0
  128. package/src/session/transcript.js +2 -1
  129. package/src/session/wake/resolve-target.js +7 -3
  130. package/src/skills/listener-runtime.cjs +92 -0
  131. package/src/skills/service.js +207 -0
  132. package/src/swarm/factory.js +5 -5
  133. package/src/swarm/pentest.js +9 -9
  134. package/src/swarm/report.js +4 -4
  135. package/src/swarm/runtime.js +15 -12
  136. package/src/swarm/scenario-dsl.js +7 -5
  137. package/src/telemetry/ledger.js +16 -7
@@ -22,7 +22,7 @@ import {
22
22
  recordProvisionedIdentity,
23
23
  updateIdentityStatus,
24
24
  } from "../../ai/identity-store.js";
25
- import { resolveOutputRoot } from "../../config/service.js";
25
+ import { resolveOutputRoot, resolveOutputContext } from "../../config/service.js";
26
26
  import {
27
27
  delay,
28
28
  hasExtractionSignal,
@@ -145,7 +145,7 @@ legalHold
145
145
  .action(async (identityId, options, command) => {
146
146
  const emitJson = shouldEmitJson(options, command);
147
147
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
148
- const outputRoot = await resolveOutputRoot({
148
+ const { outputRoot, outputFs } = await resolveOutputContext({
149
149
  cwd: targetPath,
150
150
  outputDirOverride: options.outputDir,
151
151
  env: process.env,
@@ -172,7 +172,7 @@ legalHold
172
172
  status,
173
173
  underHold,
174
174
  identity: identityRecord,
175
- });
175
+ }, outputFs);
176
176
 
177
177
  const payload = {
178
178
  command: "ai identity legal-hold status",
@@ -204,7 +204,7 @@ identity
204
204
  .action(async (options, command) => {
205
205
  const emitJson = shouldEmitJson(options, command);
206
206
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
207
- const outputRoot = await resolveOutputRoot({
207
+ const { outputRoot, outputFs } = await resolveOutputContext({
208
208
  cwd: targetPath,
209
209
  outputDirOverride: options.outputDir,
210
210
  env: process.env,
@@ -223,7 +223,7 @@ identity
223
223
  staleCount: staleScan.stale.length,
224
224
  totalCount: staleScan.identities.length,
225
225
  identities,
226
- });
226
+ }, outputFs);
227
227
 
228
228
  const payload = {
229
229
  command: "ai identity audit",
@@ -269,7 +269,7 @@ identity
269
269
  throw new Error("At least one tag is required via --tags.");
270
270
  }
271
271
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
272
- const outputRoot = await resolveOutputRoot({
272
+ const { outputRoot, outputFs } = await resolveOutputContext({
273
273
  cwd: targetPath,
274
274
  outputDirOverride: options.outputDir,
275
275
  env: process.env,
@@ -293,7 +293,7 @@ identity
293
293
  candidateIdentityIds: candidates.map((item) => item.identityId),
294
294
  blockedIdentityIds: blocked.map((item) => item.identityId),
295
295
  eligibleIdentityIds: eligible.map((item) => item.identityId),
296
- });
296
+ }, outputFs);
297
297
 
298
298
  if (!options.execute) {
299
299
  const payload = {
@@ -345,6 +345,7 @@ identity
345
345
  revokeResponse = revokeExecution.response || null;
346
346
  }
347
347
  const updated = await updateIdentityStatus({
348
+ outputFs,
348
349
  outputRoot,
349
350
  identityId: identityRecord.identityId,
350
351
  status: "SQUASHED",
@@ -379,7 +380,7 @@ identity
379
380
  updated: updates,
380
381
  apiCalled: canCallApi,
381
382
  credentialsMissing: resolvedCredentials.missing,
382
- });
383
+ }, outputFs);
383
384
 
384
385
  const payload = {
385
386
  command: "ai identity kill-all",
@@ -426,7 +427,7 @@ identity
426
427
  .action(async (identityId, options, command) => {
427
428
  const emitJson = shouldEmitJson(options, command);
428
429
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
429
- const outputRoot = await resolveOutputRoot({
430
+ const { outputRoot, outputFs } = await resolveOutputContext({
430
431
  cwd: targetPath,
431
432
  outputDirOverride: options.outputDir,
432
433
  env: process.env,
@@ -441,7 +442,7 @@ identity
441
442
  apiUrl,
442
443
  idempotencyKey,
443
444
  identityId,
444
- });
445
+ }, outputFs);
445
446
 
446
447
  const { registryPath, identity: trackedIdentity } = await getIdentityById({
447
448
  outputRoot,
@@ -472,7 +473,7 @@ identity
472
473
  credentialsMissing: resolvedCredentials.missing,
473
474
  trackedIdentity,
474
475
  curlPreview: [
475
- `curl -X POST ${apiUrl}/v1/identities/${urlPathSegment(identityId, { label: "identityId" })}/revoke \\`,
476
+ `curl -X POST ${apiUrl}/v1/identities/${urlPathSegment(identityId, { label: "identityId" })}/squash \\`,
476
477
  ` -H \"Authorization: Bearer $AIDENID_API_KEY\" \\`,
477
478
  ` -H \"X-Org-Id: $AIDENID_ORG_ID\" \\`,
478
479
  ` -H \"X-Project-Id: $AIDENID_PROJECT_ID\" \\`,
@@ -517,10 +518,11 @@ identity
517
518
  apiUrl,
518
519
  idempotencyKey,
519
520
  response: execution.response,
520
- });
521
+ }, outputFs);
521
522
 
522
523
  const revokedAt = String(execution.response?.revokedAt || "").trim() || new Date().toISOString();
523
524
  const updated = await updateIdentityStatus({
525
+ outputFs,
524
526
  outputRoot,
525
527
  identityId,
526
528
  status: String(execution.response?.status || "REVOKED"),
@@ -576,7 +578,7 @@ identity
576
578
  .action(async (parentIdentityId, options, command) => {
577
579
  const emitJson = shouldEmitJson(options, command);
578
580
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
579
- const outputRoot = await resolveOutputRoot({
581
+ const { outputRoot, outputFs } = await resolveOutputContext({
580
582
  cwd: targetPath,
581
583
  outputDirOverride: options.outputDir,
582
584
  env: process.env,
@@ -617,7 +619,7 @@ identity
617
619
  idempotencyKey,
618
620
  parentIdentityId,
619
621
  payload,
620
- });
622
+ }, outputFs);
621
623
 
622
624
  const resolvedCredentials = await resolveAidenIdCredentials({
623
625
  apiKey: options.apiKey,
@@ -695,9 +697,10 @@ identity
695
697
  idempotencyKey,
696
698
  parentIdentityId,
697
699
  response: execution.response,
698
- });
700
+ }, outputFs);
699
701
 
700
702
  const registryUpdate = await recordProvisionedIdentity({
703
+ outputFs,
701
704
  outputRoot,
702
705
  response: execution.response || {},
703
706
  context: {
@@ -758,7 +761,7 @@ identity
758
761
  .action(async (identityId, options, command) => {
759
762
  const emitJson = shouldEmitJson(options, command);
760
763
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
761
- const outputRoot = await resolveOutputRoot({
764
+ const { outputRoot, outputFs } = await resolveOutputContext({
762
765
  cwd: targetPath,
763
766
  outputDirOverride: options.outputDir,
764
767
  env: process.env,
@@ -796,7 +799,7 @@ identity
796
799
  generatedAt: new Date().toISOString(),
797
800
  identityId,
798
801
  response: execution.response,
799
- });
802
+ }, outputFs);
800
803
 
801
804
  const rows = renderLineageRows({
802
805
  nodes: execution.nodes,
@@ -843,7 +846,7 @@ identity
843
846
  .action(async (identityId, options, command) => {
844
847
  const emitJson = shouldEmitJson(options, command);
845
848
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
846
- const outputRoot = await resolveOutputRoot({
849
+ const { outputRoot, outputFs } = await resolveOutputContext({
847
850
  cwd: targetPath,
848
851
  outputDirOverride: options.outputDir,
849
852
  env: process.env,
@@ -865,7 +868,7 @@ identity
865
868
  apiUrl,
866
869
  idempotencyKey,
867
870
  identityId,
868
- });
871
+ }, outputFs);
869
872
 
870
873
  const resolvedCredentials = await resolveAidenIdCredentials({
871
874
  apiKey: options.apiKey,
@@ -937,12 +940,13 @@ identity
937
940
  idempotencyKey,
938
941
  identityId,
939
942
  response: execution.response,
940
- });
943
+ }, outputFs);
941
944
 
942
945
  const updateResults = await Promise.all(
943
946
  execution.revokedIdentityIds.map(async (revokedIdentityId) => {
944
947
  try {
945
948
  const updated = await updateIdentityStatus({
949
+ outputFs,
946
950
  outputRoot,
947
951
  identityId: revokedIdentityId,
948
952
  status: "SQUASHED",
@@ -1003,7 +1007,7 @@ identity
1003
1007
  .action(async (identityId, options, command) => {
1004
1008
  const emitJson = shouldEmitJson(options, command);
1005
1009
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
1006
- const outputRoot = await resolveOutputRoot({
1010
+ const { outputRoot, outputFs } = await resolveOutputContext({
1007
1011
  cwd: targetPath,
1008
1012
  outputDirOverride: options.outputDir,
1009
1013
  env: process.env,
@@ -1048,7 +1052,7 @@ identity
1048
1052
  cursor: String(options.cursor || "").trim() || null,
1049
1053
  limit,
1050
1054
  response: execution.response,
1051
- });
1055
+ }, outputFs);
1052
1056
 
1053
1057
  const payload = {
1054
1058
  command: "ai identity events",
@@ -1096,7 +1100,7 @@ identity
1096
1100
  .action(async (identityId, options, command) => {
1097
1101
  const emitJson = shouldEmitJson(options, command);
1098
1102
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
1099
- const outputRoot = await resolveOutputRoot({
1103
+ const { outputRoot, outputFs } = await resolveOutputContext({
1100
1104
  cwd: targetPath,
1101
1105
  outputDirOverride: options.outputDir,
1102
1106
  env: process.env,
@@ -1149,7 +1153,7 @@ identity
1149
1153
  latestEvent,
1150
1154
  extraction: latestExtraction.extraction,
1151
1155
  extractionResponse: latestExtraction.response,
1152
- });
1156
+ }, outputFs);
1153
1157
 
1154
1158
  const payload = {
1155
1159
  command: "ai identity latest",
@@ -1207,7 +1211,7 @@ identity
1207
1211
  .action(async (identityId, options, command) => {
1208
1212
  const emitJson = shouldEmitJson(options, command);
1209
1213
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
1210
- const outputRoot = await resolveOutputRoot({
1214
+ const { outputRoot, outputFs } = await resolveOutputContext({
1211
1215
  cwd: targetPath,
1212
1216
  outputDirOverride: options.outputDir,
1213
1217
  env: process.env,
@@ -1309,7 +1313,7 @@ identity
1309
1313
  extraction: lastExtraction,
1310
1314
  },
1311
1315
  };
1312
- await writeArtifact(outputPath, artifactPayload);
1316
+ await writeArtifact(outputPath, artifactPayload, outputFs);
1313
1317
 
1314
1318
  if (!success) {
1315
1319
  throw new Error(
@@ -24,7 +24,7 @@ import {
24
24
  } from "../../ai/domain-target-store.js";
25
25
  import { listSites, recordTemporarySite } from "../../ai/site-store.js";
26
26
  import { getIdentityById, recordProvisionedIdentity } from "../../ai/identity-store.js";
27
- import { resolveOutputRoot } from "../../config/service.js";
27
+ import { resolveOutputRoot, resolveOutputContext } from "../../config/service.js";
28
28
  import { fetchAidenIdCredentials, resolveActiveAuthSession } from "../../auth/service.js";
29
29
  import {
30
30
  buildCurlPreview,
@@ -76,7 +76,7 @@ async function provisionEmailAction(options, command) {
76
76
  extractionTypes: options.extractionTypes,
77
77
  });
78
78
 
79
- const outputRoot = await resolveOutputRoot({
79
+ const { outputRoot, outputFs } = await resolveOutputContext({
80
80
  cwd: targetPath,
81
81
  outputDirOverride: options.outputDir,
82
82
  env: process.env,
@@ -90,7 +90,7 @@ async function provisionEmailAction(options, command) {
90
90
  apiUrl,
91
91
  idempotencyKey,
92
92
  payload,
93
- });
93
+ }, outputFs);
94
94
 
95
95
  const session = await resolveActiveAuthSession({ cwd: process.cwd(), env: process.env, autoRotate: false }).catch(
96
96
  () => null,
@@ -170,10 +170,11 @@ async function provisionEmailAction(options, command) {
170
170
  apiUrl,
171
171
  idempotencyKey,
172
172
  response: execution.response,
173
- });
173
+ }, outputFs);
174
174
 
175
175
  const responseIdentity = execution.response || {};
176
176
  const registryUpdate = await recordProvisionedIdentity({
177
+ outputFs,
177
178
  outputRoot,
178
179
  response: execution.response || {},
179
180
  context: {
@@ -259,7 +260,7 @@ domain
259
260
  .action(async (domainName, options, command) => {
260
261
  const emitJson = shouldEmitJson(options, command);
261
262
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
262
- const outputRoot = await resolveOutputRoot({
263
+ const { outputRoot, outputFs } = await resolveOutputContext({
263
264
  cwd: targetPath,
264
265
  outputDirOverride: options.outputDir,
265
266
  env: process.env,
@@ -287,7 +288,7 @@ domain
287
288
  apiUrl,
288
289
  idempotencyKey,
289
290
  payload,
290
- });
291
+ }, outputFs);
291
292
 
292
293
  const resolvedCredentials = await resolveAidenIdCredentials({
293
294
  apiKey: options.apiKey,
@@ -355,10 +356,11 @@ domain
355
356
  apiUrl,
356
357
  idempotencyKey,
357
358
  response: execution.response,
358
- });
359
+ }, outputFs);
359
360
 
360
361
  const proofResponse = execution.response || {};
361
362
  const registryUpdate = await recordDomainProofResponse({
363
+ outputFs,
362
364
  outputRoot,
363
365
  domain: proofResponse.domain || {},
364
366
  proof: proofResponse,
@@ -416,7 +418,7 @@ domain
416
418
  .action(async (domainId, options, command) => {
417
419
  const emitJson = shouldEmitJson(options, command);
418
420
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
419
- const outputRoot = await resolveOutputRoot({
421
+ const { outputRoot, outputFs } = await resolveOutputContext({
420
422
  cwd: targetPath,
421
423
  outputDirOverride: options.outputDir,
422
424
  env: process.env,
@@ -451,7 +453,7 @@ domain
451
453
  idempotencyKey,
452
454
  domainId,
453
455
  payload,
454
- });
456
+ }, outputFs);
455
457
 
456
458
  const resolvedCredentials = await resolveAidenIdCredentials({
457
459
  apiKey: options.apiKey,
@@ -511,10 +513,11 @@ domain
511
513
  idempotencyKey,
512
514
  domainId,
513
515
  response: execution.response,
514
- });
516
+ }, outputFs);
515
517
 
516
518
  const proofResponse = execution.response || {};
517
519
  const registryUpdate = await recordDomainProofResponse({
520
+ outputFs,
518
521
  outputRoot,
519
522
  domain: proofResponse.domain || {},
520
523
  proof: proofResponse,
@@ -571,7 +574,7 @@ domain
571
574
  .action(async (domainId, options, command) => {
572
575
  const emitJson = shouldEmitJson(options, command);
573
576
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
574
- const outputRoot = await resolveOutputRoot({
577
+ const { outputRoot, outputFs } = await resolveOutputContext({
575
578
  cwd: targetPath,
576
579
  outputDirOverride: options.outputDir,
577
580
  env: process.env,
@@ -593,7 +596,7 @@ domain
593
596
  idempotencyKey,
594
597
  domainId,
595
598
  payload,
596
- });
599
+ }, outputFs);
597
600
 
598
601
  const resolvedCredentials = await resolveAidenIdCredentials({
599
602
  apiKey: options.apiKey,
@@ -653,9 +656,10 @@ domain
653
656
  idempotencyKey,
654
657
  domainId,
655
658
  response: execution.response,
656
- });
659
+ }, outputFs);
657
660
 
658
661
  const registryUpdate = await recordDomainProofResponse({
662
+ outputFs,
659
663
  outputRoot,
660
664
  domain: execution.response || {},
661
665
  proof: {},
@@ -712,7 +716,7 @@ target
712
716
  .action(async (host, options, command) => {
713
717
  const emitJson = shouldEmitJson(options, command);
714
718
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
715
- const outputRoot = await resolveOutputRoot({
719
+ const { outputRoot, outputFs } = await resolveOutputContext({
716
720
  cwd: targetPath,
717
721
  outputDirOverride: options.outputDir,
718
722
  env: process.env,
@@ -745,7 +749,7 @@ target
745
749
  apiUrl,
746
750
  idempotencyKey,
747
751
  payload,
748
- });
752
+ }, outputFs);
749
753
 
750
754
  const resolvedCredentials = await resolveAidenIdCredentials({
751
755
  apiKey: options.apiKey,
@@ -802,10 +806,11 @@ target
802
806
  apiUrl,
803
807
  idempotencyKey,
804
808
  response: execution.response,
805
- });
809
+ }, outputFs);
806
810
 
807
811
  const proofResponse = execution.response || {};
808
812
  const registryUpdate = await recordTargetProofResponse({
813
+ outputFs,
809
814
  outputRoot,
810
815
  target: proofResponse.target || {},
811
816
  proof: proofResponse,
@@ -862,7 +867,7 @@ target
862
867
  .action(async (targetId, options, command) => {
863
868
  const emitJson = shouldEmitJson(options, command);
864
869
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
865
- const outputRoot = await resolveOutputRoot({
870
+ const { outputRoot, outputFs } = await resolveOutputContext({
866
871
  cwd: targetPath,
867
872
  outputDirOverride: options.outputDir,
868
873
  env: process.env,
@@ -897,7 +902,7 @@ target
897
902
  idempotencyKey,
898
903
  targetId,
899
904
  payload,
900
- });
905
+ }, outputFs);
901
906
 
902
907
  const resolvedCredentials = await resolveAidenIdCredentials({
903
908
  apiKey: options.apiKey,
@@ -957,10 +962,11 @@ target
957
962
  idempotencyKey,
958
963
  targetId,
959
964
  response: execution.response,
960
- });
965
+ }, outputFs);
961
966
 
962
967
  const proofResponse = execution.response || {};
963
968
  const registryUpdate = await recordTargetProofResponse({
969
+ outputFs,
964
970
  outputRoot,
965
971
  target: proofResponse.target || {},
966
972
  proof: proofResponse,
@@ -1012,7 +1018,7 @@ target
1012
1018
  .action(async (targetId, options, command) => {
1013
1019
  const emitJson = shouldEmitJson(options, command);
1014
1020
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
1015
- const outputRoot = await resolveOutputRoot({
1021
+ const { outputRoot, outputFs } = await resolveOutputContext({
1016
1022
  cwd: targetPath,
1017
1023
  outputDirOverride: options.outputDir,
1018
1024
  env: process.env,
@@ -1041,9 +1047,10 @@ target
1041
1047
  generatedAt: new Date().toISOString(),
1042
1048
  targetId,
1043
1049
  response: execution.response,
1044
- });
1050
+ }, outputFs);
1045
1051
 
1046
1052
  const registryUpdate = await recordTargetProofResponse({
1053
+ outputFs,
1047
1054
  outputRoot,
1048
1055
  target: execution.response || {},
1049
1056
  proof: {},
@@ -1098,7 +1105,7 @@ site
1098
1105
  .action(async (identityId, options, command) => {
1099
1106
  const emitJson = shouldEmitJson(options, command);
1100
1107
  const targetPath = path.resolve(process.cwd(), String(options.path || "."));
1101
- const outputRoot = await resolveOutputRoot({
1108
+ const { outputRoot, outputFs } = await resolveOutputContext({
1102
1109
  cwd: targetPath,
1103
1110
  outputDirOverride: options.outputDir,
1104
1111
  env: process.env,
@@ -1132,7 +1139,7 @@ site
1132
1139
  apiUrl,
1133
1140
  idempotencyKey,
1134
1141
  payload,
1135
- });
1142
+ }, outputFs);
1136
1143
 
1137
1144
  const resolvedCredentials = await resolveAidenIdCredentials({
1138
1145
  apiKey: options.apiKey,
@@ -1190,9 +1197,10 @@ site
1190
1197
  apiUrl,
1191
1198
  idempotencyKey,
1192
1199
  response: execution.response,
1193
- });
1200
+ }, outputFs);
1194
1201
 
1195
1202
  const registryUpdate = await recordTemporarySite({
1203
+ outputFs,
1196
1204
  outputRoot,
1197
1205
  site: execution.response || {},
1198
1206
  context: {
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import fsp from "node:fs/promises";
2
+
3
3
  import path from "node:path";
4
+ import { requireOutputFileSystem } from "../../config/output-filesystem.js";
4
5
 
5
6
  // Shared helper utilities for ai command modules.
6
7
 
@@ -128,9 +129,10 @@ export function renderLineageRows(lineage = {}) {
128
129
  });
129
130
  }
130
131
 
131
- export async function writeArtifact(filePath, payload) {
132
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
133
- await fsp.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
132
+ export async function writeArtifact(filePath, payload, outputFs) {
133
+ requireOutputFileSystem(outputFs);
134
+ await outputFs.mkdir(path.dirname(filePath), { recursive: true });
135
+ await outputFs.writeFile(filePath, `${JSON.stringify(payload, null, 2)}\n`, "utf-8");
134
136
  }
135
137
 
136
138
  export function buildCurlPreview({ apiUrl, idempotencyKey, requestPath }) {
@@ -7,7 +7,7 @@ import { runAuditOrchestrator } from "../audit/orchestrator.js";
7
7
  import { loadAuditRunReport, resolveAuditRunDirectory, writeDdPackage } from "../audit/package.js";
8
8
  import { writeAuditComparisonArtifact } from "../audit/replay.js";
9
9
  import { loadAuditRegistry, selectAuditAgents } from "../audit/registry.js";
10
- import { resolveOutputRoot } from "../config/service.js";
10
+ import { resolveOutputContext } from "../config/service.js";
11
11
  import { createAgentEvent } from "../events/schema.js";
12
12
  import { createAuditSessionReporter, resolveAuditSessionId } from "../session/audit-reporter.js";
13
13
  import { resolveActionOptions } from "./action-options.js";
@@ -217,7 +217,7 @@ export function registerAuditCommand(program, invokeLegacy) {
217
217
  const actionOptions = resolveActionOptions(options, command);
218
218
  const emitJson = shouldEmitJson(actionOptions, command);
219
219
  const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
220
- const outputRoot = await resolveOutputRoot({
220
+ const { outputRoot, outputFs } = await resolveOutputContext({
221
221
  cwd: targetPath,
222
222
  outputDirOverride: actionOptions.outputDir,
223
223
  env: process.env,
@@ -228,6 +228,7 @@ export function registerAuditCommand(program, invokeLegacy) {
228
228
  });
229
229
  const { report } = await loadAuditRunReport(runDirectory);
230
230
  const ddPackage = await writeDdPackage({
231
+ outputFs,
231
232
  report,
232
233
  runDirectory,
233
234
  });
@@ -269,7 +270,7 @@ export function registerAuditCommand(program, invokeLegacy) {
269
270
  const actionOptions = resolveActionOptions(options, command);
270
271
  const emitJson = shouldEmitJson(actionOptions, command);
271
272
  const initialTargetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
272
- const outputRoot = await resolveOutputRoot({
273
+ const { outputRoot } = await resolveOutputContext({
273
274
  cwd: initialTargetPath,
274
275
  outputDirOverride: actionOptions.outputDir,
275
276
  env: process.env,
@@ -306,6 +307,7 @@ export function registerAuditCommand(program, invokeLegacy) {
306
307
  baseReport,
307
308
  candidateReport: replayResult,
308
309
  outputDirectory: replayResult.runDirectory,
310
+ outputFs: replayResult.outputFs,
309
311
  });
310
312
 
311
313
  const payload = {
@@ -347,7 +349,7 @@ export function registerAuditCommand(program, invokeLegacy) {
347
349
  const actionOptions = resolveActionOptions(options, command);
348
350
  const emitJson = shouldEmitJson(actionOptions, command);
349
351
  const targetPath = path.resolve(process.cwd(), String(actionOptions.path || "."));
350
- const outputRoot = await resolveOutputRoot({
352
+ const { outputRoot, outputFs } = await resolveOutputContext({
351
353
  cwd: targetPath,
352
354
  outputDirOverride: actionOptions.outputDir,
353
355
  env: process.env,
@@ -366,6 +368,7 @@ export function registerAuditCommand(program, invokeLegacy) {
366
368
  baseReport,
367
369
  candidateReport,
368
370
  outputDirectory: candidateRunDirectory,
371
+ outputFs,
369
372
  });
370
373
 
371
374
  const payload = {
@@ -869,14 +872,12 @@ export function registerAuditCommand(program, invokeLegacy) {
869
872
  const { julesAuditLoop } = await import("../agents/jules/loop.js");
870
873
  const { JULES_DEFINITION } = await import("../agents/jules/config/definition.js");
871
874
  const { collectCodebaseIngest, generateCodebaseIngest } = await import("../ingest/engine.js");
872
- const { resolveOutputRoot: resolveOut } = await import("../config/service.js");
873
875
  const { createBlackboard } = await import("../memory/blackboard.js");
874
876
  const { runDeterministicReviewPipeline } = await import("../review/local-review.js");
875
- const fsp = await import("node:fs/promises");
876
877
 
877
- const outputRoot = await resolveOut({ targetPath, outputDir: actionOptions.outputDir });
878
+ const { outputRoot, outputFs } = await resolveOutputContext({ cwd: targetPath, outputDirOverride: actionOptions.outputDir });
878
879
  const artifactDir = path.join(outputRoot, "reports", "jules-" + Date.now());
879
- await fsp.mkdir(artifactDir, { recursive: true });
880
+ await outputFs.mkdir(artifactDir, { recursive: true });
880
881
 
881
882
  // Build event handler
882
883
  const onEvent = buildEventHandler(emitStream, emitJson, JULES_DEFINITION);
@@ -888,7 +889,7 @@ export function registerAuditCommand(program, invokeLegacy) {
888
889
  let ingest;
889
890
  try {
890
891
  ingest = await generateCodebaseIngest({
891
- rootPath: targetPath, outputDir: outputRoot,
892
+ rootPath: targetPath, outputDir: actionOptions.outputDir,
892
893
  refresh: Boolean(actionOptions.refresh),
893
894
  });
894
895
  } catch {
@@ -907,6 +908,7 @@ export function registerAuditCommand(program, invokeLegacy) {
907
908
  targetPath,
908
909
  mode: "full",
909
910
  outputDir: artifactDir,
911
+ outputFs,
910
912
  });
911
913
  emitProgress(onEvent, JULES_DEFINITION,
912
914
  "Baseline complete: P0=" + (omarBaseline?.summary?.P0 || 0) +
@@ -1085,21 +1087,21 @@ export function registerAuditCommand(program, invokeLegacy) {
1085
1087
  signature: JULES_DEFINITION.signature,
1086
1088
  };
1087
1089
 
1088
- await fsp.writeFile(
1090
+ await outputFs.writeFile(
1089
1091
  path.join(artifactDir, "JULES_AUDIT.json"),
1090
1092
  JSON.stringify(reportPayload, null, 2),
1091
1093
  );
1092
1094
 
1093
1095
  // Write baseline artifact if it exists
1094
1096
  if (omarBaseline) {
1095
- await fsp.writeFile(
1097
+ await outputFs.writeFile(
1096
1098
  path.join(artifactDir, "OMAR_BASELINE.json"),
1097
1099
  JSON.stringify(omarBaseline, null, 2),
1098
1100
  );
1099
1101
  }
1100
1102
 
1101
1103
  // Write reconciliation artifact
1102
- await fsp.writeFile(
1104
+ await outputFs.writeFile(
1103
1105
  path.join(artifactDir, "RECONCILIATION.json"),
1104
1106
  JSON.stringify(reconciliation, null, 2),
1105
1107
  );
@@ -133,6 +133,14 @@ export function registerAuthCommand(program) {
133
133
  tokenLabel: options.tokenLabel,
134
134
  tokenTtlDays,
135
135
  cliVersion: CLI_VERSION,
136
+ onAuthorize: ({ authorizeUrl }) => {
137
+ // A spawned opener does not prove a browser exists on a headless host.
138
+ if (!authorizeUrl) return;
139
+ const safeUrl = String(authorizeUrl).replace(/[\u0000-\u001f\u007f-\u009f]/g, "").slice(0, 4096);
140
+ const line = `Open this URL to approve sign-in:\n${safeUrl}\n`;
141
+ if (shouldEmitJson(options, command)) process.stderr.write(line);
142
+ else console.log(line.trimEnd());
143
+ },
136
144
  });
137
145
  } catch (error) {
138
146
  throw new Error(formatApiError(error));
@@ -159,10 +167,6 @@ export function registerAuthCommand(program) {
159
167
  if (result.filePath) {
160
168
  console.log(pc.gray(`Session metadata: ${result.filePath}`));
161
169
  }
162
- if (!result.browserOpened && result.authorizeUrl) {
163
- console.log(pc.yellow("Open this URL to approve sign-in:"));
164
- console.log(result.authorizeUrl);
165
- }
166
170
  });
167
171
 
168
172
  auth
@@ -199,6 +203,13 @@ export function registerAuthCommand(program) {
199
203
 
200
204
  console.log(pc.bold("Authentication status"));
201
205
  console.log(pc.gray(`API: ${status.apiUrl}`));
206
+ if (status.validation === "storage_unavailable") {
207
+ console.log(pc.yellow("Stored sign-in metadata exists, but its credential cannot be read. Authentication is unknown."));
208
+ if (status.filePath) console.log(pc.gray(`Session metadata: ${status.filePath}`));
209
+ console.log(pc.gray("Check the current user/home, keyring availability, encryption key and file permissions."));
210
+ console.log(pc.gray("Credentials were not cleared. Repair storage instead of logging out or signing in again for this error."));
211
+ return;
212
+ }
202
213
  if (!status.source) {
203
214
  console.log(pc.yellow("No active session token found."));
204
215
  printAuthHint();
@@ -254,6 +265,15 @@ export function registerAuthCommand(program) {
254
265
  }`
255
266
  )
256
267
  );
268
+ if (status.validation === "unavailable") {
269
+ console.log(pc.yellow("Remote validation is temporarily unavailable; your existing credentials were not cleared."));
270
+ console.log(pc.gray("Retry auth status when the API is reachable. Do not sign in again solely for this validation error."));
271
+ return;
272
+ }
273
+ if (["CREDENTIAL_DESTINATION_REFUSED", "CREDENTIAL_PATH_REFUSED"].includes(status.remoteError.code)) {
274
+ console.log(pc.yellow("Credential destination was refused. Check the configured API; signing in again does not change this boundary."));
275
+ return;
276
+ }
257
277
  } else {
258
278
  console.log(pc.yellow("Remote validation was skipped (`--offline`)."));
259
279
  }