@seanpropapp/cli 0.1.0-beta.1

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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/commands/autostart.d.ts +42 -0
  4. package/dist/commands/autostart.js +195 -0
  5. package/dist/commands/autostart.js.map +1 -0
  6. package/dist/commands/bridge.d.ts +54 -0
  7. package/dist/commands/bridge.js +145 -0
  8. package/dist/commands/bridge.js.map +1 -0
  9. package/dist/commands/connect.d.ts +56 -0
  10. package/dist/commands/connect.js +213 -0
  11. package/dist/commands/connect.js.map +1 -0
  12. package/dist/commands/doctor.d.ts +24 -0
  13. package/dist/commands/doctor.js +200 -0
  14. package/dist/commands/doctor.js.map +1 -0
  15. package/dist/commands/install-claude.d.ts +22 -0
  16. package/dist/commands/install-claude.js +56 -0
  17. package/dist/commands/install-claude.js.map +1 -0
  18. package/dist/commands/mcp.d.ts +5 -0
  19. package/dist/commands/mcp.js +23 -0
  20. package/dist/commands/mcp.js.map +1 -0
  21. package/dist/commands/pair-url.d.ts +12 -0
  22. package/dist/commands/pair-url.js +23 -0
  23. package/dist/commands/pair-url.js.map +1 -0
  24. package/dist/commands/pair.d.ts +12 -0
  25. package/dist/commands/pair.js +24 -0
  26. package/dist/commands/pair.js.map +1 -0
  27. package/dist/commands/prompt.d.ts +5 -0
  28. package/dist/commands/prompt.js +24 -0
  29. package/dist/commands/prompt.js.map +1 -0
  30. package/dist/commands/telemetry-cmd.d.ts +8 -0
  31. package/dist/commands/telemetry-cmd.js +55 -0
  32. package/dist/commands/telemetry-cmd.js.map +1 -0
  33. package/dist/config.d.ts +63 -0
  34. package/dist/config.js +77 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/http/auth-middleware.d.ts +9 -0
  37. package/dist/http/auth-middleware.js +29 -0
  38. package/dist/http/auth-middleware.js.map +1 -0
  39. package/dist/http/chat-completions.d.ts +48 -0
  40. package/dist/http/chat-completions.js +117 -0
  41. package/dist/http/chat-completions.js.map +1 -0
  42. package/dist/http/cors.d.ts +9 -0
  43. package/dist/http/cors.js +35 -0
  44. package/dist/http/cors.js.map +1 -0
  45. package/dist/http/handshake.d.ts +43 -0
  46. package/dist/http/handshake.js +28 -0
  47. package/dist/http/handshake.js.map +1 -0
  48. package/dist/http/messages-endpoint.d.ts +67 -0
  49. package/dist/http/messages-endpoint.js +95 -0
  50. package/dist/http/messages-endpoint.js.map +1 -0
  51. package/dist/http/server.d.ts +37 -0
  52. package/dist/http/server.js +83 -0
  53. package/dist/http/server.js.map +1 -0
  54. package/dist/http/sse.d.ts +35 -0
  55. package/dist/http/sse.js +72 -0
  56. package/dist/http/sse.js.map +1 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +219 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/mcp/manifest.d.ts +16 -0
  61. package/dist/mcp/manifest.js +88 -0
  62. package/dist/mcp/manifest.js.map +1 -0
  63. package/dist/mcp/server.d.ts +25 -0
  64. package/dist/mcp/server.js +166 -0
  65. package/dist/mcp/server.js.map +1 -0
  66. package/dist/providers/base.d.ts +91 -0
  67. package/dist/providers/base.js +27 -0
  68. package/dist/providers/base.js.map +1 -0
  69. package/dist/providers/claude.d.ts +22 -0
  70. package/dist/providers/claude.js +157 -0
  71. package/dist/providers/claude.js.map +1 -0
  72. package/dist/providers/codex.d.ts +38 -0
  73. package/dist/providers/codex.js +179 -0
  74. package/dist/providers/codex.js.map +1 -0
  75. package/dist/providers/detect-util.d.ts +17 -0
  76. package/dist/providers/detect-util.js +68 -0
  77. package/dist/providers/detect-util.js.map +1 -0
  78. package/dist/providers/index.d.ts +14 -0
  79. package/dist/providers/index.js +21 -0
  80. package/dist/providers/index.js.map +1 -0
  81. package/dist/telemetry.d.ts +68 -0
  82. package/dist/telemetry.js +118 -0
  83. package/dist/telemetry.js.map +1 -0
  84. package/dist/version.d.ts +9 -0
  85. package/dist/version.js +10 -0
  86. package/dist/version.js.map +1 -0
  87. package/package.json +62 -0
