tarsk 0.3.36 → 0.3.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -96,7 +96,7 @@ import { cors } from "hono/cors";
96
96
  import { serve } from "@hono/node-server";
97
97
  import { serveStatic } from "@hono/node-server/serve-static";
98
98
  import path3 from "path";
99
- import open2 from "open";
99
+ import open3 from "open";
100
100
  import { fileURLToPath as fileURLToPath3 } from "url";
101
101
 
102
102
  // src/managers/project-manager.ts
@@ -1908,12 +1908,7 @@ class MetadataManager {
1908
1908
 
1909
1909
  // src/managers/pi-executor.ts
1910
1910
  import { Agent } from "@mariozechner/pi-agent-core";
1911
- import {
1912
- getModel
1913
- } from "@mariozechner/pi-ai";
1914
- import { resolve, dirname as dirname5, isAbsolute as isAbsolute2 } from "path";
1915
- import { readFileSync as readFileSync3 } from "fs";
1916
- import { fileURLToPath as fileURLToPath2 } from "url";
1911
+ import { resolve as resolve2, isAbsolute as isAbsolute2 } from "path";
1917
1912
 
1918
1913
  // src/tools/bash.ts
1919
1914
  import { randomBytes as randomBytes2 } from "node:crypto";
@@ -2563,6 +2558,38 @@ function resolveReadPath(filePath, cwd) {
2563
2558
  return resolved;
2564
2559
  }
2565
2560
 
2561
+ // src/tools/tool-helpers.ts
2562
+ async function withAbortSignal(signal, operation) {
2563
+ return new Promise((resolve, reject) => {
2564
+ if (signal?.aborted) {
2565
+ reject(new Error("Operation aborted"));
2566
+ return;
2567
+ }
2568
+ let aborted = false;
2569
+ const onAbort = () => {
2570
+ aborted = true;
2571
+ reject(new Error("Operation aborted"));
2572
+ };
2573
+ if (signal) {
2574
+ signal.addEventListener("abort", onAbort, { once: true });
2575
+ }
2576
+ const cleanup = () => {
2577
+ if (signal)
2578
+ signal.removeEventListener("abort", onAbort);
2579
+ };
2580
+ const abortCheck = () => aborted;
2581
+ operation(abortCheck).then((result) => {
2582
+ cleanup();
2583
+ if (!aborted)
2584
+ resolve(result);
2585
+ }).catch((error) => {
2586
+ cleanup();
2587
+ if (!aborted)
2588
+ reject(error);
2589
+ });
2590
+ });
2591
+ }
2592
+
2566
2593
  // src/tools/edit.ts
2567
2594
  var editSchema = Type2.Object({
2568
2595
  path: Type2.String({ description: "Path to the file to edit (relative or absolute)" }),
@@ -2584,83 +2611,49 @@ function createEditTool(cwd, options) {
2584
2611
  execute: async (_toolCallId, { path, oldText, newText }, signal) => {
2585
2612
  const absolutePath = resolveToCwd(path, cwd);
2586
2613
  validatePathWithinCwd(absolutePath, cwd);
2587
- return new Promise((resolve, reject) => {
2588
- if (signal?.aborted) {
2589
- reject(new Error("Operation aborted"));
2590
- return;
2614
+ return withAbortSignal(signal, async (isAborted) => {
2615
+ try {
2616
+ await ops.access(absolutePath);
2617
+ } catch {
2618
+ throw new Error(`File not found: ${path}`);
2591
2619
  }
2592
- let aborted = false;
2593
- const onAbort = () => {
2594
- aborted = true;
2595
- reject(new Error("Operation aborted"));
2620
+ if (isAborted())
2621
+ return { content: [], details: undefined };
2622
+ const buffer = await ops.readFile(absolutePath);
2623
+ const rawContent = buffer.toString("utf-8");
2624
+ if (isAborted())
2625
+ return { content: [], details: undefined };
2626
+ const { bom, text: content } = stripBom(rawContent);
2627
+ const originalEnding = detectLineEnding(content);
2628
+ const normalizedContent = normalizeToLF(content);
2629
+ const normalizedOldText = normalizeToLF(oldText);
2630
+ const normalizedNewText = normalizeToLF(newText);
2631
+ const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
2632
+ if (!matchResult.found) {
2633
+ throw new Error(`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`);
2634
+ }
2635
+ const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
2636
+ const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
2637
+ const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
2638
+ if (occurrences > 1) {
2639
+ throw new Error(`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`);
2640
+ }
2641
+ if (isAborted())
2642
+ return { content: [], details: undefined };
2643
+ const baseContent = matchResult.contentForReplacement;
2644
+ const newContent = baseContent.substring(0, matchResult.index) + normalizedNewText + baseContent.substring(matchResult.index + matchResult.matchLength);
2645
+ if (baseContent === newContent) {
2646
+ throw new Error(`No changes made to ${path}. The replacement produced identical content.`);
2647
+ }
2648
+ const finalContent = bom + restoreLineEndings(newContent, originalEnding);
2649
+ await ops.writeFile(absolutePath, finalContent);
2650
+ if (isAborted())
2651
+ return { content: [], details: undefined };
2652
+ const diffResult = generateDiffString(baseContent, newContent);
2653
+ return {
2654
+ content: [{ type: "text", text: `Successfully replaced text in ${path}.` }],
2655
+ details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }
2596
2656
  };
2597
- if (signal)
2598
- signal.addEventListener("abort", onAbort, { once: true });
2599
- (async () => {
2600
- try {
2601
- try {
2602
- await ops.access(absolutePath);
2603
- } catch {
2604
- if (signal)
2605
- signal.removeEventListener("abort", onAbort);
2606
- reject(new Error(`File not found: ${path}`));
2607
- return;
2608
- }
2609
- if (aborted)
2610
- return;
2611
- const buffer = await ops.readFile(absolutePath);
2612
- const rawContent = buffer.toString("utf-8");
2613
- if (aborted)
2614
- return;
2615
- const { bom, text: content } = stripBom(rawContent);
2616
- const originalEnding = detectLineEnding(content);
2617
- const normalizedContent = normalizeToLF(content);
2618
- const normalizedOldText = normalizeToLF(oldText);
2619
- const normalizedNewText = normalizeToLF(newText);
2620
- const matchResult = fuzzyFindText(normalizedContent, normalizedOldText);
2621
- if (!matchResult.found) {
2622
- if (signal)
2623
- signal.removeEventListener("abort", onAbort);
2624
- reject(new Error(`Could not find the exact text in ${path}. The old text must match exactly including all whitespace and newlines.`));
2625
- return;
2626
- }
2627
- const fuzzyContent = normalizeForFuzzyMatch(normalizedContent);
2628
- const fuzzyOldText = normalizeForFuzzyMatch(normalizedOldText);
2629
- const occurrences = fuzzyContent.split(fuzzyOldText).length - 1;
2630
- if (occurrences > 1) {
2631
- if (signal)
2632
- signal.removeEventListener("abort", onAbort);
2633
- reject(new Error(`Found ${occurrences} occurrences of the text in ${path}. The text must be unique. Please provide more context to make it unique.`));
2634
- return;
2635
- }
2636
- if (aborted)
2637
- return;
2638
- const baseContent = matchResult.contentForReplacement;
2639
- const newContent = baseContent.substring(0, matchResult.index) + normalizedNewText + baseContent.substring(matchResult.index + matchResult.matchLength);
2640
- if (baseContent === newContent) {
2641
- if (signal)
2642
- signal.removeEventListener("abort", onAbort);
2643
- reject(new Error(`No changes made to ${path}. The replacement produced identical content.`));
2644
- return;
2645
- }
2646
- const finalContent = bom + restoreLineEndings(newContent, originalEnding);
2647
- await ops.writeFile(absolutePath, finalContent);
2648
- if (aborted)
2649
- return;
2650
- if (signal)
2651
- signal.removeEventListener("abort", onAbort);
2652
- const diffResult = generateDiffString(baseContent, newContent);
2653
- resolve({
2654
- content: [{ type: "text", text: `Successfully replaced text in ${path}.` }],
2655
- details: { diff: diffResult.diff, firstChangedLine: diffResult.firstChangedLine }
2656
- });
2657
- } catch (error) {
2658
- if (signal)
2659
- signal.removeEventListener("abort", onAbort);
2660
- if (!aborted)
2661
- reject(error);
2662
- }
2663
- })();
2664
2657
  });
2665
2658
  }
2666
2659
  };
@@ -3147,87 +3140,64 @@ function createReadTool(cwd, options) {
3147
3140
  execute: async (_toolCallId, { path: path3, offset, limit }, signal) => {
3148
3141
  const absolutePath = resolveReadPath(path3, cwd);
3149
3142
  validatePathWithinCwd(absolutePath, cwd);
3150
- return new Promise((resolve, reject) => {
3151
- if (signal?.aborted) {
3152
- reject(new Error("Operation aborted"));
3153
- return;
3154
- }
3155
- let aborted = false;
3156
- const onAbort = () => {
3157
- aborted = true;
3158
- reject(new Error("Operation aborted"));
3159
- };
3160
- if (signal) {
3161
- signal.addEventListener("abort", onAbort, { once: true });
3162
- }
3163
- (async () => {
3164
- try {
3165
- await ops.access(absolutePath);
3166
- if (aborted)
3167
- return;
3168
- const buffer = await ops.readFile(absolutePath);
3169
- const textContent = buffer.toString("utf-8");
3170
- const allLines = textContent.split(`
3143
+ return withAbortSignal(signal, async (isAborted) => {
3144
+ await ops.access(absolutePath);
3145
+ if (isAborted())
3146
+ return { content: [], details: undefined };
3147
+ const buffer = await ops.readFile(absolutePath);
3148
+ const textContent = buffer.toString("utf-8");
3149
+ const allLines = textContent.split(`
3171
3150
  `);
3172
- const totalFileLines = allLines.length;
3173
- const startLine = offset ? Math.max(0, offset - 1) : 0;
3174
- const startLineDisplay = startLine + 1;
3175
- if (startLine >= allLines.length) {
3176
- throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
3177
- }
3178
- let selectedContent;
3179
- let userLimitedLines;
3180
- if (limit !== undefined) {
3181
- const endLine = Math.min(startLine + limit, allLines.length);
3182
- selectedContent = allLines.slice(startLine, endLine).join(`
3151
+ const totalFileLines = allLines.length;
3152
+ const startLine = offset ? Math.max(0, offset - 1) : 0;
3153
+ const startLineDisplay = startLine + 1;
3154
+ if (startLine >= allLines.length) {
3155
+ throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
3156
+ }
3157
+ let selectedContent;
3158
+ let userLimitedLines;
3159
+ if (limit !== undefined) {
3160
+ const endLine = Math.min(startLine + limit, allLines.length);
3161
+ selectedContent = allLines.slice(startLine, endLine).join(`
3183
3162
  `);
3184
- userLimitedLines = endLine - startLine;
3185
- } else {
3186
- selectedContent = allLines.slice(startLine).join(`
3163
+ userLimitedLines = endLine - startLine;
3164
+ } else {
3165
+ selectedContent = allLines.slice(startLine).join(`
3187
3166
  `);
3188
- }
3189
- const truncation = truncateHead(selectedContent);
3190
- let outputText;
3191
- let details;
3192
- if (truncation.firstLineExceedsLimit) {
3193
- outputText = `[Line ${startLineDisplay} is ${formatSize(Buffer.byteLength(allLines[startLine], "utf-8"))}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path3} | head -c ${DEFAULT_MAX_BYTES}]`;
3194
- details = { truncation };
3195
- } else if (truncation.truncated) {
3196
- const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
3197
- const nextOffset = endLineDisplay + 1;
3198
- outputText = truncation.content;
3199
- if (truncation.truncatedBy === "lines") {
3200
- outputText += `
3167
+ }
3168
+ const truncation = truncateHead(selectedContent);
3169
+ let outputText;
3170
+ let details;
3171
+ if (truncation.firstLineExceedsLimit) {
3172
+ outputText = `[Line ${startLineDisplay} is ${formatSize(Buffer.byteLength(allLines[startLine], "utf-8"))}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path3} | head -c ${DEFAULT_MAX_BYTES}]`;
3173
+ details = { truncation };
3174
+ } else if (truncation.truncated) {
3175
+ const endLineDisplay = startLineDisplay + truncation.outputLines - 1;
3176
+ const nextOffset = endLineDisplay + 1;
3177
+ outputText = truncation.content;
3178
+ if (truncation.truncatedBy === "lines") {
3179
+ outputText += `
3201
3180
 
3202
3181
  [Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use offset=${nextOffset} to continue.]`;
3203
- } else {
3204
- outputText += `
3182
+ } else {
3183
+ outputText += `
3205
3184
 
3206
3185
  [Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines} (${formatSize(DEFAULT_MAX_BYTES)} limit). Use offset=${nextOffset} to continue.]`;
3207
- }
3208
- details = { truncation };
3209
- } else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
3210
- const remaining = allLines.length - (startLine + userLimitedLines);
3211
- const nextOffset = startLine + userLimitedLines + 1;
3212
- outputText = truncation.content;
3213
- outputText += `
3186
+ }
3187
+ details = { truncation };
3188
+ } else if (userLimitedLines !== undefined && startLine + userLimitedLines < allLines.length) {
3189
+ const remaining = allLines.length - (startLine + userLimitedLines);
3190
+ const nextOffset = startLine + userLimitedLines + 1;
3191
+ outputText = truncation.content;
3192
+ outputText += `
3214
3193
 
3215
3194
  [${remaining} more lines in file. Use offset=${nextOffset} to continue.]`;
3216
- } else {
3217
- outputText = truncation.content;
3218
- }
3219
- if (aborted)
3220
- return;
3221
- if (signal)
3222
- signal.removeEventListener("abort", onAbort);
3223
- resolve({ content: [{ type: "text", text: outputText }], details });
3224
- } catch (error) {
3225
- if (signal)
3226
- signal.removeEventListener("abort", onAbort);
3227
- if (!aborted)
3228
- reject(error);
3229
- }
3230
- })();
3195
+ } else {
3196
+ outputText = truncation.content;
3197
+ }
3198
+ if (isAborted())
3199
+ return { content: [], details: undefined };
3200
+ return { content: [{ type: "text", text: outputText }], details };
3231
3201
  });
3232
3202
  }
3233
3203
  };
@@ -3296,14 +3266,239 @@ function createWriteTool(cwd, options) {
3296
3266
  }
3297
3267
  var writeTool = createWriteTool(process.cwd());
3298
3268
 
3269
+ // src/tools/skill-tool.ts
3270
+ import { readdir } from "fs/promises";
3271
+ import { join as join7, extname } from "path";
3272
+ import { existsSync as existsSync6, statSync as statSync3 } from "fs";
3273
+ import { Type as Type8 } from "@sinclair/typebox";
3274
+ import { spawn as spawn7 } from "child_process";
3275
+ function getInterpreter(scriptPath) {
3276
+ const ext = extname(scriptPath);
3277
+ switch (ext) {
3278
+ case ".sh":
3279
+ case ".bash":
3280
+ return { command: "bash", args: [scriptPath] };
3281
+ case ".py":
3282
+ return { command: "python3", args: [scriptPath] };
3283
+ case ".js":
3284
+ return { command: "node", args: [scriptPath] };
3285
+ case ".ts":
3286
+ return { command: "bun", args: ["run", scriptPath] };
3287
+ default:
3288
+ try {
3289
+ const stats = statSync3(scriptPath);
3290
+ if (stats.mode & 73) {
3291
+ return { command: scriptPath, args: [] };
3292
+ }
3293
+ } catch {}
3294
+ return null;
3295
+ }
3296
+ }
3297
+ async function executeScript(scriptPath, args, cwd, timeout = 30) {
3298
+ const interpreter = getInterpreter(scriptPath);
3299
+ if (!interpreter) {
3300
+ throw new Error(`Unsupported script type: ${scriptPath}`);
3301
+ }
3302
+ return new Promise((resolve, reject) => {
3303
+ const child = spawn7(interpreter.command, [...interpreter.args, ...args], {
3304
+ cwd,
3305
+ timeout: timeout * 1000
3306
+ });
3307
+ let stdout = "";
3308
+ let stderr = "";
3309
+ let timedOut = false;
3310
+ if (child.stdout) {
3311
+ child.stdout.on("data", (data) => {
3312
+ stdout += data.toString();
3313
+ });
3314
+ }
3315
+ if (child.stderr) {
3316
+ child.stderr.on("data", (data) => {
3317
+ stderr += data.toString();
3318
+ });
3319
+ }
3320
+ const timeoutHandle = setTimeout(() => {
3321
+ timedOut = true;
3322
+ child.kill("SIGTERM");
3323
+ }, timeout * 1000);
3324
+ child.on("error", (err) => {
3325
+ clearTimeout(timeoutHandle);
3326
+ reject(err);
3327
+ });
3328
+ child.on("close", (code) => {
3329
+ clearTimeout(timeoutHandle);
3330
+ if (timedOut) {
3331
+ reject(new Error(`Script execution timed out after ${timeout}s`));
3332
+ return;
3333
+ }
3334
+ resolve({ stdout, stderr, exitCode: code });
3335
+ });
3336
+ });
3337
+ }
3338
+ var skillScriptSchema = Type8.Object({
3339
+ skillName: Type8.String({ description: "Name of the skill" }),
3340
+ scriptName: Type8.String({ description: "Name of the script to execute (without path)" }),
3341
+ args: Type8.Optional(Type8.Array(Type8.String(), { description: "Arguments to pass to the script" })),
3342
+ timeout: Type8.Optional(Type8.Number({ description: "Timeout in seconds (default: 30)" }))
3343
+ });
3344
+ function createSkillScriptTool(skills, cwd) {
3345
+ return {
3346
+ name: "execute_skill_script",
3347
+ description: "Execute a script from an activated skill. Skills provide executable scripts in their scripts/ directory.",
3348
+ input_schema: skillScriptSchema,
3349
+ execute: async (input) => {
3350
+ const { skillName, scriptName, args = [], timeout = 30 } = input;
3351
+ const skill = skills.find((s) => s.name === skillName);
3352
+ if (!skill) {
3353
+ return {
3354
+ error: `Skill not found: ${skillName}. Available skills: ${skills.map((s) => s.name).join(", ")}`
3355
+ };
3356
+ }
3357
+ const scriptsDir = join7(skill.skillPath, "scripts");
3358
+ if (!existsSync6(scriptsDir)) {
3359
+ return {
3360
+ error: `Skill '${skillName}' has no scripts directory`
3361
+ };
3362
+ }
3363
+ const scriptPath = join7(scriptsDir, scriptName);
3364
+ if (!existsSync6(scriptPath)) {
3365
+ try {
3366
+ const availableScripts = await readdir(scriptsDir);
3367
+ return {
3368
+ error: `Script '${scriptName}' not found in skill '${skillName}'. Available scripts: ${availableScripts.join(", ")}`
3369
+ };
3370
+ } catch {
3371
+ return {
3372
+ error: `Script '${scriptName}' not found in skill '${skillName}'`
3373
+ };
3374
+ }
3375
+ }
3376
+ try {
3377
+ const result = await executeScript(scriptPath, args, cwd, timeout);
3378
+ let output = "";
3379
+ if (result.stdout) {
3380
+ output += `STDOUT:
3381
+ ${result.stdout}
3382
+ `;
3383
+ }
3384
+ if (result.stderr) {
3385
+ output += `STDERR:
3386
+ ${result.stderr}
3387
+ `;
3388
+ }
3389
+ output += `Exit code: ${result.exitCode}
3390
+ `;
3391
+ return output;
3392
+ } catch (error) {
3393
+ return {
3394
+ error: `Failed to execute script: ${error instanceof Error ? error.message : String(error)}`
3395
+ };
3396
+ }
3397
+ }
3398
+ };
3399
+ }
3400
+
3401
+ // src/tools/skill-reference-tool.ts
3402
+ import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
3403
+ import { join as join8, normalize, relative as relative3 } from "path";
3404
+ import { existsSync as existsSync7 } from "fs";
3405
+ import { Type as Type9 } from "@sinclair/typebox";
3406
+ function isPathSafe(basePath, requestedPath) {
3407
+ const normalized = normalize(requestedPath);
3408
+ const fullPath = join8(basePath, normalized);
3409
+ const relativePath = relative3(basePath, fullPath);
3410
+ return !relativePath.startsWith("..") && !relativePath.startsWith("/");
3411
+ }
3412
+ var skillReferenceSchema = Type9.Object({
3413
+ skillName: Type9.String({ description: "Name of the skill" }),
3414
+ referencePath: Type9.String({
3415
+ description: `Path to the reference file relative to the skill's references/ directory (e.g., "api-reference.md" or "guides/setup.md")`
3416
+ })
3417
+ });
3418
+ function createSkillReferenceTool(skills) {
3419
+ return {
3420
+ name: "read_skill_reference",
3421
+ description: "Read a reference file from an activated skill. Skills provide additional documentation in their references/ directory.",
3422
+ input_schema: skillReferenceSchema,
3423
+ execute: async (input) => {
3424
+ const { skillName, referencePath } = input;
3425
+ const skill = skills.find((s) => s.name === skillName);
3426
+ if (!skill) {
3427
+ return {
3428
+ error: `Skill not found: ${skillName}. Available skills: ${skills.map((s) => s.name).join(", ")}`
3429
+ };
3430
+ }
3431
+ const referencesDir = join8(skill.skillPath, "references");
3432
+ if (!existsSync7(referencesDir)) {
3433
+ return {
3434
+ error: `Skill '${skillName}' has no references directory`
3435
+ };
3436
+ }
3437
+ if (!isPathSafe(referencesDir, referencePath)) {
3438
+ return {
3439
+ error: `Invalid reference path: ${referencePath}. Path must be within the skill's references directory.`
3440
+ };
3441
+ }
3442
+ const fullPath = join8(referencesDir, referencePath);
3443
+ if (!existsSync7(fullPath)) {
3444
+ try {
3445
+ const availableRefs = await listReferencesRecursive(referencesDir);
3446
+ return {
3447
+ error: `Reference file '${referencePath}' not found in skill '${skillName}'. Available references:
3448
+ ${availableRefs.join(`
3449
+ `)}`
3450
+ };
3451
+ } catch {
3452
+ return {
3453
+ error: `Reference file '${referencePath}' not found in skill '${skillName}'`
3454
+ };
3455
+ }
3456
+ }
3457
+ try {
3458
+ const content = await readFile2(fullPath, "utf-8");
3459
+ return content;
3460
+ } catch (error) {
3461
+ return {
3462
+ error: `Failed to read reference file: ${error instanceof Error ? error.message : String(error)}`
3463
+ };
3464
+ }
3465
+ }
3466
+ };
3467
+ }
3468
+ async function listReferencesRecursive(dir, prefix = "") {
3469
+ const files = [];
3470
+ try {
3471
+ const entries = await readdir2(dir, { withFileTypes: true });
3472
+ for (const entry of entries) {
3473
+ const relativePath = prefix ? join8(prefix, entry.name) : entry.name;
3474
+ if (entry.isDirectory()) {
3475
+ const subFiles = await listReferencesRecursive(join8(dir, entry.name), relativePath);
3476
+ files.push(...subFiles);
3477
+ } else {
3478
+ files.push(relativePath);
3479
+ }
3480
+ }
3481
+ } catch (error) {
3482
+ console.error(`Failed to list references in ${dir}:`, error);
3483
+ }
3484
+ return files;
3485
+ }
3299
3486
  // src/tools/index.ts
3300
3487
  function createCodingTools(cwd, options) {
3301
- return [
3488
+ const baseTools = [
3302
3489
  createReadTool(cwd, options?.read),
3303
3490
  createBashTool(cwd, options?.bash),
3304
3491
  createEditTool(cwd),
3305
3492
  createWriteTool(cwd)
3306
3493
  ];
3494
+ if (options?.skills && options.skills.length > 0) {
3495
+ const skillTools = [
3496
+ createSkillScriptTool(options.skills, cwd),
3497
+ createSkillReferenceTool(options.skills)
3498
+ ];
3499
+ return [...baseTools, ...skillTools];
3500
+ }
3501
+ return baseTools;
3307
3502
  }
3308
3503
 
3309
3504
  // src/provider.ts
@@ -3493,15 +3688,16 @@ var PROVIDERS = [
3493
3688
  ];
3494
3689
 
3495
3690
  // src/paths.ts
3496
- import { join as join7 } from "path";
3691
+ import { join as join9 } from "path";
3497
3692
  import { homedir as homedir2 } from "os";
3498
- var APP_SUPPORT_DIR = join7(homedir2(), "Library", "Application Support", "Tarsk");
3499
- var DATA_DIR = join7(APP_SUPPORT_DIR, "data");
3693
+ var APP_SUPPORT_DIR = join9(homedir2(), "Library", "Application Support", "Tarsk");
3694
+ var DATA_DIR = join9(APP_SUPPORT_DIR, "data");
3500
3695
  function getDataDir() {
3501
3696
  return DATA_DIR;
3502
3697
  }
3503
3698
 
3504
- // src/managers/pi-executor.ts
3699
+ // src/managers/pi-model-resolver.ts
3700
+ import { getModel } from "@mariozechner/pi-ai";
3505
3701
  var PROVIDER_NAME_TO_PI = {
3506
3702
  anthropic: "anthropic",
3507
3703
  openai: "openai",
@@ -3549,32 +3745,188 @@ function resolveModel(providerName, modelId, providerConfig) {
3549
3745
  maxTokens: 16384
3550
3746
  };
3551
3747
  }
3748
+
3749
+ // src/managers/pi-event-transformer.ts
3552
3750
  function extractTextFromAssistantMessage(msg) {
3553
3751
  return msg.content.filter((b) => b.type === "text").map((b) => b.text).join("");
3554
3752
  }
3555
- var getErrorCode = (error) => {
3556
- if (error && typeof error === "object" && "code" in error) {
3557
- const code = error.code;
3558
- if (typeof code === "string")
3559
- return code;
3753
+
3754
+ class EventQueue {
3755
+ queue = [];
3756
+ resolver = null;
3757
+ finalContent = "";
3758
+ errorOccurred = false;
3759
+ handlePiEvent(event) {
3760
+ if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
3761
+ const delta = event.assistantMessageEvent.delta;
3762
+ this.finalContent += delta;
3763
+ this.push({
3764
+ type: "message",
3765
+ role: "assistant",
3766
+ content: delta
3767
+ });
3768
+ } else if (event.type === "tool_execution_start") {
3769
+ this.push({
3770
+ type: "message",
3771
+ role: "tool",
3772
+ content: JSON.stringify([
3773
+ {
3774
+ type: "tool_use",
3775
+ name: event.toolName,
3776
+ id: event.toolCallId,
3777
+ input: event.args
3778
+ }
3779
+ ])
3780
+ });
3781
+ } else if (event.type === "tool_execution_end") {
3782
+ const resultContent = event.result?.content?.filter((b) => b.type === "text").map((b) => b.text || "").join("") || JSON.stringify(event.result);
3783
+ this.push({
3784
+ type: "message",
3785
+ role: "tool",
3786
+ content: JSON.stringify([
3787
+ {
3788
+ type: "tool-result",
3789
+ toolCallId: event.toolCallId,
3790
+ toolName: event.toolName,
3791
+ content: resultContent,
3792
+ isError: event.isError
3793
+ }
3794
+ ])
3795
+ });
3796
+ } else if (event.type === "agent_end") {
3797
+ const messages = event.messages;
3798
+ const lastAssistant = [...messages].reverse().find((m) => ("role" in m) && m.role === "assistant");
3799
+ if (lastAssistant) {
3800
+ const extracted = extractTextFromAssistantMessage(lastAssistant);
3801
+ if (extracted && extracted !== this.finalContent) {
3802
+ this.finalContent = extracted;
3803
+ }
3804
+ }
3805
+ }
3806
+ this.notifyResolver();
3560
3807
  }
3561
- return;
3562
- };
3563
- function loadAgentSystemPrompt() {
3564
- const DEFAULT_PROMPT = "You are a helpful coding assistant. You have access to read, bash, edit, and write tools. Use them to explore and modify the codebase as needed.";
3808
+ push(event) {
3809
+ this.queue.push(event);
3810
+ }
3811
+ shift() {
3812
+ return this.queue.shift();
3813
+ }
3814
+ get length() {
3815
+ return this.queue.length;
3816
+ }
3817
+ setResolver(resolver) {
3818
+ this.resolver = resolver;
3819
+ }
3820
+ clearResolver() {
3821
+ this.resolver = null;
3822
+ }
3823
+ notifyResolver() {
3824
+ if (this.resolver) {
3825
+ this.resolver();
3826
+ this.resolver = null;
3827
+ }
3828
+ }
3829
+ notify() {
3830
+ this.notifyResolver();
3831
+ }
3832
+ }
3833
+
3834
+ // src/managers/pi-prompt-loader.ts
3835
+ import { resolve, dirname as dirname5 } from "path";
3836
+ import { readFileSync as readFileSync3 } from "fs";
3837
+ import { fileURLToPath as fileURLToPath2 } from "url";
3838
+ var DEFAULT_PROMPT = "You are a helpful coding assistant. You have access to read, bash, edit, and write tools. Use them to explore and modify the codebase as needed.";
3839
+ function loadAgentSystemPrompt(skills) {
3840
+ let basePrompt;
3565
3841
  try {
3566
3842
  const __filename2 = fileURLToPath2(import.meta.url);
3567
3843
  const __dirname3 = dirname5(__filename2);
3568
3844
  const agentsPath = resolve(__dirname3, "../../agents.md");
3569
3845
  const content = readFileSync3(agentsPath, "utf-8");
3570
3846
  console.log("[ai] Successfully loaded agents.md for system prompt");
3571
- return content.trim();
3847
+ basePrompt = content.trim();
3572
3848
  } catch (error) {
3573
- console.warn("[ai] Could not load agents.md, using default prompt:", error);
3574
- return DEFAULT_PROMPT;
3849
+ const isFileNotFound = error && typeof error === "object" && "code" in error && error.code === "ENOENT";
3850
+ if (isFileNotFound) {
3851
+ console.log("[ai] agents.md not found, using default system prompt");
3852
+ } else {
3853
+ console.warn("[ai] Error loading agents.md, using default prompt:", error instanceof Error ? error.message : String(error));
3854
+ }
3855
+ basePrompt = DEFAULT_PROMPT;
3856
+ }
3857
+ if (!skills || skills.length === 0) {
3858
+ return basePrompt;
3859
+ }
3860
+ let skillsSection = `
3861
+
3862
+ # Available Skills
3863
+
3864
+ `;
3865
+ skillsSection += `The following skills are available for this session. Use them to enhance your capabilities:
3866
+
3867
+ `;
3868
+ for (const skill of skills) {
3869
+ skillsSection += `## ${skill.name}
3870
+
3871
+ `;
3872
+ skillsSection += `**Description**: ${skill.description}
3873
+
3874
+ `;
3875
+ if (skill.compatibility) {
3876
+ skillsSection += `**Compatibility**: ${skill.compatibility}
3877
+
3878
+ `;
3879
+ }
3880
+ skillsSection += skill.instructions;
3881
+ skillsSection += `
3882
+
3883
+ ---
3884
+
3885
+ `;
3886
+ }
3887
+ console.log(`[ai] Injected ${skills.length} skill(s) into system prompt: ${skills.map((s) => s.name).join(", ")}`);
3888
+ return basePrompt + skillsSection;
3889
+ }
3890
+
3891
+ // src/managers/pi-error-utils.ts
3892
+ function getErrorCode(error) {
3893
+ if (error && typeof error === "object" && "code" in error) {
3894
+ const code = error.code;
3895
+ if (typeof code === "string")
3896
+ return code;
3897
+ }
3898
+ return;
3899
+ }
3900
+ function formatErrorEvent(error) {
3901
+ let errCode;
3902
+ let errMessage;
3903
+ let errStack;
3904
+ let errDetails;
3905
+ if (error instanceof Error) {
3906
+ errMessage = error.message || "Unknown error";
3907
+ errStack = error.stack;
3908
+ errCode = getErrorCode(error);
3909
+ } else if (typeof error === "object" && error !== null) {
3910
+ errMessage = error.message ? String(error.message) : JSON.stringify(error);
3911
+ errCode = getErrorCode(error);
3912
+ errDetails = error;
3913
+ } else if (typeof error === "string") {
3914
+ errMessage = error;
3915
+ } else {
3916
+ errMessage = String(error);
3575
3917
  }
3918
+ return {
3919
+ type: "error",
3920
+ content: errMessage,
3921
+ error: {
3922
+ code: errCode || "EXECUTION_ERROR",
3923
+ message: errMessage || "An error occurred during execution",
3924
+ details: { stack: errStack, originalError: errDetails }
3925
+ }
3926
+ };
3576
3927
  }
3577
3928
 
3929
+ // src/managers/pi-executor.ts
3578
3930
  class PiExecutorImpl {
3579
3931
  metadataManager;
3580
3932
  constructor(metadataManager) {
@@ -3591,7 +3943,7 @@ class PiExecutorImpl {
3591
3943
  throw new Error("No model specified in execution context");
3592
3944
  }
3593
3945
  model = model.replace(`${providerName.toLowerCase()}/`, "");
3594
- const cwd = isAbsolute2(context.threadPath) ? context.threadPath : resolve(getDataDir(), context.threadPath);
3946
+ const cwd = isAbsolute2(context.threadPath) ? context.threadPath : resolve2(getDataDir(), context.threadPath);
3595
3947
  console.log("[ai] Execution context:", {
3596
3948
  model,
3597
3949
  cwd,
@@ -3619,9 +3971,9 @@ class PiExecutorImpl {
3619
3971
  api: resolvedModel.api,
3620
3972
  provider: resolvedModel.provider
3621
3973
  });
3622
- const tools = createCodingTools(cwd);
3623
- const systemPrompt = loadAgentSystemPrompt();
3624
- console.log("[ai] System prompt loaded from agents.md");
3974
+ const tools = createCodingTools(cwd, { skills: context.skills });
3975
+ const systemPrompt = loadAgentSystemPrompt(context.skills);
3976
+ console.log("[ai] System prompt loaded from agents.md", context.skills && context.skills.length > 0 ? `with ${context.skills.length} skill(s)` : "");
3625
3977
  const agent = new Agent({
3626
3978
  initialState: {
3627
3979
  systemPrompt,
@@ -3630,74 +3982,19 @@ class PiExecutorImpl {
3630
3982
  },
3631
3983
  getApiKey: async () => apiKey
3632
3984
  });
3633
- const queue = [];
3634
- let resolver = null;
3985
+ const eventQueue = new EventQueue;
3635
3986
  let done = false;
3636
- let finalContent = "";
3637
- let errorOccurred = false;
3638
3987
  const unsubscribe = agent.subscribe((event) => {
3639
- if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
3640
- const delta = event.assistantMessageEvent.delta;
3641
- finalContent += delta;
3642
- queue.push({
3643
- type: "message",
3644
- role: "assistant",
3645
- content: delta
3646
- });
3647
- } else if (event.type === "tool_execution_start") {
3648
- queue.push({
3649
- type: "message",
3650
- role: "tool",
3651
- content: JSON.stringify([
3652
- {
3653
- type: "tool_use",
3654
- name: event.toolName,
3655
- id: event.toolCallId,
3656
- input: event.args
3657
- }
3658
- ])
3659
- });
3660
- } else if (event.type === "tool_execution_end") {
3661
- const resultContent = event.result?.content?.filter((b) => b.type === "text").map((b) => b.text || "").join("") || JSON.stringify(event.result);
3662
- queue.push({
3663
- type: "message",
3664
- role: "tool",
3665
- content: JSON.stringify([
3666
- {
3667
- type: "tool-result",
3668
- toolCallId: event.toolCallId,
3669
- toolName: event.toolName,
3670
- content: resultContent,
3671
- isError: event.isError
3672
- }
3673
- ])
3674
- });
3675
- } else if (event.type === "agent_end") {
3676
- const messages = event.messages;
3677
- const lastAssistant = [...messages].reverse().find((m) => ("role" in m) && m.role === "assistant");
3678
- if (lastAssistant) {
3679
- const extracted = extractTextFromAssistantMessage(lastAssistant);
3680
- if (extracted && extracted !== finalContent) {
3681
- finalContent = extracted;
3682
- }
3683
- }
3684
- }
3685
- if (resolver) {
3686
- resolver();
3687
- resolver = null;
3688
- }
3988
+ eventQueue.handlePiEvent(event);
3689
3989
  });
3690
3990
  const promptDone = agent.prompt(userPrompt).then(() => {
3691
3991
  done = true;
3692
- if (resolver) {
3693
- resolver();
3694
- resolver = null;
3695
- }
3992
+ eventQueue.notify();
3696
3993
  }).catch((err) => {
3697
- if (!errorOccurred) {
3698
- errorOccurred = true;
3994
+ if (!eventQueue.errorOccurred) {
3995
+ eventQueue.errorOccurred = true;
3699
3996
  const errMessage = err instanceof Error ? err.message : String(err);
3700
- queue.push({
3997
+ eventQueue.push({
3701
3998
  type: "error",
3702
3999
  content: errMessage,
3703
4000
  error: {
@@ -3710,68 +4007,40 @@ class PiExecutorImpl {
3710
4007
  });
3711
4008
  }
3712
4009
  done = true;
3713
- if (resolver) {
3714
- resolver();
3715
- resolver = null;
3716
- }
4010
+ eventQueue.notify();
3717
4011
  });
3718
4012
  try {
3719
- while (!done || queue.length > 0) {
3720
- if (queue.length > 0) {
3721
- yield queue.shift();
4013
+ while (!done || eventQueue.length > 0) {
4014
+ if (eventQueue.length > 0) {
4015
+ yield eventQueue.shift();
3722
4016
  } else {
3723
- await new Promise((r) => {
3724
- resolver = r;
4017
+ await new Promise((resolve3) => {
4018
+ eventQueue.setResolver(resolve3);
3725
4019
  });
3726
4020
  }
3727
4021
  }
3728
4022
  await promptDone;
3729
- if (!errorOccurred && finalContent) {
4023
+ if (!eventQueue.errorOccurred && eventQueue.finalContent) {
3730
4024
  yield {
3731
4025
  type: "result",
3732
- content: finalContent
4026
+ content: eventQueue.finalContent
3733
4027
  };
3734
4028
  }
3735
4029
  } finally {
3736
4030
  unsubscribe();
3737
4031
  agent.abort();
4032
+ eventQueue.clearResolver();
3738
4033
  }
3739
4034
  } catch (error) {
3740
4035
  console.error("[ai] Error during execution:", error);
3741
- let errCode;
3742
- let errMessage;
3743
- let errStack;
3744
- let errDetails;
3745
- if (error instanceof Error) {
3746
- errMessage = error.message || "Unknown error";
3747
- errStack = error.stack;
3748
- errCode = getErrorCode(error);
3749
- } else if (typeof error === "object" && error !== null) {
3750
- errMessage = error.message ? String(error.message) : JSON.stringify(error);
3751
- errCode = getErrorCode(error);
3752
- errDetails = error;
3753
- } else if (typeof error === "string") {
3754
- errMessage = error;
3755
- } else {
3756
- errMessage = String(error);
3757
- }
3758
- console.error("[ai] Error details:", { errCode, errMessage, errStack });
3759
- yield {
3760
- type: "error",
3761
- content: errMessage,
3762
- error: {
3763
- code: errCode || "EXECUTION_ERROR",
3764
- message: errMessage || "An error occurred during execution",
3765
- details: { stack: errStack, originalError: errDetails }
3766
- }
3767
- };
4036
+ yield formatErrorEvent(error);
3768
4037
  }
3769
4038
  }
3770
4039
  }
3771
4040
 
3772
4041
  // src/managers/conversation-manager.ts
3773
4042
  import { promises as fs2 } from "fs";
3774
- import { join as join8, dirname as dirname6 } from "path";
4043
+ import { join as join10, dirname as dirname6 } from "path";
3775
4044
  import { randomUUID as randomUUID3 } from "crypto";
3776
4045
  var CONVERSATION_FILE = "conversation-history.json";
3777
4046
 
@@ -3860,8 +4129,8 @@ class ConversationManagerImpl {
3860
4129
  }
3861
4130
  getConversationFilePath(threadPath) {
3862
4131
  const threadId = threadPath.split(/[\\/]/).pop() || "unknown";
3863
- const conversationsDir = join8(this.metadataDir, "conversations");
3864
- return join8(conversationsDir, threadId, CONVERSATION_FILE);
4132
+ const conversationsDir = join10(this.metadataDir, "conversations");
4133
+ return join10(conversationsDir, threadId, CONVERSATION_FILE);
3865
4134
  }
3866
4135
  }
3867
4136
 
@@ -3908,6 +4177,9 @@ function streamAsyncGenerator(c, generator, options) {
3908
4177
  });
3909
4178
  }
3910
4179
 
4180
+ // src/routes/projects.ts
4181
+ init_dist();
4182
+
3911
4183
  // src/lib/response-builder.ts
3912
4184
  class ResponseBuilder {
3913
4185
  static error(code, message, statusCode, details) {
@@ -3927,8 +4199,17 @@ class ResponseBuilder {
3927
4199
  }
3928
4200
  }
3929
4201
 
4202
+ // src/lib/route-helpers.ts
4203
+ function errorResponse(c, code, message, statusCode, details) {
4204
+ const { response, statusCode: status } = ResponseBuilder.error(code, message, statusCode, details);
4205
+ return c.json(response, status);
4206
+ }
4207
+ function successResponse(c, data, statusCode = 200) {
4208
+ const { response, statusCode: status } = ResponseBuilder.success(data, statusCode);
4209
+ return c.json(response, status);
4210
+ }
4211
+
3930
4212
  // src/routes/projects.ts
3931
- init_dist();
3932
4213
  function createProjectRoutes(projectManager, threadManager) {
3933
4214
  const router = new Hono;
3934
4215
  router.post("/", async (c) => {
@@ -3936,14 +4217,12 @@ function createProjectRoutes(projectManager, threadManager) {
3936
4217
  const body = await c.req.json();
3937
4218
  const { gitUrl, commitMethod } = body;
3938
4219
  if (!gitUrl || typeof gitUrl !== "string") {
3939
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "gitUrl is required and must be a string", 400);
3940
- return c.json(response, statusCode);
4220
+ return errorResponse(c, "INVALID_REQUEST", "gitUrl is required and must be a string", 400);
3941
4221
  }
3942
4222
  const method = isValidCommitMethod(commitMethod) ? commitMethod : CommitMethods.PullRequest;
3943
4223
  return streamAsyncGenerator(c, projectManager.createProject(gitUrl, method));
3944
4224
  } catch (error) {
3945
- const { response, statusCode } = ResponseBuilder.error("REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
3946
- return c.json(response, statusCode);
4225
+ return errorResponse(c, "REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
3947
4226
  }
3948
4227
  });
3949
4228
  router.get("/", async (c) => {
@@ -3972,46 +4251,38 @@ function createProjectRoutes(projectManager, threadManager) {
3972
4251
  }));
3973
4252
  return c.json(expandedProjects);
3974
4253
  } catch (error) {
3975
- const { response, statusCode } = ResponseBuilder.error("LIST_PROJECTS_ERROR", "Failed to list projects", 500, error instanceof Error ? error.message : String(error));
3976
- return c.json(response, statusCode);
4254
+ return errorResponse(c, "LIST_PROJECTS_ERROR", "Failed to list projects", 500, error instanceof Error ? error.message : String(error));
3977
4255
  }
3978
4256
  });
3979
4257
  router.get("/:id", async (c) => {
3980
4258
  try {
3981
4259
  const projectId = c.req.param("id");
3982
4260
  if (!projectId) {
3983
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
3984
- return c.json(response, statusCode);
4261
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
3985
4262
  }
3986
4263
  const project = await projectManager.getProject(projectId);
3987
4264
  if (!project) {
3988
- const { response, statusCode } = ResponseBuilder.error("PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
3989
- return c.json(response, statusCode);
4265
+ return errorResponse(c, "PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
3990
4266
  }
3991
4267
  return c.json(project);
3992
4268
  } catch (error) {
3993
- const { response, statusCode } = ResponseBuilder.error("GET_PROJECT_ERROR", "Failed to get project", 500, error instanceof Error ? error.message : String(error));
3994
- return c.json(response, statusCode);
4269
+ return errorResponse(c, "GET_PROJECT_ERROR", "Failed to get project", 500, error instanceof Error ? error.message : String(error));
3995
4270
  }
3996
4271
  });
3997
4272
  router.delete("/:id", async (c) => {
3998
4273
  try {
3999
4274
  const projectId = c.req.param("id");
4000
4275
  if (!projectId) {
4001
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4002
- return c.json(response2, statusCode2);
4276
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4003
4277
  }
4004
4278
  await projectManager.deleteProject(projectId);
4005
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Project deleted successfully" });
4006
- return c.json(response, statusCode);
4279
+ return successResponse(c, { success: true, message: "Project deleted successfully" });
4007
4280
  } catch (error) {
4008
4281
  const errorMessage = error instanceof Error ? error.message : String(error);
4009
4282
  if (errorMessage.includes("not found")) {
4010
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("PROJECT_NOT_FOUND", errorMessage, 404);
4011
- return c.json(response2, statusCode2);
4283
+ return errorResponse(c, "PROJECT_NOT_FOUND", errorMessage, 404);
4012
4284
  }
4013
- const { response, statusCode } = ResponseBuilder.error("DELETE_PROJECT_ERROR", "Failed to delete project", 500, errorMessage);
4014
- return c.json(response, statusCode);
4285
+ return errorResponse(c, "DELETE_PROJECT_ERROR", "Failed to delete project", 500, errorMessage);
4015
4286
  }
4016
4287
  });
4017
4288
  router.post("/:id/open", async (c) => {
@@ -4021,31 +4292,25 @@ function createProjectRoutes(projectManager, threadManager) {
4021
4292
  const { program } = body;
4022
4293
  console.log("[POST /api/projects/:id/open] projectId=%s program=%s", projectId, program);
4023
4294
  if (!projectId) {
4024
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4025
- return c.json(response2, statusCode2);
4295
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4026
4296
  }
4027
4297
  if (!program) {
4028
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Program is required", 400);
4029
- return c.json(response2, statusCode2);
4298
+ return errorResponse(c, "INVALID_REQUEST", "Program is required", 400);
4030
4299
  }
4031
4300
  if (!AVAILABLE_PROGRAMS.includes(program)) {
4032
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS.join(", ")}`, 400);
4033
- return c.json(response2, statusCode2);
4301
+ return errorResponse(c, "INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS.join(", ")}`, 400);
4034
4302
  }
4035
4303
  const project = await projectManager.getProject(projectId);
4036
4304
  if (!project) {
4037
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
4038
- return c.json(response2, statusCode2);
4305
+ return errorResponse(c, "PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
4039
4306
  }
4040
4307
  console.log("[POST /api/projects/:id/open] Calling projectManager.openWith");
4041
4308
  await projectManager.openWith(projectId, program);
4042
4309
  console.log("[POST /api/projects/:id/open] openWith completed successfully");
4043
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: `Project opened in ${program}` });
4044
- return c.json(response, statusCode);
4310
+ return successResponse(c, { success: true, message: `Project opened in ${program}` });
4045
4311
  } catch (error) {
4046
4312
  const errorMessage = error instanceof Error ? error.message : String(error);
4047
- const { response, statusCode } = ResponseBuilder.error("OPEN_PROGRAM_ERROR", "Failed to open project", 500, errorMessage);
4048
- return c.json(response, statusCode);
4313
+ return errorResponse(c, "OPEN_PROGRAM_ERROR", "Failed to open project", 500, errorMessage);
4049
4314
  }
4050
4315
  });
4051
4316
  router.put("/:id", async (c) => {
@@ -4054,22 +4319,18 @@ function createProjectRoutes(projectManager, threadManager) {
4054
4319
  const body = await c.req.json();
4055
4320
  const { program, setupScript, name, runCommand, commitMethod, runNow } = body;
4056
4321
  if (!projectId) {
4057
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4058
- return c.json(response2, statusCode2);
4322
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4059
4323
  }
4060
4324
  const project = await projectManager.getProject(projectId);
4061
4325
  if (!project) {
4062
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
4063
- return c.json(response2, statusCode2);
4326
+ return errorResponse(c, "PROJECT_NOT_FOUND", `Project not found: ${projectId}`, 404);
4064
4327
  }
4065
4328
  if (program !== undefined) {
4066
4329
  if (!program) {
4067
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Program cannot be empty", 400);
4068
- return c.json(response2, statusCode2);
4330
+ return errorResponse(c, "INVALID_REQUEST", "Program cannot be empty", 400);
4069
4331
  }
4070
4332
  if (!AVAILABLE_PROGRAMS.includes(program)) {
4071
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS.join(", ")}`, 400);
4072
- return c.json(response2, statusCode2);
4333
+ return errorResponse(c, "INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS.join(", ")}`, 400);
4073
4334
  }
4074
4335
  await projectManager.updateOpenWith(projectId, program);
4075
4336
  }
@@ -4087,24 +4348,20 @@ function createProjectRoutes(projectManager, threadManager) {
4087
4348
  if (commitMethod !== undefined) {
4088
4349
  if (!isValidCommitMethod(commitMethod)) {
4089
4350
  const validMethods = Object.values(CommitMethods).join('", "');
4090
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", `Commit method must be one of: "${validMethods}"`, 400);
4091
- return c.json(response2, statusCode2);
4351
+ return errorResponse(c, "INVALID_REQUEST", `Commit method must be one of: "${validMethods}"`, 400);
4092
4352
  }
4093
4353
  await projectManager.updateCommitMethod(projectId, commitMethod);
4094
4354
  }
4095
4355
  if (name !== undefined) {
4096
4356
  if (!name || !name.trim()) {
4097
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project name is required", 400);
4098
- return c.json(response2, statusCode2);
4357
+ return errorResponse(c, "INVALID_REQUEST", "Project name is required", 400);
4099
4358
  }
4100
4359
  await projectManager.updateProjectName(projectId, name);
4101
4360
  }
4102
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Project updated" });
4103
- return c.json(response, statusCode);
4361
+ return successResponse(c, { success: true, message: "Project updated" });
4104
4362
  } catch (error) {
4105
4363
  const errorMessage = error instanceof Error ? error.message : String(error);
4106
- const { response, statusCode } = ResponseBuilder.error("UPDATE_PROJECT_ERROR", "Failed to update project", 500, errorMessage);
4107
- return c.json(response, statusCode);
4364
+ return errorResponse(c, "UPDATE_PROJECT_ERROR", "Failed to update project", 500, errorMessage);
4108
4365
  }
4109
4366
  });
4110
4367
  router.post("/:id/commands", async (c) => {
@@ -4112,15 +4369,12 @@ function createProjectRoutes(projectManager, threadManager) {
4112
4369
  const projectId = c.req.param("id");
4113
4370
  const command = await c.req.json();
4114
4371
  if (!projectId) {
4115
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4116
- return c.json(response2, statusCode2);
4372
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4117
4373
  }
4118
4374
  await projectManager.saveCommand(projectId, command);
4119
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Command saved" });
4120
- return c.json(response, statusCode);
4375
+ return successResponse(c, { success: true, message: "Command saved" });
4121
4376
  } catch (error) {
4122
- const { response, statusCode } = ResponseBuilder.error("SAVE_COMMAND_ERROR", "Failed to save command", 500, error instanceof Error ? error.message : String(error));
4123
- return c.json(response, statusCode);
4377
+ return errorResponse(c, "SAVE_COMMAND_ERROR", "Failed to save command", 500, error instanceof Error ? error.message : String(error));
4124
4378
  }
4125
4379
  });
4126
4380
  router.delete("/:id/commands/:commandId", async (c) => {
@@ -4128,15 +4382,12 @@ function createProjectRoutes(projectManager, threadManager) {
4128
4382
  const projectId = c.req.param("id");
4129
4383
  const commandId = c.req.param("commandId");
4130
4384
  if (!projectId || !commandId) {
4131
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID and Command ID are required", 400);
4132
- return c.json(response2, statusCode2);
4385
+ return errorResponse(c, "INVALID_REQUEST", "Project ID and Command ID are required", 400);
4133
4386
  }
4134
4387
  await projectManager.deleteCommand(projectId, commandId);
4135
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Command deleted" });
4136
- return c.json(response, statusCode);
4388
+ return successResponse(c, { success: true, message: "Command deleted" });
4137
4389
  } catch (error) {
4138
- const { response, statusCode } = ResponseBuilder.error("DELETE_COMMAND_ERROR", "Failed to delete command", 500, error instanceof Error ? error.message : String(error));
4139
- return c.json(response, statusCode);
4390
+ return errorResponse(c, "DELETE_COMMAND_ERROR", "Failed to delete command", 500, error instanceof Error ? error.message : String(error));
4140
4391
  }
4141
4392
  });
4142
4393
  router.post("/:projectId/threads/:threadId/commands/run", async (c) => {
@@ -4144,71 +4395,58 @@ function createProjectRoutes(projectManager, threadManager) {
4144
4395
  const threadId = c.req.param("threadId");
4145
4396
  const { commandLine, cwd } = await c.req.json();
4146
4397
  if (!threadId || !commandLine) {
4147
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "Thread ID and commandLine are required", 400);
4148
- return c.json(response, statusCode);
4398
+ return errorResponse(c, "INVALID_REQUEST", "Thread ID and commandLine are required", 400);
4149
4399
  }
4150
4400
  return streamAsyncGenerator(c, projectManager.runCommand(threadId, commandLine, cwd));
4151
4401
  } catch (error) {
4152
- const { response, statusCode } = ResponseBuilder.error("RUN_COMMAND_ERROR", "Failed to run command", 500, error instanceof Error ? error.message : String(error));
4153
- return c.json(response, statusCode);
4402
+ return errorResponse(c, "RUN_COMMAND_ERROR", "Failed to run command", 500, error instanceof Error ? error.message : String(error));
4154
4403
  }
4155
4404
  });
4156
4405
  router.post("/:id/run", async (c) => {
4157
4406
  try {
4158
4407
  const projectId = c.req.param("id");
4159
4408
  if (!projectId) {
4160
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4161
- return c.json(response, statusCode);
4409
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4162
4410
  }
4163
4411
  return streamAsyncGenerator(c, projectManager.startRunningProcess(projectId));
4164
4412
  } catch (error) {
4165
- const { response, statusCode } = ResponseBuilder.error("START_PROCESS_ERROR", "Failed to start dev server", 500, error instanceof Error ? error.message : String(error));
4166
- return c.json(response, statusCode);
4413
+ return errorResponse(c, "START_PROCESS_ERROR", "Failed to start dev server", 500, error instanceof Error ? error.message : String(error));
4167
4414
  }
4168
4415
  });
4169
4416
  router.post("/:id/run-suggest", async (c) => {
4170
4417
  try {
4171
4418
  const projectId = c.req.param("id");
4172
4419
  if (!projectId) {
4173
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4174
- return c.json(response2, statusCode2);
4420
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4175
4421
  }
4176
4422
  const runCommand = await projectManager.suggestRunCommand(projectId);
4177
- const { response, statusCode } = ResponseBuilder.success({ runCommand });
4178
- return c.json(response, statusCode);
4423
+ return successResponse(c, { runCommand });
4179
4424
  } catch (error) {
4180
- const { response, statusCode } = ResponseBuilder.error("SUGGEST_RUN_COMMAND_ERROR", "Failed to suggest run command", 500, error instanceof Error ? error.message : String(error));
4181
- return c.json(response, statusCode);
4425
+ return errorResponse(c, "SUGGEST_RUN_COMMAND_ERROR", "Failed to suggest run command", 500, error instanceof Error ? error.message : String(error));
4182
4426
  }
4183
4427
  });
4184
4428
  router.post("/:id/stop", async (c) => {
4185
4429
  try {
4186
4430
  const projectId = c.req.param("id");
4187
4431
  if (!projectId) {
4188
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4189
- return c.json(response2, statusCode2);
4432
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4190
4433
  }
4191
4434
  await projectManager.stopRunningProcess(projectId);
4192
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Dev server stopped" });
4193
- return c.json(response, statusCode);
4435
+ return successResponse(c, { success: true, message: "Dev server stopped" });
4194
4436
  } catch (error) {
4195
- const { response, statusCode } = ResponseBuilder.error("STOP_PROCESS_ERROR", "Failed to stop dev server", 500, error instanceof Error ? error.message : String(error));
4196
- return c.json(response, statusCode);
4437
+ return errorResponse(c, "STOP_PROCESS_ERROR", "Failed to stop dev server", 500, error instanceof Error ? error.message : String(error));
4197
4438
  }
4198
4439
  });
4199
4440
  router.get("/:id/running", async (c) => {
4200
4441
  try {
4201
4442
  const projectId = c.req.param("id");
4202
4443
  if (!projectId) {
4203
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
4204
- return c.json(response2, statusCode2);
4444
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
4205
4445
  }
4206
4446
  const isRunning = await projectManager.isProcessRunning(projectId);
4207
- const { response, statusCode } = ResponseBuilder.success({ isRunning });
4208
- return c.json(response, statusCode);
4447
+ return successResponse(c, { isRunning });
4209
4448
  } catch (error) {
4210
- const { response, statusCode } = ResponseBuilder.error("CHECK_PROCESS_ERROR", "Failed to check process status", 500, error instanceof Error ? error.message : String(error));
4211
- return c.json(response, statusCode);
4449
+ return errorResponse(c, "CHECK_PROCESS_ERROR", "Failed to check process status", 500, error instanceof Error ? error.message : String(error));
4212
4450
  }
4213
4451
  });
4214
4452
  return router;
@@ -4328,6 +4566,7 @@ function extractAssistantContent(events, fallback) {
4328
4566
  }
4329
4567
 
4330
4568
  // src/routes/threads.ts
4569
+ import open2 from "open";
4331
4570
  var buildStructuredContentFromEvents = (events, fallbackContent) => {
4332
4571
  if (!events || events.length === 0) {
4333
4572
  return fallbackContent;
@@ -4378,98 +4617,81 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
4378
4617
  const body = await c.req.json();
4379
4618
  const { projectId, title } = body;
4380
4619
  if (!projectId || typeof projectId !== "string") {
4381
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "projectId is required and must be a string", 400);
4382
- return c.json(response, statusCode);
4620
+ return errorResponse(c, "INVALID_REQUEST", "projectId is required and must be a string", 400);
4383
4621
  }
4384
4622
  if (title !== undefined && typeof title !== "string") {
4385
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "title must be a string if provided", 400);
4386
- return c.json(response, statusCode);
4623
+ return errorResponse(c, "INVALID_REQUEST", "title must be a string if provided", 400);
4387
4624
  }
4388
4625
  if (title && title.trim() !== "") {
4389
4626
  const nameError = validateThreadName(title);
4390
4627
  if (nameError) {
4391
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", nameError, 400);
4392
- return c.json(response, statusCode);
4628
+ return errorResponse(c, "INVALID_REQUEST", nameError, 400);
4393
4629
  }
4394
4630
  const existingThreads = await threadManager.listThreads(projectId);
4395
4631
  const sanitizedNewName = gitManager.sanitizeBranchName(title);
4396
4632
  const existingSanitized = existingThreads.map((t) => gitManager.sanitizeBranchName(t.title));
4397
4633
  if (existingSanitized.includes(sanitizedNewName)) {
4398
- const { response, statusCode } = ResponseBuilder.error("DUPLICATE_BRANCH", `A thread with branch name "${sanitizedNewName}" already exists`, 409);
4399
- return c.json(response, statusCode);
4634
+ return errorResponse(c, "DUPLICATE_BRANCH", `A thread with branch name "${sanitizedNewName}" already exists`, 409);
4400
4635
  }
4401
4636
  }
4402
4637
  return streamAsyncGenerator(c, threadManager.createThread(projectId, title));
4403
4638
  } catch (error) {
4404
- const { response, statusCode } = ResponseBuilder.error("REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
4405
- return c.json(response, statusCode);
4639
+ return errorResponse(c, "REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
4406
4640
  }
4407
4641
  });
4408
4642
  router.get("/", async (c) => {
4409
4643
  try {
4410
4644
  const projectId = c.req.query("projectId");
4411
4645
  if (!projectId) {
4412
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "projectId query parameter is required", 400);
4413
- return c.json(response, statusCode);
4646
+ return errorResponse(c, "INVALID_REQUEST", "projectId query parameter is required", 400);
4414
4647
  }
4415
4648
  const threads = await threadManager.listThreads(projectId);
4416
4649
  return c.json(threads);
4417
4650
  } catch (error) {
4418
- const { response, statusCode } = ResponseBuilder.error("LIST_THREADS_ERROR", "Failed to list threads", 500, error instanceof Error ? error.message : String(error));
4419
- return c.json(response, statusCode);
4651
+ return errorResponse(c, "LIST_THREADS_ERROR", "Failed to list threads", 500, error instanceof Error ? error.message : String(error));
4420
4652
  }
4421
4653
  });
4422
4654
  router.delete("/:id", async (c) => {
4423
4655
  try {
4424
4656
  const threadId = c.req.param("id");
4425
4657
  if (!threadId) {
4426
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Thread ID is required", 400);
4427
- return c.json(response2, statusCode2);
4658
+ return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
4428
4659
  }
4429
4660
  await threadManager.deleteThread(threadId);
4430
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Thread deleted successfully" });
4431
- return c.json(response, statusCode);
4661
+ return successResponse(c, { success: true, message: "Thread deleted successfully" });
4432
4662
  } catch (error) {
4433
4663
  const errorMessage = error instanceof Error ? error.message : String(error);
4434
4664
  if (errorMessage.includes("not found")) {
4435
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("THREAD_NOT_FOUND", errorMessage, 404);
4436
- return c.json(response2, statusCode2);
4665
+ return errorResponse(c, "THREAD_NOT_FOUND", errorMessage, 404);
4437
4666
  }
4438
- const { response, statusCode } = ResponseBuilder.error("DELETE_THREAD_ERROR", "Failed to delete thread", 500, errorMessage);
4439
- return c.json(response, statusCode);
4667
+ return errorResponse(c, "DELETE_THREAD_ERROR", "Failed to delete thread", 500, errorMessage);
4440
4668
  }
4441
4669
  });
4442
4670
  router.post("/:id/select", async (c) => {
4443
4671
  try {
4444
4672
  const threadId = c.req.param("id");
4445
4673
  if (!threadId) {
4446
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Thread ID is required", 400);
4447
- return c.json(response2, statusCode2);
4674
+ return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
4448
4675
  }
4449
4676
  const thread = await threadManager.getThread(threadId);
4450
4677
  if (!thread) {
4451
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4452
- return c.json(response2, statusCode2);
4678
+ return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4453
4679
  }
4454
4680
  await threadManager.selectThread(threadId);
4455
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Thread selected successfully", threadId });
4456
- return c.json(response, statusCode);
4681
+ return successResponse(c, { success: true, message: "Thread selected successfully", threadId });
4457
4682
  } catch (error) {
4458
- const { response, statusCode } = ResponseBuilder.error("SELECT_THREAD_ERROR", "Failed to select thread", 500, error instanceof Error ? error.message : String(error));
4459
- return c.json(response, statusCode);
4683
+ return errorResponse(c, "SELECT_THREAD_ERROR", "Failed to select thread", 500, error instanceof Error ? error.message : String(error));
4460
4684
  }
4461
4685
  });
4462
4686
  router.get("/:id/messages", async (c) => {
4463
4687
  try {
4464
4688
  const threadId = c.req.param("id");
4465
4689
  if (!threadId) {
4466
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "Thread ID is required", 400);
4467
- return c.json(response, statusCode);
4690
+ return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
4468
4691
  }
4469
4692
  const thread = await threadManager.getThread(threadId);
4470
4693
  if (!thread) {
4471
- const { response, statusCode } = ResponseBuilder.error("THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4472
- return c.json(response, statusCode);
4694
+ return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4473
4695
  }
4474
4696
  const history = await conversationManager.getConversationHistory(thread.path);
4475
4697
  if (!history || history.messages.length === 0) {
@@ -4504,22 +4726,19 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
4504
4726
  });
4505
4727
  return c.json(messages);
4506
4728
  } catch (error) {
4507
- const { response, statusCode } = ResponseBuilder.error("GET_THREAD_MESSAGES_ERROR", "Failed to load thread messages", 500, error instanceof Error ? error.message : String(error));
4508
- return c.json(response, statusCode);
4729
+ return errorResponse(c, "GET_THREAD_MESSAGES_ERROR", "Failed to load thread messages", 500, error instanceof Error ? error.message : String(error));
4509
4730
  }
4510
4731
  });
4511
4732
  router.get("/:id/files", async (c) => {
4512
4733
  try {
4513
4734
  const threadId = c.req.param("id");
4514
4735
  if (!threadId) {
4515
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "Thread ID is required", 400);
4516
- return c.json(response, statusCode);
4736
+ return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
4517
4737
  }
4518
4738
  const files = await threadManager.listFiles(threadId);
4519
4739
  return c.json(files);
4520
4740
  } catch (error) {
4521
- const { response, statusCode } = ResponseBuilder.error("LIST_THREAD_FILES_ERROR", "Failed to list thread files", 500, error instanceof Error ? error.message : String(error));
4522
- return c.json(response, statusCode);
4741
+ return errorResponse(c, "LIST_THREAD_FILES_ERROR", "Failed to list thread files", 500, error instanceof Error ? error.message : String(error));
4523
4742
  }
4524
4743
  });
4525
4744
  router.patch("/:id", async (c) => {
@@ -4527,62 +4746,62 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
4527
4746
  const threadId = c.req.param("id");
4528
4747
  const body = await c.req.json();
4529
4748
  if (!threadId) {
4530
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Thread ID is required", 400);
4531
- return c.json(response2, statusCode2);
4749
+ return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
4532
4750
  }
4533
4751
  await threadManager.updateThread(threadId, body);
4534
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Thread updated successfully" });
4535
- return c.json(response, statusCode);
4752
+ return successResponse(c, { success: true, message: "Thread updated successfully" });
4536
4753
  } catch (error) {
4537
4754
  const errorMessage = error instanceof Error ? error.message : String(error);
4538
- const { response, statusCode } = ResponseBuilder.error("UPDATE_THREAD_ERROR", "Failed to update thread", 500, errorMessage);
4539
- return c.json(response, statusCode);
4755
+ return errorResponse(c, "UPDATE_THREAD_ERROR", "Failed to update thread", 500, errorMessage);
4540
4756
  }
4541
4757
  });
4758
+ const OPEN_APP_NAMES = {
4759
+ "VS Code": ["Visual Studio Code", "code"],
4760
+ Cursor: ["Cursor", "cursor"],
4761
+ Windsurf: ["Windsurf", "windsurf"],
4762
+ Xcode: "Xcode",
4763
+ "Android Studio": ["Android Studio", "studio"],
4764
+ Kiro: ["Kiro", "kiro"]
4765
+ };
4542
4766
  router.post("/:id/open", async (c) => {
4543
4767
  try {
4544
4768
  const threadId = c.req.param("id");
4545
4769
  const body = await c.req.json();
4546
4770
  const { program } = body;
4547
4771
  if (!threadId) {
4548
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Thread ID is required", 400);
4549
- return c.json(response2, statusCode2);
4772
+ return errorResponse(c, "INVALID_REQUEST", "Thread ID is required", 400);
4550
4773
  }
4551
4774
  if (!program) {
4552
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Program is required", 400);
4553
- return c.json(response2, statusCode2);
4775
+ return errorResponse(c, "INVALID_REQUEST", "Program is required", 400);
4554
4776
  }
4555
4777
  const { AVAILABLE_PROGRAMS: AVAILABLE_PROGRAMS2 } = await Promise.resolve().then(() => (init_dist(), exports_dist));
4556
4778
  if (!AVAILABLE_PROGRAMS2.includes(program)) {
4557
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS2.join(", ")}`, 400);
4558
- return c.json(response2, statusCode2);
4779
+ return errorResponse(c, "INVALID_REQUEST", `Invalid program. Must be one of: ${AVAILABLE_PROGRAMS2.join(", ")}`, 400);
4559
4780
  }
4560
4781
  const thread = await threadManager.getThread(threadId);
4561
4782
  if (!thread) {
4562
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4563
- return c.json(response2, statusCode2);
4783
+ return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4564
4784
  }
4565
- const { spawn: spawn7 } = await import("child_process");
4566
4785
  const threadPath = thread.path;
4567
- if (program === "VS Code") {
4568
- spawn7("code", [threadPath], { detached: true });
4569
- } else if (program === "Cursor") {
4570
- spawn7("cursor", [threadPath], { detached: true });
4571
- } else if (program === "Windsurf") {
4572
- spawn7("windsurf", [threadPath], { detached: true });
4573
- } else if (program === "Xcode") {
4574
- spawn7("open", ["-a", "Xcode", threadPath], { detached: true });
4575
- } else if (program === "Android Studio") {
4576
- spawn7("open", ["-a", "Android Studio", threadPath], { detached: true });
4577
- } else if (program === "Kiro") {
4578
- spawn7("kiro", [threadPath], { detached: true });
4579
- }
4580
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: `Thread opened in ${program}` });
4581
- return c.json(response, statusCode);
4786
+ const appNames = OPEN_APP_NAMES[program];
4787
+ if (!appNames) {
4788
+ return errorResponse(c, "OPEN_PROGRAM_ERROR", `Unsupported program: ${program}`, 500);
4789
+ }
4790
+ console.log("[POST /api/threads/:id/open] threadId=%s program=%s threadPath=%s", threadId, program, threadPath);
4791
+ try {
4792
+ await open2(threadPath, {
4793
+ app: { name: appNames }
4794
+ });
4795
+ console.log("[POST /api/threads/:id/open] Successfully opened thread in %s", program);
4796
+ } catch (openError) {
4797
+ const message = openError instanceof Error ? openError.message : String(openError);
4798
+ console.error("[POST /api/threads/:id/open] Failed to open thread:", message);
4799
+ return errorResponse(c, "OPEN_PROGRAM_ERROR", `Failed to open ${program}: ${message}`, 500);
4800
+ }
4801
+ return successResponse(c, { success: true, message: `Thread opened in ${program}` });
4582
4802
  } catch (error) {
4583
4803
  const errorMessage = error instanceof Error ? error.message : String(error);
4584
- const { response, statusCode } = ResponseBuilder.error("OPEN_PROGRAM_ERROR", "Failed to open thread", 500, errorMessage);
4585
- return c.json(response, statusCode);
4804
+ return errorResponse(c, "OPEN_PROGRAM_ERROR", "Failed to open thread", 500, errorMessage);
4586
4805
  }
4587
4806
  });
4588
4807
  return router;
@@ -4591,9 +4810,274 @@ function createThreadRoutes(threadManager, gitManager, conversationManager) {
4591
4810
  // src/routes/chat.ts
4592
4811
  import { Hono as Hono3 } from "hono";
4593
4812
 
4813
+ // src/managers/skill-manager.ts
4814
+ import { readdir as readdir3, readFile as readFile3 } from "fs/promises";
4815
+ import { join as join11 } from "path";
4816
+ import { existsSync as existsSync8 } from "fs";
4817
+ import { homedir as homedir3 } from "os";
4818
+ function parseFrontmatter(markdown) {
4819
+ const lines = markdown.split(`
4820
+ `);
4821
+ if (lines[0]?.trim() !== "---") {
4822
+ return {
4823
+ metadata: {},
4824
+ content: markdown.trim()
4825
+ };
4826
+ }
4827
+ let endIndex = -1;
4828
+ for (let i = 1;i < lines.length; i++) {
4829
+ if (lines[i]?.trim() === "---") {
4830
+ endIndex = i;
4831
+ break;
4832
+ }
4833
+ }
4834
+ if (endIndex === -1) {
4835
+ return {
4836
+ metadata: {},
4837
+ content: markdown.trim()
4838
+ };
4839
+ }
4840
+ const frontmatterLines = lines.slice(1, endIndex);
4841
+ const metadata = {};
4842
+ for (const line of frontmatterLines) {
4843
+ const colonIndex = line.indexOf(":");
4844
+ if (colonIndex === -1)
4845
+ continue;
4846
+ const key = line.slice(0, colonIndex).trim();
4847
+ const value = line.slice(colonIndex + 1).trim();
4848
+ if (key === "name") {
4849
+ metadata.name = value;
4850
+ } else if (key === "description") {
4851
+ metadata.description = value;
4852
+ } else if (key === "license") {
4853
+ metadata.license = value;
4854
+ } else if (key === "compatibility") {
4855
+ metadata.compatibility = value;
4856
+ } else if (key === "allowed-tools") {
4857
+ metadata.allowedTools = value;
4858
+ } else if (key === "metadata") {}
4859
+ }
4860
+ const content = lines.slice(endIndex + 1).join(`
4861
+ `).trim();
4862
+ return { metadata, content };
4863
+ }
4864
+ function getGlobalSkillsDir() {
4865
+ return join11(homedir3(), ".tarsk", "skills");
4866
+ }
4867
+ function getProjectSkillsDir(threadPath) {
4868
+ return join11(threadPath, ".tarsk", "skills");
4869
+ }
4870
+ function validateSkillName(name) {
4871
+ if (!name || name.length === 0 || name.length > 64) {
4872
+ return false;
4873
+ }
4874
+ if (!/^[a-z0-9-]+$/.test(name)) {
4875
+ return false;
4876
+ }
4877
+ if (name.startsWith("-") || name.endsWith("-")) {
4878
+ return false;
4879
+ }
4880
+ if (name.includes("--")) {
4881
+ return false;
4882
+ }
4883
+ return true;
4884
+ }
4885
+ function validateDescription(description) {
4886
+ return description && description.length > 0 && description.length <= 1024;
4887
+ }
4888
+
4889
+ class SkillManager {
4890
+ async loadSkills(threadPath) {
4891
+ const skills = new Map;
4892
+ const globalDir = getGlobalSkillsDir();
4893
+ if (existsSync8(globalDir)) {
4894
+ const globalSkills = await this.loadSkillsFromDir(globalDir, "global");
4895
+ for (const skill of globalSkills) {
4896
+ skills.set(skill.name, skill);
4897
+ }
4898
+ }
4899
+ const projectDir = getProjectSkillsDir(threadPath);
4900
+ if (existsSync8(projectDir)) {
4901
+ const projectSkills = await this.loadSkillsFromDir(projectDir, "project");
4902
+ for (const skill of projectSkills) {
4903
+ skills.set(skill.name, skill);
4904
+ }
4905
+ }
4906
+ return Array.from(skills.values());
4907
+ }
4908
+ async loadSkillsFromDir(dir, scope) {
4909
+ const skills = [];
4910
+ try {
4911
+ const entries = await readdir3(dir, { withFileTypes: true });
4912
+ for (const entry of entries) {
4913
+ if (!entry.isDirectory())
4914
+ continue;
4915
+ const skillDirName = entry.name;
4916
+ const skillPath = join11(dir, skillDirName);
4917
+ const skillFilePath = join11(skillPath, "SKILL.md");
4918
+ if (!existsSync8(skillFilePath)) {
4919
+ console.warn(`Skipping skill directory ${skillDirName}: SKILL.md not found`);
4920
+ continue;
4921
+ }
4922
+ try {
4923
+ const fileContent = await readFile3(skillFilePath, "utf-8");
4924
+ const { metadata, content } = parseFrontmatter(fileContent);
4925
+ if (!metadata.name) {
4926
+ console.warn(`Skipping skill in ${skillDirName}: missing 'name' in frontmatter`);
4927
+ continue;
4928
+ }
4929
+ if (!metadata.description) {
4930
+ console.warn(`Skipping skill in ${skillDirName}: missing 'description' in frontmatter`);
4931
+ continue;
4932
+ }
4933
+ if (!validateSkillName(metadata.name)) {
4934
+ console.warn(`Skipping skill in ${skillDirName}: invalid name format '${metadata.name}'. ` + `Name must be 1-64 chars, lowercase alphanumeric + hyphens, no leading/trailing/consecutive hyphens.`);
4935
+ continue;
4936
+ }
4937
+ if (!validateDescription(metadata.description)) {
4938
+ console.warn(`Skipping skill in ${skillDirName}: description must be 1-1024 characters`);
4939
+ continue;
4940
+ }
4941
+ if (metadata.name !== skillDirName) {
4942
+ console.warn(`Skipping skill in ${skillDirName}: directory name must match frontmatter name '${metadata.name}'`);
4943
+ continue;
4944
+ }
4945
+ const skill = {
4946
+ name: metadata.name,
4947
+ description: metadata.description,
4948
+ scope,
4949
+ skillPath,
4950
+ instructions: content
4951
+ };
4952
+ if (metadata.license) {
4953
+ skill.license = metadata.license;
4954
+ }
4955
+ if (metadata.compatibility) {
4956
+ skill.compatibility = metadata.compatibility;
4957
+ }
4958
+ if (metadata.metadata) {
4959
+ skill.metadata = metadata.metadata;
4960
+ }
4961
+ if (metadata.allowedTools) {
4962
+ skill.allowedTools = metadata.allowedTools;
4963
+ }
4964
+ skills.push(skill);
4965
+ } catch (error) {
4966
+ console.error(`Failed to load skill from ${skillDirName}:`, error);
4967
+ }
4968
+ }
4969
+ } catch (error) {
4970
+ console.error(`Failed to load skills from ${dir}:`, error);
4971
+ }
4972
+ return skills;
4973
+ }
4974
+ async getSkill(name, threadPath) {
4975
+ const skills = await this.loadSkills(threadPath);
4976
+ return skills.find((skill) => skill.name === name) || null;
4977
+ }
4978
+ validateSkillName(name) {
4979
+ return validateSkillName(name);
4980
+ }
4981
+ }
4982
+
4983
+ // src/managers/skill-activation.ts
4984
+ var STOP_WORDS = new Set([
4985
+ "a",
4986
+ "an",
4987
+ "and",
4988
+ "are",
4989
+ "as",
4990
+ "at",
4991
+ "be",
4992
+ "by",
4993
+ "for",
4994
+ "from",
4995
+ "has",
4996
+ "he",
4997
+ "in",
4998
+ "is",
4999
+ "it",
5000
+ "its",
5001
+ "of",
5002
+ "on",
5003
+ "that",
5004
+ "the",
5005
+ "to",
5006
+ "was",
5007
+ "will",
5008
+ "with",
5009
+ "when",
5010
+ "where",
5011
+ "which",
5012
+ "who",
5013
+ "use",
5014
+ "used",
5015
+ "using",
5016
+ "this",
5017
+ "can",
5018
+ "do",
5019
+ "does",
5020
+ "how",
5021
+ "what",
5022
+ "i",
5023
+ "you",
5024
+ "we",
5025
+ "they",
5026
+ "them",
5027
+ "their",
5028
+ "my",
5029
+ "your",
5030
+ "our"
5031
+ ]);
5032
+ function extractKeywords(text) {
5033
+ const keywords = new Set;
5034
+ const words = text.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word.length >= 3);
5035
+ for (const word of words) {
5036
+ if (!STOP_WORDS.has(word)) {
5037
+ keywords.add(word);
5038
+ }
5039
+ }
5040
+ return keywords;
5041
+ }
5042
+ function calculateRelevanceScore(taskKeywords, skillDescription) {
5043
+ const skillKeywords = extractKeywords(skillDescription);
5044
+ if (skillKeywords.size === 0 || taskKeywords.size === 0) {
5045
+ return 0;
5046
+ }
5047
+ let matches = 0;
5048
+ for (const keyword of taskKeywords) {
5049
+ if (skillKeywords.has(keyword)) {
5050
+ matches++;
5051
+ }
5052
+ }
5053
+ return matches / taskKeywords.size;
5054
+ }
5055
+ function autoDiscoverSkills(allSkills, taskDescription, relevanceThreshold = 0.1, maxSkills = 5) {
5056
+ const taskKeywords = extractKeywords(taskDescription);
5057
+ const scoredSkills = allSkills.map((skill) => ({
5058
+ skill,
5059
+ score: calculateRelevanceScore(taskKeywords, skill.description)
5060
+ }));
5061
+ const relevantSkills = scoredSkills.filter(({ score }) => score >= relevanceThreshold).sort((a, b) => b.score - a.score).slice(0, maxSkills).map(({ skill }) => skill);
5062
+ return relevantSkills;
5063
+ }
5064
+ async function activateSkills(allSkills, taskDescription, thread, options) {
5065
+ const { relevanceThreshold = 0.1, maxSkills = 5 } = options || {};
5066
+ if (thread?.enabledSkills && thread.enabledSkills.length > 0) {
5067
+ const enabledSet = new Set(thread.enabledSkills);
5068
+ return allSkills.filter((skill) => enabledSet.has(skill.name));
5069
+ }
5070
+ const autoDiscovered = autoDiscoverSkills(allSkills, taskDescription, relevanceThreshold, maxSkills);
5071
+ if (thread?.disabledSkills && thread.disabledSkills.length > 0) {
5072
+ const disabledSet = new Set(thread.disabledSkills);
5073
+ return autoDiscovered.filter((skill) => !disabledSet.has(skill.name));
5074
+ }
5075
+ return autoDiscovered;
5076
+ }
5077
+
4594
5078
  // src/utils.ts
4595
5079
  function delay(ms) {
4596
- return new Promise((resolve2) => setTimeout(resolve2, ms));
5080
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
4597
5081
  }
4598
5082
 
4599
5083
  // src/routes/chat.ts
@@ -4611,34 +5095,29 @@ function createChatRoutes(threadManager, agentExecutor, conversationManager, pro
4611
5095
  }
4612
5096
  }
4613
5097
  if (!threadId || typeof threadId !== "string") {
4614
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "threadId is required and must be a string", 400);
4615
- return c.json(response, statusCode);
5098
+ return errorResponse(c, "INVALID_REQUEST", "threadId is required and must be a string", 400);
4616
5099
  }
4617
5100
  if (!content || typeof content !== "string") {
4618
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "content is required and must be a string", 400);
4619
- return c.json(response, statusCode);
5101
+ return errorResponse(c, "INVALID_REQUEST", "content is required and must be a string", 400);
4620
5102
  }
4621
5103
  if (!model || typeof model !== "string") {
4622
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "model is required and must be a string", 400);
4623
- return c.json(response, statusCode);
5104
+ return errorResponse(c, "INVALID_REQUEST", "model is required and must be a string", 400);
4624
5105
  }
5106
+ console.log(`[chat] User message (thread: ${threadId}): ${content}`);
4625
5107
  const thread = await threadManager.getThread(threadId);
4626
5108
  if (!thread) {
4627
- const { response, statusCode } = ResponseBuilder.error("THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4628
- return c.json(response, statusCode);
5109
+ return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4629
5110
  }
4630
5111
  const threadPath = thread.path;
4631
5112
  if (content.trim().toLowerCase() === "replay") {
4632
5113
  console.log("[ChatRoute] Replay request detected");
4633
5114
  const lastMessages = await conversationManager.getLastMessages(threadPath, 1);
4634
5115
  if (lastMessages.length === 0) {
4635
- const { response: response2, statusCode } = ResponseBuilder.error("NO_HISTORY", "No previous conversation found to replay", 404);
4636
- return c.json(response2, statusCode);
5116
+ return errorResponse(c, "NO_HISTORY", "No previous conversation found to replay", 404);
4637
5117
  }
4638
5118
  const lastMessage = lastMessages[0];
4639
5119
  if (!lastMessage.response) {
4640
- const { response: response2, statusCode } = ResponseBuilder.error("NO_RESPONSE", "Previous message has no completed response to replay", 404);
4641
- return c.json(response2, statusCode);
5120
+ return errorResponse(c, "NO_RESPONSE", "Previous message has no completed response to replay", 404);
4642
5121
  }
4643
5122
  console.log("[ChatRoute] Replaying message:", {
4644
5123
  messageId: lastMessage.id,
@@ -4669,13 +5148,20 @@ function createChatRoutes(threadManager, agentExecutor, conversationManager, pro
4669
5148
  }
4670
5149
  return streamAsyncGenerator(c, replayGenerator());
4671
5150
  }
5151
+ const skillManager = new SkillManager;
5152
+ const allSkills = await skillManager.loadSkills(threadPath);
5153
+ const activatedSkills = await activateSkills(allSkills, content, thread);
5154
+ if (activatedSkills.length > 0) {
5155
+ console.log("[ChatRoute] Activated skills:", activatedSkills.map((s) => s.name).join(", "));
5156
+ }
4672
5157
  const context = {
4673
5158
  threadId,
4674
5159
  threadPath,
4675
5160
  model,
4676
5161
  provider,
4677
5162
  attachments,
4678
- planMode
5163
+ planMode,
5164
+ skills: activatedSkills
4679
5165
  };
4680
5166
  console.log("[ChatRoute] Execution context:", {
4681
5167
  threadId,
@@ -4701,6 +5187,7 @@ User: ${content}` : content;
4701
5187
  capturedEvents.push(event);
4702
5188
  if (event.type === "message" && event.content) {
4703
5189
  fullContent += event.content;
5190
+ console.log(`[chat] Assistant (role: ${event.role ?? "assistant"}): ${event.content}`);
4704
5191
  }
4705
5192
  if (event.type === "message" && typeof event.content === "string" && (event.role === "tool" || isToolLikeContent(event.content))) {
4706
5193
  yield { type: "thinking", content: event.content };
@@ -4738,30 +5225,29 @@ User: ${content}` : content;
4738
5225
  processingStateManager.clearProcessing(threadId);
4739
5226
  }
4740
5227
  }
4741
- return streamAsyncGenerator(c, chatExecutionGenerator());
5228
+ async function* withCompleteSignal() {
5229
+ yield* chatExecutionGenerator();
5230
+ yield { type: "complete" };
5231
+ }
5232
+ return streamAsyncGenerator(c, withCompleteSignal());
4742
5233
  } catch (error) {
4743
- const { response, statusCode } = ResponseBuilder.error("REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
4744
- return c.json(response, statusCode);
5234
+ return errorResponse(c, "REQUEST_PARSE_ERROR", "Failed to parse request body", 400, error instanceof Error ? error.message : String(error));
4745
5235
  }
4746
5236
  });
4747
5237
  router.delete("/:threadId", async (c) => {
4748
5238
  try {
4749
5239
  const threadId = c.req.param("threadId");
4750
5240
  if (!threadId || typeof threadId !== "string") {
4751
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "threadId is required and must be a string", 400);
4752
- return c.json(response2, statusCode2);
5241
+ return errorResponse(c, "INVALID_REQUEST", "threadId is required and must be a string", 400);
4753
5242
  }
4754
5243
  const thread = await threadManager.getThread(threadId);
4755
5244
  if (!thread) {
4756
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4757
- return c.json(response2, statusCode2);
5245
+ return errorResponse(c, "THREAD_NOT_FOUND", `Thread not found: ${threadId}`, 404);
4758
5246
  }
4759
5247
  await conversationManager.clearChatHistory(thread.path);
4760
- const { response, statusCode } = ResponseBuilder.success({ message: "Chat history cleared" }, 200);
4761
- return c.json(response, statusCode);
5248
+ return successResponse(c, { message: "Chat history cleared" }, 200);
4762
5249
  } catch (error) {
4763
- const { response, statusCode } = ResponseBuilder.error("CLEAR_CHAT_ERROR", "Failed to clear chat history", 500, error instanceof Error ? error.message : String(error));
4764
- return c.json(response, statusCode);
5250
+ return errorResponse(c, "CLEAR_CHAT_ERROR", "Failed to clear chat history", 500, error instanceof Error ? error.message : String(error));
4765
5251
  }
4766
5252
  });
4767
5253
  return router;
@@ -4934,9 +5420,9 @@ async function getAIHubMixCredits(apiKey) {
4934
5420
 
4935
5421
  // src/utils/env-manager.ts
4936
5422
  import { promises as fs3 } from "fs";
4937
- import { join as join9 } from "path";
5423
+ import { join as join12 } from "path";
4938
5424
  async function updateEnvFile(keyNames) {
4939
- const envPath = join9(process.cwd(), ".env");
5425
+ const envPath = join12(process.cwd(), ".env");
4940
5426
  let content = "";
4941
5427
  try {
4942
5428
  content = await fs3.readFile(envPath, "utf-8");
@@ -4967,7 +5453,7 @@ async function updateEnvFile(keyNames) {
4967
5453
  `, "utf-8");
4968
5454
  }
4969
5455
  async function readEnvFile() {
4970
- const envPath = join9(process.cwd(), ".env");
5456
+ const envPath = join12(process.cwd(), ".env");
4971
5457
  const envMap = {};
4972
5458
  try {
4973
5459
  const content = await fs3.readFile(envPath, "utf-8");
@@ -5083,32 +5569,6 @@ import { Hono as Hono5 } from "hono";
5083
5569
 
5084
5570
  // src/provider-data.ts
5085
5571
  var PROVIDER_DATA = [
5086
- {
5087
- id: "aihubmix",
5088
- models: [
5089
- "DeepSeek-R1",
5090
- "DeepSeek-V3",
5091
- "claude-3-5-sonnet-20241022",
5092
- "claude-3-7-sonnet-20250219",
5093
- "claude-opus-4-1",
5094
- "claude-opus-4-20250514",
5095
- "claude-sonnet-4-20250514",
5096
- "claude-sonnet-4-5",
5097
- "gemini-2.5-flash",
5098
- "gemini-2.5-flash-lite",
5099
- "gemini-2.5-pro",
5100
- "glm-4.6",
5101
- "gpt-4",
5102
- "gpt-4.1",
5103
- "gpt-4o",
5104
- "gpt-5",
5105
- "gpt-5-mini",
5106
- "kimi-k2-thinking",
5107
- "kimi-k2-turbo-preview",
5108
- "o3-mini",
5109
- "o4-mini"
5110
- ]
5111
- },
5112
5572
  {
5113
5573
  id: "anthropic",
5114
5574
  models: [
@@ -5585,6 +6045,163 @@ async function getModelInfoForProvider(provider, modelIds, apiKey) {
5585
6045
  return result;
5586
6046
  }
5587
6047
 
6048
+ // src/generated-data.ts
6049
+ var GENERATED_DATA = [
6050
+ {
6051
+ id: "aihubmix",
6052
+ models: [
6053
+ "BAAI/bge-large-en-v1.5",
6054
+ "BAAI/bge-large-zh-v1.5",
6055
+ "DeepSeek-R1",
6056
+ "DeepSeek-V3",
6057
+ "DeepSeek-V3-Fast",
6058
+ "DeepSeek-V3.1-Fast",
6059
+ "DeepSeek-V3.1-Terminus",
6060
+ "DeepSeek-V3.1-Think",
6061
+ "DeepSeek-V3.2-Exp",
6062
+ "DeepSeek-V3.2-Exp-Think",
6063
+ "ERNIE-X1.1-Preview",
6064
+ "Kimi-K2-0905",
6065
+ "Qwen/QwQ-32B",
6066
+ "Qwen/Qwen2.5-VL-32B-Instruct",
6067
+ "baidu/ERNIE-4.5-300B-A47B",
6068
+ "bge-large-en",
6069
+ "bge-large-zh",
6070
+ "cc-MiniMax-M2",
6071
+ "cc-deepseek-v3.1",
6072
+ "cc-ernie-4.5-300b-a47b",
6073
+ "cc-kimi-k2-instruct",
6074
+ "cc-kimi-k2-instruct-0905",
6075
+ "cc-minimax-m2",
6076
+ "cc-minimax-m2.1",
6077
+ "cc-minimax-m2.5",
6078
+ "claude-3-5-haiku",
6079
+ "claude-3-5-sonnet",
6080
+ "claude-3-7-sonnet",
6081
+ "claude-haiku-4-5",
6082
+ "claude-opus-4-0",
6083
+ "claude-opus-4-1",
6084
+ "claude-opus-4-5",
6085
+ "claude-opus-4-5-think",
6086
+ "claude-opus-4-6",
6087
+ "claude-opus-4-6-think",
6088
+ "claude-sonnet-4-0",
6089
+ "claude-sonnet-4-5",
6090
+ "claude-sonnet-4-5-think",
6091
+ "claude-sonnet-4-6",
6092
+ "claude-sonnet-4-6-think",
6093
+ "coding-glm-4.6",
6094
+ "coding-glm-4.6-free",
6095
+ "coding-glm-4.7",
6096
+ "coding-glm-4.7-free",
6097
+ "coding-glm-5",
6098
+ "coding-glm-5-free",
6099
+ "coding-minimax-m2",
6100
+ "coding-minimax-m2-free",
6101
+ "coding-minimax-m2.1",
6102
+ "coding-minimax-m2.5",
6103
+ "deepseek-v3.2",
6104
+ "deepseek-v3.2-think",
6105
+ "doubao-seed-1-8",
6106
+ "doubao-seed-2-0-code-preview",
6107
+ "doubao-seed-2-0-lite",
6108
+ "doubao-seed-2-0-mini",
6109
+ "doubao-seed-2-0-pro",
6110
+ "ernie-4.5",
6111
+ "ernie-4.5-0.3b",
6112
+ "ernie-4.5-turbo-128k-preview",
6113
+ "ernie-4.5-turbo-latest",
6114
+ "ernie-4.5-turbo-vl",
6115
+ "ernie-irag-edit",
6116
+ "ernie-x1-turbo",
6117
+ "gemini-2.0-flash",
6118
+ "gemini-2.0-flash-exp-search",
6119
+ "gemini-2.0-flash-free",
6120
+ "gemini-2.5-flash",
6121
+ "gemini-2.5-flash-lite",
6122
+ "gemini-2.5-flash-lite-preview-09-2025",
6123
+ "gemini-2.5-flash-nothink",
6124
+ "gemini-2.5-flash-preview-05-20-nothink",
6125
+ "gemini-2.5-flash-preview-05-20-search",
6126
+ "gemini-2.5-flash-preview-09-2025",
6127
+ "gemini-2.5-flash-search",
6128
+ "gemini-2.5-pro",
6129
+ "gemini-2.5-pro-preview-03-25",
6130
+ "gemini-2.5-pro-preview-03-25-search",
6131
+ "gemini-2.5-pro-preview-06-05",
6132
+ "gemini-2.5-pro-preview-06-05-search",
6133
+ "gemini-2.5-pro-search",
6134
+ "gemini-3-flash-preview",
6135
+ "gemini-3-flash-preview-free",
6136
+ "gemini-3-flash-preview-search",
6137
+ "gemini-3-pro-preview",
6138
+ "gemini-3-pro-preview-search",
6139
+ "gemini-3.1-pro-preview",
6140
+ "gemini-3.1-pro-preview-customtools",
6141
+ "gemini-3.1-pro-preview-search",
6142
+ "glm-4.6",
6143
+ "glm-4.7",
6144
+ "glm-4.7-flash-free",
6145
+ "gpt-4.1",
6146
+ "gpt-4.1-free",
6147
+ "gpt-4.1-mini",
6148
+ "gpt-4.1-mini-free",
6149
+ "gpt-4.1-nano",
6150
+ "gpt-4.1-nano-free",
6151
+ "gpt-4o",
6152
+ "gpt-4o-free",
6153
+ "gpt-5",
6154
+ "gpt-5-codex",
6155
+ "gpt-5-mini",
6156
+ "gpt-5-nano",
6157
+ "gpt-5-pro",
6158
+ "gpt-5.1",
6159
+ "gpt-5.2",
6160
+ "gpt-5.2-high",
6161
+ "gpt-5.2-low",
6162
+ "gpt-5.2-pro",
6163
+ "grok-4-1-fast-non-reasoning",
6164
+ "grok-4-1-fast-reasoning",
6165
+ "grok-code-fast-1",
6166
+ "inclusionAI/Ling-1T",
6167
+ "inclusionAI/Ling-flash-2.0",
6168
+ "inclusionAI/Ling-mini-2.0",
6169
+ "inclusionAI/Ring-1T",
6170
+ "inclusionAI/Ring-flash-2.0",
6171
+ "kimi-for-coding-free",
6172
+ "kimi-k2-0711",
6173
+ "kimi-k2-thinking",
6174
+ "kimi-k2-turbo-preview",
6175
+ "llama-4-maverick",
6176
+ "llama-4-scout",
6177
+ "o3",
6178
+ "o3-pro",
6179
+ "qwen3-235b-a22b",
6180
+ "qwen3-235b-a22b-instruct-2507",
6181
+ "qwen3-235b-a22b-thinking-2507",
6182
+ "qwen3-coder-30b-a3b-instruct",
6183
+ "qwen3-coder-480b-a35b-instruct",
6184
+ "qwen3-coder-flash",
6185
+ "qwen3-coder-next",
6186
+ "qwen3-coder-plus",
6187
+ "qwen3-coder-plus-2025-07-22",
6188
+ "qwen3-max",
6189
+ "qwen3-max-preview",
6190
+ "qwen3-next-80b-a3b-instruct",
6191
+ "qwen3-next-80b-a3b-thinking",
6192
+ "qwen3-vl-235b-a22b-instruct",
6193
+ "qwen3-vl-235b-a22b-thinking",
6194
+ "qwen3-vl-30b-a3b-instruct",
6195
+ "qwen3-vl-30b-a3b-thinking",
6196
+ "qwen3-vl-flash",
6197
+ "qwen3-vl-flash-2026-01-22",
6198
+ "qwen3-vl-plus",
6199
+ "qwen3.5-397b-a17b",
6200
+ "qwen3.5-plus"
6201
+ ]
6202
+ }
6203
+ ];
6204
+
5588
6205
  // src/managers/model-manager.ts
5589
6206
  class ModelManager {
5590
6207
  metadataManager;
@@ -5593,7 +6210,10 @@ class ModelManager {
5593
6210
  }
5594
6211
  async getAvailableModels(provider) {
5595
6212
  const providerId = provider.toLowerCase();
5596
- const providerData = PROVIDER_DATA.find((p) => p.id === providerId);
6213
+ let providerData = PROVIDER_DATA.find((p) => p.id === providerId);
6214
+ if (!providerData) {
6215
+ providerData = GENERATED_DATA.find((p) => p.id === providerId);
6216
+ }
5597
6217
  if (!providerData) {
5598
6218
  throw new Error(`Provider "${provider}" is not supported`);
5599
6219
  }
@@ -5753,10 +6373,10 @@ function createModelRoutes(metadataManager) {
5753
6373
 
5754
6374
  // src/routes/git.ts
5755
6375
  import { Hono as Hono6 } from "hono";
5756
- import { spawn as spawn7 } from "child_process";
5757
- import { existsSync as existsSync6, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
5758
- import { join as join10 } from "path";
5759
- import { isAbsolute as isAbsolute3, normalize, resolve as resolve2 } from "path";
6376
+ import { spawn as spawn8 } from "child_process";
6377
+ import { existsSync as existsSync9, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
6378
+ import { join as join13 } from "path";
6379
+ import { isAbsolute as isAbsolute3, normalize as normalize2, resolve as resolve3 } from "path";
5760
6380
  import {
5761
6381
  completeSimple,
5762
6382
  getModel as getModel2
@@ -5881,12 +6501,12 @@ Generate only the commit message, nothing else:`;
5881
6501
  }
5882
6502
  }
5883
6503
  function resolveThreadPath(repoPath) {
5884
- const base = isAbsolute3(repoPath) ? repoPath : resolve2(getDataDir(), repoPath);
5885
- return normalize(base);
6504
+ const base = isAbsolute3(repoPath) ? repoPath : resolve3(getDataDir(), repoPath);
6505
+ return normalize2(base);
5886
6506
  }
5887
6507
  function getGitRoot(cwd) {
5888
6508
  return new Promise((resolveRoot, reject) => {
5889
- const proc = spawn7("git", ["rev-parse", "--show-toplevel"], { cwd });
6509
+ const proc = spawn8("git", ["rev-parse", "--show-toplevel"], { cwd });
5890
6510
  let out = "";
5891
6511
  let err = "";
5892
6512
  proc.stdout.on("data", (d) => {
@@ -5909,8 +6529,8 @@ function createGitRoutes(metadataManager) {
5909
6529
  const router = new Hono6;
5910
6530
  router.get("/username", async (c) => {
5911
6531
  try {
5912
- const name = await new Promise((resolve3, reject) => {
5913
- const proc = spawn7("git", ["config", "user.name"]);
6532
+ const name = await new Promise((resolve4, reject) => {
6533
+ const proc = spawn8("git", ["config", "user.name"]);
5914
6534
  let out = "";
5915
6535
  let err = "";
5916
6536
  proc.stdout.on("data", (d) => {
@@ -5921,7 +6541,7 @@ function createGitRoutes(metadataManager) {
5921
6541
  });
5922
6542
  proc.on("close", (code) => {
5923
6543
  if (code === 0) {
5924
- resolve3(out.trim());
6544
+ resolve4(out.trim());
5925
6545
  } else {
5926
6546
  reject(new Error(err.trim() || `git exited ${code}`));
5927
6547
  }
@@ -5945,7 +6565,7 @@ function createGitRoutes(metadataManager) {
5945
6565
  return c.json({ error: "Thread path not found" }, 404);
5946
6566
  }
5947
6567
  const absolutePath = resolveThreadPath(repoPath);
5948
- if (!existsSync6(absolutePath)) {
6568
+ if (!existsSync9(absolutePath)) {
5949
6569
  return c.json({
5950
6570
  error: `Thread repo path does not exist: ${absolutePath}. Check that the project folder is present.`
5951
6571
  }, 400);
@@ -5959,8 +6579,8 @@ function createGitRoutes(metadataManager) {
5959
6579
  error: `Path is not a git repository: ${absolutePath}. ${msg}`
5960
6580
  }, 400);
5961
6581
  }
5962
- const { hasChanges, changedFilesCount } = await new Promise((resolve3) => {
5963
- const proc = spawn7("git", ["status", "--porcelain"], { cwd: gitRoot });
6582
+ const { hasChanges, changedFilesCount } = await new Promise((resolve4) => {
6583
+ const proc = spawn8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: gitRoot });
5964
6584
  let out = "";
5965
6585
  proc.stdout.on("data", (d) => {
5966
6586
  out += d.toString();
@@ -5968,34 +6588,34 @@ function createGitRoutes(metadataManager) {
5968
6588
  proc.on("close", () => {
5969
6589
  const lines = out.trim().split(`
5970
6590
  `).filter((line) => line.length > 0);
5971
- resolve3({
6591
+ resolve4({
5972
6592
  hasChanges: lines.length > 0,
5973
6593
  changedFilesCount: lines.length
5974
6594
  });
5975
6595
  });
5976
- proc.on("error", () => resolve3({ hasChanges: false, changedFilesCount: 0 }));
6596
+ proc.on("error", () => resolve4({ hasChanges: false, changedFilesCount: 0 }));
5977
6597
  });
5978
- const currentBranch = await new Promise((resolve3) => {
5979
- const proc = spawn7("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
6598
+ const currentBranch = await new Promise((resolve4) => {
6599
+ const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
5980
6600
  let out = "";
5981
6601
  proc.stdout.on("data", (d) => {
5982
6602
  out += d.toString();
5983
6603
  });
5984
6604
  proc.on("close", () => {
5985
- resolve3(out.trim());
6605
+ resolve4(out.trim());
5986
6606
  });
5987
- proc.on("error", () => resolve3(""));
6607
+ proc.on("error", () => resolve4(""));
5988
6608
  });
5989
- const hasUnpushedCommits = await new Promise((resolve3) => {
5990
- const proc = spawn7("git", ["log", `origin/${currentBranch}..HEAD`, "--oneline"], { cwd: gitRoot });
6609
+ const hasUnpushedCommits = await new Promise((resolve4) => {
6610
+ const proc = spawn8("git", ["log", `origin/${currentBranch}..HEAD`, "--oneline"], { cwd: gitRoot });
5991
6611
  let out = "";
5992
6612
  proc.stdout.on("data", (d) => {
5993
6613
  out += d.toString();
5994
6614
  });
5995
6615
  proc.on("close", () => {
5996
- resolve3(out.trim().length > 0);
6616
+ resolve4(out.trim().length > 0);
5997
6617
  });
5998
- proc.on("error", () => resolve3(false));
6618
+ proc.on("error", () => resolve4(false));
5999
6619
  });
6000
6620
  return c.json({
6001
6621
  hasChanges,
@@ -6025,14 +6645,14 @@ function createGitRoutes(metadataManager) {
6025
6645
  } catch {
6026
6646
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
6027
6647
  }
6028
- const statusOutput = await new Promise((resolve3) => {
6029
- const proc = spawn7("git", ["status", "--porcelain"], { cwd: gitRoot });
6648
+ const statusOutput = await new Promise((resolve4) => {
6649
+ const proc = spawn8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: gitRoot });
6030
6650
  let out = "";
6031
6651
  proc.stdout.on("data", (d) => {
6032
6652
  out += d.toString();
6033
6653
  });
6034
- proc.on("close", () => resolve3(out));
6035
- proc.on("error", () => resolve3(""));
6654
+ proc.on("close", () => resolve4(out));
6655
+ proc.on("error", () => resolve4(""));
6036
6656
  });
6037
6657
  const changedFiles = [];
6038
6658
  const statusLines = statusOutput.trim().split(`
@@ -6066,51 +6686,51 @@ function createGitRoutes(metadataManager) {
6066
6686
  let newContent = "";
6067
6687
  const hunks = [];
6068
6688
  if (file.status === "deleted") {
6069
- const content = await new Promise((resolve3) => {
6070
- const proc = spawn7("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
6689
+ const content = await new Promise((resolve4) => {
6690
+ const proc = spawn8("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
6071
6691
  let out = "";
6072
6692
  proc.stdout.on("data", (d) => {
6073
6693
  out += d.toString();
6074
6694
  });
6075
- proc.on("close", () => resolve3(out));
6076
- proc.on("error", () => resolve3(""));
6695
+ proc.on("close", () => resolve4(out));
6696
+ proc.on("error", () => resolve4(""));
6077
6697
  });
6078
6698
  oldContent = content;
6079
6699
  } else if (file.status === "added" && file.path.startsWith("(new)")) {
6080
6700
  const fs4 = await import("fs");
6081
6701
  try {
6082
- newContent = fs4.readFileSync(resolve2(gitRoot, file.path.replace("(new) ", "")), "utf-8");
6702
+ newContent = fs4.readFileSync(resolve3(gitRoot, file.path.replace("(new) ", "")), "utf-8");
6083
6703
  } catch {
6084
6704
  newContent = "";
6085
6705
  }
6086
6706
  } else {
6087
- const diffOutput = await new Promise((resolve3) => {
6088
- const proc = spawn7("git", file.status === "added" ? ["diff", "--cached", "--", file.path] : ["diff", "HEAD", "--", file.path], { cwd: gitRoot });
6707
+ const diffOutput = await new Promise((resolve4) => {
6708
+ const proc = spawn8("git", file.status === "added" ? ["diff", "--cached", "--", file.path] : ["diff", "HEAD", "--", file.path], { cwd: gitRoot });
6089
6709
  let out = "";
6090
6710
  proc.stdout.on("data", (d) => {
6091
6711
  out += d.toString();
6092
6712
  });
6093
- proc.on("close", () => resolve3(out));
6094
- proc.on("error", () => resolve3(""));
6713
+ proc.on("close", () => resolve4(out));
6714
+ proc.on("error", () => resolve4(""));
6095
6715
  });
6096
6716
  const lines = diffOutput.split(`
6097
6717
  `);
6098
6718
  let currentHunk = null;
6099
6719
  let oldLineNum = 0;
6100
6720
  let newLineNum = 0;
6101
- const oldContentOutput = await new Promise((resolve3) => {
6102
- const proc = spawn7("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
6721
+ const oldContentOutput = await new Promise((resolve4) => {
6722
+ const proc = spawn8("git", ["show", `HEAD:${file.path}`], { cwd: gitRoot });
6103
6723
  let out = "";
6104
6724
  proc.stdout.on("data", (d) => {
6105
6725
  out += d.toString();
6106
6726
  });
6107
- proc.on("close", () => resolve3(out));
6108
- proc.on("error", () => resolve3(""));
6727
+ proc.on("close", () => resolve4(out));
6728
+ proc.on("error", () => resolve4(""));
6109
6729
  });
6110
6730
  oldContent = oldContentOutput;
6111
6731
  const fs4 = await import("fs");
6112
6732
  try {
6113
- newContent = fs4.readFileSync(resolve2(gitRoot, file.path), "utf-8");
6733
+ newContent = fs4.readFileSync(resolve3(gitRoot, file.path), "utf-8");
6114
6734
  } catch {
6115
6735
  newContent = "";
6116
6736
  }
@@ -6193,7 +6813,7 @@ function createGitRoutes(metadataManager) {
6193
6813
  const absolutePath = resolveThreadPath(repoPath);
6194
6814
  process.stdout.write(`[generate-commit-message] resolved path: ${absolutePath}
6195
6815
  `);
6196
- if (!existsSync6(absolutePath)) {
6816
+ if (!existsSync9(absolutePath)) {
6197
6817
  process.stdout.write(`[generate-commit-message] path does not exist: ${absolutePath}
6198
6818
  `);
6199
6819
  return c.json({
@@ -6217,7 +6837,7 @@ function createGitRoutes(metadataManager) {
6217
6837
  const runUnstagedDiff = () => {
6218
6838
  process.stdout.write(`[generate-commit-message] using unstaged diff (git diff)
6219
6839
  `);
6220
- const proc2 = spawn7("git", ["diff"], { cwd: gitRoot });
6840
+ const proc2 = spawn8("git", ["diff"], { cwd: gitRoot });
6221
6841
  let out2 = "";
6222
6842
  proc2.stdout.on("data", (d) => {
6223
6843
  out2 += d.toString();
@@ -6227,7 +6847,7 @@ function createGitRoutes(metadataManager) {
6227
6847
  });
6228
6848
  proc2.on("error", reject);
6229
6849
  };
6230
- const proc = spawn7("git", ["diff", "--cached"], { cwd: gitRoot });
6850
+ const proc = spawn8("git", ["diff", "--cached"], { cwd: gitRoot });
6231
6851
  let out = "";
6232
6852
  let err = "";
6233
6853
  proc.stdout.on("data", (d) => {
@@ -6262,7 +6882,7 @@ function createGitRoutes(metadataManager) {
6262
6882
  });
6263
6883
  if (!diff.trim()) {
6264
6884
  const statusOut = await new Promise((resolveStatus) => {
6265
- const proc = spawn7("git", ["status", "--porcelain"], { cwd: gitRoot });
6885
+ const proc = spawn8("git", ["status", "--porcelain", "--untracked-files=all"], { cwd: gitRoot });
6266
6886
  let out = "";
6267
6887
  proc.stdout.on("data", (d) => {
6268
6888
  out += d.toString();
@@ -6286,11 +6906,11 @@ function createGitRoutes(metadataManager) {
6286
6906
  const parts = [];
6287
6907
  const maxFileSize = 1e5;
6288
6908
  for (const relPath of untrackedPaths) {
6289
- const fullPath = join10(gitRoot, relPath);
6290
- if (!existsSync6(fullPath))
6909
+ const fullPath = join13(gitRoot, relPath);
6910
+ if (!existsSync9(fullPath))
6291
6911
  continue;
6292
6912
  try {
6293
- if (statSync3(fullPath).isDirectory())
6913
+ if (statSync4(fullPath).isDirectory())
6294
6914
  continue;
6295
6915
  const content = readFileSync4(fullPath, "utf-8");
6296
6916
  const safeContent = content.length > maxFileSize ? content.slice(0, maxFileSize) + `
@@ -6353,21 +6973,21 @@ new file mode 100644
6353
6973
  } catch {
6354
6974
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
6355
6975
  }
6356
- await new Promise((resolve3, reject) => {
6357
- const proc = spawn7("git", ["add", "-A"], { cwd: gitRoot });
6976
+ await new Promise((resolve4, reject) => {
6977
+ const proc = spawn8("git", ["add", "-A"], { cwd: gitRoot });
6358
6978
  proc.on("close", (code) => {
6359
6979
  if (code === 0)
6360
- resolve3();
6980
+ resolve4();
6361
6981
  else
6362
6982
  reject(new Error("Failed to stage changes"));
6363
6983
  });
6364
6984
  proc.on("error", reject);
6365
6985
  });
6366
- await new Promise((resolve3, reject) => {
6367
- const proc = spawn7("git", ["commit", "-m", message], { cwd: gitRoot });
6986
+ await new Promise((resolve4, reject) => {
6987
+ const proc = spawn8("git", ["commit", "-m", message], { cwd: gitRoot });
6368
6988
  proc.on("close", (code) => {
6369
6989
  if (code === 0)
6370
- resolve3();
6990
+ resolve4();
6371
6991
  else
6372
6992
  reject(new Error("Failed to commit changes"));
6373
6993
  });
@@ -6397,24 +7017,24 @@ new file mode 100644
6397
7017
  } catch {
6398
7018
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
6399
7019
  }
6400
- const currentBranch = await new Promise((resolve3, reject) => {
6401
- const proc = spawn7("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
7020
+ const currentBranch = await new Promise((resolve4, reject) => {
7021
+ const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
6402
7022
  let out = "";
6403
7023
  proc.stdout.on("data", (d) => {
6404
7024
  out += d.toString();
6405
7025
  });
6406
- proc.on("close", () => resolve3(out.trim()));
7026
+ proc.on("close", () => resolve4(out.trim()));
6407
7027
  proc.on("error", reject);
6408
7028
  });
6409
- await new Promise((resolve3, reject) => {
6410
- const proc = spawn7("git", ["push", "-u", "origin", currentBranch], { cwd: gitRoot });
7029
+ await new Promise((resolve4, reject) => {
7030
+ const proc = spawn8("git", ["push", "-u", "origin", currentBranch], { cwd: gitRoot });
6411
7031
  let err = "";
6412
7032
  proc.stderr.on("data", (d) => {
6413
7033
  err += d.toString();
6414
7034
  });
6415
7035
  proc.on("close", (code) => {
6416
7036
  if (code === 0)
6417
- resolve3();
7037
+ resolve4();
6418
7038
  else
6419
7039
  reject(new Error(err || "Failed to push changes"));
6420
7040
  });
@@ -6444,15 +7064,15 @@ new file mode 100644
6444
7064
  } catch {
6445
7065
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
6446
7066
  }
6447
- await new Promise((resolve3, reject) => {
6448
- const proc = spawn7("git", ["fetch", "origin"], { cwd: gitRoot });
7067
+ await new Promise((resolve4, reject) => {
7068
+ const proc = spawn8("git", ["fetch", "origin"], { cwd: gitRoot });
6449
7069
  let err = "";
6450
7070
  proc.stderr.on("data", (d) => {
6451
7071
  err += d.toString();
6452
7072
  });
6453
7073
  proc.on("close", (code) => {
6454
7074
  if (code === 0)
6455
- resolve3();
7075
+ resolve4();
6456
7076
  else
6457
7077
  reject(new Error(err || "Failed to fetch from origin"));
6458
7078
  });
@@ -6486,7 +7106,7 @@ new file mode 100644
6486
7106
  }
6487
7107
  const diff = await new Promise((resolveDiff, reject) => {
6488
7108
  const runPlainDiff = () => {
6489
- const proc2 = spawn7("git", ["diff"], { cwd: gitRoot });
7109
+ const proc2 = spawn8("git", ["diff"], { cwd: gitRoot });
6490
7110
  let out2 = "";
6491
7111
  proc2.stdout.on("data", (d) => {
6492
7112
  out2 += d.toString();
@@ -6494,7 +7114,7 @@ new file mode 100644
6494
7114
  proc2.on("close", () => resolveDiff(out2));
6495
7115
  proc2.on("error", reject);
6496
7116
  };
6497
- const proc = spawn7("git", ["diff", "HEAD"], { cwd: gitRoot });
7117
+ const proc = spawn8("git", ["diff", "HEAD"], { cwd: gitRoot });
6498
7118
  let out = "";
6499
7119
  let err = "";
6500
7120
  proc.stdout.on("data", (d) => {
@@ -6578,8 +7198,8 @@ DESCRIPTION: <description here>`;
6578
7198
  } catch {
6579
7199
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
6580
7200
  }
6581
- const commits = await new Promise((resolve3, reject) => {
6582
- const proc = spawn7("git", ["log", "--oneline", "-n", limit.toString(), "--format=%H|%s|%an|%ai"], { cwd: gitRoot });
7201
+ const commits = await new Promise((resolve4, reject) => {
7202
+ const proc = spawn8("git", ["log", "--oneline", "-n", limit.toString(), "--format=%H|%s|%an|%ai"], { cwd: gitRoot });
6583
7203
  let out = "";
6584
7204
  let err = "";
6585
7205
  proc.stdout.on("data", (d) => {
@@ -6601,7 +7221,7 @@ DESCRIPTION: <description here>`;
6601
7221
  date: parts[3] || ""
6602
7222
  };
6603
7223
  });
6604
- resolve3(parsed);
7224
+ resolve4(parsed);
6605
7225
  } else {
6606
7226
  reject(new Error(err || "Failed to get git log"));
6607
7227
  }
@@ -6634,18 +7254,18 @@ DESCRIPTION: <description here>`;
6634
7254
  } catch {
6635
7255
  return c.json({ error: `Path is not a git repository: ${absolutePath}` }, 400);
6636
7256
  }
6637
- const currentBranch = await new Promise((resolve3, reject) => {
6638
- const proc = spawn7("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
7257
+ const currentBranch = await new Promise((resolve4, reject) => {
7258
+ const proc = spawn8("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: gitRoot });
6639
7259
  let out = "";
6640
7260
  proc.stdout.on("data", (d) => {
6641
7261
  out += d.toString();
6642
7262
  });
6643
- proc.on("close", () => resolve3(out.trim()));
7263
+ proc.on("close", () => resolve4(out.trim()));
6644
7264
  proc.on("error", reject);
6645
7265
  });
6646
- const prUrl = await new Promise((resolve3, reject) => {
7266
+ const prUrl = await new Promise((resolve4, reject) => {
6647
7267
  const args = ["pr", "create", "--title", title || currentBranch, "--body", description || ""];
6648
- const proc = spawn7("gh", args, { cwd: gitRoot });
7268
+ const proc = spawn8("gh", args, { cwd: gitRoot });
6649
7269
  let out = "";
6650
7270
  let err = "";
6651
7271
  proc.stdout.on("data", (d) => {
@@ -6657,7 +7277,7 @@ DESCRIPTION: <description here>`;
6657
7277
  proc.on("close", (code) => {
6658
7278
  if (code === 0) {
6659
7279
  const urlMatch = out.match(/https:\/\/[^\s]+/);
6660
- resolve3(urlMatch ? urlMatch[0] : out.trim());
7280
+ resolve4(urlMatch ? urlMatch[0] : out.trim());
6661
7281
  } else {
6662
7282
  reject(new Error(err || "Failed to create PR"));
6663
7283
  }
@@ -6681,84 +7301,69 @@ function createRunRoutes(projectManager) {
6681
7301
  try {
6682
7302
  const projectId = c.req.param("id");
6683
7303
  if (!projectId) {
6684
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
6685
- return c.json(response2, statusCode2);
7304
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
6686
7305
  }
6687
7306
  const runCommand = await projectManager.suggestRunCommand(projectId);
6688
- const { response, statusCode } = ResponseBuilder.success({ runCommand });
6689
- return c.json(response, statusCode);
7307
+ return successResponse(c, { runCommand });
6690
7308
  } catch (error) {
6691
7309
  const errorMessage = error instanceof Error ? error.message : String(error);
6692
- const { response, statusCode } = ResponseBuilder.error("SUGGEST_RUN_COMMAND_ERROR", "Failed to suggest run command", 500, errorMessage);
6693
- return c.json(response, statusCode);
7310
+ return errorResponse(c, "SUGGEST_RUN_COMMAND_ERROR", "Failed to suggest run command", 500, errorMessage);
6694
7311
  }
6695
7312
  });
6696
7313
  router.post("/:id/run", async (c) => {
6697
7314
  try {
6698
7315
  const projectId = c.req.param("id");
6699
7316
  if (!projectId) {
6700
- const { response, statusCode } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
6701
- return c.json(response, statusCode);
7317
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
6702
7318
  }
6703
7319
  return streamAsyncGenerator(c, projectManager.startRunningProcess(projectId));
6704
7320
  } catch (error) {
6705
7321
  const errorMessage = error instanceof Error ? error.message : String(error);
6706
- const { response, statusCode } = ResponseBuilder.error("RUN_PROCESS_ERROR", "Failed to start process", 500, errorMessage);
6707
- return c.json(response, statusCode);
7322
+ return errorResponse(c, "RUN_PROCESS_ERROR", "Failed to start process", 500, errorMessage);
6708
7323
  }
6709
7324
  });
6710
7325
  router.post("/:id/stop", async (c) => {
6711
7326
  try {
6712
7327
  const projectId = c.req.param("id");
6713
7328
  if (!projectId) {
6714
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
6715
- return c.json(response2, statusCode2);
7329
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
6716
7330
  }
6717
7331
  const stopped = await projectManager.stopRunningProcess(projectId);
6718
7332
  if (!stopped) {
6719
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("NO_PROCESS_RUNNING", "No process running for this project", 400);
6720
- return c.json(response2, statusCode2);
7333
+ return errorResponse(c, "NO_PROCESS_RUNNING", "No process running for this project", 400);
6721
7334
  }
6722
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Process stopped" });
6723
- return c.json(response, statusCode);
7335
+ return successResponse(c, { success: true, message: "Process stopped" });
6724
7336
  } catch (error) {
6725
7337
  const errorMessage = error instanceof Error ? error.message : String(error);
6726
- const { response, statusCode } = ResponseBuilder.error("STOP_PROCESS_ERROR", "Failed to stop process", 500, errorMessage);
6727
- return c.json(response, statusCode);
7338
+ return errorResponse(c, "STOP_PROCESS_ERROR", "Failed to stop process", 500, errorMessage);
6728
7339
  }
6729
7340
  });
6730
7341
  router.get("/:id/running", async (c) => {
6731
7342
  try {
6732
7343
  const projectId = c.req.param("id");
6733
7344
  if (!projectId) {
6734
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
6735
- return c.json(response2, statusCode2);
7345
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
6736
7346
  }
6737
7347
  const isRunning = await projectManager.isProcessRunning(projectId);
6738
- const { response, statusCode } = ResponseBuilder.success({ isRunning });
6739
- return c.json(response, statusCode);
7348
+ return successResponse(c, { isRunning });
6740
7349
  } catch (error) {
6741
7350
  const errorMessage = error instanceof Error ? error.message : String(error);
6742
- const { response, statusCode } = ResponseBuilder.error("CHECK_RUNNING_ERROR", "Failed to check running status", 500, errorMessage);
6743
- return c.json(response, statusCode);
7351
+ return errorResponse(c, "CHECK_RUNNING_ERROR", "Failed to check running status", 500, errorMessage);
6744
7352
  }
6745
7353
  });
6746
7354
  router.put("/:id/run-command", async (c) => {
6747
7355
  try {
6748
7356
  const projectId = c.req.param("id");
6749
7357
  if (!projectId) {
6750
- const { response: response2, statusCode: statusCode2 } = ResponseBuilder.error("INVALID_REQUEST", "Project ID is required", 400);
6751
- return c.json(response2, statusCode2);
7358
+ return errorResponse(c, "INVALID_REQUEST", "Project ID is required", 400);
6752
7359
  }
6753
7360
  const body = await c.req.json();
6754
7361
  const { runCommand } = body;
6755
7362
  await projectManager.updateRunCommand(projectId, runCommand || null);
6756
- const { response, statusCode } = ResponseBuilder.success({ success: true, message: "Run command updated" });
6757
- return c.json(response, statusCode);
7363
+ return successResponse(c, { success: true, message: "Run command updated" });
6758
7364
  } catch (error) {
6759
7365
  const errorMessage = error instanceof Error ? error.message : String(error);
6760
- const { response, statusCode } = ResponseBuilder.error("UPDATE_RUN_COMMAND_ERROR", "Failed to update run command", 500, errorMessage);
6761
- return c.json(response, statusCode);
7366
+ return errorResponse(c, "UPDATE_RUN_COMMAND_ERROR", "Failed to update run command", 500, errorMessage);
6762
7367
  }
6763
7368
  });
6764
7369
  return router;
@@ -6766,7 +7371,7 @@ function createRunRoutes(projectManager) {
6766
7371
 
6767
7372
  // src/routes/onboarding.ts
6768
7373
  import { Hono as Hono8 } from "hono";
6769
- import { spawn as spawn8 } from "child_process";
7374
+ import { spawn as spawn9 } from "child_process";
6770
7375
  function createOnboardingRoutes(metadataManager) {
6771
7376
  const router = new Hono8;
6772
7377
  router.get("/status", async (c) => {
@@ -6780,17 +7385,17 @@ function createOnboardingRoutes(metadataManager) {
6780
7385
  });
6781
7386
  router.get("/git-check", async (c) => {
6782
7387
  try {
6783
- const gitInstalled = await new Promise((resolve3) => {
6784
- const proc = spawn8("git", ["--version"]);
7388
+ const gitInstalled = await new Promise((resolve4) => {
7389
+ const proc = spawn9("git", ["--version"]);
6785
7390
  let _err = "";
6786
7391
  proc.stderr.on("data", (d) => {
6787
7392
  _err += d.toString();
6788
7393
  });
6789
7394
  proc.on("close", (code) => {
6790
- resolve3(code === 0);
7395
+ resolve4(code === 0);
6791
7396
  });
6792
7397
  proc.on("error", () => {
6793
- resolve3(false);
7398
+ resolve4(false);
6794
7399
  });
6795
7400
  });
6796
7401
  return c.json({ gitInstalled });
@@ -6858,6 +7463,357 @@ function createScaffoldRoutes(projectManager) {
6858
7463
  return router;
6859
7464
  }
6860
7465
 
7466
+ // src/managers/slash-command-manager.ts
7467
+ import { readdir as readdir4, readFile as readFile4, mkdir as mkdir2, writeFile, unlink } from "fs/promises";
7468
+ import { join as join14, basename, extname as extname2 } from "path";
7469
+ import { existsSync as existsSync10 } from "fs";
7470
+ import { homedir as homedir4 } from "os";
7471
+ function slugify(filename) {
7472
+ return filename.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
7473
+ }
7474
+ function parseFrontmatter2(markdown) {
7475
+ const lines = markdown.split(`
7476
+ `);
7477
+ if (lines[0]?.trim() !== "---") {
7478
+ return {
7479
+ metadata: {},
7480
+ content: markdown.trim()
7481
+ };
7482
+ }
7483
+ let endIndex = -1;
7484
+ for (let i = 1;i < lines.length; i++) {
7485
+ if (lines[i]?.trim() === "---") {
7486
+ endIndex = i;
7487
+ break;
7488
+ }
7489
+ }
7490
+ if (endIndex === -1) {
7491
+ return {
7492
+ metadata: {},
7493
+ content: markdown.trim()
7494
+ };
7495
+ }
7496
+ const frontmatterLines = lines.slice(1, endIndex);
7497
+ const metadata = {};
7498
+ for (const line of frontmatterLines) {
7499
+ const colonIndex = line.indexOf(":");
7500
+ if (colonIndex === -1)
7501
+ continue;
7502
+ const key = line.slice(0, colonIndex).trim();
7503
+ const value = line.slice(colonIndex + 1).trim();
7504
+ if (key === "description") {
7505
+ metadata.description = value;
7506
+ } else if (key === "argument-hint") {
7507
+ metadata.argumentHint = value;
7508
+ } else if (key === "mode") {
7509
+ metadata.mode = value;
7510
+ }
7511
+ }
7512
+ const content = lines.slice(endIndex + 1).join(`
7513
+ `).trim();
7514
+ return { metadata, content };
7515
+ }
7516
+ function getGlobalCommandsDir() {
7517
+ return join14(homedir4(), ".tarsk", "commands");
7518
+ }
7519
+ function getProjectCommandsDir(threadPath) {
7520
+ return join14(threadPath, ".tarsk", "commands");
7521
+ }
7522
+
7523
+ class SlashCommandManager {
7524
+ static BUILT_IN_COMMANDS = new Set(["init"]);
7525
+ async loadCommands(threadPath) {
7526
+ const commands = new Map;
7527
+ const globalDir = getGlobalCommandsDir();
7528
+ if (existsSync10(globalDir)) {
7529
+ const globalCommands = await this.loadCommandsFromDir(globalDir, "global");
7530
+ for (const cmd of globalCommands) {
7531
+ commands.set(cmd.name, cmd);
7532
+ }
7533
+ }
7534
+ const projectDir = getProjectCommandsDir(threadPath);
7535
+ if (existsSync10(projectDir)) {
7536
+ const projectCommands = await this.loadCommandsFromDir(projectDir, "project");
7537
+ for (const cmd of projectCommands) {
7538
+ if (!SlashCommandManager.BUILT_IN_COMMANDS.has(cmd.name)) {
7539
+ commands.set(cmd.name, cmd);
7540
+ }
7541
+ }
7542
+ }
7543
+ return Array.from(commands.values());
7544
+ }
7545
+ async loadCommandsFromDir(dir, scope) {
7546
+ const commands = [];
7547
+ try {
7548
+ const files = await readdir4(dir);
7549
+ for (const file of files) {
7550
+ if (!file.endsWith(".md"))
7551
+ continue;
7552
+ const filePath = join14(dir, file);
7553
+ const fileContent = await readFile4(filePath, "utf-8");
7554
+ const { metadata, content } = parseFrontmatter2(fileContent);
7555
+ const nameWithoutExt = basename(file, extname2(file));
7556
+ const commandName = slugify(nameWithoutExt);
7557
+ if (!commandName)
7558
+ continue;
7559
+ commands.push({
7560
+ name: commandName,
7561
+ content,
7562
+ metadata,
7563
+ scope,
7564
+ filePath
7565
+ });
7566
+ }
7567
+ } catch (error) {
7568
+ console.error(`Failed to load commands from ${dir}:`, error);
7569
+ }
7570
+ return commands;
7571
+ }
7572
+ async createCommand(name, content, metadata, scope, threadPath) {
7573
+ if (SlashCommandManager.BUILT_IN_COMMANDS.has(name)) {
7574
+ throw new Error(`Cannot create built-in command: ${name}`);
7575
+ }
7576
+ const dir = scope === "global" ? getGlobalCommandsDir() : threadPath ? getProjectCommandsDir(threadPath) : null;
7577
+ if (!dir) {
7578
+ throw new Error("threadPath required for project-scoped commands");
7579
+ }
7580
+ if (!existsSync10(dir)) {
7581
+ await mkdir2(dir, { recursive: true });
7582
+ }
7583
+ const filename = `${name}.md`;
7584
+ const filePath = join14(dir, filename);
7585
+ if (existsSync10(filePath)) {
7586
+ throw new Error(`Command already exists: ${name}`);
7587
+ }
7588
+ let markdown = "";
7589
+ if (metadata.description || metadata.argumentHint || metadata.mode) {
7590
+ markdown += `---
7591
+ `;
7592
+ if (metadata.description) {
7593
+ markdown += `description: ${metadata.description}
7594
+ `;
7595
+ }
7596
+ if (metadata.argumentHint) {
7597
+ markdown += `argument-hint: ${metadata.argumentHint}
7598
+ `;
7599
+ }
7600
+ if (metadata.mode) {
7601
+ markdown += `mode: ${metadata.mode}
7602
+ `;
7603
+ }
7604
+ markdown += `---
7605
+
7606
+ `;
7607
+ }
7608
+ markdown += content;
7609
+ await writeFile(filePath, markdown, "utf-8");
7610
+ return {
7611
+ name,
7612
+ content,
7613
+ metadata,
7614
+ scope,
7615
+ filePath
7616
+ };
7617
+ }
7618
+ async updateCommand(filePath, content, metadata) {
7619
+ if (!existsSync10(filePath)) {
7620
+ throw new Error(`Command file not found: ${filePath}`);
7621
+ }
7622
+ let markdown = "";
7623
+ if (metadata.description || metadata.argumentHint || metadata.mode) {
7624
+ markdown += `---
7625
+ `;
7626
+ if (metadata.description) {
7627
+ markdown += `description: ${metadata.description}
7628
+ `;
7629
+ }
7630
+ if (metadata.argumentHint) {
7631
+ markdown += `argument-hint: ${metadata.argumentHint}
7632
+ `;
7633
+ }
7634
+ if (metadata.mode) {
7635
+ markdown += `mode: ${metadata.mode}
7636
+ `;
7637
+ }
7638
+ markdown += `---
7639
+
7640
+ `;
7641
+ }
7642
+ markdown += content;
7643
+ await writeFile(filePath, markdown, "utf-8");
7644
+ }
7645
+ async deleteCommand(filePath) {
7646
+ if (!existsSync10(filePath)) {
7647
+ throw new Error(`Command file not found: ${filePath}`);
7648
+ }
7649
+ await unlink(filePath);
7650
+ }
7651
+ async getCommand(name, threadPath) {
7652
+ const commands = await this.loadCommands(threadPath);
7653
+ return commands.find((cmd) => cmd.name === name) || null;
7654
+ }
7655
+ }
7656
+
7657
+ // src/routes/slash-commands.ts
7658
+ var slashCommandManager = new SlashCommandManager;
7659
+ var skillManager = new SkillManager;
7660
+ function createSlashCommandRoutes(router, threadManager) {
7661
+ router.get("/api/slash-commands", async (c) => {
7662
+ const threadId = c.req.query("threadId");
7663
+ if (!threadId) {
7664
+ return c.json({
7665
+ error: {
7666
+ code: "MISSING_THREAD_ID",
7667
+ message: "threadId query parameter is required",
7668
+ timestamp: new Date().toISOString()
7669
+ }
7670
+ }, 400);
7671
+ }
7672
+ try {
7673
+ const thread = await threadManager.getThread(threadId);
7674
+ if (!thread) {
7675
+ return c.json({
7676
+ error: {
7677
+ code: "THREAD_NOT_FOUND",
7678
+ message: `Thread not found: ${threadId}`,
7679
+ timestamp: new Date().toISOString()
7680
+ }
7681
+ }, 404);
7682
+ }
7683
+ const commands = await slashCommandManager.loadCommands(thread.path);
7684
+ const skills = await skillManager.loadSkills(thread.path);
7685
+ const response = {
7686
+ commands,
7687
+ skills
7688
+ };
7689
+ return c.json(response);
7690
+ } catch (error) {
7691
+ console.error("Failed to load slash commands:", error);
7692
+ return c.json({
7693
+ error: {
7694
+ code: "LOAD_FAILED",
7695
+ message: error instanceof Error ? error.message : "Failed to load slash commands",
7696
+ details: error,
7697
+ timestamp: new Date().toISOString()
7698
+ }
7699
+ }, 500);
7700
+ }
7701
+ });
7702
+ router.post("/api/slash-commands", async (c) => {
7703
+ try {
7704
+ const body = await c.req.json();
7705
+ const { name, content, metadata, scope, threadId } = body;
7706
+ if (!name || !content) {
7707
+ return c.json({
7708
+ error: {
7709
+ code: "MISSING_FIELDS",
7710
+ message: "name and content are required",
7711
+ timestamp: new Date().toISOString()
7712
+ }
7713
+ }, 400);
7714
+ }
7715
+ if (scope !== "global" && scope !== "project") {
7716
+ return c.json({
7717
+ error: {
7718
+ code: "INVALID_SCOPE",
7719
+ message: 'scope must be either "global" or "project"',
7720
+ timestamp: new Date().toISOString()
7721
+ }
7722
+ }, 400);
7723
+ }
7724
+ let threadPath;
7725
+ if (scope === "project") {
7726
+ if (!threadId) {
7727
+ return c.json({
7728
+ error: {
7729
+ code: "MISSING_THREAD_ID",
7730
+ message: "threadId is required for project-scoped commands",
7731
+ timestamp: new Date().toISOString()
7732
+ }
7733
+ }, 400);
7734
+ }
7735
+ const thread = await threadManager.getThread(threadId);
7736
+ if (!thread) {
7737
+ return c.json({
7738
+ error: {
7739
+ code: "THREAD_NOT_FOUND",
7740
+ message: `Thread not found: ${threadId}`,
7741
+ timestamp: new Date().toISOString()
7742
+ }
7743
+ }, 404);
7744
+ }
7745
+ threadPath = thread.path;
7746
+ }
7747
+ const command = await slashCommandManager.createCommand(name, content, metadata || {}, scope, threadPath);
7748
+ return c.json(command, 201);
7749
+ } catch (error) {
7750
+ console.error("Failed to create slash command:", error);
7751
+ return c.json({
7752
+ error: {
7753
+ code: "CREATE_FAILED",
7754
+ message: error instanceof Error ? error.message : "Failed to create slash command",
7755
+ details: error,
7756
+ timestamp: new Date().toISOString()
7757
+ }
7758
+ }, 500);
7759
+ }
7760
+ });
7761
+ router.put("/api/slash-commands", async (c) => {
7762
+ try {
7763
+ const body = await c.req.json();
7764
+ const { filePath, content, metadata } = body;
7765
+ if (!filePath || !content) {
7766
+ return c.json({
7767
+ error: {
7768
+ code: "MISSING_FIELDS",
7769
+ message: "filePath and content are required",
7770
+ timestamp: new Date().toISOString()
7771
+ }
7772
+ }, 400);
7773
+ }
7774
+ await slashCommandManager.updateCommand(filePath, content, metadata || {});
7775
+ return c.json({ success: true });
7776
+ } catch (error) {
7777
+ console.error("Failed to update slash command:", error);
7778
+ return c.json({
7779
+ error: {
7780
+ code: "UPDATE_FAILED",
7781
+ message: error instanceof Error ? error.message : "Failed to update slash command",
7782
+ details: error,
7783
+ timestamp: new Date().toISOString()
7784
+ }
7785
+ }, 500);
7786
+ }
7787
+ });
7788
+ router.delete("/api/slash-commands", async (c) => {
7789
+ try {
7790
+ const body = await c.req.json();
7791
+ const { filePath } = body;
7792
+ if (!filePath) {
7793
+ return c.json({
7794
+ error: {
7795
+ code: "MISSING_FILE_PATH",
7796
+ message: "filePath is required",
7797
+ timestamp: new Date().toISOString()
7798
+ }
7799
+ }, 400);
7800
+ }
7801
+ await slashCommandManager.deleteCommand(filePath);
7802
+ return c.json({ success: true });
7803
+ } catch (error) {
7804
+ console.error("Failed to delete slash command:", error);
7805
+ return c.json({
7806
+ error: {
7807
+ code: "DELETE_FAILED",
7808
+ message: error instanceof Error ? error.message : "Failed to delete slash command",
7809
+ details: error,
7810
+ timestamp: new Date().toISOString()
7811
+ }
7812
+ }, 500);
7813
+ }
7814
+ });
7815
+ }
7816
+
6861
7817
  // src/index.ts
6862
7818
  init_dist();
6863
7819
  var __filename2 = fileURLToPath3(import.meta.url);
@@ -6905,6 +7861,7 @@ app.route("/api/models", createModelRoutes(metadataManager));
6905
7861
  app.route("/api/git", createGitRoutes(metadataManager));
6906
7862
  app.route("/api/onboarding", createOnboardingRoutes(metadataManager));
6907
7863
  app.route("/api/scaffold", createScaffoldRoutes(projectManager));
7864
+ createSlashCommandRoutes(app, threadManager);
6908
7865
  var publicDir = path3.join(__dirname3, "public");
6909
7866
  var staticRoot = path3.relative(process.cwd(), publicDir);
6910
7867
  app.use("/*", async (c, next) => {
@@ -6922,14 +7879,14 @@ app.get("*", async (c, next) => {
6922
7879
  })(c, next);
6923
7880
  });
6924
7881
  app.all("*", (c) => {
6925
- const errorResponse = {
7882
+ const errorResponse2 = {
6926
7883
  error: {
6927
7884
  code: "NOT_FOUND",
6928
7885
  message: `Route not found: ${c.req.method} ${c.req.path}`,
6929
7886
  timestamp: new Date().toISOString()
6930
7887
  }
6931
7888
  };
6932
- return c.json(errorResponse, 404);
7889
+ return c.json(errorResponse2, 404);
6933
7890
  });
6934
7891
  var port = isDebug ? 462 : process.env.PORT ? parseInt(process.env.PORT) : 641;
6935
7892
  var url = `http://localhost:${port}`;
@@ -6941,6 +7898,6 @@ serve({
6941
7898
  }, () => {
6942
7899
  const isDevelopment = process.env.MODE === "development";
6943
7900
  if (shouldOpenBrowser || !isDevelopment) {
6944
- open2(url).catch(() => {});
7901
+ open3(url).catch(() => {});
6945
7902
  }
6946
7903
  });