toolcraft 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/cli.js +11 -9
  2. package/dist/error-report.js +109 -36
  3. package/dist/redaction.d.ts +4 -0
  4. package/dist/redaction.js +70 -0
  5. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +33 -9
  6. package/node_modules/@poe-code/config-mutations/dist/formats/json.d.ts +2 -1
  7. package/node_modules/@poe-code/config-mutations/dist/formats/json.js +36 -9
  8. package/node_modules/@poe-code/config-mutations/dist/types.d.ts +2 -0
  9. package/node_modules/@poe-code/design-system/dist/components/browser.js +1 -1
  10. package/node_modules/@poe-code/design-system/dist/explorer/actions.js +1 -1
  11. package/node_modules/@poe-code/design-system/dist/explorer/keymap.js +11 -1
  12. package/node_modules/@poe-code/design-system/dist/explorer/reducer.js +64 -8
  13. package/node_modules/@poe-code/design-system/dist/explorer/render/detail.js +9 -11
  14. package/node_modules/@poe-code/design-system/dist/explorer/render/footer.js +18 -8
  15. package/node_modules/@poe-code/design-system/dist/explorer/render/header.js +11 -18
  16. package/node_modules/@poe-code/design-system/dist/explorer/render/index.js +2 -10
  17. package/node_modules/@poe-code/design-system/dist/explorer/render/list.js +32 -22
  18. package/node_modules/@poe-code/design-system/dist/explorer/render/modal.js +5 -9
  19. package/node_modules/@poe-code/design-system/dist/explorer/render/text.d.ts +12 -0
  20. package/node_modules/@poe-code/design-system/dist/explorer/render/text.js +81 -0
  21. package/node_modules/@poe-code/design-system/dist/explorer/state.d.ts +1 -0
  22. package/node_modules/@poe-code/design-system/dist/explorer/state.js +2 -0
  23. package/node_modules/@poe-code/design-system/dist/prompts/index.js +3 -3
  24. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +24 -3
  25. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +1 -0
  26. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-sync.js +8 -0
  27. package/node_modules/auth-store/dist/keychain-store.js +20 -1
  28. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +6 -3
  29. package/node_modules/tiny-mcp-client/dist/internal.d.ts +2 -0
  30. package/node_modules/tiny-mcp-client/dist/internal.js +30 -13
  31. package/node_modules/tiny-mcp-client/src/internal.ts +35 -16
  32. package/node_modules/tiny-mcp-client/src/transports.test.ts +68 -0
  33. package/package.json +2 -2
@@ -171,6 +171,7 @@ function spawnControlCommand(engine, context, args) {
171
171
  stdio: "ignore"
172
172
  });
173
173
  child.once("error", () => undefined);
174
+ child.unref();
174
175
  }
