@trops/dash-core 0.1.48 → 0.1.49

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.
@@ -22,7 +22,8 @@ var require$$2$4 = require('algoliasearch');
22
22
  var require$$3$2 = require('node:path');
23
23
  var require$$0$3 = require('openai');
24
24
  require('live-plugin-manager');
25
- var require$$0$4 = require('@anthropic-ai/sdk');
25
+ var require$$0$5 = require('@anthropic-ai/sdk');
26
+ var require$$0$4 = require('child_process');
26
27
  var require$$2$6 = require('os');
27
28
  var require$$3$3 = require('adm-zip');
28
29
  var require$$4$1 = require('url');
@@ -525,23 +526,27 @@ var openaiEvents$1 = {
525
526
  const LLM_SEND_MESSAGE$1 = "llm-send-message";
526
527
  const LLM_ABORT_REQUEST$1 = "llm-abort-request";
527
528
  const LLM_LIST_CONNECTED_TOOLS$1 = "llm-list-connected-tools";
529
+ const LLM_CHECK_CLI_AVAILABLE$1 = "llm-check-cli-available";
530
+ const LLM_CLEAR_CLI_SESSION$1 = "llm-clear-cli-session";
528
531
 
529
532
  // --- Main → Renderer (send) ---
530
- const LLM_STREAM_DELTA$2 = "llm-stream-delta";
531
- const LLM_STREAM_TOOL_CALL$2 = "llm-stream-tool-call";
532
- const LLM_STREAM_TOOL_RESULT$2 = "llm-stream-tool-result";
533
- const LLM_STREAM_COMPLETE$2 = "llm-stream-complete";
534
- const LLM_STREAM_ERROR$2 = "llm-stream-error";
533
+ const LLM_STREAM_DELTA$3 = "llm-stream-delta";
534
+ const LLM_STREAM_TOOL_CALL$3 = "llm-stream-tool-call";
535
+ const LLM_STREAM_TOOL_RESULT$3 = "llm-stream-tool-result";
536
+ const LLM_STREAM_COMPLETE$3 = "llm-stream-complete";
537
+ const LLM_STREAM_ERROR$3 = "llm-stream-error";
535
538
 
536
539
  var llmEvents$1 = {
537
540
  LLM_SEND_MESSAGE: LLM_SEND_MESSAGE$1,
538
541
  LLM_ABORT_REQUEST: LLM_ABORT_REQUEST$1,
539
542
  LLM_LIST_CONNECTED_TOOLS: LLM_LIST_CONNECTED_TOOLS$1,
540
- LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
541
- LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
542
- LLM_STREAM_TOOL_RESULT: LLM_STREAM_TOOL_RESULT$2,
543
- LLM_STREAM_COMPLETE: LLM_STREAM_COMPLETE$2,
544
- LLM_STREAM_ERROR: LLM_STREAM_ERROR$2,
543
+ LLM_CHECK_CLI_AVAILABLE: LLM_CHECK_CLI_AVAILABLE$1,
544
+ LLM_CLEAR_CLI_SESSION: LLM_CLEAR_CLI_SESSION$1,
545
+ LLM_STREAM_DELTA: LLM_STREAM_DELTA$3,
546
+ LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$3,
547
+ LLM_STREAM_TOOL_RESULT: LLM_STREAM_TOOL_RESULT$3,
548
+ LLM_STREAM_COMPLETE: LLM_STREAM_COMPLETE$3,
549
+ LLM_STREAM_ERROR: LLM_STREAM_ERROR$3,
545
550
  };
546
551
 
547
552
  /**
@@ -4809,28 +4814,28 @@ const fs$5 = require$$2;
4809
4814
  /**
4810
4815
  * Cached shell PATH result (resolved once, reused for all spawns).
4811
4816
  */
4812
- let _shellPath = null;
4817
+ let _shellPath$1 = null;
4813
4818
 
4814
4819
  /**
4815
4820
  * Get the user's full shell PATH (including nvm, homebrew, volta, etc.).
4816
4821
  * Electron GUI apps on macOS don't inherit the shell PATH, so we
4817
4822
  * resolve it once by invoking a login shell.
4818
4823
  */
4819
- function getShellPath() {
4820
- if (_shellPath !== null) return _shellPath;
4824
+ function getShellPath$1() {
4825
+ if (_shellPath$1 !== null) return _shellPath$1;
4821
4826
 
4822
4827
  try {
4823
4828
  const { execSync } = require("child_process");
4824
4829
  const shell = process.env.SHELL || "/bin/bash";
4825
- _shellPath = execSync(`${shell} -ilc 'echo -n "$PATH"'`, {
4830
+ _shellPath$1 = execSync(`${shell} -ilc 'echo -n "$PATH"'`, {
4826
4831
  encoding: "utf8",
4827
4832
  timeout: 5000,
4828
4833
  });
4829
4834
  } catch {
4830
- _shellPath = process.env.PATH || "";
4835
+ _shellPath$1 = process.env.PATH || "";
4831
4836
  }
4832
4837
 
4833
- return _shellPath;
4838
+ return _shellPath$1;
4834
4839
  }
4835
4840
 
4836
4841
  /**
@@ -4948,7 +4953,7 @@ const mcpController$2 = {
4948
4953
  const env = { ...process.env };
4949
4954
  // Ensure full shell PATH is available (Electron GUI apps
4950
4955
  // on macOS don't inherit nvm/homebrew paths)
4951
- env.PATH = getShellPath();
4956
+ env.PATH = getShellPath$1();
4952
4957
  if (mcpConfig.envMapping && credentials) {
4953
4958
  Object.entries(mcpConfig.envMapping).forEach(
4954
4959
  ([envVar, credentialKey]) => {
@@ -6317,6 +6322,392 @@ const pluginController$1 = {
6317
6322
 
6318
6323
  var pluginController_1 = pluginController$1;
6319
6324
 
6325
+ /**
6326
+ * cliController.js
6327
+ *
6328
+ * Manages Claude Code CLI (`claude -p`) as an alternative LLM backend.
6329
+ * Spawns the CLI subprocess, parses stream-json NDJSON output, and emits
6330
+ * the same LLM_STREAM_* events as the Anthropic SDK path.
6331
+ *
6332
+ * Users with a Claude Pro/Max subscription and Claude Code installed
6333
+ * can use the Chat widget without a separate API key.
6334
+ */
6335
+
6336
+ const { spawn, execSync } = require$$0$4;
6337
+ const {
6338
+ LLM_STREAM_DELTA: LLM_STREAM_DELTA$2,
6339
+ LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$2,
6340
+ LLM_STREAM_TOOL_RESULT: LLM_STREAM_TOOL_RESULT$2,
6341
+ LLM_STREAM_COMPLETE: LLM_STREAM_COMPLETE$2,
6342
+ LLM_STREAM_ERROR: LLM_STREAM_ERROR$2,
6343
+ } = llmEvents$1;
6344
+
6345
+ /**
6346
+ * Cached shell PATH result (resolved once, reused for all spawns).
6347
+ * Same pattern as mcpController.js.
6348
+ */
6349
+ let _shellPath = null;
6350
+
6351
+ function getShellPath() {
6352
+ if (_shellPath !== null) return _shellPath;
6353
+
6354
+ try {
6355
+ const shell = process.env.SHELL || "/bin/bash";
6356
+ _shellPath = execSync(`${shell} -ilc 'echo -n "$PATH"'`, {
6357
+ encoding: "utf8",
6358
+ timeout: 5000,
6359
+ });
6360
+ } catch {
6361
+ _shellPath = process.env.PATH || "";
6362
+ }
6363
+
6364
+ return _shellPath;
6365
+ }
6366
+
6367
+ /**
6368
+ * Cached CLI binary path (resolved once via `which claude`).
6369
+ */
6370
+ let _cliBinaryPath = undefined; // undefined = not yet checked
6371
+
6372
+ function resolveCliBinary() {
6373
+ if (_cliBinaryPath !== undefined) return _cliBinaryPath;
6374
+
6375
+ try {
6376
+ const fullPath = getShellPath();
6377
+ _cliBinaryPath = execSync("which claude", {
6378
+ encoding: "utf8",
6379
+ timeout: 5000,
6380
+ env: { ...process.env, PATH: fullPath },
6381
+ }).trim();
6382
+ } catch {
6383
+ _cliBinaryPath = null;
6384
+ }
6385
+
6386
+ return _cliBinaryPath;
6387
+ }
6388
+
6389
+ /**
6390
+ * Active CLI processes for abort support.
6391
+ * Map<requestId, ChildProcess>
6392
+ */
6393
+ const activeProcesses = new Map();
6394
+
6395
+ /**
6396
+ * Session IDs for conversation continuity.
6397
+ * Map<widgetUuid, sessionId>
6398
+ */
6399
+ const sessions = new Map();
6400
+
6401
+ /**
6402
+ * Send events safely to a window.
6403
+ */
6404
+ function safeSend(win, channel, data) {
6405
+ if (win && !win.isDestroyed()) {
6406
+ win.webContents.send(channel, data);
6407
+ }
6408
+ }
6409
+
6410
+ const cliController$2 = {
6411
+ /**
6412
+ * isAvailable
6413
+ * Check if the Claude Code CLI is installed and accessible.
6414
+ *
6415
+ * @returns {{ available: boolean, path?: string }}
6416
+ */
6417
+ isAvailable: () => {
6418
+ const binaryPath = resolveCliBinary();
6419
+ if (binaryPath) {
6420
+ return { available: true, path: binaryPath };
6421
+ }
6422
+ return { available: false };
6423
+ },
6424
+
6425
+ /**
6426
+ * sendMessage
6427
+ * Stream a response from the Claude Code CLI with NDJSON parsing.
6428
+ *
6429
+ * @param {BrowserWindow} win - the window to send stream events to
6430
+ * @param {string} requestId - unique ID for this request
6431
+ * @param {object} params - { model, messages, systemPrompt, maxToolRounds, widgetUuid }
6432
+ */
6433
+ sendMessage: async (win, requestId, params) => {
6434
+ const { model, messages, systemPrompt, widgetUuid } = params;
6435
+
6436
+ const binaryPath = resolveCliBinary();
6437
+ if (!binaryPath) {
6438
+ safeSend(win, LLM_STREAM_ERROR$2, {
6439
+ requestId,
6440
+ error:
6441
+ "Claude Code CLI not found. Install from https://claude.ai/download",
6442
+ code: "CLI_NOT_FOUND",
6443
+ });
6444
+ return;
6445
+ }
6446
+
6447
+ // Build CLI args
6448
+ const args = ["-p", "--output-format", "stream-json", "--verbose"];
6449
+
6450
+ if (model) {
6451
+ args.push("--model", model);
6452
+ }
6453
+
6454
+ if (systemPrompt) {
6455
+ args.push("--append-system-prompt", systemPrompt);
6456
+ }
6457
+
6458
+ // Resume existing session for conversation continuity
6459
+ const sessionId = widgetUuid ? sessions.get(widgetUuid) : null;
6460
+ if (sessionId) {
6461
+ args.push("--resume", sessionId);
6462
+ }
6463
+
6464
+ // Extract the user message (last user message in the array)
6465
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
6466
+ const userText =
6467
+ typeof lastUserMsg?.content === "string"
6468
+ ? lastUserMsg.content
6469
+ : Array.isArray(lastUserMsg?.content)
6470
+ ? lastUserMsg.content
6471
+ .filter((b) => b.type === "text")
6472
+ .map((b) => b.text)
6473
+ .join("\n")
6474
+ : "";
6475
+
6476
+ if (!userText) {
6477
+ safeSend(win, LLM_STREAM_ERROR$2, {
6478
+ requestId,
6479
+ error: "No user message to send.",
6480
+ code: "CLI_ERROR",
6481
+ });
6482
+ return;
6483
+ }
6484
+
6485
+ try {
6486
+ const fullPath = getShellPath();
6487
+ const child = spawn(binaryPath, args, {
6488
+ env: { ...process.env, PATH: fullPath },
6489
+ stdio: ["pipe", "pipe", "pipe"],
6490
+ });
6491
+
6492
+ activeProcesses.set(requestId, child);
6493
+
6494
+ // Pipe user message via stdin (not visible in ps)
6495
+ child.stdin.write(userText);
6496
+ child.stdin.end();
6497
+
6498
+ let stdoutBuffer = "";
6499
+ let stderrBuffer = "";
6500
+ let capturedSessionId = null;
6501
+ let retried = false;
6502
+
6503
+ // Track active tool calls for mapping results
6504
+ const activeToolCalls = new Map();
6505
+
6506
+ child.stdout.on("data", (chunk) => {
6507
+ stdoutBuffer += chunk.toString();
6508
+
6509
+ // Process complete lines
6510
+ const lines = stdoutBuffer.split("\n");
6511
+ stdoutBuffer = lines.pop(); // keep incomplete line in buffer
6512
+
6513
+ for (const line of lines) {
6514
+ if (!line.trim()) continue;
6515
+
6516
+ let parsed;
6517
+ try {
6518
+ parsed = JSON.parse(line);
6519
+ } catch {
6520
+ console.warn("[cliController] Skipping invalid JSON line:", line);
6521
+ continue;
6522
+ }
6523
+
6524
+ // Capture session ID from any message that has it
6525
+ if (parsed.session_id && widgetUuid) {
6526
+ capturedSessionId = parsed.session_id;
6527
+ sessions.set(widgetUuid, capturedSessionId);
6528
+ }
6529
+
6530
+ // Map CLI stream-json events to IPC events
6531
+ if (parsed.type === "content_block_delta") {
6532
+ if (parsed.delta?.type === "text_delta" && parsed.delta.text) {
6533
+ safeSend(win, LLM_STREAM_DELTA$2, {
6534
+ requestId,
6535
+ text: parsed.delta.text,
6536
+ });
6537
+ } else if (parsed.delta?.type === "input_json_delta") {
6538
+ // Update tool input incrementally
6539
+ const tc = activeToolCalls.get(parsed.index);
6540
+ if (tc) {
6541
+ tc.partialInput =
6542
+ (tc.partialInput || "") + (parsed.delta.partial_json || "");
6543
+ }
6544
+ }
6545
+ } else if (parsed.type === "content_block_start") {
6546
+ if (parsed.content_block?.type === "tool_use") {
6547
+ const toolBlock = parsed.content_block;
6548
+ activeToolCalls.set(parsed.index, {
6549
+ toolUseId: toolBlock.id,
6550
+ toolName: toolBlock.name,
6551
+ partialInput: "",
6552
+ });
6553
+ safeSend(win, LLM_STREAM_TOOL_CALL$2, {
6554
+ requestId,
6555
+ toolUseId: toolBlock.id,
6556
+ toolName: toolBlock.name,
6557
+ serverName: "Claude Code",
6558
+ input: toolBlock.input || {},
6559
+ });
6560
+ }
6561
+ } else if (parsed.type === "content_block_stop") {
6562
+ // Tool call completed — try to parse the accumulated input
6563
+ const tc = activeToolCalls.get(parsed.index);
6564
+ if (tc && tc.partialInput) {
6565
+ try {
6566
+ tc.finalInput = JSON.parse(tc.partialInput);
6567
+ } catch {
6568
+ tc.finalInput = tc.partialInput;
6569
+ }
6570
+ }
6571
+ } else if (parsed.type === "message_stop") {
6572
+ // Individual message completed (may be followed by more in tool-use loops)
6573
+ } else if (parsed.type === "result") {
6574
+ // Final result — conversation complete
6575
+ const content = [];
6576
+ if (parsed.result) {
6577
+ content.push({ type: "text", text: parsed.result });
6578
+ }
6579
+
6580
+ safeSend(win, LLM_STREAM_COMPLETE$2, {
6581
+ requestId,
6582
+ content,
6583
+ stopReason: parsed.stop_reason || "end_turn",
6584
+ usage: parsed.usage || {},
6585
+ });
6586
+ }
6587
+ }
6588
+ });
6589
+
6590
+ child.stderr.on("data", (chunk) => {
6591
+ stderrBuffer += chunk.toString();
6592
+ });
6593
+
6594
+ child.on("error", (err) => {
6595
+ activeProcesses.delete(requestId);
6596
+ safeSend(win, LLM_STREAM_ERROR$2, {
6597
+ requestId,
6598
+ error: `Failed to start Claude CLI: ${err.message}`,
6599
+ code: "CLI_SPAWN_ERROR",
6600
+ });
6601
+ });
6602
+
6603
+ child.on("close", (code) => {
6604
+ activeProcesses.delete(requestId);
6605
+
6606
+ // Process any remaining buffer
6607
+ if (stdoutBuffer.trim()) {
6608
+ try {
6609
+ const parsed = JSON.parse(stdoutBuffer);
6610
+ if (parsed.session_id && widgetUuid) {
6611
+ sessions.set(widgetUuid, parsed.session_id);
6612
+ }
6613
+ if (parsed.type === "result") {
6614
+ const content = [];
6615
+ if (parsed.result) {
6616
+ content.push({ type: "text", text: parsed.result });
6617
+ }
6618
+ safeSend(win, LLM_STREAM_COMPLETE$2, {
6619
+ requestId,
6620
+ content,
6621
+ stopReason: parsed.stop_reason || "end_turn",
6622
+ usage: parsed.usage || {},
6623
+ });
6624
+ return;
6625
+ }
6626
+ } catch {
6627
+ // ignore
6628
+ }
6629
+ }
6630
+
6631
+ if (code !== 0 && code !== null) {
6632
+ // Check if resume failed and retry without it
6633
+ if (sessionId && !retried && stderrBuffer.includes("session")) {
6634
+ retried = true;
6635
+ if (widgetUuid) sessions.delete(widgetUuid);
6636
+ // Retry without --resume
6637
+ cliController$2.sendMessage(win, requestId, {
6638
+ ...params,
6639
+ _retryWithoutResume: true,
6640
+ });
6641
+ return;
6642
+ }
6643
+
6644
+ // Check for auth errors
6645
+ if (
6646
+ stderrBuffer.includes("auth") ||
6647
+ stderrBuffer.includes("login") ||
6648
+ stderrBuffer.includes("not authenticated")
6649
+ ) {
6650
+ safeSend(win, LLM_STREAM_ERROR$2, {
6651
+ requestId,
6652
+ error:
6653
+ "Claude Code CLI is not authenticated. Run `claude auth login` in your terminal.",
6654
+ code: "CLI_AUTH_ERROR",
6655
+ });
6656
+ return;
6657
+ }
6658
+
6659
+ safeSend(win, LLM_STREAM_ERROR$2, {
6660
+ requestId,
6661
+ error: `Claude CLI exited with code ${code}${stderrBuffer ? ": " + stderrBuffer.slice(0, 500) : ""}`,
6662
+ code: "CLI_ERROR",
6663
+ });
6664
+ }
6665
+ });
6666
+ } catch (err) {
6667
+ activeProcesses.delete(requestId);
6668
+ safeSend(win, LLM_STREAM_ERROR$2, {
6669
+ requestId,
6670
+ error: `Failed to start Claude CLI: ${err.message}`,
6671
+ code: "CLI_SPAWN_ERROR",
6672
+ });
6673
+ }
6674
+ },
6675
+
6676
+ /**
6677
+ * abortRequest
6678
+ * Kill an in-flight CLI process.
6679
+ *
6680
+ * @param {string} requestId - the request to cancel
6681
+ * @returns {{ success: boolean }}
6682
+ */
6683
+ abortRequest: (requestId) => {
6684
+ const child = activeProcesses.get(requestId);
6685
+ if (child) {
6686
+ child.kill("SIGTERM");
6687
+ activeProcesses.delete(requestId);
6688
+ return { success: true };
6689
+ }
6690
+ return { success: false, message: "Request not found" };
6691
+ },
6692
+
6693
+ /**
6694
+ * clearSession
6695
+ * Remove the stored session ID for a widget (called on "New Chat").
6696
+ *
6697
+ * @param {string} widgetUuid - the widget whose session to clear
6698
+ * @returns {{ success: boolean }}
6699
+ */
6700
+ clearSession: (widgetUuid) => {
6701
+ if (widgetUuid && sessions.has(widgetUuid)) {
6702
+ sessions.delete(widgetUuid);
6703
+ return { success: true };
6704
+ }
6705
+ return { success: false };
6706
+ },
6707
+ };
6708
+
6709
+ var cliController_1 = cliController$2;
6710
+
6320
6711
  /**
6321
6712
  * llmController.js
6322
6713
  *
@@ -6328,8 +6719,9 @@ var pluginController_1 = pluginController$1;
6328
6719
  * per-request, receiving the full messages array each time.
6329
6720
  */
6330
6721
 
6331
- const Anthropic = require$$0$4;
6722
+ const Anthropic = require$$0$5;
6332
6723
  const mcpController$1 = mcpController_1;
6724
+ const cliController$1 = cliController_1;
6333
6725
  const {
6334
6726
  LLM_STREAM_DELTA: LLM_STREAM_DELTA$1,
6335
6727
  LLM_STREAM_TOOL_CALL: LLM_STREAM_TOOL_CALL$1,
@@ -6398,6 +6790,11 @@ const llmController$1 = {
6398
6790
  * @param {object} params - { apiKey, model, messages, tools, toolServerMap, systemPrompt, maxToolRounds }
6399
6791
  */
6400
6792
  sendMessage: async (win, requestId, params) => {
6793
+ // Route to CLI backend if specified
6794
+ if (params.backend === "claude-code") {
6795
+ return cliController$1.sendMessage(win, requestId, params);
6796
+ }
6797
+
6401
6798
  const {
6402
6799
  apiKey,
6403
6800
  model = "claude-sonnet-4-20250514",
@@ -6621,7 +7018,8 @@ const llmController$1 = {
6621
7018
  activeRequests.delete(requestId);
6622
7019
  return { success: true };
6623
7020
  }
6624
- return { success: false, message: "Request not found" };
7021
+ // Fallback to CLI controller
7022
+ return cliController$1.abortRequest(requestId);
6625
7023
  },
6626
7024
  };
6627
7025
 
@@ -7889,6 +8287,8 @@ const {
7889
8287
  LLM_SEND_MESSAGE,
7890
8288
  LLM_ABORT_REQUEST,
7891
8289
  LLM_LIST_CONNECTED_TOOLS,
8290
+ LLM_CHECK_CLI_AVAILABLE,
8291
+ LLM_CLEAR_CLI_SESSION,
7892
8292
  LLM_STREAM_DELTA,
7893
8293
  LLM_STREAM_TOOL_CALL,
7894
8294
  LLM_STREAM_TOOL_RESULT,
@@ -7926,6 +8326,24 @@ const llmApi$2 = {
7926
8326
  */
7927
8327
  listConnectedTools: () => ipcRenderer$2.invoke(LLM_LIST_CONNECTED_TOOLS),
7928
8328
 
8329
+ /**
8330
+ * checkCliAvailable
8331
+ * Check if the Claude Code CLI is installed and accessible.
8332
+ *
8333
+ * @returns {Promise<{ available: boolean, path?: string }>}
8334
+ */
8335
+ checkCliAvailable: () => ipcRenderer$2.invoke(LLM_CHECK_CLI_AVAILABLE),
8336
+
8337
+ /**
8338
+ * clearCliSession
8339
+ * Clear the CLI conversation session for a widget (for "New Chat").
8340
+ *
8341
+ * @param {string} widgetUuid - the widget whose session to clear
8342
+ * @returns {Promise<{ success: boolean }>}
8343
+ */
8344
+ clearCliSession: (widgetUuid) =>
8345
+ ipcRenderer$2.invoke(LLM_CLEAR_CLI_SESSION, { widgetUuid }),
8346
+
7929
8347
  // --- Stream event listeners ---
7930
8348
 
7931
8349
  /**
@@ -9553,6 +9971,7 @@ const openaiController = openaiController_1;
9553
9971
  const menuItemsController = menuItemsController_1;
9554
9972
  const pluginController = pluginController_1;
9555
9973
  const llmController = llmController_1;
9974
+ const cliController = cliController_1;
9556
9975
 
9557
9976
  // --- Utils ---
9558
9977
  const clientCache = requireClientCache();
@@ -9609,6 +10028,7 @@ var electron = {
9609
10028
  menuItemsController,
9610
10029
  pluginController,
9611
10030
  llmController,
10031
+ cliController,
9612
10032
 
9613
10033
  // Controller functions (flat) — spread for convenient destructuring
9614
10034
  ...controllers,