@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.
- package/LICENSE +21 -0
- package/README.md +64 -0
- package/dist/commands/autostart.d.ts +42 -0
- package/dist/commands/autostart.js +195 -0
- package/dist/commands/autostart.js.map +1 -0
- package/dist/commands/bridge.d.ts +54 -0
- package/dist/commands/bridge.js +145 -0
- package/dist/commands/bridge.js.map +1 -0
- package/dist/commands/connect.d.ts +56 -0
- package/dist/commands/connect.js +213 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/doctor.d.ts +24 -0
- package/dist/commands/doctor.js +200 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/install-claude.d.ts +22 -0
- package/dist/commands/install-claude.js +56 -0
- package/dist/commands/install-claude.js.map +1 -0
- package/dist/commands/mcp.d.ts +5 -0
- package/dist/commands/mcp.js +23 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/pair-url.d.ts +12 -0
- package/dist/commands/pair-url.js +23 -0
- package/dist/commands/pair-url.js.map +1 -0
- package/dist/commands/pair.d.ts +12 -0
- package/dist/commands/pair.js +24 -0
- package/dist/commands/pair.js.map +1 -0
- package/dist/commands/prompt.d.ts +5 -0
- package/dist/commands/prompt.js +24 -0
- package/dist/commands/prompt.js.map +1 -0
- package/dist/commands/telemetry-cmd.d.ts +8 -0
- package/dist/commands/telemetry-cmd.js +55 -0
- package/dist/commands/telemetry-cmd.js.map +1 -0
- package/dist/config.d.ts +63 -0
- package/dist/config.js +77 -0
- package/dist/config.js.map +1 -0
- package/dist/http/auth-middleware.d.ts +9 -0
- package/dist/http/auth-middleware.js +29 -0
- package/dist/http/auth-middleware.js.map +1 -0
- package/dist/http/chat-completions.d.ts +48 -0
- package/dist/http/chat-completions.js +117 -0
- package/dist/http/chat-completions.js.map +1 -0
- package/dist/http/cors.d.ts +9 -0
- package/dist/http/cors.js +35 -0
- package/dist/http/cors.js.map +1 -0
- package/dist/http/handshake.d.ts +43 -0
- package/dist/http/handshake.js +28 -0
- package/dist/http/handshake.js.map +1 -0
- package/dist/http/messages-endpoint.d.ts +67 -0
- package/dist/http/messages-endpoint.js +95 -0
- package/dist/http/messages-endpoint.js.map +1 -0
- package/dist/http/server.d.ts +37 -0
- package/dist/http/server.js +83 -0
- package/dist/http/server.js.map +1 -0
- package/dist/http/sse.d.ts +35 -0
- package/dist/http/sse.js +72 -0
- package/dist/http/sse.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +219 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/manifest.d.ts +16 -0
- package/dist/mcp/manifest.js +88 -0
- package/dist/mcp/manifest.js.map +1 -0
- package/dist/mcp/server.d.ts +25 -0
- package/dist/mcp/server.js +166 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/providers/base.d.ts +91 -0
- package/dist/providers/base.js +27 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/claude.d.ts +22 -0
- package/dist/providers/claude.js +157 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex.d.ts +38 -0
- package/dist/providers/codex.js +179 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/detect-util.d.ts +17 -0
- package/dist/providers/detect-util.js +68 -0
- package/dist/providers/detect-util.js.map +1 -0
- package/dist/providers/index.d.ts +14 -0
- package/dist/providers/index.js +21 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/telemetry.d.ts +68 -0
- package/dist/telemetry.js +118 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/version.d.ts +9 -0
- package/dist/version.js +10 -0
- package/dist/version.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export const MODULES = [
|
|
2
|
+
{
|
|
3
|
+
id: "EXEC_SUMMARY",
|
|
4
|
+
displayName: "Executive Summary",
|
|
5
|
+
description: "Board-ready synthesis tying market, customer, competition, and economics into one decision narrative. Best run after the research modules.",
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
id: "SETUP",
|
|
9
|
+
displayName: "Initial Framing",
|
|
10
|
+
description: "START HERE. Establishes the company's context, sector, and business model so every later module reasons from the right baseline.",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "TAM_SIZING",
|
|
14
|
+
displayName: "Market Sizing & TAM",
|
|
15
|
+
description: "Sizes the opportunity (TAM / SAM / SOM + segment prioritization) to test whether the market is big enough to matter.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "ICP",
|
|
19
|
+
displayName: "Ideal Customer Profile",
|
|
20
|
+
description: "Pinpoints the highest-fit customer segments and the personas who actually buy.",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "JTBD",
|
|
24
|
+
displayName: "Jobs To Be Done",
|
|
25
|
+
description: "Uncovers the real job customers hire this for: their pains, desired outcomes, and switch triggers.",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "COMPETITIVE",
|
|
29
|
+
displayName: "Competitive Landscape",
|
|
30
|
+
description: "Maps the competitive field and where you genuinely differentiate vs. where you don't.",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: "POSITIONING",
|
|
34
|
+
displayName: "Positioning Statement",
|
|
35
|
+
description: "Turns the research into a sharp positioning statement: who it's for, what it is, why it wins.",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: "PITCHES",
|
|
39
|
+
displayName: "Elevator Pitches",
|
|
40
|
+
description: "Audience-tuned elevator pitches (exec, customer, investor) you can use verbatim.",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "QUOTES",
|
|
44
|
+
displayName: "Customer Quotes",
|
|
45
|
+
description: "Illustrative customer/stakeholder quotes that make the value concrete and testable.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "PRESS_RELEASE",
|
|
49
|
+
displayName: "Future Press Release",
|
|
50
|
+
description: "Amazon Working-Backwards future press release: forces clarity on the outcome you're building toward.",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "DISCOVERY",
|
|
54
|
+
displayName: "Discovery & Validation Plan",
|
|
55
|
+
description: "A concrete plan to validate the riskiest assumptions before you over-invest.",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: "GAP",
|
|
59
|
+
displayName: "Gap Analysis",
|
|
60
|
+
description: "Surfaces gaps between today's reality and the proposition: what must be true to win.",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "VALUE_STACK",
|
|
64
|
+
displayName: "Value Stack",
|
|
65
|
+
description: "Builds the value stack and pressure-tests it against the 'do nothing / DIY' alternative.",
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "MOAT",
|
|
69
|
+
displayName: "Moat Deep Dive",
|
|
70
|
+
description: "Assesses durable advantage: what stops a competitor from copying this within a year.",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "UNIT_ECON",
|
|
74
|
+
displayName: "Unit Economics",
|
|
75
|
+
description: "Unit economics, cost-to-serve, pricing mechanics, and revenue scenarios: does the model pay?",
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "TOP_QUESTIONS",
|
|
79
|
+
displayName: "Top Questions & Action Plan",
|
|
80
|
+
description: "The critical unanswered questions plus an action plan to resolve them.",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "IDEAS",
|
|
84
|
+
displayName: "Five Additional Ideas",
|
|
85
|
+
description: "Five adjacent initiative or investment ideas worth exploring next.",
|
|
86
|
+
},
|
|
87
|
+
];
|
|
88
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/mcp/manifest.ts"],"names":[],"mappings":"AAgBA,MAAM,CAAC,MAAM,OAAO,GAA0B;IAC5C;QACE,EAAE,EAAE,cAAc;QAClB,WAAW,EAAE,mBAAmB;QAChC,WAAW,EACT,4IAA4I;KAC/I;IACD;QACE,EAAE,EAAE,OAAO;QACX,WAAW,EAAE,iBAAiB;QAC9B,WAAW,EACT,kIAAkI;KACrI;IACD;QACE,EAAE,EAAE,YAAY;QAChB,WAAW,EAAE,qBAAqB;QAClC,WAAW,EACT,sHAAsH;KACzH;IACD;QACE,EAAE,EAAE,KAAK;QACT,WAAW,EAAE,wBAAwB;QACrC,WAAW,EACT,gFAAgF;KACnF;IACD;QACE,EAAE,EAAE,MAAM;QACV,WAAW,EAAE,iBAAiB;QAC9B,WAAW,EACT,oGAAoG;KACvG;IACD;QACE,EAAE,EAAE,aAAa;QACjB,WAAW,EAAE,uBAAuB;QACpC,WAAW,EACT,uFAAuF;KAC1F;IACD;QACE,EAAE,EAAE,aAAa;QACjB,WAAW,EAAE,uBAAuB;QACpC,WAAW,EACT,+FAA+F;KAClG;IACD;QACE,EAAE,EAAE,SAAS;QACb,WAAW,EAAE,kBAAkB;QAC/B,WAAW,EACT,kFAAkF;KACrF;IACD;QACE,EAAE,EAAE,QAAQ;QACZ,WAAW,EAAE,iBAAiB;QAC9B,WAAW,EACT,qFAAqF;KACxF;IACD;QACE,EAAE,EAAE,eAAe;QACnB,WAAW,EAAE,sBAAsB;QACnC,WAAW,EACT,sGAAsG;KACzG;IACD;QACE,EAAE,EAAE,WAAW;QACf,WAAW,EAAE,6BAA6B;QAC1C,WAAW,EACT,8EAA8E;KACjF;IACD;QACE,EAAE,EAAE,KAAK;QACT,WAAW,EAAE,cAAc;QAC3B,WAAW,EACT,sFAAsF;KACzF;IACD;QACE,EAAE,EAAE,aAAa;QACjB,WAAW,EAAE,aAAa;QAC1B,WAAW,EACT,0FAA0F;KAC7F;IACD;QACE,EAAE,EAAE,MAAM;QACV,WAAW,EAAE,gBAAgB;QAC7B,WAAW,EACT,sFAAsF;KACzF;IACD;QACE,EAAE,EAAE,WAAW;QACf,WAAW,EAAE,gBAAgB;QAC7B,WAAW,EACT,8FAA8F;KACjG;IACD;QACE,EAAE,EAAE,eAAe;QACnB,WAAW,EAAE,6BAA6B;QAC1C,WAAW,EACT,wEAAwE;KAC3E;IACD;QACE,EAAE,EAAE,OAAO;QACX,WAAW,EAAE,uBAAuB;QACpC,WAAW,EACT,oEAAoE;KACvE;CACF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface McpServerOptions {
|
|
2
|
+
/** Override config dir (used by tests). */
|
|
3
|
+
configDir?: string;
|
|
4
|
+
/** Override base URL (used by tests + dev). */
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
/** Override fetch (used by tests). */
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
/** Override stderr writer (used by tests). */
|
|
9
|
+
stderr?: (line: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolvedToken {
|
|
12
|
+
token: string;
|
|
13
|
+
source: "env" | "config";
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Resolve the MCP bearer token from env > config. Throws a clear error if
|
|
17
|
+
* neither source has one.
|
|
18
|
+
*/
|
|
19
|
+
export declare function resolveMcpToken(opts?: McpServerOptions): Promise<ResolvedToken>;
|
|
20
|
+
/**
|
|
21
|
+
* Run the stdio MCP server until the transport closes.
|
|
22
|
+
*
|
|
23
|
+
* Returns when the transport disconnects (which under stdio means stdin EOF).
|
|
24
|
+
*/
|
|
25
|
+
export declare function runMcpServer(opts?: McpServerOptions): Promise<void>;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal MCP stdio server for `seanpropapp mcp`.
|
|
3
|
+
*
|
|
4
|
+
* v0.1.0-alpha scope:
|
|
5
|
+
* - Reads bearer token from `~/.seanpropapp/config.json` (`mcp_token` field)
|
|
6
|
+
* or `SEANPROPAPP_MCP_TOKEN` env override.
|
|
7
|
+
* - Exposes one tool (`run_module`) plus one prompt per methodology module,
|
|
8
|
+
* mirroring the existing `@seanpropapp/mcp` surface so Claude Desktop /
|
|
9
|
+
* Cursor configs continue to work.
|
|
10
|
+
* - Routes every assemble call to the production prompt-assembly endpoint
|
|
11
|
+
* (`/api/mcp/assemble-prompt`). The server never sees the LLM response;
|
|
12
|
+
* the host's LLM does inference using the user's own subscription.
|
|
13
|
+
*
|
|
14
|
+
* Out of scope (deferred to v1.4.0 full migration):
|
|
15
|
+
* - Streaming, multi-region routing, local methodology caching.
|
|
16
|
+
* - Telemetry beyond stderr session-ready line.
|
|
17
|
+
*
|
|
18
|
+
* Migration tracker: see proposition-app#341 + the README "MCP" section.
|
|
19
|
+
*/
|
|
20
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import { randomUUID } from "node:crypto";
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
import { MODULES } from "./manifest.js";
|
|
25
|
+
import { loadConfig } from "../config.js";
|
|
26
|
+
import { CLI_VERSION } from "../version.js";
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the MCP bearer token from env > config. Throws a clear error if
|
|
29
|
+
* neither source has one.
|
|
30
|
+
*/
|
|
31
|
+
export async function resolveMcpToken(opts = {}) {
|
|
32
|
+
const env = process.env["SEANPROPAPP_MCP_TOKEN"];
|
|
33
|
+
if (env && env.trim())
|
|
34
|
+
return { token: env.trim(), source: "env" };
|
|
35
|
+
const cfg = await loadConfig(opts.configDir);
|
|
36
|
+
if (cfg.mcp_token && cfg.mcp_token.trim()) {
|
|
37
|
+
return { token: cfg.mcp_token.trim(), source: "config" };
|
|
38
|
+
}
|
|
39
|
+
throw new Error("No MCP token found. Generate one at https://prop.seanoneill.com/mcp-setup, " +
|
|
40
|
+
'then either set SEANPROPAPP_MCP_TOKEN or run `seanpropapp pair` and paste the token into ~/.seanpropapp/config.json under "mcp_token".');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Run the stdio MCP server until the transport closes.
|
|
44
|
+
*
|
|
45
|
+
* Returns when the transport disconnects (which under stdio means stdin EOF).
|
|
46
|
+
*/
|
|
47
|
+
export async function runMcpServer(opts = {}) {
|
|
48
|
+
const baseUrl = (opts.baseUrl ??
|
|
49
|
+
process.env["SEANPROPAPP_MCP_URL"] ??
|
|
50
|
+
"https://prop.seanoneill.com").replace(/\/$/, "");
|
|
51
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
52
|
+
const stderr = opts.stderr ?? ((s) => process.stderr.write(s));
|
|
53
|
+
const { token, source } = await resolveMcpToken(opts);
|
|
54
|
+
// One id per process launch, forwarded on every call. Lets the server count
|
|
55
|
+
// distinct sessions without PII.
|
|
56
|
+
const sessionId = randomUUID();
|
|
57
|
+
const server = new McpServer({
|
|
58
|
+
name: "SeanPropApp",
|
|
59
|
+
version: CLI_VERSION,
|
|
60
|
+
});
|
|
61
|
+
function callerHost() {
|
|
62
|
+
return server.server.getClientVersion()?.name ?? "unknown";
|
|
63
|
+
}
|
|
64
|
+
async function assemble(args) {
|
|
65
|
+
const res = await fetchImpl(`${baseUrl}/api/mcp/assemble-prompt`, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
Authorization: `Bearer ${token}`,
|
|
70
|
+
"X-MCP-Session": sessionId,
|
|
71
|
+
"X-MCP-Client": callerHost(),
|
|
72
|
+
"X-MCP-Transport": "seanpropapp-cli",
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify({
|
|
75
|
+
moduleId: args.moduleId,
|
|
76
|
+
company: args.company,
|
|
77
|
+
persona: args.persona,
|
|
78
|
+
initiativeName: args.initiative,
|
|
79
|
+
investorQuestions: args.keyQuestion,
|
|
80
|
+
priorOutputs: args.priorOutputs,
|
|
81
|
+
}),
|
|
82
|
+
});
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
const err = (await res.json().catch(() => ({})));
|
|
85
|
+
const detail = err.message || err.error || `request failed (${res.status})`;
|
|
86
|
+
throw new Error(`SeanPropApp: ${detail}`);
|
|
87
|
+
}
|
|
88
|
+
return (await res.json());
|
|
89
|
+
}
|
|
90
|
+
const moduleIds = MODULES.map((m) => m.id);
|
|
91
|
+
server.registerTool("run_module", {
|
|
92
|
+
title: "Run a SeanPropApp methodology module",
|
|
93
|
+
description: "Assemble a SeanPropApp proposition-analysis module and return its instructions for you to execute. " +
|
|
94
|
+
"Run SETUP first for any new company. Pass `initiative` to focus on a specific What-If; pass `keyQuestion` to anchor the analysis. " +
|
|
95
|
+
"For continuity, pass earlier module outputs in `priorOutputs`. " +
|
|
96
|
+
`Available modules: ${moduleIds.join(", ")}.`,
|
|
97
|
+
inputSchema: {
|
|
98
|
+
moduleId: z
|
|
99
|
+
.enum(moduleIds)
|
|
100
|
+
.describe("Which methodology module to run (start with SETUP)"),
|
|
101
|
+
company: z.string().describe("The company or product being analyzed"),
|
|
102
|
+
persona: z
|
|
103
|
+
.enum(["internal_leader", "investor"])
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Analysis lens; defaults to internal_leader."),
|
|
106
|
+
initiative: z
|
|
107
|
+
.string()
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("Optional What-If hypothesis to focus the analysis on."),
|
|
110
|
+
keyQuestion: z
|
|
111
|
+
.string()
|
|
112
|
+
.optional()
|
|
113
|
+
.describe("Optional key question the user wants answered."),
|
|
114
|
+
priorOutputs: z
|
|
115
|
+
.record(z.string(), z.string())
|
|
116
|
+
.optional()
|
|
117
|
+
.describe("Map of prior module id to its output text."),
|
|
118
|
+
},
|
|
119
|
+
}, async ({ moduleId, company, persona, initiative, keyQuestion, priorOutputs, }) => {
|
|
120
|
+
const a = await assemble({
|
|
121
|
+
moduleId,
|
|
122
|
+
company,
|
|
123
|
+
persona,
|
|
124
|
+
initiative,
|
|
125
|
+
keyQuestion,
|
|
126
|
+
priorOutputs,
|
|
127
|
+
});
|
|
128
|
+
const text = [a.systemPrompt, "", "---", "", a.userMessage].join("\n");
|
|
129
|
+
return { content: [{ type: "text", text }] };
|
|
130
|
+
});
|
|
131
|
+
// Prompt surface (Claude Desktop menu): one prompt per module.
|
|
132
|
+
for (const m of MODULES) {
|
|
133
|
+
server.registerPrompt(m.id, {
|
|
134
|
+
title: m.displayName,
|
|
135
|
+
description: m.description,
|
|
136
|
+
argsSchema: {
|
|
137
|
+
company: z.string().describe("The company or product being analyzed"),
|
|
138
|
+
persona: z.enum(["internal_leader", "investor"]).optional(),
|
|
139
|
+
initiative: z.string().optional(),
|
|
140
|
+
keyQuestion: z.string().optional(),
|
|
141
|
+
},
|
|
142
|
+
}, async ({ company, persona, initiative, keyQuestion }) => {
|
|
143
|
+
const a = await assemble({
|
|
144
|
+
moduleId: m.id,
|
|
145
|
+
company,
|
|
146
|
+
persona,
|
|
147
|
+
initiative,
|
|
148
|
+
keyQuestion,
|
|
149
|
+
});
|
|
150
|
+
return {
|
|
151
|
+
messages: [
|
|
152
|
+
{
|
|
153
|
+
role: "user",
|
|
154
|
+
content: {
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `${a.systemPrompt}\n\n${a.userMessage}`,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
await server.connect(new StdioServerTransport());
|
|
164
|
+
stderr(`SeanPropApp MCP server ready (${baseUrl}, session ${sessionId}, token from ${source}).\n`);
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common provider interface. Each provider wraps a local subscription CLI
|
|
3
|
+
* (Claude CLI, Codex CLI, future Gemini CLI) and exposes a streaming API in
|
|
4
|
+
* Anthropic's Messages SSE shape.
|
|
5
|
+
*
|
|
6
|
+
* The bridge speaks both Anthropic and OpenAI wire formats; providers
|
|
7
|
+
* normalize to Anthropic internally and the OpenAI endpoint translates
|
|
8
|
+
* on the way out.
|
|
9
|
+
*/
|
|
10
|
+
export interface AnthropicMessage {
|
|
11
|
+
role: "user" | "assistant" | "system";
|
|
12
|
+
content: string | Array<{
|
|
13
|
+
type: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export interface AnthropicLikeRequest {
|
|
18
|
+
model: string;
|
|
19
|
+
max_tokens?: number;
|
|
20
|
+
system?: string;
|
|
21
|
+
messages: AnthropicMessage[];
|
|
22
|
+
stream?: boolean;
|
|
23
|
+
temperature?: number;
|
|
24
|
+
}
|
|
25
|
+
export type AnthropicSSEEvent = {
|
|
26
|
+
type: "message_start";
|
|
27
|
+
message: {
|
|
28
|
+
id: string;
|
|
29
|
+
model: string;
|
|
30
|
+
};
|
|
31
|
+
} | {
|
|
32
|
+
type: "content_block_start";
|
|
33
|
+
index: number;
|
|
34
|
+
content_block: {
|
|
35
|
+
type: "text";
|
|
36
|
+
text: string;
|
|
37
|
+
};
|
|
38
|
+
} | {
|
|
39
|
+
type: "content_block_delta";
|
|
40
|
+
index: number;
|
|
41
|
+
delta: {
|
|
42
|
+
type: "text_delta";
|
|
43
|
+
text: string;
|
|
44
|
+
};
|
|
45
|
+
} | {
|
|
46
|
+
type: "content_block_stop";
|
|
47
|
+
index: number;
|
|
48
|
+
} | {
|
|
49
|
+
type: "message_delta";
|
|
50
|
+
delta: {
|
|
51
|
+
stop_reason: string | null;
|
|
52
|
+
};
|
|
53
|
+
usage?: {
|
|
54
|
+
output_tokens: number;
|
|
55
|
+
};
|
|
56
|
+
} | {
|
|
57
|
+
type: "message_stop";
|
|
58
|
+
} | {
|
|
59
|
+
type: "error";
|
|
60
|
+
error: {
|
|
61
|
+
type: string;
|
|
62
|
+
message: string;
|
|
63
|
+
retry_after_seconds?: number;
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
export interface ProviderDetectResult {
|
|
67
|
+
installed: boolean;
|
|
68
|
+
binary?: string;
|
|
69
|
+
version?: string;
|
|
70
|
+
reason?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface Provider {
|
|
73
|
+
readonly name: string;
|
|
74
|
+
detect(): Promise<ProviderDetectResult>;
|
|
75
|
+
stream(request: AnthropicLikeRequest, signal?: AbortSignal): AsyncIterable<AnthropicSSEEvent>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Error category emitted when a provider's underlying CLI reports a
|
|
79
|
+
* subscription rate-limit (Claude Pro window cap, ChatGPT Plus cap, etc.).
|
|
80
|
+
* The browser maps this to ProviderErrorBlock's subscription_limit subtype.
|
|
81
|
+
*/
|
|
82
|
+
export declare class ClassifiedError extends Error {
|
|
83
|
+
readonly category: "subscription_limit" | "auth_required" | "cli_missing" | "cli_crashed" | "short_output" | "unknown";
|
|
84
|
+
readonly retryAfterSeconds?: number;
|
|
85
|
+
readonly provider?: string;
|
|
86
|
+
constructor(message: string, opts: {
|
|
87
|
+
category: ClassifiedError["category"];
|
|
88
|
+
retryAfterSeconds?: number;
|
|
89
|
+
provider?: string;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Common provider interface. Each provider wraps a local subscription CLI
|
|
3
|
+
* (Claude CLI, Codex CLI, future Gemini CLI) and exposes a streaming API in
|
|
4
|
+
* Anthropic's Messages SSE shape.
|
|
5
|
+
*
|
|
6
|
+
* The bridge speaks both Anthropic and OpenAI wire formats; providers
|
|
7
|
+
* normalize to Anthropic internally and the OpenAI endpoint translates
|
|
8
|
+
* on the way out.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Error category emitted when a provider's underlying CLI reports a
|
|
12
|
+
* subscription rate-limit (Claude Pro window cap, ChatGPT Plus cap, etc.).
|
|
13
|
+
* The browser maps this to ProviderErrorBlock's subscription_limit subtype.
|
|
14
|
+
*/
|
|
15
|
+
export class ClassifiedError extends Error {
|
|
16
|
+
category;
|
|
17
|
+
retryAfterSeconds;
|
|
18
|
+
provider;
|
|
19
|
+
constructor(message, opts) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "ClassifiedError";
|
|
22
|
+
this.category = opts.category;
|
|
23
|
+
this.retryAfterSeconds = opts.retryAfterSeconds;
|
|
24
|
+
this.provider = opts.provider;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base.js","sourceRoot":"","sources":["../../src/providers/base.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAyCH;;;;GAIG;AACH,MAAM,OAAO,eAAgB,SAAQ,KAAK;IACxB,QAAQ,CAMV;IACE,iBAAiB,CAAU;IAC3B,QAAQ,CAAU;IAElC,YACE,OAAe,EACf,IAIC;QAED,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;QAC9B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;QAC9B,IAAI,CAAC,iBAAiB,GAAG,IAAI,CAAC,iBAAiB,CAAC;QAChD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAChC,CAAC;CACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { type AnthropicLikeRequest, type AnthropicSSEEvent, type Provider, type ProviderDetectResult } from "./base.js";
|
|
3
|
+
import { runCapture } from "./detect-util.js";
|
|
4
|
+
/**
|
|
5
|
+
* Override hooks for testing. The provider takes optional injectables so we
|
|
6
|
+
* can mock subprocess behavior without complex module mocks.
|
|
7
|
+
*/
|
|
8
|
+
export interface ClaudeProviderDeps {
|
|
9
|
+
whichFn?: (bin: string) => Promise<string | null>;
|
|
10
|
+
runCaptureFn?: typeof runCapture;
|
|
11
|
+
spawnFn?: typeof spawn;
|
|
12
|
+
binaryName?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function detectRateLimit(text: string): boolean;
|
|
15
|
+
export declare function parseRetryAfter(text: string): number | undefined;
|
|
16
|
+
export declare class ClaudeProvider implements Provider {
|
|
17
|
+
readonly name = "claude";
|
|
18
|
+
private readonly deps;
|
|
19
|
+
constructor(deps?: ClaudeProviderDeps);
|
|
20
|
+
detect(): Promise<ProviderDetectResult>;
|
|
21
|
+
stream(request: AnthropicLikeRequest, signal?: AbortSignal): AsyncIterable<AnthropicSSEEvent>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { ClassifiedError, } from "./base.js";
|
|
3
|
+
import { runCapture, which } from "./detect-util.js";
|
|
4
|
+
const DEFAULT_BINARY = "claude";
|
|
5
|
+
const RATE_LIMIT_PATTERNS = [
|
|
6
|
+
/rate.?limit/i,
|
|
7
|
+
/429/,
|
|
8
|
+
/too many requests/i,
|
|
9
|
+
/subscription.*limit/i,
|
|
10
|
+
/window.*capped/i,
|
|
11
|
+
];
|
|
12
|
+
const RETRY_AFTER_PATTERNS = [
|
|
13
|
+
/retry.?after[:\s]+(\d+)\s*s/i,
|
|
14
|
+
/retry.?after[:\s]+(\d+)/i,
|
|
15
|
+
/try again in\s+(\d+)\s*s/i,
|
|
16
|
+
];
|
|
17
|
+
export function detectRateLimit(text) {
|
|
18
|
+
return RATE_LIMIT_PATTERNS.some((re) => re.test(text));
|
|
19
|
+
}
|
|
20
|
+
export function parseRetryAfter(text) {
|
|
21
|
+
for (const re of RETRY_AFTER_PATTERNS) {
|
|
22
|
+
const m = re.exec(text);
|
|
23
|
+
if (m && m[1]) {
|
|
24
|
+
const n = Number(m[1]);
|
|
25
|
+
if (Number.isFinite(n) && n >= 0)
|
|
26
|
+
return n;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
function lastUserText(req) {
|
|
32
|
+
for (let i = req.messages.length - 1; i >= 0; i--) {
|
|
33
|
+
const m = req.messages[i];
|
|
34
|
+
if (!m)
|
|
35
|
+
continue;
|
|
36
|
+
if (m.role !== "user")
|
|
37
|
+
continue;
|
|
38
|
+
if (typeof m.content === "string")
|
|
39
|
+
return m.content;
|
|
40
|
+
return m.content
|
|
41
|
+
.filter((block) => block.type === "text" && typeof block.text === "string")
|
|
42
|
+
.map((block) => block.text ?? "")
|
|
43
|
+
.join("\n");
|
|
44
|
+
}
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
export class ClaudeProvider {
|
|
48
|
+
name = "claude";
|
|
49
|
+
deps;
|
|
50
|
+
constructor(deps = {}) {
|
|
51
|
+
this.deps = {
|
|
52
|
+
whichFn: deps.whichFn ?? which,
|
|
53
|
+
runCaptureFn: deps.runCaptureFn ?? runCapture,
|
|
54
|
+
spawnFn: deps.spawnFn ?? spawn,
|
|
55
|
+
binaryName: deps.binaryName ?? DEFAULT_BINARY,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
async detect() {
|
|
59
|
+
const binary = await this.deps.whichFn(this.deps.binaryName);
|
|
60
|
+
if (!binary) {
|
|
61
|
+
return { installed: false, reason: "claude binary not found in PATH" };
|
|
62
|
+
}
|
|
63
|
+
const versionRun = await this.deps.runCaptureFn(binary, ["--version"], {
|
|
64
|
+
timeoutMs: 5000,
|
|
65
|
+
});
|
|
66
|
+
const version = versionRun.stdout.trim().split(/\s+/).pop() || versionRun.stderr.trim();
|
|
67
|
+
return { installed: true, binary, version: version || undefined };
|
|
68
|
+
}
|
|
69
|
+
async *stream(request, signal) {
|
|
70
|
+
const detected = await this.detect();
|
|
71
|
+
if (!detected.installed || !detected.binary) {
|
|
72
|
+
throw new ClassifiedError("Claude CLI not installed", {
|
|
73
|
+
category: "cli_missing",
|
|
74
|
+
provider: this.name,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
const args = ["--print", "--model", request.model];
|
|
78
|
+
if (request.system) {
|
|
79
|
+
args.push("--system-prompt", request.system);
|
|
80
|
+
}
|
|
81
|
+
const stdinPayload = lastUserText(request);
|
|
82
|
+
const child = this.deps.spawnFn(detected.binary, args, {
|
|
83
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
84
|
+
});
|
|
85
|
+
const stderrChunks = [];
|
|
86
|
+
child.stderr?.on("data", (chunk) => {
|
|
87
|
+
stderrChunks.push(chunk.toString());
|
|
88
|
+
});
|
|
89
|
+
const abortHandler = () => {
|
|
90
|
+
child.kill("SIGTERM");
|
|
91
|
+
};
|
|
92
|
+
signal?.addEventListener("abort", abortHandler);
|
|
93
|
+
if (child.stdin) {
|
|
94
|
+
child.stdin.end(stdinPayload);
|
|
95
|
+
}
|
|
96
|
+
const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
97
|
+
yield { type: "message_start", message: { id: messageId, model: request.model } };
|
|
98
|
+
yield {
|
|
99
|
+
type: "content_block_start",
|
|
100
|
+
index: 0,
|
|
101
|
+
content_block: { type: "text", text: "" },
|
|
102
|
+
};
|
|
103
|
+
try {
|
|
104
|
+
let totalOut = "";
|
|
105
|
+
if (child.stdout) {
|
|
106
|
+
for await (const chunk of child.stdout) {
|
|
107
|
+
const text = typeof chunk === "string" ? chunk : chunk.toString();
|
|
108
|
+
if (text.length === 0)
|
|
109
|
+
continue;
|
|
110
|
+
totalOut += text;
|
|
111
|
+
// Check stdout for rate-limit signals too (CLIs sometimes write to stdout).
|
|
112
|
+
if (detectRateLimit(text)) {
|
|
113
|
+
const retryAfter = parseRetryAfter(text);
|
|
114
|
+
throw new ClassifiedError("Subscription rate limit", {
|
|
115
|
+
category: "subscription_limit",
|
|
116
|
+
retryAfterSeconds: retryAfter,
|
|
117
|
+
provider: this.name,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
yield {
|
|
121
|
+
type: "content_block_delta",
|
|
122
|
+
index: 0,
|
|
123
|
+
delta: { type: "text_delta", text },
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const exitCode = await new Promise((resolve) => {
|
|
128
|
+
if (child.exitCode !== null)
|
|
129
|
+
return resolve(child.exitCode);
|
|
130
|
+
child.once("close", (code) => resolve(code));
|
|
131
|
+
});
|
|
132
|
+
const stderr = stderrChunks.join("");
|
|
133
|
+
if (exitCode !== 0) {
|
|
134
|
+
if (detectRateLimit(stderr) || detectRateLimit(totalOut)) {
|
|
135
|
+
const retryAfter = parseRetryAfter(stderr) ?? parseRetryAfter(totalOut);
|
|
136
|
+
throw new ClassifiedError("Subscription rate limit", {
|
|
137
|
+
category: "subscription_limit",
|
|
138
|
+
retryAfterSeconds: retryAfter,
|
|
139
|
+
provider: this.name,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
throw new ClassifiedError(`Claude CLI exited with code ${exitCode}: ${stderr.slice(0, 500)}`, { category: "cli_crashed", provider: this.name });
|
|
143
|
+
}
|
|
144
|
+
yield { type: "content_block_stop", index: 0 };
|
|
145
|
+
yield {
|
|
146
|
+
type: "message_delta",
|
|
147
|
+
delta: { stop_reason: "end_turn" },
|
|
148
|
+
usage: { output_tokens: 0 },
|
|
149
|
+
};
|
|
150
|
+
yield { type: "message_stop" };
|
|
151
|
+
}
|
|
152
|
+
finally {
|
|
153
|
+
signal?.removeEventListener("abort", abortHandler);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/providers/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { type AnthropicLikeRequest, type AnthropicSSEEvent, type Provider, type ProviderDetectResult } from "./base.js";
|
|
3
|
+
import { runCapture } from "./detect-util.js";
|
|
4
|
+
export interface CodexProviderDeps {
|
|
5
|
+
whichFn?: (bin: string) => Promise<string | null>;
|
|
6
|
+
runCaptureFn?: typeof runCapture;
|
|
7
|
+
spawnFn?: typeof spawn;
|
|
8
|
+
binaryName?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* OpenAI Chat Completion format used internally before translating back
|
|
12
|
+
* to Anthropic SSE for the bridge's unified output.
|
|
13
|
+
*/
|
|
14
|
+
export interface OpenAIChatMessage {
|
|
15
|
+
role: "system" | "user" | "assistant";
|
|
16
|
+
content: string;
|
|
17
|
+
}
|
|
18
|
+
export interface OpenAIChatRequest {
|
|
19
|
+
model: string;
|
|
20
|
+
messages: OpenAIChatMessage[];
|
|
21
|
+
max_tokens?: number;
|
|
22
|
+
temperature?: number;
|
|
23
|
+
stream?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Translate Anthropic Messages → OpenAI Chat Completions shape.
|
|
27
|
+
* Exported so tests can verify the translation surface directly.
|
|
28
|
+
*/
|
|
29
|
+
export declare function anthropicToOpenAI(req: AnthropicLikeRequest): OpenAIChatRequest;
|
|
30
|
+
export declare function detectRateLimit(text: string): boolean;
|
|
31
|
+
export declare function parseRetryAfter(text: string): number | undefined;
|
|
32
|
+
export declare class CodexProvider implements Provider {
|
|
33
|
+
readonly name = "codex";
|
|
34
|
+
private readonly deps;
|
|
35
|
+
constructor(deps?: CodexProviderDeps);
|
|
36
|
+
detect(): Promise<ProviderDetectResult>;
|
|
37
|
+
stream(request: AnthropicLikeRequest, signal?: AbortSignal): AsyncIterable<AnthropicSSEEvent>;
|
|
38
|
+
}
|