tarsk 0.4.26 → 0.4.28

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 (80) hide show
  1. package/dist/index.js +634 -198
  2. package/dist/public/assets/account-view-DcpSNB3N.js +1 -0
  3. package/dist/public/assets/alert-dialog-CQRWdGnn.js +1 -0
  4. package/dist/public/assets/api-CH5gAPs5.js +1 -0
  5. package/dist/public/assets/{browser-tab-BNkQAd5u.js → browser-tab-CiAz6nc1.js} +1 -1
  6. package/dist/public/assets/chat-input-container-C6U_WFFu.js +21 -0
  7. package/dist/public/assets/context-menu-A9p22S7d.js +1 -0
  8. package/dist/public/assets/conversation-history-view-CD_uVRNY.js +1 -0
  9. package/dist/public/assets/{dialogs-config-BEsTbY1C.js → dialogs-config-vkfELnGl.js} +3 -3
  10. package/dist/public/assets/diff-view-CjbcDB6m.js +3 -0
  11. package/dist/public/assets/explorer-tab-view-BfycKUUW.js +2 -0
  12. package/dist/public/assets/explorer-tree-DTFYhczJ.js +1 -0
  13. package/dist/public/assets/explorer-view-C0KcZ8NQ.js +1 -0
  14. package/dist/public/assets/history-view-Dn0hQIKU.js +1 -0
  15. package/dist/public/assets/index-Bdde81Uo.css +1 -0
  16. package/dist/public/assets/index-DKQVo4Vz.js +29 -0
  17. package/dist/public/assets/markdown-renderer-CNuyXHcy.js +10 -0
  18. package/dist/public/assets/mcp-server-card-DBEBe3Qh.js +1 -0
  19. package/dist/public/assets/onboarding-DslO5AS6.js +1 -0
  20. package/dist/public/assets/onboarding-dialog-CE-GhoDt.js +1 -0
  21. package/dist/public/assets/{page-toolbar-DM-x5q1_.js → page-toolbar-CloU_Kvo.js} +1 -1
  22. package/dist/public/assets/{project-settings-view-wkrbmymL.js → project-settings-view-CxD9JZoB.js} +1 -1
  23. package/dist/public/assets/providers-list-view-CNxVBJlM.js +1 -0
  24. package/dist/public/assets/radio-group-Co0Lg6se.js +1 -0
  25. package/dist/public/assets/react-vendor-DyE9sO8V.js +22 -0
  26. package/dist/public/assets/resizable-dToeUeVD.js +1 -0
  27. package/dist/public/assets/{run-stop-button-CZ62jJDn.js → run-stop-button-DZ4JFwdL.js} +2 -2
  28. package/dist/public/assets/settings-instructions-view-DWzAH2yN.js +1 -0
  29. package/dist/public/assets/settings-mcp-servers-view-q6gGMMVX.js +5 -0
  30. package/dist/public/assets/settings-models-view-BDDFQWWC.js +1 -0
  31. package/dist/public/assets/settings-rules-view-DsEwgDFr.js +8 -0
  32. package/dist/public/assets/settings-skills-view-DV40_L1o.js +2 -0
  33. package/dist/public/assets/settings-slash-commands-view-BEk3BgOY.js +1 -0
  34. package/dist/public/assets/settings-subagents-view-d3P4ylDX.js +2 -0
  35. package/dist/public/assets/settings-view-ClTMPafQ.js +2 -0
  36. package/dist/public/assets/{side-panel-container-DVnN8vFb.js → side-panel-container-BAYoMt7B.js} +2 -2
  37. package/dist/public/assets/skeleton-DveUQR4w.js +1 -0
  38. package/dist/public/assets/standard-list-item-BLlgm1qW.js +1 -0
  39. package/dist/public/assets/store-BcwWHqYr.js +4 -0
  40. package/dist/public/assets/{tab-context-DBrXMyNd.js → tab-context-DWD20a4z.js} +1 -1
  41. package/dist/public/assets/tabs-CgvpwOBj.js +1 -0
  42. package/dist/public/assets/{terminal-panel-COYGYBV5.js → terminal-panel-CkIe2d2K.js} +2 -2
  43. package/dist/public/assets/textarea-C2UIAa7y.js +1 -0
  44. package/dist/public/assets/todos-view-C-awG1dZ.js +1 -0
  45. package/dist/public/assets/{use-toast-BqYP6xbg.js → use-toast-yY7qQuQP.js} +1 -1
  46. package/dist/public/assets/{utils-BavofGRh.js → utils-BKss67Vg.js} +1 -1
  47. package/dist/public/assets/whisper-wasm-B7iuRPFr.js +2 -0
  48. package/dist/public/ide/antigravity.svg +1 -0
  49. package/dist/public/ide/devin.svg +1 -0
  50. package/dist/public/index.html +22 -20
  51. package/dist/public/wasm/libmain-CWYJvMY5.js +3318 -0
  52. package/dist/public/wasm/libmain-D9-QM3iM.mjs +3301 -0
  53. package/package.json +4 -3
  54. package/dist/public/assets/account-view-g3bIVa-z.js +0 -1
  55. package/dist/public/assets/alert-dialog-DXEB8gMn.js +0 -1
  56. package/dist/public/assets/api-DQLZE-zX.js +0 -1
  57. package/dist/public/assets/chat-input-container-C8o4YcJZ.js +0 -1
  58. package/dist/public/assets/context-menu-CThafUml.js +0 -1
  59. package/dist/public/assets/conversation-history-view-64T6FDPb.js +0 -1
  60. package/dist/public/assets/diff-view-DgR-WEkB.js +0 -3
  61. package/dist/public/assets/explorer-tab-view-B53xe5xM.js +0 -2
  62. package/dist/public/assets/explorer-tree-KuYjR6r9.js +0 -1
  63. package/dist/public/assets/explorer-view-Cj6fyxMP.js +0 -1
  64. package/dist/public/assets/history-view-_L50vkTM.js +0 -1
  65. package/dist/public/assets/index-CAoVFXj2.css +0 -1
  66. package/dist/public/assets/index-Q5OqmtNF.js +0 -50
  67. package/dist/public/assets/onboarding-C_q_uRK0.js +0 -1
  68. package/dist/public/assets/onboarding-dialog-Cum5S2wE.js +0 -1
  69. package/dist/public/assets/provider-details-view-jc3yYdzr.js +0 -1
  70. package/dist/public/assets/providers-sidebar-B796hXzU.js +0 -1
  71. package/dist/public/assets/react-vendor-QkuITxN7.js +0 -17
  72. package/dist/public/assets/resizable-GT3fWOfA.js +0 -1
  73. package/dist/public/assets/settings-view-BbL7i34u.js +0 -2
  74. package/dist/public/assets/standard-list-item-ByRgCvKd.js +0 -1
  75. package/dist/public/assets/store-Dyy2UFdD.js +0 -2
  76. package/dist/public/assets/tabs-DxQZhlZp.js +0 -1
  77. package/dist/public/assets/textarea-CUYwgm5q.js +0 -1
  78. package/dist/public/assets/todos-view-DB99PEKH.js +0 -1
  79. /package/dist/public/assets/{dist-CIGnaqp2.js → dist-Bjt_i1zi.js} +0 -0
  80. /package/dist/public/assets/{monaco-c-HyCuEi.js → monaco-SWcefg0q.js} +0 -0
package/dist/index.js CHANGED
@@ -249,7 +249,7 @@ async function initializeSchema(db) {
249
249
  friendlyName TEXT NOT NULL,
250
250
  updatedAt TEXT NOT NULL,
251
251
  FOREIGN KEY (projectId) REFERENCES projects(id) ON DELETE CASCADE,
252
- UNIQUE(projectId, name)
252
+ UNIQUE(projectId, workspace, name)
253
253
  )
254
254
  `);
255
255
  await db.execute(`
@@ -364,8 +364,8 @@ async function runMigrations(db) {
364
364
  WHERE type='table' AND name='conversation_history'
365
365
  `);
366
366
  if (tableInfo.rows.length > 0) {
367
- const tableSql = tableInfo.rows[0].sql;
368
- if (!tableSql.includes("ON DELETE CASCADE")) {
367
+ const tableSql2 = tableInfo.rows[0].sql;
368
+ if (!tableSql2.includes("ON DELETE CASCADE")) {
369
369
  console.log(
370
370
  "[db] Running migration: Adding ON DELETE CASCADE to conversation_history.threadId foreign key"
371
371
  );
@@ -444,8 +444,8 @@ async function runMigrations(db) {
444
444
  WHERE type='table' AND name='threads'
445
445
  `);
