@tiangong-lca/cli 0.1.2 → 0.1.5

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 (39) hide show
  1. package/README.md +51 -12
  2. package/dist/src/auth-identity-receipt.d.ts +2 -0
  3. package/dist/src/auth-identity-receipt.js +2 -0
  4. package/dist/src/auth-identity-receipt.js.map +1 -0
  5. package/dist/src/cli.d.ts +4 -0
  6. package/dist/src/cli.js +264 -7
  7. package/dist/src/cli.js.map +1 -1
  8. package/dist/src/lib/auth-identity-receipt.d.ts +2 -2
  9. package/dist/src/lib/auth-identity-receipt.js +20 -12
  10. package/dist/src/lib/auth-identity-receipt.js.map +1 -1
  11. package/dist/src/lib/env.d.ts +8 -0
  12. package/dist/src/lib/env.js +36 -3
  13. package/dist/src/lib/env.js.map +1 -1
  14. package/dist/src/lib/lca-release.d.ts +1 -1
  15. package/dist/src/lib/lca-release.js +3 -4
  16. package/dist/src/lib/lca-release.js.map +1 -1
  17. package/dist/src/lib/lifecyclemodel-resulting-process.js +6 -2
  18. package/dist/src/lib/lifecyclemodel-resulting-process.js.map +1 -1
  19. package/dist/src/lib/oauth-loopback.d.ts +52 -0
  20. package/dist/src/lib/oauth-loopback.js +229 -0
  21. package/dist/src/lib/oauth-loopback.js.map +1 -0
  22. package/dist/src/lib/oauth-pkce.d.ts +63 -0
  23. package/dist/src/lib/oauth-pkce.js +268 -0
  24. package/dist/src/lib/oauth-pkce.js.map +1 -0
  25. package/dist/src/lib/process-refresh-references.js +2 -2
  26. package/dist/src/lib/process-refresh-references.js.map +1 -1
  27. package/dist/src/lib/process-scope-statistics.js +8 -3
  28. package/dist/src/lib/process-scope-statistics.js.map +1 -1
  29. package/dist/src/lib/state-lock.js +10 -4
  30. package/dist/src/lib/state-lock.js.map +1 -1
  31. package/dist/src/lib/supabase-client.d.ts +13 -1
  32. package/dist/src/lib/supabase-client.js +64 -3
  33. package/dist/src/lib/supabase-client.js.map +1 -1
  34. package/dist/src/lib/supabase-json-ordered-write.js +7 -3
  35. package/dist/src/lib/supabase-json-ordered-write.js.map +1 -1
  36. package/dist/src/lib/supabase-session.d.ts +63 -8
  37. package/dist/src/lib/supabase-session.js +315 -21
  38. package/dist/src/lib/supabase-session.js.map +1 -1
  39. package/package.json +5 -1
package/README.md CHANGED
@@ -13,27 +13,33 @@ whenToUpdate:
13
13
  - when user-facing command examples change
14
14
  checkPaths:
15
15
  - README.md
16
+ - .env.example
16
17
  - package.json
17
18
  - pnpm-workspace.yaml
18
19
  - pnpm-lock.yaml
