lh-ops 0.1.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 (49) hide show
  1. package/README.md +93 -0
  2. package/bin/lh-ops +2 -0
  3. package/dist/commands/audit.js +66 -0
  4. package/dist/commands/audit.js.map +1 -0
  5. package/dist/commands/cache-coverage.js +54 -0
  6. package/dist/commands/cache-coverage.js.map +1 -0
  7. package/dist/commands/export.js +138 -0
  8. package/dist/commands/export.js.map +1 -0
  9. package/dist/commands/login.js +50 -0
  10. package/dist/commands/login.js.map +1 -0
  11. package/dist/commands/member.js +25 -0
  12. package/dist/commands/member.js.map +1 -0
  13. package/dist/commands/models.js +23 -0
  14. package/dist/commands/models.js.map +1 -0
  15. package/dist/commands/org-list.js +45 -0
  16. package/dist/commands/org-list.js.map +1 -0
  17. package/dist/commands/org-usage.js +77 -0
  18. package/dist/commands/org-usage.js.map +1 -0
  19. package/dist/commands/org.js +72 -0
  20. package/dist/commands/org.js.map +1 -0
  21. package/dist/commands/phase2-status.js +41 -0
  22. package/dist/commands/phase2-status.js.map +1 -0
  23. package/dist/commands/plan.js +70 -0
  24. package/dist/commands/plan.js.map +1 -0
  25. package/dist/commands/pricebook.js +39 -0
  26. package/dist/commands/pricebook.js.map +1 -0
  27. package/dist/commands/scanners.js +33 -0
  28. package/dist/commands/scanners.js.map +1 -0
  29. package/dist/commands/statement.js +101 -0
  30. package/dist/commands/statement.js.map +1 -0
  31. package/dist/commands/subscription.js +32 -0
  32. package/dist/commands/subscription.js.map +1 -0
  33. package/dist/commands/token.js +61 -0
  34. package/dist/commands/token.js.map +1 -0
  35. package/dist/commands/topup.js +34 -0
  36. package/dist/commands/topup.js.map +1 -0
  37. package/dist/index.js +795 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/lib/client.js +11 -0
  40. package/dist/lib/client.js.map +1 -0
  41. package/dist/lib/credentials.js +68 -0
  42. package/dist/lib/credentials.js.map +1 -0
  43. package/dist/lib/errors.js +45 -0
  44. package/dist/lib/errors.js.map +1 -0
  45. package/dist/lib/output.js +41 -0
  46. package/dist/lib/output.js.map +1 -0
  47. package/dist/lib/resolve-client.js +42 -0
  48. package/dist/lib/resolve-client.js.map +1 -0
  49. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # lh-ops
