@ora-ai/cli 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/LICENSE +21 -0
- package/README.md +102 -0
- package/dist/auth-commands.d.ts +39 -0
- package/dist/auth-commands.d.ts.map +1 -0
- package/dist/auth-commands.js +116 -0
- package/dist/auth-commands.js.map +1 -0
- package/dist/core/auth.d.ts +41 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +110 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/config.d.ts +26 -0
- package/dist/core/config.d.ts.map +1 -0
- package/dist/core/config.js +51 -0
- package/dist/core/config.js.map +1 -0
- package/dist/core/http.d.ts +46 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +82 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/login.d.ts +14 -0
- package/dist/core/login.d.ts.map +1 -0
- package/dist/core/login.js +70 -0
- package/dist/core/login.js.map +1 -0
- package/dist/core/output.d.ts +18 -0
- package/dist/core/output.d.ts.map +1 -0
- package/dist/core/output.js +94 -0
- package/dist/core/output.js.map +1 -0
- package/dist/core/run-operation.d.ts +57 -0
- package/dist/core/run-operation.d.ts.map +1 -0
- package/dist/core/run-operation.js +119 -0
- package/dist/core/run-operation.js.map +1 -0
- package/dist/core/sse.d.ts +13 -0
- package/dist/core/sse.d.ts.map +1 -0
- package/dist/core/sse.js +63 -0
- package/dist/core/sse.js.map +1 -0
- package/dist/generated/operations.d.ts +47 -0
- package/dist/generated/operations.d.ts.map +1 -0
- package/dist/generated/operations.js +2050 -0
- package/dist/generated/operations.js.map +1 -0
- package/dist/generated/types.d.ts +1039 -0
- package/dist/generated/types.d.ts.map +1 -0
- package/dist/generated/types.js +5 -0
- package/dist/generated/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/derive-prompts.d.ts +44 -0
- package/dist/interactive/derive-prompts.d.ts.map +1 -0
- package/dist/interactive/derive-prompts.js +58 -0
- package/dist/interactive/derive-prompts.js.map +1 -0
- package/dist/interactive/inquirer-prompter.d.ts +11 -0
- package/dist/interactive/inquirer-prompter.d.ts.map +1 -0
- package/dist/interactive/inquirer-prompter.js +64 -0
- package/dist/interactive/inquirer-prompter.js.map +1 -0
- package/dist/interactive/menu.d.ts +25 -0
- package/dist/interactive/menu.d.ts.map +1 -0
- package/dist/interactive/menu.js +57 -0
- package/dist/interactive/menu.js.map +1 -0
- package/dist/interactive/prompter.d.ts +25 -0
- package/dist/interactive/prompter.d.ts.map +1 -0
- package/dist/interactive/prompter.js +14 -0
- package/dist/interactive/prompter.js.map +1 -0
- package/dist/interactive/session.d.ts +19 -0
- package/dist/interactive/session.d.ts.map +1 -0
- package/dist/interactive/session.js +204 -0
- package/dist/interactive/session.js.map +1 -0
- package/dist/program.d.ts +81 -0
- package/dist/program.d.ts.map +1 -0
- package/dist/program.js +178 -0
- package/dist/program.js.map +1 -0
- package/dist/render/ansi.d.ts +13 -0
- package/dist/render/ansi.d.ts.map +1 -0
- package/dist/render/ansi.js +34 -0
- package/dist/render/ansi.js.map +1 -0
- package/dist/render/banner.d.ts +7 -0
- package/dist/render/banner.d.ts.map +1 -0
- package/dist/render/banner.js +18 -0
- package/dist/render/banner.js.map +1 -0
- package/dist/render/glyphs.d.ts +39 -0
- package/dist/render/glyphs.d.ts.map +1 -0
- package/dist/render/glyphs.js +83 -0
- package/dist/render/glyphs.js.map +1 -0
- package/dist/render/index.d.ts +13 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +24 -0
- package/dist/render/index.js.map +1 -0
- package/dist/render/insight-lines.d.ts +8 -0
- package/dist/render/insight-lines.d.ts.map +1 -0
- package/dist/render/insight-lines.js +62 -0
- package/dist/render/insight-lines.js.map +1 -0
- package/dist/render/insight.d.ts +9 -0
- package/dist/render/insight.d.ts.map +1 -0
- package/dist/render/insight.js +68 -0
- package/dist/render/insight.js.map +1 -0
- package/dist/render/journey-graph.d.ts +73 -0
- package/dist/render/journey-graph.d.ts.map +1 -0
- package/dist/render/journey-graph.js +250 -0
- package/dist/render/journey-graph.js.map +1 -0
- package/dist/render/journey.d.ts +14 -0
- package/dist/render/journey.d.ts.map +1 -0
- package/dist/render/journey.js +67 -0
- package/dist/render/journey.js.map +1 -0
- package/dist/render/live-panel.d.ts +34 -0
- package/dist/render/live-panel.d.ts.map +1 -0
- package/dist/render/live-panel.js +84 -0
- package/dist/render/live-panel.js.map +1 -0
- package/dist/render/mode.d.ts +19 -0
- package/dist/render/mode.d.ts.map +1 -0
- package/dist/render/mode.js +23 -0
- package/dist/render/mode.js.map +1 -0
- package/dist/render/registry.d.ts +17 -0
- package/dist/render/registry.d.ts.map +1 -0
- package/dist/render/registry.js +34 -0
- package/dist/render/registry.js.map +1 -0
- package/dist/render/run-list.d.ts +11 -0
- package/dist/render/run-list.d.ts.map +1 -0
- package/dist/render/run-list.js +53 -0
- package/dist/render/run-list.js.map +1 -0
- package/dist/render/spinner.d.ts +14 -0
- package/dist/render/spinner.d.ts.map +1 -0
- package/dist/render/spinner.js +37 -0
- package/dist/render/spinner.js.map +1 -0
- package/dist/render/table.d.ts +20 -0
- package/dist/render/table.d.ts.map +1 -0
- package/dist/render/table.js +29 -0
- package/dist/render/table.js.map +1 -0
- package/dist/render/trajectory.d.ts +19 -0
- package/dist/render/trajectory.d.ts.map +1 -0
- package/dist/render/trajectory.js +75 -0
- package/dist/render/trajectory.js.map +1 -0
- package/dist/render/tunnel.d.ts +10 -0
- package/dist/render/tunnel.d.ts.map +1 -0
- package/dist/render/tunnel.js +108 -0
- package/dist/render/tunnel.js.map +1 -0
- package/dist/render/usage.d.ts +29 -0
- package/dist/render/usage.d.ts.map +1 -0
- package/dist/render/usage.js +83 -0
- package/dist/render/usage.js.map +1 -0
- package/dist/tunnel-command.d.ts +15 -0
- package/dist/tunnel-command.d.ts.map +1 -0
- package/dist/tunnel-command.js +267 -0
- package/dist/tunnel-command.js.map +1 -0
- package/dist/watch.d.ts +53 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +305 -0
- package/dist/watch.js.map +1 -0
- package/package.json +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ora (ora-ai)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# ora — the ora CLI
|
|
2
|
+
|
|
3
|
+
Drive the [ora](https://agentfront.sh) public API from your terminal. Every
|
|
4
|
+
command is generated from ora's public OpenAPI document, so the CLI always
|
|
5
|
+
matches the API: `https://api.agentfront.sh/openapi.json`.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
npm install -g @ora-ai/cli
|
|
10
|
+
ora --help
|
|
11
|
+
|
|
12
|
+
(`npx @ora-ai/cli` also works. Note: bare `npx ora` fetches an unrelated npm
|
|
13
|
+
spinner library — always use the scoped package name with npx.)
|
|
14
|
+
|
|
15
|
+
## Interactive
|
|
16
|
+
|
|
17
|
+
Just run `ora` — browse every command, get prompted for arguments, and see
|
|
18
|
+
the equivalent scriptable command before it runs.
|
|
19
|
+
|
|
20
|
+
ora
|
|
21
|
+
|
|
22
|
+
This only opens in a real terminal (both stdin and stdout attached to a tty).
|
|
23
|
+
Piped or scripted (`ora | less`, `ora > out.txt`, inside CI), bare `ora`
|
|
24
|
+
prints `--help` instead — nothing changes for existing scripts.
|
|
25
|
+
|
|
26
|
+
## Authenticate
|
|
27
|
+
|
|
28
|
+
Mint an api key in the ora dashboard (Settings → API keys), then:
|
|
29
|
+
|
|
30
|
+
ora login # paste the key (masked), or:
|
|
31
|
+
ora login --api-key ora_sk_...
|
|
32
|
+
ora whoami
|
|
33
|
+
|
|
34
|
+
In CI, skip login and export `ORA_API_KEY` (prefer short-expiry keys). The
|
|
35
|
+
key is stored at `~/.config/ora/config.json` (0600) and exchanged for a
|
|
36
|
+
short-lived token on every call — the key itself never travels beyond the
|
|
37
|
+
token exchange.
|
|
38
|
+
|
|
39
|
+
## Use
|
|
40
|
+
|
|
41
|
+
Every command's `--help` ends with a ready-to-run example (including a request
|
|
42
|
+
body where one is needed) — the fastest way to learn a command is to ask it:
|
|
43
|
+
|
|
44
|
+
ora --help # all command groups
|
|
45
|
+
ora experiment runs --help # everything you can do with runs
|
|
46
|
+
ora experiment runs create --help # flags + an example run.json
|
|
47
|
+
|
|
48
|
+
### Runs — launch and follow agent executions
|
|
49
|
+
|
|
50
|
+
ora experiment runs list --page-size 20
|
|
51
|
+
|
|
52
|
+
cat > run.json <<'EOF'
|
|
53
|
+
{ "intent": "check pricing for acme.com", "harness": "eve", "model": "claude-sonnet-5" }
|
|
54
|
+
EOF
|
|
55
|
+
ora experiment runs create --file run.json # returns {runId, streamUrl, status}
|
|
56
|
+
ora experiment runs create --file run.json --watch # same, then streams live events until done
|
|
57
|
+
ora experiment runs stream <run-id> # attach to a run already in flight
|
|
58
|
+
ora experiment runs feed # org-wide live feed of run status changes
|
|
59
|
+
|
|
60
|
+
After a run completes, read its artifacts:
|
|
61
|
+
|
|
62
|
+
ora experiment runs insight <run-id> # the analyzed outcome
|
|
63
|
+
ora experiment runs trajectory <run-id> # step-by-step trace
|
|
64
|
+
ora experiment runs raw-trace <run-id> # the full raw event log
|
|
65
|
+
|
|
66
|
+
### Intents — AEO measurement (studio)
|
|
67
|
+
|
|
68
|
+
ora intents list
|
|
69
|
+
cat > intent.json <<'EOF'
|
|
70
|
+
{ "title": "Best AEO platform for B2B SaaS", "layer": "discovery" }
|
|
71
|
+
EOF
|
|
72
|
+
ora intents create --file intent.json
|
|
73
|
+
ora intents measurements-status # are the daily measurements running?
|
|
74
|
+
ora intents measurements-pause-all # stop the scans
|
|
75
|
+
ora intents measurements-resume-all # restart them
|
|
76
|
+
|
|
77
|
+
### Sessions — long-lived agent environments
|
|
78
|
+
|
|
79
|
+
ora sessions catalog # harnesses + models you can launch
|
|
80
|
+
ora sessions list
|
|
81
|
+
ora sessions hibernate <id>
|
|
82
|
+
ora sessions wake <id>
|
|
83
|
+
|
|
84
|
+
### Scripting
|
|
85
|
+
|
|
86
|
+
`--json` prints the raw API response; with `--watch` it becomes NDJSON (the
|
|
87
|
+
create response first, then one line per event). Exit codes: 0 ok, 1 API/auth
|
|
88
|
+
error, 2 usage error.
|
|
89
|
+
|
|
90
|
+
id=$(ora experiment runs create --file run.json --json | jq -r .runId)
|
|
91
|
+
ora experiment runs stream "$id" --json | jq -c 'select(.event == "step")'
|
|
92
|
+
|
|
93
|
+
ora experiment runs list --json | jq '.results[] | {id, harness, status}'
|
|
94
|
+
|
|
95
|
+
In CI, export `ORA_API_KEY` instead of logging in. Point at another
|
|
96
|
+
environment with `ORA_API_URL` or `--api-url`.
|
|
97
|
+
|
|
98
|
+
## Errors that explain themselves
|
|
99
|
+
|
|
100
|
+
- **402** — a plan limit was hit; the message names the dimension and where to upgrade.
|
|
101
|
+
- **403 INSUFFICIENT_SCOPE** — the key lacks a scope; the message names it. Mint
|
|
102
|
+
a key with that scope and `ora login` again.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three handwritten auth commands: login / logout / whoami — plus
|
|
3
|
+
* performLogin/performWhoami, the cores shared verbatim with the interactive
|
|
4
|
+
* session (interactive/session.ts imports both).
|
|
5
|
+
*
|
|
6
|
+
* ResolvedDeps is imported type-only from program.ts — no runtime edge back
|
|
7
|
+
* to program.ts (program.ts imports these attach* functions by value; see
|
|
8
|
+
* that file's session-cycle doc comment for the same pattern).
|
|
9
|
+
*/
|
|
10
|
+
import type { Command } from "commander";
|
|
11
|
+
import type { ResolvedDeps } from "./program.js";
|
|
12
|
+
/**
|
|
13
|
+
* The login core: validate the `ora_sk_` prefix → store → verify by
|
|
14
|
+
* exchanging for a token → roll back the stored key on verification failure.
|
|
15
|
+
* Shared verbatim by the scripted `login` action and (Task 4) the
|
|
16
|
+
* interactive session — prints exactly the lines the action always printed
|
|
17
|
+
* and returns whether login succeeded.
|
|
18
|
+
*/
|
|
19
|
+
export declare function performLogin(deps: ResolvedDeps, opts: {
|
|
20
|
+
apiKey?: string;
|
|
21
|
+
apiUrl?: string;
|
|
22
|
+
}): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* The whoami core: verify the configured key and print its identity. THE one
|
|
25
|
+
* implementation — shared verbatim by the scripted `whoami` action and the
|
|
26
|
+
* interactive session's whoami menu entry (interactive/session.ts) so the two
|
|
27
|
+
* doors never drift apart. Returns whether it succeeded and never throws;
|
|
28
|
+
* mirrors runOperation's own caller contract (sets process.exitCode on
|
|
29
|
+
* failure, leaves it alone on success) so both callers reset exit-code state
|
|
30
|
+
* the same way.
|
|
31
|
+
*/
|
|
32
|
+
export declare function performWhoami(deps: ResolvedDeps, opts: {
|
|
33
|
+
json?: boolean;
|
|
34
|
+
apiUrlFlag?: string;
|
|
35
|
+
}): Promise<boolean>;
|
|
36
|
+
export declare function attachLogin(program: Command, deps: ResolvedDeps): void;
|
|
37
|
+
export declare function attachLogout(program: Command, deps: ResolvedDeps): void;
|
|
38
|
+
export declare function attachWhoami(program: Command, deps: ResolvedDeps): void;
|
|
39
|
+
//# sourceMappingURL=auth-commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-commands.d.ts","sourceRoot":"","sources":["../src/auth-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQzC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CA0BnH;AAED;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBvH;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAWtE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CASvE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,GAAG,IAAI,CAUvE"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The three handwritten auth commands: login / logout / whoami — plus
|
|
3
|
+
* performLogin/performWhoami, the cores shared verbatim with the interactive
|
|
4
|
+
* session (interactive/session.ts imports both).
|
|
5
|
+
*
|
|
6
|
+
* ResolvedDeps is imported type-only from program.ts — no runtime edge back
|
|
7
|
+
* to program.ts (program.ts imports these attach* functions by value; see
|
|
8
|
+
* that file's session-cycle doc comment for the same pattern).
|
|
9
|
+
*/
|
|
10
|
+
import { decodeClaims } from "./core/auth.js";
|
|
11
|
+
import { clearCredentials, readConfig, resolveApiUrl, writeConfig } from "./core/config.js";
|
|
12
|
+
import { promptForKey } from "./core/login.js";
|
|
13
|
+
import { renderError } from "./core/output.js";
|
|
14
|
+
import { credentials } from "./core/run-operation.js";
|
|
15
|
+
import { SPEC } from "./generated/operations.js";
|
|
16
|
+
/**
|
|
17
|
+
* The login core: validate the `ora_sk_` prefix → store → verify by
|
|
18
|
+
* exchanging for a token → roll back the stored key on verification failure.
|
|
19
|
+
* Shared verbatim by the scripted `login` action and (Task 4) the
|
|
20
|
+
* interactive session — prints exactly the lines the action always printed
|
|
21
|
+
* and returns whether login succeeded.
|
|
22
|
+
*/
|
|
23
|
+
export async function performLogin(deps, opts) {
|
|
24
|
+
const key = opts.apiKey ?? "";
|
|
25
|
+
if (!key.startsWith("ora_sk_")) {
|
|
26
|
+
deps.errOut(renderError(new Error('That does not look like an ora api key (expected an "ora_sk_" prefix).')));
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
const previous = readConfig(deps.dir);
|
|
31
|
+
const apiUrl = resolveApiUrl(previous, opts.apiUrl, SPEC.server, deps.env);
|
|
32
|
+
// Announce the target BEFORE verifying: a key minted on one environment
|
|
33
|
+
// failing against another must be self-diagnosing, never a mystery.
|
|
34
|
+
deps.out(`Verifying against ${apiUrl}`);
|
|
35
|
+
writeConfig({ ...previous, apiKey: key, ...(opts.apiUrl ? { apiUrl } : {}) }, deps.dir);
|
|
36
|
+
try {
|
|
37
|
+
const token = await deps.makeAuth({ apiUrl, apiKey: key }).forceRefresh();
|
|
38
|
+
const claims = decodeClaims(token);
|
|
39
|
+
deps.out(`Logged in. org=${String(claims.org ?? claims.organizationId ?? "?")} subject=${String(claims.sub ?? "?")}`);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
writeConfig(previous, deps.dir); // verification failed — roll back
|
|
44
|
+
deps.errOut(renderError(err));
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The whoami core: verify the configured key and print its identity. THE one
|
|
51
|
+
* implementation — shared verbatim by the scripted `whoami` action and the
|
|
52
|
+
* interactive session's whoami menu entry (interactive/session.ts) so the two
|
|
53
|
+
* doors never drift apart. Returns whether it succeeded and never throws;
|
|
54
|
+
* mirrors runOperation's own caller contract (sets process.exitCode on
|
|
55
|
+
* failure, leaves it alone on success) so both callers reset exit-code state
|
|
56
|
+
* the same way.
|
|
57
|
+
*/
|
|
58
|
+
export async function performWhoami(deps, opts) {
|
|
59
|
+
const creds = credentials(deps, opts.apiUrlFlag);
|
|
60
|
+
if (!creds)
|
|
61
|
+
return false;
|
|
62
|
+
try {
|
|
63
|
+
const token = await deps.makeAuth(creds).forceRefresh();
|
|
64
|
+
const claims = decodeClaims(token);
|
|
65
|
+
if (opts.json) {
|
|
66
|
+
deps.out(JSON.stringify(claims, null, 2));
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
deps.out([
|
|
70
|
+
`org: ${String(claims.org ?? claims.organizationId ?? "?")}`,
|
|
71
|
+
`subject: ${String(claims.sub ?? "?")}`,
|
|
72
|
+
...(Array.isArray(claims.scopes) ? [`scopes: ${claims.scopes.join(", ")}`] : []),
|
|
73
|
+
`api: ${creds.apiUrl}`,
|
|
74
|
+
].join("\n"));
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
deps.errOut(renderError(err));
|
|
79
|
+
process.exitCode = 1;
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function attachLogin(program, deps) {
|
|
84
|
+
program
|
|
85
|
+
.command("login")
|
|
86
|
+
.description("Store an api key for ora (paste, --api-key, or ORA_API_KEY)")
|
|
87
|
+
.option("--api-key <key>", "the ora_sk_… api key")
|
|
88
|
+
.option("--api-url <url>", "override the API base URL")
|
|
89
|
+
.addHelpText("after", "\nExamples:\n ora login --api-key ora_sk_…\n ORA_API_KEY=ora_sk_… ora login # non-interactive (CI)\n ora login --api-url http://api.localhost:8081 # dev cluster")
|
|
90
|
+
.action(async (opts) => {
|
|
91
|
+
const key = opts.apiKey ?? deps.env.ORA_API_KEY ?? (await promptForKey());
|
|
92
|
+
await performLogin(deps, { apiKey: key, apiUrl: opts.apiUrl });
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
export function attachLogout(program, deps) {
|
|
96
|
+
program
|
|
97
|
+
.command("logout")
|
|
98
|
+
.description("Delete the stored api key and cached token")
|
|
99
|
+
.addHelpText("after", "\nExamples:\n ora logout")
|
|
100
|
+
.action(() => {
|
|
101
|
+
clearCredentials(deps.dir);
|
|
102
|
+
deps.out("Logged out — credentials removed.");
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
export function attachWhoami(program, deps) {
|
|
106
|
+
program
|
|
107
|
+
.command("whoami")
|
|
108
|
+
.description("Verify the configured key against the API and print its identity")
|
|
109
|
+
.option("--json", "print the raw token claims")
|
|
110
|
+
.option("--api-url <url>", "override the API base URL")
|
|
111
|
+
.addHelpText("after", "\nExamples:\n ora whoami\n ora whoami --json")
|
|
112
|
+
.action(async (opts) => {
|
|
113
|
+
await performWhoami(deps, { json: opts.json, apiUrlFlag: opts.apiUrl });
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=auth-commands.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-commands.js","sourceRoot":"","sources":["../src/auth-commands.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Api-key → short-lived token exchange.
|
|
3
|
+
*
|
|
4
|
+
* The credential travels ONLY as "Authorization: Bearer <ora_sk_…>" to
|
|
5
|
+
* POST /auth/token (the doc's own rule: never in the request body). The
|
|
6
|
+
* response {token, expiresIn} is cached on disk (0600) and reused until a 30s
|
|
7
|
+
* skew before expiry, so a token can't die between "checked" and "used".
|
|
8
|
+
*
|
|
9
|
+
* The cache is keyed to (apiUrl, sha256(key)) — pointing the CLI at staging or
|
|
10
|
+
* switching keys can never replay a token minted elsewhere. Cache write
|
|
11
|
+
* failures are swallowed: a cache is an optimization, not a requirement.
|
|
12
|
+
*/
|
|
13
|
+
export declare class AuthError extends Error {
|
|
14
|
+
readonly status: number;
|
|
15
|
+
readonly code: string;
|
|
16
|
+
constructor(message: string, status: number, code: string);
|
|
17
|
+
}
|
|
18
|
+
export interface ApiKeyAuthDeps {
|
|
19
|
+
apiUrl: string;
|
|
20
|
+
apiKey: string;
|
|
21
|
+
dir?: string;
|
|
22
|
+
fetchFn?: typeof fetch;
|
|
23
|
+
now?: () => number;
|
|
24
|
+
}
|
|
25
|
+
export declare class ApiKeyAuth {
|
|
26
|
+
private readonly apiUrl;
|
|
27
|
+
private readonly apiKey;
|
|
28
|
+
private readonly dir;
|
|
29
|
+
private readonly fetchFn;
|
|
30
|
+
private readonly now;
|
|
31
|
+
constructor(deps: ApiKeyAuthDeps);
|
|
32
|
+
getToken(): Promise<string>;
|
|
33
|
+
forceRefresh(): Promise<string>;
|
|
34
|
+
clearCache(): void;
|
|
35
|
+
private cachePath;
|
|
36
|
+
private keyDigest;
|
|
37
|
+
private readCache;
|
|
38
|
+
}
|
|
39
|
+
/** JWT payload claims, {} on anything unparseable. Display only — never trust. */
|
|
40
|
+
export declare function decodeClaims(token: string): Record<string, unknown>;
|
|
41
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAWH,qBAAa,SAAU,SAAQ,KAAK;IAGhC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAFrB,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM;CAKxB;AASD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE,cAAc;IAQ1B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAM3B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC;IAqCrC,UAAU,IAAI,IAAI;IAIlB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,SAAS;CAUlB;AAED,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAQnE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Api-key → short-lived token exchange.
|
|
3
|
+
*
|
|
4
|
+
* The credential travels ONLY as "Authorization: Bearer <ora_sk_…>" to
|
|
5
|
+
* POST /auth/token (the doc's own rule: never in the request body). The
|
|
6
|
+
* response {token, expiresIn} is cached on disk (0600) and reused until a 30s
|
|
7
|
+
* skew before expiry, so a token can't die between "checked" and "used".
|
|
8
|
+
*
|
|
9
|
+
* The cache is keyed to (apiUrl, sha256(key)) — pointing the CLI at staging or
|
|
10
|
+
* switching keys can never replay a token minted elsewhere. Cache write
|
|
11
|
+
* failures are swallowed: a cache is an optimization, not a requirement.
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
import { configDir } from "./config.js";
|
|
17
|
+
const EXPIRY_SKEW_MS = 30_000;
|
|
18
|
+
const DEFAULT_EXPIRES_IN_S = 900;
|
|
19
|
+
export class AuthError extends Error {
|
|
20
|
+
status;
|
|
21
|
+
code;
|
|
22
|
+
constructor(message, status, code) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.status = status;
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.name = "AuthError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class ApiKeyAuth {
|
|
30
|
+
apiUrl;
|
|
31
|
+
apiKey;
|
|
32
|
+
dir;
|
|
33
|
+
fetchFn;
|
|
34
|
+
now;
|
|
35
|
+
constructor(deps) {
|
|
36
|
+
this.apiUrl = deps.apiUrl;
|
|
37
|
+
this.apiKey = deps.apiKey;
|
|
38
|
+
this.dir = deps.dir ?? configDir();
|
|
39
|
+
this.fetchFn = deps.fetchFn ?? fetch;
|
|
40
|
+
this.now = deps.now ?? Date.now;
|
|
41
|
+
}
|
|
42
|
+
async getToken() {
|
|
43
|
+
const cached = this.readCache();
|
|
44
|
+
if (cached && cached.expiresAt - EXPIRY_SKEW_MS > this.now())
|
|
45
|
+
return cached.token;
|
|
46
|
+
return this.forceRefresh();
|
|
47
|
+
}
|
|
48
|
+
async forceRefresh() {
|
|
49
|
+
const res = await this.fetchFn(`${this.apiUrl}/auth/token`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
headers: { authorization: `Bearer ${this.apiKey}`, "content-type": "application/json" },
|
|
52
|
+
body: "{}",
|
|
53
|
+
});
|
|
54
|
+
const body = (await res.json().catch(() => ({})));
|
|
55
|
+
if (!res.ok || typeof body.token !== "string") {
|
|
56
|
+
throw new AuthError(body.error ?? `token exchange failed (HTTP ${res.status})`, res.status, body.code ?? "EXCHANGE_FAILED");
|
|
57
|
+
}
|
|
58
|
+
const record = {
|
|
59
|
+
apiUrl: this.apiUrl,
|
|
60
|
+
keyDigest: this.keyDigest(),
|
|
61
|
+
token: body.token,
|
|
62
|
+
expiresAt: this.now() + (body.expiresIn ?? DEFAULT_EXPIRES_IN_S) * 1000,
|
|
63
|
+
};
|
|
64
|
+
try {
|
|
65
|
+
mkdirSync(this.dir, { recursive: true, mode: 0o700 });
|
|
66
|
+
chmodSync(this.dir, 0o700);
|
|
67
|
+
writeFileSync(this.cachePath(), `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
|
|
68
|
+
chmodSync(this.cachePath(), 0o600);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Unwritable cache just means we exchange again next invocation.
|
|
72
|
+
}
|
|
73
|
+
return body.token;
|
|
74
|
+
}
|
|
75
|
+
clearCache() {
|
|
76
|
+
rmSync(this.cachePath(), { force: true });
|
|
77
|
+
}
|
|
78
|
+
cachePath() {
|
|
79
|
+
return join(this.dir, "token.json");
|
|
80
|
+
}
|
|
81
|
+
keyDigest() {
|
|
82
|
+
return createHash("sha256").update(this.apiKey).digest("hex").slice(0, 16);
|
|
83
|
+
}
|
|
84
|
+
readCache() {
|
|
85
|
+
try {
|
|
86
|
+
const parsed = JSON.parse(readFileSync(this.cachePath(), "utf8"));
|
|
87
|
+
if (parsed.apiUrl !== this.apiUrl || parsed.keyDigest !== this.keyDigest())
|
|
88
|
+
return null;
|
|
89
|
+
if (typeof parsed.token !== "string" || typeof parsed.expiresAt !== "number")
|
|
90
|
+
return null;
|
|
91
|
+
return parsed;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** JWT payload claims, {} on anything unparseable. Display only — never trust. */
|
|
99
|
+
export function decodeClaims(token) {
|
|
100
|
+
const payload = token.split(".")[1];
|
|
101
|
+
if (!payload)
|
|
102
|
+
return {};
|
|
103
|
+
try {
|
|
104
|
+
return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return {};
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/core/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential + endpoint configuration.
|
|
3
|
+
*
|
|
4
|
+
* ~/.config/ora (XDG-respecting) holds config.json (api key, api url) and
|
|
5
|
+
* token.json (the cached short-lived token, owned by auth.ts). Both are 0600 in
|
|
6
|
+
* a 0700 dir — the key is a secret. Every read degrades to "not configured"
|
|
7
|
+
* rather than throwing: a corrupt file must never brick the CLI.
|
|
8
|
+
*
|
|
9
|
+
* Permissions are re-asserted on every write, not just on creation. This hardens
|
|
10
|
+
* pre-existing insecure files (e.g. restored from dotfiles with loose modes).
|
|
11
|
+
*
|
|
12
|
+
* Precedence (why: CI overrides laptops, flags override everything):
|
|
13
|
+
* api key : ORA_API_KEY env > config.json
|
|
14
|
+
* api url : --api-url flag > ORA_API_URL env > config.json > the spec's server
|
|
15
|
+
*/
|
|
16
|
+
export interface OraConfig {
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
apiUrl?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function configDir(env?: NodeJS.ProcessEnv): string;
|
|
21
|
+
export declare function readConfig(dir?: string): OraConfig;
|
|
22
|
+
export declare function writeConfig(cfg: OraConfig, dir?: string): void;
|
|
23
|
+
export declare function clearCredentials(dir?: string): void;
|
|
24
|
+
export declare function resolveApiKey(cfg: OraConfig, env?: NodeJS.ProcessEnv): string | undefined;
|
|
25
|
+
export declare function resolveApiUrl(cfg: OraConfig, flagValue: string | undefined, embeddedDefault: string, env?: NodeJS.ProcessEnv): string;
|
|
26
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAMH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,SAAS,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAGtE;AAED,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAoB,GAAG,SAAS,CAU/D;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,GAAE,MAAoB,GAAG,IAAI,CAK3E;AAED,wBAAgB,gBAAgB,CAAC,GAAG,GAAE,MAAoB,GAAG,IAAI,CAGhE;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,SAAS,EAAE,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAEtG;AAED,wBAAgB,aAAa,CAC3B,GAAG,EAAE,SAAS,EACd,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,eAAe,EAAE,MAAM,EACvB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,MAAM,CAER"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential + endpoint configuration.
|
|
3
|
+
*
|
|
4
|
+
* ~/.config/ora (XDG-respecting) holds config.json (api key, api url) and
|
|
5
|
+
* token.json (the cached short-lived token, owned by auth.ts). Both are 0600 in
|
|
6
|
+
* a 0700 dir — the key is a secret. Every read degrades to "not configured"
|
|
7
|
+
* rather than throwing: a corrupt file must never brick the CLI.
|
|
8
|
+
*
|
|
9
|
+
* Permissions are re-asserted on every write, not just on creation. This hardens
|
|
10
|
+
* pre-existing insecure files (e.g. restored from dotfiles with loose modes).
|
|
11
|
+
*
|
|
12
|
+
* Precedence (why: CI overrides laptops, flags override everything):
|
|
13
|
+
* api key : ORA_API_KEY env > config.json
|
|
14
|
+
* api url : --api-url flag > ORA_API_URL env > config.json > the spec's server
|
|
15
|
+
*/
|
|
16
|
+
import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
export function configDir(env = process.env) {
|
|
20
|
+
const base = env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
21
|
+
return join(base, "ora");
|
|
22
|
+
}
|
|
23
|
+
export function readConfig(dir = configDir()) {
|
|
24
|
+
try {
|
|
25
|
+
const parsed = JSON.parse(readFileSync(join(dir, "config.json"), "utf8"));
|
|
26
|
+
return {
|
|
27
|
+
...(typeof parsed.apiKey === "string" ? { apiKey: parsed.apiKey } : {}),
|
|
28
|
+
...(typeof parsed.apiUrl === "string" ? { apiUrl: parsed.apiUrl } : {}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function writeConfig(cfg, dir = configDir()) {
|
|
36
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
37
|
+
chmodSync(dir, 0o700);
|
|
38
|
+
writeFileSync(join(dir, "config.json"), `${JSON.stringify(cfg, null, 2)}\n`, { mode: 0o600 });
|
|
39
|
+
chmodSync(join(dir, "config.json"), 0o600);
|
|
40
|
+
}
|
|
41
|
+
export function clearCredentials(dir = configDir()) {
|
|
42
|
+
rmSync(join(dir, "config.json"), { force: true });
|
|
43
|
+
rmSync(join(dir, "token.json"), { force: true });
|
|
44
|
+
}
|
|
45
|
+
export function resolveApiKey(cfg, env = process.env) {
|
|
46
|
+
return env.ORA_API_KEY || cfg.apiKey;
|
|
47
|
+
}
|
|
48
|
+
export function resolveApiUrl(cfg, flagValue, embeddedDefault, env = process.env) {
|
|
49
|
+
return (flagValue || env.ORA_API_URL || cfg.apiUrl || embeddedDefault).replace(/\/+$/, "");
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC,MAAM,UAAU,SAAS,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC5D,MAAM,IAAI,GAAG,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAc,SAAS,EAAE;IAClD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAA4B,CAAC;QACrG,OAAO;YACL,GAAG,CAAC,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,GAAG,CAAC,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,GAAc,EAAE,MAAc,SAAS,EAAE;IACnE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACjD,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACtB,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9F,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,KAAK,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc,SAAS,EAAE;IACxD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,GAAc,EAAE,MAAyB,OAAO,CAAC,GAAG;IAChF,OAAO,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,GAAc,EACd,SAA6B,EAC7B,eAAuB,EACvB,MAAyB,OAAO,CAAC,GAAG;IAEpC,OAAO,CAAC,SAAS,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,IAAI,eAAe,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC7F,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one HTTP door for every generated command.
|
|
3
|
+
*
|
|
4
|
+
* Auth policy mirrors the platform's createServiceClient: on a 401 the token
|
|
5
|
+
* is force-re-exchanged and the request retried EXACTLY once. 5xx is never
|
|
6
|
+
* retried — a POST /runs may have been admitted and billed; at-most-once is
|
|
7
|
+
* the CLI's promise for mutations.
|
|
8
|
+
*
|
|
9
|
+
* Non-ok responses map to ApiError carrying the platform envelope
|
|
10
|
+
* {error, code, details?} so output.ts can tell the 402/403 stories.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ApiError extends Error {
|
|
13
|
+
readonly status: number;
|
|
14
|
+
readonly code: string;
|
|
15
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
16
|
+
constructor(message: string, status: number, code: string, details?: Record<string, unknown> | undefined);
|
|
17
|
+
}
|
|
18
|
+
export interface CallOptions {
|
|
19
|
+
method: string;
|
|
20
|
+
path: string;
|
|
21
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
22
|
+
body?: unknown;
|
|
23
|
+
}
|
|
24
|
+
/** The narrow auth port — both members required so a fake-retry can't hide. */
|
|
25
|
+
export interface TokenSource {
|
|
26
|
+
getToken(): Promise<string>;
|
|
27
|
+
forceRefresh(): Promise<string>;
|
|
28
|
+
}
|
|
29
|
+
export interface HttpClientDeps {
|
|
30
|
+
apiUrl: string;
|
|
31
|
+
auth: TokenSource;
|
|
32
|
+
fetchFn?: typeof fetch;
|
|
33
|
+
}
|
|
34
|
+
export declare class HttpClient {
|
|
35
|
+
private readonly apiUrl;
|
|
36
|
+
private readonly auth;
|
|
37
|
+
private readonly fetchFn;
|
|
38
|
+
constructor(deps: HttpClientDeps);
|
|
39
|
+
/** JSON operations: parsed body (null when empty), ApiError on non-ok. */
|
|
40
|
+
call(opts: CallOptions): Promise<unknown>;
|
|
41
|
+
/** Stream operations: the raw ok Response, for core/sse.ts to consume. */
|
|
42
|
+
open(opts: CallOptions): Promise<Response>;
|
|
43
|
+
private url;
|
|
44
|
+
private send;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/core/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,qBAAa,QAAS,SAAQ,KAAK;IAG/B,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAH1C,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;CAK7C;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;gBAE3B,IAAI,EAAE,cAAc;IAMhC,0EAA0E;IACpE,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAW/C,0EAA0E;IACpE,IAAI,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IAIhD,OAAO,CAAC,GAAG;YAQG,IAAI;CAmBnB"}
|