19
20
  - bin/**
20
21
  - src/cli.ts
21
22
  - src/main.ts
23
+ - src/auth-identity-receipt.ts
22
24
  - src/batch.ts
23
25
  - src/command-spec.ts
24
26
  - src/lib/auth-identity-receipt.ts
27
+ - src/lib/oauth-loopback.ts
28
+ - src/lib/oauth-pkce.ts
29
+ - src/lib/supabase-session.ts
25
30
  - src/lib/lca-release.ts
26
31
  - scripts/run-auth-identity-production-case.ts
27
32
  - test/auth-identity*.test.ts
33
+ - test/public-auth-identity-receipt.test.ts
28
34
  - test/lca-release*.test.ts
29
- lastReviewedAt: 2026-08-26
30
- lastReviewedCommit: b9a99011fcc1d7388355e66649113ac5d4a7a9c8
31
- lastReviewedNote: 'Reviewed for Issue #237: identifies 0.1.2 as the release-only package identity for the bounded pnpm 11.24 public API while runtime, exports, dependencies, and release behavior stay fixed.'
35
+ lastReviewedAt: 2026-08-31
36
+ lastReviewedCommit: 352c11c5ffafaea1bf94133be92f3337b847866a
37
+ lastReviewedNote: 'Reviewed for Issue #252: identifies 0.1.5 as the replacement OAuth release after unpublished 0.1.4, preserving exports, dependencies, and release behavior.'
32
38
  ---
33
39
 
34
40
  # TianGong LCA CLI
35
41
 
36
- Package: `@tiangong-lca/cli` Executable: `tiangong-lca` Current package version: `0.1.2` Node: `24.19.0`
42
+ Package: `@tiangong-lca/cli` Executable: `tiangong-lca` Current package version: `0.1.5` Node: `24.19.0`
37
43
 
38
44
  Repository development is single-track on pnpm `11.24.0` and TypeScript `7.0.2`. The published package remains a clean, package-manager-neutral consumer artifact: it contains runtime files only, not pnpm, TypeScript, Oxlint, tests, source-only tooling, or repository lockfiles.
39
45
 
@@ -53,6 +59,18 @@ Review note, 2026-08-26: Issue #236 changes only the repository and clean-consum
53
59
 
54
60
  Review note, 2026-08-26: Issue #237 releases the already reviewed bounded CommandSpec/batch runtime and pnpm 11.24 toolchain as `@tiangong-lca/cli@0.1.2`. The release changes only package metadata and four live CLI-version fixtures; runtime files, public subpaths and object identities, dependencies, the sole lock, Node 24.19.0, TypeScript 7.0.2, package-manager-neutral consumers, and automated provenance path remain unchanged.
55
61
 
62
+ Review note, 2026-08-29: Issue #240 adds the typed `@tiangong-lca/cli/auth-identity-receipt` public parser entry without changing package version 0.1.2 or auth behavior. It directly re-exports the existing strict parser, schema/timeout constants, and receipt types; the remote runner and test internals stay private, and the previous `dist/src/lib/**` deep path remains blocked.
63
+
64
+ Review note, 2026-08-29: Issue #242 releases that public parser as `@tiangong-lca/cli@0.1.3`. Only package identity and four existing exact-version fixtures change; `./batch`, `./command-spec`, `./auth-identity-receipt`, the executable, dependencies, pnpm lock, Node 24.19.0 / TypeScript 7.0.2 toolchain, and package-manager-neutral consumer contract are otherwise unchanged.
65
+
66
+ Review note, 2026-08-31: Issue #244 adds Supabase OAuth 2.1 Authorization Code + S256 PKCE without adding a dependency or changing package version/exports. `auth login` uses a registered public client, exact literal-`127.0.0.1` callback, state validation, and shell-free system browser; it atomically stores rotating access/refresh tokens in a private session file. `auth status` performs no network request and reveals no credential or session path; `auth whoami` and `auth doctor-auth` use the live redacted identity receipt. `auth logout` deletes only that matching local session; Connected applications owns grant revocation. `TIANGONG_LCA_ACCESS_TOKEN` is a short-lived, online-verified, memory-only headless option. The reversible API key remains only as a transition fallback and is never used by OAuth mode.
67
+
68
+ Review note, 2026-08-31: Issue #247 prevents a second CLI process from failing when the first process removes `session.json.lock` between contention and metadata read. Only an absent lock is treated as released; permission, I/O, malformed-owner, timeout, and stale-owner safety behavior is unchanged.
69
+
70
+ Review note, 2026-08-31: Issue #246 releases that OAuth runtime as `@tiangong-lca/cli@0.1.4`. Only package identity and four existing exact-version fixtures change; the executable and public subpaths, dependencies, pnpm lock, Node 24.19.0 / TypeScript 7.0.2 toolchain, auth behavior, and package-manager-neutral consumer contract are otherwise unchanged.
71
+
72
+ Review note, 2026-08-31: Issue #252 replaces the stopped-unpublished 0.1.4 attempt with `@tiangong-lca/cli@0.1.5` after platform-complete session coverage merged. Only package identity, four version fixtures, and release evidence change; executable/public subpaths, OAuth behavior, dependencies, pnpm lock, Node/TypeScript toolchain, and consumer contract stay unchanged.
73
+
56
74
  Review note, 2026-07-12: `dataset maintenance plan/apply/verify` provides current-user RLS-scoped exact-row maintenance with immutable plans, explicit approval, per-action logs, platform audit correlation, and independent readback. `merge-support-aliases` now runs only in `target_mode=owner_draft`: source/target support and all changed rows stay private `state_code=0`; publication is a separate future workflow.
57
75
 
58
76
  Review note, 2026-07-13: maintenance scans now prove exact-count pagination even when PostgREST returns fewer rows than the requested `--page-size`. An incomplete or inconsistent scan fails before artifacts, approval, or mutation; under stable filtered membership/order the proof represents a complete ordered multi-request traversal, not one transaction-level/MVCC snapshot.
@@ -108,11 +126,12 @@ node ./bin/tiangong-lca.js --help
108
126
 
109
127
  The package exposes only these supported module APIs in addition to the executable launcher subpath:
110
128
 
129
+ - `@tiangong-lca/cli/auth-identity-receipt` parses the exact safe receipt projection offline and exports its schema, Node-safe timeout ceiling, and receipt types. It does not expose session resolution, network execution, or test internals; callers that need a fresh receipt still invoke the CLI command.
111
130
  - `@tiangong-lca/cli/command-spec` parses, creates, artifact-binds, and executes exact `tiangong-foundry.command-spec.v1` objects. `display` is diagnostic only; executable plus argv and binding bytes/SHA-256 form the canonical authority. Sync and async execution always use `shell:false`; async callers may inject resolver, clock, sleep, spawn, timeout, and abort adapters. `timeoutMs` must fit Node's maximum timer delay.
112
131
  - `@tiangong-lca/cli/batch` runs bounded generic work with an overall run contract and a required per-item `{ item_id, content_sha256, policy_sha256 }` contract. Every identity/content/policy/resource projection is validated before work starts and rechecked before resumed acceptance or a fresh claim. Identity changes or getter failures emit `item_identity_drift` with `BatchItemIdentityDriftError`, execute zero attempts, and do not let the batch return before other in-flight items drain. An escaping scheduler/event/stop callback records the first infrastructure error, closes further claims, awaits all worker settlements, then rejects. Input order, truthful resource-aware claim order, completion order, pause/stop behavior, awaited monotonic events, and exception isolation are explicit. Optional exclusive keys serialize only matching resources; a blocked key remains unclaimed and consumes no worker while later free keys may use the public ceiling of 64 workers. Per-resource FIFO queues expose only their head through an ordered binary min-heap, giving near `O(n log k)` ready scheduling for `k` resources.
113
132
  - Mutation batches reject automatic retry. A consumed or ambiguous mutation can continue only through an explicitly supplied readback-recovery callback; resume requires exact run and item contracts, and each resumed or rejected result participates in stop decisions before fresh claims. Retry/backoff values must be non-negative safe integers within Node's timer limit. Exclusive keys must be runtime strings. `withBatchRunLock` gives one canonical run directory one cross-process lock domain, permits reentrancy only from a still-live scope owned by the current holder, keeps the top-level promise pending until detached nested scopes drain, rejects completed-context reentry into a later owner, preserves live or foreign-host locks, and stale-recovers only a same-host dead PID. Public lock callers cannot set PID, host, or ownership time; timeout/poll values are non-negative safe integers within Node's timer limit.
114
133
 
115
- The packed ESM, CJS dynamic-import, and TypeScript consumers exercise both public subpaths and generated declarations. Deep imports and `import '@tiangong-lca/cli'` remain unsupported.
134
+ The packed ESM, CJS dynamic-import, and TypeScript consumers exercise every public subpath and generated declaration. Deep imports and `import '@tiangong-lca/cli'` remain unsupported.
116
135
 
117
136
  ## Env
118
137
 
@@ -120,25 +139,45 @@ Remote commands require:
120
139
 
121
140
  ```bash
122
141
  TIANGONG_LCA_API_BASE_URL=
123
- TIANGONG_LCA_API_KEY=
124
142
  TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY=
143
+ TIANGONG_LCA_OAUTH_CLIENT_ID=
125
144
  TIANGONG_LCA_REGION=us-east-1
126
145
  ```
127
146
 
128
147
  Notes:
129
148
 
130
149
  - `TIANGONG_LCA_API_BASE_URL` accepts the project root, `/functions/v1`, or `/rest/v1`.
131
- - `TIANGONG_LCA_API_KEY` is the TianGong user API key from the account page, not a Supabase project key.
132
- - The CLI exchanges `TIANGONG_LCA_API_KEY` for a user session, then reuses the access token for both Edge Functions and direct Supabase access.
150
+ - `TIANGONG_LCA_OAUTH_CLIENT_ID` is the environment-specific registered public CLI client; it is not a secret.
151
+ - Run `tiangong-lca auth login` once in a trusted terminal. All Edge Function and direct Supabase commands then reuse the OAuth access token and rotate the refresh token on demand.
152
+ - For approved headless execution, set `TIANGONG_LCA_AUTH_MODE=access-token` and inject one short-lived `TIANGONG_LCA_ACCESS_TOKEN`. It is verified online, kept only in process memory, and never refreshed.
153
+ - Transition-only compatibility uses `TIANGONG_LCA_AUTH_MODE=legacy-user-api-key` plus `TIANGONG_LCA_API_KEY`. OAuth failures never fall back to this password-equivalent credential.
133
154
 
134
155
  Optional session control:
135
156
 
136
157
  ```bash
158
+ TIANGONG_LCA_AUTH_MODE=oauth
159
+ TIANGONG_LCA_OAUTH_REDIRECT_URI=http://127.0.0.1:49191/oauth/callback
137
160
  TIANGONG_LCA_SESSION_FILE=
138
161
  TIANGONG_LCA_DISABLE_SESSION_CACHE=false
139
162
  TIANGONG_LCA_FORCE_REAUTH=false
140
163
  ```
141
164
 
165
+ ## OAuth Session Commands
166
+
167
+ ```text
168
+ tiangong-lca auth login
169
+ tiangong-lca auth status --json
170
+ tiangong-lca auth whoami --json
171
+ tiangong-lca auth doctor-auth --json
172
+ tiangong-lca auth logout
173
+ ```
174
+
175
+ The callback URI must exactly match the URI registered with the selected Supabase OAuth client. The default is `http://127.0.0.1:49191/oauth/callback`; OAuth client redirect URIs do not support wildcards. Login never accepts a username, password, authorization code, access token, refresh token, or PKCE verifier through argv. On POSIX, the app directory is `0700` and `session.json` is `0600`; writes and refresh-token rotation use a temporary file, atomic rename, and the existing cross-process state lock. Windows callers must keep the selected parent ACL current-user-only because chmod bits are not an ACL.
176
+
177
+ `auth status` is intentionally local-only and non-mutating. It reports whether a matching session can be used or refreshed, but sets `onlineVerified: false`; it never prints email, tokens, a session path, or a credential fingerprint. `auth whoami` performs the live redacted identity receipt. `auth doctor-auth` first checks local readiness, then performs that live check; a missing OAuth session returns `login-required` so a human can run `auth login`. An AI agent must never ask for or handle the user's password, authorization code, access token, or refresh token.
178
+
179
+ Local logout does not revoke the server grant. To invalidate every refresh token for the CLI client, open Account → Connected applications and disconnect TianGong CLI.
180
+
142
181
  ## Auth Identity Receipt
143
182
 
144
183
  Use the identity receipt before a production-backed case or any later owner-draft write gate:
@@ -147,7 +186,7 @@ Use the identity receipt before a production-backed case or any later owner-draf
147
186
  tiangong-lca auth identity-receipt --expected-project-ref <project-ref> --expected-user-id <user-id> --json
148
187
  ```
149
188
 
150
- The command performs no dataset write. It exchanges the normal user API key for a session, checks the canonical Supabase project, and makes a bounded live `GET /auth/v1/user`. A cached token that receives `401` or `403` may be refreshed and retried exactly once; all other transport or response failures are terminal. A valid production guard requires:
189
+ The command performs no dataset write. It resolves the selected OAuth, explicit headless, or transition-only legacy session, checks the canonical Supabase project, and makes a bounded live `GET /auth/v1/user`. A refreshable cached token that receives `401` or `403` may be refreshed and retried exactly once; an explicit headless access token is never refreshed. All other transport or response failures are terminal. A valid production guard requires:
151
190
 
152
191
  - `schema` exactly `tiangong-lca.auth-identity-receipt.v1`;
153
192
  - `status: "passed"`, `operation: "current-user-read"`, and `remote_write_mode: "read-only"`;
@@ -162,7 +201,7 @@ For the explicitly authorized local production read case from a validated reposi
162
201
  pnpm case:auth-identity:production -- --env-file <data-foundry-ignored-.env> --expected-project-ref <project-ref> --expected-user-id <user-id> --out-dir <new-private-case-directory>
163
202
  ```
164
203
 
165
- The runner reads only `TIANGONG_LCA_API_BASE_URL`, `TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY`, and `TIANGONG_LCA_TEST_API_KEY`; the last is mapped to the child process's standard API-key variable. It does not accept an alternate CLI path. The pnpm command first performs a clean TS7 build without the production env. Its plain-Node runner then single-reads source/config/lock and the freshly generated `dist/src/**/*.js`, hashes the source tree, runner, runtime, exact entrypoint, and pnpm lock, and copies those exact built buffers into a private snapshot before exposing the key. It forces reauthentication with session cache disabled, runs only the built snapshot from an exclusively created clean directory with an argv array and `shell:false`, cleans the snapshot before publishing success artifacts, and persists only the parsed receipt and case manifest. POSIX creates the case directory as `0700` and files as `0600`; Windows inherits ACLs from the caller-selected parent, so use a user-restricted parent because mode bits are not an ACL guarantee. The runner never stores raw child stdout/stderr and is intentionally not wired to CI secrets. This receipt is locally hash-verifiable, not server-signed attestation.
204
+ The historical production-case runner remains a transition-only legacy fixture: it reads only `TIANGONG_LCA_API_BASE_URL`, `TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY`, and `TIANGONG_LCA_TEST_API_KEY`; the last is mapped to the child process's explicit legacy variable. It does not accept an alternate CLI path. The pnpm command first performs a clean TS7 build without the production env. Its plain-Node runner then single-reads source/config/lock and the freshly generated `dist/src/**/*.js`, hashes the source tree, runner, runtime, exact entrypoint, and pnpm lock, and copies those exact built buffers into a private snapshot before exposing the key. It forces reauthentication with session cache disabled, runs only the built snapshot from an exclusively created clean directory with an argv array and `shell:false`, cleans the snapshot before publishing success artifacts, and persists only the parsed receipt and case manifest. POSIX creates the case directory as `0700` and files as `0600`; Windows inherits ACLs from the caller-selected parent, so use a user-restricted parent because mode bits are not an ACL guarantee. The runner never stores raw child stdout/stderr and is intentionally not wired to CI secrets. New automation should use OAuth/headless mode instead. This receipt is locally hash-verifiable, not server-signed attestation.
166
205
 
167
206
  ## LCI/LCIA Data Release
168
207
 
@@ -228,7 +267,7 @@ tiangong-lca release status --release-run-id <release-run-id> --json
228
267
  }
229
268
  ```
