@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.
Files changed (110) hide show
  1. package/README.md +154 -0
  2. package/dist/auth/file-backend.d.ts +16 -0
  3. package/dist/auth/file-backend.js +98 -0
  4. package/dist/auth/file-backend.js.map +1 -0
  5. package/dist/auth/keychain.d.ts +54 -0
  6. package/dist/auth/keychain.js +232 -0
  7. package/dist/auth/keychain.js.map +1 -0
  8. package/dist/auth/resolve-token.d.ts +34 -0
  9. package/dist/auth/resolve-token.js +91 -0
  10. package/dist/auth/resolve-token.js.map +1 -0
  11. package/dist/auth/secure-write.d.ts +2 -0
  12. package/dist/auth/secure-write.js +30 -0
  13. package/dist/auth/secure-write.js.map +1 -0
  14. package/dist/auth/token-store.d.ts +104 -0
  15. package/dist/auth/token-store.js +208 -0
  16. package/dist/auth/token-store.js.map +1 -0
  17. package/dist/cli.d.ts +16 -0
  18. package/dist/cli.js +238 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/commands/agents.d.ts +2 -0
  21. package/dist/commands/agents.js +370 -0
  22. package/dist/commands/agents.js.map +1 -0
  23. package/dist/commands/ask.d.ts +2 -0
  24. package/dist/commands/ask.js +246 -0
  25. package/dist/commands/ask.js.map +1 -0
  26. package/dist/commands/context.d.ts +72 -0
  27. package/dist/commands/context.js +7 -0
  28. package/dist/commands/context.js.map +1 -0
  29. package/dist/commands/doctor.d.ts +2 -0
  30. package/dist/commands/doctor.js +237 -0
  31. package/dist/commands/doctor.js.map +1 -0
  32. package/dist/commands/flags.d.ts +25 -0
  33. package/dist/commands/flags.js +100 -0
  34. package/dist/commands/flags.js.map +1 -0
  35. package/dist/commands/handlers.d.ts +2 -0
  36. package/dist/commands/handlers.js +26 -0
  37. package/dist/commands/handlers.js.map +1 -0
  38. package/dist/commands/http.d.ts +8 -0
  39. package/dist/commands/http.js +19 -0
  40. package/dist/commands/http.js.map +1 -0
  41. package/dist/commands/inbox.d.ts +2 -0
  42. package/dist/commands/inbox.js +111 -0
  43. package/dist/commands/inbox.js.map +1 -0
  44. package/dist/commands/login.d.ts +2 -0
  45. package/dist/commands/login.js +272 -0
  46. package/dist/commands/login.js.map +1 -0
  47. package/dist/commands/logout.d.ts +2 -0
  48. package/dist/commands/logout.js +35 -0
  49. package/dist/commands/logout.js.map +1 -0
  50. package/dist/commands/messaging/await.d.ts +43 -0
  51. package/dist/commands/messaging/await.js +125 -0
  52. package/dist/commands/messaging/await.js.map +1 -0
  53. package/dist/commands/messaging/build.d.ts +46 -0
  54. package/dist/commands/messaging/build.js +66 -0
  55. package/dist/commands/messaging/build.js.map +1 -0
  56. package/dist/commands/messaging/http.d.ts +22 -0
  57. package/dist/commands/messaging/http.js +270 -0
  58. package/dist/commands/messaging/http.js.map +1 -0
  59. package/dist/commands/messaging/identity.d.ts +29 -0
  60. package/dist/commands/messaging/identity.js +63 -0
  61. package/dist/commands/messaging/identity.js.map +1 -0
  62. package/dist/commands/messaging/shared.d.ts +53 -0
  63. package/dist/commands/messaging/shared.js +135 -0
  64. package/dist/commands/messaging/shared.js.map +1 -0
  65. package/dist/commands/messaging/state.d.ts +26 -0
  66. package/dist/commands/messaging/state.js +82 -0
  67. package/dist/commands/messaging/state.js.map +1 -0
  68. package/dist/commands/messaging/validate.d.ts +40 -0
  69. package/dist/commands/messaging/validate.js +193 -0
  70. package/dist/commands/messaging/validate.js.map +1 -0
  71. package/dist/commands/messaging/wire.d.ts +133 -0
  72. package/dist/commands/messaging/wire.js +16 -0
  73. package/dist/commands/messaging/wire.js.map +1 -0
  74. package/dist/commands/notify.d.ts +2 -0
  75. package/dist/commands/notify.js +68 -0
  76. package/dist/commands/notify.js.map +1 -0
  77. package/dist/commands/registry.d.ts +14 -0
  78. package/dist/commands/registry.js +144 -0
  79. package/dist/commands/registry.js.map +1 -0
  80. package/dist/commands/stub.d.ts +1 -0
  81. package/dist/commands/stub.js +9 -0
  82. package/dist/commands/stub.js.map +1 -0
  83. package/dist/commands/task.d.ts +2 -0
  84. package/dist/commands/task.js +223 -0
  85. package/dist/commands/task.js.map +1 -0
  86. package/dist/commands/whoami.d.ts +6 -0
  87. package/dist/commands/whoami.js +90 -0
  88. package/dist/commands/whoami.js.map +1 -0
  89. package/dist/config-file.d.ts +38 -0
  90. package/dist/config-file.js +233 -0
  91. package/dist/config-file.js.map +1 -0
  92. package/dist/config.d.ts +64 -0
  93. package/dist/config.js +97 -0
  94. package/dist/config.js.map +1 -0
  95. package/dist/envelope.d.ts +25 -0
  96. package/dist/envelope.js +41 -0
  97. package/dist/envelope.js.map +1 -0
  98. package/dist/exit-codes.d.ts +51 -0
  99. package/dist/exit-codes.js +57 -0
  100. package/dist/exit-codes.js.map +1 -0
  101. package/dist/help.d.ts +1 -0
  102. package/dist/help.js +17 -0
  103. package/dist/help.js.map +1 -0
  104. package/dist/terminal.d.ts +5 -0
  105. package/dist/terminal.js +18 -0
  106. package/dist/terminal.js.map +1 -0
  107. package/dist/version.d.ts +8 -0
  108. package/dist/version.js +23 -0
  109. package/dist/version.js.map +1 -0
  110. package/package.json +38 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { parseArgs } from "node:util";
