intentdna 1.7.4 → 1.8.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 (152) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/LICENSE +201 -0
  4. package/README.md +143 -194
  5. package/dist/assets/candidates.d.ts +124 -0
  6. package/dist/assets/candidates.js +1249 -0
  7. package/dist/assets/catalog.d.ts +101 -0
  8. package/dist/assets/catalog.js +311 -0
  9. package/dist/assets/evidence.d.ts +93 -0
  10. package/dist/assets/evidence.js +391 -0
  11. package/dist/assets/index.d.ts +4 -0
  12. package/dist/assets/index.js +4 -0
  13. package/dist/assets/metadata.d.ts +19 -0
  14. package/dist/assets/metadata.js +227 -0
  15. package/dist/cli/commands/assets.d.ts +65 -0
  16. package/dist/cli/commands/assets.js +2871 -0
  17. package/dist/cli/commands/auth.d.ts +322 -0
  18. package/dist/cli/commands/auth.js +1592 -0
  19. package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
  20. package/dist/cli/commands/cloud-auth-guidance.js +4 -0
  21. package/dist/cli/commands/cloud.d.ts +1359 -0
  22. package/dist/cli/commands/cloud.js +8364 -0
  23. package/dist/cli/commands/evolve.js +18 -2
  24. package/dist/cli/commands/generate.js +25 -2
  25. package/dist/cli/commands/guard.d.ts +1 -1
  26. package/dist/cli/commands/guard.js +1 -1
  27. package/dist/cli/commands/guide.d.ts +15 -0
  28. package/dist/cli/commands/guide.js +1747 -0
  29. package/dist/cli/commands/init.d.ts +12 -0
  30. package/dist/cli/commands/init.js +95 -60
  31. package/dist/cli/commands/org.d.ts +16 -0
  32. package/dist/cli/commands/org.js +2393 -0
  33. package/dist/cli/commands/release.d.ts +59 -0
  34. package/dist/cli/commands/release.js +716 -0
  35. package/dist/cli/commands/report.d.ts +82 -0
  36. package/dist/cli/commands/report.js +1516 -0
  37. package/dist/cli/commands/run.d.ts +6 -1
  38. package/dist/cli/commands/run.js +95 -26
  39. package/dist/cli/commands/sync.d.ts +8 -1
  40. package/dist/cli/commands/sync.js +436 -141
  41. package/dist/cli/commands/templates.d.ts +64 -0
  42. package/dist/cli/commands/templates.js +2151 -1
  43. package/dist/cli/commands/workflow.d.ts +26 -0
  44. package/dist/cli/commands/workflow.js +3053 -0
  45. package/dist/cli/index.js +719 -42
  46. package/dist/compiler/cascade.js +4 -9
  47. package/dist/compiler/compile.js +1 -0
  48. package/dist/governance/evidence-capture-attribution.d.ts +12 -0
  49. package/dist/governance/evidence-capture-attribution.js +46 -0
  50. package/dist/governance/index.d.ts +2 -0
  51. package/dist/governance/index.js +1 -0
  52. package/dist/hooks/cli.d.ts +6 -0
  53. package/dist/hooks/cli.js +168 -12
  54. package/dist/hooks/enforce.d.ts +1 -0
  55. package/dist/hooks/protocol.d.ts +1 -1
  56. package/dist/hooks/protocol.js +1 -1
  57. package/dist/hooks/state.d.ts +41 -0
  58. package/dist/hooks/state.js +126 -2
  59. package/dist/index.d.ts +3 -0
  60. package/dist/index.js +3 -0
  61. package/dist/mcp/tools-state.js +2 -0
  62. package/dist/organization/index.d.ts +121 -0
  63. package/dist/organization/index.js +413 -0
  64. package/dist/runtime/claude-sync-target.d.ts +49 -0
  65. package/dist/runtime/claude-sync-target.js +280 -0
  66. package/dist/runtime/codex-adapter.d.ts +254 -0
  67. package/dist/runtime/codex-adapter.js +1204 -0
  68. package/dist/runtime/codex-sync-target.d.ts +33 -0
  69. package/dist/runtime/codex-sync-target.js +241 -0
  70. package/dist/runtime/index.d.ts +8 -0
  71. package/dist/runtime/index.js +4 -0
  72. package/dist/runtime/output-artifact-registry.d.ts +1 -1
  73. package/dist/runtime/output-artifact-registry.js +2 -0
  74. package/dist/runtime/settings-adapter.d.ts +1 -1
  75. package/dist/runtime/settings-adapter.js +1 -1
  76. package/dist/runtime/skill-adapter.d.ts +5 -1
  77. package/dist/runtime/skill-adapter.js +120 -34
  78. package/dist/runtime/sync-artifact-registry.d.ts +99 -0
  79. package/dist/runtime/sync-artifact-registry.js +194 -0
  80. package/dist/runtime/sync-target-plan.d.ts +45 -0
  81. package/dist/runtime/sync-target-plan.js +19 -0
  82. package/dist/schema/controller-registry.d.ts +8 -1
  83. package/dist/schema/controller-registry.js +8 -0
  84. package/dist/schema/types.d.ts +35 -23
  85. package/dist/schema/types.js +10 -1
  86. package/dist/schema/validate.js +22 -22
  87. package/dist/schema/validators/controllers.js +38 -20
  88. package/dist/schema/workflow-authoring-contract.d.ts +29 -0
  89. package/dist/schema/workflow-authoring-contract.js +109 -0
  90. package/dist/schema/yaml-parser.js +18 -0
  91. package/dist/templates/api-backend.dna.yaml +19 -0
  92. package/dist/templates/brainstorming-first.dna.yaml +19 -0
  93. package/dist/templates/code-cleanup.dna.yaml +19 -0
  94. package/dist/templates/code-review-pipeline.dna.yaml +18 -0
  95. package/dist/templates/documentation-writer.dna.yaml +19 -0
  96. package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
  97. package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
  98. package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
  99. package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
  100. package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
  101. package/dist/templates/flutter-rewrite.dna.yaml +20 -0
  102. package/dist/templates/frontend-quality.dna.yaml +19 -0
  103. package/dist/templates/full-pipeline.dna.yaml +20 -0
  104. package/dist/templates/marketplace.d.ts +92 -0
  105. package/dist/templates/marketplace.js +502 -0
  106. package/dist/templates/metadata.d.ts +25 -0
  107. package/dist/templates/metadata.js +136 -3
  108. package/dist/templates/mobile-dev.dna.yaml +19 -0
  109. package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
  110. package/dist/templates/multi-perspective-review.dna.yaml +19 -0
  111. package/dist/templates/persistent-executor.dna.yaml +19 -0
  112. package/dist/templates/qa-loop.dna.yaml +18 -0
  113. package/dist/templates/release-evidence-gate.dna.yaml +131 -0
  114. package/dist/templates/requirements-gate.dna.yaml +19 -0
  115. package/dist/templates/research-orchestration.dna.yaml +19 -0
  116. package/dist/templates/root-cause-analysis.dna.yaml +19 -0
  117. package/dist/templates/safe-refactoring.dna.yaml +321 -0
  118. package/dist/templates/secure-dev.dna.yaml +19 -0
  119. package/dist/templates/subagent-parallel.dna.yaml +19 -0
  120. package/dist/templates/systematic-debugging.dna.yaml +19 -0
  121. package/dist/templates/tdd-strict.dna.yaml +19 -0
  122. package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
  123. package/dist/templates/workflow-author.dna.yaml +302 -0
  124. package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
  125. package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
  126. package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
  127. package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
  128. package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
  129. package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
  130. package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
  131. package/dist/workflow/authoring-packet.d.ts +67 -0
  132. package/dist/workflow/authoring-packet.js +218 -0
  133. package/dist/workflow/draft.d.ts +159 -0
  134. package/dist/workflow/draft.js +800 -0
  135. package/dist/workflow/index.d.ts +3 -0
  136. package/dist/workflow/index.js +3 -0
  137. package/dist/workflow/suggestion-review.d.ts +65 -0
  138. package/dist/workflow/suggestion-review.js +245 -0
  139. package/package.json +1 -1
  140. package/spec/README.md +15 -48
  141. package/spec/codex-adapter-contract.md +176 -0
  142. package/spec/sync-target-plan.md +158 -0
  143. package/spec/workflow-capability-ir.md +153 -0
  144. package/spec/agent-dispatch-fix.md +0 -116
  145. package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
  146. package/spec/flutter-rewrite-template-optimization.md +0 -301
  147. package/spec/foundation-hardening.md +0 -178
  148. package/spec/hooks-infra-harness-hardening.md +0 -741
  149. package/spec/multi-config.md +0 -172
  150. package/spec/parallel-isolation.md +0 -268
  151. package/spec/template-version-namespace-fix.md +0 -653
  152. package/spec/workflow-pipeline.md +0 -101