230
269
 
231
- The CLI verifies every local upload against its declared byte size, SHA-256, media type, and required pair before requesting signed URLs. Upload receipts and downloads are written atomically with private file permissions. Existing outputs are preserved unless `--force` is explicit. The publish credential fingerprint is derived locally from `TIANGONG_LCA_API_KEY`; callers cannot inject it through the request file.
270
+ The CLI verifies every local upload against its declared byte size, SHA-256, media type, and required pair before requesting signed URLs. Upload receipts and downloads are written atomically with private file permissions. Existing outputs are preserved unless `--force` is explicit. Release requests use the resolved actor access token and `client_id`-aware database policy; no credential-derived fingerprint is accepted from local state or the request file.
232
271
 
233
272
  Calculation results and published artifacts remain file-first:
234
273
 
@@ -318,7 +357,7 @@ Key outputs under `--out-dir`:
318
357
  - `outputs/identity-candidates.jsonl`
319
358
  - `outputs/identity-candidate-sources.json`
320
359
 
321
- `--candidate-input` is repeatable and accepts JSON, JSONL, or a directory scanned recursively for JSON/JSONL candidate rows. Embedded `candidates` from the request and local-scan candidates are evaluated together. Add `--remote-candidates` when the preflight should also call `process_hybrid_search` or `flow_hybrid_search`; `--remote-query` overrides the target-derived search text and `--remote-limit` caps returned candidate rows. Remote candidate search uses the normal Supabase session env: `TIANGONG_LCA_API_BASE_URL`, `TIANGONG_LCA_API_KEY`, `TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY`, and optional `TIANGONG_LCA_REGION`.
360
+ `--candidate-input` is repeatable and accepts JSON, JSONL, or a directory scanned recursively for JSON/JSONL candidate rows. Embedded `candidates` from the request and local-scan candidates are evaluated together. Add `--remote-candidates` when the preflight should also call `process_hybrid_search` or `flow_hybrid_search`; `--remote-query` overrides the target-derived search text and `--remote-limit` caps returned candidate rows. Remote candidate search uses the normal OAuth session env above and optional `TIANGONG_LCA_REGION`.
322
361
 
