notex-companion 0.3.2 → 0.3.3

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 CHANGED
@@ -45,10 +45,13 @@ notex-companion mcp # start the stdio MCP server — graph_statu
45
45
  # graph_query, graph_path, graph_node, plus notex_list_questions,
46
46
  # notex_get_question, notex_get_answer, notex_save_answer
47
47
  # (require .notex/notex.json — see docs/specs/notex-mcp-server.md)
48
+ notex-companion link [options] # write .notex/notex.json, pairing this checkout to a Notex Repository
48
49
  ```
49
50
 
50
51
  Setup, how it picks which checkout to serve, and a manual verification walkthrough:
51
52
  [`docs/testing/mcp-server-setup.md`](https://github.com/bhirmbani/notex/blob/main/docs/testing/mcp-server-setup.md).
53
+ For `link` specifically — happy path, rotation, and every refusal path — see
54
+ [`docs/testing/notex-companion-link-manual-test.md`](https://github.com/bhirmbani/notex/blob/main/docs/testing/notex-companion-link-manual-test.md).
52
55
 
53
56
  ### `serve` options
54
57
 
@@ -58,6 +61,19 @@ Setup, how it picks which checkout to serve, and a manual verification walkthrou
58
61
  | `--origin <url>` | — | An additional allowed CORS origin, beyond the production Notex origin (and `http://localhost:3000` outside `NODE_ENV=production`). Repeatable. |
59
62
  | `--rotate-token` | off | Generate a new pairing token, invalidating the previous one. |
60
63
 
64
+ ### `link` options (all required)
65
+
66
+ | Flag | Meaning |
67
+ |---|---|
68
+ | `--organization-id <id>` | Notex organization id. |
69
+ | `--project-id <id>` | Notex project id — cross-checked against the Repository's actual project before writing. |
70
+ | `--repository-id <id>` | Notex repository id to bind this checkout to. |
71
+ | `--api-key <key>` | Generated once, plaintext, from Notex Settings → API keys. |
72
+
73
+ `link` validates all four against the Notex API before writing `.notex/notex.json` (mode `0600`) —
74
+ a bad key, an inaccessible repository, or a repository/project mismatch fails with an actionable
75
+ message and writes nothing.
76
+
61
77
  ## Pairing
62
78
 
63
79
  On first run the companion generates a 32-byte token, persists it to `.notex/companion.json`