@@ -0,0 +1,1592 @@
1
+ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { homedir } from "node:os";
5
+ function authBoundaries() {
6
+ return [
7
+ "CLI auth uses an IntentDNA API token, not MCP authorization.",
8
+ "dna auth stores local user credentials outside the project repository.",
9
+ "dna auth does not validate credentials against Supabase until a cloud endpoint exists.",
10
+ "Auth state does not grant runtime allow/warn/block/validate authority.",
11
+ "Service role keys are server-only and are rejected as CLI tokens when detected.",
12
+ ];
13
+ }
14
+ function authIssueBoundaries() {
15
+ return [
16
+ "dna auth issue-token uses a Supabase user JWT to request an IntentDNA CLI API token; it is not MCP authorization.",
17
+ "The Supabase user JWT is used only for the issue request unless explicitly stored through dna auth user-jwt set.",
18
+ "Raw issued CLI API tokens are consumed from the Edge Function response and stored locally; stdout prints only a masked preview.",
19
+ "Service role keys, secret keys, and token peppers are server-only and are rejected as user-facing CLI credentials.",
20
+ "Issued API tokens do not grant runtime allow/warn/block/validate authority.",
21
+ "dna-hook and local runtime evidence remain runtime truth.",
22
+ ];
23
+ }
24
+ function authBootstrapAccountBoundaries() {
25
+ return [
26
+ "dna auth bootstrap-account uses a Supabase user JWT to create or reuse account org/project records; it is not MCP authorization.",
27
+ "Account bootstrap does not issue CLI API tokens; run dna auth onboard after bootstrap to create the IntentDNA CLI API token.",
28
+ "Account bootstrap is an account onboarding mutation only; it does not upload reports, publish templates, read storage object contents, or execute workflows.",
29
+ "Service role keys, secret keys, token peppers, and CLI API tokens are rejected as user-facing bootstrap credentials.",
30
+ "Auth/account state does not grant runtime allow/warn/block/validate authority.",
31
+ "dna-hook and local runtime evidence remain runtime truth.",
32
+ ];
33
+ }
34
+ function authAccountBoundaries() {
35
+ return [
36
+ "dna auth projects and dna auth tokens use a Supabase user JWT for account onboarding; they are not MCP authorization.",
37
+ "The Supabase user JWT is used only for account requests and may be stored locally only through explicit dna auth user-jwt set.",
38
+ "Project and API token management receipts are account/auth projections, not runtime allow/warn/block/validate authority.",
39
+ "Raw API tokens, token hashes, service role keys, secret keys, and token peppers must never be printed by account commands.",
40
+ "Revoking a CLI API token changes authentication state only; dna-hook and local runtime evidence remain runtime truth.",
41
+ ];
42
+ }
43
+ function authOnboardBoundaries() {
44
+ return [
45
+ "dna auth onboard composes project discovery and CLI API token issuance; it is not MCP authorization.",
46
+ "Onboarding stores only the issued IntentDNA CLI API token locally and prints masked previews only.",
47
+ "A Supabase user JWT is an account onboarding credential, not the CLI ingestion API token.",
48
+ "Multiple account projects require an explicit --project-id; dna auth onboard does not guess.",
49
+ "Auth onboarding does not grant runtime allow/warn/block/validate authority.",
50
+ "dna-hook and local runtime evidence remain runtime truth.",
51
+ ];
52
+ }
53
+ function supabaseUserJwtBoundaries() {
54
+ return [
55
+ "dna auth user-jwt stores a Supabase user access token for account onboarding only; it is not the CLI ingestion API token.",
56
+ "Stored Supabase user JWTs are local user credentials outside the project repository and stdout prints only a masked preview.",
57
+ "Supabase user JWTs can request project listings, issue CLI API tokens, and manage API token metadata; they do not grant runtime allow/warn/block/validate authority.",
58
+ "Service role keys, secret keys, token peppers, and CLI API tokens must not be stored through dna auth user-jwt.",
59
+ "MCP remains hidden transport and is not a visible product auth path.",
60
+ ];
61
+ }
62
+ export function maskAuthToken(value) {
63
+ if (!value)
64
+ return undefined;
65
+ if (value.length <= 10)
66
+ return `${value.slice(0, 3)}***${value.slice(-2)}`;
67
+ return `${value.slice(0, 7)}...${value.slice(-4)}`;
68
+ }
69
+ export function authConfigPath(env = process.env, configDir) {
70
+ const baseDir = configDir
71
+ ?? env.INTENTDNA_AUTH_CONFIG_DIR
72
+ ?? (env.XDG_CONFIG_HOME ? join(env.XDG_CONFIG_HOME, "intentdna") : join(env.HOME ?? homedir(), ".config", "intentdna"));
73
+ return join(baseDir, "auth.json");
74
+ }
75
+ export function supabaseUserJwtConfigPath(env = process.env, configDir) {
76
+ const baseDir = configDir
77
+ ?? env.INTENTDNA_AUTH_CONFIG_DIR
78
+ ?? (env.XDG_CONFIG_HOME ? join(env.XDG_CONFIG_HOME, "intentdna") : join(env.HOME ?? homedir(), ".config", "intentdna"));
79
+ return join(baseDir, "supabase-user-jwt.json");
80
+ }
81
+ export async function resolveAuthToken(params = {}) {
82
+ const env = params.env ?? process.env;
83
+ const configPath = authConfigPath(env, params.configDir);
84
+ const envToken = env.INTENTDNA_API_TOKEN?.trim();
85
+ if (envToken)
86
+ return { token: envToken, source: "env", configPath };
87
+ const stored = await readStoredAuth(configPath);
88
+ if (stored)
89
+ return { token: stored.token, source: "user_config", configPath };
90
+ return { source: "none", configPath };
91
+ }
92
+ function decodeJwtPayload(token) {
93
+ const parts = token.split(".");
94
+ if (parts.length !== 3)
95
+ return undefined;
96
+ try {
97
+ const normalized = parts[1].replace(/-/g, "+").replace(/_/g, "/");
98
+ const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
99
+ return JSON.parse(Buffer.from(padded, "base64").toString("utf-8"));
100
+ }
101
+ catch {
102
+ return undefined;
103
+ }
104
+ }
105
+ function inspectJwtExpiry(token) {
106
+ if (!token)
107
+ return { expired: false };
108
+ const payload = decodeJwtPayload(token);
109
+ const exp = payload?.exp;
110
+ if (typeof exp !== "number" || !Number.isFinite(exp))
111
+ return { expired: false };
112
+ const expiresAtMs = exp * 1000;
113
+ return {
114
+ expiresAt: new Date(expiresAtMs).toISOString(),
115
+ expired: expiresAtMs <= Date.now(),
116
+ };
117
+ }
118
+ export function isRejectedServiceRoleToken(token, env) {
119
+ if (env.SUPABASE_SERVICE_ROLE_KEY && token === env.SUPABASE_SERVICE_ROLE_KEY)
120
+ return true;
121
+ const payload = decodeJwtPayload(token);
122
+ return payload?.role === "service_role";
123
+ }
124
+ function buildIssueTokenEndpoint(opts) {
125
+ if (opts.endpoint?.trim())
126
+ return opts.endpoint.trim();
127
+ const env = opts.env ?? process.env;
128
+ const cloudUrl = env.INTENTDNA_CLOUD_URL?.trim()
129
+ ?? env.SUPABASE_URL?.trim()
130
+ ?? linkedSupabaseProjectUrl(env);
131
+ if (!cloudUrl)
132
+ return undefined;
133
+ return `${cloudUrl.replace(/\/+$/, "")}/functions/v1/issue-api-token`;
134
+ }
135
+ export function buildAccountEndpoint(opts, functionName) {
136
+ if (opts.endpoint?.trim()) {
137
+ const normalized = opts.endpoint.trim().replace(/\/+$/, "");
138
+ const functionRoot = "/functions/v1/";
139
+ if (normalized.endsWith(`${functionRoot}${functionName}`))
140
+ return normalized;
141
+ if (normalized.includes(functionRoot))
142
+ return `${normalized.slice(0, normalized.indexOf(functionRoot) + functionRoot.length)}${functionName}`;
143
+ return `${normalized}${functionRoot}${functionName}`;
144
+ }
145
+ const env = opts.env ?? process.env;
146
+ const cloudUrl = env.INTENTDNA_CLOUD_URL?.trim()
147
+ ?? env.SUPABASE_URL?.trim()
148
+ ?? linkedSupabaseProjectUrl(env);
149
+ if (!cloudUrl)
150
+ return undefined;
151
+ return `${cloudUrl.replace(/\/+$/, "")}/functions/v1/${functionName}`;
152
+ }
153
+ function deriveOnboardEndpoint(opts, functionName) {
154
+ const explicit = opts.endpoint?.trim();
155
+ const env = opts.env ?? process.env;
156
+ const cloudUrl = explicit || env.INTENTDNA_CLOUD_URL?.trim() || env.SUPABASE_URL?.trim() || linkedSupabaseProjectUrl(env);
157
+ if (!cloudUrl)
158
+ return undefined;
159
+ const normalized = cloudUrl.replace(/\/+$/, "");
160
+ const functionRoot = "/functions/v1/";
161
+ const rootIndex = normalized.indexOf(functionRoot);
162
+ if (rootIndex >= 0)
163
+ return `${normalized.slice(0, rootIndex)}${functionRoot}${functionName}`;
164
+ return `${normalized}${functionRoot}${functionName}`;
165
+ }
166
+ function linkedSupabaseProjectUrl(env = process.env, readLinkedFiles = env === process.env) {
167
+ if (env.INTENTDNA_SUPABASE_DISABLE_LINKED_PROJECT === "1")
168
+ return undefined;
169
+ if (env.INTENTDNA_SUPABASE_LINKED_PROJECT_REF?.trim()) {
170
+ return `https://${env.INTENTDNA_SUPABASE_LINKED_PROJECT_REF.trim()}.supabase.co`;
171
+ }
172
+ if (!readLinkedFiles)
173
+ return undefined;
174
+ const directRef = existsSync("supabase/.temp/project-ref")
175
+ ? readFileSync("supabase/.temp/project-ref", "utf-8").trim()
176
+ : "";
177
+ if (directRef)
178
+ return `https://${directRef}.supabase.co`;
179
+ if (!existsSync("supabase/.temp/linked-project.json"))
180
+ return undefined;
181
+ try {
182
+ const linked = JSON.parse(readFileSync("supabase/.temp/linked-project.json", "utf-8"));
183
+ return typeof linked.ref === "string" && linked.ref.trim()
184
+ ? `https://${linked.ref.trim()}.supabase.co`
185
+ : undefined;
186
+ }
187
+ catch {
188
+ return undefined;
189
+ }
190
+ }
191
+ function withOnboardEndpoint(opts, functionName) {
192
+ return {
193
+ ...opts,
194
+ endpoint: deriveOnboardEndpoint(opts, functionName),
195
+ };
196
+ }
197
+ export async function resolveSupabaseUserJwt(opts) {
198
+ const optionToken = opts.userJwt?.trim();
199
+ const env = opts.env ?? process.env;
200
+ const configPath = supabaseUserJwtConfigPath(env, opts.configDir);
201
+ const resolveToken = (token, source) => {
202
+ const expiry = inspectJwtExpiry(token);
203
+ return { token, source, configPath, expiresAt: expiry.expiresAt, expired: expiry.expired };
204
+ };
205
+ if (optionToken)
206
+ return resolveToken(optionToken, "option");
207
+ const envToken = env.INTENTDNA_SUPABASE_USER_JWT?.trim() ?? env.SUPABASE_USER_ACCESS_TOKEN?.trim();
208
+ if (envToken)
209
+ return resolveToken(envToken, "env");
210
+ const stored = await readStoredSupabaseUserJwt(configPath);
211
+ if (stored)
212
+ return resolveToken(stored.token, "user_config");
213
+ return { source: "none", configPath, expired: false };
214
+ }
215
+ function normalizeIssueScopes(scopes) {
216
+ const raw = scopes && scopes.length > 0 ? scopes : ["report:ingest"];
217
+ const normalized = raw
218
+ .flatMap((scope) => scope.split(","))
219
+ .map((scope) => scope.trim())
220
+ .filter(Boolean);
221
+ return Array.from(new Set(normalized.length > 0 ? normalized : ["report:ingest"]));
222
+ }
223
+ function buildIssueRequest(opts) {
224
+ return {
225
+ schema_version: "intentdna.api_token_issue_request.v1",
226
+ name: opts.tokenName?.trim() || undefined,
227
+ organization_id: opts.organizationId?.trim() || undefined,
228
+ project_id: opts.projectId?.trim() || undefined,
229
+ scopes: normalizeIssueScopes(opts.scopes),
230
+ expires_at: opts.expiresAt?.trim() || null,
231
+ };
232
+ }
233
+ function supabaseUserJwtMissing(jwt) {
234
+ if (!jwt.token)
235
+ return "INTENTDNA_SUPABASE_USER_JWT, --user-jwt <supabase-user-access-token>, or dna auth user-jwt set <token>";
236
+ if (jwt.expired)
237
+ return "fresh Supabase user JWT from dna auth user-jwt set <token>";
238
+ return undefined;
239
+ }
240
+ function buildBootstrapAccountRequest(opts) {
241
+ return {
242
+ schema_version: "intentdna.account_bootstrap_request.v1",
243
+ organization_name: opts.orgName?.trim() || opts.organizationId?.trim() || undefined,
244
+ organization_slug: opts.orgSlug?.trim() || undefined,
245
+ project_name: opts.projectName?.trim() || opts.projectId?.trim() || undefined,
246
+ project_slug: opts.projectSlug?.trim() || undefined,
247
+ local_project_hint: opts.projectHint?.trim() || null,
248
+ };
249
+ }
250
+ function normalizeTokenLimit(value) {
251
+ if (!Number.isFinite(value))
252
+ return 25;
253
+ const parsed = Math.floor(Number(value));
254
+ if (parsed <= 0)
255
+ return 25;
256
+ return Math.min(parsed, 100);
257
+ }
258
+ function buildProjectsRequest() {
259
+ return {
260
+ schema_version: "intentdna.account_projects_request.v1",
261
+ method: "POST",
262
+ function: "list-projects",
263
+ body: null,
264
+ };
265
+ }
266
+ function buildTokensRequest(opts, action) {
267
+ return {
268
+ schema_version: "intentdna.api_tokens_manage_request.v1",
269
+ action,
270
+ organization_id: opts.organizationId?.trim() || undefined,
271
+ project_id: opts.projectId?.trim() || undefined,
272
+ token_id: opts.tokenId?.trim() || undefined,
273
+ include_revoked: opts.includeRevoked === true,
274
+ limit: normalizeTokenLimit(opts.limit),
275
+ };
276
+ }
277
+ function removeSensitiveFields(value) {
278
+ if (Array.isArray(value))
279
+ return value.map((item) => removeSensitiveFields(item));
280
+ if (!value || typeof value !== "object")
281
+ return value;
282
+ const output = {};
283
+ for (const [key, item] of Object.entries(value)) {
284
+ const normalized = key.toLowerCase();
285
+ if (normalized === "raw_token"
286
+ || normalized === "token_hash"
287
+ || normalized === "hash"
288
+ || normalized === "supabase_user_jwt"
289
+ || normalized === "user_jwt"
290
+ || normalized === "service_role_key"
291
+ || normalized === "secret_key"
292
+ || normalized === "token_pepper") {
293
+ continue;
294
+ }
295
+ output[key] = removeSensitiveFields(item);
296
+ }
297
+ return output;
298
+ }
299
+ function sanitizeRecord(value) {
300
+ const sanitized = removeSensitiveFields(value);
301
+ if (!sanitized || typeof sanitized !== "object" || Array.isArray(sanitized))
302
+ return undefined;
303
+ return sanitized;
304
+ }
305
+ function sanitizeIssueReceipt(value) {
306
+ const receipt = sanitizeRecord(value);
307
+ if (!receipt)
308
+ return undefined;
309
+ const token = receipt.api_token && typeof receipt.api_token === "object" && !Array.isArray(receipt.api_token)
310
+ ? receipt.api_token
311
+ : undefined;
312
+ const sanitizedToken = token ? { ...token } : undefined;
313
+ return {
314
+ ...receipt,
315
+ api_token: sanitizedToken,
316
+ };
317
+ }
318
+ function sanitizeProjectsReceipt(value) {
319
+ return sanitizeRecord(value);
320
+ }
321
+ function sanitizeTokenManagementReceipt(value) {
322
+ return sanitizeRecord(value);
323
+ }
324
+ function rawTokenFromIssueReceipt(value) {
325
+ if (!value || typeof value !== "object" || Array.isArray(value))
326
+ return undefined;
327
+ const token = value.api_token;
328
+ if (!token || typeof token !== "object" || Array.isArray(token))
329
+ return undefined;
330
+ const rawToken = token.raw_token;
331
+ return typeof rawToken === "string" && rawToken.trim() ? rawToken : undefined;
332
+ }
333
+ function statusFromIssueReceipt(value) {
334
+ if (!value || typeof value !== "object" || Array.isArray(value))
335
+ return undefined;
336
+ const status = value.status;
337
+ return status === "issued" || status === "pending" || status === "rejected" ? status : undefined;
338
+ }
339
+ function reasonsFromIssueReceipt(value) {
340
+ if (!value || typeof value !== "object" || Array.isArray(value))
341
+ return [];
342
+ const reasons = value.reasons;
343
+ return Array.isArray(reasons) ? reasons.map((reason) => String(reason)) : [];
344
+ }
345
+ function statusFromProjectsReceipt(value) {
346
+ if (!value || typeof value !== "object" || Array.isArray(value))
347
+ return undefined;
348
+ const status = value.status;
349
+ return status === "ready" || status === "pending" || status === "rejected" ? status : undefined;
350
+ }
351
+ function reasonsFromReceipt(value) {
352
+ if (!value || typeof value !== "object" || Array.isArray(value))
353
+ return [];
354
+ const reasons = value.reasons;
355
+ return Array.isArray(reasons) ? reasons.map((reason) => String(reason)) : [];
356
+ }
357
+ function statusFromTokensReceipt(value) {
358
+ if (!value || typeof value !== "object" || Array.isArray(value))
359
+ return undefined;
360
+ const status = value.status;
361
+ return status === "ready" || status === "revoked" || status === "pending" || status === "rejected" ? status : undefined;
362
+ }
363
+ function buildIssueTokenResult(params) {
364
+ return {
365
+ schema_version: "intentdna.auth_issue_token_result.v1",
366
+ generated_at: new Date().toISOString(),
367
+ status: params.status,
368
+ endpoint: params.endpoint,
369
+ request: params.request,
370
+ auth: {
371
+ supabase_user_jwt_present: params.jwtPresent,
372
+ source: params.jwtSource,
373
+ },
374
+ local_storage: {
375
+ stored: params.stored ?? false,
376
+ config_path: params.configPath,
377
+ token_preview: params.tokenPreview,
378
+ },
379
+ transport: {
380
+ network_performed: params.networkPerformed ?? false,
381
+ hidden_transport: false,
382
+ },
383
+ runtime_authority: false,
384
+ raw_token_printed: false,
385
+ issue_receipt: sanitizeIssueReceipt(params.issueReceipt),
386
+ missing: params.missing ?? [],
387
+ reasons: params.reasons,
388
+ boundaries: authIssueBoundaries(),
389
+ };
390
+ }
391
+ function buildBootstrapAccountResult(params) {
392
+ const bootstrapReceipt = sanitizeRecord(params.bootstrapReceipt);
393
+ const account = bootstrapReceipt?.account;
394
+ const projectId = account && typeof account === "object" && !Array.isArray(account)
395
+ ? account.project_id
396
+ : undefined;
397
+ return {
398
+ schema_version: "intentdna.auth_bootstrap_account_result.v1",
399
+ generated_at: new Date().toISOString(),
400
+ status: params.status,
401
+ endpoint: params.endpoint,
402
+ request: params.request,
403
+ auth: {
404
+ supabase_user_jwt_present: params.jwtPresent,
405
+ source: params.jwtSource,
406
+ },
407
+ transport: {
408
+ network_performed: params.networkPerformed ?? false,
409
+ hidden_transport: false,
410
+ },
411
+ mutation_performed: params.mutationPerformed ?? false,
412
+ runtime_authority: false,
413
+ bootstrap_receipt: bootstrapReceipt,
414
+ missing: params.missing ?? [],
415
+ reasons: params.reasons,
416
+ boundaries: authBootstrapAccountBoundaries(),
417
+ next_commands: typeof projectId === "string" && projectId.trim()
418
+ ? [
419
+ `Run dna auth onboard --project-id ${projectId} --json to issue and store a CLI API token.`,
420
+ "Run dna auth status --json to confirm CLI token readiness.",
421
+ "Run dna report upload <envelope-path> --dry-run --json before live ingestion.",
422
+ ]
423
+ : [
424
+ "Run dna auth bootstrap-account --org <name> --project <name> --dry-run --json.",
425
+ "Run dna auth bootstrap-account --org <name> --project <name> --json after Supabase auth is available.",
426
+ "Run dna auth projects --json to discover project ids.",
427
+ ],
428
+ };
429
+ }
430
+ function buildProjectsResult(params) {
431
+ return {
432
+ schema_version: "intentdna.auth_projects_result.v1",
433
+ generated_at: new Date().toISOString(),
434
+ status: params.status,
435
+ endpoint: params.endpoint,
436
+ request: params.request,
437
+ auth: {
438
+ supabase_user_jwt_present: params.jwtPresent,
439
+ source: params.jwtSource,
440
+ },
441
+ transport: {
442
+ network_performed: params.networkPerformed ?? false,
443
+ hidden_transport: false,
444
+ },
445
+ runtime_authority: false,
446
+ projects_receipt: sanitizeProjectsReceipt(params.projectsReceipt),
447
+ missing: params.missing ?? [],
448
+ reasons: params.reasons,
449
+ boundaries: authAccountBoundaries(),
450
+ };
451
+ }
452
+ function buildTokensResult(params) {
453
+ return {
454
+ schema_version: "intentdna.auth_tokens_result.v1",
455
+ generated_at: new Date().toISOString(),
456
+ action: params.action,
457
+ status: params.status,
458
+ endpoint: params.endpoint,
459
+ request: params.request,
460
+ auth: {
461
+ supabase_user_jwt_present: params.jwtPresent,
462
+ source: params.jwtSource,
463
+ },
464
+ mutation_confirmed: params.mutationConfirmed ?? false,
465
+ transport: {
466
+ network_performed: params.networkPerformed ?? false,
467
+ hidden_transport: false,
468
+ },
469
+ runtime_authority: false,
470
+ token_management_receipt: sanitizeTokenManagementReceipt(params.tokenManagementReceipt),
471
+ missing: params.missing ?? [],
472
+ reasons: params.reasons,
473
+ boundaries: authAccountBoundaries(),
474
+ };
475
+ }
476
+ function stringField(record, key) {
477
+ const value = record[key];
478
+ return typeof value === "string" && value.trim() ? value : undefined;
479
+ }
480
+ function projectsFromResult(result) {
481
+ const projects = result.projects_receipt?.projects;
482
+ if (!Array.isArray(projects))
483
+ return [];
484
+ return projects.filter((project) => (Boolean(project)
485
+ && typeof project === "object"
486
+ && !Array.isArray(project)));
487
+ }
488
+ function selectOnboardProject(result, explicitProjectId) {
489
+ const projects = projectsFromResult(result);
490
+ const projectCount = projects.length;
491
+ const explicit = explicitProjectId?.trim();
492
+ if (explicit) {
493
+ const matched = projects.find((project) => stringField(project, "project_id") === explicit);
494
+ return {
495
+ project_id: explicit,
496
+ organization_id: matched ? stringField(matched, "organization_id") : undefined,
497
+ project_name: matched ? stringField(matched, "project_name") : undefined,
498
+ organization_name: matched ? stringField(matched, "organization_name") : undefined,
499
+ source: "explicit",
500
+ project_count: projectCount,
501
+ ambiguous: false,
502
+ };
503
+ }
504
+ if (projectCount === 1) {
505
+ const project = projects[0];
506
+ return {
507
+ project_id: stringField(project, "project_id") ?? stringField(project, "id"),
508
+ organization_id: stringField(project, "organization_id"),
509
+ project_name: stringField(project, "project_name") ?? stringField(project, "name"),
510
+ organization_name: stringField(project, "organization_name"),
511
+ source: "single_project",
512
+ project_count: projectCount,
513
+ ambiguous: false,
514
+ };
515
+ }
516
+ return {
517
+ source: "unselected",
518
+ project_count: projectCount,
519
+ ambiguous: projectCount > 1,
520
+ };
521
+ }
522
+ function buildOnboardResult(params) {
523
+ const issueResult = params.issueResult;
524
+ return {
525
+ schema_version: "intentdna.auth_onboard_result.v1",
526
+ generated_at: new Date().toISOString(),
527
+ status: params.status,
528
+ selected_project: params.selectedProject,
529
+ endpoints: {
530
+ list_projects: params.projectsResult.endpoint,
531
+ issue_api_token: issueResult?.endpoint ?? deriveOnboardEndpoint({
532
+ subcommand: "onboard",
533
+ endpoint: params.projectsResult.endpoint,
534
+ }, "issue-api-token"),
535
+ },
536
+ auth: {
537
+ supabase_user_jwt_present: params.projectsResult.auth.supabase_user_jwt_present,
538
+ source: params.projectsResult.auth.source,
539
+ },
540
+ local_storage: {
541
+ stored: issueResult?.local_storage.stored ?? false,
542
+ config_path: issueResult?.local_storage.config_path ?? params.configPath,
543
+ token_preview: issueResult?.local_storage.token_preview,
544
+ },
545
+ transport: {
546
+ network_performed: params.projectsResult.transport.network_performed || (issueResult?.transport.network_performed ?? false),
547
+ hidden_transport: false,
548
+ },
549
+ runtime_authority: false,
550
+ raw_token_printed: false,
551
+ projects_result: params.projectsResult,
552
+ issue_result: issueResult,
553
+ auth_status: params.authStatus,
554
+ missing: params.missing ?? [],
555
+ reasons: params.reasons,
556
+ boundaries: authOnboardBoundaries(),
557
+ next_commands: params.status === "issued"
558
+ ? [
559
+ "Run dna auth status --json to confirm the local CLI API token is present.",
560
+ "Run dna report upload <envelope-path> --dry-run --json to preview cloud ingestion.",
561
+ ]
562
+ : [
563
+ "Run dna auth onboard --dry-run --json to preview account discovery and token issuance.",
564
+ "Run dna auth onboard --project-id <id> --json when a project is selected.",
565
+ ],
566
+ };
567
+ }
568
+ export async function readStoredAuth(path) {
569
+ try {
570
+ const parsed = JSON.parse(await readFile(path, "utf-8"));
571
+ if (parsed.schema_version !== "intentdna.auth_config.v1")
572
+ return undefined;
573
+ if (typeof parsed.token !== "string" || parsed.token.length === 0)
574
+ return undefined;
575
+ return {
576
+ schema_version: "intentdna.auth_config.v1",
577
+ token: parsed.token,
578
+ token_preview: typeof parsed.token_preview === "string" ? parsed.token_preview : maskAuthToken(parsed.token) ?? "",
579
+ token_prefix: typeof parsed.token_prefix === "string" ? parsed.token_prefix : parsed.token.slice(0, 7),
580
+ created_at: typeof parsed.created_at === "string" ? parsed.created_at : new Date(0).toISOString(),
581
+ updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : new Date(0).toISOString(),
582
+ };
583
+ }
584
+ catch {
585
+ return undefined;
586
+ }
587
+ }
588
+ export async function readStoredSupabaseUserJwt(path) {
589
+ try {
590
+ const parsed = JSON.parse(await readFile(path, "utf-8"));
591
+ if (parsed.schema_version !== "intentdna.supabase_user_jwt_config.v1")
592
+ return undefined;
593
+ if (typeof parsed.token !== "string" || parsed.token.length === 0)
594
+ return undefined;
595
+ return {
596
+ schema_version: "intentdna.supabase_user_jwt_config.v1",
597
+ token: parsed.token,
598
+ token_preview: typeof parsed.token_preview === "string" ? parsed.token_preview : maskAuthToken(parsed.token) ?? "",
599
+ token_prefix: typeof parsed.token_prefix === "string" ? parsed.token_prefix : parsed.token.slice(0, 7),
600
+ created_at: typeof parsed.created_at === "string" ? parsed.created_at : new Date(0).toISOString(),
601
+ updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : new Date(0).toISOString(),
602
+ };
603
+ }
604
+ catch {
605
+ return undefined;
606
+ }
607
+ }
608
+ export async function buildAuthStatus(params = {}) {
609
+ const resolved = await resolveAuthToken(params);
610
+ const token = resolved.token;
611
+ const credentialPresent = Boolean(token);
612
+ return {
613
+ schema_version: "intentdna.auth_status.v1",
614
+ mode: "api_token_auth_status",
615
+ authenticated: credentialPresent,
616
+ credential_present: credentialPresent,
617
+ verified_online: false,
618
+ network_performed: false,
619
+ runtime_authority: false,
620
+ source: resolved.source,
621
+ config_path: resolved.configPath,
622
+ token_preview: maskAuthToken(token),
623
+ missing: credentialPresent ? [] : ["INTENTDNA_API_TOKEN or dna auth token set <token>"],
624
+ boundaries: authBoundaries(),
625
+ next_commands: credentialPresent
626
+ ? [
627
+ "Run dna report package --json, then dna report sign --package .dna/reports/latest-report-package.json --json and dna report verify <envelope-path> --json for local proof.",
628
+ "Run dna cloud status --json before optional dna report upload <envelope-path> --json.",
629
+ ]
630
+ : ["Run dna auth token set <token> after creating an IntentDNA API token online."],
631
+ };
632
+ }
633
+ export async function buildSupabaseUserJwtStatus(params = {}) {
634
+ const resolved = await resolveSupabaseUserJwt({
635
+ subcommand: "user-jwt",
636
+ env: params.env,
637
+ configDir: params.configDir,
638
+ });
639
+ const credentialPresent = Boolean(resolved.token);
640
+ const credentialExpired = credentialPresent && resolved.expired;
641
+ return {
642
+ schema_version: "intentdna.supabase_user_jwt_status.v1",
643
+ mode: "supabase_user_jwt_account_bridge_status",
644
+ credential_present: credentialPresent,
645
+ credential_expired: credentialExpired,
646
+ verified_online: false,
647
+ network_performed: false,
648
+ runtime_authority: false,
649
+ source: resolved.source,
650
+ config_path: resolved.configPath,
651
+ token_preview: maskAuthToken(resolved.token),
652
+ expires_at: resolved.expiresAt,
653
+ missing: !credentialPresent
654
+ ? ["INTENTDNA_SUPABASE_USER_JWT, SUPABASE_USER_ACCESS_TOKEN, or dna auth user-jwt set <token>"]
655
+ : credentialExpired
656
+ ? ["fresh Supabase user JWT from dna auth user-jwt set <token>"]
657
+ : [],
658
+ boundaries: supabaseUserJwtBoundaries(),
659
+ next_commands: credentialPresent && !credentialExpired
660
+ ? [
661
+ "Run dna auth projects --json to discover Supabase projects.",
662
+ "Run dna auth issue-token --project-id <id> --json to create a CLI API token.",
663
+ ]
664
+ : ["Run dna auth user-jwt set <supabase-user-access-token> --json after signing in through Supabase Auth."],
665
+ };
666
+ }
667
+ export async function storeAuthToken(params) {
668
+ const env = params.env ?? process.env;
669
+ const configPath = authConfigPath(env, params.configDir);
670
+ const token = params.token.trim();
671
+ if (!token) {
672
+ return {
673
+ schema_version: "intentdna.auth_token_result.v1",
674
+ action: "set",
675
+ status: "rejected",
676
+ config_path: configPath,
677
+ raw_token_printed: false,
678
+ network_performed: false,
679
+ runtime_authority: false,
680
+ reason: "Token is empty.",
681
+ boundaries: authBoundaries(),
682
+ };
683
+ }
684
+ if (isRejectedServiceRoleToken(token, env)) {
685
+ return {
686
+ schema_version: "intentdna.auth_token_result.v1",
687
+ action: "set",
688
+ status: "rejected",
689
+ config_path: configPath,
690
+ raw_token_printed: false,
691
+ network_performed: false,
692
+ runtime_authority: false,
693
+ reason: "Supabase service role keys are server-only and cannot be stored as CLI API tokens.",
694
+ boundaries: authBoundaries(),
695
+ };
696
+ }
697
+ const now = new Date().toISOString();
698
+ const existing = await readStoredAuth(configPath);
699
+ const config = {
700
+ schema_version: "intentdna.auth_config.v1",
701
+ token,
702
+ token_preview: maskAuthToken(token) ?? "",
703
+ token_prefix: token.slice(0, 7),
704
+ created_at: existing?.created_at ?? now,
705
+ updated_at: now,
706
+ };
707
+ await mkdir(dirname(configPath), { recursive: true });
708
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
709
+ await chmod(configPath, 0o600);
710
+ return {
711
+ schema_version: "intentdna.auth_token_result.v1",
712
+ action: "set",
713
+ status: "stored",
714
+ config_path: configPath,
715
+ token_preview: config.token_preview,
716
+ raw_token_printed: false,
717
+ network_performed: false,
718
+ runtime_authority: false,
719
+ boundaries: authBoundaries(),
720
+ };
721
+ }
722
+ export async function storeSupabaseUserJwt(params) {
723
+ const env = params.env ?? process.env;
724
+ const configPath = supabaseUserJwtConfigPath(env, params.configDir);
725
+ const token = params.token.trim();
726
+ if (!token) {
727
+ return {
728
+ schema_version: "intentdna.supabase_user_jwt_result.v1",
729
+ action: "set",
730
+ status: "rejected",
731
+ config_path: configPath,
732
+ raw_token_printed: false,
733
+ network_performed: false,
734
+ runtime_authority: false,
735
+ reason: "Supabase user JWT is empty.",
736
+ boundaries: supabaseUserJwtBoundaries(),
737
+ };
738
+ }
739
+ if (token.startsWith("idna_")) {
740
+ return {
741
+ schema_version: "intentdna.supabase_user_jwt_result.v1",
742
+ action: "set",
743
+ status: "rejected",
744
+ config_path: configPath,
745
+ raw_token_printed: false,
746
+ network_performed: false,
747
+ runtime_authority: false,
748
+ reason: "IntentDNA CLI API tokens belong in dna auth token set, not dna auth user-jwt.",
749
+ boundaries: supabaseUserJwtBoundaries(),
750
+ };
751
+ }
752
+ if (isRejectedServiceRoleToken(token, env)) {
753
+ return {
754
+ schema_version: "intentdna.supabase_user_jwt_result.v1",
755
+ action: "set",
756
+ status: "rejected",
757
+ config_path: configPath,
758
+ raw_token_printed: false,
759
+ network_performed: false,
760
+ runtime_authority: false,
761
+ reason: "Supabase service role keys are server-only and cannot be stored as user JWTs.",
762
+ boundaries: supabaseUserJwtBoundaries(),
763
+ };
764
+ }
765
+ const now = new Date().toISOString();
766
+ const existing = await readStoredSupabaseUserJwt(configPath);
767
+ const config = {
768
+ schema_version: "intentdna.supabase_user_jwt_config.v1",
769
+ token,
770
+ token_preview: maskAuthToken(token) ?? "",
771
+ token_prefix: token.slice(0, 7),
772
+ created_at: existing?.created_at ?? now,
773
+ updated_at: now,
774
+ };
775
+ await mkdir(dirname(configPath), { recursive: true });
776
+ await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", { encoding: "utf-8", mode: 0o600 });
777
+ await chmod(configPath, 0o600);
778
+ return {
779
+ schema_version: "intentdna.supabase_user_jwt_result.v1",
780
+ action: "set",
781
+ status: "stored",
782
+ config_path: configPath,
783
+ token_preview: config.token_preview,
784
+ raw_token_printed: false,
785
+ network_performed: false,
786
+ runtime_authority: false,
787
+ boundaries: supabaseUserJwtBoundaries(),
788
+ };
789
+ }
790
+ export async function clearAuthToken(params = {}) {
791
+ const configPath = authConfigPath(params.env ?? process.env, params.configDir);
792
+ const existing = await readStoredAuth(configPath);
793
+ await rm(configPath, { force: true });
794
+ return {
795
+ schema_version: "intentdna.auth_token_result.v1",
796
+ action: "clear",
797
+ status: existing ? "cleared" : "already_clear",
798
+ config_path: configPath,
799
+ raw_token_printed: false,
800
+ network_performed: false,
801
+ runtime_authority: false,
802
+ boundaries: authBoundaries(),
803
+ };
804
+ }
805
+ export async function clearSupabaseUserJwt(params = {}) {
806
+ const configPath = supabaseUserJwtConfigPath(params.env ?? process.env, params.configDir);
807
+ const existing = await readStoredSupabaseUserJwt(configPath);
808
+ await rm(configPath, { force: true });
809
+ return {
810
+ schema_version: "intentdna.supabase_user_jwt_result.v1",
811
+ action: "clear",
812
+ status: existing ? "cleared" : "already_clear",
813
+ config_path: configPath,
814
+ raw_token_printed: false,
815
+ network_performed: false,
816
+ runtime_authority: false,
817
+ boundaries: supabaseUserJwtBoundaries(),
818
+ };
819
+ }
820
+ export async function issueAuthToken(options) {
821
+ const env = options.env ?? process.env;
822
+ const configPath = authConfigPath(env, options.configDir);
823
+ const endpoint = buildIssueTokenEndpoint(options);
824
+ const request = buildIssueRequest(options);
825
+ const jwt = await resolveSupabaseUserJwt(options);
826
+ const missing = [
827
+ !endpoint ? "INTENTDNA_CLOUD_URL or --endpoint <issue-api-token-url>" : undefined,
828
+ !request.project_id ? "--project-id <supabase-project-uuid>" : undefined,
829
+ supabaseUserJwtMissing(jwt),
830
+ ].filter((item) => Boolean(item));
831
+ if (jwt.token && isRejectedServiceRoleToken(jwt.token, env)) {
832
+ return buildIssueTokenResult({
833
+ status: "rejected",
834
+ endpoint,
835
+ request,
836
+ jwtPresent: true,
837
+ jwtSource: jwt.source,
838
+ configPath,
839
+ missing: ["Supabase user JWT"],
840
+ reasons: ["rejected: Supabase service role keys are server-only and cannot be used to issue CLI API tokens."],
841
+ });
842
+ }
843
+ if (options.dryRun) {
844
+ return buildIssueTokenResult({
845
+ status: "dry_run",
846
+ endpoint,
847
+ request,
848
+ jwtPresent: Boolean(jwt.token),
849
+ jwtSource: jwt.source,
850
+ configPath,
851
+ missing,
852
+ reasons: missing.length > 0
853
+ ? ["dry_run: issue-token request shape was built; live issuance still needs missing inputs"]
854
+ : ["dry_run: issue-token request is ready but no network request was performed"],
855
+ });
856
+ }
857
+ if (missing.length > 0) {
858
+ return buildIssueTokenResult({
859
+ status: "rejected",
860
+ endpoint,
861
+ request,
862
+ jwtPresent: Boolean(jwt.token),
863
+ jwtSource: jwt.source,
864
+ configPath,
865
+ missing,
866
+ reasons: ["rejected: missing inputs for live API token issuance"],
867
+ });
868
+ }
869
+ const fetchImpl = options.fetch ?? globalThis.fetch;
870
+ const response = await fetchImpl(endpoint, {
871
+ method: "POST",
872
+ headers: {
873
+ "content-type": "application/json",
874
+ authorization: `Bearer ${jwt.token}`,
875
+ "x-intentdna-cli": "dna auth issue-token",
876
+ },
877
+ body: JSON.stringify(request),
878
+ });
879
+ const text = await response.text();
880
+ let parsed;
881
+ try {
882
+ parsed = JSON.parse(text);
883
+ }
884
+ catch {
885
+ parsed = undefined;
886
+ }
887
+ if (!response.ok || !parsed) {
888
+ return buildIssueTokenResult({
889
+ status: "rejected",
890
+ endpoint,
891
+ request,
892
+ jwtPresent: true,
893
+ jwtSource: jwt.source,
894
+ configPath,
895
+ networkPerformed: true,
896
+ reasons: [`rejected: issue-api-token endpoint returned HTTP ${response.status}`],
897
+ });
898
+ }
899
+ const receiptStatus = statusFromIssueReceipt(parsed);
900
+ const rawToken = rawTokenFromIssueReceipt(parsed);
901
+ const receiptReasons = reasonsFromIssueReceipt(parsed);
902
+ if (receiptStatus !== "issued" || !rawToken) {
903
+ return buildIssueTokenResult({
904
+ status: receiptStatus ?? "rejected",
905
+ endpoint,
906
+ request,
907
+ jwtPresent: true,
908
+ jwtSource: jwt.source,
909
+ configPath,
910
+ networkPerformed: true,
911
+ issueReceipt: parsed,
912
+ reasons: receiptReasons.length > 0
913
+ ? receiptReasons
914
+ : ["rejected: issue-api-token response did not include an issued raw CLI token"],
915
+ });
916
+ }
917
+ const stored = await storeAuthToken({
918
+ token: rawToken,
919
+ env,
920
+ configDir: options.configDir,
921
+ });
922
+ if (stored.status === "rejected") {
923
+ return buildIssueTokenResult({
924
+ status: "rejected",
925
+ endpoint,
926
+ request,
927
+ jwtPresent: true,
928
+ jwtSource: jwt.source,
929
+ configPath,
930
+ networkPerformed: true,
931
+ issueReceipt: parsed,
932
+ reasons: [stored.reason ?? "rejected: issued CLI API token could not be stored locally"],
933
+ });
934
+ }
935
+ return buildIssueTokenResult({
936
+ status: "issued",
937
+ endpoint,
938
+ request,
939
+ jwtPresent: true,
940
+ jwtSource: jwt.source,
941
+ configPath,
942
+ stored: true,
943
+ tokenPreview: stored.token_preview,
944
+ networkPerformed: true,
945
+ issueReceipt: parsed,
946
+ reasons: receiptReasons.length > 0
947
+ ? receiptReasons
948
+ : ["issued: CLI API token was issued by Supabase and stored locally"],
949
+ });
950
+ }
951
+ function statusFromBootstrapReceipt(value) {
952
+ if (!value || typeof value !== "object" || Array.isArray(value))
953
+ return undefined;
954
+ const status = value.status;
955
+ return status === "bootstrapped" || status === "pending" || status === "rejected" ? status : undefined;
956
+ }
957
+ export async function bootstrapAccount(options) {
958
+ const env = options.env ?? process.env;
959
+ const endpoint = buildAccountEndpoint(options, "bootstrap-account");
960
+ const request = buildBootstrapAccountRequest(options);
961
+ const jwt = await resolveSupabaseUserJwt(options);
962
+ const missing = [
963
+ !endpoint ? "INTENTDNA_CLOUD_URL or --endpoint <bootstrap-account-url>" : undefined,
964
+ supabaseUserJwtMissing(jwt),
965
+ ].filter((item) => Boolean(item));
966
+ if (jwt.token && isRejectedServiceRoleToken(jwt.token, env)) {
967
+ return buildBootstrapAccountResult({
968
+ status: "rejected",
969
+ endpoint,
970
+ request,
971
+ jwtPresent: true,
972
+ jwtSource: jwt.source,
973
+ missing: ["Supabase user JWT"],
974
+ reasons: ["rejected: Supabase service role keys are server-only and cannot be used for account bootstrap."],
975
+ });
976
+ }
977
+ if (jwt.token?.startsWith("idna_")) {
978
+ return buildBootstrapAccountResult({
979
+ status: "rejected",
980
+ endpoint,
981
+ request,
982
+ jwtPresent: true,
983
+ jwtSource: jwt.source,
984
+ missing: ["Supabase user JWT"],
985
+ reasons: ["rejected: IntentDNA CLI API tokens cannot bootstrap Supabase account records; use a Supabase user JWT."],
986
+ });
987
+ }
988
+ if (options.dryRun) {
989
+ return buildBootstrapAccountResult({
990
+ status: "dry_run",
991
+ endpoint,
992
+ request,
993
+ jwtPresent: Boolean(jwt.token),
994
+ jwtSource: jwt.source,
995
+ missing,
996
+ reasons: missing.length > 0
997
+ ? ["dry_run: account bootstrap request shape was built; live bootstrap still needs missing inputs"]
998
+ : ["dry_run: account bootstrap request is ready but no network request was performed"],
999
+ });
1000
+ }
1001
+ if (missing.length > 0) {
1002
+ return buildBootstrapAccountResult({
1003
+ status: "rejected",
1004
+ endpoint,
1005
+ request,
1006
+ jwtPresent: Boolean(jwt.token),
1007
+ jwtSource: jwt.source,
1008
+ missing,
1009
+ reasons: ["rejected: missing inputs for live account bootstrap"],
1010
+ });
1011
+ }
1012
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1013
+ const response = await fetchImpl(endpoint, {
1014
+ method: "POST",
1015
+ headers: {
1016
+ "content-type": "application/json",
1017
+ authorization: `Bearer ${jwt.token}`,
1018
+ "x-intentdna-cli": "dna auth bootstrap-account",
1019
+ },
1020
+ body: JSON.stringify(request),
1021
+ });
1022
+ const text = await response.text();
1023
+ let parsed;
1024
+ try {
1025
+ parsed = JSON.parse(text);
1026
+ }
1027
+ catch {
1028
+ parsed = undefined;
1029
+ }
1030
+ if (!response.ok || !parsed) {
1031
+ return buildBootstrapAccountResult({
1032
+ status: "rejected",
1033
+ endpoint,
1034
+ request,
1035
+ jwtPresent: true,
1036
+ jwtSource: jwt.source,
1037
+ networkPerformed: true,
1038
+ reasons: [`rejected: bootstrap-account endpoint returned HTTP ${response.status}`],
1039
+ });
1040
+ }
1041
+ const receiptStatus = statusFromBootstrapReceipt(parsed);
1042
+ const receiptReasons = reasonsFromReceipt(parsed);
1043
+ const receipt = sanitizeRecord(parsed);
1044
+ return buildBootstrapAccountResult({
1045
+ status: receiptStatus ?? "rejected",
1046
+ endpoint,
1047
+ request,
1048
+ jwtPresent: true,
1049
+ jwtSource: jwt.source,
1050
+ networkPerformed: true,
1051
+ mutationPerformed: receipt?.mutation_performed === true,
1052
+ bootstrapReceipt: parsed,
1053
+ reasons: receiptReasons.length > 0
1054
+ ? receiptReasons
1055
+ : [receiptStatus ? `${receiptStatus}: bootstrap-account response returned` : "rejected: bootstrap-account response did not include a supported status"],
1056
+ });
1057
+ }
1058
+ export async function listAuthProjects(options) {
1059
+ const env = options.env ?? process.env;
1060
+ const endpoint = buildAccountEndpoint(options, "list-projects");
1061
+ const request = buildProjectsRequest();
1062
+ const jwt = await resolveSupabaseUserJwt(options);
1063
+ const missing = [
1064
+ !endpoint ? "INTENTDNA_CLOUD_URL or --endpoint <list-projects-url>" : undefined,
1065
+ supabaseUserJwtMissing(jwt),
1066
+ ].filter((item) => Boolean(item));
1067
+ if (jwt.token && isRejectedServiceRoleToken(jwt.token, env)) {
1068
+ return buildProjectsResult({
1069
+ status: "rejected",
1070
+ endpoint,
1071
+ request,
1072
+ jwtPresent: true,
1073
+ jwtSource: jwt.source,
1074
+ missing: ["Supabase user JWT"],
1075
+ reasons: ["rejected: Supabase service role keys are server-only and cannot be used for account project discovery."],
1076
+ });
1077
+ }
1078
+ if (options.dryRun) {
1079
+ return buildProjectsResult({
1080
+ status: "dry_run",
1081
+ endpoint,
1082
+ request,
1083
+ jwtPresent: Boolean(jwt.token),
1084
+ jwtSource: jwt.source,
1085
+ missing,
1086
+ reasons: missing.length > 0
1087
+ ? ["dry_run: projects request shape was built; live listing still needs missing inputs"]
1088
+ : ["dry_run: projects request is ready but no network request was performed"],
1089
+ });
1090
+ }
1091
+ if (missing.length > 0) {
1092
+ return buildProjectsResult({
1093
+ status: "rejected",
1094
+ endpoint,
1095
+ request,
1096
+ jwtPresent: Boolean(jwt.token),
1097
+ jwtSource: jwt.source,
1098
+ missing,
1099
+ reasons: ["rejected: missing inputs for live account project discovery"],
1100
+ });
1101
+ }
1102
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1103
+ const response = await fetchImpl(endpoint, {
1104
+ method: "POST",
1105
+ headers: {
1106
+ "content-type": "application/json",
1107
+ authorization: `Bearer ${jwt.token}`,
1108
+ "x-intentdna-cli": "dna auth projects",
1109
+ },
1110
+ body: JSON.stringify({ schema_version: request.schema_version }),
1111
+ });
1112
+ const text = await response.text();
1113
+ let parsed;
1114
+ try {
1115
+ parsed = JSON.parse(text);
1116
+ }
1117
+ catch {
1118
+ parsed = undefined;
1119
+ }
1120
+ if (!response.ok || !parsed) {
1121
+ return buildProjectsResult({
1122
+ status: "rejected",
1123
+ endpoint,
1124
+ request,
1125
+ jwtPresent: true,
1126
+ jwtSource: jwt.source,
1127
+ networkPerformed: true,
1128
+ reasons: [`rejected: list-projects endpoint returned HTTP ${response.status}`],
1129
+ });
1130
+ }
1131
+ const receiptStatus = statusFromProjectsReceipt(parsed);
1132
+ return buildProjectsResult({
1133
+ status: receiptStatus ?? "rejected",
1134
+ endpoint,
1135
+ request,
1136
+ jwtPresent: true,
1137
+ jwtSource: jwt.source,
1138
+ networkPerformed: true,
1139
+ projectsReceipt: parsed,
1140
+ reasons: reasonsFromReceipt(parsed).length > 0
1141
+ ? reasonsFromReceipt(parsed)
1142
+ : [receiptStatus ? `${receiptStatus}: list-projects response returned` : "rejected: list-projects response did not include a supported status"],
1143
+ });
1144
+ }
1145
+ export async function manageAuthTokens(options) {
1146
+ const env = options.env ?? process.env;
1147
+ const action = options.tokenAction === "revoke" ? "revoke" : "list";
1148
+ const endpoint = buildAccountEndpoint(options, "manage-api-tokens");
1149
+ const tokenId = options.tokenId ?? options.token;
1150
+ const request = buildTokensRequest({ ...options, tokenId }, action);
1151
+ const jwt = await resolveSupabaseUserJwt(options);
1152
+ const mutationMissing = action === "revoke" && !options.dryRun && !options.yes ? "--yes confirmation required" : undefined;
1153
+ const missing = [
1154
+ !endpoint ? "INTENTDNA_CLOUD_URL or --endpoint <manage-api-tokens-url>" : undefined,
1155
+ supabaseUserJwtMissing(jwt),
1156
+ action === "revoke" && !request.token_id ? "token id argument or --token-id <id>" : undefined,
1157
+ mutationMissing,
1158
+ ].filter((item) => Boolean(item));
1159
+ if (jwt.token && isRejectedServiceRoleToken(jwt.token, env)) {
1160
+ return buildTokensResult({
1161
+ action,
1162
+ status: "rejected",
1163
+ endpoint,
1164
+ request,
1165
+ jwtPresent: true,
1166
+ jwtSource: jwt.source,
1167
+ missing: ["Supabase user JWT"],
1168
+ reasons: ["rejected: Supabase service role keys are server-only and cannot be used for API token management."],
1169
+ });
1170
+ }
1171
+ if (options.dryRun) {
1172
+ return buildTokensResult({
1173
+ action,
1174
+ status: "dry_run",
1175
+ endpoint,
1176
+ request,
1177
+ jwtPresent: Boolean(jwt.token),
1178
+ jwtSource: jwt.source,
1179
+ mutationConfirmed: action === "revoke" && options.yes === true,
1180
+ missing,
1181
+ reasons: missing.length > 0
1182
+ ? [`dry_run: API token ${action} request shape was built; live request still needs missing inputs`]
1183
+ : [`dry_run: API token ${action} request is ready but no network request was performed`],
1184
+ });
1185
+ }
1186
+ if (missing.length > 0) {
1187
+ return buildTokensResult({
1188
+ action,
1189
+ status: "rejected",
1190
+ endpoint,
1191
+ request,
1192
+ jwtPresent: Boolean(jwt.token),
1193
+ jwtSource: jwt.source,
1194
+ mutationConfirmed: action === "revoke" && options.yes === true,
1195
+ missing,
1196
+ reasons: [`rejected: missing inputs for live API token ${action}`],
1197
+ });
1198
+ }
1199
+ const fetchImpl = options.fetch ?? globalThis.fetch;
1200
+ const response = await fetchImpl(endpoint, {
1201
+ method: "POST",
1202
+ headers: {
1203
+ "content-type": "application/json",
1204
+ authorization: `Bearer ${jwt.token}`,
1205
+ "x-intentdna-cli": `dna auth tokens ${action}`,
1206
+ },
1207
+ body: JSON.stringify(request),
1208
+ });
1209
+ const text = await response.text();
1210
+ let parsed;
1211
+ try {
1212
+ parsed = JSON.parse(text);
1213
+ }
1214
+ catch {
1215
+ parsed = undefined;
1216
+ }
1217
+ if (!response.ok || !parsed) {
1218
+ return buildTokensResult({
1219
+ action,
1220
+ status: "rejected",
1221
+ endpoint,
1222
+ request,
1223
+ jwtPresent: true,
1224
+ jwtSource: jwt.source,
1225
+ mutationConfirmed: action === "revoke" && options.yes === true,
1226
+ networkPerformed: true,
1227
+ reasons: [`rejected: manage-api-tokens endpoint returned HTTP ${response.status}`],
1228
+ });
1229
+ }
1230
+ const receiptStatus = statusFromTokensReceipt(parsed);
1231
+ const receiptReasons = reasonsFromReceipt(parsed);
1232
+ return buildTokensResult({
1233
+ action,
1234
+ status: receiptStatus ?? "rejected",
1235
+ endpoint,
1236
+ request,
1237
+ jwtPresent: true,
1238
+ jwtSource: jwt.source,
1239
+ mutationConfirmed: action === "revoke" && options.yes === true,
1240
+ networkPerformed: true,
1241
+ tokenManagementReceipt: parsed,
1242
+ reasons: receiptReasons.length > 0
1243
+ ? receiptReasons
1244
+ : [receiptStatus ? `${receiptStatus}: manage-api-tokens response returned` : "rejected: manage-api-tokens response did not include a supported status"],
1245
+ });
1246
+ }
1247
+ export async function onboardAuth(options) {
1248
+ const env = options.env ?? process.env;
1249
+ const configPath = authConfigPath(env, options.configDir);
1250
+ const projectsResult = await listAuthProjects(withOnboardEndpoint(options, "list-projects"));
1251
+ const selectedProject = selectOnboardProject(projectsResult, options.projectId);
1252
+ if (projectsResult.status === "rejected") {
1253
+ return buildOnboardResult({
1254
+ status: "rejected",
1255
+ projectsResult,
1256
+ selectedProject,
1257
+ configPath,
1258
+ missing: projectsResult.missing,
1259
+ reasons: projectsResult.reasons.length > 0
1260
+ ? projectsResult.reasons
1261
+ : ["rejected: account project discovery failed before token issuance"],
1262
+ });
1263
+ }
1264
+ if (options.dryRun) {
1265
+ const issuePreview = await issueAuthToken({
1266
+ ...withOnboardEndpoint(options, "issue-api-token"),
1267
+ projectId: selectedProject.project_id ?? options.projectId,
1268
+ organizationId: options.organizationId ?? selectedProject.organization_id,
1269
+ dryRun: true,
1270
+ });
1271
+ const missing = Array.from(new Set([
1272
+ ...projectsResult.missing,
1273
+ ...issuePreview.missing,
1274
+ !selectedProject.project_id && projectsResult.transport.network_performed ? "--project-id <id> or exactly one account project" : undefined,
1275
+ ].filter((item) => Boolean(item))));
1276
+ return buildOnboardResult({
1277
+ status: "dry_run",
1278
+ projectsResult,
1279
+ selectedProject,
1280
+ issueResult: issuePreview,
1281
+ configPath,
1282
+ missing,
1283
+ reasons: missing.length > 0
1284
+ ? ["dry_run: onboarding plan was built; live issuance still needs missing inputs"]
1285
+ : ["dry_run: onboarding can discover projects and issue a CLI API token without using MCP"],
1286
+ });
1287
+ }
1288
+ if (!selectedProject.project_id) {
1289
+ return buildOnboardResult({
1290
+ status: "pending",
1291
+ projectsResult,
1292
+ selectedProject,
1293
+ configPath,
1294
+ missing: ["--project-id <id>"],
1295
+ reasons: selectedProject.ambiguous
1296
+ ? ["pending: multiple projects are available; rerun with --project-id <id> so onboarding does not guess"]
1297
+ : ["pending: no account project id was available for CLI API token issuance"],
1298
+ });
1299
+ }
1300
+ const issueResult = await issueAuthToken({
1301
+ ...withOnboardEndpoint(options, "issue-api-token"),
1302
+ projectId: selectedProject.project_id,
1303
+ organizationId: options.organizationId ?? selectedProject.organization_id,
1304
+ });
1305
+ if (issueResult.status !== "issued") {
1306
+ return buildOnboardResult({
1307
+ status: issueResult.status === "pending" ? "pending" : "rejected",
1308
+ projectsResult,
1309
+ selectedProject,
1310
+ issueResult,
1311
+ configPath,
1312
+ missing: issueResult.missing,
1313
+ reasons: issueResult.reasons.length > 0
1314
+ ? issueResult.reasons
1315
+ : ["rejected: CLI API token issuance did not complete"],
1316
+ });
1317
+ }
1318
+ const authStatus = await buildAuthStatus({ env, configDir: options.configDir });
1319
+ return buildOnboardResult({
1320
+ status: "issued",
1321
+ projectsResult,
1322
+ selectedProject,
1323
+ issueResult,
1324
+ authStatus,
1325
+ configPath,
1326
+ reasons: ["issued: account project was selected, a CLI API token was issued, and local auth status is ready"],
1327
+ });
1328
+ }
1329
+ function printJson(value) {
1330
+ process.stdout.write(JSON.stringify(value, null, 2) + "\n");
1331
+ }
1332
+ function printAuthStatus(status) {
1333
+ const lines = [
1334
+ `Authenticated: ${status.authenticated ? "yes (local token present, not verified online)" : "no"}`,
1335
+ `Credential source: ${status.source}`,
1336
+ `Verified online: ${status.verified_online}`,
1337
+ `Network performed: ${status.network_performed}`,
1338
+ `Runtime authority: ${status.runtime_authority}`,
1339
+ `Config path: ${status.config_path}`,
1340
+ `Token: ${status.token_preview ?? "missing"}`,
1341
+ `Missing: ${status.missing.length > 0 ? status.missing.join(", ") : "none"}`,
1342
+ ...status.boundaries.map((boundary) => `Boundary: ${boundary}`),
1343
+ ];
1344
+ process.stdout.write(lines.join("\n") + "\n");
1345
+ }
1346
+ function printTokenResult(result) {
1347
+ const lines = [
1348
+ `Token ${result.status}: ${result.action}`,
1349
+ `Config path: ${result.config_path}`,
1350
+ `Token: ${result.token_preview ?? "not stored"}`,
1351
+ `Raw token printed: ${result.raw_token_printed}`,
1352
+ `Network performed: ${result.network_performed}`,
1353
+ `Runtime authority: ${result.runtime_authority}`,
1354
+ ...(result.reason ? [`Reason: ${result.reason}`] : []),
1355
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
1356
+ ];
1357
+ process.stdout.write(lines.join("\n") + "\n");
1358
+ }
1359
+ function printSupabaseUserJwtStatus(status) {
1360
+ const lines = [
1361
+ `Supabase user JWT present: ${status.credential_present ? "yes (local/account credential present, not verified online)" : "no"}`,
1362
+ `Credential source: ${status.source}`,
1363
+ `Verified online: ${status.verified_online}`,
1364
+ `Network performed: ${status.network_performed}`,
1365
+ `Runtime authority: ${status.runtime_authority}`,
1366
+ `Config path: ${status.config_path}`,
1367
+ `Token: ${status.token_preview ?? "missing"}`,
1368
+ `Missing: ${status.missing.length > 0 ? status.missing.join(", ") : "none"}`,
1369
+ ...status.boundaries.map((boundary) => `Boundary: ${boundary}`),
1370
+ ];
1371
+ process.stdout.write(lines.join("\n") + "\n");
1372
+ }
1373
+ function printSupabaseUserJwtResult(result) {
1374
+ const lines = [
1375
+ `Supabase user JWT ${result.status}: ${result.action}`,
1376
+ `Config path: ${result.config_path}`,
1377
+ `Token: ${result.token_preview ?? "not stored"}`,
1378
+ `Raw token printed: ${result.raw_token_printed}`,
1379
+ `Network performed: ${result.network_performed}`,
1380
+ `Runtime authority: ${result.runtime_authority}`,
1381
+ ...(result.reason ? [`Reason: ${result.reason}`] : []),
1382
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
1383
+ ];
1384
+ process.stdout.write(lines.join("\n") + "\n");
1385
+ }
1386
+ function printIssueTokenResult(result) {
1387
+ const lines = [
1388
+ `Issue token: ${result.status}`,
1389
+ `Endpoint: ${result.endpoint ?? "missing"}`,
1390
+ `Project id: ${result.request.project_id ?? "missing"}`,
1391
+ `Supabase user JWT: ${result.auth.supabase_user_jwt_present ? "present" : "missing"}`,
1392
+ `Network performed: ${result.transport.network_performed}`,
1393
+ `Runtime authority: ${result.runtime_authority}`,
1394
+ `Stored locally: ${result.local_storage.stored}`,
1395
+ `Config path: ${result.local_storage.config_path}`,
1396
+ `Token: ${result.local_storage.token_preview ?? "not stored"}`,
1397
+ `Missing: ${result.missing.length > 0 ? result.missing.join(", ") : "none"}`,
1398
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
1399
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
1400
+ ];
1401
+ process.stdout.write(lines.join("\n") + "\n");
1402
+ }
1403
+ function printBootstrapAccountResult(result) {
1404
+ const account = result.bootstrap_receipt?.account;
1405
+ const accountRefs = account && typeof account === "object" && !Array.isArray(account)
1406
+ ? account
1407
+ : {};
1408
+ const lines = [
1409
+ `Bootstrap account: ${result.status}`,
1410
+ `Endpoint: ${result.endpoint ?? "missing"}`,
1411
+ `Organization: ${result.request.organization_name ?? result.request.organization_slug ?? "default"}`,
1412
+ `Project: ${result.request.project_name ?? result.request.project_slug ?? "default"}`,
1413
+ `Supabase user JWT: ${result.auth.supabase_user_jwt_present ? "present" : "missing"}`,
1414
+ `Network performed: ${result.transport.network_performed}`,
1415
+ `Mutation performed: ${result.mutation_performed}`,
1416
+ `Runtime authority: ${result.runtime_authority}`,
1417
+ `Organization id: ${accountRefs.organization_id ?? "unknown"}`,
1418
+ `Project id: ${accountRefs.project_id ?? "unknown"}`,
1419
+ `Missing: ${result.missing.length > 0 ? result.missing.join(", ") : "none"}`,
1420
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
1421
+ ...result.next_commands.map((command) => `Next: ${command}`),
1422
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
1423
+ ];
1424
+ process.stdout.write(lines.join("\n") + "\n");
1425
+ }
1426
+ function printProjectsResult(result) {
1427
+ const receipt = result.projects_receipt;
1428
+ const totals = receipt?.totals && typeof receipt.totals === "object" && !Array.isArray(receipt.totals)
1429
+ ? receipt.totals
1430
+ : undefined;
1431
+ const lines = [
1432
+ `Projects: ${result.status}`,
1433
+ `Endpoint: ${result.endpoint ?? "missing"}`,
1434
+ `Supabase user JWT: ${result.auth.supabase_user_jwt_present ? "present" : "missing"}`,
1435
+ `Network performed: ${result.transport.network_performed}`,
1436
+ `Runtime authority: ${result.runtime_authority}`,
1437
+ `Organizations: ${totals?.organizations ?? "unknown"}`,
1438
+ `Projects total: ${totals?.projects ?? "unknown"}`,
1439
+ `Missing: ${result.missing.length > 0 ? result.missing.join(", ") : "none"}`,
1440
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
1441
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
1442
+ ];
1443
+ process.stdout.write(lines.join("\n") + "\n");
1444
+ }
1445
+ function printTokensResult(result) {
1446
+ const receipt = result.token_management_receipt;
1447
+ const totals = receipt?.totals && typeof receipt.totals === "object" && !Array.isArray(receipt.totals)
1448
+ ? receipt.totals
1449
+ : undefined;
1450
+ const lines = [
1451
+ `API tokens ${result.action}: ${result.status}`,
1452
+ `Endpoint: ${result.endpoint ?? "missing"}`,
1453
+ `Supabase user JWT: ${result.auth.supabase_user_jwt_present ? "present" : "missing"}`,
1454
+ `Mutation confirmed: ${result.mutation_confirmed}`,
1455
+ `Network performed: ${result.transport.network_performed}`,
1456
+ `Runtime authority: ${result.runtime_authority}`,
1457
+ `Tokens total: ${totals?.tokens ?? "unknown"}`,
1458
+ `Missing: ${result.missing.length > 0 ? result.missing.join(", ") : "none"}`,
1459
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
1460
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
1461
+ ];
1462
+ process.stdout.write(lines.join("\n") + "\n");
1463
+ }
1464
+ function printOnboardResult(result) {
1465
+ const lines = [
1466
+ `Auth onboard: ${result.status}`,
1467
+ `Projects endpoint: ${result.endpoints.list_projects ?? "missing"}`,
1468
+ `Issue endpoint: ${result.endpoints.issue_api_token ?? "missing"}`,
1469
+ `Selected project: ${result.selected_project.project_id ?? "unselected"}`,
1470
+ `Project selection: ${result.selected_project.source}${result.selected_project.ambiguous ? " (ambiguous)" : ""}`,
1471
+ `Supabase user JWT: ${result.auth.supabase_user_jwt_present ? "present" : "missing"}`,
1472
+ `Network performed: ${result.transport.network_performed}`,
1473
+ `Runtime authority: ${result.runtime_authority}`,
1474
+ `Stored locally: ${result.local_storage.stored}`,
1475
+ `Config path: ${result.local_storage.config_path}`,
1476
+ `Token: ${result.local_storage.token_preview ?? "not stored"}`,
1477
+ `Missing: ${result.missing.length > 0 ? result.missing.join(", ") : "none"}`,
1478
+ ...result.reasons.map((reason) => `Reason: ${reason}`),
1479
+ ...result.boundaries.map((boundary) => `Boundary: ${boundary}`),
1480
+ ];
1481
+ process.stdout.write(lines.join("\n") + "\n");
1482
+ }
1483
+ export async function runAuth(options) {
1484
+ if (options.subcommand === "status") {
1485
+ const status = await buildAuthStatus({ env: options.env, configDir: options.configDir });
1486
+ if (options.json)
1487
+ printJson(status);
1488
+ else
1489
+ printAuthStatus(status);
1490
+ return 0;
1491
+ }
1492
+ if (options.subcommand === "token") {
1493
+ if (options.tokenAction === "set") {
1494
+ const result = await storeAuthToken({
1495
+ token: options.token ?? "",
1496
+ env: options.env,
1497
+ configDir: options.configDir,
1498
+ });
1499
+ if (options.json)
1500
+ printJson(result);
1501
+ else
1502
+ printTokenResult(result);
1503
+ return result.status === "rejected" ? 2 : 0;
1504
+ }
1505
+ if (options.tokenAction === "clear") {
1506
+ const result = await clearAuthToken({ env: options.env, configDir: options.configDir });
1507
+ if (options.json)
1508
+ printJson(result);
1509
+ else
1510
+ printTokenResult(result);
1511
+ return 0;
1512
+ }
1513
+ }
1514
+ if (options.subcommand === "user-jwt") {
1515
+ if (options.userJwtAction === "status" || !options.userJwtAction) {
1516
+ const status = await buildSupabaseUserJwtStatus({ env: options.env, configDir: options.configDir });
1517
+ if (options.json)
1518
+ printJson(status);
1519
+ else
1520
+ printSupabaseUserJwtStatus(status);
1521
+ return 0;
1522
+ }
1523
+ if (options.userJwtAction === "set") {
1524
+ const result = await storeSupabaseUserJwt({
1525
+ token: options.userJwt ?? options.token ?? "",
1526
+ env: options.env,
1527
+ configDir: options.configDir,
1528
+ });
1529
+ if (options.json)
1530
+ printJson(result);
1531
+ else
1532
+ printSupabaseUserJwtResult(result);
1533
+ return result.status === "rejected" ? 2 : 0;
1534
+ }
1535
+ if (options.userJwtAction === "clear") {
1536
+ const result = await clearSupabaseUserJwt({ env: options.env, configDir: options.configDir });
1537
+ if (options.json)
1538
+ printJson(result);
1539
+ else
1540
+ printSupabaseUserJwtResult(result);
1541
+ return 0;
1542
+ }
1543
+ process.stderr.write("Usage: dna auth user-jwt [status|set|clear] [token] [--json]\n");
1544
+ return 2;
1545
+ }
1546
+ if (options.subcommand === "issue-token") {
1547
+ const result = await issueAuthToken(options);
1548
+ if (options.json)
1549
+ printJson(result);
1550
+ else
1551
+ printIssueTokenResult(result);
1552
+ return result.status === "dry_run" || result.status === "issued" ? 0 : 1;
1553
+ }
1554
+ if (options.subcommand === "bootstrap-account") {
1555
+ const result = await bootstrapAccount(options);
1556
+ if (options.json)
1557
+ printJson(result);
1558
+ else
1559
+ printBootstrapAccountResult(result);
1560
+ return result.status === "dry_run" || result.status === "bootstrapped" || result.status === "pending" ? 0 : 1;
1561
+ }
1562
+ if (options.subcommand === "onboard") {
1563
+ const result = await onboardAuth(options);
1564
+ if (options.json)
1565
+ printJson(result);
1566
+ else
1567
+ printOnboardResult(result);
1568
+ return result.status === "dry_run" || result.status === "issued" || result.status === "pending" ? 0 : 1;
1569
+ }
1570
+ if (options.subcommand === "projects") {
1571
+ const result = await listAuthProjects(options);
1572
+ if (options.json)
1573
+ printJson(result);
1574
+ else
1575
+ printProjectsResult(result);
1576
+ return result.status === "dry_run" || result.status === "ready" || result.status === "pending" ? 0 : 1;
1577
+ }
1578
+ if (options.subcommand === "tokens") {
1579
+ if (options.tokenAction !== "list" && options.tokenAction !== "revoke") {
1580
+ process.stderr.write("Usage: dna auth tokens <list|revoke> [token-id] [--json|--dry-run]\n");
1581
+ return 2;
1582
+ }
1583
+ const result = await manageAuthTokens(options);
1584
+ if (options.json)
1585
+ printJson(result);
1586
+ else
1587
+ printTokensResult(result);
1588
+ return result.status === "dry_run" || result.status === "ready" || result.status === "revoked" || result.status === "pending" ? 0 : 1;
1589
+ }
1590
+ process.stderr.write("Usage: dna auth status [--json]\n dna auth token set <token> [--json]\n dna auth token clear [--json]\n dna auth bootstrap-account [--org <name>] [--project <name>] [--json|--dry-run]\n dna auth onboard [--project-id <id>] [--json|--dry-run]\n dna auth issue-token --project-id <id> [--json|--dry-run]\n");
1591
+ return 2;
1592
+ }