haas-mcp 0.2.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 (74) hide show
  1. package/README.md +98 -0
  2. package/dist/check.d.ts +23 -0
  3. package/dist/check.d.ts.map +1 -0
  4. package/dist/check.js +34 -0
  5. package/dist/check.js.map +1 -0
  6. package/dist/cli.d.ts +3 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +173 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/client.d.ts +44 -0
  11. package/dist/client.d.ts.map +1 -0
  12. package/dist/client.js +168 -0
  13. package/dist/client.js.map +1 -0
  14. package/dist/config.d.ts +33 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +53 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/connect.d.ts +68 -0
  19. package/dist/connect.d.ts.map +1 -0
  20. package/dist/connect.js +117 -0
  21. package/dist/connect.js.map +1 -0
  22. package/dist/server.d.ts +20 -0
  23. package/dist/server.d.ts.map +1 -0
  24. package/dist/server.js +77 -0
  25. package/dist/server.js.map +1 -0
  26. package/dist/setup.d.ts +94 -0
  27. package/dist/setup.d.ts.map +1 -0
  28. package/dist/setup.js +149 -0
  29. package/dist/setup.js.map +1 -0
  30. package/dist/tools/cancel_job.d.ts +3 -0
  31. package/dist/tools/cancel_job.d.ts.map +1 -0
  32. package/dist/tools/cancel_job.js +22 -0
  33. package/dist/tools/cancel_job.js.map +1 -0
  34. package/dist/tools/estimate_cost.d.ts +3 -0
  35. package/dist/tools/estimate_cost.d.ts.map +1 -0
  36. package/dist/tools/estimate_cost.js +28 -0
  37. package/dist/tools/estimate_cost.js.map +1 -0
  38. package/dist/tools/get_balance.d.ts +3 -0
  39. package/dist/tools/get_balance.d.ts.map +1 -0
  40. package/dist/tools/get_balance.js +13 -0
  41. package/dist/tools/get_balance.js.map +1 -0
  42. package/dist/tools/get_job_status.d.ts +3 -0
  43. package/dist/tools/get_job_status.d.ts.map +1 -0
  44. package/dist/tools/get_job_status.js +19 -0
  45. package/dist/tools/get_job_status.js.map +1 -0
  46. package/dist/tools/list_devices.d.ts +3 -0
  47. package/dist/tools/list_devices.d.ts.map +1 -0
  48. package/dist/tools/list_devices.js +13 -0
  49. package/dist/tools/list_devices.js.map +1 -0
  50. package/dist/tools/purchase_lease.d.ts +16 -0
  51. package/dist/tools/purchase_lease.d.ts.map +1 -0
  52. package/dist/tools/purchase_lease.js +50 -0
  53. package/dist/tools/purchase_lease.js.map +1 -0
  54. package/dist/tools/submit_batch.d.ts +3 -0
  55. package/dist/tools/submit_batch.d.ts.map +1 -0
  56. package/dist/tools/submit_batch.js +58 -0
  57. package/dist/tools/submit_batch.js.map +1 -0
  58. package/dist/tools/submit_job.d.ts +3 -0
  59. package/dist/tools/submit_job.d.ts.map +1 -0
  60. package/dist/tools/submit_job.js +49 -0
  61. package/dist/tools/submit_job.js.map +1 -0
  62. package/dist/tools/submit_source.d.ts +3 -0
  63. package/dist/tools/submit_source.d.ts.map +1 -0
  64. package/dist/tools/submit_source.js +55 -0
  65. package/dist/tools/submit_source.js.map +1 -0
  66. package/dist/tools/tail_serial.d.ts +3 -0
  67. package/dist/tools/tail_serial.d.ts.map +1 -0
  68. package/dist/tools/tail_serial.js +26 -0
  69. package/dist/tools/tail_serial.js.map +1 -0
  70. package/dist/tools/types.d.ts +9 -0
  71. package/dist/tools/types.d.ts.map +1 -0
  72. package/dist/tools/types.js +2 -0
  73. package/dist/tools/types.js.map +1 -0
  74. package/package.json +35 -0
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # haas-mcp
2
+
3
+ stdio MCP server for [haas-lab](https://haas-lab.com) — gives any AI client
4
+ (Claude Desktop, Cursor, OpenAI Codex CLI, etc.) native tools to compile and
5
+ flash firmware on real ESP32 boards in the cloud.
6
+
7
+ ## Install
8
+
9
+ One line, once the package is on npm. It opens your browser at the token page
10
+ (sign in if asked); you click **Connect** there, and the token it mints
11
+ arrives at the command by itself — nothing to paste. The command proves it
12
+ against the API, then connects every AI client it finds on the machine: Claude
13
+ Code and Codex through their own CLIs, Claude Desktop and Cursor by merging
14
+ their server lists.
15
+
16
+ ```bash
17
+ npx -y haas-mcp setup
18
+ ```
19
+
20
+ The terminal prints a four-character check and the page shows the same one;
21
+ if they differ, or you did not start the command yourself, close the tab. The
22
+ token it mints has the `read` and `submit` scopes only.
23
+
24
+ `setup --paste` asks for the token at the prompt instead, and is what the
25
+ command falls back to on a Node older than 22, which has no WebSocket.
26
+
27
+ By hand instead, one line per client:
28
+
29
+ ```bash
30
+ # Claude Code
31
+ claude mcp add haas-lab -e HAAS_PAT=pat_... -- npx -y haas-mcp
32
+
33
+ # OpenAI Codex CLI
34
+ codex mcp add haas-lab --env HAAS_PAT=pat_... -- npx -y haas-mcp
35
+ ```
36
+
37
+ Claude Desktop, Cursor and every other client that takes a JSON server list
38
+ get the same thing in that form — `claude_desktop_config.json` under
39
+ `~/Library/Application Support/Claude/` on a Mac or `%APPDATA%\Claude\` on
40
+ Windows, `.cursor/mcp.json` for Cursor:
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "haas-lab": {
46
+ "command": "npx",
47
+ "args": ["-y", "haas-mcp"],
48
+ "env": { "HAAS_PAT": "pat_..." }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ Prove the token before restarting the client:
55
+
56
+ ```bash
57
+ HAAS_PAT=pat_... npx -y haas-mcp --check
58
+ # haas-mcp: connected to https://haas-lab.com — 1200 credits available
59
+ ```
60
+
61
+ Then ask the client: *"List the haas-lab devices."* — it will call
62
+ `list_devices`.
63
+
64
+ ## Configure
65
+
66
+ Two environment variables (or `--token` / `--base-url` flags):
67
+
68
+ | Var | What | Default |
69
+ |---|---|---|
70
+ | `HAAS_PAT` | Personal Access Token from your haas-lab dashboard | required |
71
+ | `HAAS_BASE_URL` | API root | `https://haas-lab.com` |
72
+
73
+ A developer running the web app locally sets `HAAS_BASE_URL=http://localhost:3000`.
74
+ `--help` prints the install lines above.
75
+
76
+ ## Tools
77
+
78
+ | Tool | What |
79
+ |---|---|
80
+ | `list_devices` | List rentable hardware |
81
+ | `submit_source` | Compile C source into a FreeRTOS app on the bench and flash it — the default way to run a program, no local toolchain |
82
+ | `submit_job` | Safety-check and flash one prebuilt application image (base64; simulation recorded as skipped) |
83
+ | `submit_batch` | Fan out N variants in parallel (the differentiator) |
84
+ | `get_job_status` | Fetch a job's state, cost, escrow |
85
+ | `tail_serial` | Long-poll the SSE feed for serial output |
86
+ | `get_balance` | Check wallet credits + reputation |
87
+ | `estimate_cost` | Price a hypothetical job |
88
+ | `cancel_job` | Cancel + refund |
89
+ | `purchase_lease` | Book a board for a number of hours; runs on it during the window pay no runtime cost |
90
+
91
+ ## Develop
92
+
93
+ ```bash
94
+ cd mcp
95
+ npm install
96
+ npm run dev # tsx, no build step
97
+ HAAS_PAT=pat_... npm run dev
98
+ ```
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Prove a token against the API before an AI client is restarted around it
3
+ * (HAAS-142).
4
+ *
5
+ * One read-scoped call, the cheapest the API has — the wallet balance — so a
6
+ * `--check` costs the same as one tool call and says the three things a
7
+ * misconfigured install cannot otherwise tell apart: the base URL is right,
8
+ * the token is accepted, and the account behind it is the expected one.
9
+ */
10
+ import type { HaasClient } from "./client.js";
11
+ export type CheckResult = {
12
+ ok: true;
13
+ baseUrl: string;
14
+ credits: number | null;
15
+ } | {
16
+ ok: false;
17
+ baseUrl: string;
18
+ reason: string;
19
+ };
20
+ /** Only `get` is needed, which is also what lets a test hand in a double that throws. */
21
+ export declare function checkConnection(client: Pick<HaasClient, "get">, baseUrl: string): Promise<CheckResult>;
22
+ export declare function describeCheck(result: CheckResult): string;
23
+ //# sourceMappingURL=check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,MAAM,WAAW,GACnB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACrD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,yFAAyF;AACzF,wBAAsB,eAAe,CACnC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,EAC/B,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAOtB;AASD,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAMzD"}
package/dist/check.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Prove a token against the API before an AI client is restarted around it
3
+ * (HAAS-142).
4
+ *
5
+ * One read-scoped call, the cheapest the API has — the wallet balance — so a
6
+ * `--check` costs the same as one tool call and says the three things a
7
+ * misconfigured install cannot otherwise tell apart: the base URL is right,
8
+ * the token is accepted, and the account behind it is the expected one.
9
+ */
10
+ /** Only `get` is needed, which is also what lets a test hand in a double that throws. */
11
+ export async function checkConnection(client, baseUrl) {
12
+ try {
13
+ const body = await client.get("/api/v1/balance");
14
+ return { ok: true, baseUrl, credits: creditsOf(body) };
15
+ }
16
+ catch (error) {
17
+ return { ok: false, baseUrl, reason: error instanceof Error ? error.message : String(error) };
18
+ }
19
+ }
20
+ /** The balance, when the API's answer carries one; the check does not depend on it. */
21
+ function creditsOf(body) {
22
+ if (typeof body !== "object" || body === null)
23
+ return null;
24
+ const { availableCredits } = body;
25
+ return typeof availableCredits === "number" ? availableCredits : null;
26
+ }
27
+ export function describeCheck(result) {
28
+ if (result.ok) {
29
+ const balance = result.credits === null ? "" : ` — ${result.credits} credits available`;
30
+ return `haas-mcp: connected to ${result.baseUrl}${balance}`;
31
+ }
32
+ return `haas-mcp: could not reach ${result.baseUrl} with this token: ${result.reason}`;
33
+ }
34
+ //# sourceMappingURL=check.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,MAA+B,EAC/B,OAAe;IAEf,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACjD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;IAChG,CAAC;AACH,CAAC;AAED,uFAAuF;AACvF,SAAS,SAAS,CAAC,IAAa;IAC9B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAsC,CAAC;IACpE,OAAO,OAAO,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,IAAI,CAAC;AACxE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAmB;IAC/C,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,OAAO,oBAAoB,CAAC;QACxF,OAAO,0BAA0B,MAAM,CAAC,OAAO,GAAG,OAAO,EAAE,CAAC;IAC9D,CAAC;IACD,OAAO,6BAA6B,MAAM,CAAC,OAAO,qBAAqB,MAAM,CAAC,MAAM,EAAE,CAAC;AACzF,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ import { spawn, spawnSync } from "node:child_process";
3
+ import { createHash, randomBytes } from "node:crypto";
4
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { homedir, hostname } from "node:os";
6
+ import { delimiter, dirname, join } from "node:path";
7
+ import { createInterface } from "node:readline/promises";
8
+ import { checkConnection, describeCheck } from "./check.js";
9
+ import { HaasClient } from "./client.js";
10
+ import { readConfig, USAGE } from "./config.js";
11
+ import { CONNECT_TIMEOUT_MS, connectPageUrl, newSetupCode, pairingCheck, receiveToken, registerUrl, setupStreamUrl, } from "./connect.js";
12
+ import { startMcpServer } from "./server.js";
13
+ import { browserCommand, describeOutcomes, planSetup, runSetup, tokenPageUrl, } from "./setup.js";
14
+ async function main() {
15
+ const config = readConfig(process.argv.slice(2), process.env);
16
+ if (config.help) {
17
+ process.stdout.write(USAGE);
18
+ return;
19
+ }
20
+ if (config.setup) {
21
+ process.exit(await setup(config.pat, config.baseUrl, config.paste));
22
+ }
23
+ if (!config.pat) {
24
+ process.stderr.write("haas-mcp: HAAS_PAT environment variable (or --token flag) is required.\n" +
25
+ `Run \`npx -y haas-mcp setup\` once, or get a token from ${config.baseUrl}/dashboard/tokens\n`);
26
+ process.exit(1);
27
+ }
28
+ const client = new HaasClient(config.baseUrl, config.pat);
29
+ if (config.check) {
30
+ const result = await checkConnection(client, config.baseUrl);
31
+ process.stderr.write(`${describeCheck(result)}\n`);
32
+ process.exit(result.ok ? 0 : 1);
33
+ }
34
+ await startMcpServer(client);
35
+ }
36
+ /**
37
+ * The wizard. No question when no token was given: the token arrives over a
38
+ * socket once the person clicks Connect on the page this opens (HAAS-144),
39
+ * and the paste prompt remains only where that path cannot be used. Then the
40
+ * plan is run against this machine. Deliberately no second question: which
41
+ * clients to connect is detected, not asked, because a person who has to
42
+ * answer that is a person who could have edited the config themselves.
43
+ */
44
+ async function setup(given, baseUrl, paste) {
45
+ let token = given;
46
+ if (!token && !paste)
47
+ token = await receiveOverSocket(baseUrl);
48
+ if (!token) {
49
+ // The browser is opened at the token page; signed out, the site's
50
+ // middleware takes the person through sign-in and back to it. The URL is
51
+ // printed as well, for a machine with no browser or where the launch
52
+ // fails silently — the launch is best-effort and never blocks the prompt.
53
+ const url = tokenPageUrl(baseUrl);
54
+ openBrowser(url);
55
+ process.stdout.write(`haas-mcp setup — opening ${url}\n` +
56
+ "Sign in if asked, mint a token with the read and submit scopes, then\n");
57
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
58
+ token = (await prompt.question("paste it here: ")).trim();
59
+ prompt.close();
60
+ if (!token) {
61
+ process.stderr.write("haas-mcp: no token given\n");
62
+ return 1;
63
+ }
64
+ }
65
+ const proof = await checkConnection(new HaasClient(baseUrl, token), baseUrl);
66
+ process.stdout.write(`${describeCheck(proof)}\n`);
67
+ if (!proof.ok)
68
+ return 1;
69
+ const actions = planSetup(token, {
70
+ platform: process.platform,
71
+ env: process.env,
72
+ home: homedir(),
73
+ exists: existsSync,
74
+ onPath: findOnPath,
75
+ });
76
+ const outcomes = runSetup(actions, token, {
77
+ run: (program, args) => {
78
+ // A `.cmd` shim on Windows only runs through a shell; elsewhere the
79
+ // resolved path runs directly.
80
+ const result = spawnSync(program, args, {
81
+ encoding: "utf8",
82
+ shell: process.platform === "win32",
83
+ });
84
+ return { ok: result.status === 0, output: `${result.stdout ?? ""}${result.stderr ?? ""}` };
85
+ },
86
+ read: (path) => (existsSync(path) ? readFileSync(path, "utf8") : null),
87
+ write: (path, text) => {
88
+ mkdirSync(dirname(path), { recursive: true });
89
+ writeFileSync(path, text, "utf8");
90
+ },
91
+ });
92
+ process.stdout.write(describeOutcomes(outcomes));
93
+ return outcomes.length > 0 && outcomes.every((o) => o.ok) ? 0 : 1;
94
+ }
95
+ /**
96
+ * The no-paste path (HAAS-144): a code, a registration, the browser at the
97
+ * token page with the code, and a socket that receives what the click mints.
98
+ * Null when it cannot be used here — no WebSocket in this Node, a server that
99
+ * would not register the code, nobody clicking in time — with the reason
100
+ * printed, so the paste prompt that follows is a fallback the person can see.
101
+ */
102
+ async function receiveOverSocket(baseUrl) {
103
+ // Looked up by name because the global exists only on Node 22 and newer;
104
+ // the structural type is the part of it this uses.
105
+ const Socket = globalThis.WebSocket;
106
+ if (!Socket) {
107
+ process.stdout.write("haas-mcp setup — this Node has no WebSocket (Node 22 or newer does); falling back to pasting the token.\n");
108
+ return null;
109
+ }
110
+ const code = newSetupCode((n) => randomBytes(n));
111
+ try {
112
+ const registered = await fetch(registerUrl(baseUrl, code), {
113
+ method: "POST",
114
+ headers: { "content-type": "application/json" },
115
+ body: JSON.stringify({ machine: hostname() }),
116
+ });
117
+ if (!registered.ok) {
118
+ process.stdout.write(`haas-mcp setup — ${baseUrl} would not register a setup code (HTTP ${registered.status}); falling back to pasting the token.\n`);
119
+ return null;
120
+ }
121
+ }
122
+ catch (error) {
123
+ process.stdout.write(`haas-mcp setup — could not reach ${baseUrl} (${error instanceof Error ? error.message : String(error)}); falling back to pasting the token.\n`);
124
+ return null;
125
+ }
126
+ const url = connectPageUrl(baseUrl, code);
127
+ const check = pairingCheck(code, (input) => createHash("sha256").update(input, "utf8").digest("hex"));
128
+ openBrowser(url);
129
+ process.stdout.write(`haas-mcp setup — opening ${url}\n` +
130
+ `Sign in if asked. The page shows the check ${check}; if it shows anything else,\n` +
131
+ "do not click Connect. Waiting for it here, up to 10 minutes\n" +
132
+ "(Ctrl-C to stop; `haas-mcp setup --paste` to paste a token instead).\n");
133
+ try {
134
+ return await receiveToken(new Socket(setupStreamUrl(baseUrl, code)), CONNECT_TIMEOUT_MS);
135
+ }
136
+ catch (error) {
137
+ process.stdout.write(`haas-mcp setup — ${error instanceof Error ? error.message : String(error)}; falling back to pasting the token.\n`);
138
+ return null;
139
+ }
140
+ }
141
+ /** Open a URL in the default browser, detached, and never fail the wizard over it. */
142
+ function openBrowser(url) {
143
+ try {
144
+ const { program, args } = browserCommand(process.platform, url);
145
+ const child = spawn(program, args, { detached: true, stdio: "ignore" });
146
+ child.on("error", () => {
147
+ // No browser here; the URL is printed beside the prompt.
148
+ });
149
+ child.unref();
150
+ }
151
+ catch {
152
+ // Same: the printed URL is the fallback.
153
+ }
154
+ }
155
+ /** Where a program is on PATH, honouring Windows' executable suffixes. */
156
+ function findOnPath(name) {
157
+ const suffixes = process.platform === "win32" ? [".cmd", ".exe", ".bat", ""] : [""];
158
+ for (const dir of (process.env.PATH ?? "").split(delimiter)) {
159
+ if (!dir)
160
+ continue;
161
+ for (const suffix of suffixes) {
162
+ const candidate = join(dir, `${name}${suffix}`);
163
+ if (existsSync(candidate))
164
+ return candidate;
165
+ }
166
+ }
167
+ return null;
168
+ }
169
+ main().catch((err) => {
170
+ process.stderr.write(`haas-mcp fatal: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
171
+ process.exit(1);
172
+ });
173
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Minimal REST client for the haas-lab API. Forwards `Authorization: Bearer <pat>`
3
+ * on every call, throws on non-2xx with the response body for visibility.
4
+ */
5
+ export declare class HaasClient {
6
+ private readonly baseUrl;
7
+ private readonly pat;
8
+ constructor(baseUrl: string, pat: string);
9
+ private headers;
10
+ private parse;
11
+ private ensureOk;
12
+ get(path: string): Promise<unknown>;
13
+ postJson(path: string, body: unknown): Promise<unknown>;
14
+ postEmpty(path: string): Promise<unknown>;
15
+ /**
16
+ * Multipart upload — used by submit_job. Pass the firmware as a Buffer.
17
+ */
18
+ postMultipartFirmware(path: string, firmware: Buffer | Uint8Array, fields?: Record<string, string>): Promise<unknown>;
19
+ /**
20
+ * Submit C to be compiled on the bench (HAAS-230): the same multipart intake
21
+ * as a firmware, with a `source` text field in place of the `firmware` blob.
22
+ */
23
+ postMultipartSource(path: string, source: string, fields?: Record<string, string>): Promise<unknown>;
24
+ /**
25
+ * SSE long-poll: open the feed, collect chunks/events for up to `timeoutMs`,
26
+ * then close and return them. Used by tail_serial.
27
+ */
28
+ streamFeed(path: string, opts: {
29
+ timeoutMs: number;
30
+ maxChunks: number;
31
+ sinceSeq?: number;
32
+ }): Promise<{
33
+ chunks: {
34
+ seq: number;
35
+ data: string;
36
+ }[];
37
+ events: {
38
+ event: string;
39
+ data: string;
40
+ }[];
41
+ timedOut: boolean;
42
+ }>;
43
+ }
44
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qBAAa,UAAU;IAEnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,GAAG;gBADH,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM;IAG9B,OAAO,CAAC,OAAO;YAOD,KAAK;YAUL,QAAQ;IAShB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQnC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IASvD,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQ/C;;OAEG;IACG,qBAAqB,CACzB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GAAG,UAAU,EAC7B,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,OAAO,CAAC,OAAO,CAAC;IAiBnB;;;OAGG;IACG,mBAAmB,CACvB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAClC,OAAO,CAAC,OAAO,CAAC;IAcnB;;;OAGG;IACG,UAAU,CACd,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAChE,OAAO,CAAC;QAAE,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,MAAM,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;CA6DtH"}
package/dist/client.js ADDED
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Minimal REST client for the haas-lab API. Forwards `Authorization: Bearer <pat>`
3
+ * on every call, throws on non-2xx with the response body for visibility.
4
+ */
5
+ export class HaasClient {
6
+ baseUrl;
7
+ pat;
8
+ constructor(baseUrl, pat) {
9
+ this.baseUrl = baseUrl;
10
+ this.pat = pat;
11
+ }
12
+ headers(extra = {}) {
13
+ return {
14
+ Authorization: `Bearer ${this.pat}`,
15
+ ...extra,
16
+ };
17
+ }
18
+ async parse(res) {
19
+ const text = await res.text();
20
+ if (!text)
21
+ return null;
22
+ try {
23
+ return JSON.parse(text);
24
+ }
25
+ catch {
26
+ return text;
27
+ }
28
+ }
29
+ async ensureOk(res, label) {
30
+ const body = await this.parse(res);
31
+ if (!res.ok) {
32
+ const detail = typeof body === "string" ? body : JSON.stringify(body);
33
+ throw new Error(`${label} failed: HTTP ${res.status} — ${detail}`);
34
+ }
35
+ return body;
36
+ }
37
+ async get(path) {
38
+ const res = await fetch(`${this.baseUrl}${path}`, {
39
+ method: "GET",
40
+ headers: this.headers(),
41
+ });
42
+ return this.ensureOk(res, `GET ${path}`);
43
+ }
44
+ async postJson(path, body) {
45
+ const res = await fetch(`${this.baseUrl}${path}`, {
46
+ method: "POST",
47
+ headers: this.headers({ "Content-Type": "application/json" }),
48
+ body: JSON.stringify(body),
49
+ });
50
+ return this.ensureOk(res, `POST ${path}`);
51
+ }
52
+ async postEmpty(path) {
53
+ const res = await fetch(`${this.baseUrl}${path}`, {
54
+ method: "POST",
55
+ headers: this.headers(),
56
+ });
57
+ return this.ensureOk(res, `POST ${path}`);
58
+ }
59
+ /**
60
+ * Multipart upload — used by submit_job. Pass the firmware as a Buffer.
61
+ */
62
+ async postMultipartFirmware(path, firmware, fields = {}) {
63
+ const form = new FormData();
64
+ const blob = new Blob([new Uint8Array(firmware)], {
65
+ type: "application/octet-stream",
66
+ });
67
+ form.append("firmware", blob, "firmware.bin");
68
+ for (const [k, v] of Object.entries(fields)) {
69
+ form.append(k, v);
70
+ }
71
+ const res = await fetch(`${this.baseUrl}${path}`, {
72
+ method: "POST",
73
+ headers: this.headers(), // do not set Content-Type; fetch sets multipart boundary
74
+ body: form,
75
+ });
76
+ return this.ensureOk(res, `POST ${path} (multipart)`);
77
+ }
78
+ /**
79
+ * Submit C to be compiled on the bench (HAAS-230): the same multipart intake
80
+ * as a firmware, with a `source` text field in place of the `firmware` blob.
81
+ */
82
+ async postMultipartSource(path, source, fields = {}) {
83
+ const form = new FormData();
84
+ form.append("source", source);
85
+ for (const [k, v] of Object.entries(fields)) {
86
+ form.append(k, v);
87
+ }
88
+ const res = await fetch(`${this.baseUrl}${path}`, {
89
+ method: "POST",
90
+ headers: this.headers(), // do not set Content-Type; fetch sets multipart boundary
91
+ body: form,
92
+ });
93
+ return this.ensureOk(res, `POST ${path} (source)`);
94
+ }
95
+ /**
96
+ * SSE long-poll: open the feed, collect chunks/events for up to `timeoutMs`,
97
+ * then close and return them. Used by tail_serial.
98
+ */
99
+ async streamFeed(path, opts) {
100
+ const ac = new AbortController();
101
+ const timer = setTimeout(() => ac.abort(), opts.timeoutMs);
102
+ const chunks = [];
103
+ const events = [];
104
+ try {
105
+ const res = await fetch(`${this.baseUrl}${path}`, {
106
+ method: "GET",
107
+ headers: this.headers({ Accept: "text/event-stream" }),
108
+ signal: ac.signal,
109
+ });
110
+ if (!res.ok || !res.body) {
111
+ await this.ensureOk(res, `GET ${path}`);
112
+ }
113
+ const reader = res.body.getReader();
114
+ const decoder = new TextDecoder();
115
+ let buf = "";
116
+ let currentEvent = "message";
117
+ let currentData = "";
118
+ let seq = opts.sinceSeq ?? 0;
119
+ while (chunks.length < opts.maxChunks) {
120
+ const { done, value } = await reader.read();
121
+ if (done)
122
+ break;
123
+ buf += decoder.decode(value, { stream: true });
124
+ const frames = buf.split(/\n\n/);
125
+ buf = frames.pop() ?? "";
126
+ for (const frame of frames) {
127
+ currentEvent = "message";
128
+ currentData = "";
129
+ let sawData = false;
130
+ for (const line of frame.split(/\n/)) {
131
+ if (line.startsWith("event:"))
132
+ currentEvent = line.slice(6).trim();
133
+ // Several data lines are one value joined with newlines — the
134
+ // specification's rule, and what a multi-line serial chunk needs
135
+ // (HAAS-139). One leading space after the colon is the separator,
136
+ // not content; anything else stays.
137
+ else if (line.startsWith("data:")) {
138
+ const piece = line.slice(5).replace(/^ /, "");
139
+ currentData = currentData === "" && !sawData ? piece : `${currentData}\n${piece}`;
140
+ sawData = true;
141
+ }
142
+ }
143
+ events.push({ event: currentEvent, data: currentData });
144
+ if (currentEvent === "serial" || currentEvent === "message") {
145
+ chunks.push({ seq: ++seq, data: currentData });
146
+ if (chunks.length >= opts.maxChunks)
147
+ break;
148
+ }
149
+ }
150
+ }
151
+ try {
152
+ reader.releaseLock();
153
+ }
154
+ catch { /* ignore */ }
155
+ return { chunks, events, timedOut: false };
156
+ }
157
+ catch (err) {
158
+ if (ac.signal.aborted) {
159
+ return { chunks, events, timedOut: true };
160
+ }
161
+ throw err;
162
+ }
163
+ finally {
164
+ clearTimeout(timer);
165
+ }
166
+ }
167
+ }
168
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * What `haas-mcp` is told, and what it assumes when told nothing (HAAS-142).
3
+ *
4
+ * Pure, so the one-line install can be tested: the whole point of a one-liner
5
+ * is that the only thing a user supplies is a token, which means every other
6
+ * value has to default to the right thing. The base URL defaulted to a
7
+ * localhost port for the first months of this package's life, so a user who
8
+ * ran the documented `npx haas-mcp` against production would have been
9
+ * talking to nothing.
10
+ */
11
+ /** Where the hosted service is. A developer running the app locally overrides it. */
12
+ export declare const PRODUCTION_BASE_URL = "https://haas-lab.com";
13
+ export interface CliConfig {
14
+ /** The Personal Access Token, or null when none was given anywhere. */
15
+ pat: string | null;
16
+ /** No trailing slash, so paths concatenate cleanly. */
17
+ baseUrl: string;
18
+ /** Prove the token against the API and exit, instead of serving. */
19
+ check: boolean;
20
+ /** `haas-mcp setup`: connect every AI client on this machine, then exit. */
21
+ setup: boolean;
22
+ /** `setup --paste`: ask for the token at the prompt instead of receiving it over a socket. */
23
+ paste: boolean;
24
+ help: boolean;
25
+ }
26
+ /**
27
+ * Flags win over the environment, which wins over the default. `--token` and
28
+ * `--base-url` take a value; `--check` and `--help` stand alone.
29
+ */
30
+ export declare function readConfig(argv: readonly string[], env: Readonly<Record<string, string | undefined>>): CliConfig;
31
+ /** Printed by `--help`, and the same lines the README and the docs page carry. */
32
+ export declare const USAGE = "haas-mcp \u2014 flash firmware on real boards from any AI client, over MCP.\n\n npx -y haas-mcp setup opens the token page; click Connect there; every AI client here is connected\n npx -y haas-mcp setup --paste the same, pasting the token instead (a Node older than 22 has no WebSocket)\n HAAS_PAT=pat_... npx -y haas-mcp serve (stdio)\n HAAS_PAT=pat_... npx -y haas-mcp --check prove the token, then exit 0 or 1\n npx -y haas-mcp --help\n\nFlags --token <pat> --base-url <url> --check --paste --help\nEnvironment HAAS_PAT (required to serve) HAAS_BASE_URL (default https://haas-lab.com)\n\nMint a token at https://haas-lab.com/dashboard/tokens with the read and submit scopes.\n\nBy hand, one line per client:\n Claude Code claude mcp add haas-lab -e HAAS_PAT=pat_... -- npx -y haas-mcp\n Codex CLI codex mcp add haas-lab --env HAAS_PAT=pat_... -- npx -y haas-mcp\n Claude Desktop, Cursor and others take the JSON form:\n {\"mcpServers\":{\"haas-lab\":{\"command\":\"npx\",\"args\":[\"-y\",\"haas-mcp\"],\"env\":{\"HAAS_PAT\":\"pat_...\"}}}}\n";
33
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,qFAAqF;AACrF,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D,MAAM,WAAW,SAAS;IACxB,uEAAuE;IACvE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,KAAK,EAAE,OAAO,CAAC;IACf,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;IACf,8FAA8F;IAC9F,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAChD,SAAS,CAoBX;AAED,kFAAkF;AAClF,eAAO,MAAM,KAAK,4mCAkBjB,CAAC"}