446
446
  if (threadsTableInfo.rows.length > 0) {
447
- const tableSql = threadsTableInfo.rows[0].sql;
448
- if (!tableSql.includes("ON DELETE CASCADE")) {
447
+ const tableSql2 = threadsTableInfo.rows[0].sql;
448
+ if (!tableSql2.includes("ON DELETE CASCADE")) {
449
449
  console.log(
450
450
  "[db] Running migration: Adding ON DELETE CASCADE to threads.projectId foreign key"
451
451
  );
@@ -680,7 +680,31 @@ async function runMigrations(db) {
680
680
  friendlyName TEXT NOT NULL,
681
681
  updatedAt TEXT NOT NULL,
682
682
  FOREIGN KEY (projectId) REFERENCES projects(id) ON DELETE CASCADE,
683
- UNIQUE(projectId, name)
683
+ UNIQUE(projectId, workspace, name)
684
+ )
685
+ `);
686
+ await db.execute(`
687
+ CREATE INDEX idx_project_scripts_projectId ON project_scripts(projectId)
688
+ `);
689
+ }
690
+ const projectScriptsInfo = await db.execute(
691
+ `SELECT sql FROM sqlite_master WHERE type='table' AND name='project_scripts'`
692
+ );
693
+ const tableSql = projectScriptsInfo.rows[0];
694
+ if (projectScriptsInfo.rows.length > 0 && typeof tableSql["sql"] === "string" && tableSql["sql"].includes('UNIQUE("projectId", "name")')) {
695
+ console.log("[db] Running migration: Fixing project_scripts UNIQUE constraint");
696
+ await db.execute(`DROP TABLE IF EXISTS project_scripts`);
697
+ await db.execute(`
698
+ CREATE TABLE project_scripts (
699
+ id TEXT PRIMARY KEY,
700
+ projectId TEXT NOT NULL,
701
+ workspace TEXT NOT NULL,
702
+ name TEXT NOT NULL,
703
+ command TEXT NOT NULL,
704
+ friendlyName TEXT NOT NULL,
705
+ updatedAt TEXT NOT NULL,
706
+ FOREIGN KEY (projectId) REFERENCES projects(id) ON DELETE CASCADE,
707
+ UNIQUE(projectId, workspace, name)
684
708
  )
685
709
  `);
686
710
  await db.execute(`
@@ -745,6 +769,14 @@ var PROGRAMS_CONFIG = {
745
769
  Terminal: {
746
770
  name: "Terminal",
747
771
  appName: "Terminal"
772
+ },
773
+ "Devin Desktop": {
774
+ name: "Devin Desktop",
775
+ appName: ["Devin", "Devin Desktop"]
776
+ },
777
+ Antigravity: {
778
+ name: "Antigravity",
779
+ appName: ["com.google.antigravity-ide", "Antigravity IDE"]
748
780
  }
749
781
  };
750
782
  var AVAILABLE_PROGRAMS = Object.keys(PROGRAMS_CONFIG);
@@ -792,7 +824,7 @@ function isValidPackageManager(value) {
792
824
 
793
825
  // src/server.ts
794
826
  import fs3 from "fs";
795
- import { Hono as Hono21 } from "hono";
827
+ import { Hono as Hono23 } from "hono";
796
828
  import { cors } from "hono/cors";
797
829
  import open3 from "open";
798
830
  import path4 from "path";
@@ -1796,9 +1828,9 @@ function createGrepTool(cwd, options) {
1796
1828
  const effectiveLimit = Math.max(1, limit ?? DEFAULT_LIMIT2);
1797
1829
  const formatPath = (filePath) => {
1798
1830
  if (isDirectory) {
1799
- const relative7 = path2.relative(searchPath, filePath);
1800
- if (relative7 && !relative7.startsWith("..")) {
1801
- return relative7.replace(/\\/g, "/");
1831
+ const relative8 = path2.relative(searchPath, filePath);
1832
+ if (relative8 && !relative8.startsWith("..")) {
1833
+ return relative8.replace(/\\/g, "/");
1802
1834
  }
1803
1835
  }
1804
1836
  return path2.basename(filePath);
@@ -4116,16 +4148,56 @@ var ModelManager = class {
4116
4148
  };
4117
4149
 
4118
4150
  // src/features/providers/provider-resolver.ts
4151
+ function getProviderApiKeyEnvName(providerEnv) {
4152
+ if (providerEnv.length === 0) {
4153
+ return "";
4154
+ }
4155
+ const explicitApiKeyEnv = providerEnv.find((envName) => /api_key$/i.test(envName));
4156
+ return explicitApiKeyEnv ?? providerEnv[providerEnv.length - 1];
4157
+ }
4158
+ function getProviderApiUrlEnvName(providerName) {
4159
+ return `${providerName.toUpperCase().replace(/-/g, "_")}_API_URL`;
4160
+ }
4161
+ function getProviderResourceName(providerEnv) {
4162
+ const explicitResourceEnv = providerEnv.find((envName) => /resource_name$/i.test(envName));
4163
+ return explicitResourceEnv ?? providerEnv[0] ?? "";
4164
+ }
4165
+ function resolveAzureApiUrl(providerEnv) {
4166
+ const resourceEnvName = getProviderResourceName(providerEnv);
4167
+ const resourceName = process.env[resourceEnvName];
4168
+ if (!resourceName) {
4169
+ return "";
4170
+ }
4171
+ return `https://${resourceName}.openai.azure.com/openai/v1`;
4172
+ }
4173
+ function resolveTemplate(template, envValues) {
4174
+ let hasMissingValue = false;
4175
+ const resolved = template.replace(/\$\{([A-Z0-9_]+)\}/gi, (_match, envName) => {
4176
+ const value = envValues[envName] ?? process.env[envName] ?? "";
4177
+ if (!value) {
4178
+ hasMissingValue = true;
4179
+ }
4180
+ return value;
4181
+ });
4182
+ return hasMissingValue ? "" : resolved;
4183
+ }
4119
4184
  async function resolveProviderConfig(providerName) {
4120
4185
  const slug = providerName.toLowerCase();
4121
4186
  const catalogProvider = await getCatalogProvider(slug);
4122
4187
  if (!catalogProvider) return null;
4123
- return catalogProvider;
4188
+ const apiKeyEnv = getProviderApiKeyEnvName(catalogProvider.env ?? []);
4189
+ const apiUrlEnvName = getProviderApiUrlEnvName(slug);
4190
+ const api = slug === "azure" ? resolveAzureApiUrl(catalogProvider.env ?? []) : process.env[apiUrlEnvName] || (catalogProvider.api ? resolveTemplate(catalogProvider.api, process.env) : "");
4191
+ return {
4192
+ ...catalogProvider,
4193
+ api,
4194
+ apiKeyEnv
4195
+ };
4124
4196
  }
4125
4197
  async function resolveProviderKeyName(providerName) {
4126
4198
  const slug = providerName.toLowerCase();
4127
4199
  const catalogProvider = await getCatalogProvider(slug);
4128
- if (catalogProvider?.env?.[0]) return catalogProvider.env[0];
4200
+ if (catalogProvider?.env?.length) return getProviderApiKeyEnvName(catalogProvider.env);
4129
4201
  return "";
4130
4202
  }
4131
4203
 
@@ -4322,7 +4394,7 @@ function createGenerateImageTool(options) {
4322
4394
  details: void 0
4323
4395
  };
4324
4396
  }
4325
- let apiKey = providerConfig.env && providerConfig.env.length > 0 ? process.env[providerConfig.env[0]] : void 0;
4397
+ let apiKey = providerConfig.apiKeyEnv ? process.env[providerConfig.apiKeyEnv] : void 0;
4326
4398
  if (!apiKey) {
4327
4399
  const providerKeys = await metadataManager.getProviderKeys();
4328
4400
  apiKey = providerKeys[providerConfig.id];
@@ -4760,8 +4832,12 @@ function createToolSearchTool(options) {
4760
4832
  let matched = [];
4761
4833
  if (parsed.mode === "select") {
4762
4834
  for (const name of parsed.names) {
4763
- const tool = deferredTools.get(name);
4764
- if (tool) matched.push(tool);
4835
+ for (const [toolName, tool] of deferredTools.entries()) {
4836
+ if (toolName.toLowerCase() === name) {
4837
+ matched.push(tool);
4838
+ break;
4839
+ }
4840
+ }
4765
4841
  }
4766
4842
  } else {
4767
4843
  const candidates = [];
@@ -5015,6 +5091,7 @@ var EventQueue = class {
5015
5091
  finalContent = "";
5016
5092
  errorOccurred = false;
5017
5093
  toolCallCount = 0;
5094
+ lastErrorMessage;
5018
5095
  /**
5019
5096
  * Processes Pi Agent events and transforms them into AgentEvent format
5020
5097
  */
@@ -5083,6 +5160,9 @@ var EventQueue = class {
5083
5160
  } else if (event.type === "agent_end") {
5084
5161
  const messages = event.messages;
5085
5162
  const lastAssistant = [...messages].reverse().find((m) => "role" in m && m.role === "assistant");
5163
+ if (lastAssistant && "errorMessage" in lastAssistant && typeof lastAssistant.errorMessage === "string") {
5164
+ this.lastErrorMessage = lastAssistant.errorMessage;
5165
+ }
5086
5166
  if (lastAssistant) {
5087
5167
  const extracted = extractTextFromAssistantMessage(lastAssistant);
5088
5168
  if (extracted && extracted !== this.finalContent) {
@@ -5196,14 +5276,14 @@ async function executeSubagent(options) {
5196
5276
  if (!providerConfig) {
5197
5277
  throw new Error(`Unknown provider for subagent: ${providerName}`);
5198
5278
  }
5199
- let apiKey = providerConfig.env && providerConfig.env.length > 0 ? process.env[providerConfig.env[0]] : void 0;
5279
+ let apiKey = providerConfig.apiKeyEnv ? process.env[providerConfig.apiKeyEnv] : void 0;
5200
5280
  if (!apiKey) {
5201
5281
  const providerKeys = await metadataManager.getProviderKeys();
5202
5282
  apiKey = providerKeys[providerConfig.id];
5203
5283
  }
5204
5284
  if (!apiKey) {
5205
5285
  throw new Error(
5206
- `No API key found for subagent provider: ${providerName}. Set ${providerConfig.env && providerConfig.env.length > 0 ? providerConfig.env[0] : "an API key"} in environment variables or configuration.`
5286
+ `No API key found for subagent provider: ${providerName}. Set ${providerConfig.apiKeyEnv ?? "an API key"} in environment variables or configuration.`
5207
5287
  );
5208
5288
  }
5209
5289
  const resolvedModel = resolveModel(providerName, model, providerConfig);
@@ -6091,10 +6171,94 @@ async function loadAgentSystemPrompt(threadPath, tools, skills, planMode, agents
6091
6171
  var cachedStreamFn = (model, context, options) => {
6092
6172
  return streamSimple(model, context, { ...options, cacheRetention: "long" });
6093
6173
  };
6174
+ function summarizeAssistantResponse(response) {
6175
+ const lines = response.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
6176
+ const salient = lines.filter(
6177
+ (line) => /^(#+\s|[-*]\s|\d+\.\s|```|changed|created|updated|deleted|fixed|implemented|added|removed|renamed|moved|tested|next|summary|result)/i.test(
6178
+ line
6179
+ ) || line.length <= 180
6180
+ );
6181
+ const summary = (salient.length > 0 ? salient : lines).slice(0, 12).join("\n");
6182
+ return summary.length > 3e3 ? `${summary.slice(0, 3e3)}\u2026` : summary;
6183
+ }
6184
+ function getAssistantHistoryContent(exchange, isMostRecent) {
6185
+ if (isMostRecent) {
6186
+ return exchange.assistantResponse;
6187
+ }
6188
+ return exchange.assistantSummary ?? summarizeAssistantResponse(exchange.assistantResponse);
6189
+ }
6190
+ function buildAssistantHistoryText(content, isMostRecent) {
6191
+ if (isMostRecent) {
6192
+ return content;
6193
+ }
6194
+ return `[Earlier assistant response summarized to reduce context tokens]
6195
+ ${content}`;
6196
+ }
6197
+ function serializeLogValue(value, depth = 0, seen = /* @__PURE__ */ new WeakSet()) {
6198
+ if (value === null || value === void 0) {
6199
+ return value;
6200
+ }
6201
+ const valueType = typeof value;
6202
+ if (valueType === "string" || valueType === "number" || valueType === "boolean") {
6203
+ return value;
6204
+ }
6205
+ if (valueType === "bigint" || valueType === "symbol") {
6206
+ return "[Primitive]";
6207
+ }
6208
+ if (valueType === "function") {
6209
+ return "[Function]";
6210
+ }
6211
+ if (value instanceof Error) {
6212
+ const serializedError = {
6213
+ name: value.name,
6214
+ message: value.message
6215
+ };
6216
+ if (value.stack) {
6217
+ serializedError.stack = value.stack;
6218
+ }
6219
+ if ("cause" in value) {
6220
+ serializedError.cause = serializeLogValue(
6221
+ value.cause,
6222
+ depth + 1,
6223
+ seen
6224
+ );
6225
+ }
6226
+ const errorObject = value;
6227
+ for (const [key, nestedValue] of Object.entries(errorObject)) {
6228
+ if (key in serializedError) continue;
6229
+ serializedError[key] = serializeLogValue(nestedValue, depth + 1, seen);
6230
+ }
6231
+ return serializedError;
6232
+ }
6233
+ if (Array.isArray(value)) {
6234
+ if (depth >= 4) {
6235
+ return "[Truncated]";
6236
+ }
6237
+ return value.slice(0, 50).map((item) => serializeLogValue(item, depth + 1, seen));
6238
+ }
6239
+ if (valueType === "object") {
6240
+ if (seen.has(value)) {
6241
+ return "[Circular]";
6242
+ }
6243
+ if (depth >= 4) {
6244
+ return "[Truncated]";
6245
+ }
6246
+ seen.add(value);
6247
+ const serializedObject = {};
6248
+ const plainObject = value;
6249
+ for (const [key, nestedValue] of Object.entries(plainObject).slice(0, 50)) {
6250
+ serializedObject[key] = serializeLogValue(nestedValue, depth + 1, seen);
6251
+ }
6252
+ return serializedObject;
6253
+ }
6254
+ return "[Unsupported value]";
6255
+ }
6094
6256
  function buildHistoryMessages(history) {
6095
6257
  const messages = [];
6096
6258
  const now = Date.now();
6097
- for (const exchange of history) {
6259
+ history.forEach((exchange, index) => {
6260
+ const isMostRecent = index === history.length - 1;
6261
+ const assistantContent = getAssistantHistoryContent(exchange, isMostRecent);
6098
6262
  messages.push({
6099
6263
  role: "user",
6100
6264
  content: exchange.userMessage,
@@ -6102,7 +6266,9 @@ function buildHistoryMessages(history) {
6102
6266
  });
6103
6267
  messages.push({
6104
6268
  role: "assistant",
6105
- content: [{ type: "text", text: exchange.assistantResponse }],
6269
+ content: [
6270
+ { type: "text", text: buildAssistantHistoryText(assistantContent, isMostRecent) }
6271
+ ],
6106
6272
  api: "",
6107
6273
  provider: "",
6108
6274
  model: "",
@@ -6117,7 +6283,7 @@ function buildHistoryMessages(history) {
6117
6283
  stopReason: "stop",
6118
6284
  timestamp: now
6119
6285
  });
6120
- }
6286
+ });
6121
6287
  return messages;
6122
6288
  }
6123
6289
  var PiExecutorImpl = class {
@@ -6147,7 +6313,7 @@ var PiExecutorImpl = class {
6147
6313
  if (!providerConfig) {
6148
6314
  throw new Error(`Unknown provider: ${providerName}`);
6149
6315
  }
6150
- let apiKey = providerConfig.env && providerConfig.env.length > 0 ? process.env[providerConfig.env[0]] : void 0;
6316
+ let apiKey = providerConfig.apiKeyEnv ? process.env[providerConfig.apiKeyEnv] : void 0;
6151
6317
  let apiKeySource = "environment";
6152
6318
  if (!apiKey) {
6153
6319
  try {
@@ -6163,7 +6329,7 @@ var PiExecutorImpl = class {
6163
6329
  }
6164
6330
  if (!apiKey) {
6165
6331
  throw new Error(
6166
- `No API key found for provider: ${providerName}. Set ${providerConfig.env && providerConfig.env.length > 0 ? providerConfig.env[0] : "an API key"} in environment variables or configuration.`
6332
+ `No API key found for provider: ${providerName}. Set ${providerConfig.apiKeyEnv ?? "an API key"} in environment variables or configuration.`
6167
6333
  );
6168
6334
  }
6169
6335
  if (apiKey.includes(":") && apiKey.length > 100) {
@@ -6278,15 +6444,27 @@ ${decoded}
6278
6444
 
6279
6445
  ${userPrompt}`;
6280
6446
  }
6281
- console.log(
6282
- `[ai] Attachments: ${context.attachments?.length ?? 0} total, ${images.length} image(s), ${textAttachments.length} text file(s)`,
6283
- (context.attachments ?? []).map((a) => ({
6447
+ const promptRequest = {
6448
+ provider: providerName,
6449
+ model,
6450
+ cwd,
6451
+ prompt: finalPrompt,
6452
+ promptLength: finalPrompt.length,
6453
+ promptPreview: finalPrompt.substring(0, 200) + (finalPrompt.length > 200 ? "..." : ""),
6454
+ imageCount: images.length,
6455
+ images: images.map((img) => ({
6456
+ mimeType: img.mimeType,
6457
+ dataLength: img.data?.length ?? 0
6458
+ })),
6459
+ attachments: (context.attachments ?? []).map((a) => ({
6284
6460
  name: a.name,
6285
6461
  mimeType: a.mimeType,
6286
6462
  contentLength: a.content?.length ?? 0
6287
- })),
6288
- "images \u2192",
6289
- images.map((img) => ({ mimeType: img.mimeType, dataLength: img.data?.length ?? 0 }))
6463
+ }))
6464
+ };
6465
+ console.log(
6466
+ `[ai] Attachments: ${context.attachments?.length ?? 0} total, ${images.length} image(s), ${textAttachments.length} text file(s)`,
6467
+ promptRequest
6290
6468
  );
6291
6469
  const promptDone = agent.prompt(finalPrompt, images.length > 0 ? images : void 0).then(() => {
6292
6470
  console.log(`[ai] Agent prompt completed successfully`);
@@ -6295,7 +6473,8 @@ ${userPrompt}`;
6295
6473
  }).catch((err) => {
6296
6474
  const errMessage = err instanceof Error ? err.message : String(err);
6297
6475
  console.error(`[ai] Agent prompt failed:`, {
6298
- error: errMessage,
6476
+ request: promptRequest,
6477
+ error: serializeLogValue(err),
6299
6478
  errorType: err instanceof Error ? err.constructor.name : typeof err
6300
6479
  });
6301
6480
  if (!eventQueue.errorOccurred) {
@@ -6349,12 +6528,20 @@ ${userPrompt}`;
6349
6528
  };
6350
6529
  } else {
6351
6530
  console.log("[ai] \u26A0\uFE0F Agent ended with no text content and no tool calls.");
6531
+ const upstreamError = eventQueue.lastErrorMessage ? { message: eventQueue.lastErrorMessage } : void 0;
6532
+ console.error("[ai] Agent ended without text content:", {
6533
+ request: promptRequest,
6534
+ upstreamError,
6535
+ finalContentLength: eventQueue.finalContent.length,
6536
+ toolCallCount: eventQueue.toolCallCount
6537
+ });
6352
6538
  yield {
6353
6539
  type: "error",
6354
6540
  content: `The model ${model} from ${providerName} did not provide a response to your prompt. Check your API key and balance and try again.`,
6355
6541
  error: {
6356
6542
  code: "NO_CONTENT_NO_TOOLS",
6357
- message: `The model ${model} from ${providerName} did not provide a response to your prompt.`
6543
+ message: `The model ${model} from ${providerName} did not provide a response to your prompt.`,
6544
+ details: upstreamError
6358
6545
  }
6359
6546
  };
6360
6547
  }
@@ -6461,6 +6648,63 @@ var ProcessingStateManagerImpl = class {
6461
6648
  }
6462
6649
  };
6463
6650
 
6651
+ // src/core/env-manager.ts
6652
+ import { promises as fs } from "fs";
6653
+ import { join as join11 } from "path";
6654
+ function updateRuntimeEnv(values) {
6655
+ for (const [key, value] of Object.entries(values)) {
6656
+ process.env[key] = value;
6657
+ }
6658
+ }
6659
+ async function updateEnvFile(keyNames) {
6660
+ const envPath = join11(process.cwd(), ".env");
6661
+ let content = "";
6662
+ try {
6663
+ content = await fs.readFile(envPath, "utf-8");
6664
+ } catch {
6665
+ }
6666
+ const lines = content.split("\n");
6667
+ const envMap = {};
6668
+ for (const line of lines) {
6669
+ const trimmed = line.trim();
6670
+ if (trimmed && !trimmed.startsWith("#") && trimmed.includes("=")) {
6671
+ const index = trimmed.indexOf("=");
6672
+ const key = trimmed.substring(0, index).trim();
6673
+ const value = trimmed.substring(index + 1).trim();
6674
+ envMap[key] = value;
6675
+ }
6676
+ }
6677
+ for (const [key, value] of Object.entries(keyNames)) {
6678
+ if (key) {
6679
+ envMap[key] = value;
6680
+ }
6681
+ }
6682
+ const newLines = [];
6683
+ for (const [key, value] of Object.entries(envMap)) {
6684
+ newLines.push(`${key}=${value}`);
6685
+ }
6686
+ await fs.writeFile(envPath, newLines.join("\n") + "\n", "utf-8");
6687
+ }
6688
+ async function readEnvFile() {
6689
+ const envPath = join11(process.cwd(), ".env");
6690
+ const envMap = {};
6691
+ try {
6692
+ const content = await fs.readFile(envPath, "utf-8");
6693
+ const lines = content.split("\n");
6694
+ for (const line of lines) {
6695
+ const trimmed = line.trim();
6696
+ if (trimmed && !trimmed.startsWith("#") && trimmed.includes("=")) {
6697
+ const index = trimmed.indexOf("=");
6698
+ const key = trimmed.substring(0, index).trim();
6699
+ const value = trimmed.substring(index + 1).trim();
6700
+ envMap[key] = value;
6701
+ }
6702
+ }
6703
+ } catch {
6704
+ }
6705
+ return envMap;
6706
+ }
6707
+
6464
6708
  // src/features/ask-user/ask-user.routes.ts
6465
6709
  import { Hono } from "hono";
6466
6710
 
@@ -6577,7 +6821,8 @@ var ErrorCodes = {
6577
6821
  // Git operation errors
6578
6822
  DUPLICATE_BRANCH: "DUPLICATE_BRANCH",
6579
6823
  // General server errors
6580
- INTERNAL_ERROR: "INTERNAL_ERROR"
6824
+ INTERNAL_ERROR: "INTERNAL_ERROR",
6825
+ EXTERNAL_API_ERROR: "EXTERNAL_API_ERROR"
6581
6826
  };
6582
6827
 
6583
6828
  // src/features/ask-user/ask-user.routes.ts
@@ -6634,7 +6879,7 @@ import { randomUUID as randomUUID4 } from "crypto";
6634
6879
 
6635
6880
  // src/features/skills/skills.manager.ts
6636
6881
  import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
6637
- import { join as join11 } from "path";
6882
+ import { join as join12 } from "path";
6638
6883
  import { existsSync as existsSync9 } from "fs";
6639
6884
  import { homedir as homedir5 } from "os";
6640
6885
  function parseFrontmatter(markdown) {
@@ -6682,10 +6927,10 @@ function parseFrontmatter(markdown) {
6682
6927
  return { metadata, content };
6683
6928
  }
6684
6929
  function getGlobalSkillsDir() {
6685
- return join11(homedir5(), ".agents", "skills");
6930
+ return join12(homedir5(), ".agents", "skills");
6686
6931
  }
6687
6932
  function getProjectSkillsDir(threadPath) {
6688
- return join11(threadPath, ".agents", "skills");
6933
+ return join12(threadPath, ".agents", "skills");
6689
6934
  }
6690
6935
  function validateSkillName(name) {
6691
6936
  if (!name || name.length === 0 || name.length > 64) {
@@ -6738,8 +6983,8 @@ var SkillManager = class {
6738
6983
  for (const entry of entries) {
6739
6984
  if (!entry.isDirectory()) continue;
6740
6985
  const skillDirName = entry.name;
6741
- const skillPath = join11(dir, skillDirName);
6742
- const skillFilePath = join11(skillPath, "SKILL.md");
6986
+ const skillPath = join12(dir, skillDirName);
6987
+ const skillFilePath = join12(skillPath, "SKILL.md");
6743
6988
  if (!existsSync9(skillFilePath)) {
6744
6989
  console.warn(`Skipping skill directory ${skillDirName}: SKILL.md not found`);
6745
6990
  continue;
@@ -6919,7 +7164,7 @@ async function activateSkills(allSkills, taskDescription, thread, options) {
6919
7164
 
6920
7165
  // src/features/agents/agents.manager.ts
6921
7166
  import { readdir as readdir5, readFile as readFile7 } from "fs/promises";
6922
- import { join as join12 } from "path";
7167
+ import { join as join13 } from "path";
6923
7168
  import { existsSync as existsSync10 } from "fs";
6924
7169
  import { homedir as homedir6 } from "os";
6925
7170
  function parseFrontmatter2(markdown) {
@@ -6977,10 +7222,10 @@ function validateDescription2(description) {
6977
7222
  return !!description && description.length > 0 && description.length <= 1024;
6978
7223
  }
6979
7224
  function getGlobalAgentsDir() {
6980
- return join12(homedir6(), ".agents", "agents");
7225
+ return join13(homedir6(), ".agents", "agents");
6981
7226
  }
6982
7227
  function getProjectAgentsDir(threadPath) {
6983
- return join12(threadPath, ".agents", "agents");
7228
+ return join13(threadPath, ".agents", "agents");
6984
7229
  }
6985
7230
  var AgentsManager = class {
6986
7231
  /**
@@ -7012,8 +7257,8 @@ var AgentsManager = class {
7012
7257
  for (const entry of entries) {
7013
7258
  if (!entry.isDirectory()) continue;
7014
7259
  const agentDirName = entry.name;
7015
- const agentPath = join12(dir, agentDirName);
7016
- const agentFilePath = join12(agentPath, "AGENT.md");
7260
+ const agentPath = join13(dir, agentDirName);
7261
+ const agentFilePath = join13(agentPath, "AGENT.md");
7017
7262
  if (!existsSync10(agentFilePath)) {
7018
7263
  console.warn(`[agents] Skipping agent directory ${agentDirName}: AGENT.md not found`);
7019
7264
  continue;
@@ -7200,7 +7445,7 @@ function extractAssistantContent(events, fallback) {
7200
7445
  // src/features/chat/chat-post.route.ts
7201
7446
  init_database();
7202
7447
  import { readFile as readFile8, unlink } from "fs/promises";
7203
- import { join as join13 } from "path";
7448
+ import { join as join14 } from "path";
7204
7449
 
7205
7450
  // src/features/project-todos/project-todos.database.ts
7206
7451
  init_database();
@@ -8193,7 +8438,7 @@ ${result.output}`;
8193
8438
  try {
8194
8439
  const todo = await getTodoByThreadId(db, threadId);
8195
8440
  if (todo && todo.status === "Plan") {
8196
- const planFilePath = join13(threadPath, `${todo.id}-plan.md`);
8441
+ const planFilePath = join14(threadPath, `${todo.id}-plan.md`);
8197
8442
  try {
8198
8443
  const planContent = await readFile8(planFilePath, "utf-8");
8199
8444
  await updateTodo(db, todo.id, { description: planContent.trim() });
@@ -8405,13 +8650,15 @@ async function updateMessage(db, messageId, content, events, imageUrl) {
8405
8650
  if (hasMessageUsage) {
8406
8651
  if (event.type === "message" && event.message?.usage) {
8407
8652
  const usage = event.message.usage;
8408
- inputTokens += usage.input ?? 0;
8409
- outputTokens += usage.output ?? 0;
8653
+ const tokens = calculateCostEquivalentTokens(usage);
8654
+ inputTokens += tokens.inputTokens;
8655
+ outputTokens += tokens.outputTokens;
8410
8656
  }
8411
8657
  } else {
8412
8658
  if (event.type === "toolcall_delta" && event.partial?.usage) {
8413
- inputTokens += event.partial.usage.input ?? 0;
8414
- outputTokens += event.partial.usage.output ?? 0;
8659
+ const tokens = calculateCostEquivalentTokens(event.partial.usage);
8660
+ inputTokens += tokens.inputTokens;
8661
+ outputTokens += tokens.outputTokens;
8415
8662
  }
8416
8663
  }
8417
8664
  }
@@ -8442,6 +8689,34 @@ async function updateMessage(db, messageId, content, events, imageUrl) {
8442
8689
  throw error;
8443
8690
  }
8444
8691
  }
8692
+ function calculateCostEquivalentTokens(usage) {
8693
+ const input = usage.input ?? 0;
8694
+ const output = usage.output ?? 0;
8695
+ const cacheRead = usage.cacheRead ?? 0;
8696
+ const cacheWrite = usage.cacheWrite ?? 0;
8697
+ const cachedInput = cacheRead + cacheWrite;
8698
+ const uncachedInput = input >= cachedInput ? input - cachedInput : input;
8699
+ const inputCost = usage.cost?.input ?? 0;
8700
+ const cacheCost = (usage.cost?.cacheRead ?? 0) + (usage.cost?.cacheWrite ?? 0);
8701
+ if (uncachedInput > 0 && inputCost > 0 && cacheCost > 0) {
8702
+ const inputCostPerToken = inputCost / uncachedInput;
8703
+ const costEquivalentCachedInput = cacheCost / inputCostPerToken;
8704
+ return {
8705
+ inputTokens: Math.round(uncachedInput + costEquivalentCachedInput),
8706
+ outputTokens: output
8707
+ };
8708
+ }
8709
+ if (input >= cachedInput) {
8710
+ return {
8711
+ inputTokens: uncachedInput + cachedInput,
8712
+ outputTokens: output
8713
+ };
8714
+ }
8715
+ return {
8716
+ inputTokens: input,
8717
+ outputTokens: output
8718
+ };
8719
+ }
8445
8720
  async function getConversationHistory(db, threadId) {
8446
8721
  try {
8447
8722
  const result = await db.execute(
@@ -9735,6 +10010,7 @@ var MetadataManager = class {
9735
10010
  await setState(this.db, "selectedThreadId", state.selectedThreadId);
9736
10011
  await setState(this.db, "enabledModels", state.enabledModels);
9737
10012
  await setState(this.db, "enabledImageModels", state.enabledImageModels);
10013
+ await setState(this.db, "providerEnvValues", state.providerEnvValues);
9738
10014
  await setState(this.db, "onboardingCompleted", state.onboardingCompleted);
9739
10015
  const encryptedKeys = {};
9740
10016
  for (const [provider, key] of Object.entries(state.providerKeys)) {
@@ -9767,6 +10043,7 @@ var MetadataManager = class {
9767
10043
  const onboardingCompleted = allState.onboardingCompleted || false;
9768
10044
  const enabledModels = allState.enabledModels || {};
9769
10045
  const enabledImageModels = allState.enabledImageModels || {};
10046
+ const providerEnvValues = allState.providerEnvValues || {};
9770
10047
  const encryptedKeys = allState.providerKeys || {};
9771
10048
  const providerKeys = {};
9772
10049
  let needsMigration = false;
@@ -9799,6 +10076,7 @@ var MetadataManager = class {
9799
10076
  selectedThreadId,
9800
10077
  onboardingCompleted,
9801
10078
  providerKeys,
10079
+ providerEnvValues,
9802
10080
  enabledModels,
9803
10081
  enabledImageModels
9804
10082
  };
@@ -9877,6 +10155,36 @@ var MetadataManager = class {
9877
10155
  throw new Error(`Failed to get provider keys: ${errorMessage}`);
9878
10156
  }
9879
10157
  }
10158
+ /**
10159
+ * Save provider environment values (non-secret values such as Azure resource name)
10160
+ * @param values - Environment values to merge into metadata state
10161
+ */
10162
+ async saveProviderEnvValues(values) {
10163
+ try {
10164
+ const state = await this.loadState();
10165
+ state.providerEnvValues = {
10166
+ ...state.providerEnvValues,
10167
+ ...values
10168
+ };
10169
+ await this.saveState(state);
10170
+ } catch (error) {
10171
+ throw new Error(`Failed to save provider environment values: ${String(error)}`);
10172
+ }
10173
+ }
10174
+ /**
10175
+ * Get provider environment values stored in metadata state
10176
+ * @returns Record of environment variable names to values
10177
+ */
10178
+ async getProviderEnvValues() {
10179
+ try {
10180
+ const state = await this.loadState();
10181
+ return state.providerEnvValues;
10182
+ } catch (error) {
10183
+ const errorMessage = error instanceof Error ? error.message : String(error);
10184
+ console.error("[MetadataManager] Failed to get provider environment values:", errorMessage);
10185
+ throw new Error(`Failed to get provider environment values: ${errorMessage}`);
10186
+ }
10187
+ }
9880
10188
  /**
9881
10189
  * Set the selected thread ID
9882
10190
  * @param threadId - Thread ID to select (or null to deselect)
@@ -10061,7 +10369,7 @@ async function handleGetAvailableModels(c, modelManager) {
10061
10369
  const bEnabled = enabledModelIds.has(b.id) ? 0 : 1;
10062
10370
  return aEnabled - bEnabled;
10063
10371
  });
10064
- return c.json({ provider, models });
10372
+ return c.json({ provider, models, enabledModelIds: [...enabledModelIds] });
10065
10373
  } catch (error) {
10066
10374
  const message = error instanceof Error ? error.message : "Unknown error";
10067
10375
  return c.json({ error: message }, 500);
@@ -10933,10 +11241,10 @@ async function handleCheckRunning(c, projectManager) {
10933
11241
 
10934
11242
  // src/core/run-command-detector.ts
10935
11243
  import { readFile as readFile9 } from "fs/promises";
10936
- import { join as join14 } from "path";
11244
+ import { join as join15 } from "path";
10937
11245
  async function detectPackageManager(projectPath) {
10938
11246
  try {
10939
- const packageJsonPath = join14(projectPath, "package.json");
11247
+ const packageJsonPath = join15(projectPath, "package.json");
10940
11248
  const content = await readFile9(packageJsonPath, "utf-8");
10941
11249
  const packageJson = JSON.parse(content);
10942
11250
  if (packageJson.packageManager) {
@@ -10952,7 +11260,7 @@ async function detectPackageManager(projectPath) {
10952
11260
  }
10953
11261
  async function detectRunScripts(projectPath) {
10954
11262
  try {
10955
- const packageJsonPath = join14(projectPath, "package.json");
11263
+ const packageJsonPath = join15(projectPath, "package.json");
10956
11264
  const content = await readFile9(packageJsonPath, "utf-8");
10957
11265
  const packageJson = JSON.parse(content);
10958
11266
  const scripts = packageJson.scripts ?? {};
@@ -10999,7 +11307,7 @@ async function suggestRunCommand(projectPath) {
10999
11307
 
11000
11308
  // src/features/projects/projects-package-scripts.route.ts
11001
11309
  import { readFile as readFile10 } from "fs/promises";
11002
- import { join as join15 } from "path";
11310
+ import { join as join16 } from "path";
11003
11311
  import { glob } from "glob";
11004
11312
  function formatScriptName(scriptName) {
11005
11313
  return scriptName.replace(/[-:_]/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
@@ -11027,7 +11335,7 @@ async function findPackageScripts(projectPath) {
11027
11335
  const packageManager = await detectPackageManager(projectPath);
11028
11336
  for (const filePath of packageJsonFiles) {
11029
11337
  try {
11030
- const fullPath = join15(projectPath, filePath);
11338
+ const fullPath = join16(projectPath, filePath);
11031
11339
  const content = await readFile10(fullPath, "utf-8");
11032
11340
  const packageJson = JSON.parse(content);
11033
11341
  if (packageJson.scripts) {
@@ -11076,7 +11384,7 @@ async function handleGetPackageScripts(c, projectManager) {
11076
11384
 
11077
11385
  // src/features/projects/projects-ai-files.route.ts
11078
11386
  import { readdir as readdir6, readFile as readFile11, writeFile as writeFile2, mkdir, access as access2, rm } from "fs/promises";
11079
- import { join as join16, extname as extname3, basename } from "path";
11387
+ import { join as join17, extname as extname3, basename } from "path";
11080
11388
  import { existsSync as existsSync11 } from "fs";
11081
11389
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
11082
11390
  ".git",
@@ -11095,8 +11403,8 @@ async function buildFullTree(dirPath, relativeDirPath) {
11095
11403
  try {
11096
11404
  const entries = await readdir6(dirPath, { withFileTypes: true });
11097
11405
  for (const entry of entries) {
11098
- const entryRelPath = join16(relativeDirPath, entry.name);
11099
- const entryAbsPath = join16(dirPath, entry.name);
11406
+ const entryRelPath = join17(relativeDirPath, entry.name);
11407
+ const entryAbsPath = join17(dirPath, entry.name);
11100
11408
  if (entry.isDirectory()) {
11101
11409
  const children = await buildFullTree(entryAbsPath, entryRelPath);
11102
11410
  nodes.push({
@@ -11124,8 +11432,8 @@ async function buildMarkdownFilteredTree(dirPath, relativeDirPath) {
11124
11432
  try {
11125
11433
  const entries = await readdir6(dirPath, { withFileTypes: true });
11126
11434
  for (const entry of entries) {
11127
- const entryRelPath = join16(relativeDirPath, entry.name);
11128
- const entryAbsPath = join16(dirPath, entry.name);
11435
+ const entryRelPath = join17(relativeDirPath, entry.name);
11436
+ const entryAbsPath = join17(dirPath, entry.name);
11129
11437
  if (entry.isDirectory()) {
11130
11438
  if (IGNORED_DIRS.has(entry.name)) continue;
11131
11439
  const children = await buildMarkdownFilteredTree(entryAbsPath, entryRelPath);
@@ -11160,8 +11468,8 @@ async function buildAIFileTree(projectPath) {
11160
11468
  { key: "agents", label: "Agents" }
11161
11469
  ];
11162
11470
  for (const { key, label } of agentsFolders) {
11163
- const relPath = join16(".agents", key);
11164
- const absPath = join16(projectPath, relPath);
11471
+ const relPath = join17(".agents", key);
11472
+ const absPath = join17(projectPath, relPath);
11165
11473
  const exists = existsSync11(absPath);
11166
11474
  if (exists) {
11167
11475
  const children = await buildFullTree(absPath, relPath);
@@ -11196,7 +11504,7 @@ async function buildAIFileTree(projectPath) {
11196
11504
  if (entry.name === ".agents" || entry.name === "AGENTS.md" || IGNORED_DIRS.has(entry.name))
11197
11505
  continue;
11198
11506
  const entryRelPath = entry.name;
11199
- const entryAbsPath = join16(projectPath, entry.name);
11507
+ const entryAbsPath = join17(projectPath, entry.name);
11200
11508
  if (entry.isFile() && MARKDOWN_EXTS.has(extname3(entry.name))) {
11201
11509
  nodes.push({
11202
11510
  id: entryRelPath,
@@ -11223,7 +11531,7 @@ async function buildAIFileTree(projectPath) {
11223
11531
  }
11224
11532
  function validateFilePath(projectPath, filePath) {
11225
11533
  if (!filePath) return null;
11226
- const abs = join16(projectPath, filePath);
11534
+ const abs = join17(projectPath, filePath);
11227
11535
  if (!abs.startsWith(projectPath + "/") && abs !== projectPath) return null;
11228
11536
  return abs;
11229
11537
  }
@@ -11349,7 +11657,7 @@ async function handleSaveAIFile(c, projectManager) {
11349
11657
  if (!absPath) {
11350
11658
  return c.json({ error: { code: "BAD_REQUEST", message: "Invalid file path" } }, 400);
11351
11659
  }
11352
- const parentDir = join16(absPath, "..");
11660
+ const parentDir = join17(absPath, "..");
11353
11661
  await mkdir(parentDir, { recursive: true });
11354
11662
  await writeFile2(absPath, content, "utf-8");
11355
11663
  return c.json({ success: true, path: filePath });
@@ -11449,10 +11757,10 @@ async function handleCreateSkill(c, projectManager) {
11449
11757
  if (!project) {
11450
11758
  return errorResponse(c, ErrorCodes.PROJECT_NOT_FOUND, `Project not found: ${projectId}`, 404);
11451
11759
  }
11452
- const skillRelPath = join16(".agents", "skills", name);
11453
- const skillAbsPath = join16(project.path, skillRelPath);
11454
- const skillFileRelPath = join16(skillRelPath, "SKILL.md");
11455
- const skillFileAbsPath = join16(skillAbsPath, "SKILL.md");
11760
+ const skillRelPath = join17(".agents", "skills", name);
11761
+ const skillAbsPath = join17(project.path, skillRelPath);
11762
+ const skillFileRelPath = join17(skillRelPath, "SKILL.md");
11763
+ const skillFileAbsPath = join17(skillAbsPath, "SKILL.md");
11456
11764
  if (existsSync11(skillAbsPath)) {
11457
11765
  return c.json(
11458
11766
  { error: { code: "CONFLICT", message: `Skill '${name}' already exists` } },
@@ -11554,7 +11862,7 @@ function createProjectRoutes(projectManager, threadManager) {
11554
11862
 
11555
11863
  // src/features/projects/projects.manager.ts
11556
11864
  init_utils();
11557
- import { join as join19 } from "path";
11865
+ import { join as join20 } from "path";
11558
11866
  import { realpathSync as realpathSync2 } from "fs";
11559
11867
  import { rm as rm3 } from "fs/promises";
11560
11868
 
@@ -11775,13 +12083,13 @@ var ProcessManager = class extends EventEmitter {
11775
12083
  // src/features/projects/projects.creator.ts
11776
12084
  init_utils();
11777
12085
  import { randomUUID as randomUUID7 } from "crypto";
11778
- import { basename as basename2, join as join18, relative as relative5 } from "path";
12086
+ import { basename as basename2, join as join19, relative as relative5 } from "path";
11779
12087
  import { access as access3, stat as stat3, readdir as readdir8 } from "fs/promises";
11780
12088
 
11781
12089
  // src/features/scaffold/scaffold.runner.ts
11782
12090
  init_utils();
11783
12091
  import { existsSync as existsSync12 } from "fs";
11784
- import { join as join17 } from "path";
12092
+ import { join as join18 } from "path";
11785
12093
  import { mkdir as mkdir2, readdir as readdir7, stat as stat2, rename, rm as rm2, writeFile as writeFile3 } from "fs/promises";
11786
12094
  import { createInterface as createInterface2 } from "readline";
11787
12095
 
@@ -12503,7 +12811,7 @@ function loadCatalog() {
12503
12811
  }
12504
12812
  async function writeAgentsMd(projectPath, agentsMd) {
12505
12813
  if (!agentsMd) return;
12506
- const agentsPath = join17(projectPath, "AGENTS.md");
12814
+ const agentsPath = join18(projectPath, "AGENTS.md");
12507
12815
  if (existsSync12(agentsPath)) return;
12508
12816
  await writeFile3(agentsPath, agentsMd, "utf-8");
12509
12817
  }
@@ -12661,7 +12969,7 @@ async function* runCommandStreaming(cwd, command) {
12661
12969
  }
12662
12970
  }
12663
12971
  async function* createScaffoldedProjectStreaming(options) {
12664
- const projectPath = join17(options.parentDir, options.threadId);
12972
+ const projectPath = join18(options.parentDir, options.threadId);
12665
12973
  if (!existsSync12(options.parentDir)) {
12666
12974
  yield {
12667
12975
  type: "result",
@@ -12751,7 +13059,7 @@ async function* createScaffoldedProjectStreaming(options) {
12751
13059
  }
12752
13060
  try {
12753
13061
  const projectName2 = getProjectName(options.projectName);
12754
- const projectSubDir = join17(projectPath, projectName2);
13062
+ const projectSubDir = join18(projectPath, projectName2);
12755
13063
  try {
12756
13064
  const subDirStat = await stat2(projectSubDir);
12757
13065
  if (subDirStat.isDirectory()) {
@@ -12761,8 +13069,8 @@ async function* createScaffoldedProjectStreaming(options) {
12761
13069
  };
12762
13070
  const items = await readdir7(projectSubDir);
12763
13071
  for (const item of items) {
12764
- const oldPath = join17(projectSubDir, item);
12765
- const newPath = join17(projectPath, item);
13072
+ const oldPath = join18(projectSubDir, item);
13073
+ const newPath = join18(projectPath, item);
12766
13074
  await rename(oldPath, newPath);
12767
13075
  }
12768
13076
  await rm2(projectSubDir, { recursive: true, force: true });
@@ -13230,7 +13538,7 @@ var ProjectCreator = class {
13230
13538
  return name;
13231
13539
  }
13232
13540
  generateThreadPath(_projectId, threadId) {
13233
- return join18(this.rootFolder, threadId);
13541
+ return join19(this.rootFolder, threadId);
13234
13542
  }
13235
13543
  async *createProjectFromFolder() {
13236
13544
  await loadUtils();
@@ -13380,19 +13688,19 @@ var ProjectCreator = class {
13380
13688
  }
13381
13689
  async findAllPackageJsonFiles(rootPath, currentPath, setupScripts) {
13382
13690
  try {
13383
- await access3(join18(currentPath, "package.json"));
13691
+ await access3(join19(currentPath, "package.json"));
13384
13692
  const relativePath = relative5(rootPath, currentPath);
13385
13693
  let installCommand = "";
13386
13694
  try {
13387
- await access3(join18(currentPath, "yarn.lock"));
13695
+ await access3(join19(currentPath, "yarn.lock"));
13388
13696
  installCommand = "yarn install";
13389
13697
  } catch {
13390
13698
  try {
13391
- await access3(join18(currentPath, "bun.lock"));
13699
+ await access3(join19(currentPath, "bun.lock"));
13392
13700
  installCommand = "bun install";
13393
13701
  } catch {
13394
13702
  try {
13395
- await access3(join18(currentPath, "pnpm-lock.yaml"));
13703
+ await access3(join19(currentPath, "pnpm-lock.yaml"));
13396
13704
  installCommand = "pnpm install";
13397
13705
  } catch {
13398
13706
  installCommand = "npm install";
@@ -13409,7 +13717,7 @@ var ProjectCreator = class {
13409
13717
  try {
13410
13718
  const entries = await readdir8(currentPath);
13411
13719
  for (const entry of entries) {
13412
- const entryPath = join18(currentPath, entry);
13720
+ const entryPath = join19(currentPath, entry);
13413
13721
  try {
13414
13722
  const stats = await stat3(entryPath);
13415
13723
  if (stats.isDirectory() && !entry.startsWith(".") && entry !== "node_modules") {
@@ -13617,12 +13925,15 @@ var ProjectManagerImpl = class {
13617
13925
  const threads = await this.metadataManager.loadThreads();
13618
13926
  const remainingThreads = threads.filter((t) => t.projectId !== projectId);
13619
13927
  await this.metadataManager.saveThreads(remainingThreads);
13620
- try {
13621
- await rm3(project.path, { recursive: true, force: true });
13622
- } catch (error) {
13623
- throw new Error(
13624
- `Failed to remove project directory: ${error instanceof Error ? error.message : String(error)}`
13625
- );
13928
+ const isInsideDataDir = project.path.startsWith(this.rootFolder + "/");
13929
+ if (isInsideDataDir) {
13930
+ try {
13931
+ await rm3(project.path, { recursive: true, force: true });
13932
+ } catch (error) {
13933
+ throw new Error(
13934
+ `Failed to remove project directory: ${error instanceof Error ? error.message : String(error)}`
13935
+ );
13936
+ }
13626
13937
  }
13627
13938
  projects.splice(projectIndex, 1);
13628
13939
  await this.metadataManager.saveProjects(projects);
@@ -13746,7 +14057,7 @@ var ProjectManagerImpl = class {
13746
14057
  const session = this.terminalSessionManager.getOrCreateSession(threadId, thread.path);
13747
14058
  let workingDir;
13748
14059
  if (cwd) {
13749
- workingDir = cwd.startsWith("/") ? cwd : join19(thread.path, cwd);
14060
+ workingDir = cwd.startsWith("/") ? cwd : join20(thread.path, cwd);
13750
14061
  this.terminalSessionManager.updateWorkingDirectory(threadId, workingDir);
13751
14062
  } else {
13752
14063
  workingDir = session.currentWorkingDirectory;
@@ -13949,78 +14260,61 @@ ___CWD___%s
13949
14260
  // src/features/providers/providers.routes.ts
13950
14261
  import { Hono as Hono7 } from "hono";
13951
14262
 
13952
- // src/core/env-manager.ts
13953
- import { promises as fs } from "fs";
13954
- import { join as join20 } from "path";
13955
- async function updateEnvFile(keyNames) {
13956
- const envPath = join20(process.cwd(), ".env");
13957
- let content = "";
13958
- try {
13959
- content = await fs.readFile(envPath, "utf-8");
13960
- } catch {
13961
- }
13962
- const lines = content.split("\n");
13963
- const envMap = {};
13964
- for (const line of lines) {
13965
- const trimmed = line.trim();
13966
- if (trimmed && !trimmed.startsWith("#") && trimmed.includes("=")) {
13967
- const index = trimmed.indexOf("=");
13968
- const key = trimmed.substring(0, index).trim();
13969
- const value = trimmed.substring(index + 1).trim();
13970
- envMap[key] = value;
13971
- }
13972
- }
13973
- for (const [key, value] of Object.entries(keyNames)) {
13974
- if (key) {
13975
- envMap[key] = value;
13976
- }
13977
- }
13978
- const newLines = [];
13979
- for (const [key, value] of Object.entries(envMap)) {
13980
- newLines.push(`${key}=${value}`);
14263
+ // src/features/providers/providers-get.route.ts
14264
+ function getProviderApiKeyEnvName2(providerEnv) {
14265
+ if (providerEnv.length === 0) {
14266
+ return "";
13981
14267
  }
13982
- await fs.writeFile(envPath, newLines.join("\n") + "\n", "utf-8");
14268
+ const explicitApiKeyEnv = providerEnv.find((envName) => /api_key$/i.test(envName));
14269
+ return explicitApiKeyEnv ?? providerEnv[providerEnv.length - 1];
13983
14270
  }
13984
- async function readEnvFile() {
13985
- const envPath = join20(process.cwd(), ".env");
13986
- const envMap = {};
13987
- try {
13988
- const content = await fs.readFile(envPath, "utf-8");
13989
- const lines = content.split("\n");
13990
- for (const line of lines) {
13991
- const trimmed = line.trim();
13992
- if (trimmed && !trimmed.startsWith("#") && trimmed.includes("=")) {
13993
- const index = trimmed.indexOf("=");
13994
- const key = trimmed.substring(0, index).trim();
13995
- const value = trimmed.substring(index + 1).trim();
13996
- envMap[key] = value;
13997
- }
13998
- }
13999
- } catch {
14271
+ function getProviderResourceName2(providerEnv) {
14272
+ const explicitResourceEnv = providerEnv.find((envName) => /resource_name$/i.test(envName));
14273
+ return explicitResourceEnv ?? providerEnv[0] ?? "";
14274
+ }
14275
+ function resolveAzureApiUrl2(providerEnv, envValues) {
14276
+ const resourceEnvName = getProviderResourceName2(providerEnv);
14277
+ const resourceName = envValues[resourceEnvName] ?? "";
14278
+ if (!resourceName) {
14279
+ return "";
14000
14280
  }
14001
- return envMap;
14281
+ return `https://${resourceName}.openai.azure.com/openai/v1`;
14002
14282
  }
14003
-
14004
- // src/features/providers/providers-get.route.ts
14005
14283
  async function getProviders(c, metadataManager) {
14006
14284
  const keys = await metadataManager.getProviderKeys();
14285
+ const persistedEnvValues = await metadataManager.getProviderEnvValues();
14007
14286
  const envFileKeys = await readEnvFile();
14008
14287
  const catalog = await getModelsCatalog();
14009
14288
  const providersWithKeys = [];
14010
14289
  if (catalog) {
14011
14290
  for (const [catalogId, catalogProvider] of Object.entries(catalog)) {
14012
- const keyName = catalogProvider.env?.[0] || "";
14291
+ const envNames = catalogProvider.env ?? [];
14292
+ const keyName = getProviderApiKeyEnvName2(envNames);
14293
+ const envValues = {};
14294
+ for (const envName of envNames) {
14295
+ envValues[envName] = envFileKeys[envName] ?? process.env[envName] ?? persistedEnvValues[envName] ?? "";
14296
+ }
14013
14297
  const envFileKey = keyName ? envFileKeys[keyName] : "";
14014
14298
  const processEnvKey = keyName ? process.env[keyName] : "";
14015
14299
  const storedKey = keys[catalogProvider.name] || keys[catalogId];
14300
+ const hasEnvKey = Boolean(envFileKey || processEnvKey);
14301
+ const resolvedKey = storedKey ?? envFileKey ?? processEnvKey ?? "";
14302
+ const isEnv = !storedKey && hasEnvKey && resolvedKey !== "";
14303
+ const api = catalogId === "azure" ? resolveAzureApiUrl2(envNames, envValues) : catalogProvider.api ?? "";
14304
+ if (keyName && !envValues[keyName]) {
14305
+ envValues[keyName] = resolvedKey;
14306
+ }
14016
14307
  providersWithKeys.push({
14017
14308
  name: catalogProvider.name ?? catalogId,
14018
14309
  slug: catalogId,
14019
14310
  url: catalogProvider.doc ?? "",
14020
14311
  keyName,
14021
14312
  authType: "APIKey",
14022
- api: catalogProvider.api ?? "",
14023
- apiKey: storedKey ?? envFileKey ?? processEnvKey ?? ""
14313
+ api,
14314
+ env: envNames,
14315
+ envValues,
14316
+ apiKey: resolvedKey,
14317
+ isEnv
14024
14318
  });
14025
14319
  }
14026
14320
  }
@@ -14037,6 +14331,7 @@ async function postProviderKeys(c, metadataManager) {
14037
14331
  await metadataManager.saveProviderKey(providerName, apiKey);
14038
14332
  const keyName = await resolveProviderKeyName(providerName);
14039
14333
  if (keyName) {
14334
+ updateRuntimeEnv({ [keyName]: apiKey });
14040
14335
  await updateEnvFile({ [keyName]: apiKey });
14041
14336
  }
14042
14337
  return c.json({ success: true });
@@ -14061,6 +14356,7 @@ async function postProviderBulkKeys(c, metadataManager) {
14061
14356
  }
14062
14357
  }
14063
14358
  if (Object.keys(envUpdates).length > 0) {
14359
+ updateRuntimeEnv(envUpdates);
14064
14360
  await updateEnvFile(envUpdates);
14065
14361
  }
14066
14362
  return c.json({ success: true });
@@ -14069,6 +14365,28 @@ async function postProviderBulkKeys(c, metadataManager) {
14069
14365
  }
14070
14366
  }
14071
14367
 
14368
+ // src/features/providers/providers-post-env.route.ts
14369
+ async function postProviderEnv(c, metadataManager) {
14370
+ try {
14371
+ const { values } = await c.req.json();
14372
+ if (!values || typeof values !== "object") {
14373
+ return c.json({ error: "values object is required" }, 400);
14374
+ }
14375
+ const envValues = {};
14376
+ for (const [key, value] of Object.entries(values)) {
14377
+ if (typeof value === "string") {
14378
+ envValues[key] = value;
14379
+ }
14380
+ }
14381
+ await metadataManager.saveProviderEnvValues(envValues);
14382
+ updateRuntimeEnv(envValues);
14383
+ await updateEnvFile(envValues);
14384
+ return c.json({ success: true });
14385
+ } catch (error) {
14386
+ return c.json({ error: `Failed to save provider environment: ${String(error)}` }, 500);
14387
+ }
14388
+ }
14389
+
14072
14390
  // src/features/models/model-info-openrouter.ts
14073
14391
  async function getOpenRouterCredits(apiKey) {
14074
14392
  try {
@@ -14161,6 +14479,44 @@ async function getProviderCredits(c, metadataManager) {
14161
14479
  return c.json({ balance: null, status: "unknown" });
14162
14480
  }
14163
14481
 
14482
+ // src/features/providers/providers-get-logo.route.ts
14483
+ var LOGO_BASE_URL = "https://models.tarsk.io/svg";
14484
+ var SLUG_PATTERN = /^[a-z0-9._-]+$/i;
14485
+ var logoCache = /* @__PURE__ */ new Map();
14486
+ var LOGO_CACHE_TTL_MS = 15 * 60 * 1e3;
14487
+ async function getProviderLogo(c) {
14488
+ const slug = c.req.param("slug");
14489
+ if (!slug || !SLUG_PATTERN.test(slug)) {
14490
+ return c.body(null, 404);
14491
+ }
14492
+ const cached = logoCache.get(slug);
14493
+ if (cached && Date.now() < cached.expires) {
14494
+ return c.body(cached.svg, 200, {
14495
+ "Content-Type": "image/svg+xml",
14496
+ "Cache-Control": "public, max-age=86400"
14497
+ });
14498
+ }
14499
+ try {
14500
+ const response = await fetch(`${LOGO_BASE_URL}/${slug}.svg`, {
14501
+ signal: AbortSignal.timeout(5e3)
14502
+ });
14503
+ if (!response.ok) {
14504
+ return c.body(null, 404);
14505
+ }
14506
+ const svg = await response.text();
14507
+ if (!svg.includes("<svg")) {
14508
+ return c.body(null, 404);
14509
+ }
14510
+ logoCache.set(slug, { svg, expires: Date.now() + LOGO_CACHE_TTL_MS });
14511
+ return c.body(svg, 200, {
14512
+ "Content-Type": "image/svg+xml",
14513
+ "Cache-Control": "public, max-age=86400"
14514
+ });
14515
+ } catch {
14516
+ return c.body(null, 404);
14517
+ }
14518
+ }
14519
+
14164
14520
  // src/features/providers/providers-open-external.route.ts
14165
14521
  var Utils2 = null;
14166
14522
  var utilsLoaded2 = false;
@@ -14204,7 +14560,9 @@ function createProviderRoutes(metadataManager) {
14204
14560
  router.get("/", (c) => getProviders(c, metadataManager));
14205
14561
  router.post("/keys", (c) => postProviderKeys(c, metadataManager));
14206
14562
  router.post("/bulk-keys", (c) => postProviderBulkKeys(c, metadataManager));
14563
+ router.post("/env", (c) => postProviderEnv(c, metadataManager));
14207
14564
  router.get("/:name/credits", (c) => getProviderCredits(c, metadataManager));
14565
+ router.get("/:slug/logo", (c) => getProviderLogo(c));
14208
14566
  router.post("/open-external", (c) => openExternalUrl(c));
14209
14567
  return router;
14210
14568
  }
@@ -14453,7 +14811,7 @@ function createScaffoldRoutes(projectManager) {
14453
14811
 
14454
14812
  // src/features/slash-commands/slash-commands.manager.ts
14455
14813
  import { readdir as readdir9, readFile as readFile12, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
14456
- import { join as join21, basename as basename3, extname as extname4 } from "path";
14814
+ import { join as join21, basename as basename3, extname as extname4, relative as relative6 } from "path";
14457
14815
  import { existsSync as existsSync13 } from "fs";
14458
14816
  import { homedir as homedir7 } from "os";
14459
14817
  function slugify(filename) {
@@ -14534,14 +14892,14 @@ var SlashCommandManager = class _SlashCommandManager {
14534
14892
  const commands = /* @__PURE__ */ new Map();
14535
14893
  const globalDir = getGlobalCommandsDir();
14536
14894
  if (existsSync13(globalDir)) {
14537
- const globalCommands = await this.loadCommandsFromDir(globalDir, "global");
14895
+ const globalCommands = await this.loadCommandsFromDir(globalDir, threadPath, "global");
14538
14896
  for (const cmd of globalCommands) {
14539
14897
  commands.set(cmd.name, cmd);
14540
14898
  }
14541
14899
  }
14542
14900
  const projectDir = getProjectCommandsDir(threadPath);
14543
14901
  if (existsSync13(projectDir)) {
14544
- const projectCommands = await this.loadCommandsFromDir(projectDir, "project");
14902
+ const projectCommands = await this.loadCommandsFromDir(projectDir, threadPath, "project");
14545
14903
  for (const cmd of projectCommands) {
14546
14904
  if (!_SlashCommandManager.BUILT_IN_COMMANDS.has(cmd.name)) {
14547
14905
  commands.set(cmd.name, cmd);
@@ -14558,7 +14916,7 @@ var SlashCommandManager = class _SlashCommandManager {
14558
14916
  /**
14559
14917
  * Load commands from a specific directory
14560
14918
  */
14561
- async loadCommandsFromDir(dir, scope) {
14919
+ async loadCommandsFromDir(dir, threadPath, scope) {
14562
14920
  const commands = [];
14563
14921
  try {
14564
14922
  const files = await readdir9(dir);
@@ -14575,7 +14933,7 @@ var SlashCommandManager = class _SlashCommandManager {
14575
14933
  content,
14576
14934
  metadata,
14577
14935
  scope,
14578
- filePath
14936
+ filePath: scope === "project" ? relative6(threadPath, filePath) : filePath
14579
14937
  });
14580
14938
  }
14581
14939
  } catch (error) {
@@ -15462,12 +15820,15 @@ var ThreadManagerImpl = class {
15462
15820
  throw new Error(`Thread not found: ${threadId}`);
15463
15821
  }
15464
15822
  const thread = threads[threadIndex];
15465
- try {
15466
- await rm4(thread.path, { recursive: true, force: true });
15467
- } catch (error) {
15468
- throw new Error(
15469
- `Failed to remove thread directory: ${error instanceof Error ? error.message : String(error)}`
15470
- );
15823
+ const isInsideDataDir = thread.path.startsWith(this.rootFolder + "/");
15824
+ if (isInsideDataDir) {
15825
+ try {
15826
+ await rm4(thread.path, { recursive: true, force: true });
15827
+ } catch (error) {
15828
+ throw new Error(
15829
+ `Failed to remove thread directory: ${error instanceof Error ? error.message : String(error)}`
15830
+ );
15831
+ }
15471
15832
  }
15472
15833
  threads.splice(threadIndex, 1);
15473
15834
  await this.metadataManager.saveThreads(threads);
@@ -15793,7 +16154,7 @@ async function handleDeleteThread(c, threadManager) {
15793
16154
  // src/core/project-inspector.ts
15794
16155
  import { readFile as readFile13, readdir as readdir10 } from "fs/promises";
15795
16156
  import { existsSync as existsSync14 } from "fs";
15796
- import { join as join23, basename as basename4, relative as relative6 } from "path";
16157
+ import { join as join23, basename as basename4, relative as relative7 } from "path";
15797
16158
  import { glob as glob2 } from "glob";
15798
16159
 
15799
16160
  // src/features/project-scripts/project-scripts.database.ts
@@ -16143,7 +16504,7 @@ function buildRunCommand(scriptName, workspace, packageManager, repoType, projec
16143
16504
  return `npm run ${scriptName} --workspace=${workspace.relativePath}`;
16144
16505
  }
16145
16506
  if (!isRoot) {
16146
- const relPath = relative6(projectPath, workspace.folder);
16507
+ const relPath = relative7(projectPath, workspace.folder);
16147
16508
  return `cd ${relPath} && ${runCmd}`;
16148
16509
  }
16149
16510
  return runCmd;
@@ -16804,15 +17165,17 @@ async function handleOpenThread(c, threadManager) {
16804
17165
  }
16805
17166
 
16806
17167
  // src/features/threads/threads-conversation-folder-path.route.ts
16807
- import { join as join25 } from "path";
16808
- async function handleGetConversationFolderPath(c) {
17168
+ async function handleGetConversationFolderPath(c, threadManager) {
16809
17169
  try {
16810
17170
  const threadId = c.req.param("id");
16811
17171
  if (!threadId) {
16812
17172
  return errorResponse(c, ErrorCodes.INVALID_REQUEST, "Thread ID is required", 400);
16813
17173
  }
16814
- const path6 = join25(getDataDir(), threadId);
16815
- return successResponse(c, { path: path6 });
17174
+ const thread = await threadManager.getThread(threadId);
17175
+ if (!thread) {
17176
+ return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
17177
+ }
17178
+ return successResponse(c, { path: thread.path });
16816
17179
  } catch (error) {
16817
17180
  const errorMessage = error instanceof Error ? error.message : String(error);
16818
17181
  return errorResponse(
@@ -16866,12 +17229,12 @@ async function handleFixComments(c, threadManager) {
16866
17229
 
16867
17230
  // src/features/threads/threads-ai-files.route.ts
16868
17231
  import { readFile as readFile15, writeFile as writeFile7, mkdir as mkdir7, access as access5, rm as rm6 } from "fs/promises";
16869
- import { join as join27 } from "path";
17232
+ import { join as join26 } from "path";
16870
17233
  import { existsSync as existsSync15 } from "fs";
16871
17234
 
16872
17235
  // src/features/git/git-download-folder.ts
16873
17236
  import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
16874
- import { join as join26, dirname as dirname4 } from "path";
17237
+ import { join as join25, dirname as dirname4 } from "path";
16875
17238
  async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
16876
17239
  const { token, ref } = options;
16877
17240
  const match = repoUrl.replace(/\.git$/, "").match(/github\.com\/([^/]+)\/([^/]+)/);
@@ -16907,7 +17270,7 @@ async function downloadGithubFolder(repoUrl, srcPath, destPath, options = {}) {
16907
17270
  await Promise.all(
16908
17271
  files.map(async (file) => {
16909
17272
  const relativePath = file.path.slice(prefix.length);
16910
- const localPath = join26(destPath, relativePath);
17273
+ const localPath = join25(destPath, relativePath);
16911
17274
  await mkdir6(dirname4(localPath), { recursive: true });
16912
17275
  const rawUrl = `https://raw.githubusercontent.com/${owner}/${repo}/${refParam}/${file.path}`;
16913
17276
  const fileRes = await fetch(rawUrl, { headers });
@@ -17049,7 +17412,7 @@ async function handleSaveThreadAIFile(c, threadManager) {
17049
17412
  if (!absPath) {
17050
17413
  return c.json({ error: { code: "BAD_REQUEST", message: "Invalid file path" } }, 400);
17051
17414
  }
17052
- const parentDir = join27(absPath, "..");
17415
+ const parentDir = join26(absPath, "..");
17053
17416
  await mkdir7(parentDir, { recursive: true });
17054
17417
  await writeFile7(absPath, content, "utf-8");
17055
17418
  return c.json({ success: true, path: filePath });
@@ -17138,10 +17501,10 @@ async function handleCreateThreadAgent(c, threadManager) {
17138
17501
  if (!thread) {
17139
17502
  return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
17140
17503
  }
17141
- const agentRelPath = join27(".agents", "agents", name);
17142
- const agentAbsPath = join27(thread.path, agentRelPath);
17143
- const agentFileRelPath = join27(agentRelPath, "AGENT.md");
17144
- const agentFileAbsPath = join27(agentAbsPath, "AGENT.md");
17504
+ const agentRelPath = join26(".agents", "agents", name);
17505
+ const agentAbsPath = join26(thread.path, agentRelPath);
17506
+ const agentFileRelPath = join26(agentRelPath, "AGENT.md");
17507
+ const agentFileAbsPath = join26(agentAbsPath, "AGENT.md");
17145
17508
  if (existsSync15(agentAbsPath)) {
17146
17509
  return c.json(
17147
17510
  { error: { code: "CONFLICT", message: `Agent '${name}' already exists` } },
@@ -17208,10 +17571,10 @@ async function handleCreateThreadSkill(c, threadManager) {
17208
17571
  if (!thread) {
17209
17572
  return errorResponse(c, ErrorCodes.THREAD_NOT_FOUND, `Thread not found: ${threadId}`, 404);
17210
17573
  }
17211
- const skillRelPath = join27(".agents", "skills", name);
17212
- const skillAbsPath = join27(thread.path, skillRelPath);
17213
- const skillFileRelPath = join27(skillRelPath, "SKILL.md");
17214
- const skillFileAbsPath = join27(skillAbsPath, "SKILL.md");
17574
+ const skillRelPath = join26(".agents", "skills", name);
17575
+ const skillAbsPath = join26(thread.path, skillRelPath);
17576
+ const skillFileRelPath = join26(skillRelPath, "SKILL.md");
17577
+ const skillFileAbsPath = join26(skillAbsPath, "SKILL.md");
17215
17578
  if (existsSync15(skillAbsPath)) {
17216
17579
  return c.json(
17217
17580
  { error: { code: "CONFLICT", message: `Skill '${name}' already exists` } },
@@ -17525,7 +17888,7 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
17525
17888
  return handleOpenThread(c, threadManager);
17526
17889
  });
17527
17890
  router.get("/:id/conversation-folder-path", async (c) => {
17528
- return handleGetConversationFolderPath(c);
17891
+ return handleGetConversationFolderPath(c, threadManager);
17529
17892
  });
17530
17893
  router.post("/:id/fix-comments", async (c) => {
17531
17894
  return handleFixComments(c, threadManager);
@@ -17895,12 +18258,12 @@ import { existsSync as existsSync17 } from "fs";
17895
18258
  // src/features/git/git.utils.ts
17896
18259
  init_utils();
17897
18260
  import { existsSync as existsSync16, readFileSync as readFileSync5, statSync as statSync4 } from "fs";
17898
- import { isAbsolute as isAbsolute3, normalize as normalize2, resolve as resolve3, join as join28 } from "path";
18261
+ import { isAbsolute as isAbsolute3, normalize as normalize2, resolve as resolve3, join as join27 } from "path";
17899
18262
  import { completeSimple } from "@mariozechner/pi-ai";
17900
18263
  async function resolveModelAndKey(provider, modelId, metadataManager) {
17901
18264
  const providerConfig = await resolveProviderConfig(provider);
17902
18265
  if (!providerConfig) return null;
17903
- let apiKey = providerConfig.env && providerConfig.env.length > 0 ? process.env[providerConfig.env[0]] : void 0;
18266
+ let apiKey = providerConfig.apiKeyEnv ? process.env[providerConfig.apiKeyEnv] : void 0;
17904
18267
  if (!apiKey) {
17905
18268
  const providerKeys = await metadataManager.getProviderKeys();
17906
18269
  apiKey = providerKeys[providerConfig.id];
@@ -18152,7 +18515,7 @@ async function getUntrackedFilesDiff(gitRoot) {
18152
18515
  const parts = [];
18153
18516
  const maxFileSize = 1e5;
18154
18517
  for (const relPath of untrackedPaths) {
18155
- const fullPath = join28(gitRoot, relPath);
18518
+ const fullPath = join27(gitRoot, relPath);
18156
18519
  if (!existsSync16(fullPath)) continue;
18157
18520
  try {
18158
18521
  if (statSync4(fullPath).isDirectory()) continue;
@@ -19830,15 +20193,6 @@ async function gitSyncBranchHandler(c, metadataManager) {
19830
20193
  400
19831
20194
  );
19832
20195
  }
19833
- if (currentBranch === defaultBranch) {
19834
- console.log(`[sync-branch] Already on default branch: ${defaultBranch}`);
19835
- return c.json(
19836
- {
19837
- error: `Already on default branch (${defaultBranch})`
19838
- },
19839
- 400
19840
- );
19841
- }
19842
20196
  const { spawnProcess: spawnProcess2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
19843
20197
  return new Promise((resolve6) => {
19844
20198
  console.log(`[sync-branch] Checking branch status...`);
@@ -19974,13 +20328,12 @@ async function gitSyncBranchHandler(c, metadataManager) {
19974
20328
  }
19975
20329
  pullProc.on("close", (code) => {
19976
20330
  if (code === 0) {
19977
- console.log(
19978
- `[sync-branch] \u2713 Successfully synced ${currentBranch} with ${defaultBranch}`
19979
- );
20331
+ const message = currentBranch === defaultBranch ? `Successfully pulled latest changes from origin/${defaultBranch}` : `Successfully synced ${currentBranch} with ${defaultBranch}`;
20332
+ console.log(`[sync-branch] \u2713 ${message}`);
19980
20333
  resolve6(
19981
20334
  c.json({
19982
20335
  success: true,
19983
- message: `Successfully synced ${currentBranch} with ${defaultBranch}`,
20336
+ message,
19984
20337
  branch: currentBranch,
19985
20338
  defaultBranch
19986
20339
  })
@@ -20522,7 +20875,7 @@ function createUpdateRoutes(updater) {
20522
20875
  // src/features/mcp/mcp.routes.ts
20523
20876
  import { Hono as Hono13 } from "hono";
20524
20877
  import { readFile as readFile16, writeFile as writeFile8, mkdir as mkdir9, access as access6 } from "fs/promises";
20525
- import { join as join29, dirname as dirname6 } from "path";
20878
+ import { join as join28, dirname as dirname6 } from "path";
20526
20879
 
20527
20880
  // src/features/mcp/mcp.popular.json
20528
20881
  var mcp_popular_default = [
@@ -20633,7 +20986,7 @@ var mcp_popular_default = [
20633
20986
  var MCP_CONFIG_PATHS2 = [".agents/mcp.json", "mcp.json"];
20634
20987
  async function readMCPConfig(projectPath) {
20635
20988
  for (const configPath of MCP_CONFIG_PATHS2) {
20636
- const fullPath = join29(projectPath, configPath);
20989
+ const fullPath = join28(projectPath, configPath);
20637
20990
  try {
20638
20991
  await access6(fullPath);
20639
20992
  const content = await readFile16(fullPath, "utf-8");
@@ -20647,7 +21000,7 @@ async function readMCPConfig(projectPath) {
20647
21000
  }
20648
21001
  async function writeMCPConfig(projectPath, config) {
20649
21002
  const existing = await readMCPConfig(projectPath);
20650
- const targetPath = existing?.filePath ?? join29(projectPath, ".agents/mcp.json");
21003
+ const targetPath = existing?.filePath ?? join28(projectPath, ".agents/mcp.json");
20651
21004
  await mkdir9(dirname6(targetPath), { recursive: true });
20652
21005
  await writeFile8(targetPath, JSON.stringify(config, null, 2), "utf-8");
20653
21006
  }
@@ -21140,7 +21493,7 @@ async function generateImage(c, metadataManager, conversationManager, threadMana
21140
21493
  if (!providerConfig) {
21141
21494
  return c.json({ error: `Unknown provider: ${provider}` }, 400);
21142
21495
  }
21143
- let apiKey = providerConfig.env && providerConfig.env.length > 0 ? process.env[providerConfig.env[0]] : void 0;
21496
+ let apiKey = providerConfig.apiKeyEnv ? process.env[providerConfig.apiKeyEnv] : void 0;
21144
21497
  if (!apiKey) {
21145
21498
  const providerKeys = await metadataManager.getProviderKeys();
21146
21499
  apiKey = providerKeys[providerConfig.id];
@@ -21321,7 +21674,7 @@ async function clipboardWriteImage(c) {
21321
21674
  }
21322
21675
 
21323
21676
  // src/features/image/image-save.route.ts
21324
- import { join as join30 } from "path";
21677
+ import { join as join29 } from "path";
21325
21678
  var Utils5 = null;
21326
21679
  var utilsLoaded5 = false;
21327
21680
  async function loadUtils5() {
@@ -21358,7 +21711,7 @@ async function saveImage(c) {
21358
21711
  return c.json({ error: "imageUrl or imageData is required" }, 400);
21359
21712
  }
21360
21713
  const name = filename ?? `generated-image-${Date.now()}.png`;
21361
- const savePath = join30(Utils5.paths.downloads, name);
21714
+ const savePath = join29(Utils5.paths.downloads, name);
21362
21715
  await Bun.write(savePath, data);
21363
21716
  return c.json({ success: true, path: savePath });
21364
21717
  } catch (error) {
@@ -21447,20 +21800,102 @@ function createBrowserJsRoutes(threadManager) {
21447
21800
  return router;
21448
21801
  }
21449
21802
 
21803
+ // src/features/voice-model/voice-model.routes.ts
21804
+ import { Hono as Hono21 } from "hono";
21805
+ var MODEL_URL = "https://install.tarsk.io/voice-models/ggml-tiny.en.bin";
21806
+ function createVoiceModelRoutes() {
21807
+ const router = new Hono21();
21808
+ router.get("/download", async (c) => {
21809
+ try {
21810
+ const response = await fetch(MODEL_URL);
21811
+ if (!response.ok) {
21812
+ return errorResponse(
21813
+ c,
21814
+ ErrorCodes.EXTERNAL_API_ERROR,
21815
+ `Download server returned ${response.status} ${response.statusText}`,
21816
+ 502
21817
+ );
21818
+ }
21819
+ const contentLength = response.headers.get("content-length");
21820
+ const headers = {
21821
+ "Content-Type": "application/octet-stream"
21822
+ };
21823
+ if (contentLength) {
21824
+ headers["Content-Length"] = contentLength;
21825
+ }
21826
+ return new Response(response.body, {
21827
+ status: 200,
21828
+ headers
21829
+ });
21830
+ } catch (error) {
21831
+ const message = error instanceof Error ? error.message : String(error);
21832
+ return errorResponse(
21833
+ c,
21834
+ ErrorCodes.EXTERNAL_API_ERROR,
21835
+ `Failed to download voice model: ${message}`,
21836
+ 502,
21837
+ message
21838
+ );
21839
+ }
21840
+ });
21841
+ return router;
21842
+ }
21843
+
21844
+ // src/features/logs/logs.routes.ts
21845
+ import { Hono as Hono22 } from "hono";
21846
+ function createLogsRoutes() {
21847
+ const router = new Hono22();
21848
+ router.post("/", async (c) => {
21849
+ let body = {};
21850
+ try {
21851
+ body = await c.req.json();
21852
+ } catch {
21853
+ return c.json({ ok: false, error: "invalid json" }, 400);
21854
+ }
21855
+ const { level = "INFO", context = "app", message = "", data } = body;
21856
+ let line = `[VOICE] [${level}] [${context}] ${message}`;
21857
+ if (data !== void 0 && data !== "") {
21858
+ try {
21859
+ line += ` | ${JSON.stringify(data)}`;
21860
+ } catch {
21861
+ line += ` | [non-serializable]`;
21862
+ }
21863
+ }
21864
+ if (level === "ERROR") {
21865
+ console.error(line);
21866
+ } else if (level === "WARN") {
21867
+ console.warn(line);
21868
+ } else {
21869
+ console.log(line);
21870
+ }
21871
+ return c.json({ ok: true });
21872
+ });
21873
+ router.delete("/", (c) => {
21874
+ console.log("[VOICE] Log cleared by client request");
21875
+ return c.json({ ok: true });
21876
+ });
21877
+ return router;
21878
+ }
21879
+
21450
21880
  // src/server.ts
21451
21881
  var __filename = fileURLToPath(import.meta.url);
21452
21882
  var __dirname = path4.dirname(__filename);
21453
21883
  async function startTarskServer(options) {
21454
21884
  const { isDebug: isDebug2, publicDir: publicDirOverride } = options;
21455
21885
  const port = isDebug2 ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
21456
- const app = new Hono21();
21886
+ const app = new Hono23();
21457
21887
  app.use("/*", cors());
21888
+ app.use("/*", async (c, next) => {
21889
+ c.header("Cross-Origin-Opener-Policy", "same-origin");
21890
+ c.header("Cross-Origin-Embedder-Policy", "require-corp");
21891
+ return next();
21892
+ });
21458
21893
  app.use("/*", async (c, next) => {
21459
21894
  if (c.req.path.startsWith("/api/")) {
21460
21895
  const method = c.req.method;
21461
21896
  const reqPath = c.req.path;
21462
21897
  const fullUrl = `http://localhost:${port}${reqPath}`;
21463
- if (!fullUrl.includes("processing")) {
21898
+ if (!fullUrl.includes("processing") && !reqPath.startsWith("/api/logs")) {
21464
21899
  console.log(`${method} ${fullUrl}`);
21465
21900
  }
21466
21901
  }
@@ -21485,6 +21920,7 @@ async function startTarskServer(options) {
21485
21920
  const conversationManager = new ConversationManagerImpl(dataDir);
21486
21921
  const agentExecutor = new PiExecutorImpl(metadataManager);
21487
21922
  await metadataManager.initialize();
21923
+ updateRuntimeEnv(await metadataManager.getProviderEnvValues());
21488
21924
  await conversationManager.initialize();
21489
21925
  app.get("/health", (c) => {
21490
21926
  return c.json({
@@ -21525,6 +21961,8 @@ async function startTarskServer(options) {
21525
21961
  app.route("/api/scaffold", createScaffoldRoutes(projectManager));
21526
21962
  app.route("/api/ask-user", createAskUserRoutes());
21527
21963
  app.route("/api/browser-js", createBrowserJsRoutes(threadManager));
21964
+ app.route("/api/voice-model", createVoiceModelRoutes());
21965
+ app.route("/api/logs", createLogsRoutes());
21528
21966
  app.route("/api/update", createUpdateRoutes(options.updater));
21529
21967
  createSlashCommandRoutes(app, threadManager);
21530
21968
  createRuleRoutes(app, projectManager);
@@ -21583,7 +22021,6 @@ async function startTarskServer(options) {
21583
22021
  // src/index.ts
21584
22022
  import fs4 from "fs";
21585
22023
  import path5 from "path";
21586
- import { fileURLToPath as fileURLToPath2 } from "url";
21587
22024
  var args = process.argv.slice(2);
21588
22025
  var isDebug = args.includes("--debug");
21589
22026
  var shouldOpenBrowser = args.includes("--open");
@@ -21591,10 +22028,9 @@ if (!isDebug) {
21591
22028
  console.log = () => {
21592
22029
  };
21593
22030
  } else {
21594
- const __filename2 = fileURLToPath2(import.meta.url);
21595
- const cliDir = path5.resolve(path5.dirname(__filename2), "..");
21596
- const logFilePath = path5.join(cliDir, "logs.txt");
22031
+ const logFilePath = path5.join(APP_SUPPORT_DIR, "debug-logs.txt");
21597
22032
  try {
22033
+ fs4.mkdirSync(path5.dirname(logFilePath), { recursive: true });
21598
22034
  fs4.writeFileSync(logFilePath, "");
21599
22035
  } catch {
21600
22036
  }