@@ -0,0 +1 @@
1
+ export declare function atomicWriteFile(path: string, content: string, mode: number): void;
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- /** Thrown by argument parsing on bad input — `main()` turns it into a stderr message + exit 1. */
2
- export declare class CliUsageError extends Error {
3
- }
1
+ import { CliUsageError } from "./cliErrors.js";
2
+ export { CliUsageError };
4
3
  export type ServeArgs = {
5
4
  port: number | undefined;
6
5
  origins: Array<string>;
package/dist/cli.js CHANGED
@@ -6520,10 +6520,203 @@ var require_dist = __commonJS((exports, module) => {
6520
6520
  import { existsSync } from "node:fs";
6521
6521
  import { resolve as resolve2 } from "node:path";
6522
6522
 
6523
+ // src/cliErrors.ts
6524
+ class CliUsageError extends Error {
6525
+ }
6526
+
6527
+ // src/atomicWrite.ts
6528
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
6529
+ import { dirname } from "node:path";
6530
+ function atomicWriteFile(path, content, mode) {
6531
+ mkdirSync(dirname(path), { recursive: true });
6532
+ const tmpPath = `${path}.${process.pid}.tmp`;
6533
+ writeFileSync(tmpPath, content, { mode, flag: "wx" });
6534
+ renameSync(tmpPath, path);
6535
+ }
6536
+
6537
+ // src/notexConfig.ts
6538
+ import { readFileSync } from "node:fs";
6539
+ import { join } from "node:path";
6540
+ function configFilePath(checkoutPath) {
6541
+ return join(checkoutPath, ".notex", "notex.json");
6542
+ }
6543
+ function nonEmptyString(value) {
6544
+ return typeof value === "string" && value.length > 0;
6545
+ }
6546
+ function loadNotexConfig(checkoutPath, env = process.env) {
6547
+ let raw;
6548
+ try {
6549
+ raw = readFileSync(configFilePath(checkoutPath), "utf8");
6550
+ } catch {
6551
+ return { kind: "unlinked", reason: "no .notex/notex.json found" };
6552
+ }
6553
+ let parsed;
6554
+ try {
6555
+ parsed = JSON.parse(raw);
6556
+ } catch {
6557
+ return { kind: "unlinked", reason: ".notex/notex.json is not valid JSON" };
6558
+ }
6559
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
6560
+ return { kind: "unlinked", reason: ".notex/notex.json must be a JSON object" };
6561
+ }
6562
+ const { organizationId, projectId, repositoryId, apiKey } = parsed;
6563
+ if (!nonEmptyString(organizationId) || !nonEmptyString(projectId) || !nonEmptyString(repositoryId)) {
6564
+ return { kind: "unlinked", reason: ".notex/notex.json is missing organizationId, projectId, or repositoryId" };
6565
+ }
6566
+ const resolvedKey = nonEmptyString(env.NOTEX_API_KEY) ? env.NOTEX_API_KEY : apiKey;
6567
+ if (!nonEmptyString(resolvedKey)) {
6568
+ return { kind: "unlinked", reason: ".notex/notex.json is missing apiKey, and NOTEX_API_KEY is not set" };
6569
+ }
6570
+ return { kind: "linked", config: { organizationId, projectId, repositoryId, apiKey: resolvedKey } };
6571
+ }
6572
+
6573
+ // src/types.ts
6574
+ var ERROR_CODES = {
6575
+ unauthorized: "unauthorized",
6576
+ notFound: "not_found",
6577
+ graphUnreadable: "graph_unreadable",
6578
+ invalidRequest: "invalid_request",
6579
+ graphLoading: "graph_loading",
6580
+ forbidden: "forbidden",
6581
+ notexApiError: "notex_api_error"
6582
+ };
6583
+
6584
+ class OpError extends Error {
6585
+ code;
6586
+ detail;
6587
+ constructor(code, message, detail) {
6588
+ super(message);
6589
+ this.name = "OpError";
6590
+ this.code = code;
6591
+ this.detail = detail;
6592
+ }
6593
+ }
6594
+
6595
+ // src/notexClient.ts
6596
+ var DEFAULT_API_URL = "http://localhost:3000";
6597
+ function resolveApiUrl(env = process.env) {
6598
+ return env.NOTEX_API_URL?.trim() || DEFAULT_API_URL;
6599
+ }
6600
+ async function request(config, fetchImpl, method, path, body) {
6601
+ let res;
6602
+ try {
6603
+ res = await fetchImpl(`${config.baseUrl}${path}`, {
6604
+ method,
6605
+ headers: {
6606
+ "x-api-key": config.apiKey,
6607
+ ...body !== undefined ? { "content-type": "application/json" } : {}
6608
+ },
6609
+ body: body !== undefined ? JSON.stringify(body) : undefined
6610
+ });
6611
+ } catch (err) {
6612
+ throw new OpError(ERROR_CODES.notexApiError, `Could not reach the Notex API at ${config.baseUrl}`, err);
6613
+ }
6614
+ if (res.status === 401)
6615
+ throw new OpError(ERROR_CODES.unauthorized, "Notex rejected the API key");
6616
+ if (res.status === 403)
6617
+ throw new OpError(ERROR_CODES.forbidden, "Not authorized for this Project");
6618
+ if (res.status === 404)
6619
+ throw new OpError(ERROR_CODES.notFound, "Not found in Notex");
6620
+ if (!res.ok) {
6621
+ const body2 = await res.json().catch(() => ({}));
6622
+ throw new OpError(ERROR_CODES.notexApiError, body2.error?.message ?? `Notex API error (HTTP ${res.status})`);
6623
+ }
6624
+ return await res.json();
6625
+ }
6626
+ var enc = (id) => encodeURIComponent(id);
6627
+ function createNotexClient(config, fetchImpl = fetch) {
6628
+ return {
6629
+ getRepository: (organizationId, id) => request(config, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/repositories/${enc(id)}`),
6630
+ listContexts: (organizationId, repositoryId) => request(config, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/repositories/${enc(repositoryId)}/contexts`),
6631
+ getContext: (organizationId, id) => request(config, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(id)}`),
6632
+ createContext: (organizationId, repositoryId, question) => request(config, fetchImpl, "POST", `/api/v1/organizations/${enc(organizationId)}/repositories/${enc(repositoryId)}/contexts`, { question }),
6633
+ listFiles: (organizationId, contextId) => request(config, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(contextId)}/files`),
6634
+ getFile: (organizationId, id) => request(config, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/files/${enc(id)}`),
6635
+ createFile: (organizationId, contextId, file) => request(config, fetchImpl, "POST", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(contextId)}/files`, file),
6636
+ deleteContext: (organizationId, id) => request(config, fetchImpl, "DELETE", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(id)}`)
6637
+ };
6638
+ }
6639
+
6640
+ // src/link.ts
6641
+ var REQUIRED_FLAGS = ["--organization-id", "--project-id", "--repository-id", "--api-key"];
6642
+ function parseLinkArgs(args) {
6643
+ let organizationId;
6644
+ let projectId;
6645
+ let repositoryId;
6646
+ let apiKey;
6647
+ for (let i = 0;i < args.length; i++) {
6648
+ const arg = args[i];
6649
+ switch (arg) {
6650
+ case "--organization-id":
6651
+ organizationId = args[++i];
6652
+ if (!organizationId)
6653
+ throw new CliUsageError("--organization-id needs a value");
6654
+ break;
6655
+ case "--project-id":
6656
+ projectId = args[++i];
6657
+ if (!projectId)
6658
+ throw new CliUsageError("--project-id needs a value");
6659
+ break;
6660
+ case "--repository-id":
6661
+ repositoryId = args[++i];
6662
+ if (!repositoryId)
6663
+ throw new CliUsageError("--repository-id needs a value");
6664
+ break;
6665
+ case "--api-key":
6666
+ apiKey = args[++i];
6667
+ if (!apiKey)
6668
+ throw new CliUsageError("--api-key needs a value");
6669
+ break;
6670
+ default:
6671
+ throw new CliUsageError(`unrecognised option "${arg}"`);
6672
+ }
6673
+ }
6674
+ if (!organizationId || !projectId || !repositoryId || !apiKey) {
6675
+ throw new CliUsageError(`link needs ${REQUIRED_FLAGS.join(", ")}, all required`);
6676
+ }
6677
+ return { organizationId, projectId, repositoryId, apiKey };
6678
+ }
6679
+ function writeNotexConfig(checkoutPath, config) {
6680
+ atomicWriteFile(configFilePath(checkoutPath), JSON.stringify(config, null, 2), 384);
6681
+ }
6682
+ function describeValidationFailure(err, args) {
6683
+ if (err instanceof OpError) {
6684
+ switch (err.code) {
6685
+ case ERROR_CODES.unauthorized:
6686
+ return "Notex rejected the API key — generate a new one from Notex Settings → API keys and try again.";
6687
+ case ERROR_CODES.forbidden:
6688
+ return `Not authorized for repository ${args.repositoryId} in organization ${args.organizationId} — check the ids, or that your API key's owner holds a Grant on this Project.`;
6689
+ case ERROR_CODES.notFound:
6690
+ return `Repository ${args.repositoryId} was not found in organization ${args.organizationId} — check the ids from Notex Settings.`;
6691
+ default:
6692
+ return `Could not verify with the Notex API: ${err.message}`;
6693
+ }
6694
+ }
6695
+ return `Could not verify with the Notex API: ${err instanceof Error ? err.message : String(err)}`;
6696
+ }
6697
+ async function link(args, opts = { checkoutPath: process.cwd() }) {
6698
+ const client = createNotexClient({ baseUrl: resolveApiUrl(), apiKey: args.apiKey }, opts.fetchImpl);
6699
+ let repository;
6700
+ try {
6701
+ repository = await client.getRepository(args.organizationId, args.repositoryId);
6702
+ } catch (err) {
6703
+ throw new CliUsageError(describeValidationFailure(err, args));
6704
+ }
6705
+ if (repository.projectId !== args.projectId) {
6706
+ throw new CliUsageError(`Repository ${args.repositoryId} belongs to project ${repository.projectId}, not ${args.projectId} — check the ids from Notex Settings.`);
6707
+ }
6708
+ writeNotexConfig(opts.checkoutPath, {
6709
+ organizationId: args.organizationId,
6710
+ projectId: args.projectId,
6711
+ repositoryId: args.repositoryId,
6712
+ apiKey: args.apiKey
6713
+ });
6714
+ }
6715
+
6523
6716
  // src/graph.ts
6524
6717
  import { createHash } from "node:crypto";
6525
6718
  import { execSync } from "node:child_process";
6526
- import { readFileSync, statSync } from "node:fs";
6719
+ import { readFileSync as readFileSync2, statSync } from "node:fs";
6527
6720
  import { resolve } from "node:path";
6528
6721
 
6529
6722
  // src/scoring.ts
@@ -6574,28 +6767,6 @@ function scoreNodes(index, queryTerms) {
6574
6767
  return scored.sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
6575
6768
  }
6576
6769
 
6577
- // src/types.ts
6578
- var ERROR_CODES = {
6579
- unauthorized: "unauthorized",
6580
- notFound: "not_found",
6581
- graphUnreadable: "graph_unreadable",
6582
- invalidRequest: "invalid_request",
6583
- graphLoading: "graph_loading",
6584
- forbidden: "forbidden",
6585
- notexApiError: "notex_api_error"
6586
- };
6587
-
6588
- class OpError extends Error {
6589
- code;
6590
- detail;
6591
- constructor(code, message, detail) {
6592
- super(message);
6593
- this.name = "OpError";
6594
- this.code = code;
6595
- this.detail = detail;
6596
- }
6597
- }
6598
-
6599
6770
  // src/graph.ts