2
+ import type { ParseArgsConfig } from "node:util";
3
+ type OptionsConfig = NonNullable<ParseArgsConfig["options"]>;
4
+ /** The global flags available on every command (§4.2). */
5
+ export declare const GLOBAL_OPTIONS: OptionsConfig;
6
+ /**
7
+ * Coerce a `parseArgs` boolean-flag value to on/off. Under `strict: false`, `--json` yields
8
+ * `true` but `--json=true` yields the STRING `"true"`, so a bare `=== true` check would treat
9
+ * the common `--json=true` / `--version=1` forms as off. Bare presence and truthy `=`-values
10
+ * are on; `--json=false` / `0` / `no` are off. A repeated flag becomes an array — last wins.
11
+ */
12
+ export declare function flagOn(value: unknown): boolean;
13
+ /** Parse argv for a command with no subcommand operand (the auth commands). */
14
+ export declare function parseCommandArgs(argv: string[], extra?: OptionsConfig): ReturnType<typeof parseArgs>;
15
+ /** Parse argv for a command that takes a SUBCOMMAND operand (`ask`/`task` → submit | await).
16
+ * Like `parseCommandArgs` (global + `extra` options, `strict:false` so the `--flag=value`
17
+ * boolean form isn't rejected, then reject any unknown flag), but `positionals[0]` is the
18
+ * command token and `positionals[1]` MUST be one of `subcommands`. With correct option types
19
+ * no flag value leaks into the positionals, so exactly `[command, subcommand]` is expected;
20
+ * a missing subcommand, an unknown one, or an extra operand is a usage error (exit 2). */
21
+ export declare function parseSubcommandArgs(argv: string[], subcommands: readonly string[], extra?: OptionsConfig): {
22
+ values: ReturnType<typeof parseArgs>["values"];
23
+ subcommand: string;
24
+ };
25
+ export {};
@@ -0,0 +1,100 @@
1
+ // Shared flag parsing (docs/specs/cli.md §4.2). The global-flag schema lives here so both the
2
+ // top-level entrypoint (cli.ts) and each per-command handler parse the SAME globals; a handler
3
+ // adds only its own command-specific options on top. `strict: false` mirrors the entrypoint —
4
+ // unknown tokens don't throw, they're tolerated (a handler ignores globals it doesn't consume).
5
+ import { parseArgs } from "node:util";
6
+ import { CliError } from "../envelope.js";
7
+ /** The global flags available on every command (§4.2). */
8
+ export const GLOBAL_OPTIONS = {
9
+ "base-url": { type: "string" },
10
+ account: { type: "string" },
11
+ agent: { type: "string" },
12
+ json: { type: "boolean" },
13
+ quiet: { type: "boolean", short: "q" },
14
+ verbose: { type: "boolean", short: "v" },
15
+ "no-color": { type: "boolean" },
16
+ timeout: { type: "string" },
17
+ help: { type: "boolean", short: "h" },
18
+ version: { type: "boolean" },
19
+ };
20
+ /**
21
+ * Coerce a `parseArgs` boolean-flag value to on/off. Under `strict: false`, `--json` yields
22
+ * `true` but `--json=true` yields the STRING `"true"`, so a bare `=== true` check would treat
23
+ * the common `--json=true` / `--version=1` forms as off. Bare presence and truthy `=`-values
24
+ * are on; `--json=false` / `0` / `no` are off. A repeated flag becomes an array — last wins.
25
+ */
26
+ export function flagOn(value) {
27
+ if (value === true) {
28
+ return true;
29
+ }
30
+ if (typeof value === "string") {
31
+ const v = value.trim().toLowerCase();
32
+ return v === "" || v === "true" || v === "1" || v === "yes" || v === "on";
33
+ }
34
+ if (Array.isArray(value)) {
35
+ return value.length > 0 && flagOn(value[value.length - 1]);
36
+ }
37
+ return false;
38
+ }
39
+ /** Parse argv against the global flags plus the command's own `extra` options with `strict: false`
40
+ * (so the `--flag=value` boolean form the CLI supports isn't rejected), then reject any flag NOT
41
+ * in the known set — so a typo like `oh-hai whoami --chek` is a usage error (exit 2), not silently
42
+ * ignored. Shared by the no-subcommand and subcommand parsers below. */
43
+ function parseKnownFlags(argv, extra) {
44
+ const options = { ...GLOBAL_OPTIONS, ...extra };
45
+ // Guard the parseArgs greedy-value footgun: under strict:false a string option either consumes the
46
+ // NEXT token even when it's another flag (`notify --title --dry-run` → title="--dry-run", --dry-run
47
+ // UNSET) or, when it's the last token, is left as boolean `true` (`notify --title Hi --base-url` →
48
+ // base-url silently falls back to default). Both let a malformed invocation perform a LIVE action
49
+ // instead of failing. If a bare `--<string-flag>` has no real value — the next token is another
50
+ // `--…` flag OR the args end — reject as usage (pass `--flag=<value>` for a value starting with `--`).
51
+ for (let i = 0; i < argv.length; i++) {
52
+ const tok = argv[i];
53
+ if (tok.startsWith("--") && tok !== "--" && !tok.includes("=") && options[tok.slice(2)]?.type === "string") {
54
+ const next = argv[i + 1];
55
+ if (next === undefined || next.startsWith("--")) {
56
+ const got = next === undefined ? "reached the end of the arguments" : `got ${next}`;
57
+ throw new CliError("usage", `${tok} expects a value but ${got}; to pass a value that starts with '--', use ${tok}=<value>.`);
58
+ }
59
+ }
60
+ }
61
+ const parsed = parseArgs({ args: argv, allowPositionals: true, strict: false, options });
62
+ const allowed = new Set(Object.keys(options));
63
+ for (const key of Object.keys(parsed.values)) {
64
+ if (!allowed.has(key)) {
65
+ throw new CliError("usage", `unknown flag: --${key}. Run 'oh-hai <command> --help'.`);
66
+ }
67
+ }
68
+ return parsed;
69
+ }
70
+ /** Parse argv for a command with no subcommand operand (the auth commands). */
71
+ export function parseCommandArgs(argv, extra = {}) {
72
+ const parsed = parseKnownFlags(argv, extra);
73
+ // positionals[0] is the command token; these auth commands take no positional operands, so a
74
+ // stray one (`oh-hai logout all`, `oh-hai whoami check`) is a typo, not a silently-ignored arg.
75
+ if (parsed.positionals.length > 1) {
76
+ throw new CliError("usage", `unexpected argument: ${parsed.positionals[1]}. Run 'oh-hai <command> --help'.`);
77
+ }
78
+ return parsed;
79
+ }
80
+ /** Parse argv for a command that takes a SUBCOMMAND operand (`ask`/`task` → submit | await).
81
+ * Like `parseCommandArgs` (global + `extra` options, `strict:false` so the `--flag=value`
82
+ * boolean form isn't rejected, then reject any unknown flag), but `positionals[0]` is the
83
+ * command token and `positionals[1]` MUST be one of `subcommands`. With correct option types
84
+ * no flag value leaks into the positionals, so exactly `[command, subcommand]` is expected;
85
+ * a missing subcommand, an unknown one, or an extra operand is a usage error (exit 2). */
86
+ export function parseSubcommandArgs(argv, subcommands, extra = {}) {
87
+ const parsed = parseKnownFlags(argv, extra);
88
+ const subcommand = parsed.positionals[1];
89
+ if (subcommand === undefined) {
90
+ throw new CliError("usage", `missing subcommand: expected one of ${subcommands.join(" | ")}.`);
91
+ }
92
+ if (!subcommands.includes(subcommand)) {
93
+ throw new CliError("usage", `unknown subcommand: ${subcommand} (expected ${subcommands.join(" | ")}).`);
94
+ }
95
+ if (parsed.positionals.length > 2) {
96
+ throw new CliError("usage", `unexpected argument: ${parsed.positionals[2]}.`);
97
+ }
98
+ return { values: parsed.values, subcommand };
99
+ }
100
+ //# sourceMappingURL=flags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flags.js","sourceRoot":"","sources":["../../src/commands/flags.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,+FAA+F;AAC/F,8FAA8F;AAC9F,gGAAgG;AAEhG,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI1C,0DAA0D;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAkB;IAC3C,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC9B,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC3B,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzB,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IACzB,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACtC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACxC,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC/B,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC3B,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;IACrC,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;CAC7B,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,KAAc;IACnC,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,IAAI,CAAC;IAC5E,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;yEAGyE;AACzE,SAAS,eAAe,CAAC,IAAc,EAAE,KAAoB;IAC3D,MAAM,OAAO,GAAG,EAAE,GAAG,cAAc,EAAE,GAAG,KAAK,EAAE,CAAC;IAChD,mGAAmG;IACnG,oGAAoG;IACpG,mGAAmG;IACnG,kGAAkG;IAClG,gGAAgG;IAChG,uGAAuG;IACvG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACrB,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3G,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACzB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChD,MAAM,GAAG,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;gBACpF,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,GAAG,GAAG,wBAAwB,GAAG,gDAAgD,GAAG,WAAW,CAAC,CAAC;YAC/H,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IACzF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,mBAAmB,GAAG,kCAAkC,CAAC,CAAC;QACxF,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAC9B,IAAc,EACd,QAAuB,EAAE;IAEzB,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC5C,6FAA6F;IAC7F,gGAAgG;IAChG,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,QAAQ,CAChB,OAAO,EACP,wBAAwB,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,kCAAkC,CAChF,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;2FAK2F;AAC3F,MAAM,UAAU,mBAAmB,CACjC,IAAc,EACd,WAA8B,EAC9B,QAAuB,EAAE;IAEzB,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACzC,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,uCAAuC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACjG,CAAC;IACD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,uBAAuB,UAAU,cAAc,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC1G,CAAC;IACD,IAAI,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,wBAAwB,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { CommandHandler } from "./context.js";
2
+ export declare const COMMAND_HANDLERS: Record<string, CommandHandler>;
@@ -0,0 +1,26 @@
1
+ // The command-handler registry (docs/specs/cli.md §4). #106 wired the three auth commands; #107
2
+ // wires the messaging commands (notify / ask / task); #209 wires `agents` (account/identity
3
+ // management); #108 wires `doctor` (health). Every registry command now has a handler; the
4
+ // entrypoint (cli.ts) looks a command up here first, and a miss still falls through to
5
+ // `notImplemented()` as a defensive fallback for any future entry added without a handler.
6
+ import { agentsCommand } from "./agents.js";
7
+ import { askCommand } from "./ask.js";
8
+ import { doctorCommand } from "./doctor.js";
9
+ import { inboxCommand } from "./inbox.js";
10
+ import { loginCommand } from "./login.js";
11
+ import { logoutCommand } from "./logout.js";
12
+ import { notifyCommand } from "./notify.js";
13
+ import { taskCommand } from "./task.js";
14
+ import { whoamiCommand } from "./whoami.js";
15
+ export const COMMAND_HANDLERS = {
16
+ login: loginCommand,
17
+ logout: logoutCommand,
18
+ whoami: whoamiCommand,
19
+ notify: notifyCommand,
20
+ ask: askCommand,
21
+ task: taskCommand,
22
+ agents: agentsCommand,
23
+ inbox: inboxCommand,
24
+ doctor: doctorCommand,
25
+ };
26
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/commands/handlers.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,4FAA4F;AAC5F,2FAA2F;AAC3F,uFAAuF;AACvF,2FAA2F;AAE3F,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,gBAAgB,GAAmC;IAC9D,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,MAAM,EAAE,aAAa;IACrB,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,aAAa;IACrB,KAAK,EAAE,YAAY;IACnB,MAAM,EAAE,aAAa;CACtB,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Map a transport-layer failure (the `fetch` itself rejecting, before any HTTP response) to the
3
+ * right §7 exit code: a per-request timeout is exit 7 (`timeout`, §7); any other connection failure
4
+ * (DNS, refused, unreachable) is exit 5 (`network`, §9). A non-`Error` throw is not a timeout, so it
5
+ * takes the network branch. Always throws — the `never` return lets a caller use it as the whole
6
+ * body of a `catch`.
7
+ */
8
+ export declare function throwTransportError(error: unknown, baseUrl: string): never;
@@ -0,0 +1,19 @@
1
+ // Shared HTTP helper for the network-touching commands (login, whoami). Kept tiny and
2
+ // dependency-free: the one thing worth sharing is the transport-error → §7 exit-code mapping, so a
3
+ // DNS/refused failure and a per-request timeout surface the SAME codes and messages from every
4
+ // command (cli spec §7/§9) instead of each command carrying its own copy.
5
+ import { CliError } from "../envelope.js";
6
+ /**
7
+ * Map a transport-layer failure (the `fetch` itself rejecting, before any HTTP response) to the
8
+ * right §7 exit code: a per-request timeout is exit 7 (`timeout`, §7); any other connection failure
9
+ * (DNS, refused, unreachable) is exit 5 (`network`, §9). A non-`Error` throw is not a timeout, so it
10
+ * takes the network branch. Always throws — the `never` return lets a caller use it as the whole
11
+ * body of a `catch`.
12
+ */
13
+ export function throwTransportError(error, baseUrl) {
14
+ if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
15
+ throw new CliError("timeout", `Hub did not respond within the timeout (${baseUrl}).`);
16
+ }
17
+ throw new CliError("network", `Hub unreachable at ${baseUrl}.`);
18
+ }
19
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/commands/http.ts"],"names":[],"mappings":"AAAA,sFAAsF;AACtF,mGAAmG;AACnG,+FAA+F;AAC/F,0EAA0E;AAE1E,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAc,EAAE,OAAe;IACjE,IAAI,KAAK,YAAY,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,CAAC;QAC7F,MAAM,IAAI,QAAQ,CAAC,SAAS,EAAE,2CAA2C,OAAO,IAAI,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,IAAI,QAAQ,CAAC,SAAS,EAAE,sBAAsB,OAAO,GAAG,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { CommandContext } from "./context.js";
2
+ export declare function inboxCommand(ctx: CommandContext): Promise<void>;
@@ -0,0 +1,111 @@
1
+ // `oh-hai inbox` (docs/specs/cli.md §4.7) — the CLI-owned poll loop for the MA2H v0.4 human→agent
2
+ // leg (spec §13/§14/§15; issue #153). Subcommand `watch`: DRAIN the agent's mailbox, HAND each
3
+ // directive to the agent runtime (a stdout stream), post the single batched consume-ACK, and — since
4
+ // every authenticated drain touches `agent_seen` server-side — emit the PRESENCE heartbeat simply by
5
+ // polling on an interval. Owning this plumbing is the point: "so the agent invents nothing and stays
6
+ // spec-conformant" — a runtime can `oh-hai inbox watch --json | my-runtime` and get a clean directive stream.
7
+ //
8
+ // TRUST / SCOPE (deliberate): the CLI drains over the AUTHENTICATED PULL channel (bearer GET over
9
+ // TLS, straight from the Hub) and trusts it exactly as `await.ts` trusts a pulled Response — it does
10
+ // NOT verify the §9.7 inbound signature, keep a jti-replay cache, or run the §13.4 addressee check.
11
+ // Those are PUSH/webhook-channel obligations (conformance vectors dp-007 cases 5–6), not this pull
12
+ // loop's. The signed delivery `{ directive, signature }` is forwarded VERBATIM so a runtime that
13
+ // wants defense-in-depth can verify it against the Hub key. In-session `id` dedup (§13.4) IS handled
14
+ // here (see the loop). Sibling subcommands (`inbox list` / `inbox ack`) and long-poll are out of scope.
15
+ import { buildOk, serializeEnvelope } from "../envelope.js";
16
+ import { parseSubcommandArgs } from "./flags.js";
17
+ import { ackInbox, drainInbox, redactToken } from "./messaging/http.js";
18
+ import { requireToken } from "./messaging/identity.js";
19
+ import { positiveInt, sanitizeForTerminal, strictBoolFlag, stringFlag } from "./messaging/shared.js";
20
+ const SUBCOMMANDS = ["watch"];
21
+ const INBOX_WATCH_OPTIONS = {
22
+ interval: { type: "string" },
23
+ max: { type: "string" },
24
+ once: { type: "boolean" },
25
+ };
26
+ /** Default poll cadence (ms). Well under the Hub's 90s presence-freshness window so the agent stays
27
+ * `online` between polls, and responsive to arriving directives without hammering the Hub. */
28
+ const DEFAULT_INTERVAL_MS = 5000;
29
+ /** A sane upper bound for `--max` (a drain batch size, not a timer): generous enough to never reject a
30
+ * realistic mailbox drain, small enough to reject an absurd `?max=` — the Hub clamps to its own cap
31
+ * regardless. Distinct from the timer ceiling `--interval` uses. */
32
+ const MAX_DRAIN_BATCH = 10_000;
33
+ export async function inboxCommand(ctx) {
34
+ // `watch` is the only subcommand, so dispatch is unconditional — the parse still validates the
35
+ // operand (an unknown/absent subcommand is a usage error) before we get here.
36
+ const { values } = parseSubcommandArgs(ctx.argv, SUBCOMMANDS, INBOX_WATCH_OPTIONS);
37
+ await inboxWatch(ctx, values);
38
+ }
39
+ async function inboxWatch(ctx, flags) {
40
+ const intervalMs = flags.interval !== undefined ? positiveInt(stringFlag(flags.interval), "--interval") : DEFAULT_INTERVAL_MS;
41
+ const max = flags.max !== undefined ? positiveInt(stringFlag(flags.max), "--max", "a positive integer", MAX_DRAIN_BATCH) : undefined;
42
+ // Strict boolean: a mistyped `--once=treu` is a usage error, not a silent fall-through to the
43
+ // infinite watch loop (it would keep draining + acking every interval instead of a one-shot).
44
+ const once = strictBoolFlag(flags.once, "--once");
45
+ // The mailbox is selected by the bearer alone (the Hub derives the agent from the token), so this
46
+ // needs only a token — like `await`, and unlike a submit, it does not require a resolvable account.
47
+ const token = await requireToken(ctx);
48
+ // Cross-poll dedup (§13.4): once a directive id has been emitted AND its batch acked, a later
49
+ // redelivery of that same id (fresh t/jti, after a crash or the server's visibility timeout) is
50
+ // suppressed from re-emission — but still acked so the Hub consumes it. `seen` commits only AFTER a
51
+ // successful ack (below), so an unacked batch is never suppressed: on ack failure the loop throws
52
+ // and exits, and a fresh process re-emits the redelivery (at-least-once holds). The set is in-memory
53
+ // / per-session — cross-restart at-most-once needs an idempotent side effect or a persisted dedup
54
+ // set (dp-007), the runtime's concern, not this loop's. A long-lived watch grows `seen` with
55
+ // directive volume; acceptable for v1.
56
+ const seen = new Set();
57
+ for (;;) {
58
+ const { directives } = await drainInbox(ctx, token, max);
59
+ // The unique ids in THIS drain — both the ack list and the within-batch emit guard (a Hub could
60
+ // repeat an id inside one batch; emit it once and ack it once, not twice).
61
+ const batchIds = new Set();
62
+ for (const delivery of directives) {
63
+ const id = delivery.directive.id;
64
+ if (!seen.has(id) && !batchIds.has(id))
65
+ emitDirective(ctx, delivery, token);
66
+ batchIds.add(id);
67
+ }
68
+ if (batchIds.size > 0) {
69
+ // Flush stdout BEFORE acking so the emit-then-ack handoff is genuinely at-least-once: `io.log`
70
+ // only queues to stdout, so when `watch` is piped to a runtime under backpressure the bytes may
71
+ // still be buffered in this process. Acking first would let the Hub consume a directive that a
72
+ // crash/EPIPE could still lose before it reaches the consumer. `flushOutput` waits for the
73
+ // queued writes to complete (a write-callback barrier, not just the drain flag); THEN ack.
74
+ await ctx.runtime.flushOutput?.();
75
+ // The single batched consume-ack (§14) — one call per drained batch, deduped ids.
76
+ await ackInbox(ctx, token, [...batchIds]);
77
+ // Commit to the cross-poll dedup set only now that the batch is durably consumed.
78
+ for (const id of batchIds)
79
+ seen.add(id);
80
+ }
81
+ // --once: a single drain→emit→ack pass (cron / scripting / deterministic tests). Otherwise loop;
82
+ // the poll itself IS the presence heartbeat, so keep polling on the interval even on an empty drain.
83
+ if (once)
84
+ return;
85
+ await ctx.runtime.sleep(intervalMs);
86
+ }
87
+ }
88
+ /** Hand one directive to the agent runtime. stdout is the directive STREAM.
89
+ *
90
+ * Under `--json` the delivery is emitted BYTE-VERBATIM — one NDJSON `inbox.watch` envelope carrying
91
+ * the exact `{ directive, signature }`. This is deliberate and must not be redacted: the detached
92
+ * `signature` is computed over the `directive` bytes, so mutating the directive (even to strip an
93
+ * echoed token) would break the signature a defense-in-depth runtime verifies — and we'd then ack a
94
+ * directive the runtime couldn't authenticate. A conformant Hub never puts the bearer in directive
95
+ * content, and the machine stream feeds the agent's OWN runtime (which already holds the token), so
96
+ * verbatim is the right posture; JSON.stringify escapes control chars.
97
+ *
98
+ * The HUMAN summary carries no signature contract, so it stays hardened (§5.4): fields are
99
+ * terminal-sanitized AND the bearer is redacted — a compromised/buggy Hub can't inject ANSI or echo
100
+ * the token into a line a person might read or log. */
101
+ function emitDirective(ctx, delivery, token) {
102
+ if (ctx.json) {
103
+ ctx.io.log(serializeEnvelope(buildOk("inbox.watch", { directive: delivery.directive, signature: delivery.signature })));
104
+ return;
105
+ }
106
+ const d = delivery.directive;
107
+ const priority = d.priority !== undefined ? ` (${sanitizeForTerminal(d.priority)})` : "";
108
+ const summary = `📨 ${sanitizeForTerminal(d.id)} · ${sanitizeForTerminal(d.from)} · ${sanitizeForTerminal(d.title)}${priority}`;
109
+ ctx.io.log(redactToken(summary, token) ?? summary);
110
+ }
111
+ //# sourceMappingURL=inbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.js","sourceRoot":"","sources":["../../src/commands/inbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { CommandContext } from "./context.js";
2
+ export declare function loginCommand(ctx: CommandContext): Promise<void>;
@@ -0,0 +1,272 @@
1
+ // `oh-hai login` (docs/specs/cli.md §4.3). Two paths:
2
+ // - `--token-stdin`: read a token from stdin and store it in the OS keychain (or the 0600
3
+ // file fallback). The token is NEVER printed and NEVER accepted as a flag value (§5.4).
4
+ // - bare `login` (device-code): the PRIMARY flow — request a device code, print a short
5
+ // `user_code` + verification URL to STDERR, and poll the Hub's device-authorization grant
6
+ // (RFC 8628; server routes from #193) until the human approves in a browser. On success the
7
+ // Hub returns the agent bearer, stored under the Hub-scoped key `<origin>|<agent id>` (§5.1).
8
+ // The token is never shown, never typed, never through the LLM (§1/§5.4).
9
+ import { CliError, buildOk, serializeEnvelope } from "../envelope.js";
10
+ import { originOf } from "../auth/resolve-token.js";
11
+ import { sanitizeForTerminal } from "../terminal.js";
12
+ import { flagOn, parseCommandArgs } from "./flags.js";
13
+ import { throwTransportError } from "./http.js";
14
+ /** Per-request bound for each device-grant POST when no `--timeout`/`MA2H_TIMEOUT_MS` is set. It
15
+ * bounds a SINGLE request, never the whole flow — the human's approval window (minutes) is bounded
16
+ * separately by the poll budget below, so a snappy per-request timeout can't abort a slow human. */
17
+ const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
18
+ /** RFC 8628 §3.5: on `slow_down` (and the Hub's HTTP 429 rate-limit), the client backs off +5s. */
19
+ const SLOW_DOWN_INCREMENT_MS = 5_000;
20
+ /** Cap on an honored `Retry-After` (seconds) so a hostile Hub can't stall the poll indefinitely. */
21
+ const MAX_RETRY_AFTER_SECONDS = 300;
22
+ // Poll interval / code-expiry bounds. The server advertises `interval` (5s) and `expires_in` (900s),
23
+ // but they arrive over the wire from a not-yet-authenticated endpoint, so they're clamped into sane
24
+ // ranges: a missing / zero / fractional-floors-to-zero / negative / absurd value must never yield a
25
+ // zero or effectively unbounded poll budget (a `0` interval alone divides `maxPolls` to Infinity and
26
+ // spins the loop with a no-op sleep). Maxima are generous so a legit human window is never cut short.
27
+ const DEFAULT_INTERVAL_SECONDS = 5;
28
+ const MIN_INTERVAL_SECONDS = 1;
29
+ const MAX_INTERVAL_SECONDS = 60;
30
+ const DEFAULT_EXPIRES_IN_SECONDS = 900;
31
+ const MIN_EXPIRES_IN_SECONDS = 1;
32
+ const MAX_EXPIRES_IN_SECONDS = 3600;
33
+ export async function loginCommand(ctx) {
34
+ const { values } = parseCommandArgs(ctx.argv, { "token-stdin": { type: "boolean" } });
35
+ if (flagOn(values["token-stdin"])) {
36
+ await tokenStdinLogin(ctx);
37
+ }
38
+ else {
39
+ await deviceCodeLogin(ctx);
40
+ }
41
+ }
42
+ /** `--token-stdin` (§4.3): read a bearer from stdin (local bootstrap / paste) and store it the same
43
+ * way the device-code path does. Behavior is frozen — #195 only adds the device-code flow. */
44
+ async function tokenStdinLogin(ctx) {
45
+ const account = ctx.config.account;
46
+ if (account === undefined) {
47
+ throw new CliError("usage", "an agent id is required — pass --account/--agent or set MA2H_AGENT_ID.");
48
+ }
49
+ const raw = await ctx.runtime.readStdin();
50
+ const token = raw.trim();
51
+ if (token === "") {
52
+ throw new CliError("usage", "no token on stdin — pipe the token, e.g. `printf %s \"$TOKEN\" | oh-hai login --token-stdin`.");
53
+ }
54
+ await storeAndReport(ctx, account, token);
55
+ }
56
+ /**
57
+ * The bare `oh-hai login` device-code acquisition (§4.3/§1, RFC 8628). Requests a device code,
58
+ * prompts on STDERR (stdout stays reserved for the single `--json` envelope, §8), then polls until
59
+ * the human approves — storing the returned bearer under `<origin>|<agent id>`. The agent id comes
60
+ * FROM the token response (the human chose which agent to bind at approval time), so this path
61
+ * needs no `--account`, unlike `--token-stdin`.
62
+ */
63
+ async function deviceCodeLogin(ctx) {
64
+ const code = await requestDeviceCode(ctx);
65
+ // Prompt on stderr. `openUrl` prefers the one-click `verification_uri_complete` (embeds the code);
66
+ // the code is shown separately so a human copying the plain URL still knows what to enter. Both are
67
+ // sanitized of control characters first — they're server-supplied and echoed straight to the terminal.
68
+ ctx.io.err(`oh-hai: to authorize this device, open ${sanitizeForTerminal(code.openUrl)}`);
69
+ ctx.io.err(`oh-hai: and enter the code: ${sanitizeForTerminal(code.userCode)}`);
70
+ ctx.io.err("oh-hai: waiting for approval…");
71
+ // Poll budget: the server expires the code and returns `expired_token` at the real deadline; this
72
+ // cap is a pure anti-infinite-loop guard against a server that never returns a terminal state.
73
+ const maxPolls = Math.ceil(code.expiresInSeconds / code.intervalSeconds) + 2;
74
+ // `intervalMs` is the advertised base interval; only `slow_down` raises it permanently (RFC 8628
75
+ // §3.5). `nextDelayMs` is the wait before the NEXT poll — it resets to the base each round, so a
76
+ // one-shot 429 back-off (below) never stretches every later poll and risks missing an approval
77
+ // before the code expires.
78
+ let intervalMs = code.intervalSeconds * 1000;
79
+ let nextDelayMs = intervalMs;
80
+ for (let poll = 0; poll < maxPolls; poll++) {
81
+ await ctx.runtime.sleep(nextDelayMs);
82
+ nextDelayMs = intervalMs; // resume the advertised interval unless this round overrides it below
83
+ const res = await hubPost(ctx, "/auth/device/token", { device_code: code.deviceCode });
84
+ if (res.status === 200) {
85
+ const token = await readAccessToken(res);
86
+ await storeAndReport(ctx, token.agentId, token.accessToken);
87
+ return;
88
+ }
89
+ // The Hub's per-IP limiter throttles the shared device surface with HTTP 429 `rate_limited`
90
+ // (+ Retry-After); a legit poller (e.g. behind a corporate NAT) can trip it mid-approval, so
91
+ // back off and keep polling rather than aborting. Honor Retry-After (the remaining window) as a
92
+ // ONE-SHOT delay so we don't hammer inside the window, then resume the advertised interval.
93
+ if (res.status === 429) {
94
+ const retryAfterMs = retryAfterMillis(res.headers?.get("retry-after"));
95
+ nextDelayMs = Math.max(intervalMs + SLOW_DOWN_INCREMENT_MS, retryAfterMs);
96
+ continue;
97
+ }
98
+ // Every other non-200 poll response is the grant's HTTP 400 carrying an RFC 8628 `error.code`.
99
+ // A non-400 here is an unexpected transport/server error (5xx, a proxy) — map it and stop, so a
100
+ // stray RFC-looking body on a 5xx isn't mistaken for a live grant state and silently retried.
101
+ const { code: errCode, message } = await readErrorEnvelope(res);
102
+ if (res.status !== 400) {
103
+ throw mapHubError(res.status, message);
104
+ }
105
+ switch (errCode) {
106
+ case "authorization_pending":
107
+ continue; // human hasn't approved yet — keep polling at the current interval
108
+ case "slow_down":
109
+ intervalMs += SLOW_DOWN_INCREMENT_MS; // RFC 8628 §3.5 — permanent interval increase
110
+ nextDelayMs = intervalMs;
111
+ continue;
112
+ case "access_denied":
113
+ throw new CliError("auth", "device authorization was denied.");
114
+ case "expired_token":
115
+ throw new CliError("timeout", "the device code expired before it was approved; run `oh-hai login` again.");
116
+ default:
117
+ // A 400 with an unexpected/absent error code — not one of the four RFC states.
118
+ throw mapHubError(res.status, message);
119
+ }
120
+ }
121
+ // The loop exhausted its budget without a terminal response — treat as a timeout (§7).
122
+ throw new CliError("timeout", "device authorization did not complete before the code expired; run `oh-hai login` again.");
123
+ }
124
+ /** Store the bearer Hub-scoped (§5.1) and report success. The token is NEVER in the output (§5.4);
125
+ * an existing token for this identity is overwritten silently (§4.3). Shared by both login paths. */
126
+ async function storeAndReport(ctx, agentId, token) {
127
+ const store = await ctx.runtime.openStore();
128
+ await store.set(originOf(ctx.config.baseUrl), agentId, token);
129
+ if (ctx.json) {
130
+ // No token field, ever (§5.4). `storage` reports which backend actually holds it. The agent id
131
+ // is emitted raw — JSON.stringify escapes any control character, so it can't reach the terminal.
132
+ ctx.io.log(serializeEnvelope(buildOk("login", { agent_id: agentId, base_url: ctx.config.baseUrl, storage: store.backendName })));
133
+ }
134
+ else {
135
+ // Sanitize the DISPLAYED agent id — on the device-code path it's server-supplied, so a hostile
136
+ // Hub could return control characters. The stored credential key above uses the RAW id so it
137
+ // still matches the Hub's identity for later resolution.
138
+ ctx.io.log(`Logged in as ${sanitizeForTerminal(agentId)} @ ${ctx.config.baseUrl}`);
139
+ }
140
+ }
141
+ /** POST /auth/device/code, validate the fields the prompt + poll need, and normalize the poll
142
+ * interval / expiry to sane bounded values. */
143
+ async function requestDeviceCode(ctx) {
144
+ const res = await hubPost(ctx, "/auth/device/code", {});
145
+ if (res.status !== 200) {
146
+ const { message } = await readErrorEnvelope(res);
147
+ throw mapHubError(res.status, message);
148
+ }
149
+ const body = await readJson(res);
150
+ const deviceCode = str(body.device_code);
151
+ const userCode = str(body.user_code);
152
+ const verificationUri = str(body.verification_uri);
153
+ if (deviceCode === undefined || userCode === undefined || verificationUri === undefined) {
154
+ throw new CliError("server", "the Hub returned a malformed device-code response.");
155
+ }
156
+ // Only ever invite the user to open an http(s) URL — a hostile Hub must not get a `file:` /
157
+ // `javascript:` verification URL echoed as "open this". Prefer the complete form when it's also http(s).
158
+ if (!isHttpUrl(verificationUri)) {
159
+ throw new CliError("server", "the Hub returned a non-http verification URL.");
160
+ }
161
+ const complete = str(body.verification_uri_complete);
162
+ return {
163
+ deviceCode,
164
+ userCode,
165
+ openUrl: complete !== undefined && isHttpUrl(complete) ? complete : verificationUri,
166
+ intervalSeconds: boundedInt(body.interval, DEFAULT_INTERVAL_SECONDS, MIN_INTERVAL_SECONDS, MAX_INTERVAL_SECONDS),
167
+ expiresInSeconds: boundedInt(body.expires_in, DEFAULT_EXPIRES_IN_SECONDS, MIN_EXPIRES_IN_SECONDS, MAX_EXPIRES_IN_SECONDS),
168
+ };
169
+ }
170
+ /** Read + validate the 200 token response (`{ access_token, agent_id }`). */
171
+ async function readAccessToken(res) {
172
+ const body = await readJson(res);
173
+ const accessToken = str(body.access_token);
174
+ const agentId = str(body.agent_id);
175
+ if (accessToken === undefined || agentId === undefined) {
176
+ throw new CliError("server", "the Hub returned a malformed token response.");
177
+ }
178
+ return { accessToken, agentId };
179
+ }
180
+ /** POST a JSON body to `path` on the Hub, mapping transport failures to §7 exit codes. The
181
+ * per-request timeout bounds THIS request only; the poll budget bounds the whole flow. */
182
+ async function hubPost(ctx, path, body) {
183
+ const url = `${ctx.config.baseUrl}${path}`;
184
+ const init = {
185
+ method: "POST",
186
+ headers: { "Content-Type": "application/json" },
187
+ body: JSON.stringify(body),
188
+ signal: AbortSignal.timeout(ctx.config.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS),
189
+ };
190
+ try {
191
+ return await ctx.runtime.fetchImpl(url, init);
192
+ }
193
+ catch (error) {
194
+ throwTransportError(error, ctx.config.baseUrl);
195
+ }
196
+ }
197
+ /** Parse a response body as a JSON object, defensively — a fake without `json()`, a non-JSON body,
198
+ * or a non-object (array / string / null) all collapse to `{}`, so callers read fields off a plain
199
+ * record without unchecked casts. */
200
+ async function readJson(res) {
201
+ if (typeof res.json !== "function")
202
+ return {};
203
+ try {
204
+ const parsed = await res.json();
205
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)
206
+ ? parsed
207
+ : {};
208
+ }
209
+ catch {
210
+ return {};
211
+ }
212
+ }
213
+ /** Read the A2H error envelope `{ error: { code, message } }` from a non-200 response. */
214
+ async function readErrorEnvelope(res) {
215
+ const body = await readJson(res);
216
+ const error = body.error;
217
+ const record = error !== null && typeof error === "object" ? error : {};
218
+ return { code: str(record.code), message: str(record.message) };
219
+ }
220
+ /** Map an unexpected Hub error status to the right §7 exit code, preferring the server's message.
221
+ * The server-supplied `message` is stripped of terminal control characters — the top-level catch
222
+ * prints a CliError's message to the terminal, so a hostile Hub could otherwise inject escapes. */
223
+ function mapHubError(status, message) {
224
+ const detail = message !== undefined ? sanitizeForTerminal(message) : `Hub returned ${status}.`;
225
+ if (status === 401 || status === 403)
226
+ return new CliError("auth", detail);
227
+ if (status === 404)
228
+ return new CliError("not_found", detail);
229
+ if (status === 413)
230
+ return new CliError("payload_too_large", detail);
231
+ if (status === 400 || status === 422)
232
+ return new CliError("bad_request", detail);
233
+ if (status >= 500)
234
+ return new CliError("server", detail);
235
+ return new CliError("error", detail);
236
+ }
237
+ /** Parse a `Retry-After` header of the numeric-seconds form the Hub sends into milliseconds, capped
238
+ * so a hostile value can't stall the poll; 0 when the header is absent or unparseable. */
239
+ function retryAfterMillis(headerValue) {
240
+ if (headerValue === null || headerValue === undefined)
241
+ return 0;
242
+ const seconds = Number.parseInt(headerValue.trim(), 10);
243
+ if (!Number.isFinite(seconds) || seconds <= 0)
244
+ return 0;
245
+ return Math.min(seconds, MAX_RETRY_AFTER_SECONDS) * 1000;
246
+ }
247
+ /** A trimmed non-empty string from an unknown field, else undefined (validates response fields). */
248
+ function str(value) {
249
+ if (typeof value !== "string")
250
+ return undefined;
251
+ const trimmed = value.trim();
252
+ return trimmed === "" ? undefined : trimmed;
253
+ }
254
+ /** True when `value` parses as an http(s) URL — the only schemes we invite the user to open (§4.3). */
255
+ function isHttpUrl(value) {
256
+ try {
257
+ const { protocol } = new URL(value);
258
+ return protocol === "http:" || protocol === "https:";
259
+ }
260
+ catch {
261
+ return false;
262
+ }
263
+ }
264
+ /** An integer from an unknown response field, clamped to `[min, max]`; `fallback` (already in range)
265
+ * is used when the value is missing or non-finite. Clamping means a missing, zero, fractional
266
+ * (floors to 0), negative, or absurdly large server-supplied `interval` / `expires_in` can never
267
+ * produce a zero/negative or effectively unbounded poll budget — which would defeat the poll cap. */
268
+ function boundedInt(value, fallback, min, max) {
269
+ const n = typeof value === "number" && Number.isFinite(value) ? Math.floor(value) : fallback;
270
+ return Math.min(Math.max(n, min), max);
271
+ }
272
+ //# sourceMappingURL=login.js.map