323
362
  When remote search is enabled, the CLI sends a compact fielded `query` string plus supported edge-search options to `process_hybrid_search` or `flow_hybrid_search`: `filter`, `match_count`, `page_size`, `data_source`, `match_threshold`, `lexical_weight`, `semantic_weight`, and `rrf_k`. `lexical_weight` controls the single database-owned `extracted_md` lexical branch. Request-level `remote_candidate_search.profile_hints` are not sent to the Edge Function. They are applied locally before scoring candidates so Foundry can provide source-derived facts such as flow type, flow property, reference unit, elementary categories, geography, reference-flow names, technology route, and system boundary without polluting the full-text/semantic query.
324
363
 
@@ -0,0 +1,2 @@
1
+ export { AUTH_IDENTITY_MAX_TIMEOUT_MS, AUTH_IDENTITY_RECEIPT_SCHEMA, parseAuthIdentityReceipt, } from './lib/auth-identity-receipt.js';
2
+ export type { AuthIdentityReceipt, AuthIdentityReceiptScope } from './lib/auth-identity-receipt.js';
@@ -0,0 +1,2 @@
1
+ export { AUTH_IDENTITY_MAX_TIMEOUT_MS, AUTH_IDENTITY_RECEIPT_SCHEMA, parseAuthIdentityReceipt, } from './lib/auth-identity-receipt.js';
2
+ //# sourceMappingURL=auth-identity-receipt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-identity-receipt.js","sourceRoot":"","sources":["../../src/auth-identity-receipt.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4BAA4B,EAC5B,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,gCAAgC,CAAC","sourcesContent":["export {\n AUTH_IDENTITY_MAX_TIMEOUT_MS,\n AUTH_IDENTITY_RECEIPT_SCHEMA,\n parseAuthIdentityReceipt,\n} from './lib/auth-identity-receipt.js';\nexport type { AuthIdentityReceipt, AuthIdentityReceiptScope } from './lib/auth-identity-receipt.js';\n"]}
package/dist/src/cli.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { DotEnvLoadResult } from './lib/dotenv.js';
2
2
  import type { FetchLike } from './lib/http.js';