6600
6771
  function rootPrefixFor(checkoutPath, graphRoot) {
6601
6772
  if (graphRoot === checkoutPath)
@@ -6607,7 +6778,7 @@ function rootPrefixFor(checkoutPath, graphRoot) {
6607
6778
  }
6608
6779
  function readGraphRoot(checkoutPath) {
6609
6780
  try {
6610
- return readFileSync(resolve(checkoutPath, "graphify-out/.graphify_root"), "utf8").trim();
6781
+ return readFileSync2(resolve(checkoutPath, "graphify-out/.graphify_root"), "utf8").trim();
6611
6782
  } catch {
6612
6783
  return checkoutPath;
6613
6784
  }
@@ -6623,7 +6794,7 @@ function loadGraph(checkoutPath) {
6623
6794
  const graphPath = resolve(checkoutPath, "graphify-out/graph.json");
6624
6795
  let raw;
6625
6796
  try {
6626
- raw = readFileSync(graphPath, "utf8");
6797
+ raw = readFileSync2(graphPath, "utf8");
6627
6798
  } catch (err) {
6628
6799
  throw new OpError("graph_unreadable", `graph.json not found at ${graphPath}`, err);
6629
6800
  }
@@ -7194,8 +7365,8 @@ function startNodeServer(opts) {
7194
7365
  }
7195
7366
  async function handleRequest(req, res, fetch2) {
7196
7367
  try {
7197
- const request = await toWebRequest(req);
7198
- const response = await fetch2(request);
7368
+ const request2 = await toWebRequest(req);
7369
+ const response = await fetch2(request2);
7199
7370
  res.statusCode = response.status;
7200
7371
  response.headers.forEach((value, key) => res.setHeader(key, value));
7201
7372
  res.end(Buffer.from(await response.arrayBuffer()));
@@ -7231,18 +7402,18 @@ async function toWebRequest(req) {
7231
7402
  }
7232
7403
 
7233
7404
  // src/pairing.ts
7234
- import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
7405
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
7235
7406
  import { randomBytes } from "node:crypto";
7236
- import { dirname, join } from "node:path";
7407
+ import { dirname as dirname2, join as join2 } from "node:path";
7237
7408
  function pairingFilePath(checkoutPath) {
7238
- return join(checkoutPath, ".notex", "companion.json");
7409
+ return join2(checkoutPath, ".notex", "companion.json");
7239
7410
  }
7240
7411
  function generateToken() {
7241
7412
  return randomBytes(32).toString("base64url");
7242
7413
  }
7243
7414
  function readExistingToken(path2) {
7244
7415
  try {
7245
- const parsed = JSON.parse(readFileSync2(path2, "utf8"));
7416
+ const parsed = JSON.parse(readFileSync3(path2, "utf8"));
7246
7417
  return typeof parsed.token === "string" ? parsed.token : undefined;
7247
7418
  } catch {
7248
7419
  return;
@@ -7252,14 +7423,11 @@ function tokenContents(token) {
7252
7423
  return JSON.stringify({ token }, null, 2);
7253
7424
  }
7254
7425
  function createTokenFileExclusive(path2, token) {
7255
- mkdirSync(dirname(path2), { recursive: true });
7256
- writeFileSync(path2, tokenContents(token), { mode: 384, flag: "wx" });
7426
+ mkdirSync2(dirname2(path2), { recursive: true });
7427
+ writeFileSync2(path2, tokenContents(token), { mode: 384, flag: "wx" });
7257
7428
  }
7258
7429
  function rewriteTokenFile(path2, token) {
7259
- mkdirSync(dirname(path2), { recursive: true });
7260
- const tmpPath = `${path2}.${process.pid}.tmp`;
7261
- writeFileSync(tmpPath, tokenContents(token), { mode: 384, flag: "wx" });
7262
- renameSync(tmpPath, path2);
7430
+ atomicWriteFile(path2, tokenContents(token), 384);
7263
7431
  }
7264
7432
  function loadOrCreateToken(checkoutPath, opts = {}) {
7265
7433
  const path2 = pairingFilePath(checkoutPath);
@@ -16474,14 +16642,14 @@ var CompleteRequestSchema = RequestSchema.extend({
16474
16642
  method: literal("completion/complete"),
16475
16643
  params: CompleteRequestParamsSchema
16476
16644
  });
16477
- function assertCompleteRequestPrompt(request) {
16478
- if (request.params.ref.type !== "ref/prompt") {
16479
- throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
16645
+ function assertCompleteRequestPrompt(request2) {
16646
+ if (request2.params.ref.type !== "ref/prompt") {
16647
+ throw new TypeError(`Expected CompleteRequestPrompt, but got ${request2.params.ref.type}`);
16480
16648
  }
16481
16649
  }
16482
- function assertCompleteRequestResourceTemplate(request) {
16483
- if (request.params.ref.type !== "ref/resource") {
16484
- throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
16650
+ function assertCompleteRequestResourceTemplate(request2) {
16651
+ if (request2.params.ref.type !== "ref/resource") {
16652
+ throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request2.params.ref.type}`);
16485
16653
  }
16486
16654
  }
16487
16655
  var CompleteResultSchema = ResultSchema.extend({
@@ -17918,8 +18086,8 @@ class Protocol {
17918
18086
  this._taskStore = _options?.taskStore;
17919
18087
  this._taskMessageQueue = _options?.taskMessageQueue;
17920
18088
  if (this._taskStore) {
17921
- this.setRequestHandler(GetTaskRequestSchema, async (request, extra) => {
17922
- const task = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
18089
+ this.setRequestHandler(GetTaskRequestSchema, async (request2, extra) => {
18090
+ const task = await this._taskStore.getTask(request2.params.taskId, extra.sessionId);
17923
18091
  if (!task) {
17924
18092
  throw new McpError(ErrorCode.InvalidParams, "Failed to retrieve task: Task not found");
17925
18093
  }
@@ -17927,9 +18095,9 @@ class Protocol {
17927
18095
  ...task
17928
18096
  };
17929
18097
  });
17930
- this.setRequestHandler(GetTaskPayloadRequestSchema, async (request, extra) => {
18098
+ this.setRequestHandler(GetTaskPayloadRequestSchema, async (request2, extra) => {
17931
18099
  const handleTaskResult = async () => {
17932
- const taskId = request.params.taskId;
18100
+ const taskId = request2.params.taskId;
17933
18101
  if (this._taskMessageQueue) {
17934
18102
  let queuedMessage;
17935
18103
  while (queuedMessage = await this._taskMessageQueue.dequeue(taskId, extra.sessionId)) {
@@ -17980,9 +18148,9 @@ class Protocol {
17980
18148
  };
17981
18149
  return await handleTaskResult();
17982
18150
  });
17983
- this.setRequestHandler(ListTasksRequestSchema, async (request, extra) => {
18151
+ this.setRequestHandler(ListTasksRequestSchema, async (request2, extra) => {
17984
18152
  try {
17985
- const { tasks, nextCursor } = await this._taskStore.listTasks(request.params?.cursor, extra.sessionId);
18153
+ const { tasks, nextCursor } = await this._taskStore.listTasks(request2.params?.cursor, extra.sessionId);
17986
18154
  return {
17987
18155
  tasks,
17988
18156
  nextCursor,
@@ -17992,20 +18160,20 @@ class Protocol {
17992
18160
  throw new McpError(ErrorCode.InvalidParams, `Failed to list tasks: ${error2 instanceof Error ? error2.message : String(error2)}`);
17993
18161
  }
17994
18162
  });
17995
- this.setRequestHandler(CancelTaskRequestSchema, async (request, extra) => {
18163
+ this.setRequestHandler(CancelTaskRequestSchema, async (request2, extra) => {
17996
18164
  try {
17997
- const task = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
18165
+ const task = await this._taskStore.getTask(request2.params.taskId, extra.sessionId);
17998
18166
  if (!task) {
17999
- throw new McpError(ErrorCode.InvalidParams, `Task not found: ${request.params.taskId}`);
18167
+ throw new McpError(ErrorCode.InvalidParams, `Task not found: ${request2.params.taskId}`);
18000
18168
  }
18001
18169
  if (isTerminal(task.status)) {
18002
18170
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task in terminal status: ${task.status}`);
18003
18171
  }
18004
- await this._taskStore.updateTaskStatus(request.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
18005
- this._clearTaskQueue(request.params.taskId);
18006
- const cancelledTask = await this._taskStore.getTask(request.params.taskId, extra.sessionId);
18172
+ await this._taskStore.updateTaskStatus(request2.params.taskId, "cancelled", "Client cancelled task execution.", extra.sessionId);
18173
+ this._clearTaskQueue(request2.params.taskId);
18174
+ const cancelledTask = await this._taskStore.getTask(request2.params.taskId, extra.sessionId);
18007
18175
  if (!cancelledTask) {
18008
- throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request.params.taskId}`);
18176
+ throw new McpError(ErrorCode.InvalidParams, `Task not found after cancellation: ${request2.params.taskId}`);
18009
18177
  }
18010
18178
  return {
18011
18179
  _meta: {},
@@ -18121,14 +18289,14 @@ class Protocol {
18121
18289
  }
18122
18290
  Promise.resolve().then(() => handler(notification)).catch((error2) => this._onerror(new Error(`Uncaught error in notification handler: ${error2}`)));
18123
18291
  }
18124
- _onrequest(request, extra) {
18125
- const handler = this._requestHandlers.get(request.method) ?? this.fallbackRequestHandler;
18292
+ _onrequest(request2, extra) {
18293
+ const handler = this._requestHandlers.get(request2.method) ?? this.fallbackRequestHandler;
18126
18294
  const capturedTransport = this._transport;
18127
- const relatedTaskId = request.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
18295
+ const relatedTaskId = request2.params?._meta?.[RELATED_TASK_META_KEY]?.taskId;
18128
18296
  if (handler === undefined) {
18129
18297
  const errorResponse = {
18130
18298
  jsonrpc: "2.0",
18131
- id: request.id,
18299
+ id: request2.id,
18132
18300
  error: {
18133
18301
  code: ErrorCode.MethodNotFound,
18134
18302
  message: "Method not found"
@@ -18146,17 +18314,17 @@ class Protocol {
18146
18314
  return;
18147
18315
  }
18148
18316
  const abortController = new AbortController;
18149
- this._requestHandlerAbortControllers.set(request.id, abortController);
18150
- const taskCreationParams = isTaskAugmentedRequestParams(request.params) ? request.params.task : undefined;
18151
- const taskStore = this._taskStore ? this.requestTaskStore(request, capturedTransport?.sessionId) : undefined;
18317
+ this._requestHandlerAbortControllers.set(request2.id, abortController);
18318
+ const taskCreationParams = isTaskAugmentedRequestParams(request2.params) ? request2.params.task : undefined;
18319
+ const taskStore = this._taskStore ? this.requestTaskStore(request2, capturedTransport?.sessionId) : undefined;
18152
18320
  const fullExtra = {
18153
18321
  signal: abortController.signal,
18154
18322
  sessionId: capturedTransport?.sessionId,
18155
- _meta: request.params?._meta,
18323
+ _meta: request2.params?._meta,
18156
18324
  sendNotification: async (notification) => {
18157
18325
  if (abortController.signal.aborted)
18158
18326
  return;
18159
- const notificationOptions = { relatedRequestId: request.id };
18327
+ const notificationOptions = { relatedRequestId: request2.id };
18160
18328
  if (relatedTaskId) {
18161
18329
  notificationOptions.relatedTask = { taskId: relatedTaskId };
18162
18330
  }
@@ -18166,7 +18334,7 @@ class Protocol {
18166
18334
  if (abortController.signal.aborted) {
18167
18335
  throw new McpError(ErrorCode.ConnectionClosed, "Request was cancelled");
18168
18336
  }
18169
- const requestOptions = { ...options, relatedRequestId: request.id };
18337
+ const requestOptions = { ...options, relatedRequestId: request2.id };
18170
18338
  if (relatedTaskId && !requestOptions.relatedTask) {
18171
18339
  requestOptions.relatedTask = { taskId: relatedTaskId };
18172
18340
  }
@@ -18177,7 +18345,7 @@ class Protocol {
18177
18345
  return await this.request(r, resultSchema, requestOptions);
18178
18346
  },
18179
18347
  authInfo: extra?.authInfo,
18180
- requestId: request.id,
18348
+ requestId: request2.id,
18181
18349
  requestInfo: extra?.requestInfo,
18182
18350
  taskId: relatedTaskId,
18183
18351
  taskStore,
@@ -18187,16 +18355,16 @@ class Protocol {
18187
18355
  };
18188
18356
  Promise.resolve().then(() => {
18189
18357
  if (taskCreationParams) {
18190
- this.assertTaskHandlerCapability(request.method);
18358
+ this.assertTaskHandlerCapability(request2.method);
18191
18359
  }
18192
- }).then(() => handler(request, fullExtra)).then(async (result) => {
18360
+ }).then(() => handler(request2, fullExtra)).then(async (result) => {
18193
18361
  if (abortController.signal.aborted) {
18194
18362
  return;
18195
18363
  }
18196
18364
  const response = {
18197
18365
  result,
18198
18366
  jsonrpc: "2.0",
18199
- id: request.id
18367
+ id: request2.id
18200
18368
  };
18201
18369
  if (relatedTaskId && this._taskMessageQueue) {
18202
18370
  await this._enqueueTaskMessage(relatedTaskId, {
@@ -18213,7 +18381,7 @@ class Protocol {
18213
18381
  }
18214
18382
  const errorResponse = {
18215
18383
  jsonrpc: "2.0",
18216
- id: request.id,
18384
+ id: request2.id,
18217
18385
  error: {
18218
18386
  code: Number.isSafeInteger(error2["code"]) ? error2["code"] : ErrorCode.InternalError,
18219
18387
  message: error2.message ?? "Internal error",
@@ -18230,8 +18398,8 @@ class Protocol {
18230
18398
  await capturedTransport?.send(errorResponse);
18231
18399
  }
18232
18400
  }).catch((error2) => this._onerror(new Error(`Failed to send response: ${error2}`))).finally(() => {
18233
- if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
18234
- this._requestHandlerAbortControllers.delete(request.id);
18401
+ if (this._requestHandlerAbortControllers.get(request2.id) === abortController) {
18402
+ this._requestHandlerAbortControllers.delete(request2.id);
18235
18403
  }
18236
18404
  });
18237
18405
  }
@@ -18305,11 +18473,11 @@ class Protocol {
18305
18473
  async close() {
18306
18474
  await this._transport?.close();
18307
18475
  }
18308
- async* requestStream(request, resultSchema, options) {
18476
+ async* requestStream(request2, resultSchema, options) {
18309
18477
  const { task } = options ?? {};
18310
18478
  if (!task) {
18311
18479
  try {
18312
- const result = await this.request(request, resultSchema, options);
18480
+ const result = await this.request(request2, resultSchema, options);
18313
18481
  yield { type: "result", result };
18314
18482
  } catch (error2) {
18315
18483
  yield {
@@ -18321,7 +18489,7 @@ class Protocol {
18321
18489
  }
18322
18490
  let taskId;
18323
18491
  try {
18324
- const createResult = await this.request(request, CreateTaskResultSchema, options);
18492
+ const createResult = await this.request(request2, CreateTaskResultSchema, options);
18325
18493
  if (createResult.task) {
18326
18494
  taskId = createResult.task.taskId;
18327
18495
  yield { type: "taskCreated", task: createResult.task };
@@ -18364,7 +18532,7 @@ class Protocol {
18364
18532
  };
18365
18533
  }
18366
18534
  }
18367
- request(request, resultSchema, options) {
18535
+ request(request2, resultSchema, options) {
18368
18536
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
18369
18537
  return new Promise((resolve2, reject) => {
18370
18538
  const earlyReject = (error2) => {
@@ -18376,9 +18544,9 @@ class Protocol {
18376
18544
  }
18377
18545
  if (this._options?.enforceStrictCapabilities === true) {
18378
18546
  try {
18379
- this.assertCapabilityForMethod(request.method);
18547
+ this.assertCapabilityForMethod(request2.method);
18380
18548
  if (task) {
18381
- this.assertTaskCapability(request.method);
18549
+ this.assertTaskCapability(request2.method);
18382
18550
  }
18383
18551
  } catch (e) {
18384
18552
  earlyReject(e);
@@ -18388,16 +18556,16 @@ class Protocol {
18388
18556
  options?.signal?.throwIfAborted();
18389
18557
  const messageId = this._requestMessageId++;
18390
18558
  const jsonrpcRequest = {
18391
- ...request,
18559
+ ...request2,
18392
18560
  jsonrpc: "2.0",
18393
18561
  id: messageId
18394
18562
  };
18395
18563
  if (options?.onprogress) {
18396
18564
  this._progressHandlers.set(messageId, options.onprogress);
18397
18565
  jsonrpcRequest.params = {
18398
- ...request.params,
18566
+ ...request2.params,
18399
18567
  _meta: {
18400
- ...request.params?._meta || {},
18568
+ ...request2.params?._meta || {},
18401
18569
  progressToken: messageId
18402
18570
  }
18403
18571
  };
@@ -18573,8 +18741,8 @@ class Protocol {
18573
18741
  setRequestHandler(requestSchema, handler) {
18574
18742
  const method = getMethodLiteral(requestSchema);
18575
18743
  this.assertRequestHandlerCapability(method);
18576
- this._requestHandlers.set(method, (request, extra) => {
18577
- const parsed = parseWithCompat(requestSchema, request);
18744
+ this._requestHandlers.set(method, (request2, extra) => {
18745
+ const parsed = parseWithCompat(requestSchema, request2);
18578
18746
  return Promise.resolve(handler(parsed, extra));
18579
18747
  });
18580
18748
  }
@@ -18647,19 +18815,19 @@ class Protocol {
18647
18815
  }, { once: true });
18648
18816
  });
18649
18817
  }
18650
- requestTaskStore(request, sessionId) {
18818
+ requestTaskStore(request2, sessionId) {
18651
18819
  const taskStore = this._taskStore;
18652
18820
  if (!taskStore) {
18653
18821
  throw new Error("No task store configured");
18654
18822
  }
18655
18823
  return {
18656
18824
  createTask: async (taskParams) => {
18657
- if (!request) {
18825
+ if (!request2) {
18658
18826
  throw new Error("No request provided");
18659
18827
  }
18660
- return await taskStore.createTask(taskParams, request.id, {
18661
- method: request.method,
18662
- params: request.params
18828
+ return await taskStore.createTask(taskParams, request2.id, {
18829
+ method: request2.method,
18830
+ params: request2.params
18663
18831
  }, sessionId);
18664
18832
  },
18665
18833
  getTask: async (taskId) => {
@@ -18778,8 +18946,8 @@ class ExperimentalServerTasks {
18778
18946
  constructor(_server) {
18779
18947
  this._server = _server;
18780
18948
  }
18781
- requestStream(request, resultSchema, options) {
18782
- return this._server.requestStream(request, resultSchema, options);
18949
+ requestStream(request2, resultSchema, options) {
18950
+ return this._server.requestStream(request2, resultSchema, options);
18783
18951
  }
18784
18952
  createMessageStream(params, options) {
18785
18953
  const clientCapabilities = this._server.getClientCapabilities();
@@ -18900,12 +19068,12 @@ class Server extends Protocol {
18900
19068
  this._capabilities = options?.capabilities ?? {};
18901
19069
  this._instructions = options?.instructions;
18902
19070
  this._jsonSchemaValidator = options?.jsonSchemaValidator ?? new AjvJsonSchemaValidator;
18903
- this.setRequestHandler(InitializeRequestSchema, (request) => this._oninitialize(request));
19071
+ this.setRequestHandler(InitializeRequestSchema, (request2) => this._oninitialize(request2));
18904
19072
  this.setNotificationHandler(InitializedNotificationSchema, () => this.oninitialized?.());
18905
19073
  if (this._capabilities.logging) {
18906
- this.setRequestHandler(SetLevelRequestSchema, async (request, extra) => {
19074
+ this.setRequestHandler(SetLevelRequestSchema, async (request2, extra) => {
18907
19075
  const transportSessionId = extra.sessionId || extra.requestInfo?.headers["mcp-session-id"] || undefined;
18908
- const { level } = request.params;
19076
+ const { level } = request2.params;
18909
19077
  const parseResult = LoggingLevelSchema.safeParse(level);
18910
19078
  if (parseResult.success) {
18911
19079
  this._loggingLevels.set(transportSessionId, parseResult.data);
@@ -18949,14 +19117,14 @@ class Server extends Protocol {
18949
19117
  }
18950
19118
  const method = methodValue;
18951
19119
  if (method === "tools/call") {
18952
- const wrappedHandler = async (request, extra) => {
18953
- const validatedRequest = safeParse2(CallToolRequestSchema, request);
19120
+ const wrappedHandler = async (request2, extra) => {
19121
+ const validatedRequest = safeParse2(CallToolRequestSchema, request2);
18954
19122
  if (!validatedRequest.success) {
18955
19123
  const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
18956
19124
  throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage}`);
18957
19125
  }
18958
19126
  const { params } = validatedRequest.data;
18959
- const result = await Promise.resolve(handler(request, extra));
19127
+ const result = await Promise.resolve(handler(request2, extra));
18960
19128
  if (params.task) {
18961
19129
  const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
18962
19130
  if (!taskValidationResult.success) {
@@ -19087,10 +19255,10 @@ class Server extends Protocol {
19087
19255
  }
19088
19256
  assertToolsCallTaskCapability(this._capabilities.tasks?.requests, method, "Server");
19089
19257
  }
19090
- async _oninitialize(request) {
19091
- const requestedVersion = request.params.protocolVersion;
19092
- this._clientCapabilities = request.params.capabilities;
19093
- this._clientVersion = request.params.clientInfo;
19258
+ async _oninitialize(request2) {
19259
+ const requestedVersion = request2.params.protocolVersion;
19260
+ this._clientCapabilities = request2.params.capabilities;
19261
+ this._clientVersion = request2.params.clientInfo;
19094
19262
  const protocolVersion = SUPPORTED_PROTOCOL_VERSIONS.includes(requestedVersion) ? requestedVersion : LATEST_PROTOCOL_VERSION;
19095
19263
  return {
19096
19264
  protocolVersion,
@@ -19373,33 +19541,33 @@ class McpServer {
19373
19541
  return toolDefinition;
19374
19542
  })
19375
19543
  }));
19376
- this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
19544
+ this.server.setRequestHandler(CallToolRequestSchema, async (request2, extra) => {
19377
19545
  try {
19378
- const tool = this._registeredTools[request.params.name];
19546
+ const tool = this._registeredTools[request2.params.name];
19379
19547
  if (!tool) {
19380
- throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
19548
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${request2.params.name} not found`);
19381
19549
  }
19382
19550
  if (!tool.enabled) {
19383
- throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
19551
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${request2.params.name} disabled`);
19384
19552
  }
19385
- const isTaskRequest = !!request.params.task;
19553
+ const isTaskRequest = !!request2.params.task;
19386
19554
  const taskSupport = tool.execution?.taskSupport;
19387
19555
  const isTaskHandler = "createTask" in tool.handler;
19388
19556
  if ((taskSupport === "required" || taskSupport === "optional") && !isTaskHandler) {
19389
- throw new McpError(ErrorCode.InternalError, `Tool ${request.params.name} has taskSupport '${taskSupport}' but was not registered with registerToolTask`);
19557
+ throw new McpError(ErrorCode.InternalError, `Tool ${request2.params.name} has taskSupport '${taskSupport}' but was not registered with registerToolTask`);
19390
19558
  }
19391
19559
  if (taskSupport === "required" && !isTaskRequest) {
19392
- throw new McpError(ErrorCode.MethodNotFound, `Tool ${request.params.name} requires task augmentation (taskSupport: 'required')`);
19560
+ throw new McpError(ErrorCode.MethodNotFound, `Tool ${request2.params.name} requires task augmentation (taskSupport: 'required')`);
19393
19561
  }
19394
19562
  if (taskSupport === "optional" && !isTaskRequest && isTaskHandler) {
19395
- return await this.handleAutomaticTaskPolling(tool, request, extra);
19563
+ return await this.handleAutomaticTaskPolling(tool, request2, extra);
19396
19564
  }
19397
- const args = await this.validateToolInput(tool, request.params.arguments, request.params.name);
19565
+ const args = await this.validateToolInput(tool, request2.params.arguments, request2.params.name);
19398
19566
  const result = await this.executeToolHandler(tool, args, extra);
19399
19567
  if (isTaskRequest) {
19400
19568
  return result;
19401
19569
  }
19402
- await this.validateToolOutput(tool, result, request.params.name);
19570
+ await this.validateToolOutput(tool, result, request2.params.name);
19403
19571
  return result;
19404
19572
  } catch (error2) {
19405
19573
  if (error2 instanceof McpError) {
@@ -19482,11 +19650,11 @@ class McpServer {
19482
19650
  return await Promise.resolve(typedHandler(extra));
19483
19651
  }
19484
19652
  }
19485
- async handleAutomaticTaskPolling(tool, request, extra) {
19653
+ async handleAutomaticTaskPolling(tool, request2, extra) {
19486
19654
  if (!extra.taskStore) {
19487
19655
  throw new Error("No task store provided for task-capable tool.");
19488
19656
  }
19489
- const args = await this.validateToolInput(tool, request.params.arguments, request.params.name);
19657
+ const args = await this.validateToolInput(tool, request2.params.arguments, request2.params.name);
19490
19658
  const handler = tool.handler;
19491
19659
  const taskExtra = { ...extra, taskStore: extra.taskStore };
19492
19660
  const createTaskResult = args ? await Promise.resolve(handler.createTask(args, taskExtra)) : await Promise.resolve(handler.createTask(taskExtra));
@@ -19511,21 +19679,21 @@ class McpServer {
19511
19679
  this.server.registerCapabilities({
19512
19680
  completions: {}
19513
19681
  });
19514
- this.server.setRequestHandler(CompleteRequestSchema, async (request) => {
19515
- switch (request.params.ref.type) {
19682
+ this.server.setRequestHandler(CompleteRequestSchema, async (request2) => {
19683
+ switch (request2.params.ref.type) {
19516
19684
  case "ref/prompt":
19517
- assertCompleteRequestPrompt(request);
19518
- return this.handlePromptCompletion(request, request.params.ref);
19685
+ assertCompleteRequestPrompt(request2);
19686
+ return this.handlePromptCompletion(request2, request2.params.ref);
19519
19687
  case "ref/resource":
19520
- assertCompleteRequestResourceTemplate(request);
19521
- return this.handleResourceCompletion(request, request.params.ref);
19688
+ assertCompleteRequestResourceTemplate(request2);
19689
+ return this.handleResourceCompletion(request2, request2.params.ref);
19522
19690
  default:
19523
- throw new McpError(ErrorCode.InvalidParams, `Invalid completion reference: ${request.params.ref}`);
19691
+ throw new McpError(ErrorCode.InvalidParams, `Invalid completion reference: ${request2.params.ref}`);
19524
19692
  }
19525
19693
  });
19526
19694
  this._completionHandlerInitialized = true;
19527
19695
  }
19528
- async handlePromptCompletion(request, ref) {
19696
+ async handlePromptCompletion(request2, ref) {
19529
19697
  const prompt = this._registeredPrompts[ref.name];
19530
19698
  if (!prompt) {
19531
19699
  throw new McpError(ErrorCode.InvalidParams, `Prompt ${ref.name} not found`);
@@ -19537,7 +19705,7 @@ class McpServer {
19537
19705
  return EMPTY_COMPLETION_RESULT;
19538
19706
  }
19539
19707
  const promptShape = getObjectShape(prompt.argsSchema);
19540
- const field = promptShape?.[request.params.argument.name];
19708
+ const field = promptShape?.[request2.params.argument.name];
19541
19709
  if (!isCompletable(field)) {
19542
19710
  return EMPTY_COMPLETION_RESULT;
19543
19711
  }
@@ -19545,22 +19713,22 @@ class McpServer {
19545
19713
  if (!completer) {
19546
19714
  return EMPTY_COMPLETION_RESULT;
19547
19715
  }
19548
- const suggestions = await completer(request.params.argument.value, request.params.context);
19716
+ const suggestions = await completer(request2.params.argument.value, request2.params.context);
19549
19717
  return createCompletionResult(suggestions);
19550
19718
  }
19551
- async handleResourceCompletion(request, ref) {
19719
+ async handleResourceCompletion(request2, ref) {
19552
19720
  const template = Object.values(this._registeredResourceTemplates).find((t) => t.resourceTemplate.uriTemplate.toString() === ref.uri);
19553
19721
  if (!template) {
19554
19722
  if (this._registeredResources[ref.uri]) {
19555
19723
  return EMPTY_COMPLETION_RESULT;
19556
19724
  }
19557
- throw new McpError(ErrorCode.InvalidParams, `Resource template ${request.params.ref.uri} not found`);
19725
+ throw new McpError(ErrorCode.InvalidParams, `Resource template ${request2.params.ref.uri} not found`);
19558
19726
  }
19559
- const completer = template.resourceTemplate.completeCallback(request.params.argument.name);
19727
+ const completer = template.resourceTemplate.completeCallback(request2.params.argument.name);
19560
19728
  if (!completer) {
19561
19729
  return EMPTY_COMPLETION_RESULT;
19562
19730
  }
19563
- const suggestions = await completer(request.params.argument.value, request.params.context);
19731
+ const suggestions = await completer(request2.params.argument.value, request2.params.context);
19564
19732
  return createCompletionResult(suggestions);
19565
19733
  }
19566
19734
  setResourceRequestHandlers() {
@@ -19575,7 +19743,7 @@ class McpServer {
19575
19743
  listChanged: true
19576
19744
  }
19577
19745
  });
19578
- this.server.setRequestHandler(ListResourcesRequestSchema, async (request, extra) => {
19746
+ this.server.setRequestHandler(ListResourcesRequestSchema, async (request2, extra) => {
19579
19747
  const resources = Object.entries(this._registeredResources).filter(([_, resource]) => resource.enabled).map(([uri, resource]) => ({
19580
19748
  uri,
19581
19749
  name: resource.name,
@@ -19604,8 +19772,8 @@ class McpServer {
19604
19772
  }));
19605
19773
  return { resourceTemplates };
19606
19774
  });
19607
- this.server.setRequestHandler(ReadResourceRequestSchema, async (request, extra) => {
19608
- const uri = new URL(request.params.uri);
19775
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request2, extra) => {
19776
+ const uri = new URL(request2.params.uri);
19609
19777
  const resource = this._registeredResources[uri.toString()];
19610
19778
  if (resource) {
19611
19779
  if (!resource.enabled) {
@@ -19644,21 +19812,21 @@ class McpServer {
19644
19812
  };
19645
19813
  })
19646
19814
  }));
19647
- this.server.setRequestHandler(GetPromptRequestSchema, async (request, extra) => {
19648
- const prompt = this._registeredPrompts[request.params.name];
19815
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request2, extra) => {
19816
+ const prompt = this._registeredPrompts[request2.params.name];
19649
19817
  if (!prompt) {
19650
- throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.name} not found`);
19818
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${request2.params.name} not found`);
19651
19819
  }
19652
19820
  if (!prompt.enabled) {
19653
- throw new McpError(ErrorCode.InvalidParams, `Prompt ${request.params.name} disabled`);
19821
+ throw new McpError(ErrorCode.InvalidParams, `Prompt ${request2.params.name} disabled`);
19654
19822
  }
19655
19823
  if (prompt.argsSchema) {
19656
19824
  const argsObj = normalizeObjectSchema(prompt.argsSchema);
19657
- const parseResult = await safeParseAsync2(argsObj, request.params.arguments);
19825
+ const parseResult = await safeParseAsync2(argsObj, request2.params.arguments);
19658
19826
  if (!parseResult.success) {
19659
19827
  const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
19660
19828
  const errorMessage = getParseErrorMessage(error2);
19661
- throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request.params.name}: ${errorMessage}`);
19829
+ throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for prompt ${request2.params.name}: ${errorMessage}`);
19662
19830
  }
19663
19831
  const args = parseResult.data;
19664
19832
  const cb = prompt.callback;
@@ -32396,8 +32564,8 @@ function hex2(_params) {
32396
32564
  return _stringFormat2(ZodCustomStringFormat, "hex", exports_regexes2.hex, _params);
32397
32565
  }
32398
32566
  function hash(alg, params) {
32399
- const enc = params?.enc ?? "hex";
32400
- const format = `${alg}_${enc}`;
32567
+ const enc2 = params?.enc ?? "hex";
32568
+ const format = `${alg}_${enc2}`;
32401
32569
  const regex = exports_regexes2[format];
32402
32570
  if (!regex)
32403
32571
  throw new Error(`Unrecognized hash format: ${format}`);
@@ -33967,86 +34135,6 @@ ${answers.length} answer(s)`;
33967
34135
  };
33968
34136
  }
33969
34137
 
33970
- // src/notexClient.ts
33971
- var DEFAULT_API_URL = "http://localhost:3000";
33972
- function resolveApiUrl(env = process.env) {
33973
- return env.NOTEX_API_URL?.trim() || DEFAULT_API_URL;
33974
- }
33975
- async function request(config3, fetchImpl, method, path2, body) {
33976
- let res;
33977
- try {
33978
- res = await fetchImpl(`${config3.baseUrl}${path2}`, {
33979
- method,
33980
- headers: {
33981
- "x-api-key": config3.apiKey,
33982
- ...body !== undefined ? { "content-type": "application/json" } : {}
33983
- },
33984
- body: body !== undefined ? JSON.stringify(body) : undefined
33985
- });
33986
- } catch (err) {
33987
- throw new OpError(ERROR_CODES.notexApiError, `Could not reach the Notex API at ${config3.baseUrl}`, err);
33988
- }
33989
- if (res.status === 401)
33990
- throw new OpError(ERROR_CODES.unauthorized, "Notex rejected the API key");
33991
- if (res.status === 403)
33992
- throw new OpError(ERROR_CODES.forbidden, "Not authorized for this Project");
33993
- if (res.status === 404)
33994
- throw new OpError(ERROR_CODES.notFound, "Not found in Notex");
33995
- if (!res.ok) {
33996
- const body2 = await res.json().catch(() => ({}));
33997
- throw new OpError(ERROR_CODES.notexApiError, body2.error?.message ?? `Notex API error (HTTP ${res.status})`);
33998
- }
33999
- return await res.json();
34000
- }
34001
- var enc = (id) => encodeURIComponent(id);
34002
- function createNotexClient(config3, fetchImpl = fetch) {
34003
- return {
34004
- listContexts: (organizationId, repositoryId) => request(config3, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/repositories/${enc(repositoryId)}/contexts`),
34005
- getContext: (organizationId, id) => request(config3, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(id)}`),
34006
- createContext: (organizationId, repositoryId, question) => request(config3, fetchImpl, "POST", `/api/v1/organizations/${enc(organizationId)}/repositories/${enc(repositoryId)}/contexts`, { question }),
34007
- listFiles: (organizationId, contextId) => request(config3, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(contextId)}/files`),
34008
- getFile: (organizationId, id) => request(config3, fetchImpl, "GET", `/api/v1/organizations/${enc(organizationId)}/files/${enc(id)}`),
34009
- createFile: (organizationId, contextId, file2) => request(config3, fetchImpl, "POST", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(contextId)}/files`, file2),
34010
- deleteContext: (organizationId, id) => request(config3, fetchImpl, "DELETE", `/api/v1/organizations/${enc(organizationId)}/contexts/${enc(id)}`)
34011
- };
34012
- }
34013
-
34014
- // src/notexConfig.ts
34015
- import { readFileSync as readFileSync3 } from "node:fs";
34016
- import { join as join2 } from "node:path";
34017
- function configFilePath(checkoutPath) {
34018
- return join2(checkoutPath, ".notex", "notex.json");
34019
- }
34020
- function nonEmptyString(value) {
34021
- return typeof value === "string" && value.length > 0;
34022
- }
34023
- function loadNotexConfig(checkoutPath, env = process.env) {
34024
- let raw;
34025
- try {
34026
- raw = readFileSync3(configFilePath(checkoutPath), "utf8");
34027
- } catch {
34028
- return { kind: "unlinked", reason: "no .notex/notex.json found" };
34029
- }
34030
- let parsed;
34031
- try {
34032
- parsed = JSON.parse(raw);
34033
- } catch {
34034
- return { kind: "unlinked", reason: ".notex/notex.json is not valid JSON" };
34035
- }
34036
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
34037
- return { kind: "unlinked", reason: ".notex/notex.json must be a JSON object" };
34038
- }
34039
- const { organizationId, projectId, repositoryId, apiKey } = parsed;
34040
- if (!nonEmptyString(organizationId) || !nonEmptyString(projectId) || !nonEmptyString(repositoryId)) {
34041
- return { kind: "unlinked", reason: ".notex/notex.json is missing organizationId, projectId, or repositoryId" };
34042
- }
34043
- const resolvedKey = nonEmptyString(env.NOTEX_API_KEY) ? env.NOTEX_API_KEY : apiKey;
34044
- if (!nonEmptyString(resolvedKey)) {
34045
- return { kind: "unlinked", reason: ".notex/notex.json is missing apiKey, and NOTEX_API_KEY is not set" };
34046
- }
34047
- return { kind: "linked", config: { organizationId, projectId, repositoryId, apiKey: resolvedKey } };
34048
- }
34049
-
34050
34138
  // src/retrievalLog.ts
34051
34139
  function createRetrievalLog() {
34052
34140
  const seen = new Set;
@@ -34095,16 +34183,20 @@ var HELP = `notex-companion — local retrieval companion over a checkout's grap
34095
34183
  Usage:
34096
34184
  notex-companion [serve] [options] Start the loopback HTTP server (default command)
34097
34185
  notex-companion mcp Start the stdio MCP server
34186
+ notex-companion link [options] Write .notex/notex.json, pairing this checkout to a Notex Repository
34098
34187
 
34099
34188
  Options for serve:
34100
34189
  --port <n> Port to bind (default 7717)
34101
34190
  --origin <url> Additional allowed CORS origin, beyond the built-in defaults. Repeatable.
34102
34191
  --rotate-token Generate a new pairing token, invalidating the old one
34103
34192
  -h, --help Show this message
34104
- `;
34105
34193
 
34106
- class CliUsageError extends Error {
34107
- }
34194
+ Options for link (all required):
34195
+ --organization-id <id>
34196
+ --project-id <id>
34197
+ --repository-id <id>
34198
+ --api-key <key> Generated from Notex Settings → API keys
34199
+ `;
34108
34200
  function parseServeArgs(args) {
34109
34201
  const origins = [];
34110
34202
  let port;
@@ -34137,6 +34229,17 @@ function parseServeArgs(args) {
34137
34229
  }
34138
34230
  return { port, origins, rotateToken };
34139
34231
  }
34232
+ function runLink(args) {
34233
+ const parsed = parseLinkArgs(args);
34234
+ const checkoutPath = process.cwd();
34235
+ link(parsed, { checkoutPath }).then(() => {
34236
+ console.log(`notex-companion: linked ${checkoutPath} to repository ${parsed.repositoryId}`);
34237
+ console.log("notex-companion: wrote .notex/notex.json (mode 0600)");
34238
+ }, (err) => {
34239
+ console.error(`notex-companion link: ${err instanceof Error ? err.message : String(err)}`);
34240
+ process.exit(1);
34241
+ });
34242
+ }
34140
34243
  function runMcp() {
34141
34244
  startMcpServer(process.cwd()).catch((err) => {
34142
34245
  console.error(`notex-companion mcp: failed to start: ${err instanceof Error ? err.message : String(err)}`);
@@ -34178,6 +34281,8 @@ function main(argv = process.argv.slice(2)) {
34178
34281
  try {
34179
34282
  if (command === "mcp") {
34180
34283
  runMcp();
34284
+ } else if (command === "link") {
34285
+ runLink(rest);
34181
34286
  } else if (command === undefined || command === "serve" || command.startsWith("-")) {
34182
34287
  runServe(command === "serve" ? rest : argv);
34183
34288
  } else {
@@ -0,0 +1,3 @@
1
+ /** Thrown by argument parsing on bad input — `main()` turns it into a stderr message + exit 1. */
2
+ export declare class CliUsageError extends Error {
3
+ }
package/dist/index.js CHANGED
@@ -704,9 +704,21 @@ async function toWebRequest(req) {
704
704
  }
705
705
 
706
706
  // src/pairing.ts
707
- import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "node:fs";
707
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
708
708
  import { randomBytes } from "node:crypto";
709
- import { dirname, join } from "node:path";
709
+ import { dirname as dirname2, join } from "node:path";
710
+
711
+ // src/atomicWrite.ts
712
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
713
+ import { dirname } from "node:path";
714
+ function atomicWriteFile(path2, content, mode) {
715
+ mkdirSync(dirname(path2), { recursive: true });
716
+ const tmpPath = `${path2}.${process.pid}.tmp`;
717
+ writeFileSync(tmpPath, content, { mode, flag: "wx" });
718
+ renameSync(tmpPath, path2);
719
+ }
720
+
721
+ // src/pairing.ts
710
722
  function pairingFilePath(checkoutPath) {
711
723
  return join(checkoutPath, ".notex", "companion.json");
712
724
  }
@@ -725,14 +737,11 @@ function tokenContents(token) {
725
737
  return JSON.stringify({ token }, null, 2);
726
738
  }
727
739
  function createTokenFileExclusive(path2, token) {
728
- mkdirSync(dirname(path2), { recursive: true });
729
- writeFileSync(path2, tokenContents(token), { mode: 384, flag: "wx" });
740
+ mkdirSync2(dirname2(path2), { recursive: true });
741
+ writeFileSync2(path2, tokenContents(token), { mode: 384, flag: "wx" });
730
742
  }
731
743
  function rewriteTokenFile(path2, token) {
732
- mkdirSync(dirname(path2), { recursive: true });
733
- const tmpPath = `${path2}.${process.pid}.tmp`;
734
- writeFileSync(tmpPath, tokenContents(token), { mode: 384, flag: "wx" });
735
- renameSync(tmpPath, path2);
744
+ atomicWriteFile(path2, tokenContents(token), 384);
736
745
  }
737
746
  function loadOrCreateToken(checkoutPath, opts = {}) {
738
747
  const path2 = pairingFilePath(checkoutPath);
package/dist/link.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export type LinkArgs = {
2
+ organizationId: string;
3
+ projectId: string;
4
+ repositoryId: string;
5
+ apiKey: string;
6
+ };
7
+ export declare function parseLinkArgs(args: Array<string>): LinkArgs;
8
+ /**
9
+ * Validates `args` against the Notex API, then writes `.notex/notex.json`. Throws `CliUsageError`
10
+ * with an actionable message on bad input; writes nothing in that case.
11
+ */
12
+ export declare function link(args: LinkArgs, opts?: {
13
+ checkoutPath: string;
14
+ fetchImpl?: typeof fetch;
15
+ }): Promise<void>;
@@ -12,6 +12,12 @@ export type NotexFile = {
12
12
  content: string;
13
13
  createdAt: string;
14
14
  };
15
+ export type NotexRepository = {
16
+ id: string;
17
+ projectId: string;
18
+ name: string;
19
+ description: string | null;
20
+ };
15
21
  export type NotexClientConfig = {
16
22
  baseUrl: string;
17
23
  apiKey: string;
@@ -23,6 +29,10 @@ export declare function resolveApiUrl(env?: {
23
29
  NOTEX_API_URL?: string;
24
30
  }): string;
25
31
  export declare function createNotexClient(config: NotexClientConfig, fetchImpl?: FetchImpl): {
32
+ /** Used by `link.ts` (TBR-85) to validate organizationId/repositoryId/apiKey together before
33
+ * writing `.notex/notex.json` — the returned `projectId` is checked against the one the user
34
+ * supplied, since no route accepts all three ids at once. */
35
+ getRepository: (organizationId: string, id: string) => Promise<NotexRepository>;
26
36
  listContexts: (organizationId: string, repositoryId: string) => Promise<NotexContext[]>;
27
37
  getContext: (organizationId: string, id: string) => Promise<NotexContext>;
28
38
  createContext: (organizationId: string, repositoryId: string, question: string) => Promise<NotexContext>;
@@ -11,6 +11,8 @@ export type NotexConfigState = {
11
11
  kind: "unlinked";
12
12
  reason: string;
13
13
  };
14
+ /** Also used by `link.ts` (TBR-85), the sole writer of this path — this module is the sole reader. */
15
+ export declare function configFilePath(checkoutPath: string): string;
14
16
  /** `NOTEX_API_KEY` overrides the file's `apiKey` (companion-api.md / notex-mcp-server.md §7). */
15
17
  export declare function loadNotexConfig(checkoutPath: string, env?: {
16
18
  NOTEX_API_KEY?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notex-companion",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Local retrieval companion for Notex — reads a checkout's graphify-out/graph.json and serves deterministic search/query/path/node lookups over loopback HTTP and MCP stdio. No LLM, no graph building, no network beyond 127.0.0.1.",
5
5
  "keywords": ["notex", "graphify", "mcp", "code-graph"],
6
6
  "license": "MIT",