@tempo-ai/mcp 0.0.123-staging.1 → 0.0.123
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/dist/bin.js +3 -3
- package/dist/{call-IOFLRETE.js → call-VU7GFC2V.js} +4 -4
- package/dist/{chunk-WRI3HTCJ.js → chunk-JKT5N6IQ.js} +13 -2
- package/dist/{chunk-EINSFKA4.js → chunk-P3TEHMQY.js} +36 -8
- package/dist/chunk-P3TEHMQY.js.map +1 -0
- package/dist/{chunk-BQBDBXKP.js → chunk-X2JA3GU2.js} +7 -7
- package/dist/chunk-X2JA3GU2.js.map +1 -0
- package/dist/index.js +3 -3
- package/dist/{pdfjs-SLAZWVHA.js → pdfjs-GSPIHND6.js} +2 -2
- package/dist/{serve-XIUA35JV.js → serve-MWQKPY7F.js} +36074 -3057
- package/dist/serve-MWQKPY7F.js.map +1 -0
- package/dist/{token-A6U2CCWJ.js → token-INLNRXZ5.js} +3 -3
- package/package.json +3 -2
- package/dist/chunk-BQBDBXKP.js.map +0 -1
- package/dist/chunk-EINSFKA4.js.map +0 -1
- package/dist/serve-XIUA35JV.js.map +0 -1
- /package/dist/{call-IOFLRETE.js.map → call-VU7GFC2V.js.map} +0 -0
- /package/dist/{chunk-WRI3HTCJ.js.map → chunk-JKT5N6IQ.js.map} +0 -0
- /package/dist/{pdfjs-SLAZWVHA.js.map → pdfjs-GSPIHND6.js.map} +0 -0
- /package/dist/{token-A6U2CCWJ.js.map → token-INLNRXZ5.js.map} +0 -0
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ pick by where your AI runs.
|
|
|
7
7
|
|
|
8
8
|
| Where your AI runs | Install this | What you get |
|
|
9
9
|
| --- | --- | --- |
|
|
10
|
-
| **A coding agent on your machine** — Claude Code, Codex CLI, Cursor, any stdio MCP host | **`@tempo-ai/mcp` (npm, local)** — see [Local package](#local-package-tempo-aimcp) | The FULL suite: issues, docs, comments, agents, scripts, Slack, Linear — reads **and writes** — plus canvas/design tools (screenshots, share links) when run inside a Tempo repo |
|
|
11
|
-
| **A chat app in the browser or on mobile** — ChatGPT, claude.ai, Claude mobile, or any client that takes a remote MCP URL | **`https://mcp.tempo.new/mcp` (hosted)** — see [Hosted server](#hosted-server-mcptempobuild) | The DB-backed toolsets, reads **and writes**: issues, docs, comments, agents, scripts, Slack, Linear.
|
|
10
|
+
| **A coding agent on your machine** — Claude Code, Codex CLI, Cursor, any stdio MCP host | **`@tempo-ai/mcp` (npm, local)** — see [Local package](#local-package-tempo-aimcp) | The FULL suite: issues, docs, comments, agents, scripts, Slack, Linear, live desktop selection context — reads **and writes** — plus canvas/design tools (screenshots, share links) when run inside a Tempo repo |
|
|
11
|
+
| **A chat app in the browser or on mobile** — ChatGPT, claude.ai, Claude mobile, or any client that takes a remote MCP URL | **`https://mcp.tempo.new/mcp` (hosted)** — see [Hosted server](#hosted-server-mcptempobuild) | The DB-backed toolsets, reads **and writes**: issues, docs, comments, agents, scripts, Slack, Linear, plus live desktop selection context through Convex. Filesystem canvas authoring requires the local package |
|
|
12
12
|
|
|
13
13
|
Rules of thumb:
|
|
14
14
|
|
|
@@ -64,6 +64,28 @@ Optional: sign in ahead of time with `npx -y @tempo-ai/mcp login`, or set
|
|
|
64
64
|
- `--toolsets issues,docs,canvas` — serve only the named toolsets
|
|
65
65
|
- `--readonly` — register no write tools at all (also `TEMPO_MCP_READONLY=1`)
|
|
66
66
|
|
|
67
|
+
### Live desktop selection context (local and hosted)
|
|
68
|
+
|
|
69
|
+
The `design-context` toolset works outside a local repository, including on
|
|
70
|
+
hosted MCP. Keep Tempo open and signed into the same account and organization:
|
|
71
|
+
|
|
72
|
+
1. Call `tempo_list_orgs` to find the `orgId`.
|
|
73
|
+
2. Call `list_design_context_targets({ orgId })` to find an open canvas.
|
|
74
|
+
3. Select elements or storyboards in Tempo, then call
|
|
75
|
+
`get_design_context({ orgId, targetId })`.
|
|
76
|
+
|
|
77
|
+
The result contains the same selection metadata as Cmd+L (source locations,
|
|
78
|
+
classes, ancestors, storyboard identity and rendering errors), plus PNG image
|
|
79
|
+
blocks when capture succeeds. It does not attach anything to a Tempo chat or
|
|
80
|
+
export full DOM/CSS. Both tools remain available with `--readonly` / `?readonly=1`.
|
|
81
|
+
|
|
82
|
+
Multiple canvases require an explicit `targetId`. Closed, changed, empty, or
|
|
83
|
+
offline selections return errors instead of silently choosing another canvas.
|
|
84
|
+
Captures are on demand through Convex, limited to 20 items / 8 MiB, and expire
|
|
85
|
+
within 30 seconds if the desktop does not answer. Private snapshot blobs are
|
|
86
|
+
purged five minutes after that deadline. No desktop port or remote-control
|
|
87
|
+
feature flag is required; the workspace must resolve to a registered org project.
|
|
88
|
+
|
|
67
89
|
### Canvas visuals
|
|
68
90
|
|
|
69
91
|
`canvas_screenshot`, `canvas_verify_vrt`, and `canvas_share`
|
|
@@ -132,7 +154,7 @@ claude mcp add --transport http tempo-hosted https://mcp.tempo.new/mcp
|
|
|
132
154
|
### Connection options
|
|
133
155
|
|
|
134
156
|
- `?toolsets=issues,docs` — serve only the named toolsets (valid:
|
|
135
|
-
issues, docs, comments, agents, scripts, slack, linear)
|
|
157
|
+
issues, docs, comments, agents, scripts, slack, linear, design-context)
|
|
136
158
|
- `?readonly=1` — strict opt-down: write tools are never registered on
|
|
137
159
|
this connection, whatever else is configured
|
|
138
160
|
|
package/dist/bin.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
run
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-X2JA3GU2.js";
|
|
6
|
+
import "./chunk-P3TEHMQY.js";
|
|
7
|
+
import "./chunk-JKT5N6IQ.js";
|
|
8
8
|
|
|
9
9
|
// src/bin.ts
|
|
10
10
|
run(process.argv).then(
|
|
@@ -2,14 +2,14 @@ import { createRequire as __tempoCreateRequire } from 'node:module'; const requi
|
|
|
2
2
|
import {
|
|
3
3
|
ensureCliAuth,
|
|
4
4
|
parseServeArgs
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-P3TEHMQY.js";
|
|
6
|
+
import "./chunk-JKT5N6IQ.js";
|
|
7
7
|
|
|
8
8
|
// src/call.ts
|
|
9
9
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
10
10
|
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
11
11
|
async function connectAggregate(options) {
|
|
12
|
-
const { createTempoAggregate } = await import("./serve-
|
|
12
|
+
const { createTempoAggregate } = await import("./serve-MWQKPY7F.js");
|
|
13
13
|
const handle = await createTempoAggregate(options, { stageDocuments: false });
|
|
14
14
|
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
15
15
|
const client = new Client({ name: "tempo-mcp-cli", version: "0" });
|
|
@@ -128,4 +128,4 @@ export {
|
|
|
128
128
|
runCall,
|
|
129
129
|
runTools
|
|
130
130
|
};
|
|
131
|
-
//# sourceMappingURL=call-
|
|
131
|
+
//# sourceMappingURL=call-VU7GFC2V.js.map
|
|
@@ -11,9 +11,16 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
|
|
|
11
11
|
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
12
12
|
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
13
13
|
});
|
|
14
|
+
var __esm = (fn, res) => function __init() {
|
|
15
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
16
|
+
};
|
|
14
17
|
var __commonJS = (cb, mod) => function __require2() {
|
|
15
18
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
19
|
};
|
|
20
|
+
var __export = (target, all) => {
|
|
21
|
+
for (var name in all)
|
|
22
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
23
|
+
};
|
|
17
24
|
var __copyProps = (to, from, except, desc) => {
|
|
18
25
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
19
26
|
for (let key of __getOwnPropNames(from))
|
|
@@ -30,10 +37,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
37
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
31
38
|
mod
|
|
32
39
|
));
|
|
40
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
33
41
|
|
|
34
42
|
export {
|
|
35
43
|
__require,
|
|
44
|
+
__esm,
|
|
36
45
|
__commonJS,
|
|
37
|
-
|
|
46
|
+
__export,
|
|
47
|
+
__toESM,
|
|
48
|
+
__toCommonJS
|
|
38
49
|
};
|
|
39
|
-
//# sourceMappingURL=chunk-
|
|
50
|
+
//# sourceMappingURL=chunk-JKT5N6IQ.js.map
|
|
@@ -583,7 +583,7 @@ async function createAggregateServer(options) {
|
|
|
583
583
|
)
|
|
584
584
|
}));
|
|
585
585
|
let callChain = Promise.resolve();
|
|
586
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
586
|
+
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
587
587
|
const toolName = request.params.name;
|
|
588
588
|
const backend = toolRoutes.get(toolName);
|
|
589
589
|
if (!backend) {
|
|
@@ -637,6 +637,7 @@ async function createAggregateServer(options) {
|
|
|
637
637
|
scopeValues[param.name] = value;
|
|
638
638
|
}
|
|
639
639
|
const run = callChain.then(async () => {
|
|
640
|
+
extra.signal.throwIfAborted();
|
|
640
641
|
if (injected.length > 0) onScopedCall?.(scopeValues);
|
|
641
642
|
const requestId = randomUUID();
|
|
642
643
|
backend.onRequestStart?.(requestId);
|
|
@@ -644,7 +645,10 @@ async function createAggregateServer(options) {
|
|
|
644
645
|
return await backend.client.callTool(
|
|
645
646
|
{ name: toolName, arguments: args },
|
|
646
647
|
void 0,
|
|
647
|
-
{
|
|
648
|
+
{
|
|
649
|
+
timeout: BACKEND_CALL_TIMEOUT_MS,
|
|
650
|
+
signal: backend.forwardCancellation ? extra.signal : void 0
|
|
651
|
+
}
|
|
648
652
|
);
|
|
649
653
|
} finally {
|
|
650
654
|
backend.onRequestEnd?.(requestId);
|
|
@@ -767,6 +771,7 @@ function resolveSignInWaitMs() {
|
|
|
767
771
|
return Number.isFinite(raw) && raw >= 0 ? raw : DEFAULT_SIGN_IN_WAIT_MS;
|
|
768
772
|
}
|
|
769
773
|
var ALL_TOOLSETS = [
|
|
774
|
+
"design-context",
|
|
770
775
|
"issues",
|
|
771
776
|
"docs",
|
|
772
777
|
"comments",
|
|
@@ -785,7 +790,8 @@ function parseServeArgs(args) {
|
|
|
785
790
|
readonly = true;
|
|
786
791
|
} else if (arg === "--toolsets") {
|
|
787
792
|
const value = args[++i];
|
|
788
|
-
if (!value)
|
|
793
|
+
if (!value)
|
|
794
|
+
return { error: "--toolsets requires a comma-separated list" };
|
|
789
795
|
const requested = value.split(",").map((t) => t.trim()).filter(Boolean);
|
|
790
796
|
const unknown = requested.filter(
|
|
791
797
|
(t) => !ALL_TOOLSETS.includes(t)
|
|
@@ -901,7 +907,13 @@ var BASE_DELAY_MS = 200;
|
|
|
901
907
|
var AuthedConvexClient = class {
|
|
902
908
|
constructor(opts) {
|
|
903
909
|
this.cachedToken = null;
|
|
904
|
-
this.
|
|
910
|
+
this.signal = opts.signal;
|
|
911
|
+
this.client = new ConvexHttpClient(
|
|
912
|
+
opts.url,
|
|
913
|
+
opts.signal ? {
|
|
914
|
+
fetch: (input, init) => fetch(input, { ...init, signal: opts.signal })
|
|
915
|
+
} : void 0
|
|
916
|
+
);
|
|
905
917
|
this.authProvider = opts.authProvider;
|
|
906
918
|
}
|
|
907
919
|
async query(fn, args) {
|
|
@@ -919,16 +931,18 @@ var AuthedConvexClient = class {
|
|
|
919
931
|
this.client.setAuth(this.cachedToken.jwt);
|
|
920
932
|
return;
|
|
921
933
|
}
|
|
922
|
-
const token = await this.authProvider.getToken();
|
|
934
|
+
const token = await this.withSignal(() => this.authProvider.getToken());
|
|
923
935
|
this.cachedToken = token;
|
|
924
936
|
this.client.setAuth(token.jwt);
|
|
925
937
|
}
|
|
926
938
|
async callWithAuth(fn) {
|
|
939
|
+
this.signal?.throwIfAborted();
|
|
927
940
|
await this.ensureAuth();
|
|
928
941
|
for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
|
|
929
942
|
try {
|
|
930
|
-
return await fn
|
|
943
|
+
return await this.withSignal(fn);
|
|
931
944
|
} catch (err) {
|
|
945
|
+
this.signal?.throwIfAborted();
|
|
932
946
|
const mapped = mapConvexError(err);
|
|
933
947
|
if (mapped.code === "auth_required" && attempt === 0) {
|
|
934
948
|
this.cachedToken = null;
|
|
@@ -936,7 +950,7 @@ var AuthedConvexClient = class {
|
|
|
936
950
|
continue;
|
|
937
951
|
}
|
|
938
952
|
if (mapped.code === "internal" && attempt < MAX_RETRIES - 1) {
|
|
939
|
-
await sleep4(BASE_DELAY_MS * 2 ** attempt);
|
|
953
|
+
await this.withSignal(() => sleep4(BASE_DELAY_MS * 2 ** attempt));
|
|
940
954
|
continue;
|
|
941
955
|
}
|
|
942
956
|
throw new McpToolError(mapped);
|
|
@@ -947,6 +961,20 @@ var AuthedConvexClient = class {
|
|
|
947
961
|
message: "Max retries exceeded"
|
|
948
962
|
});
|
|
949
963
|
}
|
|
964
|
+
/** Auth providers and transports may stall; cancellation must release callers too. */
|
|
965
|
+
withSignal(run) {
|
|
966
|
+
const signal = this.signal;
|
|
967
|
+
if (!signal) return run();
|
|
968
|
+
signal.throwIfAborted();
|
|
969
|
+
return new Promise((resolve, reject) => {
|
|
970
|
+
const onAbort = () => reject(signal.reason);
|
|
971
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
972
|
+
Promise.resolve().then(() => {
|
|
973
|
+
signal.throwIfAborted();
|
|
974
|
+
return run();
|
|
975
|
+
}).then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
|
|
976
|
+
});
|
|
977
|
+
}
|
|
950
978
|
};
|
|
951
979
|
function sleep4(ms) {
|
|
952
980
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -1037,4 +1065,4 @@ export {
|
|
|
1037
1065
|
envTokenReauth,
|
|
1038
1066
|
createCliConvex
|
|
1039
1067
|
};
|
|
1040
|
-
//# sourceMappingURL=chunk-
|
|
1068
|
+
//# sourceMappingURL=chunk-P3TEHMQY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../mcp-runtime/auth/token-store.ts","../../mcp-runtime/paths.ts","../../mcp-runtime/auth/refresh-loop.ts","../../mcp-runtime/auth/auth-provider.ts","../../mcp-runtime/auth/browser-flow.ts","../../mcp-runtime/auth/open-browser.ts","../../mcp-runtime/auth/sign-in-prompt.ts","../../mcp-runtime/auth/token-exchange.ts","../../mcp-runtime/aggregate/aggregator.ts","../../mcp-runtime/safety/process-handlers.ts","../../mcp-runtime/transport/pin-interceptor.ts","../src/config.ts","../src/login.ts","../../convex-http-client-auth/client.ts","../../convex-http-client-auth/errors.ts","../src/cli-auth.ts"],"sourcesContent":["/**\n * Persistent token store backed by ~/.tempo/auth.json.\n *\n * - Atomic writes via tmp + rename (safe across concurrent MCP processes).\n * - chmod 600 on every write so other local users can't read the JWT.\n * - fs.watchFile-based change observer so a `tempo-mcp login` in another\n * terminal hot-rotates creds in already-running MCPs (no restart needed).\n *\n * v1 stores the sessionId in the same plaintext JSON. Future enhancement:\n * move sessionId to OS keychain (keytar/Credential Manager/libsecret).\n * See open items in the plan doc.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { authFilePath, tempoHome } from \"../paths.ts\";\n\nexport interface StoredAuth {\n token: string;\n sessionId: string;\n userId: string;\n email?: string;\n firstName?: string;\n lastName?: string;\n /**\n * Org id remembered from the sign-in handoff, if the auth page sent one.\n * Purely informational — org SCOPE is never read from stored auth; every\n * scoped tool call carries an explicit orgId parameter.\n */\n lastOrgId?: string | null;\n /** Decoded JWT exp (epoch ms). Refresh fires ~60s before this. */\n expiresAt: number;\n /**\n * Non-secret fingerprint (truncated SHA-256) of the TEMPO_AUTH_TOKEN this\n * auth was exchanged from. Absent for browser sign-ins. Lets the CLI make\n * the env token AUTHORITATIVE: cached credentials from a different token\n * (or a browser login) are re-exchanged instead of silently reused —\n * otherwise a shared cloud cache could run as the wrong user.\n */\n accessTokenFingerprint?: string;\n}\n\nexport type AuthListener = (next: StoredAuth | null) => void;\n\n/**\n * Read the auth file. Returns null when the file is missing or unparseable\n * — both legitimate states (first-run / corrupted), and the caller decides\n * how to handle (trigger login, throw, etc).\n */\nexport async function readAuth(): Promise<StoredAuth | null> {\n try {\n const raw = await fsp.readFile(authFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<StoredAuth>;\n if (\n typeof parsed.token === \"string\" &&\n typeof parsed.sessionId === \"string\" &&\n typeof parsed.userId === \"string\" &&\n typeof parsed.expiresAt === \"number\"\n ) {\n return parsed as StoredAuth;\n }\n return null;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n return null;\n }\n}\n\n/**\n * Synchronous reader used by lifecycle hooks that can't await (e.g.\n * `AuthProvider.getToken` in the middle of a Convex call). Returns null\n * on any I/O failure.\n */\nexport function readAuthSync(): StoredAuth | null {\n try {\n const raw = fs.readFileSync(authFilePath(), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<StoredAuth>;\n if (\n typeof parsed.token === \"string\" &&\n typeof parsed.sessionId === \"string\" &&\n typeof parsed.userId === \"string\" &&\n typeof parsed.expiresAt === \"number\"\n ) {\n return parsed as StoredAuth;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/**\n * Atomic write: write to a sibling tmp file then rename. Rename is atomic\n * on the same filesystem; a reader either sees the previous file or the\n * new one — never a half-written one.\n */\nexport async function writeAuth(auth: StoredAuth): Promise<void> {\n const dir = tempoHome();\n await fsp.mkdir(dir, { recursive: true, mode: 0o700 });\n const target = authFilePath();\n const tmp = path.join(dir, `auth.json.tmp.${process.pid}.${Date.now()}`);\n const data = JSON.stringify(auth, null, 2);\n await fsp.writeFile(tmp, data, { mode: 0o600 });\n await fsp.rename(tmp, target);\n // rename preserves the source's mode bits on POSIX; explicit chmod is\n // belt-and-braces for filesystems where it doesn't (some network FSes).\n try {\n await fsp.chmod(target, 0o600);\n } catch {\n // Non-fatal — best-effort hardening. Worst case: file is world-readable.\n }\n}\n\n/**\n * Clear the auth file. Used by `tempo-mcp logout` and by the refresh loop\n * when Clerk returns `session_expired`.\n */\nexport async function clearAuth(): Promise<void> {\n try {\n await fsp.unlink(authFilePath());\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n}\n\n/**\n * Watch the auth file for changes from other processes (e.g. a parallel\n * `tempo-mcp login` writes new creds → all running MCPs pick them up).\n *\n * Returns an unsubscribe function. The listener is called on EVERY mtime\n * change, debounced to one call per 250ms to absorb rename+chmod pairs.\n */\nexport function watchAuth(listener: AuthListener): () => void {\n let timer: NodeJS.Timeout | null = null;\n const file = authFilePath();\n\n const fire = async () => {\n timer = null;\n try {\n const next = await readAuth();\n listener(next);\n } catch {\n listener(null);\n }\n };\n\n const onChange = () => {\n if (timer) clearTimeout(timer);\n timer = setTimeout(fire, 250);\n };\n\n // fs.watchFile polls (interval: 1s) but works on every filesystem,\n // including network mounts where fs.watch is unreliable.\n fs.watchFile(file, { interval: 1000 }, onChange);\n\n return () => {\n fs.unwatchFile(file, onChange);\n if (timer) clearTimeout(timer);\n };\n}\n","/**\n * Filesystem layout for Tempo MCP runtime state.\n *\n * `~/.tempo/auth.json` — JWT, sessionId, userId, lastOrgId, expiresAt\n * `~/.tempo/logs/mcp-*.log` — fatal-error logs (one per binary)\n *\n * Overridable via `TEMPO_MCP_HOME` for tests / parallel CLI processes. The\n * Electron app uses a different file (`~/.tempo-auth.json`) — keeping the\n * paths separate so a CLI login doesn't stomp on an Electron session.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nexport function tempoHome(): string {\n return process.env.TEMPO_MCP_HOME ?? path.join(os.homedir(), \".tempo\");\n}\n\nexport function authFilePath(): string {\n return path.join(tempoHome(), \"auth.json\");\n}\n\nexport function logsDir(): string {\n return path.join(tempoHome(), \"logs\");\n}\n\nexport function logFilePath(binaryName: string): string {\n const date = new Date().toISOString().slice(0, 10);\n return path.join(logsDir(), `mcp-${binaryName}-${date}.log`);\n}\n","/**\n * Background JWT refresh loop.\n *\n * Refreshes the JWT ~60s before its `expiresAt`. On a hard auth failure\n * (Clerk returns `session_expired` / 401), clears local state and fires\n * `onReauthRequired`; the next protected tool call then prompts a fresh\n * browser sign-in.\n *\n * The implementation is intentionally restartable and stateless across\n * processes — all persistent state lives in `auth.json`, and any number\n * of MCP processes can share the same auth file via fs-watch (only one\n * actually performs the refresh; the others observe and pick up the new\n * token).\n */\n\nimport { readAuth, writeAuth, clearAuth, type StoredAuth } from \"./token-store.ts\";\n\nexport interface RefreshLoopOptions {\n /** Convex site URL (e.g. `https://greedy-jackal-526.convex.site`). No trailing slash. */\n convexSiteUrl: string;\n /** Called when refresh exhausts and a fresh sign-in is required. */\n onReauthRequired: () => void;\n /**\n * Optional headless recovery, tried BEFORE surrendering to\n * `onReauthRequired`: re-acquire credentials without a user (e.g.\n * re-exchange `TEMPO_AUTH_TOKEN` for a fresh session). Return true after\n * writing fresh auth.json; the loop then reads it and carries on.\n */\n attemptReauth?: () => Promise<boolean>;\n /** ms before `expiresAt` to fire the refresh. Default 60_000. */\n refreshLeadMs?: number;\n /** ms between retries on transient failure. Default 5_000. */\n retryDelayMs?: number;\n /** Max retries before giving up + calling onReauthRequired. Default 3. */\n maxRetries?: number;\n}\n\nexport interface RefreshLoopHandle {\n /** Start the loop (schedules the next refresh based on current expiresAt). */\n start: () => void;\n /** Cancel any pending refresh + stop the loop. */\n stop: () => void;\n /** Refresh now, regardless of expiry. Returns the new token or null on failure. */\n refreshNow: () => Promise<string | null>;\n}\n\nexport function createRefreshLoop(opts: RefreshLoopOptions): RefreshLoopHandle {\n const leadMs = opts.refreshLeadMs ?? 60_000;\n const retryDelay = opts.retryDelayMs ?? 5_000;\n const maxRetries = opts.maxRetries ?? 3;\n let timer: NodeJS.Timeout | null = null;\n let stopped = false;\n\n /**\n * Terminal-failure funnel: try headless re-auth first (env-token\n * exchange writes a fresh auth.json), only then bother the caller.\n */\n async function reauthOrGiveUp(): Promise<string | null> {\n if (opts.attemptReauth) {\n try {\n if (await opts.attemptReauth()) {\n const fresh = await readAuth();\n if (fresh) return fresh.token;\n }\n } catch {\n // Fall through — surface as a normal reauth-required.\n }\n }\n opts.onReauthRequired();\n return null;\n }\n\n async function refreshNow(): Promise<string | null> {\n const current = await readAuth();\n if (!current) {\n return reauthOrGiveUp();\n }\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n const res = await fetch(`${opts.convexSiteUrl}/auth/refresh`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ sessionId: current.sessionId }),\n });\n\n if (res.status === 401) {\n // session_expired — irrecoverable for THIS session; an env token\n // can still mint a new one via attemptReauth.\n await clearAuth();\n return reauthOrGiveUp();\n }\n\n if (!res.ok) {\n // Transient (5xx, network) — retry with backoff.\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n return reauthOrGiveUp();\n }\n\n const body = (await res.json()) as { token?: string };\n if (!body.token) {\n return reauthOrGiveUp();\n }\n\n const next: StoredAuth = {\n ...current,\n token: body.token,\n expiresAt: decodeJwtExp(body.token),\n };\n await writeAuth(next);\n return body.token;\n } catch (err) {\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n // Network failure after retries — don't clear state (transient!),\n // but signal a re-auth flow so the host can surface the issue.\n return reauthOrGiveUp();\n }\n }\n\n return null;\n }\n\n function schedule(auth: StoredAuth) {\n if (timer) clearTimeout(timer);\n if (stopped) return;\n const delay = Math.max(0, auth.expiresAt - Date.now() - leadMs);\n timer = setTimeout(() => {\n void refreshNow().then((newToken) => {\n if (newToken && !stopped) {\n void readAuth().then((a) => {\n if (a) schedule(a);\n });\n }\n });\n }, delay);\n }\n\n function start() {\n stopped = false;\n void readAuth().then((auth) => {\n if (auth) schedule(auth);\n });\n }\n\n function stop() {\n stopped = true;\n if (timer) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n return { start, stop, refreshNow };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/**\n * Decode a JWT's `exp` claim. Returns ms-since-epoch. Falls back to\n * 5-minute-from-now on any decode failure — keeps the refresh loop alive\n * with a sane default until the next successful refresh provides a real exp.\n */\nexport function decodeJwtExp(jwt: string): number {\n try {\n const [, payload] = jwt.split(\".\");\n const decoded = JSON.parse(Buffer.from(payload, \"base64\").toString(\"utf8\")) as { exp?: number };\n if (typeof decoded.exp === \"number\") return decoded.exp * 1000;\n } catch {\n // fall through\n }\n return Date.now() + 5 * 60_000;\n}\n","/**\n * AuthProvider impl that reads from the persistent token store and\n * lazy-refreshes on expiry. Conforms to `@tempo-modules/convex-http-client-auth`'s\n * `AuthProvider` interface so it drops straight into `AuthedConvexClient`.\n *\n * Typical flow:\n * const refresh = createRefreshLoop({ ... });\n * refresh.start();\n * const authProvider = createAuthProvider(refresh);\n * const convex = new AuthedConvexClient({ url, authProvider });\n *\n * The token returned by `getToken()` is whatever's in `auth.json` at the\n * moment of the call. The background refresh loop keeps it fresh; this\n * provider's only job is to read.\n */\n\nimport type {\n AuthProvider,\n AuthToken,\n} from \"@tempo-modules/convex-http-client-auth\";\nimport { readAuth } from \"./token-store.ts\";\nimport type { RefreshLoopHandle } from \"./refresh-loop.ts\";\n\nexport function createAuthProvider(\n refreshLoop: RefreshLoopHandle,\n): AuthProvider {\n return {\n async getToken(): Promise<AuthToken> {\n const auth = await readAuth();\n if (!auth) {\n throw new Error(\n \"Not signed in to Tempo. Retry the tool to get a sign-in link, or run `npx -y @tempo-ai/mcp login`.\",\n );\n }\n // Refresh proactively if within the lead window. The background loop\n // also handles this — but a stale token surfacing here means the\n // loop hasn't fired yet (cold start, clock skew, etc).\n if (auth.expiresAt - Date.now() < 30_000) {\n const newToken = await refreshLoop.refreshNow();\n if (newToken) {\n const fresh = await readAuth();\n if (fresh) {\n return { jwt: fresh.token, expiresAt: fresh.expiresAt };\n }\n }\n throw new Error(\n \"Tempo session expired. Retry the tool to get a sign-in link, or run `npx -y @tempo-ai/mcp login`.\",\n );\n }\n return { jwt: auth.token, expiresAt: auth.expiresAt };\n },\n };\n}\n","/**\n * Browser-based OAuth handoff with a localhost HTTP callback listener.\n *\n * Mirrors the Electron app's auth flow (`tempo-client/electron/src/main/adapters/auth.ts`):\n * 1. Bind a localhost HTTP server on `127.0.0.1:0` (kernel-assigned port).\n * 2. Direct the user to `https://auth.tempo.build/sign-in?callbackPort={port}`.\n * 3. After Clerk sign-in, the web-auth page POSTs `{ token, sessionId, userId, ... }`\n * to `127.0.0.1:{port}/tempo/auth` (web-auth Callback.tsx:73-89).\n * 4. We receive the POST, persist to ~/.tempo/auth.json (via writeAuth),\n * respond 200 to unblock the browser, close the listener.\n *\n * Used by:\n * - `tempo-mcp login` (CLI) — awaits `done` directly after printing the URL.\n * - The MCP server's first-use sign-in prompt (`sign-in-prompt.ts`) — same\n * primitive with no expiry; the URL goes into the `auth_required` tool\n * result returned to the host.\n */\n\nimport http from \"node:http\";\nimport { writeAuth } from \"./token-store.ts\";\nimport { decodeJwtExp } from \"./refresh-loop.ts\";\n\nexport interface BrowserFlowOptions {\n /** Base URL for the web-auth app. Default `https://auth.tempo.build`. */\n authUrl?: string;\n /**\n * If set, bind to this exact port (e.g. 17249 for parity with the Electron\n * default). Otherwise use a kernel-assigned port. Useful for tests that\n * want a predictable callback URL.\n */\n port?: number;\n /**\n * Timeout in ms for the user to complete sign-in. Default 10 minutes.\n * `Infinity` keeps the listener open for the life of the process — the\n * MCP server's first-use prompt uses this so a sign-in link handed to\n * the user stays valid however long they take.\n */\n timeoutMs?: number;\n /** Bind address. Default `127.0.0.1`. */\n bindAddress?: string;\n}\n\nexport interface BrowserFlowHandle {\n /** The fully-qualified URL to send the user to (sign-in page + callback port). */\n signInUrl: string;\n /** The localhost port the listener is bound to. */\n callbackPort: number;\n /**\n * Resolves when the browser POSTs the auth payload (and we've persisted it).\n * Rejects on timeout or listener error.\n */\n done: Promise<void>;\n /** Cancel the listener early (e.g. user aborted). */\n cancel: () => void;\n}\n\n/**\n * Start the browser auth flow. Does NOT open the browser itself — the caller\n * decides whether to call `openInBrowser` and/or hand the URL to the host in a\n * tool result. Returns immediately with the URL the user should visit.\n */\nexport async function startBrowserAuthFlow(\n opts: BrowserFlowOptions = {},\n): Promise<BrowserFlowHandle> {\n const authUrl = opts.authUrl ?? \"https://auth.tempo.build\";\n const timeoutMs = opts.timeoutMs ?? 10 * 60_000;\n const bindAddress = opts.bindAddress ?? \"127.0.0.1\";\n\n let resolveDone!: () => void;\n let rejectDone!: (err: Error) => void;\n const done = new Promise<void>((resolve, reject) => {\n resolveDone = resolve;\n rejectDone = reject;\n });\n\n const server = http.createServer(async (req, res) => {\n if (\n req.method === \"POST\" &&\n req.url &&\n req.url.startsWith(\"/tempo/auth\")\n ) {\n try {\n const body = await readJson(req);\n if (\n !body ||\n typeof body.token !== \"string\" ||\n typeof body.sessionId !== \"string\" ||\n typeof body.userId !== \"string\"\n ) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"missing fields\" }));\n return;\n }\n\n await writeAuth({\n token: body.token,\n sessionId: body.sessionId,\n userId: body.userId,\n email: typeof body.email === \"string\" ? body.email : undefined,\n firstName: typeof body.firstName === \"string\" ? body.firstName : undefined,\n lastName: typeof body.lastName === \"string\" ? body.lastName : undefined,\n expiresAt: decodeJwtExp(body.token),\n lastOrgId: typeof body.orgId === \"string\" ? body.orgId : null,\n });\n\n res.writeHead(200, {\n \"Content-Type\": \"application/json\",\n \"Access-Control-Allow-Origin\": \"*\",\n });\n res.end(JSON.stringify({ ok: true }));\n // Allow the response to flush before closing.\n setImmediate(() => {\n server.close();\n resolveDone();\n });\n } catch (err) {\n res.writeHead(500, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: String(err) }));\n }\n return;\n }\n\n if (req.method === \"OPTIONS\") {\n // CORS preflight from the auth.tempo.build origin.\n res.writeHead(204, {\n \"Access-Control-Allow-Origin\": \"*\",\n \"Access-Control-Allow-Methods\": \"POST, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type\",\n });\n res.end();\n return;\n }\n\n res.writeHead(404);\n res.end();\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(opts.port ?? 0, bindAddress, () => resolve());\n });\n\n const address = server.address();\n if (!address || typeof address === \"string\") {\n server.close();\n throw new Error(\"Failed to bind callback listener\");\n }\n const callbackPort = address.port;\n // `flow=mcp` tells web-auth this sign-in came from the MCP CLI, not the\n // Electron app: web-auth runs its MCP onboarding (persona + org creation)\n // for brand-new accounts and shows terminal-appropriate success copy.\n const signInUrl = `${authUrl}/sign-in?callbackPort=${callbackPort}&flow=mcp`;\n\n if (Number.isFinite(timeoutMs)) {\n const timeoutTimer = setTimeout(() => {\n server.close();\n rejectDone(new Error(`Sign-in timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n // Don't hold the process open just for the timeout\n if (typeof timeoutTimer.unref === \"function\") timeoutTimer.unref();\n\n // Clear the timeout when the user completes auth or cancels. The\n // explicit `.catch()` is required because `.finally()` returns a new\n // promise that re-throws — an unhandled rejection on that chain would\n // otherwise show up as a test/process warning.\n done.finally(() => clearTimeout(timeoutTimer)).catch(() => {});\n }\n\n return {\n signInUrl,\n callbackPort,\n done,\n cancel: () => {\n server.close();\n rejectDone(new Error(\"Sign-in cancelled\"));\n },\n };\n}\n\nfunction readJson(req: http.IncomingMessage): Promise<Record<string, unknown> | null> {\n return new Promise((resolve, reject) => {\n let raw = \"\";\n req.setEncoding(\"utf8\");\n req.on(\"data\", (chunk) => {\n raw += chunk;\n if (raw.length > 1_000_000) {\n // Defensive cap: an attacker on the local network shouldn't be able\n // to spam unbounded data at our auth listener.\n req.destroy();\n reject(new Error(\"payload too large\"));\n }\n });\n req.on(\"end\", () => {\n if (!raw) return resolve(null);\n try {\n resolve(JSON.parse(raw) as Record<string, unknown>);\n } catch (err) {\n reject(err);\n }\n });\n req.on(\"error\", reject);\n });\n}\n","/**\n * Cross-platform \"open this URL in the user's default browser\". macOS uses\n * `open`, Linux `xdg-open`, Windows `start`. Returns false when none of\n * those exist or the launch fails (headless server, locked-down sandbox)\n * so the caller can fall back to printing the URL.\n *\n * `TEMPO_MCP_NO_BROWSER=1` suppresses the launch entirely (e2e, CI, and\n * users who never want a window popping out of an agent's tool call).\n */\nimport { exec } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst execAsync = promisify(exec);\n\nexport function browserLaunchSuppressed(): boolean {\n return process.env.TEMPO_MCP_NO_BROWSER === \"1\";\n}\n\nexport async function openInBrowser(url: string): Promise<boolean> {\n if (browserLaunchSuppressed()) return false;\n const cmd =\n process.platform === \"darwin\"\n ? `open \"${url}\"`\n : process.platform === \"win32\"\n ? `start \"\" \"${url}\"`\n : `xdg-open \"${url}\"`;\n try {\n await execAsync(cmd, { windowsHide: true });\n return true;\n } catch {\n return false;\n }\n}\n","/**\n * First-use browser sign-in for a long-running MCP process (TEM-2313).\n *\n * The MCP server starts and connects WITHOUT credentials; every tool call\n * that needs Tempo and finds none goes through here. One pending browser\n * flow is live at a time: its localhost callback listener never expires,\n * so the sign-in link handed to the user stays valid however long they\n * take, and every unauthenticated call in the meantime reuses the SAME\n * link (and never re-opens the browser).\n *\n * A flow is spent once its sign-in lands (or it is cancelled). Nothing is\n * cached across sign-ins — stored credentials are the only state — so a\n * later `logout` or a session the refresh loop cleared after a 401 gets a\n * FRESH link on the next call, never a pass-through with no credentials.\n *\n * ensure(waitMs) →\n * \"signed_in\" credentials are stored (the user\n * finished within waitMs, or earlier)\n * { signInUrl, browserOpened } still waiting — the caller turns\n * this into an `auth_required` tool\n * result; a later call passes once\n * the browser POSTs the session.\n */\nimport { startBrowserAuthFlow, type BrowserFlowHandle } from \"./browser-flow.ts\";\nimport { openInBrowser } from \"./open-browser.ts\";\nimport { readAuth } from \"./token-store.ts\";\n\nexport interface SignInPromptOptions {\n /** Base URL for the web-auth app (previews / e2e). */\n authUrl?: string;\n /** Injected for tests; defaults to the real cross-platform opener. */\n openBrowser?: (url: string) => Promise<boolean>;\n}\n\nexport type SignInPromptResult =\n | \"signed_in\"\n | { signInUrl: string; browserOpened: boolean };\n\nexport interface SignInPrompt {\n ensure: (waitMs: number) => Promise<SignInPromptResult>;\n /** Close the pending listener (process shutdown). */\n dispose: () => void;\n}\n\ninterface Flow {\n handle: Promise<BrowserFlowHandle>;\n browserOpened: Promise<boolean> | null;\n /** `done` has settled — landed or cancelled. Never reused. */\n spent: boolean;\n}\n\nexport function createSignInPrompt(\n options: SignInPromptOptions = {},\n): SignInPrompt {\n const openBrowser = options.openBrowser ?? openInBrowser;\n let current: Flow | null = null;\n\n const currentOrFresh = (): Flow => {\n if (!current || current.spent) {\n const flow: Flow = {\n handle: startBrowserAuthFlow({\n authUrl: options.authUrl,\n timeoutMs: Infinity,\n }),\n browserOpened: null,\n spent: false,\n };\n flow.handle\n .then((handle) => handle.done)\n .catch(() => {})\n .finally(() => {\n flow.spent = true;\n });\n current = flow;\n }\n return current;\n };\n\n return {\n async ensure(waitMs) {\n for (;;) {\n if (await readAuth()) return \"signed_in\";\n const flow = currentOrFresh();\n const handle = await flow.handle;\n if (!flow.browserOpened) flow.browserOpened = openBrowser(handle.signInUrl);\n const opened = await flow.browserOpened;\n const landed = await Promise.race([\n handle.done.then(\n () => true,\n () => false,\n ),\n sleep(waitMs).then(() => false),\n ]);\n if (!landed) return { signInUrl: handle.signInUrl, browserOpened: opened };\n // The sign-in landed (auth.json was written before `done`\n // resolved), so this flow is spent. Loop: the re-read returns\n // \"signed_in\" — unless the credentials were cleared again in the\n // meantime, in which case a fresh flow starts.\n flow.spent = true;\n }\n },\n dispose() {\n if (!current || current.spent) return;\n void current.handle.then((handle) => handle.cancel()).catch(() => {});\n },\n };\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => {\n const timer = setTimeout(resolve, ms);\n if (typeof timer.unref === \"function\") timer.unref();\n });\n}\n","/**\n * Headless sign-in via a Tempo personal access token (TEM-2121).\n *\n * Cloud agent sandboxes (Claude Code cloud, Codex cloud, Grok Bot VMs)\n * have no browser, so the localhost sign-in handoff can never complete\n * there. Instead the environment sets `TEMPO_AUTH_TOKEN=tempo_...` (minted\n * once via `tempo-mcp token create` on a machine that CAN sign in) and\n * this module swaps it at Convex's `/auth/token-exchange` for a fresh\n * Clerk session + JWT, written to the standard auth.json so every\n * downstream consumer (refresh loop, AuthProvider, Convex clients) works\n * unchanged. Each exchange mints a NEW session, so the token outlives\n * Clerk's 7-day session lifetime; revoking it server-side kills the next\n * exchange.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { writeAuth, type StoredAuth } from \"./token-store.ts\";\nimport { decodeJwtExp } from \"./refresh-loop.ts\";\n\nexport const ENV_TOKEN_VAR = \"TEMPO_AUTH_TOKEN\";\n\n/** The `TEMPO_AUTH_TOKEN` value, or null when unset/blank. */\nexport function envAccessToken(): string | null {\n const raw = process.env[ENV_TOKEN_VAR]?.trim();\n return raw ? raw : null;\n}\n\n/**\n * Non-secret identifier for an access token: first 16 hex chars of its\n * SHA-256. Stored in auth.json so cached credentials can be matched to the\n * env token that produced them (16 hex ≈ 64 bits — plenty to distinguish\n * tokens, useless for recovering one).\n */\nexport function accessTokenFingerprint(accessToken: string): string {\n return createHash(\"sha256\").update(accessToken).digest(\"hex\").slice(0, 16);\n}\n\n/**\n * True when `stored` was exchanged from exactly this access token. False\n * for browser sign-ins (no fingerprint) and for a different token — both\n * cases where the env token must win and a fresh exchange is required.\n */\nexport function storedAuthMatchesAccessToken(\n stored: StoredAuth,\n accessToken: string,\n): boolean {\n return stored.accessTokenFingerprint === accessTokenFingerprint(accessToken);\n}\n\nexport class TokenExchangeError extends Error {\n constructor(\n message: string,\n /** `invalid_token` = terminal (revoked/wrong); `transient` = retryable. */\n public readonly kind: \"invalid_token\" | \"transient\",\n ) {\n super(message);\n this.name = \"TokenExchangeError\";\n }\n}\n\nexport interface ExchangeOptions {\n /** Convex site URL, e.g. `https://greedy-jackal-526.convex.site`. */\n convexSiteUrl: string;\n /** The `tempo_...` personal access token. */\n accessToken: string;\n /** Transient-failure retries. Default 2. */\n maxRetries?: number;\n /** ms between retries. Default 2_000. */\n retryDelayMs?: number;\n}\n\n/**\n * Exchange the personal access token for a JWT + session and persist the\n * result to auth.json. Throws `TokenExchangeError` — `invalid_token` means\n * the token is wrong or revoked (re-mint required); `transient` means the\n * backend/Clerk hiccuped and a later retry may succeed.\n */\nexport async function exchangeAccessToken(\n opts: ExchangeOptions,\n): Promise<StoredAuth> {\n const maxRetries = opts.maxRetries ?? 2;\n const retryDelay = opts.retryDelayMs ?? 2_000;\n\n let lastTransient: string = \"exchange failed\";\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n let res: Response;\n try {\n res = await fetch(`${opts.convexSiteUrl}/auth/token-exchange`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token: opts.accessToken }),\n });\n } catch (err) {\n lastTransient = err instanceof Error ? err.message : String(err);\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n break;\n }\n\n if (res.status === 401) {\n throw new TokenExchangeError(\n `${ENV_TOKEN_VAR} was rejected (revoked or invalid). Mint a new token with \\`tempo-mcp token create\\` on a signed-in machine.`,\n \"invalid_token\",\n );\n }\n if (!res.ok) {\n lastTransient = `token exchange returned ${res.status}`;\n if (attempt < maxRetries) {\n await sleep(retryDelay * Math.pow(1.5, attempt));\n continue;\n }\n break;\n }\n\n const body = (await res.json()) as {\n token?: string;\n sessionId?: string;\n userId?: string;\n };\n if (!body.token || !body.sessionId || !body.userId) {\n throw new TokenExchangeError(\n \"token exchange returned an incomplete payload\",\n \"transient\",\n );\n }\n\n const stored: StoredAuth = {\n token: body.token,\n sessionId: body.sessionId,\n userId: body.userId,\n email: decodeJwtEmail(body.token),\n expiresAt: decodeJwtExp(body.token),\n accessTokenFingerprint: accessTokenFingerprint(opts.accessToken),\n };\n await writeAuth(stored);\n return stored;\n }\n\n throw new TokenExchangeError(\n `Tempo token exchange failed: ${lastTransient}`,\n \"transient\",\n );\n}\n\n/** The `email` claim from the tempo-local JWT, if present. */\nfunction decodeJwtEmail(jwt: string): string | undefined {\n try {\n const [, payload] = jwt.split(\".\");\n const decoded = JSON.parse(\n Buffer.from(payload, \"base64\").toString(\"utf8\"),\n ) as { email?: string };\n return typeof decoded.email === \"string\" ? decoded.email : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n","/**\n * In-process MCP aggregator: ONE front server (stdio-facing) that proxies\n * tools and resources from N backend `McpServer` instances over linked\n * in-memory transports.\n *\n * This is what lets `tempo-mcp` present Tempo's whole toolset inventory\n * (issues, docs, comments, agents, scripts, slack, linear, canvas) as a\n * SINGLE MCP server entry in the host's config, while each toolset keeps\n * its existing factory (`create<X>McpServer`) completely unchanged.\n *\n * Request pinning: the factory servers snapshot org/user state per request\n * and hard-fail (`no_active_request`) when no pin is active. External hosts\n * (Claude Code, Codex) expose no turn boundaries, so the aggregator\n * synthesizes one pin per proxied tool call via the backend's\n * `onRequestStart`/`onRequestEnd` — the same contract the Electron host\n * drives per AI turn. The MCP SDK does NOT serialize request handlers, so\n * the aggregator chains every proxied call behind the previous one — at\n * most one pin (and one scope-slot value) is live at a time by\n * construction.\n */\nimport { randomUUID } from \"node:crypto\";\nimport { Server } from \"@modelcontextprotocol/sdk/server/index.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CallToolRequestSchema,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n ReadResourceRequestSchema,\n type CallToolResult,\n type Resource,\n type Tool,\n type Icon,\n} from \"@modelcontextprotocol/sdk/types.js\";\n\n/** Generous in-process ceiling; canvas capture can run for minutes. */\nconst BACKEND_CALL_TIMEOUT_MS = 10 * 60 * 1000;\n\nexport interface AggregateBackend {\n /** Toolset id, e.g. \"issues\", \"docs\", \"canvas\". Used in errors/telemetry. */\n toolset: string;\n server: McpServer;\n /**\n * Opt in only when handlers snapshot every scope value before their first\n * await and never use the ambient scope/pin afterwards. SDK cancellation\n * releases the proxy promise before the handler's cleanup has finished.\n */\n forwardCancellation?: boolean;\n /** Per-request pin hooks from the factory instance (see module docs). */\n onRequestStart?: (requestId: string) => void;\n onRequestEnd?: (requestId: string) => void;\n dispose?: () => void;\n}\n\n/**\n * A scope parameter the aggregator ADDS to a proxied tool's input schema\n * (required), extracts from every call, and strips before forwarding to\n * the backend. This is how external connections make org/project scoping\n * EXPLICIT per call while the factory modules keep their ambient\n * `getOrgId()`-style context — the extracted values land in\n * `onScopedCall` before the backend's request pin opens, and the host's\n * getters read them from there.\n */\nexport interface InjectedScopeParam {\n name: string;\n description: string;\n}\n\nexport interface CreateAggregateServerOptions {\n name: string;\n version: string;\n /** Optional branding emitted by the outward server, not its backends. */\n icons?: Icon[];\n /** McpServer.registerTool in SDK 1.x drops icons; emit them at this boundary. */\n toolIcons?: Record<string, Icon[]>;\n /** Reviewed host-specific action semantics; preserves other backend annotations. */\n toolAnnotations?: Record<string, NonNullable<Tool[\"annotations\"]>>;\n /** Best-effort completion observer; never allowed to change tool behavior. */\n onToolCallFinished?: (call: {\n name: string;\n toolset: string;\n args: Record<string, unknown>;\n result?: CallToolResult;\n durationMs: number;\n }) => void;\n instructions?: string;\n backends: AggregateBackend[];\n /**\n * Registration filter — a tool is exposed only when this returns true.\n * This is where the external tool manifest plugs in (readonly mode,\n * `externalAllowed: false` tools, per-connection `--toolsets`).\n */\n filterTool?: (toolset: string, toolName: string) => boolean;\n /** Scope params to inject into this tool's schema (see the type doc). */\n injectParams?: (toolset: string, toolName: string) => InjectedScopeParam[];\n /**\n * Receives the extracted scope-param values of the CURRENT call, before\n * the backend pin opens. The aggregator serializes tool calls through\n * completion (the SDK does NOT — overlapping requests are possible on\n * one session), so a request-scoped slot written here is race-free.\n */\n onScopedCall?: (values: Record<string, string>) => void;\n /**\n * Pre-call gate, run BEFORE scope-param validation and outside the\n * serialized call chain. Return a tool result to short-circuit the call\n * (the auth gate returns `auth_required` here so an unauthenticated user\n * sees the sign-in link, never `missing_scope_param`), or `null` to let\n * the call proceed.\n */\n gateCall?: (\n toolset: string,\n toolName: string,\n ) => Promise<CallToolResult | null>;\n}\n\nexport interface AggregateServer {\n /** Connect the front server to its outward transport (stdio, HTTP, ...). */\n connect: (transport: Transport) => Promise<void>;\n /** The low-level front server (exposed for tests). */\n server: Server;\n dispose: () => Promise<void>;\n}\n\ninterface ConnectedBackend extends AggregateBackend {\n client: Client;\n}\n\nexport async function createAggregateServer(\n options: CreateAggregateServerOptions,\n): Promise<AggregateServer> {\n const {\n name,\n version,\n instructions,\n backends,\n filterTool,\n injectParams,\n onScopedCall,\n gateCall,\n } = options;\n\n const connected: ConnectedBackend[] = [];\n for (const backend of backends) {\n const [clientTransport, serverTransport] =\n InMemoryTransport.createLinkedPair();\n const client = new Client({\n name: `${name}-aggregator`,\n version,\n });\n await backend.server.connect(serverTransport);\n await client.connect(clientTransport);\n connected.push({ ...backend, client });\n }\n\n // Tool inventories are static after factory registration, so build the\n // routing table once. A name collision across toolsets is a programming\n // error (tools are prefix-namespaced) — fail loudly, don't shadow.\n const toolRoutes = new Map<string, ConnectedBackend>();\n const injectedByTool = new Map<string, InjectedScopeParam[]>();\n const toolsByBackend = new Map<string, Tool[]>();\n for (const backend of connected) {\n if (!backend.client.getServerCapabilities()?.tools) {\n toolsByBackend.set(backend.toolset, []);\n continue;\n }\n const listed = await backend.client.listTools();\n const kept: Tool[] = [];\n for (const listedTool of listed.tools) {\n const icons = options.toolIcons?.[listedTool.name];\n const annotations = options.toolAnnotations?.[listedTool.name];\n const tool = {\n ...listedTool,\n ...(icons ? { icons } : {}),\n ...(annotations\n ? { annotations: { ...listedTool.annotations, ...annotations } }\n : {}),\n };\n if (filterTool && !filterTool(backend.toolset, tool.name)) continue;\n const existing = toolRoutes.get(tool.name);\n if (existing) {\n throw new Error(\n `MCP aggregate tool name collision: \"${tool.name}\" registered by both \"${existing.toolset}\" and \"${backend.toolset}\"`,\n );\n }\n toolRoutes.set(tool.name, backend);\n const injected = injectParams?.(backend.toolset, tool.name) ?? [];\n if (injected.length === 0) {\n kept.push(tool);\n continue;\n }\n injectedByTool.set(tool.name, injected);\n const schema = tool.inputSchema ?? { type: \"object\" as const };\n kept.push({\n ...tool,\n inputSchema: {\n ...schema,\n type: \"object\" as const,\n properties: {\n ...(schema.properties ?? {}),\n ...Object.fromEntries(\n injected.map((param) => [\n param.name,\n { type: \"string\", description: param.description },\n ]),\n ),\n },\n required: [\n ...new Set([\n ...((schema.required as string[] | undefined) ?? []),\n ...injected.map((param) => param.name),\n ]),\n ],\n },\n });\n }\n toolsByBackend.set(backend.toolset, kept);\n }\n\n const server = new Server(\n { name, version, ...(options.icons ? { icons: options.icons } : {}) },\n {\n capabilities: { tools: {}, resources: {} },\n instructions,\n },\n );\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: connected.flatMap(\n (backend) => toolsByBackend.get(backend.toolset) ?? [],\n ),\n }));\n\n // Tail of the in-flight tool-call chain (see the SERIALIZE note below).\n let callChain: Promise<void> = Promise.resolve();\n\n server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {\n const toolName = request.params.name;\n const backend = toolRoutes.get(toolName);\n if (!backend) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n error: {\n code: \"unknown_tool\",\n message: `No registered tool named \"${toolName}\"`,\n },\n }),\n },\n ],\n } satisfies CallToolResult;\n }\n const started = performance.now();\n let observedResult: CallToolResult | undefined;\n try {\n observedResult = await (async (): Promise<CallToolResult> => {\n if (gateCall) {\n const gated = await gateCall(backend.toolset, toolName);\n if (gated) return gated;\n }\n // Extract + strip the injected scope params (orgId/projectId) before\n // the backend sees the arguments; missing required scope is a\n // structured tool error naming the discovery tool to call.\n const args: Record<string, unknown> = {\n ...(request.params.arguments ?? {}),\n };\n const injected = injectedByTool.get(toolName) ?? [];\n const scopeValues: Record<string, string> = {};\n for (const param of injected) {\n const value = args[param.name];\n delete args[param.name];\n if (typeof value !== \"string\" || value.length === 0) {\n return {\n isError: true,\n content: [\n {\n type: \"text\" as const,\n text: JSON.stringify({\n error: {\n code: \"missing_scope_param\",\n message: `\"${toolName}\" requires \"${param.name}\". ${param.description}`,\n },\n }),\n },\n ],\n } satisfies CallToolResult;\n }\n scopeValues[param.name] = value;\n }\n // SERIALIZE through completion. The MCP SDK dispatches each incoming\n // request without awaiting the previous handler, so two tools/call\n // requests CAN overlap — and both the host's scope slot (fed by\n // onScopedCall) and the factories' request-snapshot maps (which fall\n // back to live getters once >1 pin is active) assume one scoped call\n // at a time. Chaining every proxied call behind the previous one makes\n // scope slot + pin + backend execution atomic per request, so a\n // concurrent org-A/org-B pair can never read each other's scope.\n const run = callChain.then(async (): Promise<CallToolResult> => {\n extra.signal.throwIfAborted();\n if (injected.length > 0) onScopedCall?.(scopeValues);\n\n const requestId = randomUUID();\n backend.onRequestStart?.(requestId);\n try {\n return (await backend.client.callTool(\n { name: toolName, arguments: args },\n undefined,\n {\n timeout: BACKEND_CALL_TIMEOUT_MS,\n signal: backend.forwardCancellation ? extra.signal : undefined,\n },\n )) as CallToolResult;\n } finally {\n backend.onRequestEnd?.(requestId);\n }\n });\n callChain = run.then(\n () => undefined,\n () => undefined,\n );\n return await run;\n })();\n return observedResult;\n } finally {\n try {\n options.onToolCallFinished?.({\n name: toolName,\n toolset: backend.toolset,\n args: request.params.arguments ?? {},\n result: observedResult,\n durationMs: Math.round(performance.now() - started),\n });\n } catch {\n /* Observability must never affect a tool's result. */\n }\n }\n });\n\n // Resources: merged list, URI-routed reads. Resource URIs are\n // scheme-namespaced per toolset (e.g. issues://...), so first-listed wins\n // is never exercised in practice; reads route by exact URI, falling back\n // to a live re-list for dynamic resources.\n const listResourcesForBackend = async (\n backend: ConnectedBackend,\n ): Promise<Resource[]> => {\n const caps = backend.client.getServerCapabilities();\n if (!caps?.resources) return [];\n const listed = await backend.client.listResources();\n return listed.resources;\n };\n\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n const all = await Promise.all(connected.map(listResourcesForBackend));\n return { resources: all.flat() };\n });\n\n server.setRequestHandler(ReadResourceRequestSchema, async (request) => {\n const uri = request.params.uri;\n for (const backend of connected) {\n const resources = await listResourcesForBackend(backend);\n if (resources.some((resource) => resource.uri === uri)) {\n return backend.client.readResource({ uri });\n }\n }\n throw new Error(`Unknown resource: ${uri}`);\n });\n\n return {\n server,\n connect: (transport) => server.connect(transport),\n dispose: async () => {\n for (const backend of connected) {\n await backend.client.close().catch(() => {});\n backend.dispose?.();\n }\n await server.close().catch(() => {});\n },\n };\n}\n","/**\n * Process-level safety net for standalone MCP binaries.\n *\n * MCP-over-stdio is a long-running subprocess that Claude Code spawns and\n * communicates with via JSON-RPC. If our process crashes, the host marks\n * the server \"failed\" for the remainder of the session — there is no\n * client-side reconnect in the MCP protocol. So we install handlers that:\n *\n * - log uncaught exceptions / unhandled rejections to `~/.tempo/logs/`\n * so a user can paste the trace into a bug report\n * - swallow EPIPE on stdout (Claude Code occasionally pauses pipes\n * during ctrl-C; killing the process on EPIPE is a self-inflicted\n * disconnect)\n * - exit cleanly on SIGTERM / SIGINT so the host's \"server stopped\"\n * state is correct\n *\n * Errors that legitimately should crash (out of memory, fatal SDK bugs)\n * still crash — these handlers ONLY guard against the common transient\n * failure modes that today silently take MCP servers offline.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { logFilePath, logsDir } from \"../paths.ts\";\n\nexport interface ProcessSafetyOptions {\n /** Identifier for log naming. Defaults to `process.argv[1]` basename. */\n binaryName?: string;\n /** Optional shutdown hook called on SIGTERM/SIGINT before exit. */\n onShutdown?: () => Promise<void> | void;\n}\n\nexport function installProcessSafety(opts: ProcessSafetyOptions = {}): () => void {\n const binaryName =\n opts.binaryName ?? path.basename(process.argv[1] ?? \"tempo-mcp\");\n\n const logError = (label: string, err: unknown) => {\n const line = `[${new Date().toISOString()}] ${label}: ${\n err instanceof Error ? err.stack || err.message : String(err)\n }\\n`;\n // Best-effort: never let logging block or crash the process.\n try {\n fs.mkdirSync(logsDir(), { recursive: true, mode: 0o700 });\n fs.appendFileSync(logFilePath(binaryName), line, { mode: 0o600 });\n } catch {\n // last resort: stderr (which the host captures)\n try {\n process.stderr.write(line);\n } catch {\n // give up\n }\n }\n };\n\n const onUncaught = (err: Error) => logError(\"uncaughtException\", err);\n const onUnhandled = (reason: unknown) => logError(\"unhandledRejection\", reason);\n\n process.on(\"uncaughtException\", onUncaught);\n process.on(\"unhandledRejection\", onUnhandled);\n\n // Swallow EPIPE on stdout — Claude Code occasionally pauses our pipe.\n // We can't write to stdout anyway in that case; throwing would be fatal.\n const stdoutError = (err: NodeJS.ErrnoException) => {\n if (err.code !== \"EPIPE\") logError(\"stdout-error\", err);\n };\n process.stdout.on(\"error\", stdoutError);\n\n const onSignal = (signal: NodeJS.Signals) => {\n Promise.resolve(opts.onShutdown?.())\n .catch((err) => logError(`shutdown:${signal}`, err))\n .finally(() => {\n // Use a small delay to let in-flight stdout writes flush before\n // the host kills us.\n setTimeout(() => process.exit(0), 50);\n });\n };\n process.once(\"SIGTERM\", () => onSignal(\"SIGTERM\"));\n process.once(\"SIGINT\", () => onSignal(\"SIGINT\"));\n\n return () => {\n process.off(\"uncaughtException\", onUncaught);\n process.off(\"unhandledRejection\", onUnhandled);\n process.stdout.off(\"error\", stdoutError);\n };\n}\n\n/**\n * Test helper — verify the log file path exists (and optionally has a\n * non-empty body). Returns the resolved path or null.\n */\nexport async function findLatestLogFile(binaryName: string): Promise<string | null> {\n try {\n const entries = await fsp.readdir(logsDir());\n const matching = entries.filter((e) => e.startsWith(`mcp-${binaryName}-`));\n if (matching.length === 0) return null;\n matching.sort();\n return path.join(logsDir(), matching[matching.length - 1]);\n } catch {\n return null;\n }\n}\n","/**\n * Per-tool-call request-id pin synthesis.\n *\n * Tempo's factory-mcp and docs-mcp expose `onRequestStart(id)` /\n * `onRequestEnd(id)` lifecycle hooks that the in-app SessionManager fires at\n * AI request boundaries (so all tool calls in one AI turn see a coherent\n * (orgId, boardId) snapshot). Standalone MCP hosts (Claude Code, Codex CLI)\n * don't expose those boundaries — they just dispatch `tools/call` requests\n * over JSON-RPC.\n *\n * This interceptor synthesizes a pin per tool-call:\n *\n * 1. Wraps `server.server.setRequestHandler` so we observe `tools/call`.\n * 2. Before the underlying handler runs, fire `onRequestStart(uuid)`.\n * 3. In `finally`, fire `onRequestEnd(uuid)`.\n *\n * stdio MCP servers process JSON-RPC requests serially per spec, so the\n * `RequestSnapshotMap.size <= 1` invariant in factory-mcp continues to hold.\n *\n * The interceptor must be installed AFTER all tools are registered (because\n * `McpServer.tool()` registers handlers internally, and we need the final\n * `tools/call` handler to wrap).\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\n\nexport interface PinLifecycle {\n onRequestStart: (requestId: string) => void;\n onRequestEnd: (requestId: string) => void;\n}\n\nexport interface InstallPinInterceptorOptions {\n /** The factory/docs MCP instance providing the lifecycle hooks. */\n lifecycle: PinLifecycle;\n /** Optional fixed prefix on synthesized request ids — useful in logs. */\n idPrefix?: string;\n}\n\n/**\n * Install the per-tool-call pin interceptor. Returns an `uninstall` fn for\n * tests or hot-reload scenarios.\n *\n * Implementation note: the MCP SDK exposes a `server` (the underlying\n * Server instance) under `.server` on `McpServer`. We intercept its\n * `setRequestHandler` by wrapping the `tools/call` handler at registration\n * time. Because `McpServer.tool()` registers `tools/call` itself, we need\n * to install the interceptor *after* that registration — which means\n * doing it just before `server.connect(transport)` in the binary\n * entrypoint.\n */\nexport function installPinInterceptor(\n mcpServer: McpServer,\n opts: InstallPinInterceptorOptions,\n): () => void {\n // McpServer wraps an underlying Server from @modelcontextprotocol/sdk.\n // The `.server` accessor surfaces the underlying Server (typed as `unknown`\n // here to avoid a hard dep on the SDK's internal types).\n const underlying = (mcpServer as unknown as { server: ServerLike }).server;\n if (!underlying || typeof underlying.setRequestHandler !== \"function\") {\n throw new Error(\n \"installPinInterceptor: McpServer has no .server.setRequestHandler — SDK shape changed?\",\n );\n }\n\n // Capture the current `tools/call` handler then re-register a wrapped one.\n // The SDK keeps handlers in a Map keyed by method; setting the same key\n // overrides. We pull the existing handler via the request-handlers map\n // exposed (in SDK 1.27.x) at `_requestHandlers`. If the field name shifts,\n // we fall back to a no-op that calls the wrapped handler directly.\n type Handler = (req: unknown, extra: unknown) => Promise<unknown>;\n const handlers = (underlying as ServerLike & { _requestHandlers?: Map<string, Handler> })\n ._requestHandlers;\n\n const original = handlers?.get(\"tools/call\");\n if (!original) {\n // No tools registered yet, or SDK shape changed. We can still attach\n // a wrapping handler — calls without a registered tool will surface\n // the SDK's normal MethodNotFound error.\n return () => {};\n }\n\n const idPrefix = opts.idPrefix ?? \"mcp\";\n const wrapped: Handler = async (req, extra) => {\n const requestId = `${idPrefix}-${randomUUID()}`;\n try {\n opts.lifecycle.onRequestStart(requestId);\n } catch {\n // Listener errors must not break the request\n }\n try {\n return await original(req, extra);\n } finally {\n try {\n opts.lifecycle.onRequestEnd(requestId);\n } catch {\n // Listener errors must not break the request\n }\n }\n };\n\n handlers!.set(\"tools/call\", wrapped);\n\n return () => {\n if (handlers?.get(\"tools/call\") === wrapped) {\n handlers.set(\"tools/call\", original);\n }\n };\n}\n\n/**\n * Minimal type for the underlying SDK Server we touch — kept narrow so the\n * SDK's full type surface doesn't leak into mcp-runtime consumers.\n */\ninterface ServerLike {\n setRequestHandler: unknown;\n // The actual handler map; existence is verified at runtime.\n _requestHandlers?: Map<\n string,\n (req: unknown, extra: unknown) => Promise<unknown>\n >;\n}\n","/**\n * Environment + workspace resolution for `tempo-mcp serve`.\n *\n * Convex endpoints default to production so `npx @tempo-ai/mcp` works with\n * zero configuration; TEMPO_CONVEX_URL / TEMPO_CONVEX_SITE_URL override\n * for previews and staging.\n */\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport const DEFAULT_CONVEX_URL = \"https://greedy-jackal-526.convex.cloud\";\n\nexport function resolveConvexUrl(): string {\n return process.env.TEMPO_CONVEX_URL ?? DEFAULT_CONVEX_URL;\n}\n\nexport function resolveConvexSiteUrl(convexUrl: string): string {\n const explicit = process.env.TEMPO_CONVEX_SITE_URL;\n if (explicit) return explicit;\n return convexUrl.replace(/\\.convex\\.cloud$/, \".convex.site\");\n}\n\n/**\n * How long the first protected tool call waits for the browser sign-in to\n * land before returning the link to the host (TEM-2313). Long enough for\n * a returning user to click \"Continue\" so the original call completes in\n * place; short enough to stay under hosts' tool-call timeouts.\n * `TEMPO_MCP_SIGN_IN_WAIT_MS` overrides it (hosts with tighter timeouts,\n * e2e).\n */\nexport const DEFAULT_SIGN_IN_WAIT_MS = 120_000;\n\nexport function resolveSignInWaitMs(): number {\n const raw = Number(process.env.TEMPO_MCP_SIGN_IN_WAIT_MS);\n return Number.isFinite(raw) && raw >= 0 ? raw : DEFAULT_SIGN_IN_WAIT_MS;\n}\n\nexport const ALL_TOOLSETS = [\n \"design-context\",\n \"issues\",\n \"docs\",\n \"comments\",\n \"agents\",\n \"scripts\",\n \"slack\",\n \"linear\",\n \"canvas\",\n] as const;\n\nexport type ToolsetId = (typeof ALL_TOOLSETS)[number];\n\nexport interface ServeOptions {\n toolsets: ToolsetId[];\n readonly: boolean;\n}\n\n/**\n * NOTE: org/project scope is deliberately NOT configurable here — the AI\n * passes an explicit `orgId`/`projectId` on every scoped tool call\n * (discovered via tempo_list_orgs / tempo_list_projects). The only flags\n * are capability-shaping: which toolsets, and readonly.\n */\nexport function parseServeArgs(\n args: string[],\n): ServeOptions | { error: string } {\n let toolsets: ToolsetId[] = [...ALL_TOOLSETS];\n let readonly = process.env.TEMPO_MCP_READONLY === \"1\";\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (arg === \"--readonly\") {\n readonly = true;\n } else if (arg === \"--toolsets\") {\n const value = args[++i];\n if (!value)\n return { error: \"--toolsets requires a comma-separated list\" };\n const requested = value\n .split(\",\")\n .map((t) => t.trim())\n .filter(Boolean);\n const unknown = requested.filter(\n (t) => !ALL_TOOLSETS.includes(t as ToolsetId),\n );\n if (unknown.length) {\n return {\n error: `Unknown toolset(s): ${unknown.join(\", \")}. Valid: ${ALL_TOOLSETS.join(\", \")}`,\n };\n }\n toolsets = requested as ToolsetId[];\n } else {\n return { error: `Unknown flag: ${arg}` };\n }\n }\n return { toolsets, readonly };\n}\n\nexport interface DiscoveredWorkspace {\n workspaceRoot: string;\n canvasesDir: string;\n}\n\n/**\n * Walk upward from `startDir` looking for `tempo/tempo.config.json` — the\n * marker of a Tempo-canvas-enabled repo. The canvas toolset registers only\n * when this resolves; in any other cwd the canvas tools simply don't exist.\n */\nexport function discoverWorkspace(\n startDir = process.cwd(),\n): DiscoveredWorkspace | null {\n let dir = path.resolve(startDir);\n for (;;) {\n const configPath = path.join(dir, \"tempo\", \"tempo.config.json\");\n if (fs.existsSync(configPath)) {\n let canvasesRel = \"./designs\";\n try {\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as {\n paths?: { canvases?: string };\n };\n if (parsed.paths?.canvases) canvasesRel = parsed.paths.canvases;\n } catch {\n // Unparseable config — fall back to the conventional location.\n }\n return {\n workspaceRoot: dir,\n canvasesDir: path.resolve(dir, \"tempo\", canvasesRel),\n };\n }\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n","/**\n * `tempo-mcp login` — open the browser, wait for the user to sign in, store\n * credentials in ~/.tempo/auth.json. Reuses the same localhost-callback\n * primitive that the Electron app uses (web-auth Callback.tsx POSTs to\n * `127.0.0.1:{port}/tempo/auth`).\n *\n * Signing in ahead of time is optional since TEM-2313: the MCP server\n * prompts for sign-in on the first tool call that needs it. `login` stays\n * as the explicit path (and is what short-lived subcommands run in a\n * terminal when they find no session).\n *\n * Returns an exit code — see `whoami.ts` for the rationale.\n */\n\nimport {\n startBrowserAuthFlow,\n openInBrowser,\n readAuth,\n} from \"@tempo-modules/mcp-runtime\";\n\n/**\n * Print the sign-in link (opening the browser when possible) and block\n * until the sign-in lands. Shared by `login` and by every short-lived\n * subcommand that needs a session in an interactive terminal.\n */\nexport async function signInInteractively(): Promise<number> {\n // TEMPO_AUTH_URL redirects the sign-in page (previews, e2e).\n const handle = await startBrowserAuthFlow({\n authUrl: process.env.TEMPO_AUTH_URL,\n });\n const opened = await openInBrowser(handle.signInUrl);\n\n if (opened) {\n console.log(\"Opened your browser to sign in to Tempo.\");\n console.log(\"If a browser didn't open, visit this URL:\");\n } else {\n console.log(\"Sign in to Tempo by visiting:\");\n }\n console.log(` ${handle.signInUrl}`);\n console.log(\"\");\n console.log(\"Waiting for sign-in to complete...\");\n\n try {\n await handle.done;\n const stored = await readAuth();\n console.log(\"\");\n console.log(\n `Signed in as ${stored?.email ?? stored?.userId ?? \"unknown user\"}.`,\n );\n console.log(`Credentials stored in ~/.tempo/auth.json (0600).`);\n return 0;\n } catch (err) {\n console.error(\"Sign-in failed:\", err instanceof Error ? err.message : err);\n return 1;\n }\n}\n\nexport async function runLogin(): Promise<number> {\n const existing = await readAuth();\n if (existing && existing.expiresAt - Date.now() > 60_000) {\n console.log(\n `Already signed in as ${existing.email ?? existing.userId}. Run \\`tempo-mcp logout\\` first to re-authenticate.`,\n );\n return 0;\n }\n return signInInteractively();\n}\n","import { ConvexHttpClient } from \"convex/browser\";\nimport type {\n FunctionReference,\n FunctionArgs,\n FunctionReturnType,\n} from \"convex/server\";\nimport { mapConvexError, McpToolError, type McpError } from \"./errors.ts\";\n\nexport interface AuthToken {\n jwt: string;\n expiresAt: number;\n}\n\nexport interface AuthProvider {\n getToken(): Promise<AuthToken>;\n}\n\nconst MAX_RETRIES = 3;\nconst BASE_DELAY_MS = 200;\n\nexport class AuthedConvexClient {\n private client: ConvexHttpClient;\n private authProvider: AuthProvider;\n private cachedToken: AuthToken | null = null;\n private signal?: AbortSignal;\n\n constructor(opts: {\n url: string;\n authProvider: AuthProvider;\n signal?: AbortSignal;\n }) {\n this.signal = opts.signal;\n this.client = new ConvexHttpClient(\n opts.url,\n opts.signal\n ? {\n fetch: (input, init) =>\n fetch(input, { ...init, signal: opts.signal }),\n }\n : undefined,\n );\n this.authProvider = opts.authProvider;\n }\n\n async query<F extends FunctionReference<\"query\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.query(fn, args));\n }\n\n async mutation<F extends FunctionReference<\"mutation\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.mutation(fn, args));\n }\n\n async action<F extends FunctionReference<\"action\">>(\n fn: F,\n args: FunctionArgs<F>,\n ): Promise<FunctionReturnType<F>> {\n return this.callWithAuth(() => this.client.action(fn, args));\n }\n\n private async ensureAuth(): Promise<void> {\n const now = Date.now();\n if (this.cachedToken && this.cachedToken.expiresAt - now > 30_000) {\n this.client.setAuth(this.cachedToken.jwt);\n return;\n }\n const token = await this.withSignal(() => this.authProvider.getToken());\n this.cachedToken = token;\n this.client.setAuth(token.jwt);\n }\n\n private async callWithAuth<T>(fn: () => Promise<T>): Promise<T> {\n this.signal?.throwIfAborted();\n await this.ensureAuth();\n\n for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {\n try {\n return await this.withSignal(fn);\n } catch (err: unknown) {\n this.signal?.throwIfAborted();\n const mapped = mapConvexError(err);\n\n if (mapped.code === \"auth_required\" && attempt === 0) {\n this.cachedToken = null;\n await this.ensureAuth();\n continue;\n }\n\n if (mapped.code === \"internal\" && attempt < MAX_RETRIES - 1) {\n await this.withSignal(() => sleep(BASE_DELAY_MS * 2 ** attempt));\n continue;\n }\n\n throw new McpToolError(mapped);\n }\n }\n\n throw new McpToolError({\n code: \"internal\",\n message: \"Max retries exceeded\",\n });\n }\n\n /** Auth providers and transports may stall; cancellation must release callers too. */\n private withSignal<T>(run: () => Promise<T>): Promise<T> {\n const signal = this.signal;\n if (!signal) return run();\n signal.throwIfAborted();\n return new Promise<T>((resolve, reject) => {\n const onAbort = () => reject(signal.reason);\n signal.addEventListener(\"abort\", onAbort, { once: true });\n Promise.resolve()\n .then(() => {\n signal.throwIfAborted();\n return run();\n })\n .then(resolve, reject)\n .finally(() => signal.removeEventListener(\"abort\", onAbort));\n });\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","export interface McpError {\n code:\n | \"auth_required\"\n | \"forbidden\"\n | \"not_found\"\n | \"invalid_args\"\n | \"batch_too_large\"\n | \"confirmation_required\"\n | \"duplicate\"\n | \"rate_limited\"\n | \"branch_not_pushed\"\n | \"canvas_preview_capture_failed\"\n | \"internal\";\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport class McpToolError extends Error {\n public readonly error: McpError;\n constructor(error: McpError) {\n super(error.message);\n this.name = \"McpToolError\";\n this.error = error;\n }\n}\n\nexport function mapConvexError(err: unknown): McpError {\n const message =\n err instanceof Error ? err.message : String(err);\n\n if (message.includes(\"Not authenticated\") || message.includes(\"Unauthenticated\")) {\n return { code: \"auth_required\", message: \"Authentication required. Please sign in.\" };\n }\n\n if (message.includes(\"not a member\") || message.includes(\"Unauthorized\") || message.includes(\"forbidden\")) {\n return { code: \"forbidden\", message: \"You are not a member of this organization.\" };\n }\n\n if (message.includes(\"not found\") || message.includes(\"Could not find\")) {\n return { code: \"not_found\", message };\n }\n\n if (message.includes(\"rate limit\") || message.includes(\"Too many requests\")) {\n return { code: \"rate_limited\", message: \"Rate limited. Please retry after a moment.\" };\n }\n\n if (message.includes(\"Invalid argument\") || message.includes(\"Validator error\")) {\n return { code: \"invalid_args\", message };\n }\n\n return { code: \"internal\", message };\n}\n","/**\n * Shared auth entry for the SHORT-LIVED CLI subcommands (`tools`, `call`,\n * `token …`, `whoami`) — TEM-2121 / TEM-2313.\n *\n * Two ways in, ONE stored shape:\n * - `TEMPO_AUTH_TOKEN` set (headless cloud sandboxes) → exchange it at\n * `/auth/token-exchange` for a fresh Clerk session + JWT.\n * - Otherwise → the stored browser session, refreshed silently when its\n * JWT is stale. With no session at all, an interactive terminal runs\n * the browser sign-in right here (link printed, browser opened); a\n * non-interactive caller (an agent capturing stdout) fails fast with\n * the login hint instead of hanging on a link nobody can see.\n *\n * The long-running MCP server does NOT use this — it gates per tool call\n * through `auth-gate.ts` so startup never waits on auth.\n *\n * Both paths write the standard auth.json, so the refresh loop,\n * AuthProvider, and every Convex client work identically afterwards.\n */\nimport {\n AuthedConvexClient,\n type AuthProvider,\n} from \"@tempo-modules/convex-http-client-auth\";\nimport {\n createAuthProvider,\n createRefreshLoop,\n envAccessToken,\n exchangeAccessToken,\n readAuth,\n storedAuthMatchesAccessToken,\n type RefreshLoopHandle,\n} from \"@tempo-modules/mcp-runtime\";\nimport { resolveConvexSiteUrl, resolveConvexUrl } from \"./config.ts\";\nimport { signInInteractively } from \"./login.ts\";\n\n/** Thrown when no session exists and none can be established here. */\nexport class NotSignedInError extends Error {\n constructor() {\n super(\n \"Not signed in to Tempo. Run `npx -y @tempo-ai/mcp login` in a terminal, or set TEMPO_AUTH_TOKEN for headless environments.\",\n );\n this.name = \"NotSignedInError\";\n }\n}\n\n/**\n * Ensure usable credentials exist, preferring the env token when set.\n *\n * With `TEMPO_AUTH_TOKEN` present the env token is AUTHORITATIVE: cached\n * credentials are only reused when their fingerprint proves they came from\n * this exact token. A cache left by a browser login or a DIFFERENT token\n * (shared cloud home dirs) is re-exchanged, never silently run as the\n * other user. And we never fall back to an interactive browser flow that\n * can't complete in a sandbox.\n */\nexport async function ensureCliAuth(): Promise<void> {\n const accessToken = envAccessToken();\n const convexSiteUrl = resolveConvexSiteUrl(resolveConvexUrl());\n if (!accessToken) {\n // Cold start with a stale JWT but a live session (the normal state —\n // hosts relaunch long after the short-lived JWT expired): refresh\n // silently via the stored sessionId FIRST. Only a genuinely\n // dead/absent session reaches the browser flow.\n const existing = await readAuth();\n if (existing && existing.expiresAt - Date.now() <= 60_000) {\n const loop = createRefreshLoop({\n convexSiteUrl,\n onReauthRequired: () => {},\n });\n await loop.refreshNow();\n }\n if (await readAuth()) return;\n if (!process.stdout.isTTY) throw new NotSignedInError();\n if ((await signInInteractively()) !== 0) throw new NotSignedInError();\n return;\n }\n const existing = await readAuth();\n if (\n existing &&\n storedAuthMatchesAccessToken(existing, accessToken) &&\n existing.expiresAt - Date.now() > 60_000\n ) {\n return;\n }\n await exchangeAccessToken({ convexSiteUrl, accessToken });\n}\n\n/**\n * The refresh loop's headless recovery hook: re-exchange the env token\n * when the current session dies mid-run. No env token → no recovery\n * (returns false and the loop surfaces reauth-required as before).\n */\nexport function envTokenReauth(convexSiteUrl: string): () => Promise<boolean> {\n return async () => {\n const accessToken = envAccessToken();\n if (!accessToken) return false;\n await exchangeAccessToken({ convexSiteUrl, accessToken });\n return true;\n };\n}\n\nexport interface CliConvexHandle {\n convex: AuthedConvexClient;\n authProvider: AuthProvider;\n dispose: () => void;\n}\n\n/**\n * Auth-wired Convex client for short-lived subcommands (`token …`). The\n * caller must `dispose()` so the refresh timer doesn't hold the process\n * open.\n */\nexport function createCliConvex(): CliConvexHandle {\n const convexUrl = resolveConvexUrl();\n const convexSiteUrl = resolveConvexSiteUrl(convexUrl);\n const refreshLoop: RefreshLoopHandle = createRefreshLoop({\n convexSiteUrl,\n attemptReauth: envTokenReauth(convexSiteUrl),\n onReauthRequired: () => {\n console.error(\n \"[tempo-mcp] Tempo session expired. Run `npx -y @tempo-ai/mcp login` to sign in again.\",\n );\n },\n });\n refreshLoop.start();\n const authProvider = createAuthProvider(refreshLoop);\n const convex = new AuthedConvexClient({ url: convexUrl, authProvider });\n return { convex, authProvider, dispose: () => refreshLoop.stop() };\n}\n"],"mappings":";;;AAaA,OAAO,QAAQ;AACf,OAAO,SAAS;AAChB,OAAOA,WAAU;;;ACJjB,OAAO,QAAQ;AACf,OAAO,UAAU;AAEV,SAAS,YAAoB;AAClC,SAAO,QAAQ,IAAI,kBAAkB,KAAK,KAAK,GAAG,QAAQ,GAAG,QAAQ;AACvE;AAEO,SAAS,eAAuB;AACrC,SAAO,KAAK,KAAK,UAAU,GAAG,WAAW;AAC3C;AAEO,SAAS,UAAkB;AAChC,SAAO,KAAK,KAAK,UAAU,GAAG,MAAM;AACtC;AAEO,SAAS,YAAY,YAA4B;AACtD,QAAM,QAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AACjD,SAAO,KAAK,KAAK,QAAQ,GAAG,OAAO,UAAU,IAAI,IAAI,MAAM;AAC7D;;;ADqBA,eAAsB,WAAuC;AAC3D,MAAI;AACF,UAAM,MAAM,MAAM,IAAI,SAAS,aAAa,GAAG,MAAM;AACrD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QACE,OAAO,OAAO,UAAU,YACxB,OAAO,OAAO,cAAc,YAC5B,OAAO,OAAO,WAAW,YACzB,OAAO,OAAO,cAAc,UAC5B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,QAAO;AAC7D,WAAO;AAAA,EACT;AACF;AAOO,SAAS,eAAkC;AAChD,MAAI;AACF,UAAM,MAAM,GAAG,aAAa,aAAa,GAAG,MAAM;AAClD,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QACE,OAAO,OAAO,UAAU,YACxB,OAAO,OAAO,cAAc,YAC5B,OAAO,OAAO,WAAW,YACzB,OAAO,OAAO,cAAc,UAC5B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,UAAU,MAAiC;AAC/D,QAAM,MAAM,UAAU;AACtB,QAAM,IAAI,MAAM,KAAK,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACrD,QAAM,SAAS,aAAa;AAC5B,QAAM,MAAMC,MAAK,KAAK,KAAK,iBAAiB,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC,EAAE;AACvE,QAAM,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AACzC,QAAM,IAAI,UAAU,KAAK,MAAM,EAAE,MAAM,IAAM,CAAC;AAC9C,QAAM,IAAI,OAAO,KAAK,MAAM;AAG5B,MAAI;AACF,UAAM,IAAI,MAAM,QAAQ,GAAK;AAAA,EAC/B,QAAQ;AAAA,EAER;AACF;AAMA,eAAsB,YAA2B;AAC/C,MAAI;AACF,UAAM,IAAI,OAAO,aAAa,CAAC;AAAA,EACjC,SAAS,KAAK;AACZ,QAAK,IAA8B,SAAS,SAAU,OAAM;AAAA,EAC9D;AACF;;;AE9EO,SAAS,kBAAkB,MAA6C;AAC7E,QAAM,SAAS,KAAK,iBAAiB;AACrC,QAAM,aAAa,KAAK,gBAAgB;AACxC,QAAM,aAAa,KAAK,cAAc;AACtC,MAAI,QAA+B;AACnC,MAAI,UAAU;AAMd,iBAAe,iBAAyC;AACtD,QAAI,KAAK,eAAe;AACtB,UAAI;AACF,YAAI,MAAM,KAAK,cAAc,GAAG;AAC9B,gBAAM,QAAQ,MAAM,SAAS;AAC7B,cAAI,MAAO,QAAO,MAAM;AAAA,QAC1B;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,SAAK,iBAAiB;AACtB,WAAO;AAAA,EACT;AAEA,iBAAe,aAAqC;AAClD,UAAM,UAAU,MAAM,SAAS;AAC/B,QAAI,CAAC,SAAS;AACZ,aAAO,eAAe;AAAA,IACxB;AAEA,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,GAAG,KAAK,aAAa,iBAAiB;AAAA,UAC5D,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,WAAW,QAAQ,UAAU,CAAC;AAAA,QACvD,CAAC;AAED,YAAI,IAAI,WAAW,KAAK;AAGtB,gBAAM,UAAU;AAChB,iBAAO,eAAe;AAAA,QACxB;AAEA,YAAI,CAAC,IAAI,IAAI;AAEX,cAAI,UAAU,YAAY;AACxB,kBAAM,MAAM,aAAa,KAAK,IAAI,KAAK,OAAO,CAAC;AAC/C;AAAA,UACF;AACA,iBAAO,eAAe;AAAA,QACxB;AAEA,cAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,YAAI,CAAC,KAAK,OAAO;AACf,iBAAO,eAAe;AAAA,QACxB;AAEA,cAAM,OAAmB;AAAA,UACvB,GAAG;AAAA,UACH,OAAO,KAAK;AAAA,UACZ,WAAW,aAAa,KAAK,KAAK;AAAA,QACpC;AACA,cAAM,UAAU,IAAI;AACpB,eAAO,KAAK;AAAA,MACd,SAAS,KAAK;AACZ,YAAI,UAAU,YAAY;AACxB,gBAAM,MAAM,aAAa,KAAK,IAAI,KAAK,OAAO,CAAC;AAC/C;AAAA,QACF;AAGA,eAAO,eAAe;AAAA,MACxB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAEA,WAAS,SAAS,MAAkB;AAClC,QAAI,MAAO,cAAa,KAAK;AAC7B,QAAI,QAAS;AACb,UAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,YAAY,KAAK,IAAI,IAAI,MAAM;AAC9D,YAAQ,WAAW,MAAM;AACvB,WAAK,WAAW,EAAE,KAAK,CAAC,aAAa;AACnC,YAAI,YAAY,CAAC,SAAS;AACxB,eAAK,SAAS,EAAE,KAAK,CAAC,MAAM;AAC1B,gBAAI,EAAG,UAAS,CAAC;AAAA,UACnB,CAAC;AAAA,QACH;AAAA,MACF,CAAC;AAAA,IACH,GAAG,KAAK;AAAA,EACV;AAEA,WAAS,QAAQ;AACf,cAAU;AACV,SAAK,SAAS,EAAE,KAAK,CAAC,SAAS;AAC7B,UAAI,KAAM,UAAS,IAAI;AAAA,IACzB,CAAC;AAAA,EACH;AAEA,WAAS,OAAO;AACd,cAAU;AACV,QAAI,OAAO;AACT,mBAAa,KAAK;AAClB,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,MAAM,WAAW;AACnC;AAEA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;AAOO,SAAS,aAAa,KAAqB;AAChD,MAAI;AACF,UAAM,CAAC,EAAE,OAAO,IAAI,IAAI,MAAM,GAAG;AACjC,UAAM,UAAU,KAAK,MAAM,OAAO,KAAK,SAAS,QAAQ,EAAE,SAAS,MAAM,CAAC;AAC1E,QAAI,OAAO,QAAQ,QAAQ,SAAU,QAAO,QAAQ,MAAM;AAAA,EAC5D,QAAQ;AAAA,EAER;AACA,SAAO,KAAK,IAAI,IAAI,IAAI;AAC1B;;;AC5JO,SAAS,mBACd,aACc;AACd,SAAO;AAAA,IACL,MAAM,WAA+B;AACnC,YAAM,OAAO,MAAM,SAAS;AAC5B,UAAI,CAAC,MAAM;AACT,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAIA,UAAI,KAAK,YAAY,KAAK,IAAI,IAAI,KAAQ;AACxC,cAAM,WAAW,MAAM,YAAY,WAAW;AAC9C,YAAI,UAAU;AACZ,gBAAM,QAAQ,MAAM,SAAS;AAC7B,cAAI,OAAO;AACT,mBAAO,EAAE,KAAK,MAAM,OAAO,WAAW,MAAM,UAAU;AAAA,UACxD;AAAA,QACF;AACA,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AACA,aAAO,EAAE,KAAK,KAAK,OAAO,WAAW,KAAK,UAAU;AAAA,IACtD;AAAA,EACF;AACF;;;AClCA,OAAO,UAAU;AA2CjB,eAAsB,qBACpB,OAA2B,CAAC,GACA;AAC5B,QAAM,UAAU,KAAK,WAAW;AAChC,QAAM,YAAY,KAAK,aAAa,KAAK;AACzC,QAAM,cAAc,KAAK,eAAe;AAExC,MAAI;AACJ,MAAI;AACJ,QAAM,OAAO,IAAI,QAAc,CAAC,SAAS,WAAW;AAClD,kBAAc;AACd,iBAAa;AAAA,EACf,CAAC;AAED,QAAM,SAAS,KAAK,aAAa,OAAO,KAAK,QAAQ;AACnD,QACE,IAAI,WAAW,UACf,IAAI,OACJ,IAAI,IAAI,WAAW,aAAa,GAChC;AACA,UAAI;AACF,cAAM,OAAO,MAAM,SAAS,GAAG;AAC/B,YACE,CAAC,QACD,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,cAAc,YAC1B,OAAO,KAAK,WAAW,UACvB;AACA,cAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,cAAI,IAAI,KAAK,UAAU,EAAE,OAAO,iBAAiB,CAAC,CAAC;AACnD;AAAA,QACF;AAEA,cAAM,UAAU;AAAA,UACd,OAAO,KAAK;AAAA,UACZ,WAAW,KAAK;AAAA,UAChB,QAAQ,KAAK;AAAA,UACb,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,UACrD,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAAA,UACjE,UAAU,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AAAA,UAC9D,WAAW,aAAa,KAAK,KAAK;AAAA,UAClC,WAAW,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,QAC3D,CAAC;AAED,YAAI,UAAU,KAAK;AAAA,UACjB,gBAAgB;AAAA,UAChB,+BAA+B;AAAA,QACjC,CAAC;AACD,YAAI,IAAI,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC,CAAC;AAEpC,qBAAa,MAAM;AACjB,iBAAO,MAAM;AACb,sBAAY;AAAA,QACd,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,OAAO,GAAG,EAAE,CAAC,CAAC;AAAA,MAChD;AACA;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,WAAW;AAE5B,UAAI,UAAU,KAAK;AAAA,QACjB,+BAA+B;AAAA,QAC/B,gCAAgC;AAAA,QAChC,gCAAgC;AAAA,MAClC,CAAC;AACD,UAAI,IAAI;AACR;AAAA,IACF;AAEA,QAAI,UAAU,GAAG;AACjB,QAAI,IAAI;AAAA,EACV,CAAC;AAED,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,KAAK,QAAQ,GAAG,aAAa,MAAM,QAAQ,CAAC;AAAA,EAC5D,CAAC;AAED,QAAM,UAAU,OAAO,QAAQ;AAC/B,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,WAAO,MAAM;AACb,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AACA,QAAM,eAAe,QAAQ;AAI7B,QAAM,YAAY,GAAG,OAAO,yBAAyB,YAAY;AAEjE,MAAI,OAAO,SAAS,SAAS,GAAG;AAC9B,UAAM,eAAe,WAAW,MAAM;AACpC,aAAO,MAAM;AACb,iBAAW,IAAI,MAAM,yBAAyB,SAAS,IAAI,CAAC;AAAA,IAC9D,GAAG,SAAS;AAEZ,QAAI,OAAO,aAAa,UAAU,WAAY,cAAa,MAAM;AAMjE,SAAK,QAAQ,MAAM,aAAa,YAAY,CAAC,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,MAAM;AACZ,aAAO,MAAM;AACb,iBAAW,IAAI,MAAM,mBAAmB,CAAC;AAAA,IAC3C;AAAA,EACF;AACF;AAEA,SAAS,SAAS,KAAoE;AACpF,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,QAAI,MAAM;AACV,QAAI,YAAY,MAAM;AACtB,QAAI,GAAG,QAAQ,CAAC,UAAU;AACxB,aAAO;AACP,UAAI,IAAI,SAAS,KAAW;AAG1B,YAAI,QAAQ;AACZ,eAAO,IAAI,MAAM,mBAAmB,CAAC;AAAA,MACvC;AAAA,IACF,CAAC;AACD,QAAI,GAAG,OAAO,MAAM;AAClB,UAAI,CAAC,IAAK,QAAO,QAAQ,IAAI;AAC7B,UAAI;AACF,gBAAQ,KAAK,MAAM,GAAG,CAA4B;AAAA,MACpD,SAAS,KAAK;AACZ,eAAO,GAAG;AAAA,MACZ;AAAA,IACF,CAAC;AACD,QAAI,GAAG,SAAS,MAAM;AAAA,EACxB,CAAC;AACH;;;ACjMA,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,IAAM,YAAY,UAAU,IAAI;AAEzB,SAAS,0BAAmC;AACjD,SAAO,QAAQ,IAAI,yBAAyB;AAC9C;AAEA,eAAsB,cAAc,KAA+B;AACjE,MAAI,wBAAwB,EAAG,QAAO;AACtC,QAAM,MACJ,QAAQ,aAAa,WACjB,SAAS,GAAG,MACZ,QAAQ,aAAa,UACnB,aAAa,GAAG,MAChB,aAAa,GAAG;AACxB,MAAI;AACF,UAAM,UAAU,KAAK,EAAE,aAAa,KAAK,CAAC;AAC1C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACmBO,SAAS,mBACd,UAA+B,CAAC,GAClB;AACd,QAAM,cAAc,QAAQ,eAAe;AAC3C,MAAI,UAAuB;AAE3B,QAAM,iBAAiB,MAAY;AACjC,QAAI,CAAC,WAAW,QAAQ,OAAO;AAC7B,YAAM,OAAa;AAAA,QACjB,QAAQ,qBAAqB;AAAA,UAC3B,SAAS,QAAQ;AAAA,UACjB,WAAW;AAAA,QACb,CAAC;AAAA,QACD,eAAe;AAAA,QACf,OAAO;AAAA,MACT;AACA,WAAK,OACF,KAAK,CAAC,WAAW,OAAO,IAAI,EAC5B,MAAM,MAAM;AAAA,MAAC,CAAC,EACd,QAAQ,MAAM;AACb,aAAK,QAAQ;AAAA,MACf,CAAC;AACH,gBAAU;AAAA,IACZ;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,MAAM,OAAO,QAAQ;AACnB,iBAAS;AACP,YAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,cAAM,OAAO,eAAe;AAC5B,cAAM,SAAS,MAAM,KAAK;AAC1B,YAAI,CAAC,KAAK,cAAe,MAAK,gBAAgB,YAAY,OAAO,SAAS;AAC1E,cAAM,SAAS,MAAM,KAAK;AAC1B,cAAM,SAAS,MAAM,QAAQ,KAAK;AAAA,UAChC,OAAO,KAAK;AAAA,YACV,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,UACAC,OAAM,MAAM,EAAE,KAAK,MAAM,KAAK;AAAA,QAChC,CAAC;AACD,YAAI,CAAC,OAAQ,QAAO,EAAE,WAAW,OAAO,WAAW,eAAe,OAAO;AAKzE,aAAK,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU;AACR,UAAI,CAAC,WAAW,QAAQ,MAAO;AAC/B,WAAK,QAAQ,OAAO,KAAK,CAAC,WAAW,OAAO,OAAO,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACtE;AAAA,EACF;AACF;AAEA,SAASA,OAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,WAAW,SAAS,EAAE;AACpC,QAAI,OAAO,MAAM,UAAU,WAAY,OAAM,MAAM;AAAA,EACrD,CAAC;AACH;;;AClGA,SAAS,kBAAkB;AAIpB,IAAM,gBAAgB;AAGtB,SAAS,iBAAgC;AAC9C,QAAM,MAAM,QAAQ,IAAI,aAAa,GAAG,KAAK;AAC7C,SAAO,MAAM,MAAM;AACrB;AAQO,SAAS,uBAAuB,aAA6B;AAClE,SAAO,WAAW,QAAQ,EAAE,OAAO,WAAW,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC3E;AAOO,SAAS,6BACd,QACA,aACS;AACT,SAAO,OAAO,2BAA2B,uBAAuB,WAAW;AAC7E;AAEO,IAAM,qBAAN,cAAiC,MAAM;AAAA,EAC5C,YACE,SAEgB,MAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAmBA,eAAsB,oBACpB,MACqB;AACrB,QAAM,aAAa,KAAK,cAAc;AACtC,QAAM,aAAa,KAAK,gBAAgB;AAExC,MAAI,gBAAwB;AAC5B,WAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,MAAM,GAAG,KAAK,aAAa,wBAAwB;AAAA,QAC7D,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU,EAAE,OAAO,KAAK,YAAY,CAAC;AAAA,MAClD,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,sBAAgB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAI,UAAU,YAAY;AACxB,cAAMC,OAAM,aAAa,KAAK,IAAI,KAAK,OAAO,CAAC;AAC/C;AAAA,MACF;AACA;AAAA,IACF;AAEA,QAAI,IAAI,WAAW,KAAK;AACtB,YAAM,IAAI;AAAA,QACR,GAAG,aAAa;AAAA,QAChB;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAC,IAAI,IAAI;AACX,sBAAgB,2BAA2B,IAAI,MAAM;AACrD,UAAI,UAAU,YAAY;AACxB,cAAMA,OAAM,aAAa,KAAK,IAAI,KAAK,OAAO,CAAC;AAC/C;AAAA,MACF;AACA;AAAA,IACF;AAEA,UAAM,OAAQ,MAAM,IAAI,KAAK;AAK7B,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,aAAa,CAAC,KAAK,QAAQ;AAClD,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAqB;AAAA,MACzB,OAAO,KAAK;AAAA,MACZ,WAAW,KAAK;AAAA,MAChB,QAAQ,KAAK;AAAA,MACb,OAAO,eAAe,KAAK,KAAK;AAAA,MAChC,WAAW,aAAa,KAAK,KAAK;AAAA,MAClC,wBAAwB,uBAAuB,KAAK,WAAW;AAAA,IACjE;AACA,UAAM,UAAU,MAAM;AACtB,WAAO;AAAA,EACT;AAEA,QAAM,IAAI;AAAA,IACR,gCAAgC,aAAa;AAAA,IAC7C;AAAA,EACF;AACF;AAGA,SAAS,eAAe,KAAiC;AACvD,MAAI;AACF,UAAM,CAAC,EAAE,OAAO,IAAI,IAAI,MAAM,GAAG;AACjC,UAAM,UAAU,KAAK;AAAA,MACnB,OAAO,KAAK,SAAS,QAAQ,EAAE,SAAS,MAAM;AAAA,IAChD;AACA,WAAO,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAASA,OAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;;;AC7IA,SAAS,kBAAkB;AAC3B,SAAS,cAAc;AAEvB,SAAS,cAAc;AACvB,SAAS,yBAAyB;AAElC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAKK;AAGP,IAAM,0BAA0B,KAAK,KAAK;AA2F1C,eAAsB,sBACpB,SAC0B;AAC1B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,YAAgC,CAAC;AACvC,aAAW,WAAW,UAAU;AAC9B,UAAM,CAAC,iBAAiB,eAAe,IACrC,kBAAkB,iBAAiB;AACrC,UAAM,SAAS,IAAI,OAAO;AAAA,MACxB,MAAM,GAAG,IAAI;AAAA,MACb;AAAA,IACF,CAAC;AACD,UAAM,QAAQ,OAAO,QAAQ,eAAe;AAC5C,UAAM,OAAO,QAAQ,eAAe;AACpC,cAAU,KAAK,EAAE,GAAG,SAAS,OAAO,CAAC;AAAA,EACvC;AAKA,QAAM,aAAa,oBAAI,IAA8B;AACrD,QAAM,iBAAiB,oBAAI,IAAkC;AAC7D,QAAM,iBAAiB,oBAAI,IAAoB;AAC/C,aAAW,WAAW,WAAW;AAC/B,QAAI,CAAC,QAAQ,OAAO,sBAAsB,GAAG,OAAO;AAClD,qBAAe,IAAI,QAAQ,SAAS,CAAC,CAAC;AACtC;AAAA,IACF;AACA,UAAM,SAAS,MAAM,QAAQ,OAAO,UAAU;AAC9C,UAAM,OAAe,CAAC;AACtB,eAAW,cAAc,OAAO,OAAO;AACrC,YAAM,QAAQ,QAAQ,YAAY,WAAW,IAAI;AACjD,YAAM,cAAc,QAAQ,kBAAkB,WAAW,IAAI;AAC7D,YAAM,OAAO;AAAA,QACX,GAAG;AAAA,QACH,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;AAAA,QACzB,GAAI,cACA,EAAE,aAAa,EAAE,GAAG,WAAW,aAAa,GAAG,YAAY,EAAE,IAC7D,CAAC;AAAA,MACP;AACA,UAAI,cAAc,CAAC,WAAW,QAAQ,SAAS,KAAK,IAAI,EAAG;AAC3D,YAAM,WAAW,WAAW,IAAI,KAAK,IAAI;AACzC,UAAI,UAAU;AACZ,cAAM,IAAI;AAAA,UACR,uCAAuC,KAAK,IAAI,yBAAyB,SAAS,OAAO,UAAU,QAAQ,OAAO;AAAA,QACpH;AAAA,MACF;AACA,iBAAW,IAAI,KAAK,MAAM,OAAO;AACjC,YAAM,WAAW,eAAe,QAAQ,SAAS,KAAK,IAAI,KAAK,CAAC;AAChE,UAAI,SAAS,WAAW,GAAG;AACzB,aAAK,KAAK,IAAI;AACd;AAAA,MACF;AACA,qBAAe,IAAI,KAAK,MAAM,QAAQ;AACtC,YAAM,SAAS,KAAK,eAAe,EAAE,MAAM,SAAkB;AAC7D,WAAK,KAAK;AAAA,QACR,GAAG;AAAA,QACH,aAAa;AAAA,UACX,GAAG;AAAA,UACH,MAAM;AAAA,UACN,YAAY;AAAA,YACV,GAAI,OAAO,cAAc,CAAC;AAAA,YAC1B,GAAG,OAAO;AAAA,cACR,SAAS,IAAI,CAAC,UAAU;AAAA,gBACtB,MAAM;AAAA,gBACN,EAAE,MAAM,UAAU,aAAa,MAAM,YAAY;AAAA,cACnD,CAAC;AAAA,YACH;AAAA,UACF;AAAA,UACA,UAAU;AAAA,YACR,GAAG,oBAAI,IAAI;AAAA,cACT,GAAK,OAAO,YAAqC,CAAC;AAAA,cAClD,GAAG,SAAS,IAAI,CAAC,UAAU,MAAM,IAAI;AAAA,YACvC,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AACA,mBAAe,IAAI,QAAQ,SAAS,IAAI;AAAA,EAC1C;AAEA,QAAM,SAAS,IAAI;AAAA,IACjB,EAAE,MAAM,SAAS,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI,CAAC,EAAG;AAAA,IACpE;AAAA,MACE,cAAc,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,EAAE;AAAA,MACzC;AAAA,IACF;AAAA,EACF;AAEA,SAAO,kBAAkB,wBAAwB,aAAa;AAAA,IAC5D,OAAO,UAAU;AAAA,MACf,CAAC,YAAY,eAAe,IAAI,QAAQ,OAAO,KAAK,CAAC;AAAA,IACvD;AAAA,EACF,EAAE;AAGF,MAAI,YAA2B,QAAQ,QAAQ;AAE/C,SAAO,kBAAkB,uBAAuB,OAAO,SAAS,UAAU;AACxE,UAAM,WAAW,QAAQ,OAAO;AAChC,UAAM,UAAU,WAAW,IAAI,QAAQ;AACvC,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,QACL,SAAS;AAAA,QACT,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,MAAM,KAAK,UAAU;AAAA,cACnB,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,SAAS,6BAA6B,QAAQ;AAAA,cAChD;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,UAAM,UAAU,YAAY,IAAI;AAChC,QAAI;AACJ,QAAI;AACF,uBAAiB,OAAO,YAAqC;AAC3D,YAAI,UAAU;AACZ,gBAAM,QAAQ,MAAM,SAAS,QAAQ,SAAS,QAAQ;AACtD,cAAI,MAAO,QAAO;AAAA,QACpB;AAIA,cAAM,OAAgC;AAAA,UACpC,GAAI,QAAQ,OAAO,aAAa,CAAC;AAAA,QACnC;AACA,cAAM,WAAW,eAAe,IAAI,QAAQ,KAAK,CAAC;AAClD,cAAM,cAAsC,CAAC;AAC7C,mBAAW,SAAS,UAAU;AAC5B,gBAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,iBAAO,KAAK,MAAM,IAAI;AACtB,cAAI,OAAO,UAAU,YAAY,MAAM,WAAW,GAAG;AACnD,mBAAO;AAAA,cACL,SAAS;AAAA,cACT,SAAS;AAAA,gBACP;AAAA,kBACE,MAAM;AAAA,kBACN,MAAM,KAAK,UAAU;AAAA,oBACnB,OAAO;AAAA,sBACL,MAAM;AAAA,sBACN,SAAS,IAAI,QAAQ,eAAe,MAAM,IAAI,MAAM,MAAM,WAAW;AAAA,oBACvE;AAAA,kBACF,CAAC;AAAA,gBACH;AAAA,cACF;AAAA,YACF;AAAA,UACF;AACA,sBAAY,MAAM,IAAI,IAAI;AAAA,QAC5B;AASA,cAAM,MAAM,UAAU,KAAK,YAAqC;AAC9D,gBAAM,OAAO,eAAe;AAC5B,cAAI,SAAS,SAAS,EAAG,gBAAe,WAAW;AAEnD,gBAAM,YAAY,WAAW;AAC7B,kBAAQ,iBAAiB,SAAS;AAClC,cAAI;AACF,mBAAQ,MAAM,QAAQ,OAAO;AAAA,cAC3B,EAAE,MAAM,UAAU,WAAW,KAAK;AAAA,cAClC;AAAA,cACA;AAAA,gBACE,SAAS;AAAA,gBACT,QAAQ,QAAQ,sBAAsB,MAAM,SAAS;AAAA,cACvD;AAAA,YACF;AAAA,UACF,UAAE;AACA,oBAAQ,eAAe,SAAS;AAAA,UAClC;AAAA,QACF,CAAC;AACD,oBAAY,IAAI;AAAA,UACd,MAAM;AAAA,UACN,MAAM;AAAA,QACR;AACA,eAAO,MAAM;AAAA,MACf,GAAG;AACH,aAAO;AAAA,IACT,UAAE;AACA,UAAI;AACF,gBAAQ,qBAAqB;AAAA,UAC3B,MAAM;AAAA,UACN,SAAS,QAAQ;AAAA,UACjB,MAAM,QAAQ,OAAO,aAAa,CAAC;AAAA,UACnC,QAAQ;AAAA,UACR,YAAY,KAAK,MAAM,YAAY,IAAI,IAAI,OAAO;AAAA,QACpD,CAAC;AAAA,MACH,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF,CAAC;AAMD,QAAM,0BAA0B,OAC9B,YACwB;AACxB,UAAM,OAAO,QAAQ,OAAO,sBAAsB;AAClD,QAAI,CAAC,MAAM,UAAW,QAAO,CAAC;AAC9B,UAAM,SAAS,MAAM,QAAQ,OAAO,cAAc;AAClD,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO,kBAAkB,4BAA4B,YAAY;AAC/D,UAAM,MAAM,MAAM,QAAQ,IAAI,UAAU,IAAI,uBAAuB,CAAC;AACpE,WAAO,EAAE,WAAW,IAAI,KAAK,EAAE;AAAA,EACjC,CAAC;AAED,SAAO,kBAAkB,2BAA2B,OAAO,YAAY;AACrE,UAAM,MAAM,QAAQ,OAAO;AAC3B,eAAW,WAAW,WAAW;AAC/B,YAAM,YAAY,MAAM,wBAAwB,OAAO;AACvD,UAAI,UAAU,KAAK,CAAC,aAAa,SAAS,QAAQ,GAAG,GAAG;AACtD,eAAO,QAAQ,OAAO,aAAa,EAAE,IAAI,CAAC;AAAA,MAC5C;AAAA,IACF;AACA,UAAM,IAAI,MAAM,qBAAqB,GAAG,EAAE;AAAA,EAC5C,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,SAAS,CAAC,cAAc,OAAO,QAAQ,SAAS;AAAA,IAChD,SAAS,YAAY;AACnB,iBAAW,WAAW,WAAW;AAC/B,cAAM,QAAQ,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAC3C,gBAAQ,UAAU;AAAA,MACpB;AACA,YAAM,OAAO,MAAM,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IACrC;AAAA,EACF;AACF;;;ACzWA,OAAOC,SAAQ;AACf,OAAOC,UAAS;AAChB,OAAOC,WAAU;AAUV,SAAS,qBAAqB,OAA6B,CAAC,GAAe;AAChF,QAAM,aACJ,KAAK,cAAcC,MAAK,SAAS,QAAQ,KAAK,CAAC,KAAK,WAAW;AAEjE,QAAM,WAAW,CAAC,OAAe,QAAiB;AAChD,UAAM,OAAO,KAAI,oBAAI,KAAK,GAAE,YAAY,CAAC,KAAK,KAAK,KACjD,eAAe,QAAQ,IAAI,SAAS,IAAI,UAAU,OAAO,GAAG,CAC9D;AAAA;AAEA,QAAI;AACF,MAAAC,IAAG,UAAU,QAAQ,GAAG,EAAE,WAAW,MAAM,MAAM,IAAM,CAAC;AACxD,MAAAA,IAAG,eAAe,YAAY,UAAU,GAAG,MAAM,EAAE,MAAM,IAAM,CAAC;AAAA,IAClE,QAAQ;AAEN,UAAI;AACF,gBAAQ,OAAO,MAAM,IAAI;AAAA,MAC3B,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAEA,QAAM,aAAa,CAAC,QAAe,SAAS,qBAAqB,GAAG;AACpE,QAAM,cAAc,CAAC,WAAoB,SAAS,sBAAsB,MAAM;AAE9E,UAAQ,GAAG,qBAAqB,UAAU;AAC1C,UAAQ,GAAG,sBAAsB,WAAW;AAI5C,QAAM,cAAc,CAAC,QAA+B;AAClD,QAAI,IAAI,SAAS,QAAS,UAAS,gBAAgB,GAAG;AAAA,EACxD;AACA,UAAQ,OAAO,GAAG,SAAS,WAAW;AAEtC,QAAM,WAAW,CAAC,WAA2B;AAC3C,YAAQ,QAAQ,KAAK,aAAa,CAAC,EAChC,MAAM,CAAC,QAAQ,SAAS,YAAY,MAAM,IAAI,GAAG,CAAC,EAClD,QAAQ,MAAM;AAGb,iBAAW,MAAM,QAAQ,KAAK,CAAC,GAAG,EAAE;AAAA,IACtC,CAAC;AAAA,EACL;AACA,UAAQ,KAAK,WAAW,MAAM,SAAS,SAAS,CAAC;AACjD,UAAQ,KAAK,UAAU,MAAM,SAAS,QAAQ,CAAC;AAE/C,SAAO,MAAM;AACX,YAAQ,IAAI,qBAAqB,UAAU;AAC3C,YAAQ,IAAI,sBAAsB,WAAW;AAC7C,YAAQ,OAAO,IAAI,SAAS,WAAW;AAAA,EACzC;AACF;;;AC7DA,SAAS,cAAAC,mBAAkB;;;ACjB3B,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAEV,IAAM,qBAAqB;AAE3B,SAAS,mBAA2B;AACzC,SAAO,QAAQ,IAAI,oBAAoB;AACzC;AAEO,SAAS,qBAAqB,WAA2B;AAC9D,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,SAAU,QAAO;AACrB,SAAO,UAAU,QAAQ,oBAAoB,cAAc;AAC7D;AAUO,IAAM,0BAA0B;AAEhC,SAAS,sBAA8B;AAC5C,QAAM,MAAM,OAAO,QAAQ,IAAI,yBAAyB;AACxD,SAAO,OAAO,SAAS,GAAG,KAAK,OAAO,IAAI,MAAM;AAClD;AAEO,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAeO,SAAS,eACd,MACkC;AAClC,MAAI,WAAwB,CAAC,GAAG,YAAY;AAC5C,MAAI,WAAW,QAAQ,IAAI,uBAAuB;AAElD,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,cAAc;AACxB,iBAAW;AAAA,IACb,WAAW,QAAQ,cAAc;AAC/B,YAAM,QAAQ,KAAK,EAAE,CAAC;AACtB,UAAI,CAAC;AACH,eAAO,EAAE,OAAO,6CAA6C;AAC/D,YAAM,YAAY,MACf,MAAM,GAAG,EACT,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,OAAO;AACjB,YAAM,UAAU,UAAU;AAAA,QACxB,CAAC,MAAM,CAAC,aAAa,SAAS,CAAc;AAAA,MAC9C;AACA,UAAI,QAAQ,QAAQ;AAClB,eAAO;AAAA,UACL,OAAO,uBAAuB,QAAQ,KAAK,IAAI,CAAC,YAAY,aAAa,KAAK,IAAI,CAAC;AAAA,QACrF;AAAA,MACF;AACA,iBAAW;AAAA,IACb,OAAO;AACL,aAAO,EAAE,OAAO,iBAAiB,GAAG,GAAG;AAAA,IACzC;AAAA,EACF;AACA,SAAO,EAAE,UAAU,SAAS;AAC9B;AAYO,SAAS,kBACd,WAAW,QAAQ,IAAI,GACK;AAC5B,MAAI,MAAMA,MAAK,QAAQ,QAAQ;AAC/B,aAAS;AACP,UAAM,aAAaA,MAAK,KAAK,KAAK,SAAS,mBAAmB;AAC9D,QAAID,IAAG,WAAW,UAAU,GAAG;AAC7B,UAAI,cAAc;AAClB,UAAI;AACF,cAAM,SAAS,KAAK,MAAMA,IAAG,aAAa,YAAY,MAAM,CAAC;AAG7D,YAAI,OAAO,OAAO,SAAU,eAAc,OAAO,MAAM;AAAA,MACzD,QAAQ;AAAA,MAER;AACA,aAAO;AAAA,QACL,eAAe;AAAA,QACf,aAAaC,MAAK,QAAQ,KAAK,SAAS,WAAW;AAAA,MACrD;AAAA,IACF;AACA,UAAM,SAASA,MAAK,QAAQ,GAAG;AAC/B,QAAI,WAAW,IAAK,QAAO;AAC3B,UAAM;AAAA,EACR;AACF;;;AC1GA,eAAsB,sBAAuC;AAE3D,QAAM,SAAS,MAAM,qBAAqB;AAAA,IACxC,SAAS,QAAQ,IAAI;AAAA,EACvB,CAAC;AACD,QAAM,SAAS,MAAM,cAAc,OAAO,SAAS;AAEnD,MAAI,QAAQ;AACV,YAAQ,IAAI,0CAA0C;AACtD,YAAQ,IAAI,2CAA2C;AAAA,EACzD,OAAO;AACL,YAAQ,IAAI,+BAA+B;AAAA,EAC7C;AACA,UAAQ,IAAI,KAAK,OAAO,SAAS,EAAE;AACnC,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,oCAAoC;AAEhD,MAAI;AACF,UAAM,OAAO;AACb,UAAM,SAAS,MAAM,SAAS;AAC9B,YAAQ,IAAI,EAAE;AACd,YAAQ;AAAA,MACN,gBAAgB,QAAQ,SAAS,QAAQ,UAAU,cAAc;AAAA,IACnE;AACA,YAAQ,IAAI,kDAAkD;AAC9D,WAAO;AAAA,EACT,SAAS,KAAK;AACZ,YAAQ,MAAM,mBAAmB,eAAe,QAAQ,IAAI,UAAU,GAAG;AACzE,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,WAA4B;AAChD,QAAM,WAAW,MAAM,SAAS;AAChC,MAAI,YAAY,SAAS,YAAY,KAAK,IAAI,IAAI,KAAQ;AACxD,YAAQ;AAAA,MACN,wBAAwB,SAAS,SAAS,SAAS,MAAM;AAAA,IAC3D;AACA,WAAO;AAAA,EACT;AACA,SAAO,oBAAoB;AAC7B;;;AClEA,SAAS,wBAAwB;;;ACiB1B,IAAM,eAAN,cAA2B,MAAM;AAAA,EAEtC,YAAY,OAAiB;AAC3B,UAAM,MAAM,OAAO;AACnB,SAAK,OAAO;AACZ,SAAK,QAAQ;AAAA,EACf;AACF;AAEO,SAAS,eAAe,KAAwB;AACrD,QAAM,UACJ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAEjD,MAAI,QAAQ,SAAS,mBAAmB,KAAK,QAAQ,SAAS,iBAAiB,GAAG;AAChF,WAAO,EAAE,MAAM,iBAAiB,SAAS,2CAA2C;AAAA,EACtF;AAEA,MAAI,QAAQ,SAAS,cAAc,KAAK,QAAQ,SAAS,cAAc,KAAK,QAAQ,SAAS,WAAW,GAAG;AACzG,WAAO,EAAE,MAAM,aAAa,SAAS,6CAA6C;AAAA,EACpF;AAEA,MAAI,QAAQ,SAAS,WAAW,KAAK,QAAQ,SAAS,gBAAgB,GAAG;AACvE,WAAO,EAAE,MAAM,aAAa,QAAQ;AAAA,EACtC;AAEA,MAAI,QAAQ,SAAS,YAAY,KAAK,QAAQ,SAAS,mBAAmB,GAAG;AAC3E,WAAO,EAAE,MAAM,gBAAgB,SAAS,6CAA6C;AAAA,EACvF;AAEA,MAAI,QAAQ,SAAS,kBAAkB,KAAK,QAAQ,SAAS,iBAAiB,GAAG;AAC/E,WAAO,EAAE,MAAM,gBAAgB,QAAQ;AAAA,EACzC;AAEA,SAAO,EAAE,MAAM,YAAY,QAAQ;AACrC;;;ADlCA,IAAM,cAAc;AACpB,IAAM,gBAAgB;AAEf,IAAM,qBAAN,MAAyB;AAAA,EAM9B,YAAY,MAIT;AAPH,SAAQ,cAAgC;AAQtC,SAAK,SAAS,KAAK;AACnB,SAAK,SAAS,IAAI;AAAA,MAChB,KAAK;AAAA,MACL,KAAK,SACD;AAAA,QACE,OAAO,CAAC,OAAO,SACb,MAAM,OAAO,EAAE,GAAG,MAAM,QAAQ,KAAK,OAAO,CAAC;AAAA,MACjD,IACA;AAAA,IACN;AACA,SAAK,eAAe,KAAK;AAAA,EAC3B;AAAA,EAEA,MAAM,MACJ,IACA,MACgC;AAChC,WAAO,KAAK,aAAa,MAAM,KAAK,OAAO,MAAM,IAAI,IAAI,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,SACJ,IACA,MACgC;AAChC,WAAO,KAAK,aAAa,MAAM,KAAK,OAAO,SAAS,IAAI,IAAI,CAAC;AAAA,EAC/D;AAAA,EAEA,MAAM,OACJ,IACA,MACgC;AAChC,WAAO,KAAK,aAAa,MAAM,KAAK,OAAO,OAAO,IAAI,IAAI,CAAC;AAAA,EAC7D;AAAA,EAEA,MAAc,aAA4B;AACxC,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,KAAK,eAAe,KAAK,YAAY,YAAY,MAAM,KAAQ;AACjE,WAAK,OAAO,QAAQ,KAAK,YAAY,GAAG;AACxC;AAAA,IACF;AACA,UAAM,QAAQ,MAAM,KAAK,WAAW,MAAM,KAAK,aAAa,SAAS,CAAC;AACtE,SAAK,cAAc;AACnB,SAAK,OAAO,QAAQ,MAAM,GAAG;AAAA,EAC/B;AAAA,EAEA,MAAc,aAAgB,IAAkC;AAC9D,SAAK,QAAQ,eAAe;AAC5B,UAAM,KAAK,WAAW;AAEtB,aAAS,UAAU,GAAG,UAAU,aAAa,WAAW;AACtD,UAAI;AACF,eAAO,MAAM,KAAK,WAAW,EAAE;AAAA,MACjC,SAAS,KAAc;AACrB,aAAK,QAAQ,eAAe;AAC5B,cAAM,SAAS,eAAe,GAAG;AAEjC,YAAI,OAAO,SAAS,mBAAmB,YAAY,GAAG;AACpD,eAAK,cAAc;AACnB,gBAAM,KAAK,WAAW;AACtB;AAAA,QACF;AAEA,YAAI,OAAO,SAAS,cAAc,UAAU,cAAc,GAAG;AAC3D,gBAAM,KAAK,WAAW,MAAMC,OAAM,gBAAgB,KAAK,OAAO,CAAC;AAC/D;AAAA,QACF;AAEA,cAAM,IAAI,aAAa,MAAM;AAAA,MAC/B;AAAA,IACF;AAEA,UAAM,IAAI,aAAa;AAAA,MACrB,MAAM;AAAA,MACN,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA;AAAA,EAGQ,WAAc,KAAmC;AACvD,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,OAAQ,QAAO,IAAI;AACxB,WAAO,eAAe;AACtB,WAAO,IAAI,QAAW,CAAC,SAAS,WAAW;AACzC,YAAM,UAAU,MAAM,OAAO,OAAO,MAAM;AAC1C,aAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;AACxD,cAAQ,QAAQ,EACb,KAAK,MAAM;AACV,eAAO,eAAe;AACtB,eAAO,IAAI;AAAA,MACb,CAAC,EACA,KAAK,SAAS,MAAM,EACpB,QAAQ,MAAM,OAAO,oBAAoB,SAAS,OAAO,CAAC;AAAA,IAC/D,CAAC;AAAA,EACH;AACF;AAEA,SAASA,OAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;;;AE7FO,IAAM,mBAAN,cAA+B,MAAM;AAAA,EAC1C,cAAc;AACZ;AAAA,MACE;AAAA,IACF;AACA,SAAK,OAAO;AAAA,EACd;AACF;AAYA,eAAsB,gBAA+B;AACnD,QAAM,cAAc,eAAe;AACnC,QAAM,gBAAgB,qBAAqB,iBAAiB,CAAC;AAC7D,MAAI,CAAC,aAAa;AAKhB,UAAMC,YAAW,MAAM,SAAS;AAChC,QAAIA,aAAYA,UAAS,YAAY,KAAK,IAAI,KAAK,KAAQ;AACzD,YAAM,OAAO,kBAAkB;AAAA,QAC7B;AAAA,QACA,kBAAkB,MAAM;AAAA,QAAC;AAAA,MAC3B,CAAC;AACD,YAAM,KAAK,WAAW;AAAA,IACxB;AACA,QAAI,MAAM,SAAS,EAAG;AACtB,QAAI,CAAC,QAAQ,OAAO,MAAO,OAAM,IAAI,iBAAiB;AACtD,QAAK,MAAM,oBAAoB,MAAO,EAAG,OAAM,IAAI,iBAAiB;AACpE;AAAA,EACF;AACA,QAAM,WAAW,MAAM,SAAS;AAChC,MACE,YACA,6BAA6B,UAAU,WAAW,KAClD,SAAS,YAAY,KAAK,IAAI,IAAI,KAClC;AACA;AAAA,EACF;AACA,QAAM,oBAAoB,EAAE,eAAe,YAAY,CAAC;AAC1D;AAOO,SAAS,eAAe,eAA+C;AAC5E,SAAO,YAAY;AACjB,UAAM,cAAc,eAAe;AACnC,QAAI,CAAC,YAAa,QAAO;AACzB,UAAM,oBAAoB,EAAE,eAAe,YAAY,CAAC;AACxD,WAAO;AAAA,EACT;AACF;AAaO,SAAS,kBAAmC;AACjD,QAAM,YAAY,iBAAiB;AACnC,QAAM,gBAAgB,qBAAqB,SAAS;AACpD,QAAM,cAAiC,kBAAkB;AAAA,IACvD;AAAA,IACA,eAAe,eAAe,aAAa;AAAA,IAC3C,kBAAkB,MAAM;AACtB,cAAQ;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AACD,cAAY,MAAM;AAClB,QAAM,eAAe,mBAAmB,WAAW;AACnD,QAAM,SAAS,IAAI,mBAAmB,EAAE,KAAK,WAAW,aAAa,CAAC;AACtE,SAAO,EAAE,QAAQ,cAAc,SAAS,MAAM,YAAY,KAAK,EAAE;AACnE;","names":["path","path","sleep","sleep","fs","fsp","path","path","fs","randomUUID","fs","path","sleep","existing"]}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
parseServeArgs,
|
|
8
8
|
readAuth,
|
|
9
9
|
runLogin
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-P3TEHMQY.js";
|
|
11
11
|
|
|
12
12
|
// src/help.ts
|
|
13
13
|
var USAGE = `tempo-mcp <subcommand>
|
|
@@ -31,7 +31,7 @@ configured on the connection.
|
|
|
31
31
|
Server:
|
|
32
32
|
(default) | serve Serve the aggregate MCP server over stdio
|
|
33
33
|
--toolsets a,b Only these toolsets (issues,docs,comments,agents,
|
|
34
|
-
scripts,slack,linear,canvas). Default: all.
|
|
34
|
+
scripts,slack,linear,canvas,design-context). Default: all.
|
|
35
35
|
--readonly Register no write tools at all
|
|
36
36
|
|
|
37
37
|
Direct tool access (for hosts without MCP support \u2014 Codex cloud, bots):
|
|
@@ -122,7 +122,7 @@ async function run(argv) {
|
|
|
122
122
|
process.stderr.write(USAGE);
|
|
123
123
|
return 1;
|
|
124
124
|
}
|
|
125
|
-
const { runServe } = await import("./serve-
|
|
125
|
+
const { runServe } = await import("./serve-MWQKPY7F.js");
|
|
126
126
|
return await runServe(options);
|
|
127
127
|
}
|
|
128
128
|
switch (subcommand) {
|
|
@@ -133,15 +133,15 @@ async function run(argv) {
|
|
|
133
133
|
case "whoami":
|
|
134
134
|
return await runWhoami();
|
|
135
135
|
case "token": {
|
|
136
|
-
const { runToken } = await import("./token-
|
|
136
|
+
const { runToken } = await import("./token-INLNRXZ5.js");
|
|
137
137
|
return await runToken(rest);
|
|
138
138
|
}
|
|
139
139
|
case "tools": {
|
|
140
|
-
const { runTools } = await import("./call-
|
|
140
|
+
const { runTools } = await import("./call-VU7GFC2V.js");
|
|
141
141
|
return await runTools(rest);
|
|
142
142
|
}
|
|
143
143
|
case "call": {
|
|
144
|
-
const { runCall } = await import("./call-
|
|
144
|
+
const { runCall } = await import("./call-VU7GFC2V.js");
|
|
145
145
|
return await runCall(rest);
|
|
146
146
|
}
|
|
147
147
|
default:
|
|
@@ -164,4 +164,4 @@ async function run(argv) {
|
|
|
164
164
|
export {
|
|
165
165
|
run
|
|
166
166
|
};
|
|
167
|
-
//# sourceMappingURL=chunk-
|
|
167
|
+
//# sourceMappingURL=chunk-X2JA3GU2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/help.ts","../src/whoami.ts","../src/index.ts"],"sourcesContent":["export const USAGE = `tempo-mcp <subcommand>\n\nTempo's MCP server — issues, docs, comments, agents, scripts, Slack,\nLinear, and (inside a Tempo repo) canvas tools, served as ONE stdio MCP\nserver.\n\nGet started (no account needed first):\n claude mcp add tempo -- npx -y @tempo-ai/mcp\n Then use any Tempo tool from Claude Code — the first one that needs\n Tempo opens the browser to sign in or create an account. Other hosts\n (Codex / Cursor) take the same command:\n { \"tempo\": { \"command\": \"npx\", \"args\": [\"-y\", \"@tempo-ai/mcp\"] } }\n\nScoping: the AI passes an explicit orgId (and projectId where\nproject-scoped) on every tool call, discovered via the built-in\ntempo_list_orgs / tempo_list_projects tools. Nothing org-related is\nconfigured on the connection.\n\nServer:\n (default) | serve Serve the aggregate MCP server over stdio\n --toolsets a,b Only these toolsets (issues,docs,comments,agents,\n scripts,slack,linear,canvas,design-context). Default: all.\n --readonly Register no write tools at all\n\nDirect tool access (for hosts without MCP support — Codex cloud, bots):\n tools [--schemas] List every tool (add --schemas for arg schemas)\n call <tool> --json '{\"orgId\": \"...\"}'\n Invoke any tool; same pipeline as the MCP server.\n --json - reads the argument object from stdin.\n Both accept --toolsets / --readonly.\n\nAuth (identity only — optional; the server prompts on first use):\n login Sign in via the browser ahead of time\n logout Clear stored credentials (~/.tempo/auth.json)\n whoami Show the signed-in user (exchanges TEMPO_AUTH_TOKEN\n if set — the smoke test for headless environments)\n token create [--name <label>]\n Mint a personal access token (printed once) for\n headless environments — cloud sandboxes with no\n browser. Set it there as TEMPO_AUTH_TOKEN.\n token list List your tokens\n token revoke <id-or-prefix>\n Revoke a token\n\nEnvironment:\n TEMPO_CONVEX_URL Override the Convex deployment (default: production)\n TEMPO_MCP_READONLY=1 Same as --readonly\n TEMPO_AUTH_TOKEN Personal access token — signs in without a browser\n TEMPO_MCP_NO_BROWSER=1 Never open a browser; sign-in links are only\n printed / returned to the host\n TEMPO_MCP_SIGN_IN_WAIT_MS\n How long a tool call waits for the browser sign-in\n before returning the link (default 120000)\n`;\n","import { readAuth, clearAuth, envAccessToken } from \"@tempo-modules/mcp-runtime\";\nimport { ensureCliAuth } from \"./cli-auth.ts\";\n\n/**\n * Subcommand handlers return an exit code (0 = success, 1 = error). The\n * dispatcher in `index.ts` is responsible for actually calling process.exit\n * — keeping it out of the handlers makes them unit-testable without\n * stubbing process.exit.\n */\n\nexport async function runWhoami(): Promise<number> {\n let auth = await readAuth();\n if (envAccessToken()) {\n // Headless environment: the env token is AUTHORITATIVE — exchange it\n // unless the cached credentials provably came from this exact token\n // (`whoami` is the standard smoke test after pasting TEMPO_AUTH_TOKEN,\n // so it must never report a stale cached identity).\n try {\n await ensureCliAuth();\n auth = await readAuth();\n } catch (err) {\n console.error(\n \"TEMPO_AUTH_TOKEN sign-in failed:\",\n err instanceof Error ? err.message : String(err),\n );\n return 1;\n }\n }\n if (!auth) {\n console.log(\n \"Not signed in. Use a Tempo tool from your agent to get a sign-in link, or run `npx -y @tempo-ai/mcp login`.\",\n );\n return 1;\n }\n const display = auth.email\n ? `${auth.email} (${auth.userId})`\n : auth.userId;\n console.log(`Signed in as: ${display}`);\n const ttlMs = auth.expiresAt - Date.now();\n if (ttlMs <= 0) {\n console.log(\n `Token state: expired ${Math.round(-ttlMs / 1000)}s ago (refresh on next call)`,\n );\n } else {\n console.log(`Token state: fresh, expires in ${Math.round(ttlMs / 1000)}s`);\n }\n return 0;\n}\n\nexport async function runLogout(): Promise<number> {\n await clearAuth();\n console.log(\n \"Signed out. The next Tempo tool call from your agent will prompt for sign-in (or run `npx -y @tempo-ai/mcp login`).\",\n );\n return 0;\n}\n","/**\n * Tempo MCP CLI entrypoint.\n *\n * The DEFAULT command (no subcommand) serves Tempo's full MCP tool surface\n * over stdio — that's what an MCP host config invokes:\n * { \"tempo\": { \"command\": \"npx\", \"args\": [\"-y\", \"@tempo-ai/mcp\"] } }\n * It needs no prior sign-in: the first tool call that needs Tempo prompts\n * for one (TEM-2313).\n *\n * Subcommand routing:\n * (none) | serve [--toolsets a,b] [--readonly]\n * → run the aggregate MCP server over stdio (long-running)\n * login | logout | whoami | token → auth utilities (short-lived)\n * tools | call → the same tool surface as a CLI\n * --help | -h → print usage\n */\n\nimport { USAGE } from \"./help.ts\";\nimport { NotSignedInError } from \"./cli-auth.ts\";\nimport { runLogin } from \"./login.ts\";\nimport { runLogout, runWhoami } from \"./whoami.ts\";\nimport { parseServeArgs } from \"./config.ts\";\n\nexport async function run(argv: string[]): Promise<number> {\n const [, , subcommand, ...rest] = argv;\n\n if (subcommand === \"--help\" || subcommand === \"-h\" || subcommand === \"help\") {\n process.stdout.write(USAGE);\n return 0;\n }\n\n try {\n // Bare invocation and flag-only invocations serve; `serve` is the\n // explicit spelling.\n if (\n !subcommand ||\n subcommand === \"serve\" ||\n subcommand.startsWith(\"--\")\n ) {\n const serveArgs =\n !subcommand || subcommand === \"serve\"\n ? rest\n : [subcommand, ...rest];\n const options = parseServeArgs(serveArgs);\n if (\"error\" in options) {\n console.error(options.error);\n process.stderr.write(USAGE);\n return 1;\n }\n const { runServe } = await import(\"./serve.ts\");\n return await runServe(options);\n }\n\n switch (subcommand) {\n case \"login\":\n return await runLogin();\n case \"logout\":\n return await runLogout();\n case \"whoami\":\n return await runWhoami();\n case \"token\": {\n const { runToken } = await import(\"./token.ts\");\n return await runToken(rest);\n }\n case \"tools\": {\n const { runTools } = await import(\"./call.ts\");\n return await runTools(rest);\n }\n case \"call\": {\n const { runCall } = await import(\"./call.ts\");\n return await runCall(rest);\n }\n default:\n console.error(`Unknown subcommand: ${subcommand}`);\n process.stderr.write(USAGE);\n return 1;\n }\n } catch (err) {\n // A missing session is an expected state with a one-line remedy, not\n // a crash — no stack trace.\n if (err instanceof NotSignedInError) {\n console.error(err.message);\n return 1;\n }\n console.error(\n err instanceof Error ? err.stack || err.message : String(err),\n );\n return 1;\n }\n}\n"],"mappings":";;;;;;;;;;;;AAAO,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACUrB,eAAsB,YAA6B;AACjD,MAAI,OAAO,MAAM,SAAS;AAC1B,MAAI,eAAe,GAAG;AAKpB,QAAI;AACF,YAAM,cAAc;AACpB,aAAO,MAAM,SAAS;AAAA,IACxB,SAAS,KAAK;AACZ,cAAQ;AAAA,QACN;AAAA,QACA,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACjD;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,CAAC,MAAM;AACT,YAAQ;AAAA,MACN;AAAA,IACF;AACA,WAAO;AAAA,EACT;AACA,QAAM,UAAU,KAAK,QACjB,GAAG,KAAK,KAAK,KAAK,KAAK,MAAM,MAC7B,KAAK;AACT,UAAQ,IAAI,iBAAiB,OAAO,EAAE;AACtC,QAAM,QAAQ,KAAK,YAAY,KAAK,IAAI;AACxC,MAAI,SAAS,GAAG;AACd,YAAQ;AAAA,MACN,yBAAyB,KAAK,MAAM,CAAC,QAAQ,GAAI,CAAC;AAAA,IACpD;AAAA,EACF,OAAO;AACL,YAAQ,IAAI,mCAAmC,KAAK,MAAM,QAAQ,GAAI,CAAC,GAAG;AAAA,EAC5E;AACA,SAAO;AACT;AAEA,eAAsB,YAA6B;AACjD,QAAM,UAAU;AAChB,UAAQ;AAAA,IACN;AAAA,EACF;AACA,SAAO;AACT;;;AChCA,eAAsB,IAAI,MAAiC;AACzD,QAAM,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,IAAI;AAElC,MAAI,eAAe,YAAY,eAAe,QAAQ,eAAe,QAAQ;AAC3E,YAAQ,OAAO,MAAM,KAAK;AAC1B,WAAO;AAAA,EACT;AAEA,MAAI;AAGF,QACE,CAAC,cACD,eAAe,WACf,WAAW,WAAW,IAAI,GAC1B;AACA,YAAM,YACJ,CAAC,cAAc,eAAe,UAC1B,OACA,CAAC,YAAY,GAAG,IAAI;AAC1B,YAAM,UAAU,eAAe,SAAS;AACxC,UAAI,WAAW,SAAS;AACtB,gBAAQ,MAAM,QAAQ,KAAK;AAC3B,gBAAQ,OAAO,MAAM,KAAK;AAC1B,eAAO;AAAA,MACT;AACA,YAAM,EAAE,SAAS,IAAI,MAAM,OAAO,qBAAY;AAC9C,aAAO,MAAM,SAAS,OAAO;AAAA,IAC/B;AAEA,YAAQ,YAAY;AAAA,MAClB,KAAK;AACH,eAAO,MAAM,SAAS;AAAA,MACxB,KAAK;AACH,eAAO,MAAM,UAAU;AAAA,MACzB,KAAK;AACH,eAAO,MAAM,UAAU;AAAA,MACzB,KAAK,SAAS;AACZ,cAAM,EAAE,SAAS,IAAI,MAAM,OAAO,qBAAY;AAC9C,eAAO,MAAM,SAAS,IAAI;AAAA,MAC5B;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,EAAE,SAAS,IAAI,MAAM,OAAO,oBAAW;AAC7C,eAAO,MAAM,SAAS,IAAI;AAAA,MAC5B;AAAA,MACA,KAAK,QAAQ;AACX,cAAM,EAAE,QAAQ,IAAI,MAAM,OAAO,oBAAW;AAC5C,eAAO,MAAM,QAAQ,IAAI;AAAA,MAC3B;AAAA,MACA;AACE,gBAAQ,MAAM,uBAAuB,UAAU,EAAE;AACjD,gBAAQ,OAAO,MAAM,KAAK;AAC1B,eAAO;AAAA,IACX;AAAA,EACF,SAAS,KAAK;AAGZ,QAAI,eAAe,kBAAkB;AACnC,cAAQ,MAAM,IAAI,OAAO;AACzB,aAAO;AAAA,IACT;AACA,YAAQ;AAAA,MACN,eAAe,QAAQ,IAAI,SAAS,IAAI,UAAU,OAAO,GAAG;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AACF;","names":[]}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
|
|
2
2
|
import {
|
|
3
3
|
run
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-X2JA3GU2.js";
|
|
5
|
+
import "./chunk-P3TEHMQY.js";
|
|
6
|
+
import "./chunk-JKT5N6IQ.js";
|
|
7
7
|
export {
|
|
8
8
|
run
|
|
9
9
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createRequire as __tempoCreateRequire } from 'node:module'; const require = __tempoCreateRequire(import.meta.url);
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-JKT5N6IQ.js";
|
|
3
3
|
|
|
4
4
|
// ../../node_modules/.pnpm/unpdf@1.4.0_@napi-rs+canvas@0.1.80/node_modules/unpdf/dist/pdfjs.mjs
|
|
5
5
|
void 0 === globalThis.DOMMatrix && (globalThis.DOMMatrix = class {
|
|
@@ -35240,4 +35240,4 @@ export {
|
|
|
35240
35240
|
updateUrlHash,
|
|
35241
35241
|
lh as version
|
|
35242
35242
|
};
|
|
35243
|
-
//# sourceMappingURL=pdfjs-
|
|
35243
|
+
//# sourceMappingURL=pdfjs-GSPIHND6.js.map
|