@@ -0,0 +1,24 @@
1
+ import { loadConfig, updateConfig } from "../config.js";
2
+ import { generatePairToken, osc8Link, pairUrl, } from "./pair-url.js";
3
+ /**
4
+ * Escape-hatch: generate a fresh pair URL without starting a bridge.
5
+ * Assumes the bridge is already running and will pick up the new token
6
+ * from the shared config on its next handshake.
7
+ *
8
+ * NOTE: in v1.4.0 the bridge holds tokens in-memory at start; a fully live
9
+ * rotation flow is a v1.4.x follow-up (see Lane C-Polish doctor).
10
+ */
11
+ export async function runPair(opts = {}) {
12
+ const cfg = await loadConfig(opts.configDir);
13
+ if (!cfg.bridge_port) {
14
+ process.stderr.write("Bridge not running. Start it first with: seanpropapp connect (or `seanpropapp bridge`).\n");
15
+ process.exitCode = 1;
16
+ return;
17
+ }
18
+ const token = generatePairToken();
19
+ await updateConfig({ pair_token: token }, opts.configDir);
20
+ const url = pairUrl(token);
21
+ process.stdout.write(`Pair URL: ${osc8Link(url, url)}\n`);
22
+ process.stdout.write(`Or paste in browser: ${url}\n`);
23
+ }
24
+ //# sourceMappingURL=pair.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair.js","sourceRoot":"","sources":["../../src/commands/pair.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EACL,iBAAiB,EACjB,QAAQ,EACR,OAAO,GACR,MAAM,eAAe,CAAC;AAMvB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,OAAoB,EAAE;IAClD,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;QACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2FAA2F,CAC5F,CAAC;QACF,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;QACrB,OAAO;IACT,CAAC;IAED,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC;IAClC,MAAM,YAAY,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAE1D,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAAC;AACxD,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tiny prompt helper. Returns true for empty / "y" / "yes" (case-insensitive)
3
+ * to match the [Y/n] convention used by `connect`'s inline install prompt.
4
+ */
5
+ export declare function confirm(question: string): Promise<boolean>;
@@ -0,0 +1,24 @@
1
+ import * as readline from "node:readline";
2
+ /**
3
+ * Tiny prompt helper. Returns true for empty / "y" / "yes" (case-insensitive)
4
+ * to match the [Y/n] convention used by `connect`'s inline install prompt.
5
+ */
6
+ export function confirm(question) {
7
+ return new Promise((resolve) => {
8
+ const rl = readline.createInterface({
9
+ input: process.stdin,
10
+ output: process.stdout,
11
+ });
12
+ rl.question(question, (answer) => {
13
+ rl.close();
14
+ const trimmed = answer.trim().toLowerCase();
15
+ if (trimmed === "" || trimmed === "y" || trimmed === "yes") {
16
+ resolve(true);
17
+ }
18
+ else {
19
+ resolve(false);
20
+ }
21
+ });
22
+ });
23
+ }
24
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/commands/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,QAAgB;IACtC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,CAAC,MAAM,EAAE,EAAE;YAC/B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;YAC5C,IAAI,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;gBAC3D,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,8 @@
1
+ export interface TelemetryCmdOptions {
2
+ configDir?: string;
3
+ stdout?: (line: string) => void;
4
+ json?: boolean;
5
+ }
6
+ export declare function runTelemetryEnable(opts?: TelemetryCmdOptions): Promise<void>;
7
+ export declare function runTelemetryDisable(opts?: TelemetryCmdOptions): Promise<void>;
8
+ export declare function runTelemetryStatus(opts?: TelemetryCmdOptions): Promise<void>;
@@ -0,0 +1,55 @@
1
+ /**
2
+ * `seanpropapp telemetry enable|disable|status`.
3
+ *
4
+ * Persists the opt-in flag in `~/.seanpropapp/config.json`. The first time the
5
+ * user enables, generates a correlation_id so subsequent emits have stable
6
+ * identity.
7
+ */
8
+ import { updateConfig } from "../config.js";
9
+ import { ensureCorrelationId, telemetryStatus, } from "../telemetry.js";
10
+ function emit(out, json, human, obj) {
11
+ if (json) {
12
+ out(`${JSON.stringify(obj)}\n`);
13
+ }
14
+ else {
15
+ out(`${human}\n`);
16
+ }
17
+ }
18
+ export async function runTelemetryEnable(opts = {}) {
19
+ const out = opts.stdout ?? ((s) => process.stdout.write(s));
20
+ const tcfg = {};
21
+ if (opts.configDir !== undefined)
22
+ tcfg.configDir = opts.configDir;
23
+ const correlationId = await ensureCorrelationId(tcfg);
24
+ await updateConfig({ telemetry_enabled: true }, opts.configDir);
25
+ emit(out, opts.json ?? false, `Telemetry enabled. Events will POST to https://prop.seanoneill.com/api/telemetry. See TELEMETRY.md for details.\nCorrelation id: ${correlationId}`, {
26
+ ok: true,
27
+ enabled: true,
28
+ correlation_id: correlationId,
29
+ });
30
+ }
31
+ export async function runTelemetryDisable(opts = {}) {
32
+ const out = opts.stdout ?? ((s) => process.stdout.write(s));
33
+ await updateConfig({ telemetry_enabled: false }, opts.configDir);
34
+ emit(out, opts.json ?? false, "Telemetry disabled. No events will be sent. Your correlation id is kept so re-enabling later preserves history.", { ok: true, enabled: false });
35
+ }
36
+ export async function runTelemetryStatus(opts = {}) {
37
+ const out = opts.stdout ?? ((s) => process.stdout.write(s));
38
+ const tcfg = {};
39
+ if (opts.configDir !== undefined)
40
+ tcfg.configDir = opts.configDir;
41
+ const s = await telemetryStatus(tcfg);
42
+ const human = [
43
+ `Telemetry: ${s.enabled ? "on" : "off"}`,
44
+ `URL: ${s.url}`,
45
+ `OS: ${s.os}`,
46
+ `Correlation id: ${s.correlation_id ?? "(not yet generated)"}`,
47
+ ].join("\n");
48
+ emit(out, opts.json ?? false, human, {
49
+ enabled: s.enabled,
50
+ url: s.url,
51
+ os: s.os,
52
+ correlation_id: s.correlation_id ?? null,
53
+ });
54
+ }
55
+ //# sourceMappingURL=telemetry-cmd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry-cmd.js","sourceRoot":"","sources":["../../src/commands/telemetry-cmd.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { z } from "zod";
2
+ declare const ConfigSchema: z.ZodObject<{
3
+ pair_token: z.ZodOptional<z.ZodString>;
4
+ bridge_url: z.ZodOptional<z.ZodString>;
5
+ bridge_port: z.ZodOptional<z.ZodNumber>;
6
+ paired_at: z.ZodOptional<z.ZodString>;
7
+ device_name: z.ZodOptional<z.ZodString>;
8
+ providers_detected: z.ZodOptional<z.ZodObject<{
9
+ claude: z.ZodOptional<z.ZodBoolean>;
10
+ codex: z.ZodOptional<z.ZodBoolean>;
11
+ gemini: z.ZodOptional<z.ZodBoolean>;
12
+ }, "strip", z.ZodTypeAny, {
13
+ claude?: boolean | undefined;
14
+ codex?: boolean | undefined;
15
+ gemini?: boolean | undefined;
16
+ }, {
17
+ claude?: boolean | undefined;
18
+ codex?: boolean | undefined;
19
+ gemini?: boolean | undefined;
20
+ }>>;
21
+ /** MCP bearer token for stdio MCP server (issued at /mcp-setup). */
22
+ mcp_token: z.ZodOptional<z.ZodString>;
23
+ /** Stable UUID used to correlate cli_* telemetry events. */
24
+ correlation_id: z.ZodOptional<z.ZodString>;
25
+ /** Opt-in telemetry switch. Defaults to OFF when missing. */
26
+ telemetry_enabled: z.ZodOptional<z.ZodBoolean>;
27
+ }, "strip", z.ZodTypeAny, {
28
+ pair_token?: string | undefined;
29
+ bridge_url?: string | undefined;
30
+ bridge_port?: number | undefined;
31
+ paired_at?: string | undefined;
32
+ device_name?: string | undefined;
33
+ providers_detected?: {
34
+ claude?: boolean | undefined;
35
+ codex?: boolean | undefined;
36
+ gemini?: boolean | undefined;
37
+ } | undefined;
38
+ mcp_token?: string | undefined;
39
+ correlation_id?: string | undefined;
40
+ telemetry_enabled?: boolean | undefined;
41
+ }, {
42
+ pair_token?: string | undefined;
43
+ bridge_url?: string | undefined;
44
+ bridge_port?: number | undefined;
45
+ paired_at?: string | undefined;
46
+ device_name?: string | undefined;
47
+ providers_detected?: {
48
+ claude?: boolean | undefined;
49
+ codex?: boolean | undefined;
50
+ gemini?: boolean | undefined;
51
+ } | undefined;
52
+ mcp_token?: string | undefined;
53
+ correlation_id?: string | undefined;
54
+ telemetry_enabled?: boolean | undefined;
55
+ }>;
56
+ export type Config = z.infer<typeof ConfigSchema>;
57
+ export declare function getConfigDir(override?: string): string;
58
+ export declare function getConfigPath(override?: string): string;
59
+ export declare function ensureConfigDir(override?: string): Promise<string>;
60
+ export declare function loadConfig(override?: string): Promise<Config>;
61
+ export declare function saveConfig(cfg: Config, override?: string): Promise<void>;
62
+ export declare function updateConfig(patch: Partial<Config>, override?: string): Promise<Config>;
63
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,77 @@
1
+ import { promises as fs } from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { z } from "zod";
5
+ const ProvidersDetectedSchema = z.object({
6
+ claude: z.boolean().optional(),
7
+ codex: z.boolean().optional(),
8
+ gemini: z.boolean().optional(),
9
+ });
10
+ const ConfigSchema = z.object({
11
+ pair_token: z.string().optional(),
12
+ bridge_url: z.string().optional(),
13
+ bridge_port: z.number().int().positive().optional(),
14
+ paired_at: z.string().optional(),
15
+ device_name: z.string().optional(),
16
+ providers_detected: ProvidersDetectedSchema.optional(),
17
+ /** MCP bearer token for stdio MCP server (issued at /mcp-setup). */
18
+ mcp_token: z.string().optional(),
19
+ /** Stable UUID used to correlate cli_* telemetry events. */
20
+ correlation_id: z.string().optional(),
21
+ /** Opt-in telemetry switch. Defaults to OFF when missing. */
22
+ telemetry_enabled: z.boolean().optional(),
23
+ });
24
+ const DEFAULT_CONFIG_DIR_NAME = ".seanpropapp";
25
+ export function getConfigDir(override) {
26
+ if (override)
27
+ return override;
28
+ return path.join(os.homedir(), DEFAULT_CONFIG_DIR_NAME);
29
+ }
30
+ export function getConfigPath(override) {
31
+ return path.join(getConfigDir(override), "config.json");
32
+ }
33
+ export async function ensureConfigDir(override) {
34
+ const dir = getConfigDir(override);
35
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
36
+ return dir;
37
+ }
38
+ export async function loadConfig(override) {
39
+ const filePath = getConfigPath(override);
40
+ let raw;
41
+ try {
42
+ raw = await fs.readFile(filePath, "utf8");
43
+ }
44
+ catch (err) {
45
+ if (err.code === "ENOENT")
46
+ return {};
47
+ throw err;
48
+ }
49
+ let parsed;
50
+ try {
51
+ parsed = JSON.parse(raw);
52
+ }
53
+ catch {
54
+ // Corrupt file. Return empty rather than throwing so the CLI can recover.
55
+ return {};
56
+ }
57
+ const result = ConfigSchema.safeParse(parsed);
58
+ if (!result.success)
59
+ return {};
60
+ return result.data;
61
+ }
62
+ export async function saveConfig(cfg, override) {
63
+ await ensureConfigDir(override);
64
+ const filePath = getConfigPath(override);
65
+ const validated = ConfigSchema.parse(cfg);
66
+ // Write file with 0600 perms so the pair token is not world-readable.
67
+ await fs.writeFile(filePath, JSON.stringify(validated, null, 2), {
68
+ mode: 0o600,
69
+ });
70
+ }
71
+ export async function updateConfig(patch, override) {
72
+ const existing = await loadConfig(override);
73
+ const next = { ...existing, ...patch };
74
+ await saveConfig(next, override);
75
+ return next;
76
+ }
77
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ /**
3
+ * Constant-time Bearer-token check. Token is held in-memory only; never logged.
4
+ *
5
+ * `expectedToken` accepts either a literal string (the v0.1.0-alpha behavior)
6
+ * or a getter function. The getter form is used by the bridge process so it
7
+ * can pick up a rotated token on SIGHUP without restarting the HTTP server.
8
+ */
9
+ export declare function makeAuthMiddleware(expectedToken: string | (() => string)): MiddlewareHandler;
@@ -0,0 +1,29 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ /**
3
+ * Constant-time Bearer-token check. Token is held in-memory only; never logged.
4
+ *
5
+ * `expectedToken` accepts either a literal string (the v0.1.0-alpha behavior)
6
+ * or a getter function. The getter form is used by the bridge process so it
7
+ * can pick up a rotated token on SIGHUP without restarting the HTTP server.
8
+ */
9
+ export function makeAuthMiddleware(expectedToken) {
10
+ const getToken = typeof expectedToken === "function" ? expectedToken : () => expectedToken;
11
+ return async (c, next) => {
12
+ const header = c.req.header("Authorization");
13
+ if (!header || !header.startsWith("Bearer ")) {
14
+ return c.json({ error: { type: "unauthorized", message: "Missing Bearer token" } }, 401);
15
+ }
16
+ const provided = header.slice("Bearer ".length).trim();
17
+ const providedBuf = Buffer.from(provided, "utf8");
18
+ const expectedBuf = Buffer.from(getToken(), "utf8");
19
+ let ok = false;
20
+ if (providedBuf.length === expectedBuf.length) {
21
+ ok = timingSafeEqual(providedBuf, expectedBuf);
22
+ }
23
+ if (!ok) {
24
+ return c.json({ error: { type: "unauthorized", message: "Invalid Bearer token" } }, 401);
25
+ }
26
+ await next();
27
+ };
28
+ }
29
+ //# sourceMappingURL=auth-middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-middleware.js","sourceRoot":"","sources":["../../src/http/auth-middleware.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAChC,aAAsC;IAEtC,MAAM,QAAQ,GACZ,OAAO,aAAa,KAAK,UAAU,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,aAAa,CAAC;IAE5E,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YAC7C,OAAO,CAAC,CAAC,IAAI,CACX,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,EACpE,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,MAAM,CAAC,CAAC;QAEpD,IAAI,EAAE,GAAG,KAAK,CAAC;QACf,IAAI,WAAW,CAAC,MAAM,KAAK,WAAW,CAAC,MAAM,EAAE,CAAC;YAC9C,EAAE,GAAG,eAAe,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QACjD,CAAC;QACD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,OAAO,CAAC,CAAC,IAAI,CACX,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,EACpE,GAAG,CACJ,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,EAAE,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { Context } from "hono";
2
+ import { z } from "zod";
3
+ import type { AnthropicLikeRequest, Provider } from "../providers/base.js";
4
+ declare const ChatCompletionsRequestSchema: z.ZodObject<{
5
+ model: z.ZodString;
6
+ messages: z.ZodArray<z.ZodObject<{
7
+ role: z.ZodEnum<["system", "user", "assistant"]>;
8
+ content: z.ZodString;
9
+ }, "strip", z.ZodTypeAny, {
10
+ role: "user" | "assistant" | "system";
11
+ content: string;
12
+ }, {
13
+ role: "user" | "assistant" | "system";
14
+ content: string;
15
+ }>, "many">;
16
+ max_tokens: z.ZodOptional<z.ZodNumber>;
17
+ temperature: z.ZodOptional<z.ZodNumber>;
18
+ stream: z.ZodOptional<z.ZodBoolean>;
19
+ }, "strip", z.ZodTypeAny, {
20
+ model: string;
21
+ messages: {
22
+ role: "user" | "assistant" | "system";
23
+ content: string;
24
+ }[];
25
+ max_tokens?: number | undefined;
26
+ stream?: boolean | undefined;
27
+ temperature?: number | undefined;
28
+ }, {
29
+ model: string;
30
+ messages: {
31
+ role: "user" | "assistant" | "system";
32
+ content: string;
33
+ }[];
34
+ max_tokens?: number | undefined;
35
+ stream?: boolean | undefined;
36
+ temperature?: number | undefined;
37
+ }>;
38
+ export type ChatCompletionsRequest = z.infer<typeof ChatCompletionsRequestSchema>;
39
+ export interface ChatCompletionsDeps {
40
+ pickProvider: (model: string) => Provider;
41
+ }
42
+ /**
43
+ * Translate inbound OpenAI Chat Completions shape to Anthropic shape so a
44
+ * single provider abstraction can serve it.
45
+ */
46
+ export declare function openaiToAnthropic(req: ChatCompletionsRequest): AnthropicLikeRequest;
47
+ export declare function makeChatCompletionsHandler(deps: ChatCompletionsDeps): (c: Context) => Promise<import("undici-types").Response>;
48
+ export {};
@@ -0,0 +1,117 @@
1
+ import { z } from "zod";
2
+ import { ClassifiedError } from "../providers/base.js";
3
+ import { anthropicEventToOpenAIChunk, encodeOpenAIChunk, } from "./sse.js";
4
+ const ChatCompletionsRequestSchema = z.object({
5
+ model: z.string().min(1),
6
+ messages: z
7
+ .array(z.object({
8
+ role: z.enum(["system", "user", "assistant"]),
9
+ content: z.string(),
10
+ }))
11
+ .min(1),
12
+ max_tokens: z.number().int().positive().optional(),
13
+ temperature: z.number().optional(),
14
+ stream: z.boolean().optional(),
15
+ });
16
+ /**
17
+ * Translate inbound OpenAI Chat Completions shape to Anthropic shape so a
18
+ * single provider abstraction can serve it.
19
+ */
20
+ export function openaiToAnthropic(req) {
21
+ let system;
22
+ const messages = [];
23
+ for (const m of req.messages) {
24
+ if (m.role === "system") {
25
+ system = system ? `${system}\n\n${m.content}` : m.content;
26
+ continue;
27
+ }
28
+ messages.push({
29
+ role: m.role === "assistant" ? "assistant" : "user",
30
+ content: m.content,
31
+ });
32
+ }
33
+ const out = {
34
+ model: req.model,
35
+ messages,
36
+ stream: req.stream,
37
+ };
38
+ if (system)
39
+ out.system = system;
40
+ if (req.max_tokens !== undefined)
41
+ out.max_tokens = req.max_tokens;
42
+ if (req.temperature !== undefined)
43
+ out.temperature = req.temperature;
44
+ return out;
45
+ }
46
+ export function makeChatCompletionsHandler(deps) {
47
+ return async (c) => {
48
+ let body;
49
+ try {
50
+ body = await c.req.json();
51
+ }
52
+ catch {
53
+ return c.json({ error: { type: "invalid_request", message: "Body must be JSON" } }, 400);
54
+ }
55
+ const parsed = ChatCompletionsRequestSchema.safeParse(body);
56
+ if (!parsed.success) {
57
+ return c.json({
58
+ error: {
59
+ type: "invalid_request",
60
+ message: parsed.error.issues
61
+ .map((i) => `${i.path.join(".")}: ${i.message}`)
62
+ .join("; "),
63
+ },
64
+ }, 400);
65
+ }
66
+ const request = parsed.data;
67
+ const anthropicRequest = openaiToAnthropic(request);
68
+ const provider = deps.pickProvider(request.model);
69
+ const ctx = {
70
+ id: `chatcmpl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
71
+ model: request.model,
72
+ created: Math.floor(Date.now() / 1000),
73
+ };
74
+ const { readable, writable } = new TransformStream();
75
+ const writer = writable.getWriter();
76
+ (async () => {
77
+ try {
78
+ for await (const event of provider.stream(anthropicRequest)) {
79
+ const chunk = anthropicEventToOpenAIChunk(event, ctx);
80
+ if (!chunk)
81
+ continue;
82
+ await writer.write(encodeOpenAIChunk(chunk));
83
+ }
84
+ }
85
+ catch (err) {
86
+ const message = err instanceof ClassifiedError
87
+ ? err.message
88
+ : err instanceof Error
89
+ ? err.message
90
+ : String(err);
91
+ const errBody = {
92
+ error: {
93
+ type: err instanceof ClassifiedError &&
94
+ err.category === "subscription_limit"
95
+ ? "rate_limit_exceeded"
96
+ : "internal_error",
97
+ message,
98
+ retry_after_seconds: err instanceof ClassifiedError ? err.retryAfterSeconds : undefined,
99
+ },
100
+ };
101
+ await writer.write(new TextEncoder().encode(`data: ${JSON.stringify(errBody)}\n\n`));
102
+ }
103
+ finally {
104
+ await writer.close().catch(() => undefined);
105
+ }
106
+ })();
107
+ return new Response(readable, {
108
+ status: 200,
109
+ headers: {
110
+ "Content-Type": "text/event-stream",
111
+ "Cache-Control": "no-cache, no-transform",
112
+ Connection: "keep-alive",
113
+ },
114
+ });
115
+ };
116
+ }
117
+ //# sourceMappingURL=chat-completions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-completions.js","sourceRoot":"","sources":["../../src/http/chat-completions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ export declare const ALLOWED_ORIGINS: readonly ["https://prop.seanoneill.com", "http://localhost:3000"];
3
+ export declare const PREFLIGHT_MAX_AGE_SECONDS = 86400;
4
+ export declare function isOriginAllowed(origin: string | null | undefined): boolean;
5
+ /**
6
+ * Strict CORS middleware: only allows the SeanPropApp prod origin + local dev.
7
+ * Rejects all other origins with 403. Preflights cached for 24h.
8
+ */
9
+ export declare const corsMiddleware: MiddlewareHandler;
@@ -0,0 +1,35 @@
1
+ export const ALLOWED_ORIGINS = [
2
+ "https://prop.seanoneill.com",
3
+ "http://localhost:3000",
4
+ ];
5
+ export const PREFLIGHT_MAX_AGE_SECONDS = 86400;
6
+ export function isOriginAllowed(origin) {
7
+ if (!origin)
8
+ return false;
9
+ return ALLOWED_ORIGINS.includes(origin);
10
+ }
11
+ /**
12
+ * Strict CORS middleware: only allows the SeanPropApp prod origin + local dev.
13
+ * Rejects all other origins with 403. Preflights cached for 24h.
14
+ */
15
+ export const corsMiddleware = async (c, next) => {
16
+ const origin = c.req.header("Origin");
17
+ // Same-origin / no-origin requests are allowed (e.g. curl during doctor checks).
18
+ // Only enforce when an Origin header is present.
19
+ if (origin !== undefined) {
20
+ if (!isOriginAllowed(origin)) {
21
+ return c.json({ error: { type: "forbidden_origin", message: "Origin not allowed" } }, 403);
22
+ }
23
+ c.header("Access-Control-Allow-Origin", origin);
24
+ c.header("Vary", "Origin");
25
+ c.header("Access-Control-Allow-Credentials", "false");
26
+ }
27
+ if (c.req.method === "OPTIONS") {
28
+ c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
29
+ c.header("Access-Control-Allow-Headers", "Authorization, Content-Type");
30
+ c.header("Access-Control-Max-Age", String(PREFLIGHT_MAX_AGE_SECONDS));
31
+ return c.body(null, 204);
32
+ }
33
+ await next();
34
+ };
35
+ //# sourceMappingURL=cors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/http/cors.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,6BAA6B;IAC7B,uBAAuB;CACf,CAAC;AAEX,MAAM,CAAC,MAAM,yBAAyB,GAAG,KAAK,CAAC;AAE/C,MAAM,UAAU,eAAe,CAAC,MAAiC;IAC/D,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,OAAQ,eAAqC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AACjE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACjE,MAAM,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEtC,iFAAiF;IACjF,iDAAiD;IACjD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,oBAAoB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QAC7F,CAAC;QACD,CAAC,CAAC,MAAM,CAAC,6BAA6B,EAAE,MAAM,CAAC,CAAC;QAChD,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC3B,CAAC,CAAC,MAAM,CAAC,kCAAkC,EAAE,OAAO,CAAC,CAAC;IACxD,CAAC;IAED,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAC/B,CAAC,CAAC,MAAM,CACN,8BAA8B,EAC9B,oBAAoB,CACrB,CAAC;QACF,CAAC,CAAC,MAAM,CACN,8BAA8B,EAC9B,6BAA6B,CAC9B,CAAC;QACF,CAAC,CAAC,MAAM,CAAC,wBAAwB,EAAE,MAAM,CAAC,yBAAyB,CAAC,CAAC,CAAC;QACtE,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,IAAI,EAAE,CAAC;AACf,CAAC,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { Context } from "hono";
2
+ import type { Provider, ProviderDetectResult } from "../providers/base.js";
3
+ export interface HandshakeResponse {
4
+ version: string;
5
+ providers: {
6
+ claude: ProviderDetectResult;
7
+ codex: ProviderDetectResult;
8
+ gemini: ProviderDetectResult;
9
+ };
10
+ paired_at: string | null;
11
+ device_name: string;
12
+ }
13
+ export declare function deviceName(): string;
14
+ export interface HandshakeDeps {
15
+ pairedAt: () => string | null;
16
+ claude: Provider;
17
+ codex: Provider;
18
+ }
19
+ export declare function makeHandshakeHandler(deps: HandshakeDeps): (c: Context) => Promise<Response & import("hono").TypedResponse<{
20
+ version: string;
21
+ providers: {
22
+ claude: {
23
+ installed: boolean;
24
+ binary?: string | undefined;
25
+ version?: string | undefined;
26
+ reason?: string | undefined;
27
+ };
28
+ codex: {
29
+ installed: boolean;
30
+ binary?: string | undefined;
31
+ version?: string | undefined;
32
+ reason?: string | undefined;
33
+ };
34
+ gemini: {
35
+ installed: boolean;
36
+ binary?: string | undefined;
37
+ version?: string | undefined;
38
+ reason?: string | undefined;
39
+ };
40
+ };
41
+ paired_at: string | null;
42
+ device_name: string;
43
+ }, 200, "json">>;
@@ -0,0 +1,28 @@
1
+ import os from "node:os";
2
+ import { CLI_VERSION } from "../version.js";
3
+ export function deviceName() {
4
+ const host = os.hostname();
5
+ const plat = process.platform === "darwin" ? "macOS" : process.platform;
6
+ const rel = os.release();
7
+ return `${host} (${plat} ${rel})`;
8
+ }
9
+ export function makeHandshakeHandler(deps) {
10
+ return async (c) => {
11
+ const [claude, codex] = await Promise.all([
12
+ deps.claude.detect(),
13
+ deps.codex.detect(),
14
+ ]);
15
+ const body = {
16
+ version: CLI_VERSION,
17
+ providers: {
18
+ claude,
19
+ codex,
20
+ gemini: { installed: false, reason: "not yet supported" },
21
+ },
22
+ paired_at: deps.pairedAt(),
23
+ device_name: deviceName(),
24
+ };
25
+ return c.json(body, 200);
26
+ };
27
+ }
28
+ //# sourceMappingURL=handshake.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/http/handshake.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAc5C,MAAM,UAAU,UAAU;IACxB,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACxE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IACzB,OAAO,GAAG,IAAI,KAAK,IAAI,IAAI,GAAG,GAAG,CAAC;AACpC,CAAC;AAQD,MAAM,UAAU,oBAAoB,CAAC,IAAmB;IACtD,OAAO,KAAK,EAAE,CAAU,EAAE,EAAE;QAC1B,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACxC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;YACpB,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;SACpB,CAAC,CAAC;QACH,MAAM,IAAI,GAAsB;YAC9B,OAAO,EAAE,WAAW;YACpB,SAAS,EAAE;gBACT,MAAM;gBACN,KAAK;gBACL,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,mBAAmB,EAAE;aAC1D;YACD,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE;YAC1B,WAAW,EAAE,UAAU,EAAE;SAC1B,CAAC;QACF,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC3B,CAAC,CAAC;AACJ,CAAC"}