2
+
3
+ Platform Operator CLI for LH Enterprise. Smoke-A subset of issue #36 — see
4
+ `docs/requirements/platform-admin-user-stories.md` for the full surface.
5
+
6
+ ## Installation (local dev)
7
+
8
+ ```bash
9
+ cd packages/lh-ops
10
+ npm install
11
+ npm run build
12
+ npm link # makes `lh-ops` globally available
13
+ ```
14
+
15
+ ## Configuration
16
+
17
+ | Source | Purpose |
18
+ |-------------------|---------------------------------------------|
19
+ | `LH_OPS_API_BASE` | Default API base URL (default `http://localhost:3002`) |
20
+ | `~/.lh-ops/credentials.json` | Persisted access token (mode `0600`) — written by `lh-ops login` |
21
+ | `--token <jwt>` | Per-call override (skip login) |
22
+ | `--api-base <url>`| Per-call override |
23
+ | `--json` | Emit JSON instead of human-readable output |
24
+
25
+ ## Smoke-A subset commands
26
+
27
+ ```bash
28
+ # 1. Authenticate (persists token to ~/.lh-ops/credentials.json)
29
+ lh-ops login --email admin@lh.dev --password "${PLATFORM_BOOTSTRAP_PASSWORD}"
30
+
31
+ # 2. Catalog: publish + manage plans / pricebooks (#434, ADR-020 §2)
32
+ lh-ops plan publish --file ./plans/coding-pro.json
33
+ lh-ops plan list --status ACTIVE # also --family CODING
34
+ lh-ops plan show <planId> # prints updatedAt = --if-match token
35
+ lh-ops plan update <planId> --file ./plans/coding-pro.json --if-match <updatedAt>
36
+ lh-ops plan archive <planId> --if-match <updatedAt>
37
+ lh-ops pricebook publish --file ./pricebook/2026-q1.json
38
+ lh-ops pricebook activate 2026-q1
39
+ lh-ops pricebook list
40
+
41
+ # 3. Provision org + Enterprise Admin (US-12). Temp password is shown once.
42
+ lh-ops org create \
43
+ --name "Acme" \
44
+ --admin-email alice@acme.com \
45
+ --admin-name "Alice Smith" \
46
+ --plan <planId>
47
+
48
+ # 4. Top up org balance
49
+ lh-ops topup execute --org <orgId> --usd 100 --memo "wire 2026-04-30"
50
+
51
+ # 10. Show org snapshot (used by Smoke A step 10)
52
+ lh-ops org show <orgId>
53
+ ```
54
+
55
+ Add `--json` to any command for machine-friendly output (e.g. piping into
56
+ `jq` or another LLM-driven script).
57
+
58
+ ## Command taxonomy
59
+
60
+ - Each resource is a top-level **singular** noun group with verb subcommands:
61
+ `plan`, `pricebook`, `org`, `subscription`, `topup`, `audit`, `scanners`,
62
+ `models` (`org`, not `orgs`).
63
+ - Operations scoped to one org take `<orgId>` and live under `org`
64
+ (`org show`, `org statement`, `org tokens`, `org export-keys`,
65
+ `org rename/suspend/activate`); a command stays **top-level** when it manages
66
+ a resource type across the platform (`plan`, `pricebook`, `subscription`,
67
+ `topup`, `audit query`, `models list`, `scanners status`) even when it takes
68
+ `--org` as a parameter.
69
+
70
+ ## Deferred (post-Smoke-A)
71
+
72
+ Shipped since Smoke-A: `plan list/show/update/archive`, `pricebook list`,
73
+ `org list / statement / export-{audit,keys} / tokens`, `org suspend/resume`,
74
+ `topup execute/list`, `audit query`, `scanners status`, `phase2-status`.
75
+
76
+ Still deferred:
77
+
78
+ - `org close` — needs #27 close flow
79
+ - `org contract show/extend/change-tier` — needs #29
80
+ - `topup record/approve/retry-sync` — needs the full state machine (#28)
81
+ - `checkpoint run/show/export` — async monthly reconciliation job (#30);
82
+ distinct from the synchronous `org statement` (US-OP-10, shipped)
83
+ - `operator invite/deactivate/reset-password` — deferred indefinitely
84
+ (ADR-020 §5: single shared operator account)
85
+ - `alias publish/show/history` — **withdrawn** (ADR-019; routing lives in new-api)
86
+ - OpenAPI auto-generation; npm publishing (private package)
87
+
88
+ ## Tests
89
+
90
+ ```bash
91
+ npm test # jest unit tests (commands + renderers)
92
+ npm run build # tsc → dist/
93
+ ```
package/bin/lh-ops ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../dist/index.js');
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.queryAudit = queryAudit;
4
+ exports.formatAudit = formatAudit;
5
+ exports.renderAudit = renderAudit;
6
+ const resolve_client_1 = require("../lib/resolve-client");
7
+ // Matches strict ISO8601 with date + time + Z/offset (e.g. 2026-05-01T00:00:00.000Z).
8
+ // We reject loose formats client-side so the operator sees a useful error rather
9
+ // than a 400 from the backend's class-validator @IsISO8601 check.
10
+ const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
11
+ function assertIso(label, value) {
12
+ if (value === undefined)
13
+ return;
14
+ if (!ISO_RE.test(value) || Number.isNaN(Date.parse(value))) {
15
+ throw new Error(`--${label} must be ISO8601 (e.g. 2026-04-01T00:00:00.000Z), got: ${value}`);
16
+ }
17
+ }
18
+ async function queryAudit(input) {
19
+ assertIso('from', input.from);
20
+ assertIso('to', input.to);
21
+ const client = await (0, resolve_client_1.resolveClient)(input);
22
+ const query = {
23
+ orgId: input.orgId,
24
+ action: input.action,
25
+ actor: input.actor,
26
+ from: input.from,
27
+ to: input.to,
28
+ limit: input.limit,
29
+ };
30
+ return client.request('/platform/audit', {
31
+ method: 'GET',
32
+ query,
33
+ });
34
+ }
35
+ /**
36
+ * Format audit rows for the default (non-JSON) CLI output.
37
+ * One line per row: `<occurredAt> <action> <actor> <orgId-short> <payload>`.
38
+ * orgId is truncated to the first 8 chars to keep lines readable for cuid/ulid.
39
+ */
40
+ function formatAudit(rows) {
41
+ return (rows
42
+ .map((r) => {
43
+ const orgShort = r.orgId ? r.orgId.slice(0, 8) : '-';
44
+ const payload = r.payload === null || r.payload === undefined
45
+ ? ''
46
+ : JSON.stringify(r.payload);
47
+ return `${r.occurredAt} ${r.action} ${r.actor} ${orgShort} ${payload}`.trimEnd();
48
+ })
49
+ .join('\n') + (rows.length > 0 ? '\n' : ''));
50
+ }
51
+ /**
52
+ * Top-level renderer: branches on `--json` (raw JSON pass-through) vs the
53
+ * default human-readable line format. Kept separate from formatAudit so
54
+ * --json bypasses the formatter entirely — operators piping into `jq` get
55
+ * the raw backend response shape.
56
+ */
57
+ function renderAudit(rows, opts) {
58
+ if (opts.json) {
59
+ return JSON.stringify(rows, null, 2);
60
+ }
61
+ if (rows.length === 0) {
62
+ return '(no audit records match)\n';
63
+ }
64
+ return formatAudit(rows);
65
+ }
66
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":";;AAoCA,gCAiBC;AAOD,kCAYC;AAQD,kCAQC;AAxFD,0DAAmE;AAsBnE,sFAAsF;AACtF,iFAAiF;AACjF,kEAAkE;AAClE,MAAM,MAAM,GAAG,mEAAmE,CAAC;AAEnF,SAAS,SAAS,CAAC,KAAa,EAAE,KAAyB;IACzD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO;IAChC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAC3D,MAAM,IAAI,KAAK,CACb,KAAK,KAAK,0DAA0D,KAAK,EAAE,CAC5E,CAAC;IACJ,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,UAAU,CAAC,KAAsB;IACrD,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9B,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;IAE1B,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC;IAC1C,MAAM,KAAK,GAAgD;QACzD,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC;IACF,OAAO,MAAM,CAAC,OAAO,CAAa,iBAAiB,EAAE;QACnD,MAAM,EAAE,KAAK;QACb,KAAK;KACN,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,SAAgB,WAAW,CAAC,IAAgB;IAC1C,OAAO,CACL,IAAI;SACD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,QAAQ,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACrD,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,SAAS;YAC3D,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC;IACnF,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAC9C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,SAAgB,WAAW,CAAC,IAAgB,EAAE,IAAuB;IACnE,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,4BAA4B,CAAC;IACtC,CAAC;IACD,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getCacheCoverage = getCacheCoverage;
4
+ exports.renderCacheCoverage = renderCacheCoverage;
5
+ const lh_api_client_1 = require("lh-api-client");
6
+ const resolve_client_1 = require("../lib/resolve-client");
7
+ /**
8
+ * #595 — `lh-ops cache-coverage`. Platform-wide cache-hit audit (which models
9
+ * return prompt-cache tokens + hit rate). CLI wrapper around lh-api-client's
10
+ * `getCacheCoverage`; server stays JSON-only, md rendering is client-side.
11
+ */
12
+ async function getCacheCoverage(input) {
13
+ const client = await (0, resolve_client_1.resolveClient)(input);
14
+ return (0, lh_api_client_1.getCacheCoverage)(client);
15
+ }
16
+ /** Format dispatch: json (canonical) | md (human-readable audit). */
17
+ function renderCacheCoverage(c, opts) {
18
+ if (opts.format === 'json')
19
+ return JSON.stringify(c, null, 2);
20
+ return renderCacheCoverageMd(c);
21
+ }
22
+ function renderCacheCoverageMd(c) {
23
+ const tok = (n) => compactTokens(n);
24
+ const lines = [];
25
+ lines.push('# Cache coverage (platform-wide)');
26
+ lines.push('');
27
+ lines.push(`- Rows: ${c.totals.rows}`);
28
+ lines.push(`- Cache tokens: ${tok(c.totals.cacheTokens)}`);
29
+ lines.push(`- Rows with cache: ${c.totals.rowsWithCache} (${c.totals.pctCache}%)`);
30
+ lines.push('');
31
+ if (c.models.length > 0) {
32
+ lines.push('## By model (sorted by rows DESC)');
33
+ lines.push('');
34
+ lines.push('| Model | Rows | Cache tokens | Rows w/ cache | % cache |');
35
+ lines.push('|---|---|---|---|---|');
36
+ for (const m of c.models) {
37
+ // ⚠️ flags 0%-cache models — either tiny sample or an upstream that
38
+ // doesn't return cache (worth a routing check).
39
+ const flag = m.pctCache === 0 ? ' ⚠️' : '';
40
+ lines.push(`| ${m.model} | ${m.rows} | ${tok(m.cacheTokens)} | ${m.rowsWithCache} | ${m.pctCache}%${flag} |`);
41
+ }
42
+ lines.push('');
43
+ }
44
+ return lines.join('\n') + '\n';
45
+ }
46
+ // mirrors org-usage compactTokens: 106711192 → "106.7M", 6506111 → "6.5M".
47
+ function compactTokens(n) {
48
+ if (n >= 1_000_000)
49
+ return `${(n / 1_000_000).toFixed(1)}M`;
50
+ if (n >= 1_000)
51
+ return `${(n / 1_000).toFixed(1)}K`;
52
+ return String(n);
53
+ }
54
+ //# sourceMappingURL=cache-coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache-coverage.js","sourceRoot":"","sources":["../../src/commands/cache-coverage.ts"],"names":[],"mappings":";;AAeA,4CAKC;AAGD,kDAMC;AA7BD,iDAGuB;AACvB,0DAAmE;AAMnE;;;;GAIG;AACI,KAAK,UAAU,gBAAgB,CACpC,KAA4B;IAE5B,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,IAAA,gCAAmB,EAAC,MAAM,CAAC,CAAC;AACrC,CAAC;AAED,qEAAqE;AACrE,SAAgB,mBAAmB,CACjC,CAAuB,EACvB,IAA+B;IAE/B,IAAI,IAAI,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9D,OAAO,qBAAqB,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,qBAAqB,CAAC,CAAuB;IACpD,MAAM,GAAG,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;IAC/C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACvC,KAAK,CAAC,IAAI,CAAC,mBAAmB,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;IAC3D,KAAK,CAAC,IAAI,CACR,sBAAsB,CAAC,CAAC,MAAM,CAAC,aAAa,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,IAAI,CACvE,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;QAChD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC;QACxE,KAAK,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACpC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;YACzB,oEAAoE;YACpE,gDAAgD;YAChD,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3C,KAAK,CAAC,IAAI,CACR,KAAK,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,aAAa,MAAM,CAAC,CAAC,QAAQ,IAAI,IAAI,IAAI,CAClG,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AACjC,CAAC;AAED,2EAA2E;AAC3E,SAAS,aAAa,CAAC,CAAS;IAC9B,IAAI,CAAC,IAAI,SAAS;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5D,IAAI,CAAC,IAAI,KAAK;QAAE,OAAO,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;IACpD,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC"}
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.chainOfCustodySha256 = chainOfCustodySha256;
4
+ exports.exportAudit = exportAudit;
5
+ exports.exportKeys = exportKeys;
6
+ exports.renderAuditExportCsv = renderAuditExportCsv;
7
+ exports.renderAuditExportJson = renderAuditExportJson;
8
+ exports.renderKeysExportCsv = renderKeysExportCsv;
9
+ exports.renderKeysExportJson = renderKeysExportJson;
10
+ const crypto_1 = require("crypto");
11
+ const resolve_client_1 = require("../lib/resolve-client");
12
+ const AUDIT_PAGE_SIZE = 200;
13
+ /**
14
+ * Cycle 3 — Chain-of-custody hash. Deterministic JSON.stringify with
15
+ * sorted keys, sha256 over the resulting bytes. Operators re-running
16
+ * the export against the same DB state must get the same hash;
17
+ * mutation of any field changes the hash.
18
+ */
19
+ function chainOfCustodySha256(rows) {
20
+ const canonical = JSON.stringify(rows, sortedReplacer());
21
+ return (0, crypto_1.createHash)('sha256').update(canonical).digest('hex');
22
+ }
23
+ function sortedReplacer() {
24
+ return (_key, value) => {
25
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
26
+ const sorted = {};
27
+ for (const k of Object.keys(value).sort()) {
28
+ sorted[k] = value[k];
29
+ }
30
+ return sorted;
31
+ }
32
+ return value;
33
+ };
34
+ }
35
+ /**
36
+ * Cycle 4 — Audit export: loop /platform/audit with offsets until a
37
+ * short page (< AUDIT_PAGE_SIZE) signals end. Backend caps limit at 200;
38
+ * 90-day windows can easily exceed that, so the CLI handles pagination.
39
+ */
40
+ async function exportAudit(input) {
41
+ const client = await (0, resolve_client_1.resolveClient)(input);
42
+ const all = [];
43
+ let offset = 0;
44
+ while (true) {
45
+ const page = await client.request('/platform/audit', {
46
+ method: 'GET',
47
+ query: {
48
+ orgId: input.orgId,
49
+ from: input.from,
50
+ to: input.to,
51
+ action: input.action,
52
+ actor: input.actor,
53
+ limit: String(AUDIT_PAGE_SIZE),
54
+ offset: String(offset),
55
+ },
56
+ });
57
+ all.push(...page);
58
+ if (page.length < AUDIT_PAGE_SIZE)
59
+ break;
60
+ offset += AUDIT_PAGE_SIZE;
61
+ }
62
+ return all;
63
+ }
64
+ /**
65
+ * Cycle 7 — Keys export client call. Always passes include-revoked=1
66
+ * because the compliance use case wants every key the org has ever
67
+ * had, not just the active set.
68
+ */
69
+ async function exportKeys(input) {
70
+ const client = await (0, resolve_client_1.resolveClient)(input);
71
+ return client.request(`/platform/orgs/${encodeURIComponent(input.orgId)}/keys`, {
72
+ method: 'GET',
73
+ query: { 'include-revoked': '1' },
74
+ });
75
+ }
76
+ /**
77
+ * Cycle 5 — CSV serializer for audit export. Trailing
78
+ * `# chain-of-custody-sha256: <hex>` line satisfies acceptance criteria.
79
+ * Header row + empty body + hash for empty input.
80
+ */
81
+ const AUDIT_COLUMNS = [
82
+ 'id',
83
+ 'actor',
84
+ 'action',
85
+ 'orgId',
86
+ 'occurredAt',
87
+ 'payload',
88
+ ];
89
+ function renderAuditExportCsv(rows) {
90
+ return renderCsv(rows, AUDIT_COLUMNS);
91
+ }
92
+ function renderAuditExportJson(rows) {
93
+ return renderJson(rows);
94
+ }
95
+ const KEY_COLUMNS = [
96
+ 'id',
97
+ 'keyPrefix',
98
+ 'label',
99
+ 'status',
100
+ 'memberId',
101
+ 'subscriptionId',
102
+ 'createdAt',
103
+ 'claimedAt',
104
+ 'acknowledgedBy',
105
+ 'lastUsageAt',
106
+ ];
107
+ function renderKeysExportCsv(rows) {
108
+ return renderCsv(rows, KEY_COLUMNS);
109
+ }
110
+ function renderKeysExportJson(rows) {
111
+ return renderJson(rows);
112
+ }
113
+ function renderCsv(rows, columns) {
114
+ const header = columns.join(',');
115
+ const bodyLines = rows.map((row) => columns.map((col) => csvCell(row[col])).join(','));
116
+ const hash = chainOfCustodySha256(rows);
117
+ // Always: header + (zero or more) body lines + hash comment.
118
+ return ([header, ...bodyLines, `# chain-of-custody-sha256: ${hash}`].join('\n') +
119
+ '\n');
120
+ }
121
+ function csvCell(value) {
122
+ if (value === null || value === undefined)
123
+ return '';
124
+ const s = typeof value === 'string' ? value : JSON.stringify(value);
125
+ // Escape per RFC 4180: any cell containing a comma, quote, or newline
126
+ // must be wrapped in quotes with internal quotes doubled.
127
+ if (/[,"\n]/.test(s)) {
128
+ return `"${s.replace(/"/g, '""')}"`;
129
+ }
130
+ return s;
131
+ }
132
+ function renderJson(rows) {
133
+ return JSON.stringify({
134
+ data: rows,
135
+ chain_of_custody_sha256: chainOfCustodySha256(rows),
136
+ });
137
+ }
138
+ //# sourceMappingURL=export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export.js","sourceRoot":"","sources":["../../src/commands/export.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.login = login;
4
+ exports.readJsonFile = readJsonFile;
5
+ exports.renderLogin = renderLogin;
6
+ const fs_1 = require("fs");
7
+ const client_1 = require("../lib/client");
8
+ const credentials_1 = require("../lib/credentials");
9
+ const output_1 = require("../lib/output");
10
+ /**
11
+ * Authenticate against /platform/auth/login and persist the access token.
12
+ * Returns the operator info so callers can render `--json` output.
13
+ */
14
+ async function login(input) {
15
+ const client = new client_1.Client({ apiBase: input.apiBase });
16
+ const result = await client.request('/platform/auth/login', {
17
+ method: 'POST',
18
+ body: { email: input.email, password: input.password },
19
+ });
20
+ await (0, credentials_1.saveCredentials)({
21
+ accessToken: result.accessToken,
22
+ apiBase: input.apiBase,
23
+ operatorEmail: result.operator.email,
24
+ savedAt: new Date().toISOString(),
25
+ }, input.credentialsPath);
26
+ return result;
27
+ }
28
+ /**
29
+ * Read a JSON file (used by `plan publish --file` etc.). Kept colocated with
30
+ * the login command for now since both are I/O helpers; future PR can move
31
+ * to lib/ if more callers appear.
32
+ */
33
+ async function readJsonFile(filePath) {
34
+ const raw = await fs_1.promises.readFile(filePath, 'utf8');
35
+ return JSON.parse(raw);
36
+ }
37
+ function renderLogin(result, json) {
38
+ if (json) {
39
+ (0, output_1.printValue)(result, { json: true });
40
+ return;
41
+ }
42
+ (0, output_1.printValue)({
43
+ operatorId: result.operator.id,
44
+ email: result.operator.email,
45
+ name: result.operator.name,
46
+ role: result.operator.role,
47
+ }, { json: false });
48
+ (0, output_1.printSecret)('Access token saved to ~/.lh-ops/credentials.json', result.accessToken.slice(0, 24) + '…');
49
+ }
50
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":";;AAsBA,sBAkBC;AAOD,oCAGC;AAED,kCAkBC;AAtED,2BAA4C;AAC5C,0CAAuC;AACvC,oDAAqD;AACrD,0CAAwD;AAexD;;;GAGG;AACI,KAAK,UAAU,KAAK,CAAC,KAAiB;IAC3C,MAAM,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAgB,sBAAsB,EAAE;QACzE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE;KACvD,CAAC,CAAC;IAEH,MAAM,IAAA,6BAAe,EACnB;QACE,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;QACpC,OAAO,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KAClC,EACD,KAAK,CAAC,eAAe,CACtB,CAAC;IAEF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;GAIG;AACI,KAAK,UAAU,YAAY,CAAc,QAAgB;IAC9D,MAAM,GAAG,GAAG,MAAM,aAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;AAC9B,CAAC;AAED,SAAgB,WAAW,CAAC,MAAqB,EAAE,IAAa;IAC9D,IAAI,IAAI,EAAE,CAAC;QACT,IAAA,mBAAU,EAAC,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IACD,IAAA,mBAAU,EACR;QACE,UAAU,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE;QAC9B,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,KAAK;QAC5B,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;QAC1B,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI;KAC3B,EACD,EAAE,IAAI,EAAE,KAAK,EAAE,CAChB,CAAC;IACF,IAAA,oBAAW,EACT,kDAAkD,EAClD,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CACtC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.unfreezeMember = unfreezeMember;
4
+ exports.setMemberBudgetCap = setMemberBudgetCap;
5
+ const resolve_client_1 = require("../lib/resolve-client");
6
+ /**
7
+ * Lift a budget-guardian freeze (FROZEN → ACTIVE). Mirrors
8
+ * `POST /platform/orgs/:orgId/members/:memberId/unfreeze`. The backend 409s if
9
+ * the member is not frozen.
10
+ */
11
+ async function unfreezeMember(input) {
12
+ const client = await (0, resolve_client_1.resolveClient)(input);
13
+ return client.request(`/platform/orgs/${encodeURIComponent(input.orgId)}/members/${encodeURIComponent(input.memberId)}/unfreeze`, { method: 'POST' });
14
+ }
15
+ /**
16
+ * Set, change, or clear a member's daily budget cap. The amount is in the
17
+ * org's settlement currency (backend converts to USD-canonical). `null`
18
+ * clears the cap. Mirrors
19
+ * `PATCH /platform/orgs/:orgId/members/:memberId/budget-cap`.
20
+ */
21
+ async function setMemberBudgetCap(input) {
22
+ const client = await (0, resolve_client_1.resolveClient)(input);
23
+ return client.request(`/platform/orgs/${encodeURIComponent(input.orgId)}/members/${encodeURIComponent(input.memberId)}/budget-cap`, { method: 'PATCH', body: { dailyBudgetCap: input.dailyBudgetCap } });
24
+ }
25
+ //# sourceMappingURL=member.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"member.js","sourceRoot":"","sources":["../../src/commands/member.ts"],"names":[],"mappings":";;AAgBA,wCAUC;AAeD,gDAUC;AAnDD,0DAAmE;AAWnE;;;;GAIG;AACI,KAAK,UAAU,cAAc,CAClC,KAA0B;IAE1B,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,OAAO,CACnB,kBAAkB,kBAAkB,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,kBAAkB,CAC7E,KAAK,CAAC,QAAQ,CACf,WAAW,EACZ,EAAE,MAAM,EAAE,MAAM,EAAE,CACnB,CAAC;AACJ,CAAC;AASD;;;;;GAKG;AACI,KAAK,UAAU,kBAAkB,CACtC,KAA8B;IAE9B,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC,OAAO,CACnB,kBAAkB,kBAAkB,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,kBAAkB,CAC7E,KAAK,CAAC,QAAQ,CACf,aAAa,EACd,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,cAAc,EAAE,KAAK,CAAC,cAAc,EAAE,EAAE,CACpE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listModels = listModels;
4
+ exports.renderModels = renderModels;
5
+ const lh_api_client_1 = require("lh-api-client");
6
+ const resolve_client_1 = require("../lib/resolve-client");
7
+ /**
8
+ * Canary prep (#445): list the models the upstream new-api offers, so the
9
+ * operator knows the real canonical names to put in plan / price-book JSON.
10
+ */
11
+ async function listModels(input) {
12
+ const client = await (0, resolve_client_1.resolveClient)(input);
13
+ return (0, lh_api_client_1.listUpstreamModels)(client);
14
+ }
15
+ function renderModels(models, opts) {
16
+ if (opts.json)
17
+ return JSON.stringify(models);
18
+ if (models.length === 0) {
19
+ return '(no models reported by upstream new-api)\n';
20
+ }
21
+ return models.join('\n') + '\n';
22
+ }
23
+ //# sourceMappingURL=models.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"models.js","sourceRoot":"","sources":["../../src/commands/models.ts"],"names":[],"mappings":";;AAOA,gCAGC;AAED,oCASC;AArBD,iDAA4E;AAC5E,0DAAmE;AAEnE;;;GAGG;AACI,KAAK,UAAU,UAAU,CAAC,KAAkB;IACjD,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,IAAA,kCAAqB,EAAC,MAAM,CAAC,CAAC;AACvC,CAAC;AAED,SAAgB,YAAY,CAC1B,MAAgB,EAChB,IAAuB;IAEvB,IAAI,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7C,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,4CAA4C,CAAC;IACtD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;AAClC,CAAC"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listOrgs = listOrgs;
4
+ exports.formatOrgList = formatOrgList;
5
+ exports.renderOrgList = renderOrgList;
6
+ const lh_api_client_1 = require("lh-api-client");
7
+ const resolve_client_1 = require("../lib/resolve-client");
8
+ /**
9
+ * US-OP-9 (T2 of v0.1.7 #279 / #280). Cross-org overview for platform
10
+ * operators — CLI-side wrapper around lh-api-client's `listOrgs`. The
11
+ * SDK owns query-param shaping; this wrapper just resolves the client
12
+ * from CLI options (token+apiBase or stored credentials).
13
+ */
14
+ async function listOrgs(input) {
15
+ const client = await (0, resolve_client_1.resolveClient)(input);
16
+ return (0, lh_api_client_1.listOrgs)(client, input);
17
+ }
18
+ /**
19
+ * Cycle 10 — default human-readable formatter.
20
+ * One line per row: `<orgId-short> <name> <status> <balance> <currency>`.
21
+ * id truncated to first 8 chars to keep lines readable for uuid identifiers.
22
+ */
23
+ function formatOrgList(rows) {
24
+ return (rows
25
+ .map((r) => {
26
+ const idShort = r.id.slice(0, 8);
27
+ return `${idShort} ${r.name} ${r.status} ${r.balance} ${r.currency}`;
28
+ })
29
+ .join('\n') + (rows.length > 0 ? '\n' : ''));
30
+ }
31
+ /**
32
+ * Cycle 11 (--json) + Cycle 12 (empty branch) — top-level renderer.
33
+ * Mirrors `renderAudit` (audit.ts): --json bypasses the formatter so
34
+ * operators piping into jq get the raw backend response.
35
+ */
36
+ function renderOrgList(rows, opts) {
37
+ if (opts.json) {
38
+ return JSON.stringify(rows);
39
+ }
40
+ if (rows.length === 0) {
41
+ return '(no orgs match)\n';
42
+ }
43
+ return formatOrgList(rows);
44
+ }
45
+ //# sourceMappingURL=org-list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-list.js","sourceRoot":"","sources":["../../src/commands/org-list.ts"],"names":[],"mappings":";;AAaA,4BAGC;AAOD,sCASC;AAOD,sCAWC;AAlDD,iDAAkF;AAClF,0DAAmE;AAMnE;;;;;GAKG;AACI,KAAK,UAAU,QAAQ,CAAC,KAAoB;IACjD,MAAM,MAAM,GAAG,MAAM,IAAA,8BAAa,EAAC,KAAK,CAAC,CAAC;IAC1C,OAAO,IAAA,wBAAW,EAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACpC,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa,CAAC,IAAkB;IAC9C,OAAO,CACL,IAAI;SACD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,OAAO,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACjC,OAAO,GAAG,OAAO,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;IACvE,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAC9C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa,CAC3B,IAAkB,EAClB,IAAuB;IAEvB,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACd,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,mBAAmB,CAAC;IAC7B,CAAC;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getOrgUsage = getOrgUsage;
4
+ exports.renderOrgUsage = renderOrgUsage;
5
+ const lh_api_client_1 = require("lh-api-client");
6
+ const resolve_client_1 = require("../lib/resolve-client");
7
+ /**
8
+ * #589 — `lh-ops org usage <id>`. All-time reconciliation view (current
9
+ * balance + lifetime deposits/charges + usage by billing-decision + by model).
10
+ * CLI-side wrapper around lh-api-client's `getOrgUsage`; server stays
11
+ * JSON-only, presentation (md) is dispatched client-side in `renderOrgUsage`.
12
+ */
13
+ async function getOrgUsage(input) {
14
+ const client = await (0, resolve_client_1.resolveClient)(input);
15
+ return (0, lh_api_client_1.getOrgUsage)(client, { orgId: input.orgId });
16
+ }
17
+ /**
18
+ * Format dispatch: json (canonical) | md (human-readable reconciliation).
19
+ */
20
+ function renderOrgUsage(s, opts) {
21
+ if (opts.format === 'json')
22
+ return JSON.stringify(s, null, 2);
23
+ return renderOrgUsageMd(s);
24
+ }
25
+ function renderOrgUsageMd(s) {
26
+ const rate = s.contractRate ? Number(s.contractRate) : null;
27
+ const ccy = s.currency;
28
+ // amount → "24.1581 USD (≈173.94 CNY)" when a contract rate exists, else raw.
29
+ const amt = (usd) => rate ? `${usd} USD (≈${(Number(usd) * rate).toFixed(2)} ${ccy})` : `${usd} ${ccy}`;
30
+ const tok = (n) => compactTokens(n);
31
+ const lines = [];
32
+ lines.push(`# Usage — ${s.name} (${s.orgId})`);
33
+ lines.push('');
34
+ lines.push('## Balance');
35
+ lines.push(`- Current: ${amt(s.balance)}`);
36
+ lines.push(`- Deposits: ${s.deposits.count} txn, ${amt(s.deposits.amount)}`);
37
+ lines.push(`- Charges: ${s.charges.count} txn, ${amt(s.charges.amount)}`);
38
+ lines.push('');
39
+ lines.push('## Totals (all-time)');
40
+ lines.push(`- Requests: ${s.totals.requests}`);
41
+ lines.push(`- Cost: ${amt(s.totals.cost)}`);
42
+ if (s.totals.firstSeen) {
43
+ lines.push(`- Window: ${s.totals.firstSeen} → ${s.totals.lastSeen}`);
44
+ }
45
+ lines.push('');
46
+ if (s.byBillingDecision.length > 0) {
47
+ lines.push('## By billing decision');
48
+ lines.push('');
49
+ lines.push('| Decision | Requests | Cost |');
50
+ lines.push('|---|---|---|');
51
+ for (const d of s.byBillingDecision) {
52
+ lines.push(`| ${d.billingDecision} | ${d.requests} | ${amt(d.cost)} |`);
53
+ }
54
+ lines.push('');
55
+ }
56
+ if (s.byModel.length > 0) {
57
+ lines.push('## By model');
58
+ lines.push('');
59
+ lines.push('| Model | Requests | Prompt | Completion | Cache | Cost |');
60
+ lines.push('|---|---|---|---|---|---|');
61
+ for (const m of s.byModel) {
62
+ lines.push(`| ${m.model} | ${m.requests} | ${tok(m.promptTokens)} | ${tok(m.completionTokens)} | ${tok(m.cacheTokens)} | ${amt(m.cost)} |`);
63
+ }
64
+ lines.push('');
65
+ }
66
+ return lines.join('\n') + '\n';
67
+ }
68
+ // 118294229 → "118.3M", 6506111 → "6.5M", 140 → "140". Keeps the model table
69
+ // scannable without losing order-of-magnitude intuition.
70
+ function compactTokens(n) {
71
+ if (n >= 1_000_000)
72
+ return `${(n / 1_000_000).toFixed(1)}M`;
73
+ if (n >= 1_000)
74
+ return `${(n / 1_000).toFixed(1)}K`;
75
+ return String(n);
76
+ }
77
+ //# sourceMappingURL=org-usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-usage.js","sourceRoot":"","sources":["../../src/commands/org-usage.ts"],"names":[],"mappings":"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"}