@oh-hai/cli 0.1.0-beta.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 +154 -0
- package/dist/auth/file-backend.d.ts +16 -0
- package/dist/auth/file-backend.js +98 -0
- package/dist/auth/file-backend.js.map +1 -0
- package/dist/auth/keychain.d.ts +54 -0
- package/dist/auth/keychain.js +232 -0
- package/dist/auth/keychain.js.map +1 -0
- package/dist/auth/resolve-token.d.ts +34 -0
- package/dist/auth/resolve-token.js +91 -0
- package/dist/auth/resolve-token.js.map +1 -0
- package/dist/auth/secure-write.d.ts +2 -0
- package/dist/auth/secure-write.js +30 -0
- package/dist/auth/secure-write.js.map +1 -0
- package/dist/auth/token-store.d.ts +104 -0
- package/dist/auth/token-store.js +208 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +238 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +370 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/ask.d.ts +2 -0
- package/dist/commands/ask.js +246 -0
- package/dist/commands/ask.js.map +1 -0
- package/dist/commands/context.d.ts +72 -0
- package/dist/commands/context.js +7 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +237 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/flags.d.ts +25 -0
- package/dist/commands/flags.js +100 -0
- package/dist/commands/flags.js.map +1 -0
- package/dist/commands/handlers.d.ts +2 -0
- package/dist/commands/handlers.js +26 -0
- package/dist/commands/handlers.js.map +1 -0
- package/dist/commands/http.d.ts +8 -0
- package/dist/commands/http.js +19 -0
- package/dist/commands/http.js.map +1 -0
- package/dist/commands/inbox.d.ts +2 -0
- package/dist/commands/inbox.js +111 -0
- package/dist/commands/inbox.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +272 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +2 -0
- package/dist/commands/logout.js +35 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/messaging/await.d.ts +43 -0
- package/dist/commands/messaging/await.js +125 -0
- package/dist/commands/messaging/await.js.map +1 -0
- package/dist/commands/messaging/build.d.ts +46 -0
- package/dist/commands/messaging/build.js +66 -0
- package/dist/commands/messaging/build.js.map +1 -0
- package/dist/commands/messaging/http.d.ts +22 -0
- package/dist/commands/messaging/http.js +270 -0
- package/dist/commands/messaging/http.js.map +1 -0
- package/dist/commands/messaging/identity.d.ts +29 -0
- package/dist/commands/messaging/identity.js +63 -0
- package/dist/commands/messaging/identity.js.map +1 -0
- package/dist/commands/messaging/shared.d.ts +53 -0
- package/dist/commands/messaging/shared.js +135 -0
- package/dist/commands/messaging/shared.js.map +1 -0
- package/dist/commands/messaging/state.d.ts +26 -0
- package/dist/commands/messaging/state.js +82 -0
- package/dist/commands/messaging/state.js.map +1 -0
- package/dist/commands/messaging/validate.d.ts +40 -0
- package/dist/commands/messaging/validate.js +193 -0
- package/dist/commands/messaging/validate.js.map +1 -0
- package/dist/commands/messaging/wire.d.ts +133 -0
- package/dist/commands/messaging/wire.js +16 -0
- package/dist/commands/messaging/wire.js.map +1 -0
- package/dist/commands/notify.d.ts +2 -0
- package/dist/commands/notify.js +68 -0
- package/dist/commands/notify.js.map +1 -0
- package/dist/commands/registry.d.ts +14 -0
- package/dist/commands/registry.js +144 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/stub.d.ts +1 -0
- package/dist/commands/stub.js +9 -0
- package/dist/commands/stub.js.map +1 -0
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.js +223 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/whoami.d.ts +6 -0
- package/dist/commands/whoami.js +90 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/config-file.d.ts +38 -0
- package/dist/config-file.js +233 -0
- package/dist/config-file.js.map +1 -0
- package/dist/config.d.ts +64 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/envelope.d.ts +25 -0
- package/dist/envelope.js +41 -0
- package/dist/envelope.js.map +1 -0
- package/dist/exit-codes.d.ts +51 -0
- package/dist/exit-codes.js +57 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/help.d.ts +1 -0
- package/dist/help.js +17 -0
- package/dist/help.js.map +1 -0
- package/dist/terminal.d.ts +5 -0
- package/dist/terminal.js +18 -0
- package/dist/terminal.js.map +1 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +23 -0
- package/dist/version.js.map +1 -0
- package/package.json +38 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// The command surface (docs/specs/cli.md §4). #105 registered every command with help
|
|
2
|
+
// text and its owning downstream issue; those bodies have since landed (auth #106,
|
|
3
|
+
// messaging #107, agents #209, health/doctor #108). Subcommands are listed so the
|
|
4
|
+
// dispatcher can form the envelope `command` name (e.g. "ask.submit") and print per-command help.
|
|
5
|
+
export const COMMANDS = [
|
|
6
|
+
{
|
|
7
|
+
name: "login",
|
|
8
|
+
summary: "acquire a token → OS keychain (never printed)",
|
|
9
|
+
issue: 106,
|
|
10
|
+
subcommands: [],
|
|
11
|
+
help: [
|
|
12
|
+
"oh-hai login — acquire a token and store it in the OS keychain.",
|
|
13
|
+
"",
|
|
14
|
+
"Usage: oh-hai login [--base-url <url>]",
|
|
15
|
+
" oh-hai login --token-stdin [--base-url <url>] [--account <id>]",
|
|
16
|
+
"",
|
|
17
|
+
"Bare `login` runs the device-code flow: it prints a short code + verification URL",
|
|
18
|
+
"to stderr, you approve in the browser, and the returned token is stored under the",
|
|
19
|
+
"Hub-scoped key and NEVER printed (cli spec §4.3/§5). Or pipe a token to",
|
|
20
|
+
"--token-stdin for local bootstrap; it is stored the same way (keychain, or a 0600",
|
|
21
|
+
"file fallback), also never printed.",
|
|
22
|
+
].join("\n"),
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: "logout",
|
|
26
|
+
summary: "remove the stored token",
|
|
27
|
+
issue: 106,
|
|
28
|
+
subcommands: [],
|
|
29
|
+
help: [
|
|
30
|
+
"oh-hai logout — remove the stored token for the current (or --account) identity.",
|
|
31
|
+
"",
|
|
32
|
+
"Usage: oh-hai logout [--account <id>] [--all]",
|
|
33
|
+
"",
|
|
34
|
+
"Idempotent and offline. --all clears every stored identity.",
|
|
35
|
+
].join("\n"),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "whoami",
|
|
39
|
+
summary: "show agent id + base URL + token presence (never the token)",
|
|
40
|
+
issue: 106,
|
|
41
|
+
subcommands: [],
|
|
42
|
+
help: [
|
|
43
|
+
"oh-hai whoami — show the resolved identity without revealing the secret.",
|
|
44
|
+
"",
|
|
45
|
+
"Usage: oh-hai whoami [--check]",
|
|
46
|
+
"",
|
|
47
|
+
"Prints agent id, base URL, and token presence. --check probes the Hub for validity.",
|
|
48
|
+
].join("\n"),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "notify",
|
|
52
|
+
summary: "fire-and-forget FYI / status / digest",
|
|
53
|
+
issue: 107,
|
|
54
|
+
subcommands: [],
|
|
55
|
+
help: [
|
|
56
|
+
"oh-hai notify — fire-and-forget notification (status / FYI / digest).",
|
|
57
|
+
"",
|
|
58
|
+
"Usage: oh-hai notify --title <t> [--body <b>] [--priority <low|normal|high|urgent>]",
|
|
59
|
+
" [--tag <t>]... [--dry-run]",
|
|
60
|
+
"",
|
|
61
|
+
"No idempotency key — a notify is fire-and-forget and must not be retried.",
|
|
62
|
+
].join("\n"),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "ask",
|
|
66
|
+
summary: "human decision: submit / await",
|
|
67
|
+
issue: 107,
|
|
68
|
+
subcommands: ["submit", "await"],
|
|
69
|
+
help: [
|
|
70
|
+
"oh-hai ask — request a human decision.",
|
|
71
|
+
"",
|
|
72
|
+
"Usage: oh-hai ask submit --mode <select|input|confirm> --title <t>",
|
|
73
|
+
" --resolver human:<id> [--option k:Label]... [--schema <json>]",
|
|
74
|
+
" oh-hai ask await --id <id> [--interval <ms>] [--await-timeout <ms>]",
|
|
75
|
+
"",
|
|
76
|
+
"Carries a REQUIRED idempotency key. See cli spec §4.4.",
|
|
77
|
+
].join("\n"),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: "task",
|
|
81
|
+
summary: "manual human action: submit / await",
|
|
82
|
+
issue: 107,
|
|
83
|
+
subcommands: ["submit", "await"],
|
|
84
|
+
help: [
|
|
85
|
+
"oh-hai task — hand a human a manual action.",
|
|
86
|
+
"",
|
|
87
|
+
"Usage: oh-hai task submit --instructions <text> --title <t> --resolver human:<id>",
|
|
88
|
+
" [--checklist <text>]... [--callback pull|push]",
|
|
89
|
+
" oh-hai task await --id <id> [--interval <ms>] [--await-timeout <ms>]",
|
|
90
|
+
"",
|
|
91
|
+
"Terminal by default. See cli spec §4.4.",
|
|
92
|
+
].join("\n"),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "agents",
|
|
96
|
+
summary: "list / create / revoke agent identities",
|
|
97
|
+
issue: 209,
|
|
98
|
+
subcommands: ["list", "create", "revoke"],
|
|
99
|
+
help: [
|
|
100
|
+
"oh-hai agents — account / identity management.",
|
|
101
|
+
"",
|
|
102
|
+
"Usage: oh-hai agents list",
|
|
103
|
+
" oh-hai agents create --label <label>",
|
|
104
|
+
" oh-hai agents revoke --target-agent <id> [--yes]",
|
|
105
|
+
"",
|
|
106
|
+
"See cli spec §4.5.",
|
|
107
|
+
].join("\n"),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "inbox",
|
|
111
|
+
summary: "drain + ack your agent's mailbox; heartbeat presence (watch)",
|
|
112
|
+
issue: 153,
|
|
113
|
+
subcommands: ["watch"],
|
|
114
|
+
help: [
|
|
115
|
+
"oh-hai inbox — the human→agent inbound leg (MA2H v0.4).",
|
|
116
|
+
"",
|
|
117
|
+
"Usage: oh-hai inbox watch [--interval <ms>] [--once] [--max <n>]",
|
|
118
|
+
"",
|
|
119
|
+
"watch drains your agent's mailbox, streams each directive to stdout for the",
|
|
120
|
+
"agent runtime, posts a single batched ack, and — because each authenticated",
|
|
121
|
+
"poll is the presence heartbeat — keeps you 'online' by polling on --interval",
|
|
122
|
+
"(default 5000ms). Under --json each directive is one NDJSON envelope. --once",
|
|
123
|
+
"drains a single batch and exits (cron / scripting). --max caps the drain size.",
|
|
124
|
+
"See cli spec §4.7.",
|
|
125
|
+
].join("\n"),
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "doctor",
|
|
129
|
+
summary: "config + keychain + connectivity + test-notify checks",
|
|
130
|
+
issue: 108,
|
|
131
|
+
subcommands: [],
|
|
132
|
+
help: [
|
|
133
|
+
"oh-hai doctor — diagnose setup (config, keychain, connectivity).",
|
|
134
|
+
"",
|
|
135
|
+
"Usage: oh-hai doctor [--test-notify]",
|
|
136
|
+
"",
|
|
137
|
+
"Local checks run offline; connectivity + --test-notify are network checks. See cli spec §4.6.",
|
|
138
|
+
].join("\n"),
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
export function findCommand(name) {
|
|
142
|
+
return COMMANDS.find((command) => command.name === name);
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/commands/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function notImplemented(command: string, issue: number): never;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// The honest not-yet-implemented handler. #105 ships the package + machine contract;
|
|
2
|
+
// each command body lands in its owning downstream issue (#106–#108, blocked on #96).
|
|
3
|
+
// A stub throws a CliError so the entry renders it exactly like any other failure —
|
|
4
|
+
// generic exit 1 with a stable `not_implemented` code (cli spec §7/§8) and an issue pointer.
|
|
5
|
+
import { CliError } from "../envelope.js";
|
|
6
|
+
export function notImplemented(command, issue) {
|
|
7
|
+
throw new CliError("not_implemented", `oh-hai ${command} is not implemented yet — lands in #${issue} (see docs/specs/cli.md).`);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=stub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub.js","sourceRoot":"","sources":["../../src/commands/stub.ts"],"names":[],"mappings":"AAAA,qFAAqF;AACrF,sFAAsF;AACtF,oFAAoF;AACpF,6FAA6F;AAE7F,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,UAAU,cAAc,CAAC,OAAe,EAAE,KAAa;IAC3D,MAAM,IAAI,QAAQ,CAChB,iBAAiB,EACjB,UAAU,OAAO,uCAAuC,KAAK,2BAA2B,CACzF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
// `oh-hai task` (docs/specs/cli.md §4.4) — hand a human a manual action. Subcommands `submit` +
|
|
2
|
+
// `await`. Productized from `scripts/task.ts`: account-aware bearer, `--json` envelope, §7 exit
|
|
3
|
+
// codes, injectable fetch. TERMINAL by default — `--callback` is pure opt-in; ≥1 `human:`
|
|
4
|
+
// resolver is REQUIRED for a real submit (the resolve route is human-only). A task Response
|
|
5
|
+
// carries no `value` (resolutions are completed / dismissed / expired).
|
|
6
|
+
import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
|
|
7
|
+
import { parseSubcommandArgs } from "./flags.js";
|
|
8
|
+
import { awaitResolution, printResolution, resolveAwaitTiming } from "./messaging/await.js";
|
|
9
|
+
import { buildTask } from "./messaging/build.js";
|
|
10
|
+
import { submitEnvelope } from "./messaging/http.js";
|
|
11
|
+
import { requireReplayToken, requireToken, resolveSubmitIdentity } from "./messaging/identity.js";
|
|
12
|
+
import { assertNoForeignFlags, dryRunFlag, printDryRunEnvelope, printSubmitAck, requireNonEmpty, resolveRunId, resumeRoutingSuffix, sanitizeForTerminal, stringFlag, } from "./messaging/shared.js";
|
|
13
|
+
import { openResumeState, resolveState } from "./messaging/state.js";
|
|
14
|
+
import { assertTaskEnvelope, hasConcreteHumanResolver, hasHumanResolver, isActor, isCallbackMode, normalizePushUrl, readEnvelopeArg, replayResolvers, } from "./messaging/validate.js";
|
|
15
|
+
const SUBCOMMANDS = ["submit", "await"];
|
|
16
|
+
const TASK_SUBMIT_OPTIONS = {
|
|
17
|
+
title: { type: "string" },
|
|
18
|
+
body: { type: "string" },
|
|
19
|
+
instructions: { type: "string" },
|
|
20
|
+
checklist: { type: "string", multiple: true },
|
|
21
|
+
resolver: { type: "string", multiple: true },
|
|
22
|
+
callback: { type: "string" },
|
|
23
|
+
"callback-url": { type: "string" },
|
|
24
|
+
"idempotency-key": { type: "string" },
|
|
25
|
+
"created-at": { type: "string" },
|
|
26
|
+
"dry-run": { type: "boolean" },
|
|
27
|
+
envelope: { type: "string" },
|
|
28
|
+
state: { type: "string" },
|
|
29
|
+
};
|
|
30
|
+
const TASK_AWAIT_OPTIONS = {
|
|
31
|
+
id: { type: "string" },
|
|
32
|
+
interval: { type: "string" },
|
|
33
|
+
"await-timeout": { type: "string" },
|
|
34
|
+
};
|
|
35
|
+
const TASK_OPTIONS = { ...TASK_SUBMIT_OPTIONS, ...TASK_AWAIT_OPTIONS };
|
|
36
|
+
const AWAIT_ONLY = ["id", "interval", "await-timeout"];
|
|
37
|
+
const SUBMIT_ONLY = Object.keys(TASK_SUBMIT_OPTIONS);
|
|
38
|
+
export async function taskCommand(ctx) {
|
|
39
|
+
const { values, subcommand } = parseSubcommandArgs(ctx.argv, SUBCOMMANDS, TASK_OPTIONS);
|
|
40
|
+
const flags = values;
|
|
41
|
+
if (subcommand === "submit") {
|
|
42
|
+
assertNoForeignFlags(flags, AWAIT_ONLY, "task submit");
|
|
43
|
+
await taskSubmit(ctx, flags);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
assertNoForeignFlags(flags, SUBMIT_ONLY, "task await");
|
|
47
|
+
await taskAwait(ctx, flags);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function taskSubmit(ctx, flags) {
|
|
51
|
+
// Strict parse: `--dry-run=true` still previews; a malformed `--dry-run=treu` fails as usage
|
|
52
|
+
// rather than silently performing a LIVE submit.
|
|
53
|
+
const dryRun = dryRunFlag(flags["dry-run"]);
|
|
54
|
+
const envelopeRaw = stringFlag(flags.envelope);
|
|
55
|
+
if (envelopeRaw !== undefined) {
|
|
56
|
+
await taskReplay(ctx, envelopeRaw, flags, dryRun);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const title = stringFlag(flags.title);
|
|
60
|
+
if (title === undefined || title.trim() === "") {
|
|
61
|
+
throw new CliError("usage", "missing required flag: --title (must be non-empty).");
|
|
62
|
+
}
|
|
63
|
+
const instructions = stringFlag(flags.instructions);
|
|
64
|
+
if (instructions === undefined || instructions.trim() === "") {
|
|
65
|
+
throw new CliError("usage", "missing required flag: --instructions (must be non-empty).");
|
|
66
|
+
}
|
|
67
|
+
const callback = resolveCallback(flags); // undefined → terminal task (opt-in)
|
|
68
|
+
const checklistRaw = Array.isArray(flags.checklist) ? flags.checklist.filter((c) => typeof c === "string") : [];
|
|
69
|
+
const checklist = checklistRaw.map((text) => ({ text }));
|
|
70
|
+
const resolvers = resolveResolvers(flags, dryRun, ctx);
|
|
71
|
+
const action = { instructions };
|
|
72
|
+
if (checklist.length > 0)
|
|
73
|
+
action.checklist = checklist;
|
|
74
|
+
if (resolvers.length > 0)
|
|
75
|
+
action.allowed_resolvers = resolvers;
|
|
76
|
+
if (callback !== undefined)
|
|
77
|
+
action.callback = callback;
|
|
78
|
+
const idempotencyKey = requireNonEmpty(flags, "idempotency-key");
|
|
79
|
+
const createdAt = requireNonEmpty(flags, "created-at");
|
|
80
|
+
const body = stringFlag(flags.body);
|
|
81
|
+
// Seal an optional `--state` resume blob (requires MA2H_STATE_SEAL_KEY) before the dry-run
|
|
82
|
+
// branch so the preview shows the sealed envelope; `task await` opens it on resolution.
|
|
83
|
+
const state = resolveState(stringFlag(flags.state));
|
|
84
|
+
let agentId = ctx.config.account ?? "oh-hai/task-bot";
|
|
85
|
+
let token = "";
|
|
86
|
+
if (!dryRun) {
|
|
87
|
+
const identity = await resolveSubmitIdentity(ctx);
|
|
88
|
+
agentId = identity.agentId;
|
|
89
|
+
token = identity.token;
|
|
90
|
+
}
|
|
91
|
+
const task = buildTask({
|
|
92
|
+
agent: { id: agentId, run_id: resolveRunId() },
|
|
93
|
+
title,
|
|
94
|
+
action,
|
|
95
|
+
...(body !== undefined ? { body } : {}),
|
|
96
|
+
...(idempotencyKey !== undefined ? { idempotency_key: idempotencyKey } : {}),
|
|
97
|
+
...(createdAt !== undefined ? { created_at: createdAt } : {}),
|
|
98
|
+
...(state !== undefined ? { state } : {}),
|
|
99
|
+
});
|
|
100
|
+
if (dryRun) {
|
|
101
|
+
printDryRunEnvelope(ctx, "task.submit", task);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const ack = await submitEnvelope(ctx, token, task);
|
|
105
|
+
reportTaskSubmit(ctx, ack, callback, false);
|
|
106
|
+
}
|
|
107
|
+
/** Replay a captured task envelope verbatim (§4.8). Enforces the same human-resolver invariant as
|
|
108
|
+
* the compose path — a captured envelope with no human resolver is just as unresolvable. */
|
|
109
|
+
async function taskReplay(ctx, envelopeRaw, flags, dryRun) {
|
|
110
|
+
const conflicts = ["title", "body", "instructions", "checklist", "resolver", "callback", "callback-url", "idempotency-key", "created-at", "state"].filter((k) => flags[k] !== undefined);
|
|
111
|
+
if (conflicts.length > 0) {
|
|
112
|
+
throw new CliError("usage", `--envelope replays a captured envelope verbatim and cannot be combined with composition flags (${conflicts.map((k) => `--${k}`).join(", ")}).`);
|
|
113
|
+
}
|
|
114
|
+
// readEnvelopeArg may throw a usage CliError (missing @file); only the JSON.parse is wrapped, so
|
|
115
|
+
// a truncated/invalid captured envelope is a usage error (exit 2), not the generic error path.
|
|
116
|
+
const raw = await readEnvelopeArg(envelopeRaw, ctx.runtime.readStdin);
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
parsed = JSON.parse(raw);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
throw new CliError("usage", `--envelope is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
123
|
+
}
|
|
124
|
+
const envelope = assertTaskEnvelope(parsed);
|
|
125
|
+
if (!hasConcreteHumanResolver(replayResolvers(envelope))) {
|
|
126
|
+
if (!dryRun) {
|
|
127
|
+
throw new CliError("usage", "--envelope task has no concrete human:<id> resolver in action.allowed_resolvers (a wildcard can " +
|
|
128
|
+
"never match): the resolve route is human-only, so it could never be marked done (inbox Done/Dismiss → " +
|
|
129
|
+
"403) and would sit open forever. Capture the envelope with a concrete human resolver. (Use --dry-run to preview anyway.)");
|
|
130
|
+
}
|
|
131
|
+
ctx.io.err("⚠ --envelope task has no concrete human:<id> resolver: this previewed replay would be UNRESOLVABLE.");
|
|
132
|
+
}
|
|
133
|
+
if (dryRun) {
|
|
134
|
+
printDryRunEnvelope(ctx, "task.submit", envelope);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const token = await requireReplayToken(ctx, envelope.agent.id);
|
|
138
|
+
const ack = await submitEnvelope(ctx, token, envelope);
|
|
139
|
+
reportTaskSubmit(ctx, ack, envelope.action.callback, true);
|
|
140
|
+
}
|
|
141
|
+
async function taskAwait(ctx, flags) {
|
|
142
|
+
const id = stringFlag(flags.id);
|
|
143
|
+
if (id === undefined || id.trim() === "")
|
|
144
|
+
throw new CliError("usage", "missing required flag: --id.");
|
|
145
|
+
const { intervalMs, budgetMs, perRequestMs } = resolveAwaitTiming(ctx, stringFlag(flags.interval), stringFlag(flags["await-timeout"]), stringFlag(flags.timeout));
|
|
146
|
+
const token = await requireToken(ctx);
|
|
147
|
+
const body = await awaitResolution(ctx, { token, id, expectedType: "task", intervalMs, budgetMs, perRequestMs });
|
|
148
|
+
// Open the agent-owned sealed `state` when MA2H_STATE_SEAL_KEY is set (undefined otherwise).
|
|
149
|
+
const openedState = openResumeState(body);
|
|
150
|
+
printResolution(ctx, body, false, openedState); // a task Response carries no value
|
|
151
|
+
}
|
|
152
|
+
// --- composition helpers ---
|
|
153
|
+
/** The OPT-IN return leg: a callback is attached only when `--callback` is given (omit → terminal
|
|
154
|
+
* task). A `--callback-url` without `--callback` is a mistake, rejected rather than dropped. */
|
|
155
|
+
function resolveCallback(flags) {
|
|
156
|
+
const callbackRaw = stringFlag(flags.callback);
|
|
157
|
+
const callbackUrl = stringFlag(flags["callback-url"]);
|
|
158
|
+
if (callbackRaw === undefined) {
|
|
159
|
+
if (callbackUrl !== undefined) {
|
|
160
|
+
throw new CliError("usage", "--callback-url is only valid with --callback push (no --callback given).");
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
if (!isCallbackMode(callbackRaw)) {
|
|
165
|
+
throw new CliError("usage", `invalid --callback "${callbackRaw}" (expected one of: pull, push).`);
|
|
166
|
+
}
|
|
167
|
+
if (callbackRaw === "push") {
|
|
168
|
+
if (callbackUrl === undefined || callbackUrl.trim() === "") {
|
|
169
|
+
throw new CliError("usage", "--callback push requires --callback-url.");
|
|
170
|
+
}
|
|
171
|
+
return { mode: "push", url: normalizePushUrl(callbackUrl) };
|
|
172
|
+
}
|
|
173
|
+
if (callbackUrl !== undefined) {
|
|
174
|
+
throw new CliError("usage", `--callback-url is only valid with --callback push (got --callback ${callbackRaw}).`);
|
|
175
|
+
}
|
|
176
|
+
return { mode: "pull" };
|
|
177
|
+
}
|
|
178
|
+
function resolveResolvers(flags, dryRun, ctx) {
|
|
179
|
+
const raw = Array.isArray(flags.resolver) ? flags.resolver.filter((r) => typeof r === "string") : [];
|
|
180
|
+
const resolvers = [];
|
|
181
|
+
for (const r of raw) {
|
|
182
|
+
if (!isActor(r))
|
|
183
|
+
throw new CliError("usage", `invalid --resolver "${r}" (expected <human|agent|system>:<id>).`);
|
|
184
|
+
if (r.slice(r.indexOf(":") + 1).includes("*")) {
|
|
185
|
+
throw new CliError("usage", `invalid --resolver "${r}": wildcards are not supported — name a concrete actor like human:owner.`);
|
|
186
|
+
}
|
|
187
|
+
resolvers.push(r);
|
|
188
|
+
}
|
|
189
|
+
// A task needs a concrete human resolver to be resolvable — the resolve route is human-only, so
|
|
190
|
+
// a task with none (or only agent:/system: resolvers) can never be marked done and stays open
|
|
191
|
+
// forever. "Terminal by default" means the AGENT doesn't wait — not that a human can act on a
|
|
192
|
+
// human-resolver-less task. A real submit without one is a hard error; --dry-run may preview.
|
|
193
|
+
if (!hasHumanResolver(resolvers)) {
|
|
194
|
+
if (!dryRun) {
|
|
195
|
+
const detail = resolvers.length > 0
|
|
196
|
+
? `the only --resolver(s) given (${resolvers.join(", ")}) are not human:<id>`
|
|
197
|
+
: "with none it fails closed to agent:<id> only";
|
|
198
|
+
throw new CliError("usage", `a task needs at least one --resolver human:<id>: ${detail}, and the resolve route is human-only, so ` +
|
|
199
|
+
"the task could never be marked done. (Use --dry-run to preview the envelope without one.)");
|
|
200
|
+
}
|
|
201
|
+
ctx.io.err("⚠ no --resolver human:<id> given: this previewed task would be UNRESOLVABLE (resolve is human-only).");
|
|
202
|
+
}
|
|
203
|
+
return resolvers;
|
|
204
|
+
}
|
|
205
|
+
/** Print the task submit acknowledgement + a callback-appropriate next-step hint. `--json` →
|
|
206
|
+
* `{ id, review_url? }` (a task Response has no poll semantics the CLI surfaces on submit). */
|
|
207
|
+
function reportTaskSubmit(ctx, ack, callback, replayed) {
|
|
208
|
+
if (ctx.json) {
|
|
209
|
+
ctx.io.log(serializeEnvelope(buildOk("task.submit", { id: ack.id, review_url: ack.review_url })));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
printSubmitAck(ctx, replayed ? "task submitted (replayed envelope)" : "task submitted", ack, true);
|
|
213
|
+
if (callback?.mode === "pull") {
|
|
214
|
+
ctx.io.log(`\nResume (pull): oh-hai task await --id ${sanitizeForTerminal(ack.id)}${resumeRoutingSuffix(ctx)}`);
|
|
215
|
+
}
|
|
216
|
+
else if (callback?.mode === "push") {
|
|
217
|
+
ctx.io.log(`\nResume (push): the Hub will POST the signed Response to ${callback.url} when the human resolves it.`);
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
ctx.io.log("\nHanded off (terminal): the human resolves it in the inbox; the agent expects nothing back.");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
//# sourceMappingURL=task.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task.js","sourceRoot":"","sources":["../../src/commands/task.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { CommandContext } from "./context.js";
|
|
2
|
+
/** A redaction-safe fingerprint of a token (§5.4). Real bearer tokens are long + high-entropy,
|
|
3
|
+
* so the last 4 chars identify one without revealing it; for an unexpectedly short token we
|
|
4
|
+
* fall back to a non-reversible hash prefix so the fingerprint can never be the token itself. */
|
|
5
|
+
export declare function fingerprintToken(token: string): string;
|
|
6
|
+
export declare function whoamiCommand(ctx: CommandContext): Promise<void>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// `oh-hai whoami` (docs/specs/cli.md §4.3). Show the resolved identity WITHOUT revealing the
|
|
2
|
+
// secret (§5.4): agent id, base URL, token presence + a redacted fingerprint, and where the
|
|
3
|
+
// token resolved from (`keychain` / `file` / `env`). Works fully offline. `--check` adds a
|
|
4
|
+
// network probe of the Hub for token validity: exit 0 valid, 3 on 401/403, 5 unreachable (§9).
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
|
|
7
|
+
import { resolveIdentity } from "../auth/resolve-token.js";
|
|
8
|
+
import { sanitizeForTerminal } from "../terminal.js";
|
|
9
|
+
import { flagOn, parseCommandArgs } from "./flags.js";
|
|
10
|
+
import { throwTransportError } from "./http.js";
|
|
11
|
+
/** A redaction-safe fingerprint of a token (§5.4). Real bearer tokens are long + high-entropy,
|
|
12
|
+
* so the last 4 chars identify one without revealing it; for an unexpectedly short token we
|
|
13
|
+
* fall back to a non-reversible hash prefix so the fingerprint can never be the token itself. */
|
|
14
|
+
export function fingerprintToken(token) {
|
|
15
|
+
return token.length > 8 ? token.slice(-4) : createHash("sha256").update(token).digest("hex").slice(0, 4);
|
|
16
|
+
}
|
|
17
|
+
/** Default per-request bound for the `--check` probe when no `--timeout`/`MA2H_TIMEOUT_MS` is
|
|
18
|
+
* set. §9 promises offline is an explicit state, never a silent hang — a Hub that accepts the
|
|
19
|
+
* connection but never responds must still surface a timeout rather than block forever. */
|
|
20
|
+
const DEFAULT_CHECK_TIMEOUT_MS = 10_000;
|
|
21
|
+
/** Probe the Hub for token validity using an endpoint that exists post-#96: an authenticated
|
|
22
|
+
* GET of a nonexistent message id. Because the Hub authenticates BEFORE looking the id up, the
|
|
23
|
+
* ONLY response that proves the presented bearer authenticated is the expected `404` (agent
|
|
24
|
+
* authenticated, message not found). `401`/`403` = invalid/revoked; `5xx` = the Hub errored (no
|
|
25
|
+
* validity signal); a transport failure = unreachable/timeout. Any other status (a `2xx`/`3xx`
|
|
26
|
+
* from a proxy that ignores `Authorization`, a wrong `--base-url`) is NOT proof of validity and
|
|
27
|
+
* is reported as such rather than a false "valid". Side-effect-free — a GET creates nothing. */
|
|
28
|
+
async function checkToken(ctx, token) {
|
|
29
|
+
if (token === undefined) {
|
|
30
|
+
throw new CliError("auth", "not authenticated — no stored token to check (run `oh-hai login`).");
|
|
31
|
+
}
|
|
32
|
+
const url = `${ctx.config.baseUrl}/v1/messages/oh-hai-whoami-check-nonexistent`;
|
|
33
|
+
const init = {
|
|
34
|
+
method: "GET",
|
|
35
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
36
|
+
signal: AbortSignal.timeout(ctx.config.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS),
|
|
37
|
+
};
|
|
38
|
+
let status;
|
|
39
|
+
try {
|
|
40
|
+
status = (await ctx.runtime.fetchImpl(url, init)).status;
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// A per-request timeout is exit 7 (§7); a DNS/connection failure is exit 5 (§9).
|
|
44
|
+
throwTransportError(error, ctx.config.baseUrl);
|
|
45
|
+
}
|
|
46
|
+
if (status === 401 || status === 403) {
|
|
47
|
+
throw new CliError("auth", `token is invalid or revoked (Hub returned ${status}).`);
|
|
48
|
+
}
|
|
49
|
+
if (status >= 500) {
|
|
50
|
+
throw new CliError("server", `Hub returned ${status}; token validity could not be confirmed.`);
|
|
51
|
+
}
|
|
52
|
+
if (status !== 404) {
|
|
53
|
+
// Not the expected authenticated-and-not-found response — could be a wrong base URL or a
|
|
54
|
+
// proxy that never reached the Hub's auth. Don't claim the token is valid.
|
|
55
|
+
throw new CliError("error", `unexpected Hub response ${status}; could not confirm token validity (check --base-url).`);
|
|
56
|
+
}
|
|
57
|
+
// 404 → the bearer authenticated and the probe id doesn't exist: the token is valid.
|
|
58
|
+
}
|
|
59
|
+
export async function whoamiCommand(ctx) {
|
|
60
|
+
const { values } = parseCommandArgs(ctx.argv, { check: { type: "boolean" } });
|
|
61
|
+
// Skip opening (and probing) the local store when the CI env token already resolves it (§5.3).
|
|
62
|
+
const store = ctx.config.tokenSource === "env" ? undefined : await ctx.runtime.openStore();
|
|
63
|
+
// `resolveIdentity` also DISCOVERS the account when none was configured (the device-code login
|
|
64
|
+
// path stores under a Hub-chosen agent id) — so display the resolved account, not config.account.
|
|
65
|
+
const resolved = await resolveIdentity(ctx.config, store);
|
|
66
|
+
const account = resolved.account;
|
|
67
|
+
const fingerprint = resolved.token !== undefined ? fingerprintToken(resolved.token) : undefined;
|
|
68
|
+
// A failing --check throws before any identity is printed (the command result IS the check).
|
|
69
|
+
if (flagOn(values.check)) {
|
|
70
|
+
await checkToken(ctx, resolved.token);
|
|
71
|
+
}
|
|
72
|
+
if (ctx.json) {
|
|
73
|
+
const token = { present: resolved.token !== undefined, source: resolved.source };
|
|
74
|
+
if (fingerprint !== undefined) {
|
|
75
|
+
token.fingerprint = fingerprint;
|
|
76
|
+
}
|
|
77
|
+
ctx.io.log(serializeEnvelope(buildOk("whoami", { agent_id: account, base_url: ctx.config.baseUrl, token })));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Sanitize before terminal output: a DISCOVERED account is the raw agent id the Hub chose at
|
|
81
|
+
// login time, so a hostile Hub could have stored control characters in it (the raw id is kept
|
|
82
|
+
// for lookup and the JSON envelope, where JSON.stringify escapes them — §5.4 / login parity).
|
|
83
|
+
ctx.io.log(`agent: ${account !== undefined ? sanitizeForTerminal(account) : "(none — set --account/--agent or MA2H_AGENT_ID)"}`);
|
|
84
|
+
ctx.io.log(`base url: ${ctx.config.baseUrl}`);
|
|
85
|
+
ctx.io.log(resolved.token !== undefined
|
|
86
|
+
? `token: present (…${fingerprint}) [${resolved.source}]`
|
|
87
|
+
: "token: absent");
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=whoami.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,4FAA4F;AAC5F,2FAA2F;AAC3F,+FAA+F;AAE/F,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAEhD;;kGAEkG;AAClG,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3G,CAAC;AAED;;4FAE4F;AAC5F,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAExC;;;;;;iGAMiG;AACjG,KAAK,UAAU,UAAU,CAAC,GAAmB,EAAE,KAAyB;IACtE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,oEAAoE,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,8CAA8C,CAAC;IAChF,MAAM,IAAI,GAAG;QACX,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,KAAK,EAAE,EAAE;QAC7C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,IAAI,wBAAwB,CAAC;KAC9E,CAAC;IAEF,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iFAAiF;QACjF,mBAAmB,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACrC,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,6CAA6C,MAAM,IAAI,CAAC,CAAC;IACtF,CAAC;IACD,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QAClB,MAAM,IAAI,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,MAAM,0CAA0C,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;QACnB,yFAAyF;QACzF,2EAA2E;QAC3E,MAAM,IAAI,QAAQ,CAChB,OAAO,EACP,2BAA2B,MAAM,wDAAwD,CAC1F,CAAC;IACJ,CAAC;IACD,qFAAqF;AACvF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAmB;IACrD,MAAM,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;IAC9E,+FAA+F;IAC/F,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;IAC3F,+FAA+F;IAC/F,kGAAkG;IAClG,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;IACjC,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAEhG,6FAA6F;IAC7F,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,MAAM,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC;IACxC,CAAC;IAED,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;QACb,MAAM,KAAK,GAA4B,EAAE,OAAO,EAAE,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC1G,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC;QAClC,CAAC;QACD,GAAG,CAAC,EAAE,CAAC,GAAG,CACR,iBAAiB,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,CACjG,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,6FAA6F;QAC7F,8FAA8F;QAC9F,8FAA8F;QAC9F,GAAG,CAAC,EAAE,CAAC,GAAG,CACR,aAAa,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,iDAAiD,EAAE,CACxH,CAAC;QACF,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9C,GAAG,CAAC,EAAE,CAAC,GAAG,CACR,QAAQ,CAAC,KAAK,KAAK,SAAS;YAC1B,CAAC,CAAC,uBAAuB,WAAW,MAAM,QAAQ,CAAC,MAAM,GAAG;YAC5D,CAAC,CAAC,kBAAkB,CACvB,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProjectConfig, UserConfig } from "./config.js";
|
|
2
|
+
/** A parsed scalar from the flat dialect: string, integer, or boolean. */
|
|
3
|
+
type TomlValue = string | number | boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Injectable filesystem/environment seam so the loaders are unit-testable against an in-memory map
|
|
6
|
+
* with zero disk access. `readFile` returns undefined for a missing or unreadable file (ENOENT etc.).
|
|
7
|
+
*/
|
|
8
|
+
export interface ConfigFileIo {
|
|
9
|
+
readFile(path: string): string | undefined;
|
|
10
|
+
homedir: string;
|
|
11
|
+
cwd: string;
|
|
12
|
+
xdgConfigHome: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** The two parsed config objects `resolveConfig` consumes; either may be absent. */
|
|
15
|
+
export interface LoadedConfigFiles {
|
|
16
|
+
userConfig: UserConfig | undefined;
|
|
17
|
+
projectConfig: ProjectConfig | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse the minimal config dialect: flat `key = value` lines plus an optional `[oh-hai]` section.
|
|
21
|
+
* Values are quoted strings (single/double), integers (optional sign + `_` separators), or booleans.
|
|
22
|
+
* `#` starts a comment. Keys are collected from the top level (before any section header) and from an
|
|
23
|
+
* `[oh-hai]` section; keys under any other section are ignored. Unparseable lines are skipped so a
|
|
24
|
+
* malformed file degrades gracefully — this function never throws.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseToml(text: string): Record<string, TomlValue>;
|
|
27
|
+
/** Load `~/.config/oh-hai/config.toml` ($XDG_CONFIG_HOME honored). Undefined when the file is absent. */
|
|
28
|
+
export declare function loadUserConfig(io: ConfigFileIo): UserConfig | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Discover the per-project config by walking up from cwd for a `.oh-hai/config.toml`; the nearest
|
|
31
|
+
* ancestor wins. Undefined when none is found up to the filesystem root.
|
|
32
|
+
*/
|
|
33
|
+
export declare function loadProjectConfig(io: ConfigFileIo): ProjectConfig | undefined;
|
|
34
|
+
/** Load both config files (user-global + per-project) for `resolveConfig`. */
|
|
35
|
+
export declare function loadConfigFiles(io: ConfigFileIo): LoadedConfigFiles;
|
|
36
|
+
/** Production `ConfigFileIo` backed by the real filesystem, home dir, cwd, and environment. */
|
|
37
|
+
export declare function nodeConfigFileIo(): ConfigFileIo;
|
|
38
|
+
export {};
|