3
+ import { inspectSupabaseAuthStatus, loginWithSupabaseOAuth, logoutSupabaseUserSession } from './lib/supabase-session.js';
3
4
  import { type AuthIdentityReceipt, type RunAuthIdentityReceiptOptions } from './lib/auth-identity-receipt.js';
4
5
  import { type LifecyclemodelAutoBuildReport, type RunLifecyclemodelAutoBuildOptions } from './lib/lifecyclemodel-auto-build.js';
5
6
  import { type LifecyclemodelResultingProcessReport, type RunLifecyclemodelResultingProcessOptions } from './lib/lifecyclemodel-resulting-process.js';
@@ -71,6 +72,9 @@ export type CliDeps = {
71
72
  dotEnvStatus: DotEnvLoadResult;
72
73
  fetchImpl: FetchLike;
73
74
  runAuthIdentityReceiptImpl?: (options: RunAuthIdentityReceiptOptions) => Promise<AuthIdentityReceipt>;
75
+ loginWithSupabaseOAuthImpl?: typeof loginWithSupabaseOAuth;
76
+ logoutSupabaseUserSessionImpl?: typeof logoutSupabaseUserSession;
77
+ inspectSupabaseAuthStatusImpl?: typeof inspectSupabaseAuthStatus;
74
78
  runPublishImpl?: (options: RunPublishOptions) => Promise<PublishReport>;
75
79
  runValidationImpl?: (options: RunValidationOptions) => Promise<ValidationRunReport>;
76
80
  runLcaReleaseImpl?: (options: RunLcaReleaseOptions) => Promise<LcaReleaseReport>;
package/dist/src/cli.js CHANGED
@@ -3,6 +3,9 @@ import { buildDoctorReport, readRuntimeEnv } from './lib/env.js';
3
3
  import { CliError, toErrorPayload } from './lib/errors.js';
4
4
  import { stringifyJson } from './lib/io.js';
5
5
  import { loadCliPackageVersion } from './lib/package-version.js';
6
+ import { requireSupabaseRestRuntime } from './lib/supabase-client.js';
7
+ import { inspectSupabaseAuthStatus, loginWithSupabaseOAuth, logoutSupabaseUserSession, } from './lib/supabase-session.js';
8
+ import { OAUTH_LOGIN_TIMEOUT_MAX_MS } from './lib/oauth-loopback.js';
6
9
  import { AUTH_IDENTITY_MAX_TIMEOUT_MS, AUTH_IDENTITY_RECEIPT_SCHEMA, runAuthIdentityReceipt, } from './lib/auth-identity-receipt.js';
7
10
  import { runLifecyclemodelAutoBuild, } from './lib/lifecyclemodel-auto-build.js';
8
11
  import { runLifecyclemodelBuildResultingProcess, } from './lib/lifecyclemodel-resulting-process.js';
@@ -87,7 +90,7 @@ Usage:
87
90
  Commands:
88
91
  Implemented Commands:
89
92
  doctor show environment diagnostics
90
- auth identity-receipt
93
+ auth login | status | whoami | doctor-auth | logout | identity-receipt
91
94
  search flow | process | lifecyclemodel
92
95
  process get | list | identity-preflight | build-plan | scope-statistics | dedup-review | auto-build | resume-build | publish-build | complete-required-fields | save-draft | batch-build | refresh-references | verify-rows
93
96
  dataset contract get | context-pack | classification children/path/audit/apply | curation-queue build/next/verify | import-lca convert | author | patch apply | save-draft | source upload-attachments | validate | verify-remote | bilingual extract/apply/validate | evidence-search plan/run | references rewrite/refresh-remote | maintenance clear-account/plan/apply/verify/flow-identity
@@ -100,13 +103,17 @@ Implemented Commands:
100
103
  admin embedding-run
101
104
 
102
105
  Planned Surface (not implemented yet):
103
- auth whoami | doctor-auth
104
106
  job get | wait | logs
105
107
 
106
108
  Planned commands currently print an explicit "not implemented yet" message and exit with code 2.
107
109
 
108
110
  Examples:
109
111
  tiangong-lca doctor
112
+ tiangong-lca auth login
113
+ tiangong-lca auth status --json
114
+ tiangong-lca auth whoami --json
115
+ tiangong-lca auth doctor-auth --json
116
+ tiangong-lca auth logout
110
117
  tiangong-lca auth identity-receipt --expected-project-ref <project-ref> --expected-user-id <user-id> --json
111
118
  tiangong-lca search flow --input ./request.json
112
119
  tiangong-lca search process --input ./request.json --dry-run
@@ -185,15 +192,79 @@ Environment:
185
192
  }
