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.
- package/README.md +93 -0
- package/bin/lh-ops +2 -0
- package/dist/commands/audit.js +66 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/cache-coverage.js +54 -0
- package/dist/commands/cache-coverage.js.map +1 -0
- package/dist/commands/export.js +138 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/login.js +50 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/member.js +25 -0
- package/dist/commands/member.js.map +1 -0
- package/dist/commands/models.js +23 -0
- package/dist/commands/models.js.map +1 -0
- package/dist/commands/org-list.js +45 -0
- package/dist/commands/org-list.js.map +1 -0
- package/dist/commands/org-usage.js +77 -0
- package/dist/commands/org-usage.js.map +1 -0
- package/dist/commands/org.js +72 -0
- package/dist/commands/org.js.map +1 -0
- package/dist/commands/phase2-status.js +41 -0
- package/dist/commands/phase2-status.js.map +1 -0
- package/dist/commands/plan.js +70 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/pricebook.js +39 -0
- package/dist/commands/pricebook.js.map +1 -0
- package/dist/commands/scanners.js +33 -0
- package/dist/commands/scanners.js.map +1 -0
- package/dist/commands/statement.js +101 -0
- package/dist/commands/statement.js.map +1 -0
- package/dist/commands/subscription.js +32 -0
- package/dist/commands/subscription.js.map +1 -0
- package/dist/commands/token.js +61 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/commands/topup.js +34 -0
- package/dist/commands/topup.js.map +1 -0
- package/dist/index.js +795 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/client.js +11 -0
- package/dist/lib/client.js.map +1 -0
- package/dist/lib/credentials.js +68 -0
- package/dist/lib/credentials.js.map +1 -0
- package/dist/lib/errors.js +45 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/output.js +41 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/resolve-client.js +42 -0
- package/dist/lib/resolve-client.js.map +1 -0
- 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,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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"}
|