175
176
  catch {
176
177
  return;
@@ -126,6 +126,14 @@ function buildVerifyReport(lookup, opts) {
126
126
  export async function syncGhProject(opts) {
127
127
  const client = resolveGhClient(opts);
128
128
  let lookup = await lookupProject(client, opts.owner, opts.number);
129
+ const initialReport = buildVerifyReport(lookup, opts);
130
+ if (initialReport.ok || opts.yes !== true) {
131
+ return {
132
+ ...initialReport,
133
+ created: [],
134
+ updated: []
135
+ };
136
+ }
129
137
  let resolvedNumber = opts.number;
130
138
  const created = [];
131
139
  if (lookup.project === null) {
@@ -64,6 +64,19 @@ function runSecurityCommand(command, args, options) {
64
64
  });
65
65
  let stdout = "";
66
66
  let stderr = "";
67
+ let stdinErrorMessage;
68
+ const appendStderr = (message) => {
69
+ stderr = stderr.length === 0
70
+ ? message
71
+ : `${stderr}${stderr.endsWith("\n") ? "" : "\n"}${message}`;
72
+ };
73
+ const appendStdinError = () => {
74
+ if (stdinErrorMessage === undefined) {
75
+ return;
76
+ }
77
+ appendStderr(stdinErrorMessage);
78
+ stdinErrorMessage = undefined;
79
+ };
67
80
  child.stdout?.setEncoding("utf8");
68
81
  child.stdout?.on("data", (chunk) => {
69
82
  stdout += chunk.toString();
@@ -73,17 +86,23 @@ function runSecurityCommand(command, args, options) {
73
86
  stderr += chunk.toString();
74
87
  });
75
88
  if (options?.stdin !== undefined) {
89
+ child.stdin?.once("error", (error) => {
90
+ stdinErrorMessage = error instanceof Error ? error.message : String(error);
91
+ });
76
92
  child.stdin?.end(options.stdin);
77
93
  }
78
94
  child.on("error", (error) => {
79
95
  const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
96
+ appendStdinError();
97
+ appendStderr(message);
80
98
  resolve({
81
99
  stdout,
82
- stderr: stderr ? `${stderr}${message}` : message,
100
+ stderr,
83
101
  exitCode: 127
84
102
  });
85
103
  });
86
104
  child.on("close", (code) => {
105
+ appendStdinError();
87
106
  resolve({
88
107
  stdout,
89
108
  stderr,
@@ -61,12 +61,15 @@ export function createAuthStoreClientStore(options) {
61
61
  }
62
62
  function createNamedSecretStore(key, options, defaults) {
63
63
  const hash = crypto.createHash("sha256").update(key).digest("hex");
64
- const parsedFilePath = options.fileStore?.filePath === undefined ? null : path.parse(options.fileStore.filePath);
64
+ const configuredFilePath = options.fileStore?.filePath;
65
+ const parsedFilePath = configuredFilePath === undefined ? null : path.parse(configuredFilePath);
65
66
  const fileStore = {
66
67
  ...options.fileStore,
68
+ filePath: parsedFilePath === null
69
+ ? undefined
70
+ : path.join(parsedFilePath.dir, `${parsedFilePath.name}-${hash}${parsedFilePath.ext || ".enc"}`),
67
71
  salt: options.fileStore?.salt ?? defaults.salt,
68
- defaultDirectory: parsedFilePath?.dir ||
69
- options.fileStore?.defaultDirectory ||
72
+ defaultDirectory: options.fileStore?.defaultDirectory ||
70
73
  defaults.directory,
71
74
  defaultFileName: parsedFilePath === null
72
75
  ? `${hash}.enc`
@@ -400,6 +400,7 @@ export declare class StdioTransport implements McpTransport {
400
400
  private static readonly STDERR_MAX_LENGTH;
401
401
  constructor({ command, args, cwd, env, spawn: spawnProcess, }: StdioTransportOptions);
402
402
  getStderrOutput(): string;
403
+ private appendStderrOutput;
403
404
  dispose(reason?: Error): void;
404
405
  }
405
406
  export declare class HttpTransport implements McpTransport {
@@ -542,6 +543,7 @@ export declare class JsonRpcMessageLayer {
542
543
  onRequest(method: string, handler: JsonRpcRequestHandler): void;
543
544
  onNotification(method: string, handler: JsonRpcNotificationHandler): void;
544
545
  sendRequest(method: string, params?: unknown, options?: JsonRpcRequestOptions): Promise<unknown>;
546
+ cancelRequest(requestId: RequestId, reason: unknown): boolean;
545
547
  dispose(reason?: Error): void;
546
548
  private consumeInput;
547
549
  private resolveInputStreamClosedReason;
@@ -285,6 +285,9 @@ export class McpClient {
285
285
  const abortPromise = new Promise((_, reject) => {
286
286
  const rejectWithAbortReason = () => {
287
287
  sendCancellationNotification();
288
+ if (requestId !== undefined) {
289
+ messageLayer.cancelRequest(requestId, signal.reason);
290
+ }
288
291
  reject(signal.reason);
289
292
  };
290
293
  abortListener = rejectWithAbortReason;
@@ -1511,11 +1514,15 @@ export class StdioTransport {
1511
1514
  const child = this.child;
1512
1515
  this.readable = child.stdout;
1513
1516
  this.writable = child.stdin;
1517
+ const stderrDecoder = new TextDecoder();
1514
1518
  child.stderr.on("data", (chunk) => {
1515
- this.stderrOutput += chunkToString(chunk);
1516
- if (this.stderrOutput.length > StdioTransport.STDERR_MAX_LENGTH) {
1517
- this.stderrOutput = this.stderrOutput.slice(-StdioTransport.STDERR_MAX_LENGTH);
1518
- }
1519
+ const decoded = chunk instanceof Uint8Array
1520
+ ? stderrDecoder.decode(chunk, { stream: true })
1521
+ : `${stderrDecoder.decode()}${String(chunk)}`;
1522
+ this.appendStderrOutput(decoded);
1523
+ });
1524
+ child.stderr.once("end", () => {
1525
+ this.appendStderrOutput(stderrDecoder.decode());
1519
1526
  });
1520
1527
  this.closed = new Promise((resolve) => {
1521
1528
  let settled = false;
@@ -1555,6 +1562,15 @@ export class StdioTransport {
1555
1562
  getStderrOutput() {
1556
1563
  return this.stderrOutput;
1557
1564
  }
1565
+ appendStderrOutput(chunk) {
1566
+ if (chunk.length === 0) {
1567
+ return;
1568
+ }
1569
+ this.stderrOutput += chunk;
1570
+ if (this.stderrOutput.length > StdioTransport.STDERR_MAX_LENGTH) {
1571
+ this.stderrOutput = this.stderrOutput.slice(-StdioTransport.STDERR_MAX_LENGTH);
1572
+ }
1573
+ }
1558
1574
  dispose(reason = new Error("Stdio transport disposed")) {
1559
1575
  void reason;
1560
1576
  if (this.disposed) {
@@ -1966,15 +1982,6 @@ export class McpError extends Error {
1966
1982
  export function serializeJsonRpcMessage(message) {
1967
1983
  return `${JSON.stringify(message)}\n`;
1968
1984
  }
1969
- function chunkToString(chunk) {
1970
- if (typeof chunk === "string") {
1971
- return chunk;
1972
- }
1973
- if (chunk instanceof Uint8Array) {
1974
- return Buffer.from(chunk).toString("utf8");
1975
- }
1976
- return String(chunk);
1977
- }
1978
1985
  function normalizeLine(line) {
1979
1986
  return line.endsWith("\r") ? line.slice(0, -1) : line;
1980
1987
  }
@@ -2168,6 +2175,16 @@ export class JsonRpcMessageLayer {
2168
2175
  }
2169
2176
  });
2170
2177
  }
2178
+ cancelRequest(requestId, reason) {
2179
+ const pending = this.pendingRequests.get(requestId);
2180
+ if (pending === undefined) {
2181
+ return false;
2182
+ }
2183
+ this.pendingRequests.delete(requestId);
2184
+ clearTimeout(pending.timeout);
2185
+ pending.reject(reason);
2186
+ return true;
2187
+ }
2171
2188
  dispose(reason = new Error("JSON-RPC message layer disposed")) {
2172
2189
  if (this.disposedError !== undefined) {
2173
2190
  return;
@@ -478,6 +478,9 @@ export class McpClient {
478
478
  const abortPromise = new Promise<CallToolResult>((_, reject) => {
479
479
  const rejectWithAbortReason = () => {
480
480
  sendCancellationNotification();
481
+ if (requestId !== undefined) {
482
+ messageLayer.cancelRequest(requestId, signal.reason);
483
+ }
481
484
  reject(signal.reason);
482
485
  };
483
486
 
@@ -2350,11 +2353,16 @@ export class StdioTransport implements McpTransport {
2350
2353
 
2351
2354
  this.readable = child.stdout;
2352
2355
  this.writable = child.stdin;
2356
+ const stderrDecoder = new TextDecoder();
2353
2357
  child.stderr.on("data", (chunk: unknown) => {
2354
- this.stderrOutput += chunkToString(chunk);
2355
- if (this.stderrOutput.length > StdioTransport.STDERR_MAX_LENGTH) {
2356
- this.stderrOutput = this.stderrOutput.slice(-StdioTransport.STDERR_MAX_LENGTH);
2357
- }
2358
+ const decoded =
2359
+ chunk instanceof Uint8Array
2360
+ ? stderrDecoder.decode(chunk, { stream: true })
2361
+ : `${stderrDecoder.decode()}${String(chunk)}`;
2362
+ this.appendStderrOutput(decoded);
2363
+ });
2364
+ child.stderr.once("end", () => {
2365
+ this.appendStderrOutput(stderrDecoder.decode());
2358
2366
  });
2359
2367
  this.closed = new Promise((resolve) => {
2360
2368
  let settled = false;
@@ -2405,6 +2413,17 @@ export class StdioTransport implements McpTransport {
2405
2413
  return this.stderrOutput;
2406
2414
  }
2407
2415
 
2416
+ private appendStderrOutput(chunk: string): void {
2417
+ if (chunk.length === 0) {
2418
+ return;
2419
+ }
2420
+
2421
+ this.stderrOutput += chunk;
2422
+ if (this.stderrOutput.length > StdioTransport.STDERR_MAX_LENGTH) {
2423
+ this.stderrOutput = this.stderrOutput.slice(-StdioTransport.STDERR_MAX_LENGTH);
2424
+ }
2425
+ }
2426
+
2408
2427
  dispose(reason = new Error("Stdio transport disposed")): void {
2409
2428
  void reason;
2410
2429
 
@@ -2965,18 +2984,6 @@ export function serializeJsonRpcMessage(message: JsonRpcMessage): string {
2965
2984
  return `${JSON.stringify(message)}\n`;
2966
2985
  }
2967
2986
 
2968
- function chunkToString(chunk: unknown): string {
2969
- if (typeof chunk === "string") {
2970
- return chunk;
2971
- }
2972
-
2973
- if (chunk instanceof Uint8Array) {
2974
- return Buffer.from(chunk).toString("utf8");
2975
- }
2976
-
2977
- return String(chunk);
2978
- }
2979
-
2980
2987
  function normalizeLine(line: string): string {
2981
2988
  return line.endsWith("\r") ? line.slice(0, -1) : line;
2982
2989
  }
@@ -3266,6 +3273,18 @@ export class JsonRpcMessageLayer {
3266
3273
  });
3267
3274
  }
3268
3275
 
3276
+ cancelRequest(requestId: RequestId, reason: unknown): boolean {
3277
+ const pending = this.pendingRequests.get(requestId);
3278
+ if (pending === undefined) {
3279
+ return false;
3280
+ }
3281
+
3282
+ this.pendingRequests.delete(requestId);
3283
+ clearTimeout(pending.timeout);
3284
+ pending.reject(reason);
3285
+ return true;
3286
+ }
3287
+
3269
3288
  dispose(reason = new Error("JSON-RPC message layer disposed")): void {
3270
3289
  if (this.disposedError !== undefined) {
3271
3290
  return;
@@ -1471,6 +1471,40 @@ describe("JsonRpcMessageLayer sendRequest", () => {
1471
1471
  vi.useRealTimers();
1472
1472
  }
1473
1473
  });
1474
+
1475
+ it("clears pending request state and timeout when a request is cancelled", async () => {
1476
+ vi.useFakeTimers();
1477
+ try {
1478
+ const input = new PassThrough();
1479
+ const output = new PassThrough();
1480
+ trackForCleanup(input, output);
1481
+ const layer = new JsonRpcMessageLayer(input, output, 25);
1482
+ const onTimeout = vi.fn();
1483
+ const pendingCount = () =>
1484
+ (
1485
+ layer as unknown as {
1486
+ pendingRequests: Map<unknown, unknown>;
1487
+ }
1488
+ ).pendingRequests.size;
1489
+
1490
+ const responsePromise = layer.sendRequest("slow/method", undefined, {
1491
+ onTimeout,
1492
+ });
1493
+ expect(pendingCount()).toBe(1);
1494
+
1495
+ expect(layer.cancelRequest(1, "user cancelled")).toBe(true);
1496
+
1497
+ await expect(responsePromise).rejects.toBe("user cancelled");
1498
+ expect(pendingCount()).toBe(0);
1499
+
1500
+ await vi.advanceTimersByTimeAsync(25);
1501
+
1502
+ expect(onTimeout).not.toHaveBeenCalled();
1503
+ expect(layer.cancelRequest(1, "already gone")).toBe(false);
1504
+ } finally {
1505
+ vi.useRealTimers();
1506
+ }
1507
+ });
1474
1508
  });
1475
1509
 
1476
1510
  describe("JsonRpcMessageLayer UTF-8 input", () => {
@@ -2643,6 +2677,22 @@ describe("StdioTransport stderr capture", () => {
2643
2677
  expect(transport.getStderrOutput()).toBe("first second");
2644
2678
  });
2645
2679
 
2680
+ it("preserves UTF-8 characters split across stderr chunks", () => {
2681
+ const child = createMockChildProcess();
2682
+ const spawn = vi.fn<StdioSpawn>(() => child);
2683
+
2684
+ const transport = new StdioTransport({
2685
+ command: "node",
2686
+ spawn,
2687
+ });
2688
+
2689
+ const encoded = Buffer.from("é", "utf8");
2690
+ child.stderr.write(encoded.subarray(0, 1));
2691
+ child.stderr.write(encoded.subarray(1));
2692
+
2693
+ expect(transport.getStderrOutput()).toBe("é");
2694
+ });
2695
+
2646
2696
  it("caps stderr at 64KB keeping the tail", () => {
2647
2697
  const child = createMockChildProcess();
2648
2698
  const spawn = vi.fn<StdioSpawn>(() => child);
@@ -7123,6 +7173,21 @@ describe("McpClient callTool", () => {
7123
7173
  throw new Error("Expected initialized notification line to be written");
7124
7174
  }
7125
7175
 
7176
+ const activeMessageLayer = (
7177
+ client as unknown as {
7178
+ messageLayer: JsonRpcMessageLayer | null;
7179
+ }
7180
+ ).messageLayer;
7181
+ if (activeMessageLayer === null) {
7182
+ throw new Error("Expected message layer to exist after connect");
7183
+ }
7184
+ const pendingCount = () =>
7185
+ (
7186
+ activeMessageLayer as unknown as {
7187
+ pendingRequests: Map<unknown, unknown>;
7188
+ }
7189
+ ).pendingRequests.size;
7190
+
7126
7191
  const abortController = new AbortController();
7127
7192
  const callToolPromise = client.callTool(
7128
7193
  {
@@ -7142,6 +7207,8 @@ describe("McpClient callTool", () => {
7142
7207
  const callToolRequest = JSON.parse(callToolLineResult.value) as {
7143
7208
  id: number;
7144
7209
  };
7210
+ expect(pendingCount()).toBe(1);
7211
+
7145
7212
  abortController.abort("user cancelled");
7146
7213
 
7147
7214
  const cancelledLineResult = await iterator.next();
@@ -7158,6 +7225,7 @@ describe("McpClient callTool", () => {
7158
7225
  });
7159
7226
 
7160
7227
  await callToolRejection;
7228
+ expect(pendingCount()).toBe(0);
7161
7229
  await client.close();
7162
7230
  });
7163
7231
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@clack/core": "^1.0.0",
47
47
  "@clack/prompts": "^1.0.0",
48
- "toolcraft-schema": "0.0.25",
48
+ "toolcraft-schema": "0.0.27",
49
49
  "commander": "^14.0.3",
50
50
  "jose": "^6.1.2",
51
51
  "jsonc-parser": "^3.3.1",