186
193
  function renderAuthHelp() {
187
194
  return `Usage:
188
- tiangong-lca auth identity-receipt [options]
195
+ tiangong-lca auth <login|status|whoami|doctor-auth|logout|identity-receipt> [options]
189
196
 
190
197
  Implemented Subcommands:
198
+ login Authorize the registered public CLI through browser PKCE and store a private rotating session
199
+ status Inspect only local auth readiness without reading or printing credentials
200
+ whoami Run the live, redacted identity receipt for the current user
201
+ doctor-auth Combine local readiness with a live, redacted identity check
202
+ logout Remove only this local CLI session; revoke the connected-app grant from the account page
191
203
  identity-receipt Live, read-only proof of the authenticated account and Supabase project
192
204
 
193
- Planned Subcommands:
194
- whoami | doctor-auth
205
+ Use "tiangong-lca auth <subcommand> --help" for details.
206
+ `.trim();
207
+ }
208
+ function renderAuthStatusHelp() {
209
+ return `Usage:
210
+ tiangong-lca auth status [--json]
211
+
212
+ Inspects only the matching local session metadata. It performs no network request, never refreshes
213
+ a token, and never emits email, token, session path, or credential fingerprint. A ready result can
214
+ still be remotely revoked; use "auth doctor-auth" when live verification is required. If OAuth login
215
+ is required, hand the terminal to the human user and run "tiangong-lca auth login". Never ask an AI
216
+ agent to collect a username, password, authorization code, access token, or refresh token.
217
+ `.trim();
218
+ }
219
+ function renderAuthWhoamiHelp() {
220
+ return `Usage:
221
+ tiangong-lca auth whoami [--timeout-ms <n>] [--json]
222
+
223
+ Runs the same live, read-only and redacted proof as "auth identity-receipt" without requiring an
224
+ expected identity assertion. It may refresh an OAuth session under the private session lock. Output
225
+ contains no token, full email address, session path, or credential fingerprint.
226
+ `.trim();
227
+ }
228
+ function renderAuthDoctorHelp() {
229
+ return `Usage:
230
+ tiangong-lca auth doctor-auth [--timeout-ms <n>] [--json]
231
+
232
+ First checks local readiness without network access. If ready, it performs the live redacted identity
233
+ receipt. Missing OAuth authorization returns login-required without attempting password bootstrap.
234
+ Run "tiangong-lca auth login" only in a trusted human-controlled terminal.
235
+ `.trim();
236
+ }
237
+ function renderAuthLoginHelp() {
238
+ return `Usage:
239
+ tiangong-lca auth login [options]
240
+
241
+ Options:
242
+ --timeout-ms <n> Browser callback timeout 1..${OAUTH_LOGIN_TIMEOUT_MAX_MS} ms (default: 180000)
243
+ --json Print compact JSON; default output is pretty JSON
244
+ -h, --help
245
+
246
+ Required env:
247
+ TIANGONG_LCA_API_BASE_URL
248
+ TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY
249
+ TIANGONG_LCA_OAUTH_CLIENT_ID
250
+
251
+ Optional env:
252
+ TIANGONG_LCA_AUTH_MODE=oauth
253
+ TIANGONG_LCA_OAUTH_REDIRECT_URI (default: exact registered 127.0.0.1 callback)
254
+ TIANGONG_LCA_SESSION_FILE
255
+
256
+ The CLI binds only 127.0.0.1, opens the system browser without a shell, verifies state and S256
257
+ PKCE, and atomically writes access/rotating refresh tokens to a private session file. It never
258
+ accepts a username, password, authorization code, access token, or refresh token as an argv value.
259
+ `.trim();
260
+ }
261
+ function renderAuthLogoutHelp() {
262
+ return `Usage:
263
+ tiangong-lca auth logout [--json]
195
264
 
196
- Use "tiangong-lca auth identity-receipt --help" for the machine-receipt contract.
265
+ Removes only the locally cached session bound to the current project/client. To revoke the OAuth
266
+ grant and every refresh token, disconnect TianGong CLI from Connected applications in the account
267
+ page. An explicit TIANGONG_LCA_ACCESS_TOKEN is never persisted and therefore has nothing to delete.
197
268
  `.trim();
198
269
  }
