darkfoo-code 0.2.5 → 0.3.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 +331 -340
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -151,6 +151,13 @@ var init_ollama = __esm({
|
|
|
151
151
|
yield { type: "tool_call", toolCall: normalizeToolCall(tc) };
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
if (chunk.done && (chunk.eval_count || chunk.prompt_eval_count)) {
|
|
155
|
+
yield {
|
|
156
|
+
type: "usage",
|
|
157
|
+
inputTokens: chunk.prompt_eval_count ?? 0,
|
|
158
|
+
outputTokens: chunk.eval_count ?? 0
|
|
159
|
+
};
|
|
160
|
+
}
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
163
|
} finally {
|
|
@@ -299,6 +306,13 @@ var init_openai_compat = __esm({
|
|
|
299
306
|
}
|
|
300
307
|
}
|
|
301
308
|
pendingToolCalls.clear();
|
|
309
|
+
if (chunk.usage) {
|
|
310
|
+
yield {
|
|
311
|
+
type: "usage",
|
|
312
|
+
inputTokens: chunk.usage.prompt_tokens ?? 0,
|
|
313
|
+
outputTokens: chunk.usage.completion_tokens ?? 0
|
|
314
|
+
};
|
|
315
|
+
}
|
|
302
316
|
}
|
|
303
317
|
}
|
|
304
318
|
}
|
|
@@ -549,6 +563,88 @@ var init_permissions = __esm({
|
|
|
549
563
|
}
|
|
550
564
|
});
|
|
551
565
|
|
|
566
|
+
// src/hooks.ts
|
|
567
|
+
import { execFile as execFile4 } from "child_process";
|
|
568
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
569
|
+
import { join as join6 } from "path";
|
|
570
|
+
async function loadHooks() {
|
|
571
|
+
if (cachedHooks) return cachedHooks;
|
|
572
|
+
try {
|
|
573
|
+
const raw = await readFile5(SETTINGS_PATH4, "utf-8");
|
|
574
|
+
const config = JSON.parse(raw);
|
|
575
|
+
cachedHooks = config.hooks ?? [];
|
|
576
|
+
return cachedHooks;
|
|
577
|
+
} catch {
|
|
578
|
+
cachedHooks = [];
|
|
579
|
+
return [];
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
async function executeHooks(event, context) {
|
|
583
|
+
const hooks = await loadHooks();
|
|
584
|
+
const matching = hooks.filter((h) => {
|
|
585
|
+
if (h.event !== event) return false;
|
|
586
|
+
if (h.toolFilter && context.toolName && !h.toolFilter.includes(context.toolName)) return false;
|
|
587
|
+
return true;
|
|
588
|
+
});
|
|
589
|
+
const results = [];
|
|
590
|
+
for (const hook of matching) {
|
|
591
|
+
try {
|
|
592
|
+
const output = await new Promise((resolve8, reject) => {
|
|
593
|
+
execFile4("bash", ["-c", hook.command], {
|
|
594
|
+
cwd: context.cwd,
|
|
595
|
+
timeout: 1e4,
|
|
596
|
+
env: {
|
|
597
|
+
...process.env,
|
|
598
|
+
DARKFOO_EVENT: event,
|
|
599
|
+
DARKFOO_TOOL: context.toolName ?? ""
|
|
600
|
+
}
|
|
601
|
+
}, (err, stdout) => {
|
|
602
|
+
if (err) reject(err);
|
|
603
|
+
else resolve8(stdout.trim());
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
if (output) results.push(output);
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return results;
|
|
611
|
+
}
|
|
612
|
+
var SETTINGS_PATH4, cachedHooks;
|
|
613
|
+
var init_hooks = __esm({
|
|
614
|
+
"src/hooks.ts"() {
|
|
615
|
+
"use strict";
|
|
616
|
+
SETTINGS_PATH4 = join6(process.env.HOME || "~", ".darkfoo", "settings.json");
|
|
617
|
+
cachedHooks = null;
|
|
618
|
+
}
|
|
619
|
+
});
|
|
620
|
+
|
|
621
|
+
// src/utils/debug.ts
|
|
622
|
+
var debug_exports = {};
|
|
623
|
+
__export(debug_exports, {
|
|
624
|
+
debug: () => debug,
|
|
625
|
+
isDebugMode: () => isDebugMode,
|
|
626
|
+
setDebugMode: () => setDebugMode
|
|
627
|
+
});
|
|
628
|
+
function setDebugMode(on) {
|
|
629
|
+
debugMode = on;
|
|
630
|
+
}
|
|
631
|
+
function isDebugMode() {
|
|
632
|
+
return debugMode;
|
|
633
|
+
}
|
|
634
|
+
function debug(msg) {
|
|
635
|
+
if (debugMode) {
|
|
636
|
+
process.stderr.write(`[debug] ${msg}
|
|
637
|
+
`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
var debugMode;
|
|
641
|
+
var init_debug = __esm({
|
|
642
|
+
"src/utils/debug.ts"() {
|
|
643
|
+
"use strict";
|
|
644
|
+
debugMode = false;
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
|
|
552
648
|
// src/query.ts
|
|
553
649
|
var query_exports = {};
|
|
554
650
|
__export(query_exports, {
|
|
@@ -563,6 +659,7 @@ async function* query(params) {
|
|
|
563
659
|
const ollamaTools = tools.map((t) => t.toOllamaToolDef());
|
|
564
660
|
while (turns < maxTurns) {
|
|
565
661
|
turns++;
|
|
662
|
+
debug(`query turn ${turns}/${maxTurns}, ${messages.length} messages`);
|
|
566
663
|
const ollamaMessages = toOllamaMessages(messages, systemPrompt);
|
|
567
664
|
let assistantContent = "";
|
|
568
665
|
const toolCalls = [];
|
|
@@ -588,7 +685,11 @@ async function* query(params) {
|
|
|
588
685
|
};
|
|
589
686
|
messages.push(assistantMsg);
|
|
590
687
|
yield { type: "assistant_message", message: assistantMsg };
|
|
591
|
-
if (toolCalls.length === 0)
|
|
688
|
+
if (toolCalls.length === 0) {
|
|
689
|
+
debug("no tool calls, query complete");
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
debug(`${toolCalls.length} tool calls: ${toolCalls.map((tc) => tc.function.name).join(", ")}`);
|
|
592
693
|
const readOnlyCalls = [];
|
|
593
694
|
const writeCalls = [];
|
|
594
695
|
const unknownCalls = [];
|
|
@@ -611,10 +712,14 @@ async function* query(params) {
|
|
|
611
712
|
const results = await Promise.all(
|
|
612
713
|
readOnlyCalls.map(async ({ tc, tool }) => {
|
|
613
714
|
const coercedArgs = coerceToolArgs(tc.function.arguments, tool);
|
|
715
|
+
await executeHooks("pre_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
614
716
|
try {
|
|
615
|
-
|
|
717
|
+
const result = await tool.call(coercedArgs, { cwd: process.cwd(), abortSignal: signal });
|
|
718
|
+
await executeHooks("post_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
719
|
+
return { tool, result };
|
|
616
720
|
} catch (err) {
|
|
617
721
|
const msg = err instanceof Error ? err.message : String(err);
|
|
722
|
+
await executeHooks("post_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
618
723
|
return { tool, result: { output: `Tool execution error: ${msg}`, isError: true } };
|
|
619
724
|
}
|
|
620
725
|
})
|
|
@@ -625,6 +730,12 @@ async function* query(params) {
|
|
|
625
730
|
}
|
|
626
731
|
}
|
|
627
732
|
for (const { tc, tool } of writeCalls) {
|
|
733
|
+
if (getAppState().planMode) {
|
|
734
|
+
const blocked = `Tool "${tool.name}" is blocked in plan mode. Use ExitPlanMode first.`;
|
|
735
|
+
yield { type: "tool_result", toolName: tool.name, output: blocked, isError: true };
|
|
736
|
+
messages.push({ id: nanoid3(), role: "tool", content: blocked, toolName: tool.name, timestamp: Date.now() });
|
|
737
|
+
continue;
|
|
738
|
+
}
|
|
628
739
|
const coercedArgs = coerceToolArgs(tc.function.arguments, tool);
|
|
629
740
|
const permission = await checkPermission(tool, coercedArgs);
|
|
630
741
|
if (permission === "deny") {
|
|
@@ -633,6 +744,7 @@ async function* query(params) {
|
|
|
633
744
|
messages.push({ id: nanoid3(), role: "tool", content: denied, toolName: tool.name, timestamp: Date.now() });
|
|
634
745
|
continue;
|
|
635
746
|
}
|
|
747
|
+
await executeHooks("pre_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
636
748
|
let result;
|
|
637
749
|
try {
|
|
638
750
|
result = await tool.call(coercedArgs, { cwd: process.cwd(), abortSignal: signal });
|
|
@@ -640,6 +752,7 @@ async function* query(params) {
|
|
|
640
752
|
const msg = err instanceof Error ? err.message : String(err);
|
|
641
753
|
result = { output: `Tool execution error: ${msg}`, isError: true };
|
|
642
754
|
}
|
|
755
|
+
await executeHooks("post_tool", { toolName: tool.name, cwd: process.cwd() });
|
|
643
756
|
yield { type: "tool_result", toolName: tool.name, output: result.output, isError: result.isError ?? false };
|
|
644
757
|
messages.push({ id: nanoid3(), role: "tool", content: result.output, toolName: tool.name, timestamp: Date.now() });
|
|
645
758
|
}
|
|
@@ -691,12 +804,15 @@ var init_query = __esm({
|
|
|
691
804
|
"use strict";
|
|
692
805
|
init_providers();
|
|
693
806
|
init_permissions();
|
|
807
|
+
init_hooks();
|
|
808
|
+
init_state();
|
|
809
|
+
init_debug();
|
|
694
810
|
}
|
|
695
811
|
});
|
|
696
812
|
|
|
697
813
|
// src/context-loader.ts
|
|
698
|
-
import { readFile as
|
|
699
|
-
import { join as
|
|
814
|
+
import { readFile as readFile6, readdir as readdir2, access } from "fs/promises";
|
|
815
|
+
import { join as join7, dirname, resolve } from "path";
|
|
700
816
|
async function loadProjectContext(cwd) {
|
|
701
817
|
const parts = [];
|
|
702
818
|
const globalContent = await tryRead(HOME_CONTEXT);
|
|
@@ -704,13 +820,27 @@ async function loadProjectContext(cwd) {
|
|
|
704
820
|
parts.push(`# Global instructions (~/.darkfoo/DARKFOO.md)
|
|
705
821
|
|
|
706
822
|
${globalContent}`);
|
|
823
|
+
}
|
|
824
|
+
const userRulesDir = join7(process.env.HOME || "~", ".darkfoo", "rules");
|
|
825
|
+
try {
|
|
826
|
+
await access(userRulesDir);
|
|
827
|
+
const userRuleFiles = await readdir2(userRulesDir);
|
|
828
|
+
for (const file of userRuleFiles.filter((f) => f.endsWith(".md")).sort()) {
|
|
829
|
+
const content = await tryRead(join7(userRulesDir, file));
|
|
830
|
+
if (content) {
|
|
831
|
+
parts.push(`# User rule: ${file}
|
|
832
|
+
|
|
833
|
+
${content}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
} catch {
|
|
707
837
|
}
|
|
708
838
|
const visited = /* @__PURE__ */ new Set();
|
|
709
839
|
let dir = resolve(cwd);
|
|
710
840
|
while (dir && !visited.has(dir)) {
|
|
711
841
|
visited.add(dir);
|
|
712
842
|
for (const file of CONTEXT_FILES) {
|
|
713
|
-
const filePath =
|
|
843
|
+
const filePath = join7(dir, file);
|
|
714
844
|
const content = await tryRead(filePath);
|
|
715
845
|
if (content) {
|
|
716
846
|
const rel = filePath.replace(cwd + "/", "");
|
|
@@ -723,12 +853,12 @@ ${content}`);
|
|
|
723
853
|
if (parent === dir) break;
|
|
724
854
|
dir = parent;
|
|
725
855
|
}
|
|
726
|
-
const rulesDir =
|
|
856
|
+
const rulesDir = join7(cwd, RULES_DIR);
|
|
727
857
|
try {
|
|
728
858
|
await access(rulesDir);
|
|
729
859
|
const files = await readdir2(rulesDir);
|
|
730
860
|
for (const file of files.filter((f) => f.endsWith(".md")).sort()) {
|
|
731
|
-
const content = await tryRead(
|
|
861
|
+
const content = await tryRead(join7(rulesDir, file));
|
|
732
862
|
if (content) {
|
|
733
863
|
parts.push(`# Rule: ${file}
|
|
734
864
|
|
|
@@ -741,7 +871,7 @@ ${content}`);
|
|
|
741
871
|
}
|
|
742
872
|
async function tryRead(path) {
|
|
743
873
|
try {
|
|
744
|
-
return await
|
|
874
|
+
return await readFile6(path, "utf-8");
|
|
745
875
|
} catch {
|
|
746
876
|
return null;
|
|
747
877
|
}
|
|
@@ -752,7 +882,7 @@ var init_context_loader = __esm({
|
|
|
752
882
|
"use strict";
|
|
753
883
|
CONTEXT_FILES = ["DARKFOO.md", ".darkfoo/DARKFOO.md"];
|
|
754
884
|
RULES_DIR = ".darkfoo/rules";
|
|
755
|
-
HOME_CONTEXT =
|
|
885
|
+
HOME_CONTEXT = join7(process.env.HOME || "~", ".darkfoo", "DARKFOO.md");
|
|
756
886
|
}
|
|
757
887
|
});
|
|
758
888
|
|
|
@@ -847,14 +977,14 @@ var init_types = __esm({
|
|
|
847
977
|
|
|
848
978
|
// src/tools/bash.ts
|
|
849
979
|
import { spawn } from "child_process";
|
|
850
|
-
import { writeFile as writeFile6, mkdir as mkdir6, readFile as
|
|
851
|
-
import { join as
|
|
980
|
+
import { writeFile as writeFile6, mkdir as mkdir6, readFile as readFile7 } from "fs/promises";
|
|
981
|
+
import { join as join8 } from "path";
|
|
852
982
|
import { nanoid as nanoid4 } from "nanoid";
|
|
853
983
|
import { z } from "zod";
|
|
854
984
|
async function runInBackground(command, cwd) {
|
|
855
985
|
const taskId = nanoid4(8);
|
|
856
986
|
await mkdir6(BG_OUTPUT_DIR, { recursive: true });
|
|
857
|
-
const outputPath =
|
|
987
|
+
const outputPath = join8(BG_OUTPUT_DIR, `${taskId}.output`);
|
|
858
988
|
backgroundTasks.set(taskId, { command, status: "running", outputPath });
|
|
859
989
|
const proc = spawn(command, {
|
|
860
990
|
shell: true,
|
|
@@ -906,7 +1036,7 @@ var init_bash = __esm({
|
|
|
906
1036
|
run_in_background: z.boolean().optional().describe("Run in background, returning a task ID for later retrieval")
|
|
907
1037
|
});
|
|
908
1038
|
MAX_OUTPUT = 1e5;
|
|
909
|
-
BG_OUTPUT_DIR =
|
|
1039
|
+
BG_OUTPUT_DIR = join8(process.env.HOME || "~", ".darkfoo", "bg-tasks");
|
|
910
1040
|
backgroundTasks = /* @__PURE__ */ new Map();
|
|
911
1041
|
BashTool = {
|
|
912
1042
|
name: "Bash",
|
|
@@ -967,17 +1097,17 @@ var init_bash = __esm({
|
|
|
967
1097
|
});
|
|
968
1098
|
|
|
969
1099
|
// src/tools/read.ts
|
|
970
|
-
import { readFile as
|
|
1100
|
+
import { readFile as readFile8, stat } from "fs/promises";
|
|
971
1101
|
import { extname, resolve as resolve2 } from "path";
|
|
972
1102
|
import { z as z2 } from "zod";
|
|
973
1103
|
async function readImage(filePath, size) {
|
|
974
1104
|
const ext = extname(filePath).toLowerCase();
|
|
975
1105
|
if (ext === ".svg") {
|
|
976
|
-
const content = await
|
|
1106
|
+
const content = await readFile8(filePath, "utf-8");
|
|
977
1107
|
return { output: `SVG image (${size} bytes):
|
|
978
1108
|
${content.slice(0, 5e3)}` };
|
|
979
1109
|
}
|
|
980
|
-
const buffer = await
|
|
1110
|
+
const buffer = await readFile8(filePath);
|
|
981
1111
|
const base64 = buffer.toString("base64");
|
|
982
1112
|
const sizeKB = (size / 1024).toFixed(1);
|
|
983
1113
|
const dims = detectImageDimensions(buffer, ext);
|
|
@@ -990,9 +1120,9 @@ Base64 length: ${base64.length} chars
|
|
|
990
1120
|
};
|
|
991
1121
|
}
|
|
992
1122
|
async function readPdf(filePath) {
|
|
993
|
-
const { execFile:
|
|
1123
|
+
const { execFile: execFile7 } = await import("child_process");
|
|
994
1124
|
return new Promise((resolve8) => {
|
|
995
|
-
|
|
1125
|
+
execFile7("pdftotext", [filePath, "-"], { timeout: 15e3, maxBuffer: 5 * 1024 * 1024 }, (err, stdout) => {
|
|
996
1126
|
if (err) {
|
|
997
1127
|
resolve8({
|
|
998
1128
|
output: `PDF file: ${filePath}
|
|
@@ -1069,7 +1199,7 @@ var init_read = __esm({
|
|
|
1069
1199
|
if (ext === PDF_EXT) {
|
|
1070
1200
|
return readPdf(filePath);
|
|
1071
1201
|
}
|
|
1072
|
-
const raw = await
|
|
1202
|
+
const raw = await readFile8(filePath, "utf-8");
|
|
1073
1203
|
markFileRead(filePath, raw);
|
|
1074
1204
|
const lines = raw.split("\n");
|
|
1075
1205
|
const offset = parsed.offset ?? 0;
|
|
@@ -1140,7 +1270,7 @@ var init_write = __esm({
|
|
|
1140
1270
|
});
|
|
1141
1271
|
|
|
1142
1272
|
// src/tools/edit.ts
|
|
1143
|
-
import { readFile as
|
|
1273
|
+
import { readFile as readFile9, writeFile as writeFile8 } from "fs/promises";
|
|
1144
1274
|
import { resolve as resolve4 } from "path";
|
|
1145
1275
|
import { createPatch } from "diff";
|
|
1146
1276
|
import { z as z4 } from "zod";
|
|
@@ -1186,7 +1316,7 @@ var init_edit = __esm({
|
|
|
1186
1316
|
isError: true
|
|
1187
1317
|
};
|
|
1188
1318
|
}
|
|
1189
|
-
const content = await
|
|
1319
|
+
const content = await readFile9(filePath, "utf-8");
|
|
1190
1320
|
const actualOld = findActualString(content, parsed.old_string);
|
|
1191
1321
|
if (!actualOld) {
|
|
1192
1322
|
return {
|
|
@@ -1218,7 +1348,7 @@ var init_edit = __esm({
|
|
|
1218
1348
|
});
|
|
1219
1349
|
|
|
1220
1350
|
// src/tools/grep.ts
|
|
1221
|
-
import { execFile as
|
|
1351
|
+
import { execFile as execFile5 } from "child_process";
|
|
1222
1352
|
import { resolve as resolve5 } from "path";
|
|
1223
1353
|
import { z as z5 } from "zod";
|
|
1224
1354
|
var INPUT_SCHEMA5, DEFAULT_HEAD_LIMIT, GrepTool;
|
|
@@ -1278,7 +1408,7 @@ var init_grep = __esm({
|
|
|
1278
1408
|
}
|
|
1279
1409
|
args.push(searchPath);
|
|
1280
1410
|
return new Promise((resolve8) => {
|
|
1281
|
-
|
|
1411
|
+
execFile5("rg", args, { maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }, (err, stdout, stderr) => {
|
|
1282
1412
|
if (err && !stdout) {
|
|
1283
1413
|
if (err.code === 1 || err.code === "1") {
|
|
1284
1414
|
resolve8({ output: "No matches found." });
|
|
@@ -1314,7 +1444,7 @@ ${output}`;
|
|
|
1314
1444
|
});
|
|
1315
1445
|
|
|
1316
1446
|
// src/tools/glob.ts
|
|
1317
|
-
import { execFile as
|
|
1447
|
+
import { execFile as execFile6 } from "child_process";
|
|
1318
1448
|
import { resolve as resolve6 } from "path";
|
|
1319
1449
|
import { z as z6 } from "zod";
|
|
1320
1450
|
var INPUT_SCHEMA6, MAX_RESULTS, GlobTool;
|
|
@@ -1348,7 +1478,7 @@ var init_glob = __esm({
|
|
|
1348
1478
|
searchPath
|
|
1349
1479
|
];
|
|
1350
1480
|
return new Promise((resolve8) => {
|
|
1351
|
-
|
|
1481
|
+
execFile6("rg", args, { maxBuffer: 10 * 1024 * 1024, timeout: 3e4 }, (err, stdout, stderr) => {
|
|
1352
1482
|
if (err && !stdout) {
|
|
1353
1483
|
if (err.code === 1 || err.code === "1") {
|
|
1354
1484
|
resolve8({ output: "No files matched." });
|
|
@@ -1599,7 +1729,7 @@ var init_agent = __esm({
|
|
|
1599
1729
|
});
|
|
1600
1730
|
|
|
1601
1731
|
// src/tools/notebook-edit.ts
|
|
1602
|
-
import { readFile as
|
|
1732
|
+
import { readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
|
|
1603
1733
|
import { resolve as resolve7 } from "path";
|
|
1604
1734
|
import { z as z10 } from "zod";
|
|
1605
1735
|
var INPUT_SCHEMA10, NotebookEditTool;
|
|
@@ -1622,7 +1752,7 @@ var init_notebook_edit = __esm({
|
|
|
1622
1752
|
const parsed = INPUT_SCHEMA10.parse(input);
|
|
1623
1753
|
const filePath = resolve7(context.cwd, parsed.file_path);
|
|
1624
1754
|
try {
|
|
1625
|
-
const raw = await
|
|
1755
|
+
const raw = await readFile10(filePath, "utf-8");
|
|
1626
1756
|
const notebook = JSON.parse(raw);
|
|
1627
1757
|
if (parsed.cell_index < 0 || parsed.cell_index >= notebook.cells.length) {
|
|
1628
1758
|
return {
|
|
@@ -1921,8 +2051,8 @@ var DarkfooContext = createContext({ model: "qwen2.5-coder:32b" });
|
|
|
1921
2051
|
function useDarkfooContext() {
|
|
1922
2052
|
return useContext(DarkfooContext);
|
|
1923
2053
|
}
|
|
1924
|
-
function App({ model, systemPromptOverride, children }) {
|
|
1925
|
-
return /* @__PURE__ */ jsx(DarkfooContext.Provider, { value: { model, systemPromptOverride }, children });
|
|
2054
|
+
function App({ model, systemPromptOverride, maxTurns, initialMessages, initialSessionId, children }) {
|
|
2055
|
+
return /* @__PURE__ */ jsx(DarkfooContext.Provider, { value: { model, systemPromptOverride, maxTurns, initialMessages, initialSessionId }, children });
|
|
1926
2056
|
}
|
|
1927
2057
|
|
|
1928
2058
|
// src/repl.tsx
|
|
@@ -1935,128 +2065,25 @@ init_theme();
|
|
|
1935
2065
|
import { memo as memo2 } from "react";
|
|
1936
2066
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
1937
2067
|
|
|
1938
|
-
// src/components/
|
|
1939
|
-
init_theme();
|
|
2068
|
+
// src/components/Mascot.tsx
|
|
1940
2069
|
import { memo } from "react";
|
|
1941
2070
|
import { Box, Text } from "ink";
|
|
1942
2071
|
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 = {
|
|
2072
|
+
var MOOD_COLORS = {
|
|
2018
2073
|
idle: "#5eead4",
|
|
2019
2074
|
thinking: "#a78bfa",
|
|
2020
2075
|
working: "#fbbf24",
|
|
2021
2076
|
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"
|
|
2077
|
+
error: "#f472b6"
|
|
2038
2078
|
};
|
|
2039
|
-
var
|
|
2040
|
-
const
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
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) + "'" })
|
|
2079
|
+
var Mascot = memo(function Mascot2({ mood = "idle" }) {
|
|
2080
|
+
const color = MOOD_COLORS[mood];
|
|
2081
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", alignItems: "center", children: [
|
|
2082
|
+
/* @__PURE__ */ jsx2(Text, { color, children: " \u259F\u2588\u2588\u2599" }),
|
|
2083
|
+
/* @__PURE__ */ jsx2(Text, { color, children: " \u2590\u2588\u2588\u2588\u2588\u258C" }),
|
|
2084
|
+
/* @__PURE__ */ jsx2(Text, { color, children: " \u259C\u2588\u2588\u259B" })
|
|
2058
2085
|
] });
|
|
2059
|
-
}
|
|
2086
|
+
});
|
|
2060
2087
|
|
|
2061
2088
|
// src/components/Banner.tsx
|
|
2062
2089
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -2088,26 +2115,20 @@ function gradientBar(width) {
|
|
|
2088
2115
|
color: GRADIENT[Math.round(i / (width - 1) * (GRADIENT.length - 1))]
|
|
2089
2116
|
}));
|
|
2090
2117
|
}
|
|
2091
|
-
var Banner = memo2(function Banner2({ model, cwd, providerName, providerOnline }) {
|
|
2118
|
+
var Banner = memo2(function Banner2({ model, cwd, providerName, providerOnline, mood }) {
|
|
2092
2119
|
const bar = gradientBar(60);
|
|
2093
2120
|
const statusTag = providerOnline ? "[connected]" : "[offline]";
|
|
2094
2121
|
const statusColor = providerOnline ? theme.green ?? "#4ade80" : theme.pink ?? "#f472b6";
|
|
2095
2122
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
|
|
2096
|
-
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
2097
|
-
/* @__PURE__ */
|
|
2098
|
-
|
|
2099
|
-
/* @__PURE__ */ jsx3(Text2, { color: theme.purple ?? "#a78bfa", bold: true, children: SUBTITLE })
|
|
2100
|
-
] }),
|
|
2101
|
-
/* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, flexDirection: "row", alignItems: "flex-end", children: [
|
|
2102
|
-
/* @__PURE__ */ jsx3(Fox, { mood: "greeting" }),
|
|
2103
|
-
/* @__PURE__ */ jsx3(FoxBubble, { text: "Ready to work." })
|
|
2104
|
-
] })
|
|
2123
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", alignItems: "center", children: [
|
|
2124
|
+
LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx3(Text2, { color: lineColor(i), bold: true, children: line }, i)),
|
|
2125
|
+
/* @__PURE__ */ jsx3(Text2, { color: theme.purple ?? "#a78bfa", bold: true, children: SUBTITLE })
|
|
2105
2126
|
] }),
|
|
2106
|
-
/* @__PURE__ */ jsx3(
|
|
2107
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
2127
|
+
/* @__PURE__ */ jsx3(Box2, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsx3(Mascot, { mood }) }),
|
|
2128
|
+
/* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
2108
2129
|
" ",
|
|
2109
2130
|
bar.map((d, i) => /* @__PURE__ */ jsx3(Text2, { color: d.color, children: d.char }, i))
|
|
2110
|
-
] }),
|
|
2131
|
+
] }) }),
|
|
2111
2132
|
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, marginLeft: 2, children: [
|
|
2112
2133
|
/* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: "model " }),
|
|
2113
2134
|
model ? /* @__PURE__ */ jsx3(Text2, { color: theme.cyan ?? "#5eead4", bold: true, children: model }) : /* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: "--" })
|
|
@@ -2435,12 +2456,15 @@ import { nanoid } from "nanoid";
|
|
|
2435
2456
|
var compactCommand = {
|
|
2436
2457
|
name: "compact",
|
|
2437
2458
|
description: "Compress conversation history to save context",
|
|
2438
|
-
async execute(
|
|
2459
|
+
async execute(args, context) {
|
|
2439
2460
|
if (context.messages.length < 4) {
|
|
2440
2461
|
return { output: "Conversation is too short to compact.", silent: true };
|
|
2441
2462
|
}
|
|
2442
2463
|
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
|
|
2464
|
+
const focus = args.trim();
|
|
2465
|
+
const summaryPrompt = focus ? `Summarize this conversation, focusing especially on: ${focus}. Capture key decisions, code changes, file paths, and current state:
|
|
2466
|
+
|
|
2467
|
+
${transcript}` : `Summarize this conversation concisely. Capture all key decisions, code changes, file paths mentioned, and current task state. Be thorough but brief:
|
|
2444
2468
|
|
|
2445
2469
|
${transcript}`;
|
|
2446
2470
|
try {
|
|
@@ -2514,7 +2538,7 @@ var contextCommand = {
|
|
|
2514
2538
|
}
|
|
2515
2539
|
totalTokens += tokens;
|
|
2516
2540
|
}
|
|
2517
|
-
const sysTokens = 2e3;
|
|
2541
|
+
const sysTokens = context.systemPrompt ? estimateTokens(context.systemPrompt) : 2e3;
|
|
2518
2542
|
totalTokens += sysTokens;
|
|
2519
2543
|
const usage = totalTokens / contextLimit;
|
|
2520
2544
|
const barWidth = 40;
|
|
@@ -2528,7 +2552,7 @@ var contextCommand = {
|
|
|
2528
2552
|
` ~${totalTokens.toLocaleString()} / ${contextLimit.toLocaleString()} tokens`,
|
|
2529
2553
|
"",
|
|
2530
2554
|
" Breakdown:",
|
|
2531
|
-
` System prompt:
|
|
2555
|
+
` System prompt: ${context.systemPrompt ? "" : "~"}${sysTokens.toLocaleString()} tokens`,
|
|
2532
2556
|
...breakdown.map(
|
|
2533
2557
|
(b) => ` ${b.role.padEnd(12)} ${b.count} msgs, ~${b.tokens.toLocaleString()} tokens`
|
|
2534
2558
|
),
|
|
@@ -2618,6 +2642,13 @@ async function saveSession(id, messages, model, cwd) {
|
|
|
2618
2642
|
};
|
|
2619
2643
|
await writeFile2(join2(SESSIONS_DIR, `${id}.json`), JSON.stringify(data, null, 2), "utf-8");
|
|
2620
2644
|
}
|
|
2645
|
+
async function renameSession(id, title) {
|
|
2646
|
+
const session = await loadSession(id);
|
|
2647
|
+
if (!session) return;
|
|
2648
|
+
session.title = title;
|
|
2649
|
+
session.updatedAt = Date.now();
|
|
2650
|
+
await writeFile2(join2(SESSIONS_DIR, `${id}.json`), JSON.stringify(session, null, 2), "utf-8");
|
|
2651
|
+
}
|
|
2621
2652
|
async function loadSession(id) {
|
|
2622
2653
|
try {
|
|
2623
2654
|
const raw = await readFile2(join2(SESSIONS_DIR, `${id}.json`), "utf-8");
|
|
@@ -3280,143 +3311,36 @@ var providerCommand = {
|
|
|
3280
3311
|
}
|
|
3281
3312
|
};
|
|
3282
3313
|
|
|
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" };
|
|
3314
|
+
// src/commands/rename.ts
|
|
3315
|
+
var renameCommand = {
|
|
3316
|
+
name: "rename",
|
|
3317
|
+
description: "Rename the current session (usage: /rename <title>)",
|
|
3318
|
+
async execute(args, context) {
|
|
3319
|
+
const title = args.trim();
|
|
3320
|
+
if (!title) {
|
|
3321
|
+
return { output: "Usage: /rename <new session title>", silent: true };
|
|
3322
|
+
}
|
|
3323
|
+
await renameSession(context.sessionId, title);
|
|
3324
|
+
return { output: `Session renamed to: ${title}`, silent: true };
|
|
3382
3325
|
}
|
|
3383
3326
|
};
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3327
|
+
|
|
3328
|
+
// src/commands/plan.ts
|
|
3329
|
+
init_state();
|
|
3330
|
+
var planCommand = {
|
|
3331
|
+
name: "plan",
|
|
3332
|
+
description: "Enter plan mode to explore and design before implementing (usage: /plan <description>)",
|
|
3388
3333
|
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";
|
|
3334
|
+
const description = args.trim();
|
|
3335
|
+
if (!description) {
|
|
3336
|
+
return { output: "Usage: /plan <what you want to plan>", silent: true };
|
|
3337
|
+
}
|
|
3338
|
+
updateAppState((s) => ({ ...s, planMode: true }));
|
|
3339
|
+
return {
|
|
3340
|
+
output: `Enter plan mode. Explore the codebase and design a plan for: ${description}`,
|
|
3341
|
+
silent: false,
|
|
3342
|
+
foxMood: "thinking"
|
|
3400
3343
|
};
|
|
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
3344
|
}
|
|
3421
3345
|
};
|
|
3422
3346
|
|
|
@@ -3443,10 +3367,8 @@ var COMMANDS = [
|
|
|
3443
3367
|
filesCommand,
|
|
3444
3368
|
briefCommand,
|
|
3445
3369
|
providerCommand,
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
restCommand,
|
|
3449
|
-
foxCommand
|
|
3370
|
+
renameCommand,
|
|
3371
|
+
planCommand
|
|
3450
3372
|
];
|
|
3451
3373
|
function getCommands() {
|
|
3452
3374
|
return COMMANDS;
|
|
@@ -3572,6 +3494,7 @@ init_system_prompt();
|
|
|
3572
3494
|
init_providers();
|
|
3573
3495
|
init_tools();
|
|
3574
3496
|
init_bash();
|
|
3497
|
+
init_hooks();
|
|
3575
3498
|
init_theme();
|
|
3576
3499
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3577
3500
|
function getContextLimit3(model) {
|
|
@@ -3585,10 +3508,10 @@ function getContextLimit3(model) {
|
|
|
3585
3508
|
return 8192;
|
|
3586
3509
|
}
|
|
3587
3510
|
function REPL({ initialPrompt }) {
|
|
3588
|
-
const { model: initialModel, systemPromptOverride } = useDarkfooContext();
|
|
3511
|
+
const { model: initialModel, systemPromptOverride, maxTurns, initialMessages, initialSessionId } = useDarkfooContext();
|
|
3589
3512
|
const { exit } = useApp();
|
|
3590
3513
|
const [model, setModel] = useState2(initialModel);
|
|
3591
|
-
const [messages, setMessages] = useState2([]);
|
|
3514
|
+
const [messages, setMessages] = useState2(initialMessages ?? []);
|
|
3592
3515
|
const [inputValue, setInputValue] = useState2("");
|
|
3593
3516
|
const [isStreaming, setIsStreaming] = useState2(false);
|
|
3594
3517
|
const [streamingText, setStreamingText] = useState2("");
|
|
@@ -3598,11 +3521,11 @@ function REPL({ initialPrompt }) {
|
|
|
3598
3521
|
const [inputHistory, setInputHistory] = useState2([]);
|
|
3599
3522
|
const [tokenCounts, setTokenCounts] = useState2({ input: 0, output: 0 });
|
|
3600
3523
|
const [systemPrompt, setSystemPrompt] = useState2("");
|
|
3601
|
-
const [
|
|
3524
|
+
const [mascotMood, setMascotMood] = useState2("idle");
|
|
3602
3525
|
const [providerOnline, setProviderOnline] = useState2(true);
|
|
3603
3526
|
const abortRef = useRef(null);
|
|
3604
3527
|
const hasRun = useRef(false);
|
|
3605
|
-
const sessionId = useRef(createSessionId());
|
|
3528
|
+
const sessionId = useRef(initialSessionId ?? createSessionId());
|
|
3606
3529
|
const messagesRef = useRef(messages);
|
|
3607
3530
|
messagesRef.current = messages;
|
|
3608
3531
|
const modelRef = useRef(model);
|
|
@@ -3618,6 +3541,10 @@ function REPL({ initialPrompt }) {
|
|
|
3618
3541
|
buildSystemPrompt(tools, cwd).then(setSystemPrompt);
|
|
3619
3542
|
}
|
|
3620
3543
|
}, []);
|
|
3544
|
+
useEffect(() => {
|
|
3545
|
+
executeHooks("session_start", { cwd }).catch(() => {
|
|
3546
|
+
});
|
|
3547
|
+
}, []);
|
|
3621
3548
|
useEffect(() => {
|
|
3622
3549
|
const provider = getProvider();
|
|
3623
3550
|
provider.healthCheck().then((ok) => {
|
|
@@ -3655,12 +3582,14 @@ function REPL({ initialPrompt }) {
|
|
|
3655
3582
|
messages,
|
|
3656
3583
|
model,
|
|
3657
3584
|
cwd,
|
|
3585
|
+
sessionId: sessionId.current,
|
|
3658
3586
|
setModel,
|
|
3659
3587
|
clearMessages,
|
|
3660
3588
|
exit,
|
|
3661
|
-
tokenCounts
|
|
3589
|
+
tokenCounts,
|
|
3590
|
+
systemPrompt
|
|
3662
3591
|
});
|
|
3663
|
-
commandContextRef.current = { messages, model, cwd, setModel, clearMessages, exit, tokenCounts };
|
|
3592
|
+
commandContextRef.current = { messages, model, cwd, sessionId: sessionId.current, setModel, clearMessages, exit, tokenCounts, systemPrompt };
|
|
3664
3593
|
const addToHistory = useCallback2((input) => {
|
|
3665
3594
|
setInputHistory((prev) => {
|
|
3666
3595
|
const filtered = prev.filter((h) => h !== input);
|
|
@@ -3680,42 +3609,45 @@ function REPL({ initialPrompt }) {
|
|
|
3680
3609
|
setStreamingText("");
|
|
3681
3610
|
setToolResults([]);
|
|
3682
3611
|
setCommandOutput(null);
|
|
3683
|
-
|
|
3612
|
+
setMascotMood("thinking");
|
|
3684
3613
|
const controller = new AbortController();
|
|
3685
3614
|
abortRef.current = controller;
|
|
3686
3615
|
const allMessages = [...messagesRef.current, userMsg];
|
|
3687
3616
|
const currentModel = modelRef.current;
|
|
3688
3617
|
const currentSystemPrompt = systemPromptRef.current;
|
|
3689
|
-
setTokenCounts((prev) => ({
|
|
3690
|
-
...prev,
|
|
3691
|
-
input: prev.input + Math.ceil(userMessage.length / 4)
|
|
3692
|
-
}));
|
|
3693
3618
|
try {
|
|
3694
3619
|
for await (const event of query({
|
|
3695
3620
|
model: currentModel,
|
|
3696
3621
|
messages: allMessages,
|
|
3697
3622
|
tools,
|
|
3698
3623
|
systemPrompt: currentSystemPrompt,
|
|
3699
|
-
signal: controller.signal
|
|
3624
|
+
signal: controller.signal,
|
|
3625
|
+
maxTurns
|
|
3700
3626
|
})) {
|
|
3701
3627
|
if (controller.signal.aborted) break;
|
|
3702
3628
|
switch (event.type) {
|
|
3703
3629
|
case "text_delta":
|
|
3704
3630
|
setStreamingText((prev) => prev + event.text);
|
|
3705
|
-
|
|
3631
|
+
setMascotMood("idle");
|
|
3706
3632
|
break;
|
|
3707
3633
|
case "tool_call":
|
|
3708
3634
|
setActiveTool({ name: event.toolCall.function.name, args: event.toolCall.function.arguments });
|
|
3709
|
-
|
|
3635
|
+
setMascotMood("working");
|
|
3710
3636
|
break;
|
|
3711
3637
|
case "tool_result":
|
|
3712
3638
|
setActiveTool(null);
|
|
3713
|
-
|
|
3639
|
+
setMascotMood(event.isError ? "error" : "working");
|
|
3714
3640
|
setToolResults((prev) => [
|
|
3715
3641
|
...prev,
|
|
3716
3642
|
{ id: nanoid6(), toolName: event.toolName, output: event.output, isError: event.isError }
|
|
3717
3643
|
]);
|
|
3718
3644
|
break;
|
|
3645
|
+
case "usage":
|
|
3646
|
+
setTokenCounts((prev) => ({
|
|
3647
|
+
input: prev.input + event.inputTokens,
|
|
3648
|
+
output: prev.output + event.outputTokens
|
|
3649
|
+
}));
|
|
3650
|
+
break;
|
|
3719
3651
|
case "assistant_message":
|
|
3720
3652
|
setMessages((prev) => {
|
|
3721
3653
|
const updated = [...prev, event.message];
|
|
@@ -3723,14 +3655,10 @@ function REPL({ initialPrompt }) {
|
|
|
3723
3655
|
});
|
|
3724
3656
|
return updated;
|
|
3725
3657
|
});
|
|
3726
|
-
setTokenCounts((prev) => ({
|
|
3727
|
-
...prev,
|
|
3728
|
-
output: prev.output + Math.ceil((event.message.content?.length ?? 0) / 4)
|
|
3729
|
-
}));
|
|
3730
3658
|
setStreamingText("");
|
|
3731
3659
|
break;
|
|
3732
3660
|
case "error":
|
|
3733
|
-
|
|
3661
|
+
setMascotMood("error");
|
|
3734
3662
|
setMessages((prev) => [
|
|
3735
3663
|
...prev,
|
|
3736
3664
|
{ id: nanoid6(), role: "assistant", content: `Error: ${event.error}`, timestamp: Date.now() }
|
|
@@ -3751,8 +3679,8 @@ function REPL({ initialPrompt }) {
|
|
|
3751
3679
|
setStreamingText("");
|
|
3752
3680
|
setActiveTool(null);
|
|
3753
3681
|
setToolResults([]);
|
|
3754
|
-
|
|
3755
|
-
setTimeout(() =>
|
|
3682
|
+
setMascotMood((prev) => prev === "error" ? "error" : "success");
|
|
3683
|
+
setTimeout(() => setMascotMood("idle"), 2e3);
|
|
3756
3684
|
abortRef.current = null;
|
|
3757
3685
|
}
|
|
3758
3686
|
},
|
|
@@ -3775,8 +3703,8 @@ function REPL({ initialPrompt }) {
|
|
|
3775
3703
|
setMessages(result.replaceMessages);
|
|
3776
3704
|
}
|
|
3777
3705
|
if (result.foxMood) {
|
|
3778
|
-
|
|
3779
|
-
setTimeout(() =>
|
|
3706
|
+
setMascotMood(result.foxMood);
|
|
3707
|
+
setTimeout(() => setMascotMood("idle"), 3e3);
|
|
3780
3708
|
}
|
|
3781
3709
|
if (result.exit) return;
|
|
3782
3710
|
if (!result.silent && result.output) {
|
|
@@ -3863,7 +3791,7 @@ ${result.content}
|
|
|
3863
3791
|
}
|
|
3864
3792
|
});
|
|
3865
3793
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", padding: 1, children: [
|
|
3866
|
-
/* @__PURE__ */ jsx8(Banner, { model, cwd, providerName: getActiveProviderName(), providerOnline }),
|
|
3794
|
+
/* @__PURE__ */ jsx8(Banner, { model, cwd, providerName: getActiveProviderName(), providerOnline, mood: mascotMood }),
|
|
3867
3795
|
/* @__PURE__ */ jsx8(Messages, { messages }),
|
|
3868
3796
|
commandOutput ? /* @__PURE__ */ jsx8(Box7, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text7, { children: commandOutput }) }) : null,
|
|
3869
3797
|
toolResults.map((tr) => /* @__PURE__ */ jsx8(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
|
|
@@ -3877,19 +3805,16 @@ ${result.content}
|
|
|
3877
3805
|
/* @__PURE__ */ jsx8(Text7, { color: theme.cyan, children: "..." }),
|
|
3878
3806
|
/* @__PURE__ */ jsx8(Text7, { color: theme.dim, children: " Thinking" })
|
|
3879
3807
|
] }) : 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
|
-
] }),
|
|
3808
|
+
/* @__PURE__ */ jsx8(
|
|
3809
|
+
UserInput,
|
|
3810
|
+
{
|
|
3811
|
+
value: inputValue,
|
|
3812
|
+
onChange: setInputValue,
|
|
3813
|
+
onSubmit: handleSubmit,
|
|
3814
|
+
disabled: isStreaming,
|
|
3815
|
+
history: inputHistory
|
|
3816
|
+
}
|
|
3817
|
+
),
|
|
3893
3818
|
/* @__PURE__ */ jsx8(
|
|
3894
3819
|
StatusLine,
|
|
3895
3820
|
{
|
|
@@ -3906,8 +3831,13 @@ ${result.content}
|
|
|
3906
3831
|
init_providers();
|
|
3907
3832
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
3908
3833
|
var program = new Command();
|
|
3909
|
-
program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.
|
|
3834
|
+
program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.3.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
3835
|
const { model, prompt, provider, systemPrompt } = options;
|
|
3836
|
+
if (options.debug) {
|
|
3837
|
+
const { setDebugMode: setDebugMode2 } = await Promise.resolve().then(() => (init_debug(), debug_exports));
|
|
3838
|
+
setDebugMode2(true);
|
|
3839
|
+
}
|
|
3840
|
+
const maxTurns = parseInt(options.maxTurns, 10) || 30;
|
|
3911
3841
|
await loadProviderSettings();
|
|
3912
3842
|
if (provider) {
|
|
3913
3843
|
try {
|
|
@@ -3956,19 +3886,35 @@ program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assista
|
|
|
3956
3886
|
controller.abort();
|
|
3957
3887
|
process.exit(0);
|
|
3958
3888
|
});
|
|
3889
|
+
const jsonMode = options.outputFormat === "json";
|
|
3890
|
+
let jsonContent = "";
|
|
3891
|
+
const jsonToolCalls = [];
|
|
3892
|
+
const jsonToolResults = [];
|
|
3959
3893
|
for await (const event of query2({
|
|
3960
3894
|
model: resolvedModel,
|
|
3961
3895
|
messages: [userMsg],
|
|
3962
3896
|
tools,
|
|
3963
3897
|
systemPrompt: sysPrompt,
|
|
3964
|
-
signal: controller.signal
|
|
3898
|
+
signal: controller.signal,
|
|
3899
|
+
maxTurns
|
|
3965
3900
|
})) {
|
|
3966
3901
|
switch (event.type) {
|
|
3967
3902
|
case "text_delta":
|
|
3968
|
-
|
|
3903
|
+
if (jsonMode) {
|
|
3904
|
+
jsonContent += event.text;
|
|
3905
|
+
} else {
|
|
3906
|
+
process.stdout.write(event.text);
|
|
3907
|
+
}
|
|
3908
|
+
break;
|
|
3909
|
+
case "tool_call":
|
|
3910
|
+
if (jsonMode) {
|
|
3911
|
+
jsonToolCalls.push({ name: event.toolCall.function.name, arguments: event.toolCall.function.arguments });
|
|
3912
|
+
}
|
|
3969
3913
|
break;
|
|
3970
3914
|
case "tool_result":
|
|
3971
|
-
if (
|
|
3915
|
+
if (jsonMode) {
|
|
3916
|
+
jsonToolResults.push({ tool: event.toolName, output: event.output, isError: event.isError });
|
|
3917
|
+
} else if (event.isError) {
|
|
3972
3918
|
process.stderr.write(`
|
|
3973
3919
|
[${event.toolName}] Error: ${event.output}
|
|
3974
3920
|
`);
|
|
@@ -3981,11 +3927,56 @@ Error: ${event.error}
|
|
|
3981
3927
|
break;
|
|
3982
3928
|
}
|
|
3983
3929
|
}
|
|
3984
|
-
|
|
3930
|
+
if (jsonMode) {
|
|
3931
|
+
const result = { role: "assistant", content: jsonContent };
|
|
3932
|
+
if (jsonToolCalls.length > 0) result.toolCalls = jsonToolCalls;
|
|
3933
|
+
if (jsonToolResults.length > 0) result.toolResults = jsonToolResults;
|
|
3934
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
3935
|
+
} else {
|
|
3936
|
+
process.stdout.write("\n");
|
|
3937
|
+
}
|
|
3985
3938
|
process.exit(0);
|
|
3986
3939
|
}
|
|
3940
|
+
let initialMessages;
|
|
3941
|
+
let initialSessionId;
|
|
3942
|
+
if (options.continue) {
|
|
3943
|
+
const sessions = await listSessions();
|
|
3944
|
+
if (sessions.length > 0) {
|
|
3945
|
+
const session = await loadSession(sessions[0].id);
|
|
3946
|
+
if (session) {
|
|
3947
|
+
initialMessages = session.messages;
|
|
3948
|
+
initialSessionId = session.id;
|
|
3949
|
+
process.stderr.write(`Resuming session: ${session.title}
|
|
3950
|
+
`);
|
|
3951
|
+
}
|
|
3952
|
+
} else {
|
|
3953
|
+
process.stderr.write("No previous sessions found.\n");
|
|
3954
|
+
}
|
|
3955
|
+
} else if (options.resume) {
|
|
3956
|
+
const session = await loadSession(options.resume);
|
|
3957
|
+
if (session) {
|
|
3958
|
+
initialMessages = session.messages;
|
|
3959
|
+
initialSessionId = session.id;
|
|
3960
|
+
process.stderr.write(`Resuming session: ${session.title}
|
|
3961
|
+
`);
|
|
3962
|
+
} else {
|
|
3963
|
+
process.stderr.write(`Session not found: ${options.resume}
|
|
3964
|
+
`);
|
|
3965
|
+
process.exit(1);
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3987
3968
|
const { waitUntilExit } = render(
|
|
3988
|
-
/* @__PURE__ */ jsx9(
|
|
3969
|
+
/* @__PURE__ */ jsx9(
|
|
3970
|
+
App,
|
|
3971
|
+
{
|
|
3972
|
+
model: resolvedModel,
|
|
3973
|
+
systemPromptOverride: systemPrompt,
|
|
3974
|
+
maxTurns,
|
|
3975
|
+
initialMessages,
|
|
3976
|
+
initialSessionId,
|
|
3977
|
+
children: /* @__PURE__ */ jsx9(REPL, {})
|
|
3978
|
+
}
|
|
3979
|
+
)
|
|
3989
3980
|
);
|
|
3990
3981
|
await waitUntilExit();
|
|
3991
3982
|
});
|