darkfoo-code 0.2.5 → 0.4.0
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/main.js +450 -447
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -18,18 +18,30 @@ var init_theme = __esm({
|
|
|
18
18
|
"src/utils/theme.ts"() {
|
|
19
19
|
"use strict";
|
|
20
20
|
theme = {
|
|
21
|
-
cyan: "#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
cyan: "#3b82f6",
|
|
22
|
+
// bright blue (primary)
|
|
23
|
+
cyanDim: "#2563eb",
|
|
24
|
+
// deeper blue
|
|
25
|
+
pink: "#ea580c",
|
|
26
|
+
// burnt orange (accent)
|
|
27
|
+
pinkDim: "#c2410c",
|
|
28
|
+
// deep burnt orange
|
|
29
|
+
purple: "#7c6ddb",
|
|
30
|
+
// blue-violet (midpoint)
|
|
26
31
|
green: "#4ade80",
|
|
27
|
-
|
|
32
|
+
// success green
|
|
33
|
+
yellow: "#f59e0b",
|
|
34
|
+
// warm amber
|
|
28
35
|
red: "#ef4444",
|
|
36
|
+
// errors
|
|
29
37
|
text: "#e2e8f0",
|
|
38
|
+
// body text
|
|
30
39
|
dim: "#7e8ea6",
|
|
40
|
+
// secondary
|
|
31
41
|
surface: "#111827",
|
|
42
|
+
// component backgrounds
|
|
32
43
|
bg: "#0c1021"
|
|
44
|
+
// terminal background
|
|
33
45
|
};
|
|
34
46
|
}
|
|
35
47
|
});
|
|
@@ -151,6 +163,13 @@ var init_ollama = __esm({
|
|
|
151
163
|
yield { type: "tool_call", toolCall: normalizeToolCall(tc) };
|
|
152
164
|
}
|
|
153
165
|
}
|
|
166
|
+
if (chunk.done && (chunk.eval_count || chunk.prompt_eval_count)) {
|
|
167
|
+
yield {
|
|
168
|
+
type: "usage",
|
|
169
|
+
inputTokens: chunk.prompt_eval_count ?? 0,
|
|
170
|
+
outputTokens: chunk.eval_count ?? 0
|
|
171
|
+
};
|
|
172
|
+
}
|
|
154
173
|
}
|
|
155
174
|
}
|
|
156
175
|
} finally {
|
|
@@ -299,6 +318,13 @@ var init_openai_compat = __esm({
|
|
|
299
318
|
}
|
|
300
319
|
}
|
|
301
320
|
pendingToolCalls.clear();
|
|
321
|
+
if (chunk.usage) {
|
|
322
|
+
yield {
|
|
323
|
+
type: "usage",
|
|
324
|
+
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
325
|
+
outputTokens: chunk.usage.completion_tokens ?? 0
|
|
326
|
+
};
|
|
327
|
+
}
|
|
302
328
|
}
|
|
303
329
|
}
|
|
304
330
|
}
|
|
@@ -549,6 +575,88 @@ var init_permissions = __esm({
|
|
|
549
575
|
}
|
|
550
576
|
});
|
|
551
577
|
|
|
578
|
+
// src/hooks.ts
|
|
579
|
+
import { execFile as execFile4 } from "child_process";
|
|
580
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
581
|
+
import { join as join6 } from "path";
|
|
582
|
+
async function loadHooks() {
|
|
583
|
+
if (cachedHooks) return cachedHooks;
|
|
584
|
+
try {
|
|
585
|
+
const raw = await readFile5(SETTINGS_PATH4, "utf-8");
|
|
586
|
+
const config = JSON.parse(raw);
|
|
587
|
+
cachedHooks = config.hooks ?? [];
|
|
588
|
+
return cachedHooks;
|
|
589
|
+
} catch {
|
|
590
|
+
cachedHooks = [];
|
|
591
|
+
return [];
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
async function executeHooks(event, context) {
|
|
595
|
+
const hooks = await loadHooks();
|
|
596
|
+
const matching = hooks.filter((h) => {
|
|
597
|
+
if (h.event !== event) return false;
|
|
598
|
+
if (h.toolFilter && context.toolName && !h.toolFilter.includes(context.toolName)) return false;
|
|
599
|
+
return true;
|
|
600
|
+
});
|
|
601
|
+
const results = [];
|
|
602
|
+
for (const hook of matching) {
|
|
603
|
+
try {
|
|
604
|
+
const output = await new Promise((resolve8, reject) => {
|
|
605
|
+
execFile4("bash", ["-c", hook.command], {
|
|
606
|
+
cwd: context.cwd,
|
|
607
|
+
timeout: 1e4,
|
|
608
|
+
env: {
|
|
609
|
+
...process.env,
|
|
610
|
+
DARKFOO_EVENT: event,
|
|
611
|
+
DARKFOO_TOOL: context.toolName ?? ""
|
|
612
|
+
}
|
|
613
|
+
}, (err, stdout) => {
|
|
614
|
+
if (err) reject(err);
|
|
615
|
+
else resolve8(stdout.trim());
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
if (output) results.push(output);
|
|
619
|
+
} catch {
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return results;
|
|
623
|
+
}
|
|
624
|
+
var SETTINGS_PATH4, cachedHooks;
|
|
625
|
+
var init_hooks = __esm({
|
|
626
|
+
"src/hooks.ts"() {
|
|
627
|
+
"use strict";
|
|
628
|
+
SETTINGS_PATH4 = join6(process.env.HOME || "~", ".darkfoo", "settings.json");
|
|
629
|
+
cachedHooks = null;
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// src/utils/debug.ts
|
|
634
|
+
var debug_exports = {};
|
|
635
|
+
__export(debug_exports, {
|
|
636
|
+
debug: () => debug,
|
|
637
|
+
isDebugMode: () => isDebugMode,
|
|
638
|
+
setDebugMode: () => setDebugMode
|
|
639
|
+
});
|
|
640
|
+
function setDebugMode(on) {
|
|
641
|
+
debugMode = on;
|
|
642
|
+
}
|
|
643
|
+
function isDebugMode() {
|
|
644
|
+
return debugMode;
|
|
645
|
+
}
|
|
646
|
+
function debug(msg) {
|
|
647
|
+
if (debugMode) {
|
|
648
|
+
process.stderr.write(`[debug] ${msg}
|
|
649
|
+
`);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
var debugMode;
|
|
653
|
+
var init_debug = __esm({
|
|
654
|
+
"src/utils/debug.ts"() {
|
|
655
|
+
"use strict";
|
|
656
|
+
debugMode = false;
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
552
660
|
// src/query.ts
|
|
553
661
|
var query_exports = {};
|
|
554
662
|
__export(query_exports, {
|
|
@@ -563,6 +671,7 @@ async function* query(params) {
|
|
|
563
671
|
const ollamaTools = tools.map((t) => t.toOllamaToolDef());
|
|
564
672
|
while (turns < maxTurns) {
|
|
565
673
|
turns++;
|
|
674
|
+
debug(`query turn ${turns}/${maxTurns}, ${messages.length} messages`);
|
|
566
675
|
const ollamaMessages = toOllamaMessages(messages, systemPrompt);
|
|
567
676
|
let assistantContent = "";
|
|
568
677
|
const toolCalls = [];
|
|
@@ -588,7 +697,11 @@ async function* query(params) {
|
|
|
588
697
|
};
|
|
589
698
|
messages.push(assistantMsg);
|
|
590
699
|
yield { type: "assistant_message", message: assistantMsg };
|
|
591
|
-
if (toolCalls.length === 0)
|
|
700
|
+
if (toolCalls.length === 0) {
|
|
701
|
+
debug("no tool calls, query complete");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
debug(`${toolCalls.length} tool calls: ${toolCalls.map((tc) => tc.function.name).join(", ")}`);
|
|
592
705
|
const readOnlyCalls = [];
|
|
593
706
|
const writeCalls = [];
|
|
594
707
|
const unknownCalls = [];
|
|
@@ -611,10 +724,14 @@ async function* query(params) {
|
|
|
611
724
|
const results = await Promise.all(
|
|
612
725
|
readOnlyCalls.map(async ({ tc, tool }) => {
|
|
613
726
|
const coercedArgs = coerceToolArgs(tc.function.arguments, tool);
|
|
727
|
+
await executeHooks("pre_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
614
728
|
try {
|
|
615
|
-
|
|
729
|
+
const result = await tool.call(coercedArgs, { cwd: process.cwd(), abortSignal: signal });
|
|
730
|
+
await executeHooks("post_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
731
|
+
return { tool, result };
|
|
616
732
|
} catch (err) {
|
|
617
733
|
const msg = err instanceof Error ? err.message : String(err);
|
|
734
|
+
await executeHooks("post_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
618
735
|
return { tool, result: { output: `Tool execution error: ${msg}`, isError: true } };
|
|
619
736
|
}
|
|
620
737
|
})
|
|
@@ -625,6 +742,12 @@ async function* query(params) {
|
|
|
625
742
|
}
|
|
626
743
|
}
|
|
627
744
|
for (const { tc, tool } of writeCalls) {
|
|
745
|
+
if (getAppState().planMode) {
|
|
746
|
+
const blocked = `Tool "${tool.name}" is blocked in plan mode. Use ExitPlanMode first.`;
|
|
747
|
+
yield { type: "tool_result", toolName: tool.name, output: blocked, isError: true };
|
|
748
|
+
messages.push({ id: nanoid3(), role: "tool", content: blocked, toolName: tool.name, timestamp: Date.now() });
|
|
749
|
+
continue;
|
|
750
|
+
}
|
|
628
751
|
const coercedArgs = coerceToolArgs(tc.function.arguments, tool);
|
|
629
752
|
const permission = await checkPermission(tool, coercedArgs);
|
|
630
753
|
if (permission === "deny") {
|
|
@@ -633,6 +756,7 @@ async function* query(params) {
|
|
|
633
756
|
messages.push({ id: nanoid3(), role: "tool", content: denied, toolName: tool.name, timestamp: Date.now() });
|
|
634
757
|
continue;
|
|
635
758
|
}
|
|
759
|
+
await executeHooks("pre_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
636
760
|
let result;
|
|
637
761
|
try {
|
|
638
762
|
result = await tool.call(coercedArgs, { cwd: process.cwd(), abortSignal: signal });
|
|
@@ -640,6 +764,7 @@ async function* query(params) {
|
|
|
640
764
|
const msg = err instanceof Error ? err.message : String(err);
|
|
641
765
|
result = { output: `Tool execution error: ${msg}`, isError: true };
|
|
642
766
|
}
|
|
767
|
+
await executeHooks("post_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
643
768
|
yield { type: "tool_result", toolName: tool.name, output: result.output, isError: result.isError ?? false };
|
|
644
769
|
messages.push({ id: nanoid3(), role: "tool", content: result.output, toolName: tool.name, timestamp: Date.now() });
|
|
645
770
|
}
|
|
@@ -691,12 +816,15 @@ var init_query = __esm({
|
|
|
691
816
|
"use strict";
|
|
692
817
|
init_providers();
|
|
693
818
|
init_permissions();
|
|
819
|
+
init_hooks();
|
|
820
|
+
init_state();
|
|
821
|
+
init_debug();
|
|
694
822
|
}
|
|
695
823
|
});
|
|
696
824
|
|
|
697
825
|
// src/context-loader.ts
|
|
698
|
-
import { readFile as
|
|
699
|
-
import { join as
|
|
826
|
+
import { readFile as readFile6, readdir as readdir2, access } from "fs/promises";
|
|
827
|
+
import { join as join7, dirname, resolve } from "path";
|
|
700
828
|
async function loadProjectContext(cwd) {
|
|
701
829
|
const parts = [];
|
|
702
830
|
const globalContent = await tryRead(HOME_CONTEXT);
|
|
@@ -704,13 +832,27 @@ async function loadProjectContext(cwd) {
|
|
|
704
832
|
parts.push(`# Global instructions (~/.darkfoo/DARKFOO.md)
|
|
705
833
|
|
|
706
834
|
${globalContent}`);
|
|
835
|
+
}
|
|
836
|
+
const userRulesDir = join7(process.env.HOME || "~", ".darkfoo", "rules");
|
|
837
|
+
try {
|
|
838
|
+
await access(userRulesDir);
|
|
839
|
+
const userRuleFiles = await readdir2(userRulesDir);
|
|
840
|
+
for (const file of userRuleFiles.filter((f) => f.endsWith(".md")).sort()) {
|
|
841
|
+
const content = await tryRead(join7(userRulesDir, file));
|
|
842
|
+
if (content) {
|
|
843
|
+
parts.push(`# User rule: ${file}
|
|
844
|
+
|
|
845
|
+
${content}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
} catch {
|
|
707
849
|
}
|
|
708
850
|
const visited = /* @__PURE__ */ new Set();
|
|
709
851
|
let dir = resolve(cwd);
|
|
710
852
|
while (dir && !visited.has(dir)) {
|
|
711
853
|
visited.add(dir);
|
|
712
854
|
for (const file of CONTEXT_FILES) {
|
|
713
|
-
const filePath =
|
|
855
|
+
const filePath = join7(dir, file);
|
|
714
856
|
const content = await tryRead(filePath);
|
|
715
857
|
if (content) {
|
|
716
858
|
const rel = filePath.replace(cwd + "/", "");
|
|
@@ -723,12 +865,12 @@ ${content}`);
|
|
|
723
865
|
if (parent === dir) break;
|
|
724
866
|
dir = parent;
|
|
725
867
|
}
|
|
726
|
-
const rulesDir =
|
|
868
|
+
const rulesDir = join7(cwd, RULES_DIR);
|
|
727
869
|
try {
|
|
728
870
|
await access(rulesDir);
|
|
729
871
|
const files = await readdir2(rulesDir);
|
|
730
872
|
for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
|
|
731
|
-
const content = await tryRead(
|
|
873
|
+
const content = await tryRead(join7(rulesDir, file));
|
|
732
874
|
if (content) {
|
|
733
875
|
parts.push(`# Rule: ${file}
|
|
734
876
|
|
|
@@ -741,7 +883,7 @@ ${content}`);
|
|
|
741
883
|
}
|
|
742
884
|
async function tryRead(path) {
|
|
743
885
|
try {
|
|
744
|
-
return await
|
|
886
|
+
return await readFile6(path, "utf-8");
|
|
745
887
|
} catch {
|
|
746
888
|
return null;
|
|
747
889
|
}
|
|
@@ -752,7 +894,7 @@ var init_context_loader = __esm({
|
|
|
752
894
|
"use strict";
|
|
753
895
|
CONTEXT_FILES = ["DARKFOO.md", ".darkfoo/DARKFOO.md"];
|
|
754
896
|
RULES_DIR = ".darkfoo/rules";
|
|
755
|
-
HOME_CONTEXT =
|
|
897
|
+
HOME_CONTEXT = join7(process.env.HOME || "~", ".darkfoo", "DARKFOO.md");
|
|
756
898
|
}
|
|
757
899
|
});
|
|
758
900
|
|
|
@@ -847,14 +989,14 @@ var init_types = __esm({
|
|
|
847
989
|
|
|
848
990
|
// src/tools/bash.ts
|
|
849
991
|
import { spawn } from "child_process";
|
|
850
|
-
import { writeFile as writeFile6, mkdir as mkdir6, readFile as
|
|
851
|
-
import { join as
|
|
992
|
+
import { writeFile as writeFile6, mkdir as mkdir6, readFile as readFile7 } from "fs/promises";
|
|
993
|
+
import { join as join8 } from "path";
|
|
852
994
|
import { nanoid as nanoid4 } from "nanoid";
|
|
853
995
|
import { z } from "zod";
|
|
854
996
|
async function runInBackground(command, cwd) {
|
|
855
997
|
const taskId = nanoid4(8);
|
|
856
998
|
await mkdir6(BG_OUTPUT_DIR, { recursive: true });
|
|
857
|
-
const outputPath =
|
|
999
|
+
const outputPath = join8(BG_OUTPUT_DIR, `${taskId}.output`);
|
|
858
1000
|
backgroundTasks.set(taskId, { command, status: "running", outputPath });
|
|
859
1001
|
const proc = spawn(command, {
|
|
860
1002
|
shell: true,
|
|
@@ -906,7 +1048,7 @@ var init_bash = __esm({
|
|
|
906
1048
|
run_in_background: z.boolean().optional().describe("Run in background, returning a task ID for later retrieval")
|
|
907
1049
|
});
|
|
908
1050
|
MAX_OUTPUT = 1e5;
|
|
909
|
-
BG_OUTPUT_DIR =
|
|
1051
|
+
BG_OUTPUT_DIR = join8(process.env.HOME || "~", ".darkfoo", "bg-tasks");
|
|
910
1052
|
backgroundTasks = /* @__PURE__ */ new Map();
|
|
911
1053
|
BashTool = {
|
|
912
1054
|
name: "Bash",
|
|
@@ -967,17 +1109,17 @@ var init_bash = __esm({
|
|
|
967
1109
|
});
|
|
968
1110
|
|
|
969
1111
|
// src/tools/read.ts
|
|
970
|
-
import { readFile as
|
|
1112
|
+
import { readFile as readFile8, stat } from "fs/promises";
|
|
971
1113
|
import { extname, resolve as resolve2 } from "path";
|
|
972
1114
|
import { z as z2 } from "zod";
|
|
973
1115
|
async function readImage(filePath, size) {
|
|
974
1116
|
const ext = extname(filePath).toLowerCase();
|
|
975
1117
|
if (ext === ".svg") {
|
|
976
|
-
const content = await
|
|
1118
|
+
const content = await readFile8(filePath, "utf-8");
|
|
977
1119
|
return { output: `SVG image (${size} bytes):
|
|
978
1120
|
${content.slice(0, 5e3)}` };
|
|
979
1121
|
}
|
|
980
|
-
const buffer = await
|
|
1122
|
+
const buffer = await readFile8(filePath);
|
|
981
1123
|
const base64 = buffer.toString("base64");
|
|
982
1124
|
const sizeKB = (size / 1024).toFixed(1);
|
|
983
1125
|
const dims = detectImageDimensions(buffer, ext);
|
|
@@ -990,9 +1132,9 @@ Base64 length: ${base64.length} chars
|
|
|
990
1132
|
};
|
|
991
1133
|
}
|
|
992
1134
|
async function readPdf(filePath) {
|
|
993
|
-
const { execFile:
|
|
1135
|
+
const { execFile: execFile7 } = await import("child_process");
|
|
994
1136
|
return new Promise((resolve8) => {
|
|
995
|
-
|
|
1137
|
+
execFile7("pdftotext", [filePath, "-"], { timeout: 15e3, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
|
|
996
1138
|
if (err) {
|
|
997
1139
|
resolve8({
|
|
998
1140
|
output: `PDF file: ${filePath}
|
|
@@ -1069,7 +1211,7 @@ var init_read = __esm({
|
|
|
1069
1211
|
if (ext === PDF_EXT) {
|
|
1070
1212
|
return readPdf(filePath);
|
|
1071
1213
|
}
|
|
1072
|
-
const raw = await
|
|
1214
|
+
const raw = await readFile8(filePath, "utf-8");
|
|
1073
1215
|
markFileRead(filePath, raw);
|
|
1074
1216
|
const lines = raw.split("\n");
|
|
1075
1217
|
const offset = parsed.offset ?? 0;
|
|
@@ -1140,7 +1282,7 @@ var init_write = __esm({
|
|
|
1140
1282
|
});
|
|
1141
1283
|
|
|
1142
1284
|
// src/tools/edit.ts
|
|
1143
|
-
import { readFile as
|
|
1285
|
+
import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
|
|
1144
1286
|
import { resolve as resolve4 } from "path";
|
|
1145
1287
|
import { createPatch } from "diff";
|
|
1146
1288
|
import { z as z4 } from "zod";
|
|
@@ -1186,7 +1328,7 @@ var init_edit = __esm({
|
|
|
1186
1328
|
isError: true
|
|
1187
1329
|
};
|
|
1188
1330
|
}
|
|
1189
|
-
const content = await
|
|
1331
|
+
const content = await readFile9(filePath, "utf-8");
|
|
1190
1332
|
const actualOld = findActualString(content, parsed.old_string);
|
|
1191
1333
|
if (!actualOld) {
|
|
1192
1334
|
return {
|
|
@@ -1218,7 +1360,7 @@ var init_edit = __esm({
|
|
|
1218
1360
|
});
|
|
1219
1361
|
|
|
1220
1362
|
// src/tools/grep.ts
|
|
1221
|
-
import { execFile as
|
|
1363
|
+
import { execFile as execFile5 } from "child_process";
|
|
1222
1364
|
import { resolve as resolve5 } from "path";
|
|
1223
1365
|
import { z as z5 } from "zod";
|
|
1224
1366
|
var INPUT_SCHEMA5, DEFAULT_HEAD_LIMIT, GrepTool;
|
|
@@ -1278,7 +1420,7 @@ var init_grep = __esm({
|
|
|
1278
1420
|
}
|
|
1279
1421
|
args.push(searchPath);
|
|
1280
1422
|
return new Promise((resolve8) => {
|
|
1281
|
-
|
|
1423
|
+
execFile5("rg", args, { maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }, (err, stdout, stderr) => {
|
|
1282
1424
|
if (err && !stdout) {
|
|
1283
1425
|
if (err.code === 1 || err.code === "1") {
|
|
1284
1426
|
resolve8({ output: "No matches found." });
|
|
@@ -1314,7 +1456,7 @@ ${output}`;
|
|
|
1314
1456
|
});
|
|
1315
1457
|
|
|
1316
1458
|
// src/tools/glob.ts
|
|
1317
|
-
import { execFile as
|
|
1459
|
+
import { execFile as execFile6 } from "child_process";
|
|
1318
1460
|
import { resolve as resolve6 } from "path";
|
|
1319
1461
|
import { z as z6 } from "zod";
|
|
1320
1462
|
var INPUT_SCHEMA6, MAX_RESULTS, GlobTool;
|
|
@@ -1348,7 +1490,7 @@ var init_glob = __esm({
|
|
|
1348
1490
|
searchPath
|
|
1349
1491
|
];
|
|
1350
1492
|
return new Promise((resolve8) => {
|
|
1351
|
-
|
|
1493
|
+
execFile6("rg", args, { maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }, (err, stdout, stderr) => {
|
|
1352
1494
|
if (err && !stdout) {
|
|
1353
1495
|
if (err.code === 1 || err.code === "1") {
|
|
1354
1496
|
resolve8({ output: "No files matched." });
|
|
@@ -1599,7 +1741,7 @@ var init_agent = __esm({
|
|
|
1599
1741
|
});
|
|
1600
1742
|
|
|
1601
1743
|
// src/tools/notebook-edit.ts
|
|
1602
|
-
import { readFile as
|
|
1744
|
+
import { readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
|
|
1603
1745
|
import { resolve as resolve7 } from "path";
|
|
1604
1746
|
import { z as z10 } from "zod";
|
|
1605
1747
|
var INPUT_SCHEMA10, NotebookEditTool;
|
|
@@ -1622,7 +1764,7 @@ var init_notebook_edit = __esm({
|
|
|
1622
1764
|
const parsed = INPUT_SCHEMA10.parse(input);
|
|
1623
1765
|
const filePath = resolve7(context.cwd, parsed.file_path);
|
|
1624
1766
|
try {
|
|
1625
|
-
const raw = await
|
|
1767
|
+
const raw = await readFile10(filePath, "utf-8");
|
|
1626
1768
|
const notebook = JSON.parse(raw);
|
|
1627
1769
|
if (parsed.cell_index < 0 || parsed.cell_index >= notebook.cells.length) {
|
|
1628
1770
|
return {
|
|
@@ -1921,145 +2063,21 @@ var DarkfooContext = createContext({ model: "qwen2.5-coder:32b" });
|
|
|
1921
2063
|
function useDarkfooContext() {
|
|
1922
2064
|
return useContext(DarkfooContext);
|
|
1923
2065
|
}
|
|
1924
|
-
function App({ model, systemPromptOverride, children }) {
|
|
1925
|
-
return /* @__PURE__ */ jsx(DarkfooContext.Provider, { value: { model, systemPromptOverride }, children });
|
|
2066
|
+
function App({ model, systemPromptOverride, maxTurns, initialMessages, initialSessionId, children }) {
|
|
2067
|
+
return /* @__PURE__ */ jsx(DarkfooContext.Provider, { value: { model, systemPromptOverride, maxTurns, initialMessages, initialSessionId }, children });
|
|
1926
2068
|
}
|
|
1927
2069
|
|
|
1928
2070
|
// src/repl.tsx
|
|
1929
2071
|
import { useState as useState2, useCallback as useCallback2, useEffect, useRef } from "react";
|
|
1930
|
-
import { Box as
|
|
2072
|
+
import { Box as Box6, Text as Text6, useApp, useInput as useInput2 } from "ink";
|
|
1931
2073
|
import { nanoid as nanoid6 } from "nanoid";
|
|
1932
2074
|
|
|
1933
2075
|
// src/components/Banner.tsx
|
|
1934
2076
|
init_theme();
|
|
1935
|
-
import { memo as memo2 } from "react";
|
|
1936
|
-
import { Box as Box2, Text as Text2 } from "ink";
|
|
1937
|
-
|
|
1938
|
-
// src/components/Fox.tsx
|
|
1939
|
-
init_theme();
|
|
1940
2077
|
import { memo } from "react";
|
|
1941
2078
|
import { Box, Text } from "ink";
|
|
1942
2079
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
1943
|
-
var
|
|
1944
|
-
idle: [
|
|
1945
|
-
" /\\_/\\ ",
|
|
1946
|
-
" ( o.o ) ",
|
|
1947
|
-
" > ^ < ",
|
|
1948
|
-
" /| |\\ ",
|
|
1949
|
-
" (_| |_)",
|
|
1950
|
-
" ~ "
|
|
1951
|
-
],
|
|
1952
|
-
thinking: [
|
|
1953
|
-
" /\\_/\\ ",
|
|
1954
|
-
" ( o.- ) ",
|
|
1955
|
-
" > ^ < ",
|
|
1956
|
-
" | | ",
|
|
1957
|
-
" |___| ",
|
|
1958
|
-
" . . . "
|
|
1959
|
-
],
|
|
1960
|
-
working: [
|
|
1961
|
-
" /\\_/\\ ",
|
|
1962
|
-
" ( >.< ) ",
|
|
1963
|
-
" > ^ < ",
|
|
1964
|
-
" /| |\\ ",
|
|
1965
|
-
" (_| |_)",
|
|
1966
|
-
" ~\\ "
|
|
1967
|
-
],
|
|
1968
|
-
success: [
|
|
1969
|
-
" /\\_/\\ ",
|
|
1970
|
-
" ( ^.^ ) ",
|
|
1971
|
-
" > w < ",
|
|
1972
|
-
" /| |\\ ",
|
|
1973
|
-
" (_| |_)",
|
|
1974
|
-
" \\~/ * "
|
|
1975
|
-
],
|
|
1976
|
-
error: [
|
|
1977
|
-
" /\\_/\\ ",
|
|
1978
|
-
" ( ;.; ) ",
|
|
1979
|
-
" > n < ",
|
|
1980
|
-
" | | ",
|
|
1981
|
-
" |___| ",
|
|
1982
|
-
" "
|
|
1983
|
-
],
|
|
1984
|
-
greeting: [
|
|
1985
|
-
" /\\_/\\ ",
|
|
1986
|
-
" ( ^.^ )/",
|
|
1987
|
-
" > w < ",
|
|
1988
|
-
" /| | ",
|
|
1989
|
-
" (_| |) ",
|
|
1990
|
-
" \\~/ "
|
|
1991
|
-
],
|
|
1992
|
-
pet: [
|
|
1993
|
-
" /\\_/\\ ",
|
|
1994
|
-
" ( ^w^ ) ",
|
|
1995
|
-
" > ~ < ",
|
|
1996
|
-
" /| |\\ ",
|
|
1997
|
-
" (_| |_)",
|
|
1998
|
-
" \\~/ * "
|
|
1999
|
-
],
|
|
2000
|
-
eating: [
|
|
2001
|
-
" /\\_/\\ ",
|
|
2002
|
-
" ( >o< ) ",
|
|
2003
|
-
" > ~ < ",
|
|
2004
|
-
" /| |\\ ",
|
|
2005
|
-
" (_| |_)",
|
|
2006
|
-
" \\~/ "
|
|
2007
|
-
],
|
|
2008
|
-
sleeping: [
|
|
2009
|
-
" /\\_/\\ ",
|
|
2010
|
-
" ( -.- ) ",
|
|
2011
|
-
" > ~ < ",
|
|
2012
|
-
" | | ",
|
|
2013
|
-
" |___| ",
|
|
2014
|
-
" z z z "
|
|
2015
|
-
]
|
|
2016
|
-
};
|
|
2017
|
-
var BODY_COLORS = {
|
|
2018
|
-
idle: "#5eead4",
|
|
2019
|
-
thinking: "#a78bfa",
|
|
2020
|
-
working: "#fbbf24",
|
|
2021
|
-
success: "#4ade80",
|
|
2022
|
-
error: "#f472b6",
|
|
2023
|
-
greeting: "#5eead4",
|
|
2024
|
-
pet: "#f472b6",
|
|
2025
|
-
eating: "#fbbf24",
|
|
2026
|
-
sleeping: "#7e8ea6"
|
|
2027
|
-
};
|
|
2028
|
-
var FACE_COLORS = {
|
|
2029
|
-
idle: "#e2e8f0",
|
|
2030
|
-
thinking: "#5eead4",
|
|
2031
|
-
working: "#5eead4",
|
|
2032
|
-
success: "#4ade80",
|
|
2033
|
-
error: "#f472b6",
|
|
2034
|
-
greeting: "#4ade80",
|
|
2035
|
-
pet: "#f472b6",
|
|
2036
|
-
eating: "#fbbf24",
|
|
2037
|
-
sleeping: "#7e8ea6"
|
|
2038
|
-
};
|
|
2039
|
-
var Fox = memo(function Fox2({ mood = "idle" }) {
|
|
2040
|
-
const frame = FRAMES[mood];
|
|
2041
|
-
const bodyColor = BODY_COLORS[mood];
|
|
2042
|
-
const faceColor = FACE_COLORS[mood];
|
|
2043
|
-
return /* @__PURE__ */ jsx2(Box, { flexDirection: "column", children: frame.map((line, i) => /* @__PURE__ */ jsx2(Text, { color: i <= 1 ? faceColor : bodyColor, children: line }, i)) });
|
|
2044
|
-
});
|
|
2045
|
-
function FoxBubble({ text }) {
|
|
2046
|
-
if (!text) return null;
|
|
2047
|
-
const maxW = 28;
|
|
2048
|
-
const display = text.length > maxW ? text.slice(0, maxW - 2) + ".." : text;
|
|
2049
|
-
const w = display.length;
|
|
2050
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2051
|
-
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "." + "-".repeat(w + 2) + "." }),
|
|
2052
|
-
/* @__PURE__ */ jsxs(Text, { color: theme.dim ?? "#7e8ea6", children: [
|
|
2053
|
-
"| ",
|
|
2054
|
-
/* @__PURE__ */ jsx2(Text, { color: theme.text ?? "#e2e8f0", children: display }),
|
|
2055
|
-
" |"
|
|
2056
|
-
] }),
|
|
2057
|
-
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "'" + "-".repeat(w + 2) + "'" })
|
|
2058
|
-
] });
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
// src/components/Banner.tsx
|
|
2062
|
-
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2080
|
+
var version = "0.4.0";
|
|
2063
2081
|
var LOGO_LINES = [
|
|
2064
2082
|
" \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
|
|
2065
2083
|
" \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557",
|
|
@@ -2070,14 +2088,21 @@ var LOGO_LINES = [
|
|
|
2070
2088
|
];
|
|
2071
2089
|
var SUBTITLE = " -- C O D E --";
|
|
2072
2090
|
var GRADIENT = [
|
|
2073
|
-
"#
|
|
2074
|
-
"#
|
|
2075
|
-
"#
|
|
2076
|
-
"#
|
|
2077
|
-
"#
|
|
2078
|
-
"#
|
|
2079
|
-
"#
|
|
2091
|
+
"#3b82f6",
|
|
2092
|
+
"#5a72e0",
|
|
2093
|
+
"#7c6ddb",
|
|
2094
|
+
"#9a5ec0",
|
|
2095
|
+
"#b8509a",
|
|
2096
|
+
"#d46030",
|
|
2097
|
+
"#ea580c"
|
|
2080
2098
|
];
|
|
2099
|
+
var MOOD_COLORS = {
|
|
2100
|
+
idle: "#3b82f6",
|
|
2101
|
+
thinking: "#7c6ddb",
|
|
2102
|
+
working: "#f59e0b",
|
|
2103
|
+
success: "#4ade80",
|
|
2104
|
+
error: "#ea580c"
|
|
2105
|
+
};
|
|
2081
2106
|
function lineColor(index) {
|
|
2082
2107
|
const t = index / (LOGO_LINES.length - 1);
|
|
2083
2108
|
return GRADIENT[Math.round(t * (GRADIENT.length - 1))];
|
|
@@ -2088,43 +2113,51 @@ function gradientBar(width) {
|
|
|
2088
2113
|
color: GRADIENT[Math.round(i / (width - 1) * (GRADIENT.length - 1))]
|
|
2089
2114
|
}));
|
|
2090
2115
|
}
|
|
2091
|
-
var Banner =
|
|
2116
|
+
var Banner = memo(function Banner2({ model, cwd, providerName, providerOnline, mood = "idle" }) {
|
|
2092
2117
|
const bar = gradientBar(60);
|
|
2093
2118
|
const statusTag = providerOnline ? "[connected]" : "[offline]";
|
|
2094
2119
|
const statusColor = providerOnline ? theme.green ?? "#4ade80" : theme.pink ?? "#f472b6";
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
/* @__PURE__ */
|
|
2120
|
+
const mascotColor = MOOD_COLORS[mood] ?? MOOD_COLORS.idle;
|
|
2121
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
2122
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
2123
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2124
|
+
LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx2(Text, { color: lineColor(i), bold: true, children: line }, i)),
|
|
2125
|
+
/* @__PURE__ */ jsx2(Text, { color: theme.purple ?? "#a78bfa", bold: true, children: SUBTITLE }),
|
|
2126
|
+
/* @__PURE__ */ jsxs(Text, { color: theme.dim ?? "#7e8ea6", children: [
|
|
2127
|
+
" v",
|
|
2128
|
+
version
|
|
2129
|
+
] })
|
|
2100
2130
|
] }),
|
|
2101
|
-
/* @__PURE__ */
|
|
2102
|
-
/* @__PURE__ */
|
|
2103
|
-
/* @__PURE__ */
|
|
2131
|
+
/* @__PURE__ */ jsxs(Box, { marginLeft: 2, flexDirection: "column", justifyContent: "flex-end", children: [
|
|
2132
|
+
/* @__PURE__ */ jsx2(Text, { color: mascotColor, children: " \u2584\u2588\u2588\u2584" }),
|
|
2133
|
+
/* @__PURE__ */ jsx2(Text, { color: mascotColor, children: " \u2590\u2588\u25E6\u25E6\u2588\u258C" }),
|
|
2134
|
+
/* @__PURE__ */ jsx2(Text, { color: mascotColor, children: " \u2590\u258C\u25C6\u25C6\u2590\u258C" }),
|
|
2135
|
+
/* @__PURE__ */ jsx2(Text, { color: mascotColor, children: " \u2590\u258C\u2584\u2584\u2590\u258C" }),
|
|
2136
|
+
/* @__PURE__ */ jsx2(Text, { color: mascotColor, children: " \u2580\u2580\u2580\u2580" })
|
|
2104
2137
|
] })
|
|
2105
2138
|
] }),
|
|
2106
|
-
/* @__PURE__ */
|
|
2107
|
-
/* @__PURE__ */
|
|
2139
|
+
/* @__PURE__ */ jsx2(Text, { children: " " }),
|
|
2140
|
+
/* @__PURE__ */ jsxs(Text, { children: [
|
|
2108
2141
|
" ",
|
|
2109
|
-
bar.map((d, i) => /* @__PURE__ */
|
|
2142
|
+
bar.map((d, i) => /* @__PURE__ */ jsx2(Text, { color: d.color, children: d.char }, i))
|
|
2110
2143
|
] }),
|
|
2111
|
-
/* @__PURE__ */
|
|
2112
|
-
/* @__PURE__ */
|
|
2113
|
-
model ? /* @__PURE__ */
|
|
2144
|
+
/* @__PURE__ */ jsxs(Box, { marginTop: 1, marginLeft: 2, children: [
|
|
2145
|
+
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "model " }),
|
|
2146
|
+
model ? /* @__PURE__ */ jsx2(Text, { color: theme.cyan ?? "#5eead4", bold: true, children: model }) : /* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "--" })
|
|
2114
2147
|
] }),
|
|
2115
|
-
/* @__PURE__ */
|
|
2116
|
-
/* @__PURE__ */
|
|
2117
|
-
/* @__PURE__ */
|
|
2118
|
-
/* @__PURE__ */
|
|
2119
|
-
/* @__PURE__ */
|
|
2148
|
+
/* @__PURE__ */ jsxs(Box, { marginLeft: 2, children: [
|
|
2149
|
+
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "via " }),
|
|
2150
|
+
/* @__PURE__ */ jsx2(Text, { color: theme.text ?? "#e2e8f0", children: providerName }),
|
|
2151
|
+
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: " " }),
|
|
2152
|
+
/* @__PURE__ */ jsx2(Text, { color: statusColor, children: statusTag })
|
|
2120
2153
|
] }),
|
|
2121
|
-
/* @__PURE__ */
|
|
2122
|
-
/* @__PURE__ */
|
|
2123
|
-
/* @__PURE__ */
|
|
2154
|
+
/* @__PURE__ */ jsxs(Box, { marginLeft: 2, children: [
|
|
2155
|
+
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "cwd " }),
|
|
2156
|
+
/* @__PURE__ */ jsx2(Text, { color: theme.text ?? "#e2e8f0", children: cwd })
|
|
2124
2157
|
] }),
|
|
2125
|
-
/* @__PURE__ */
|
|
2158
|
+
/* @__PURE__ */ jsx2(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
2126
2159
|
" ",
|
|
2127
|
-
bar.map((d, i) => /* @__PURE__ */
|
|
2160
|
+
bar.map((d, i) => /* @__PURE__ */ jsx2(Text, { color: d.color, children: d.char }, i))
|
|
2128
2161
|
] }) })
|
|
2129
2162
|
] });
|
|
2130
2163
|
});
|
|
@@ -2132,8 +2165,8 @@ var Banner = memo2(function Banner2({ model, cwd, providerName, providerOnline }
|
|
|
2132
2165
|
// src/components/Messages.tsx
|
|
2133
2166
|
init_theme();
|
|
2134
2167
|
init_format();
|
|
2135
|
-
import { memo as
|
|
2136
|
-
import { Box as
|
|
2168
|
+
import { memo as memo2 } from "react";
|
|
2169
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
2137
2170
|
|
|
2138
2171
|
// src/utils/markdown.ts
|
|
2139
2172
|
function renderMarkdown(text) {
|
|
@@ -2197,31 +2230,31 @@ function renderInline(text) {
|
|
|
2197
2230
|
}
|
|
2198
2231
|
|
|
2199
2232
|
// src/components/Messages.tsx
|
|
2200
|
-
import { jsx as
|
|
2201
|
-
var Messages =
|
|
2202
|
-
return /* @__PURE__ */
|
|
2233
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
2234
|
+
var Messages = memo2(function Messages2({ messages }) {
|
|
2235
|
+
return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ jsx3(MessageRow, { message: msg }, msg.id)) });
|
|
2203
2236
|
});
|
|
2204
2237
|
function MessageRow({ message }) {
|
|
2205
2238
|
switch (message.role) {
|
|
2206
2239
|
case "user":
|
|
2207
|
-
return /* @__PURE__ */
|
|
2208
|
-
/* @__PURE__ */
|
|
2209
|
-
/* @__PURE__ */
|
|
2240
|
+
return /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, marginBottom: 1, children: [
|
|
2241
|
+
/* @__PURE__ */ jsx3(Text2, { color: theme.cyan, bold: true, children: "\u276F " }),
|
|
2242
|
+
/* @__PURE__ */ jsx3(Text2, { bold: true, color: theme.text, children: message.content })
|
|
2210
2243
|
] });
|
|
2211
2244
|
case "assistant": {
|
|
2212
2245
|
if (!message.content && message.toolCalls) return null;
|
|
2213
2246
|
if (!message.content) return null;
|
|
2214
2247
|
const rendered = renderMarkdown(message.content);
|
|
2215
|
-
return /* @__PURE__ */
|
|
2216
|
-
/* @__PURE__ */
|
|
2217
|
-
/* @__PURE__ */
|
|
2248
|
+
return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs2(Box2, { children: [
|
|
2249
|
+
/* @__PURE__ */ jsx3(Text2, { color: theme.cyan, children: "\u23BF " }),
|
|
2250
|
+
/* @__PURE__ */ jsx3(Text2, { wrap: "wrap", children: rendered })
|
|
2218
2251
|
] }) });
|
|
2219
2252
|
}
|
|
2220
2253
|
case "tool": {
|
|
2221
2254
|
const lines = message.content.split("\n");
|
|
2222
2255
|
const preview = lines.length > 8 ? lines.slice(0, 8).join("\n") + `
|
|
2223
2256
|
... (${lines.length - 8} more lines)` : message.content;
|
|
2224
|
-
return /* @__PURE__ */
|
|
2257
|
+
return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
2225
2258
|
"\u23BF ",
|
|
2226
2259
|
truncate(preview, 1200)
|
|
2227
2260
|
] }) });
|
|
@@ -2234,16 +2267,16 @@ function MessageRow({ message }) {
|
|
|
2234
2267
|
// src/components/ToolCall.tsx
|
|
2235
2268
|
init_theme();
|
|
2236
2269
|
init_format();
|
|
2237
|
-
import { Box as
|
|
2238
|
-
import { jsx as
|
|
2270
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
2271
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
2239
2272
|
function ActiveToolCall({ toolName, args }) {
|
|
2240
|
-
return /* @__PURE__ */
|
|
2241
|
-
/* @__PURE__ */
|
|
2242
|
-
/* @__PURE__ */
|
|
2273
|
+
return /* @__PURE__ */ jsxs3(Box3, { marginLeft: 2, children: [
|
|
2274
|
+
/* @__PURE__ */ jsx4(Text3, { color: theme.cyan, children: "..." }),
|
|
2275
|
+
/* @__PURE__ */ jsxs3(Text3, { bold: true, color: theme.yellow, children: [
|
|
2243
2276
|
" ",
|
|
2244
2277
|
toolName
|
|
2245
2278
|
] }),
|
|
2246
|
-
args ? /* @__PURE__ */
|
|
2279
|
+
args ? /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
2247
2280
|
" ",
|
|
2248
2281
|
formatToolArgs(args)
|
|
2249
2282
|
] }) : null
|
|
@@ -2255,15 +2288,15 @@ function ToolResultDisplay({ toolName, output, isError }) {
|
|
|
2255
2288
|
const lines = output.split("\n");
|
|
2256
2289
|
const preview = lines.length > 6 ? lines.slice(0, 6).join("\n") + `
|
|
2257
2290
|
... (${lines.length - 6} more lines)` : output;
|
|
2258
|
-
return /* @__PURE__ */
|
|
2259
|
-
/* @__PURE__ */
|
|
2260
|
-
/* @__PURE__ */
|
|
2291
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
|
|
2292
|
+
/* @__PURE__ */ jsxs3(Box3, { children: [
|
|
2293
|
+
/* @__PURE__ */ jsxs3(Text3, { color: iconColor, children: [
|
|
2261
2294
|
icon,
|
|
2262
2295
|
" "
|
|
2263
2296
|
] }),
|
|
2264
|
-
/* @__PURE__ */
|
|
2297
|
+
/* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.yellow, children: toolName })
|
|
2265
2298
|
] }),
|
|
2266
|
-
/* @__PURE__ */
|
|
2299
|
+
/* @__PURE__ */ jsx4(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
|
|
2267
2300
|
"\u23BF ",
|
|
2268
2301
|
truncate(preview, 1200)
|
|
2269
2302
|
] }) })
|
|
@@ -2284,9 +2317,9 @@ function formatToolArgs(args) {
|
|
|
2284
2317
|
// src/components/StatusLine.tsx
|
|
2285
2318
|
init_theme();
|
|
2286
2319
|
init_state();
|
|
2287
|
-
import { memo as
|
|
2288
|
-
import { Box as
|
|
2289
|
-
import { Fragment, jsx as
|
|
2320
|
+
import { memo as memo3 } from "react";
|
|
2321
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
2322
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2290
2323
|
function getContextLimit(model) {
|
|
2291
2324
|
const lower = model.toLowerCase();
|
|
2292
2325
|
if (lower.includes("llama3.1")) return 131072;
|
|
@@ -2297,47 +2330,47 @@ function getContextLimit(model) {
|
|
|
2297
2330
|
if (lower.includes("deepseek")) return 32768;
|
|
2298
2331
|
return 8192;
|
|
2299
2332
|
}
|
|
2300
|
-
var StatusLine =
|
|
2333
|
+
var StatusLine = memo3(function StatusLine2({ model, messageCount, tokenEstimate, isStreaming }) {
|
|
2301
2334
|
const state2 = getAppState();
|
|
2302
2335
|
const contextLimit = getContextLimit(model);
|
|
2303
2336
|
const usage = Math.min(tokenEstimate / contextLimit, 1);
|
|
2304
2337
|
const pct = (usage * 100).toFixed(0);
|
|
2305
2338
|
const usageColor = usage > 0.8 ? theme.pink : usage > 0.5 ? theme.yellow : theme.cyan;
|
|
2306
2339
|
const activeTasks = state2.tasks.filter((t) => t.status === "in_progress").length;
|
|
2307
|
-
return /* @__PURE__ */
|
|
2308
|
-
/* @__PURE__ */
|
|
2340
|
+
return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
2341
|
+
/* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
|
|
2309
2342
|
"\u2500".repeat(2),
|
|
2310
2343
|
" "
|
|
2311
2344
|
] }),
|
|
2312
|
-
/* @__PURE__ */
|
|
2313
|
-
/* @__PURE__ */
|
|
2314
|
-
/* @__PURE__ */
|
|
2345
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.cyan, bold: true, children: model ? model.split(":")[0] : "--" }),
|
|
2346
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
|
|
2347
|
+
/* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
|
|
2315
2348
|
messageCount,
|
|
2316
2349
|
" msgs"
|
|
2317
2350
|
] }),
|
|
2318
|
-
/* @__PURE__ */
|
|
2319
|
-
/* @__PURE__ */
|
|
2351
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
|
|
2352
|
+
/* @__PURE__ */ jsxs4(Text4, { color: usageColor, children: [
|
|
2320
2353
|
"ctx ",
|
|
2321
2354
|
pct,
|
|
2322
2355
|
"%"
|
|
2323
2356
|
] }),
|
|
2324
|
-
state2.planMode ? /* @__PURE__ */
|
|
2325
|
-
/* @__PURE__ */
|
|
2326
|
-
/* @__PURE__ */
|
|
2357
|
+
state2.planMode ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
2358
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
|
|
2359
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.yellow, bold: true, children: "PLAN" })
|
|
2327
2360
|
] }) : null,
|
|
2328
|
-
activeTasks > 0 ? /* @__PURE__ */
|
|
2329
|
-
/* @__PURE__ */
|
|
2330
|
-
/* @__PURE__ */
|
|
2361
|
+
activeTasks > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
2362
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
|
|
2363
|
+
/* @__PURE__ */ jsxs4(Text4, { color: theme.green, children: [
|
|
2331
2364
|
activeTasks,
|
|
2332
2365
|
" task",
|
|
2333
2366
|
activeTasks > 1 ? "s" : ""
|
|
2334
2367
|
] })
|
|
2335
2368
|
] }) : null,
|
|
2336
|
-
isStreaming ? /* @__PURE__ */
|
|
2337
|
-
/* @__PURE__ */
|
|
2338
|
-
/* @__PURE__ */
|
|
2369
|
+
isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
|
|
2370
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
|
|
2371
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.cyan, children: "streaming" })
|
|
2339
2372
|
] }) : null,
|
|
2340
|
-
/* @__PURE__ */
|
|
2373
|
+
/* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
|
|
2341
2374
|
" ",
|
|
2342
2375
|
"\u2500".repeat(2)
|
|
2343
2376
|
] })
|
|
@@ -2346,8 +2379,8 @@ var StatusLine = memo4(function StatusLine2({ model, messageCount, tokenEstimate
|
|
|
2346
2379
|
|
|
2347
2380
|
// src/components/UserInput.tsx
|
|
2348
2381
|
init_theme();
|
|
2349
|
-
import { useState, useCallback, memo as
|
|
2350
|
-
import { Box as
|
|
2382
|
+
import { useState, useCallback, memo as memo4 } from "react";
|
|
2383
|
+
import { Box as Box5, Text as Text5, useInput } from "ink";
|
|
2351
2384
|
import TextInput from "ink-text-input";
|
|
2352
2385
|
|
|
2353
2386
|
// src/commands/help.ts
|
|
@@ -2435,12 +2468,15 @@ import { nanoid } from "nanoid";
|
|
|
2435
2468
|
var compactCommand = {
|
|
2436
2469
|
name: "compact",
|
|
2437
2470
|
description: "Compress conversation history to save context",
|
|
2438
|
-
async execute(
|
|
2471
|
+
async execute(args, context) {
|
|
2439
2472
|
if (context.messages.length < 4) {
|
|
2440
2473
|
return { output: "Conversation is too short to compact.", silent: true };
|
|
2441
2474
|
}
|
|
2442
2475
|
const transcript = context.messages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map((m) => `${m.role}: ${m.content}`).join("\n\n");
|
|
2443
|
-
const
|
|
2476
|
+
const focus = args.trim();
|
|
2477
|
+
const summaryPrompt = focus ? `Summarize this conversation, focusing especially on: ${focus}. Capture key decisions, code changes, file paths, and current state:
|
|
2478
|
+
|
|
2479
|
+
${transcript}` : `Summarize this conversation concisely. Capture all key decisions, code changes, file paths mentioned, and current task state. Be thorough but brief:
|
|
2444
2480
|
|
|
2445
2481
|
${transcript}`;
|
|
2446
2482
|
try {
|
|
@@ -2514,7 +2550,7 @@ var contextCommand = {
|
|
|
2514
2550
|
}
|
|
2515
2551
|
totalTokens += tokens;
|
|
2516
2552
|
}
|
|
2517
|
-
const sysTokens = 2e3;
|
|
2553
|
+
const sysTokens = context.systemPrompt ? estimateTokens(context.systemPrompt) : 2e3;
|
|
2518
2554
|
totalTokens += sysTokens;
|
|
2519
2555
|
const usage = totalTokens / contextLimit;
|
|
2520
2556
|
const barWidth = 40;
|
|
@@ -2528,7 +2564,7 @@ var contextCommand = {
|
|
|
2528
2564
|
` ~${totalTokens.toLocaleString()} / ${contextLimit.toLocaleString()} tokens`,
|
|
2529
2565
|
"",
|
|
2530
2566
|
" Breakdown:",
|
|
2531
|
-
` System prompt:
|
|
2567
|
+
` System prompt: ${context.systemPrompt ? "" : "~"}${sysTokens.toLocaleString()} tokens`,
|
|
2532
2568
|
...breakdown.map(
|
|
2533
2569
|
(b) => ` ${b.role.padEnd(12)} ${b.count} msgs, ~${b.tokens.toLocaleString()} tokens`
|
|
2534
2570
|
),
|
|
@@ -2618,6 +2654,13 @@ async function saveSession(id, messages, model, cwd) {
|
|
|
2618
2654
|
};
|
|
2619
2655
|
await writeFile2(join2(SESSIONS_DIR, `${id}.json`), JSON.stringify(data, null, 2), "utf-8");
|
|
2620
2656
|
}
|
|
2657
|
+
async function renameSession(id, title) {
|
|
2658
|
+
const session = await loadSession(id);
|
|
2659
|
+
if (!session) return;
|
|
2660
|
+
session.title = title;
|
|
2661
|
+
session.updatedAt = Date.now();
|
|
2662
|
+
await writeFile2(join2(SESSIONS_DIR, `${id}.json`), JSON.stringify(session, null, 2), "utf-8");
|
|
2663
|
+
}
|
|
2621
2664
|
async function loadSession(id) {
|
|
2622
2665
|
try {
|
|
2623
2666
|
const raw = await readFile2(join2(SESSIONS_DIR, `${id}.json`), "utf-8");
|
|
@@ -3280,143 +3323,36 @@ var providerCommand = {
|
|
|
3280
3323
|
}
|
|
3281
3324
|
};
|
|
3282
3325
|
|
|
3283
|
-
// src/
|
|
3284
|
-
var
|
|
3285
|
-
name: "
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
var lastDecayTime = Date.now();
|
|
3295
|
-
function getFoxStats() {
|
|
3296
|
-
decay();
|
|
3297
|
-
return { ...stats };
|
|
3298
|
-
}
|
|
3299
|
-
function setFoxName(name) {
|
|
3300
|
-
stats.name = name;
|
|
3301
|
-
}
|
|
3302
|
-
function petFox() {
|
|
3303
|
-
decay();
|
|
3304
|
-
stats.happiness = Math.min(100, stats.happiness + 15);
|
|
3305
|
-
stats.timesPetted++;
|
|
3306
|
-
const reactions = [
|
|
3307
|
-
`${stats.name} nuzzles your hand.`,
|
|
3308
|
-
`${stats.name} purrs softly.`,
|
|
3309
|
-
`${stats.name}'s tail wags happily.`,
|
|
3310
|
-
`${stats.name} leans into the pets.`,
|
|
3311
|
-
`${stats.name} rolls over for belly rubs.`,
|
|
3312
|
-
`${stats.name} makes a happy chirping sound.`,
|
|
3313
|
-
`${stats.name} bumps your hand with their nose.`
|
|
3314
|
-
];
|
|
3315
|
-
if (stats.happiness >= 95) {
|
|
3316
|
-
return `${stats.name} is absolutely overjoyed! Maximum floof achieved.`;
|
|
3317
|
-
}
|
|
3318
|
-
return reactions[stats.timesPetted % reactions.length];
|
|
3319
|
-
}
|
|
3320
|
-
function feedFox() {
|
|
3321
|
-
decay();
|
|
3322
|
-
stats.hunger = Math.min(100, stats.hunger + 25);
|
|
3323
|
-
stats.happiness = Math.min(100, stats.happiness + 5);
|
|
3324
|
-
stats.timesFed++;
|
|
3325
|
-
const foods = [
|
|
3326
|
-
"a small cookie",
|
|
3327
|
-
"some berries",
|
|
3328
|
-
"a piece of fish",
|
|
3329
|
-
"a tiny sandwich",
|
|
3330
|
-
"some trail mix",
|
|
3331
|
-
"a warm dumpling",
|
|
3332
|
-
"a bit of cheese"
|
|
3333
|
-
];
|
|
3334
|
-
const food = foods[stats.timesFed % foods.length];
|
|
3335
|
-
if (stats.hunger >= 95) {
|
|
3336
|
-
return `${stats.name} nibbles ${food} contentedly. Completely stuffed!`;
|
|
3337
|
-
}
|
|
3338
|
-
return `${stats.name} happily munches on ${food}.`;
|
|
3339
|
-
}
|
|
3340
|
-
function restFox() {
|
|
3341
|
-
decay();
|
|
3342
|
-
stats.energy = Math.min(100, stats.energy + 30);
|
|
3343
|
-
stats.happiness = Math.min(100, stats.happiness + 5);
|
|
3344
|
-
return `${stats.name} curls up for a quick nap... Energy restored!`;
|
|
3345
|
-
}
|
|
3346
|
-
function decay() {
|
|
3347
|
-
const now = Date.now();
|
|
3348
|
-
const elapsed = (now - lastDecayTime) / 6e4;
|
|
3349
|
-
if (elapsed < 1) return;
|
|
3350
|
-
lastDecayTime = now;
|
|
3351
|
-
const minutes = Math.floor(elapsed);
|
|
3352
|
-
stats.hunger = Math.max(0, stats.hunger - minutes * 1.5);
|
|
3353
|
-
stats.energy = Math.max(0, stats.energy - minutes * 0.5);
|
|
3354
|
-
if (stats.hunger < 30) stats.happiness = Math.max(0, stats.happiness - minutes * 1);
|
|
3355
|
-
if (stats.energy < 20) stats.happiness = Math.max(0, stats.happiness - minutes * 0.5);
|
|
3356
|
-
}
|
|
3357
|
-
|
|
3358
|
-
// src/commands/fox.ts
|
|
3359
|
-
var petCommand = {
|
|
3360
|
-
name: "pet",
|
|
3361
|
-
description: "Pet your fox companion",
|
|
3362
|
-
async execute(_args, _context) {
|
|
3363
|
-
const reaction = petFox();
|
|
3364
|
-
return { output: reaction, silent: true, foxMood: "pet" };
|
|
3365
|
-
}
|
|
3366
|
-
};
|
|
3367
|
-
var feedCommand = {
|
|
3368
|
-
name: "feed",
|
|
3369
|
-
description: "Feed your fox companion",
|
|
3370
|
-
async execute(_args, _context) {
|
|
3371
|
-
const reaction = feedFox();
|
|
3372
|
-
return { output: reaction, silent: true, foxMood: "eating" };
|
|
3373
|
-
}
|
|
3374
|
-
};
|
|
3375
|
-
var restCommand = {
|
|
3376
|
-
name: "rest",
|
|
3377
|
-
aliases: ["sleep", "nap"],
|
|
3378
|
-
description: "Let your fox take a nap",
|
|
3379
|
-
async execute(_args, _context) {
|
|
3380
|
-
const reaction = restFox();
|
|
3381
|
-
return { output: reaction, silent: true, foxMood: "sleeping" };
|
|
3326
|
+
// src/commands/rename.ts
|
|
3327
|
+
var renameCommand = {
|
|
3328
|
+
name: "rename",
|
|
3329
|
+
description: "Rename the current session (usage: /rename <title>)",
|
|
3330
|
+
async execute(args, context) {
|
|
3331
|
+
const title = args.trim();
|
|
3332
|
+
if (!title) {
|
|
3333
|
+
return { output: "Usage: /rename <new session title>", silent: true };
|
|
3334
|
+
}
|
|
3335
|
+
await renameSession(context.sessionId, title);
|
|
3336
|
+
return { output: `Session renamed to: ${title}`, silent: true };
|
|
3382
3337
|
}
|
|
3383
3338
|
};
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3339
|
+
|
|
3340
|
+
// src/commands/plan.ts
|
|
3341
|
+
init_state();
|
|
3342
|
+
var planCommand = {
|
|
3343
|
+
name: "plan",
|
|
3344
|
+
description: "Enter plan mode to explore and design before implementing (usage: /plan <description>)",
|
|
3388
3345
|
async execute(args, _context) {
|
|
3389
|
-
const
|
|
3390
|
-
if (
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
const empty = 20 - filled;
|
|
3399
|
-
return "\x1B[36m" + "#".repeat(filled) + "\x1B[0m\x1B[2m" + "-".repeat(empty) + "\x1B[0m";
|
|
3346
|
+
const description = args.trim();
|
|
3347
|
+
if (!description) {
|
|
3348
|
+
return { output: "Usage: /plan <what you want to plan>", silent: true };
|
|
3349
|
+
}
|
|
3350
|
+
updateAppState((s) => ({ ...s, planMode: true }));
|
|
3351
|
+
return {
|
|
3352
|
+
output: `Enter plan mode. Explore the codebase and design a plan for: ${description}`,
|
|
3353
|
+
silent: false,
|
|
3354
|
+
foxMood: "thinking"
|
|
3400
3355
|
};
|
|
3401
|
-
const age = Math.floor((Date.now() - stats2.createdAt) / 6e4);
|
|
3402
|
-
const ageStr = age < 60 ? `${age}m` : `${Math.floor(age / 60)}h ${age % 60}m`;
|
|
3403
|
-
const lines = [
|
|
3404
|
-
`\x1B[1m\x1B[36m${stats2.name}\x1B[0m`,
|
|
3405
|
-
"",
|
|
3406
|
-
` Happiness [${bar(stats2.happiness)}] ${Math.round(stats2.happiness)}%`,
|
|
3407
|
-
` Hunger [${bar(stats2.hunger)}] ${Math.round(stats2.hunger)}%`,
|
|
3408
|
-
` Energy [${bar(stats2.energy)}] ${Math.round(stats2.energy)}%`,
|
|
3409
|
-
"",
|
|
3410
|
-
` Times petted: ${stats2.timesPetted}`,
|
|
3411
|
-
` Times fed: ${stats2.timesFed}`,
|
|
3412
|
-
` Age: ${ageStr}`,
|
|
3413
|
-
"",
|
|
3414
|
-
"\x1B[2m /pet \u2014 Pet your fox",
|
|
3415
|
-
" /feed \u2014 Feed your fox",
|
|
3416
|
-
" /rest \u2014 Let your fox nap",
|
|
3417
|
-
" /fox name <name> \u2014 Rename\x1B[0m"
|
|
3418
|
-
];
|
|
3419
|
-
return { output: lines.join("\n"), silent: true };
|
|
3420
3356
|
}
|
|
3421
3357
|
};
|
|
3422
3358
|
|
|
@@ -3443,10 +3379,8 @@ var COMMANDS = [
|
|
|
3443
3379
|
filesCommand,
|
|
3444
3380
|
briefCommand,
|
|
3445
3381
|
providerCommand,
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
restCommand,
|
|
3449
|
-
foxCommand
|
|
3382
|
+
renameCommand,
|
|
3383
|
+
planCommand
|
|
3450
3384
|
];
|
|
3451
3385
|
function getCommands() {
|
|
3452
3386
|
return COMMANDS;
|
|
@@ -3482,8 +3416,8 @@ function getCommandNames() {
|
|
|
3482
3416
|
}
|
|
3483
3417
|
|
|
3484
3418
|
// src/components/UserInput.tsx
|
|
3485
|
-
import { Fragment as Fragment2, jsx as
|
|
3486
|
-
var UserInput =
|
|
3419
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
3420
|
+
var UserInput = memo4(function UserInput2({ value, onChange, onSubmit, disabled, history }) {
|
|
3487
3421
|
const [historyIdx, setHistoryIdx] = useState(-1);
|
|
3488
3422
|
const [suggestion, setSuggestion] = useState("");
|
|
3489
3423
|
useInput((_input, key) => {
|
|
@@ -3531,20 +3465,20 @@ var UserInput = memo5(function UserInput2({ value, onChange, onSubmit, disabled,
|
|
|
3531
3465
|
const borderColor = disabled ? theme.dim : isBash ? theme.yellow : isCommand ? theme.purple : theme.cyan;
|
|
3532
3466
|
const promptChar = isBash ? "!" : "\u276F";
|
|
3533
3467
|
const promptColor = isBash ? theme.yellow : theme.cyan;
|
|
3534
|
-
return /* @__PURE__ */
|
|
3535
|
-
|
|
3468
|
+
return /* @__PURE__ */ jsxs5(
|
|
3469
|
+
Box5,
|
|
3536
3470
|
{
|
|
3537
3471
|
borderStyle: "round",
|
|
3538
3472
|
borderColor,
|
|
3539
3473
|
paddingLeft: 1,
|
|
3540
3474
|
paddingRight: 1,
|
|
3541
3475
|
children: [
|
|
3542
|
-
/* @__PURE__ */
|
|
3476
|
+
/* @__PURE__ */ jsxs5(Text5, { color: disabled ? theme.dim : promptColor, bold: true, children: [
|
|
3543
3477
|
promptChar,
|
|
3544
3478
|
" "
|
|
3545
3479
|
] }),
|
|
3546
|
-
disabled ? /* @__PURE__ */
|
|
3547
|
-
/* @__PURE__ */
|
|
3480
|
+
disabled ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: "..." }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
3481
|
+
/* @__PURE__ */ jsx6(
|
|
3548
3482
|
TextInput,
|
|
3549
3483
|
{
|
|
3550
3484
|
value,
|
|
@@ -3558,7 +3492,7 @@ var UserInput = memo5(function UserInput2({ value, onChange, onSubmit, disabled,
|
|
|
3558
3492
|
}
|
|
3559
3493
|
}
|
|
3560
3494
|
),
|
|
3561
|
-
suggestion ? /* @__PURE__ */
|
|
3495
|
+
suggestion ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: suggestion }) : null
|
|
3562
3496
|
] })
|
|
3563
3497
|
]
|
|
3564
3498
|
}
|
|
@@ -3572,8 +3506,9 @@ init_system_prompt();
|
|
|
3572
3506
|
init_providers();
|
|
3573
3507
|
init_tools();
|
|
3574
3508
|
init_bash();
|
|
3509
|
+
init_hooks();
|
|
3575
3510
|
init_theme();
|
|
3576
|
-
import { jsx as
|
|
3511
|
+
import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3577
3512
|
function getContextLimit3(model) {
|
|
3578
3513
|
const lower = model.toLowerCase();
|
|
3579
3514
|
if (lower.includes("llama3.1")) return 131072;
|
|
@@ -3585,10 +3520,10 @@ function getContextLimit3(model) {
|
|
|
3585
3520
|
return 8192;
|
|
3586
3521
|
}
|
|
3587
3522
|
function REPL({ initialPrompt }) {
|
|
3588
|
-
const { model: initialModel, systemPromptOverride } = useDarkfooContext();
|
|
3523
|
+
const { model: initialModel, systemPromptOverride, maxTurns, initialMessages, initialSessionId } = useDarkfooContext();
|
|
3589
3524
|
const { exit } = useApp();
|
|
3590
3525
|
const [model, setModel] = useState2(initialModel);
|
|
3591
|
-
const [messages, setMessages] = useState2([]);
|
|
3526
|
+
const [messages, setMessages] = useState2(initialMessages ?? []);
|
|
3592
3527
|
const [inputValue, setInputValue] = useState2("");
|
|
3593
3528
|
const [isStreaming, setIsStreaming] = useState2(false);
|
|
3594
3529
|
const [streamingText, setStreamingText] = useState2("");
|
|
@@ -3598,11 +3533,11 @@ function REPL({ initialPrompt }) {
|
|
|
3598
3533
|
const [inputHistory, setInputHistory] = useState2([]);
|
|
3599
3534
|
const [tokenCounts, setTokenCounts] = useState2({ input: 0, output: 0 });
|
|
3600
3535
|
const [systemPrompt, setSystemPrompt] = useState2("");
|
|
3601
|
-
const [
|
|
3536
|
+
const [mascotMood, setMascotMood] = useState2("idle");
|
|
3602
3537
|
const [providerOnline, setProviderOnline] = useState2(true);
|
|
3603
3538
|
const abortRef = useRef(null);
|
|
3604
3539
|
const hasRun = useRef(false);
|
|
3605
|
-
const sessionId = useRef(createSessionId());
|
|
3540
|
+
const sessionId = useRef(initialSessionId ?? createSessionId());
|
|
3606
3541
|
const messagesRef = useRef(messages);
|
|
3607
3542
|
messagesRef.current = messages;
|
|
3608
3543
|
const modelRef = useRef(model);
|
|
@@ -3618,6 +3553,10 @@ function REPL({ initialPrompt }) {
|
|
|
3618
3553
|
buildSystemPrompt(tools, cwd).then(setSystemPrompt);
|
|
3619
3554
|
}
|
|
3620
3555
|
}, []);
|
|
3556
|
+
useEffect(() => {
|
|
3557
|
+
executeHooks("session_start", { cwd }).catch(() => {
|
|
3558
|
+
});
|
|
3559
|
+
}, []);
|
|
3621
3560
|
useEffect(() => {
|
|
3622
3561
|
const provider = getProvider();
|
|
3623
3562
|
provider.healthCheck().then((ok) => {
|
|
@@ -3655,12 +3594,14 @@ function REPL({ initialPrompt }) {
|
|
|
3655
3594
|
messages,
|
|
3656
3595
|
model,
|
|
3657
3596
|
cwd,
|
|
3597
|
+
sessionId: sessionId.current,
|
|
3658
3598
|
setModel,
|
|
3659
3599
|
clearMessages,
|
|
3660
3600
|
exit,
|
|
3661
|
-
tokenCounts
|
|
3601
|
+
tokenCounts,
|
|
3602
|
+
systemPrompt
|
|
3662
3603
|
});
|
|
3663
|
-
commandContextRef.current = { messages, model, cwd, setModel, clearMessages, exit, tokenCounts };
|
|
3604
|
+
commandContextRef.current = { messages, model, cwd, sessionId: sessionId.current, setModel, clearMessages, exit, tokenCounts, systemPrompt };
|
|
3664
3605
|
const addToHistory = useCallback2((input) => {
|
|
3665
3606
|
setInputHistory((prev) => {
|
|
3666
3607
|
const filtered = prev.filter((h) => h !== input);
|
|
@@ -3680,42 +3621,45 @@ function REPL({ initialPrompt }) {
|
|
|
3680
3621
|
setStreamingText("");
|
|
3681
3622
|
setToolResults([]);
|
|
3682
3623
|
setCommandOutput(null);
|
|
3683
|
-
|
|
3624
|
+
setMascotMood("thinking");
|
|
3684
3625
|
const controller = new AbortController();
|
|
3685
3626
|
abortRef.current = controller;
|
|
3686
3627
|
const allMessages = [...messagesRef.current, userMsg];
|
|
3687
3628
|
const currentModel = modelRef.current;
|
|
3688
3629
|
const currentSystemPrompt = systemPromptRef.current;
|
|
3689
|
-
setTokenCounts((prev) => ({
|
|
3690
|
-
...prev,
|
|
3691
|
-
input: prev.input + Math.ceil(userMessage.length / 4)
|
|
3692
|
-
}));
|
|
3693
3630
|
try {
|
|
3694
3631
|
for await (const event of query({
|
|
3695
3632
|
model: currentModel,
|
|
3696
3633
|
messages: allMessages,
|
|
3697
3634
|
tools,
|
|
3698
3635
|
systemPrompt: currentSystemPrompt,
|
|
3699
|
-
signal: controller.signal
|
|
3636
|
+
signal: controller.signal,
|
|
3637
|
+
maxTurns
|
|
3700
3638
|
})) {
|
|
3701
3639
|
if (controller.signal.aborted) break;
|
|
3702
3640
|
switch (event.type) {
|
|
3703
3641
|
case "text_delta":
|
|
3704
3642
|
setStreamingText((prev) => prev + event.text);
|
|
3705
|
-
|
|
3643
|
+
setMascotMood("idle");
|
|
3706
3644
|
break;
|
|
3707
3645
|
case "tool_call":
|
|
3708
3646
|
setActiveTool({ name: event.toolCall.function.name, args: event.toolCall.function.arguments });
|
|
3709
|
-
|
|
3647
|
+
setMascotMood("working");
|
|
3710
3648
|
break;
|
|
3711
3649
|
case "tool_result":
|
|
3712
3650
|
setActiveTool(null);
|
|
3713
|
-
|
|
3651
|
+
setMascotMood(event.isError ? "error" : "working");
|
|
3714
3652
|
setToolResults((prev) => [
|
|
3715
3653
|
...prev,
|
|
3716
3654
|
{ id: nanoid6(), toolName: event.toolName, output: event.output, isError: event.isError }
|
|
3717
3655
|
]);
|
|
3718
3656
|
break;
|
|
3657
|
+
case "usage":
|
|
3658
|
+
setTokenCounts((prev) => ({
|
|
3659
|
+
input: prev.input + event.inputTokens,
|
|
3660
|
+
output: prev.output + event.outputTokens
|
|
3661
|
+
}));
|
|
3662
|
+
break;
|
|
3719
3663
|
case "assistant_message":
|
|
3720
3664
|
setMessages((prev) => {
|
|
3721
3665
|
const updated = [...prev, event.message];
|
|
@@ -3723,14 +3667,10 @@ function REPL({ initialPrompt }) {
|
|
|
3723
3667
|
});
|
|
3724
3668
|
return updated;
|
|
3725
3669
|
});
|
|
3726
|
-
setTokenCounts((prev) => ({
|
|
3727
|
-
...prev,
|
|
3728
|
-
output: prev.output + Math.ceil((event.message.content?.length ?? 0) / 4)
|
|
3729
|
-
}));
|
|
3730
3670
|
setStreamingText("");
|
|
3731
3671
|
break;
|
|
3732
3672
|
case "error":
|
|
3733
|
-
|
|
3673
|
+
setMascotMood("error");
|
|
3734
3674
|
setMessages((prev) => [
|
|
3735
3675
|
...prev,
|
|
3736
3676
|
{ id: nanoid6(), role: "assistant", content: `Error: ${event.error}`, timestamp: Date.now() }
|
|
@@ -3751,8 +3691,8 @@ function REPL({ initialPrompt }) {
|
|
|
3751
3691
|
setStreamingText("");
|
|
3752
3692
|
setActiveTool(null);
|
|
3753
3693
|
setToolResults([]);
|
|
3754
|
-
|
|
3755
|
-
setTimeout(() =>
|
|
3694
|
+
setMascotMood((prev) => prev === "error" ? "error" : "success");
|
|
3695
|
+
setTimeout(() => setMascotMood("idle"), 2e3);
|
|
3756
3696
|
abortRef.current = null;
|
|
3757
3697
|
}
|
|
3758
3698
|
},
|
|
@@ -3775,8 +3715,8 @@ function REPL({ initialPrompt }) {
|
|
|
3775
3715
|
setMessages(result.replaceMessages);
|
|
3776
3716
|
}
|
|
3777
3717
|
if (result.foxMood) {
|
|
3778
|
-
|
|
3779
|
-
setTimeout(() =>
|
|
3718
|
+
setMascotMood(result.foxMood);
|
|
3719
|
+
setTimeout(() => setMascotMood("idle"), 3e3);
|
|
3780
3720
|
}
|
|
3781
3721
|
if (result.exit) return;
|
|
3782
3722
|
if (!result.silent && result.output) {
|
|
@@ -3862,35 +3802,32 @@ ${result.content}
|
|
|
3862
3802
|
}
|
|
3863
3803
|
}
|
|
3864
3804
|
});
|
|
3865
|
-
return /* @__PURE__ */
|
|
3866
|
-
/* @__PURE__ */
|
|
3867
|
-
/* @__PURE__ */
|
|
3868
|
-
commandOutput ? /* @__PURE__ */
|
|
3869
|
-
toolResults.map((tr) => /* @__PURE__ */
|
|
3870
|
-
activeTool ? /* @__PURE__ */
|
|
3871
|
-
isStreaming && streamingText ? /* @__PURE__ */
|
|
3872
|
-
/* @__PURE__ */
|
|
3873
|
-
/* @__PURE__ */
|
|
3874
|
-
/* @__PURE__ */
|
|
3805
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", padding: 1, children: [
|
|
3806
|
+
/* @__PURE__ */ jsx7(Banner, { model, cwd, providerName: getActiveProviderName(), providerOnline, mood: mascotMood }),
|
|
3807
|
+
/* @__PURE__ */ jsx7(Messages, { messages }),
|
|
3808
|
+
commandOutput ? /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx7(Text6, { children: commandOutput }) }) : null,
|
|
3809
|
+
toolResults.map((tr) => /* @__PURE__ */ jsx7(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
|
|
3810
|
+
activeTool ? /* @__PURE__ */ jsx7(ActiveToolCall, { toolName: activeTool.name, args: activeTool.args }) : null,
|
|
3811
|
+
isStreaming && streamingText ? /* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, children: [
|
|
3812
|
+
/* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: "\u23BF " }),
|
|
3813
|
+
/* @__PURE__ */ jsx7(Text6, { color: theme.text, wrap: "wrap", children: streamingText }),
|
|
3814
|
+
/* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: " ..." })
|
|
3875
3815
|
] }) : null,
|
|
3876
|
-
isStreaming && !streamingText && !activeTool ? /* @__PURE__ */
|
|
3877
|
-
/* @__PURE__ */
|
|
3878
|
-
/* @__PURE__ */
|
|
3816
|
+
isStreaming && !streamingText && !activeTool ? /* @__PURE__ */ jsxs6(Box6, { marginLeft: 2, children: [
|
|
3817
|
+
/* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: "..." }),
|
|
3818
|
+
/* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: " Thinking" })
|
|
3879
3819
|
] }) : null,
|
|
3880
|
-
/* @__PURE__ */
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
/* @__PURE__ */ jsx8(Box7, { marginLeft: 1, children: /* @__PURE__ */ jsx8(Fox, { mood: foxMood }) })
|
|
3892
|
-
] }),
|
|
3893
|
-
/* @__PURE__ */ jsx8(
|
|
3820
|
+
/* @__PURE__ */ jsx7(
|
|
3821
|
+
UserInput,
|
|
3822
|
+
{
|
|
3823
|
+
value: inputValue,
|
|
3824
|
+
onChange: setInputValue,
|
|
3825
|
+
onSubmit: handleSubmit,
|
|
3826
|
+
disabled: isStreaming,
|
|
3827
|
+
history: inputHistory
|
|
3828
|
+
}
|
|
3829
|
+
),
|
|
3830
|
+
/* @__PURE__ */ jsx7(
|
|
3894
3831
|
StatusLine,
|
|
3895
3832
|
{
|
|
3896
3833
|
model,
|
|
@@ -3904,10 +3841,15 @@ ${result.content}
|
|
|
3904
3841
|
|
|
3905
3842
|
// src/main.tsx
|
|
3906
3843
|
init_providers();
|
|
3907
|
-
import { jsx as
|
|
3844
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
3908
3845
|
var program = new Command();
|
|
3909
|
-
program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.
|
|
3846
|
+
program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.4.0").option("-m, --model <model>", "Model to use", "llama3.1:8b").option("-p, --prompt <prompt>", "Run a single prompt (non-interactive)").option("-c, --continue", "Resume the most recent session").option("--resume <id>", "Resume a specific session by ID").option("--max-turns <n>", "Maximum tool-use turns per query", "30").option("--debug", "Enable debug logging to stderr").option("--output-format <format>", "Output format for non-interactive mode (text, json)").option("--provider <name>", "LLM provider backend (ollama, llama-cpp, vllm, tgi, etc.)").option("--system-prompt <prompt>", "Override the system prompt").action(async (options) => {
|
|
3910
3847
|
const { model, prompt, provider, systemPrompt } = options;
|
|
3848
|
+
if (options.debug) {
|
|
3849
|
+
const { setDebugMode: setDebugMode2 } = await Promise.resolve().then(() => (init_debug(), debug_exports));
|
|
3850
|
+
setDebugMode2(true);
|
|
3851
|
+
}
|
|
3852
|
+
const maxTurns = parseInt(options.maxTurns, 10) || 30;
|
|
3911
3853
|
await loadProviderSettings();
|
|
3912
3854
|
if (provider) {
|
|
3913
3855
|
try {
|
|
@@ -3956,19 +3898,35 @@ program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assista
|
|
|
3956
3898
|
controller.abort();
|
|
3957
3899
|
process.exit(0);
|
|
3958
3900
|
});
|
|
3901
|
+
const jsonMode = options.outputFormat === "json";
|
|
3902
|
+
let jsonContent = "";
|
|
3903
|
+
const jsonToolCalls = [];
|
|
3904
|
+
const jsonToolResults = [];
|
|
3959
3905
|
for await (const event of query2({
|
|
3960
3906
|
model: resolvedModel,
|
|
3961
3907
|
messages: [userMsg],
|
|
3962
3908
|
tools,
|
|
3963
3909
|
systemPrompt: sysPrompt,
|
|
3964
|
-
signal: controller.signal
|
|
3910
|
+
signal: controller.signal,
|
|
3911
|
+
maxTurns
|
|
3965
3912
|
})) {
|
|
3966
3913
|
switch (event.type) {
|
|
3967
3914
|
case "text_delta":
|
|
3968
|
-
|
|
3915
|
+
if (jsonMode) {
|
|
3916
|
+
jsonContent += event.text;
|
|
3917
|
+
} else {
|
|
3918
|
+
process.stdout.write(event.text);
|
|
3919
|
+
}
|
|
3920
|
+
break;
|
|
3921
|
+
case "tool_call":
|
|
3922
|
+
if (jsonMode) {
|
|
3923
|
+
jsonToolCalls.push({ name: event.toolCall.function.name, arguments: event.toolCall.function.arguments });
|
|
3924
|
+
}
|
|
3969
3925
|
break;
|
|
3970
3926
|
case "tool_result":
|
|
3971
|
-
if (
|
|
3927
|
+
if (jsonMode) {
|
|
3928
|
+
jsonToolResults.push({ tool: event.toolName, output: event.output, isError: event.isError });
|
|
3929
|
+
} else if (event.isError) {
|
|
3972
3930
|
process.stderr.write(`
|
|
3973
3931
|
[${event.toolName}] Error: ${event.output}
|
|
3974
3932
|
`);
|
|
@@ -3981,11 +3939,56 @@ Error: ${event.error}
|
|
|
3981
3939
|
break;
|
|
3982
3940
|
}
|
|
3983
3941
|
}
|
|
3984
|
-
|
|
3942
|
+
if (jsonMode) {
|
|
3943
|
+
const result = { role: "assistant", content: jsonContent };
|
|
3944
|
+
if (jsonToolCalls.length > 0) result.toolCalls = jsonToolCalls;
|
|
3945
|
+
if (jsonToolResults.length > 0) result.toolResults = jsonToolResults;
|
|
3946
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
3947
|
+
} else {
|
|
3948
|
+
process.stdout.write("\n");
|
|
3949
|
+
}
|
|
3985
3950
|
process.exit(0);
|
|
3986
3951
|
}
|
|
3952
|
+
let initialMessages;
|
|
3953
|
+
let initialSessionId;
|
|
3954
|
+
if (options.continue) {
|
|
3955
|
+
const sessions = await listSessions();
|
|
3956
|
+
if (sessions.length > 0) {
|
|
3957
|
+
const session = await loadSession(sessions[0].id);
|
|
3958
|
+
if (session) {
|
|
3959
|
+
initialMessages = session.messages;
|
|
3960
|
+
initialSessionId = session.id;
|
|
3961
|
+
process.stderr.write(`Resuming session: ${session.title}
|
|
3962
|
+
`);
|
|
3963
|
+
}
|
|
3964
|
+
} else {
|
|
3965
|
+
process.stderr.write("No previous sessions found.\n");
|
|
3966
|
+
}
|
|
3967
|
+
} else if (options.resume) {
|
|
3968
|
+
const session = await loadSession(options.resume);
|
|
3969
|
+
if (session) {
|
|
3970
|
+
initialMessages = session.messages;
|
|
3971
|
+
initialSessionId = session.id;
|
|
3972
|
+
process.stderr.write(`Resuming session: ${session.title}
|
|
3973
|
+
`);
|
|
3974
|
+
} else {
|
|
3975
|
+
process.stderr.write(`Session not found: ${options.resume}
|
|
3976
|
+
`);
|
|
3977
|
+
process.exit(1);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3987
3980
|
const { waitUntilExit } = render(
|
|
3988
|
-
/* @__PURE__ */
|
|
3981
|
+
/* @__PURE__ */ jsx8(
|
|
3982
|
+
App,
|
|
3983
|
+
{
|
|
3984
|
+
model: resolvedModel,
|
|
3985
|
+
systemPromptOverride: systemPrompt,
|
|
3986
|
+
maxTurns,
|
|
3987
|
+
initialMessages,
|
|
3988
|
+
initialSessionId,
|
|
3989
|
+
children: /* @__PURE__ */ jsx8(REPL, {})
|
|
3990
|
+
}
|
|
3991
|
+
)
|
|
3989
3992
|
);
|
|
3990
3993
|
await waitUntilExit();
|
|
3991
3994
|
});
|