199
270
  function renderAuthIdentityReceiptHelp() {
@@ -218,8 +289,14 @@ Safety:
218
289
 
219
290
  Required env:
220
291
  TIANGONG_LCA_API_BASE_URL
221
- TIANGONG_LCA_API_KEY
222
292
  TIANGONG_LCA_SUPABASE_PUBLISHABLE_KEY
293
+ TIANGONG_LCA_OAUTH_CLIENT_ID and a prior "tiangong-lca auth login"
294
+
295
+ Headless alternative:
296
+ TIANGONG_LCA_ACCESS_TOKEN (short-lived actor token; never cached or refreshed)
297
+
298
+ Legacy compatibility:
299
+ TIANGONG_LCA_AUTH_MODE=legacy-user-api-key plus TIANGONG_LCA_API_KEY
223
300
 
224
301
  Optional session env:
225
302
  TIANGONG_LCA_SESSION_FILE
@@ -2369,6 +2446,99 @@ function parseAuthIdentityReceiptFlags(args) {
2369
2446
  timeoutMs,
2370
2447
  };
2371
2448
  }
2449
+ function parseAuthLoginFlags(args) {
2450
+ let parsed;
2451
+ try {
2452
+ parsed = parseArgs({
2453
+ args,
2454
+ allowPositionals: false,
2455
+ strict: true,
2456
+ tokens: true,
2457
+ options: {
2458
+ help: { type: 'boolean', short: 'h' },
2459
+ json: { type: 'boolean' },
2460
+ 'timeout-ms': { type: 'string' },
2461
+ },
2462
+ });
2463
+ }
2464
+ catch (error) {
2465
+ throw new CliError(String(error), { code: 'INVALID_ARGS', exitCode: 2 });
2466
+ }
2467
+ const timeoutOccurrences = parsed.tokens.filter((entry) => entry.kind === 'option' && entry.name === 'timeout-ms').length;
2468
+ if (timeoutOccurrences > 1) {
2469
+ throw new CliError('Option --timeout-ms may be provided only once.', {
2470
+ code: 'INVALID_ARGS',
2471
+ exitCode: 2,
2472
+ });
2473
+ }
2474
+ const timeoutText = parsed.values['timeout-ms'];
2475
+ const timeoutMs = typeof timeoutText === 'string' ? Number(timeoutText) : 180_000;
2476
+ if (!Number.isSafeInteger(timeoutMs) ||
2477
+ timeoutMs <= 0 ||
2478
+ timeoutMs > OAUTH_LOGIN_TIMEOUT_MAX_MS) {
2479
+ throw new CliError(`Expected --timeout-ms to be an integer between 1 and ${OAUTH_LOGIN_TIMEOUT_MAX_MS}.`, { code: 'INVALID_TIMEOUT', exitCode: 2 });
2480
+ }
2481
+ return {
2482
+ help: Boolean(parsed.values.help),
2483
+ json: Boolean(parsed.values.json),
2484
+ timeoutMs,
2485
+ };
2486
+ }
2487
+ function parseAuthLogoutFlags(args) {
2488
+ try {
2489
+ const { values } = parseArgs({
2490
+ args,
2491
+ allowPositionals: false,
2492
+ strict: true,
2493
+ options: {
2494
+ help: { type: 'boolean', short: 'h' },
2495
+ json: { type: 'boolean' },
2496
+ },
2497
+ });
2498
+ return { help: Boolean(values.help), json: Boolean(values.json) };
2499
+ }
2500
+ catch (error) {
2501
+ throw new CliError(String(error), { code: 'INVALID_ARGS', exitCode: 2 });
2502
+ }
2503
+ }
2504
+ function parseAuthLiveFlags(args) {
2505
+ let parsed;
2506
+ try {
2507
+ parsed = parseArgs({
2508
+ args,
2509
+ allowPositionals: false,
2510
+ strict: true,
2511
+ tokens: true,
2512
+ options: {
2513
+ help: { type: 'boolean', short: 'h' },
2514
+ json: { type: 'boolean' },
2515
+ 'timeout-ms': { type: 'string' },
2516
+ },
2517
+ });
2518
+ }
2519
+ catch (error) {
2520
+ throw new CliError(String(error), { code: 'INVALID_ARGS', exitCode: 2 });
2521
+ }
2522
+ const timeoutOccurrences = parsed.tokens.filter((entry) => entry.kind === 'option' && entry.name === 'timeout-ms').length;
2523
+ if (timeoutOccurrences > 1) {
2524
+ throw new CliError('Option --timeout-ms may be provided only once.', {
2525
+ code: 'INVALID_ARGS',
2526
+ exitCode: 2,
2527
+ });
2528
+ }
2529
+ const timeoutText = parsed.values['timeout-ms'];
2530
+ const timeoutMs = typeof timeoutText === 'string' ? Number(timeoutText) : 10_000;
2531
+ if (!Number.isSafeInteger(timeoutMs) ||
2532
+ timeoutMs <= 0 ||
2533
+ timeoutMs > AUTH_IDENTITY_MAX_TIMEOUT_MS) {
2534
+ throw new CliError(`Expected --timeout-ms to be an integer between 1 and ${AUTH_IDENTITY_MAX_TIMEOUT_MS}.`, { code: 'INVALID_TIMEOUT', exitCode: 2 });
2535
+ }
2536
+ return {
2537
+ help: Boolean(parsed.values.help),
2538
+ json: Boolean(parsed.values.json),
2539
+ timeoutMs,
2540
+ };
2541
+ }
2372
2542
  function parseRemoteFlags(args) {
2373
2543
  let values;
2374
2544
  try {
@@ -5543,6 +5713,9 @@ export async function executeCli(argv, deps) {
5543
5713
  try {
5544
5714
  const { flags, command, subcommand, commandArgs } = parseCommandLine(argv);
5545
5715
  const authIdentityReceiptImpl = deps.runAuthIdentityReceiptImpl ?? runAuthIdentityReceipt;
5716
+ const oauthLoginImpl = deps.loginWithSupabaseOAuthImpl ?? loginWithSupabaseOAuth;
5717
+ const oauthLogoutImpl = deps.logoutSupabaseUserSessionImpl ?? logoutSupabaseUserSession;
5718
+ const authStatusImpl = deps.inspectSupabaseAuthStatusImpl ?? inspectSupabaseAuthStatus;
5546
5719
  const publishImpl = deps.runPublishImpl ?? runPublish;
5547
5720
  const validationImpl = deps.runValidationImpl ?? runValidation;
5548
5721
  const lcaReleaseImpl = deps.runLcaReleaseImpl ?? runLcaRelease;
@@ -5671,6 +5844,90 @@ export async function executeCli(argv, deps) {
5671
5844
  stderr: '',
5672
5845
  };
5673
5846
  }
5847
+ if (command === 'auth' && subcommand === 'status') {
5848
+ const authFlags = parseAuthLogoutFlags(commandArgs);
5849
+ if (authFlags.help) {
5850
+ return { exitCode: 0, stdout: `${renderAuthStatusHelp()}\n`, stderr: '' };
5851
+ }
5852
+ const receipt = authStatusImpl({ runtime: requireSupabaseRestRuntime(deps.env) });
5853
+ return {
5854
+ exitCode: receipt.status === 'ready' ? 0 : 1,
5855
+ stdout: stringifyJson(receipt, authFlags.json),
5856
+ stderr: '',
5857
+ };
5858
+ }
5859
+ if (command === 'auth' && subcommand === 'whoami') {
5860
+ const authFlags = parseAuthLiveFlags(commandArgs);
5861
+ if (authFlags.help) {
5862
+ return { exitCode: 0, stdout: `${renderAuthWhoamiHelp()}\n`, stderr: '' };
5863
+ }
5864
+ const receipt = await authIdentityReceiptImpl({
5865
+ env: deps.env,
5866
+ fetchImpl: deps.fetchImpl,
5867
+ cliVersion: loadCliPackageVersion(import.meta.url),
5868
+ expectedProjectRef: null,
5869
+ expectedUserId: null,
5870
+ timeoutMs: authFlags.timeoutMs,
5871
+ });
5872
+ return { exitCode: 0, stdout: stringifyJson(receipt, authFlags.json), stderr: '' };
5873
+ }
5874
+ if (command === 'auth' && subcommand === 'doctor-auth') {
5875
+ const authFlags = parseAuthLiveFlags(commandArgs);
5876
+ if (authFlags.help) {
5877
+ return { exitCode: 0, stdout: `${renderAuthDoctorHelp()}\n`, stderr: '' };
5878
+ }
5879
+ const local = authStatusImpl({ runtime: requireSupabaseRestRuntime(deps.env) });
5880
+ if (local.status !== 'ready') {
5881
+ return {
5882
+ exitCode: 1,
5883
+ stdout: stringifyJson({
5884
+ schemaVersion: 'tiangong.cli-auth-doctor.v1',
5885
+ status: 'login-required',
5886
+ local,
5887
+ live: null,
5888
+ }, authFlags.json),
5889
+ stderr: '',
5890
+ };
5891
+ }
5892
+ const live = await authIdentityReceiptImpl({
5893
+ env: deps.env,
5894
+ fetchImpl: deps.fetchImpl,
5895
+ cliVersion: loadCliPackageVersion(import.meta.url),
5896
+ expectedProjectRef: null,
5897
+ expectedUserId: null,
5898
+ timeoutMs: authFlags.timeoutMs,
5899
+ });
5900
+ return {
5901
+ exitCode: 0,
5902
+ stdout: stringifyJson({
5903
+ schemaVersion: 'tiangong.cli-auth-doctor.v1',
5904
+ status: 'passed',
5905
+ local,
5906
+ live,
5907
+ }, authFlags.json),
5908
+ stderr: '',
5909
+ };
5910
+ }
5911
+ if (command === 'auth' && subcommand === 'login') {
5912
+ const authFlags = parseAuthLoginFlags(commandArgs);
5913
+ if (authFlags.help) {
5914
+ return { exitCode: 0, stdout: `${renderAuthLoginHelp()}\n`, stderr: '' };
5915
+ }
5916
+ const receipt = await oauthLoginImpl({
5917
+ runtime: requireSupabaseRestRuntime(deps.env),
5918
+ fetchImpl: deps.fetchImpl,
5919
+ loginTimeoutMs: authFlags.timeoutMs,
5920
+ });
5921
+ return { exitCode: 0, stdout: stringifyJson(receipt, authFlags.json), stderr: '' };
5922
+ }
5923
+ if (command === 'auth' && subcommand === 'logout') {
5924
+ const authFlags = parseAuthLogoutFlags(commandArgs);
5925
+ if (authFlags.help) {
5926
+ return { exitCode: 0, stdout: `${renderAuthLogoutHelp()}\n`, stderr: '' };
5927
+ }
5928
+ const receipt = await oauthLogoutImpl({ runtime: requireSupabaseRestRuntime(deps.env) });
5929
+ return { exitCode: 0, stdout: stringifyJson(receipt, authFlags.json), stderr: '' };
5930
+ }
5674
5931
  if (command === 'search' && !subcommand && commandArgs.includes('--help')) {
5675
5932
  return { exitCode: 0, stdout: `${renderSearchHelp()}\n`, stderr: '' };
5676
5933
  }