darkfoo-code 0.2.4 → 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 +360 -706
- 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,14 +2051,13 @@ 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
|
|
1929
|
-
import { useState as
|
|
2059
|
+
import { useState as useState2, useCallback as useCallback2, useEffect, useRef } from "react";
|
|
1930
2060
|
import { Box as Box7, Text as Text7, useApp, useInput as useInput2 } from "ink";
|
|
1931
|
-
import Spinner2 from "ink-spinner";
|
|
1932
2061
|
import { nanoid as nanoid6 } from "nanoid";
|
|
1933
2062
|
|
|
1934
2063
|
// src/components/Banner.tsx
|
|
@@ -1936,535 +2065,25 @@ init_theme();
|
|
|
1936
2065
|
import { memo as memo2 } from "react";
|
|
1937
2066
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
1938
2067
|
|
|
1939
|
-
// src/components/
|
|
1940
|
-
|
|
1941
|
-
import { useState, useEffect, useRef, memo } from "react";
|
|
2068
|
+
// src/components/Mascot.tsx
|
|
2069
|
+
import { memo } from "react";
|
|
1942
2070
|
import { Box, Text } from "ink";
|
|
1943
|
-
|
|
1944
|
-
// src/fox-state.ts
|
|
1945
|
-
var DEFAULT_STATS = {
|
|
1946
|
-
name: "DarkFox",
|
|
1947
|
-
happiness: 70,
|
|
1948
|
-
hunger: 80,
|
|
1949
|
-
energy: 90,
|
|
1950
|
-
timesPetted: 0,
|
|
1951
|
-
timesFed: 0,
|
|
1952
|
-
createdAt: Date.now()
|
|
1953
|
-
};
|
|
1954
|
-
var stats = { ...DEFAULT_STATS };
|
|
1955
|
-
var lastDecayTime = Date.now();
|
|
1956
|
-
function getFoxStats() {
|
|
1957
|
-
decay();
|
|
1958
|
-
return { ...stats };
|
|
1959
|
-
}
|
|
1960
|
-
function setFoxName(name) {
|
|
1961
|
-
stats.name = name;
|
|
1962
|
-
}
|
|
1963
|
-
function petFox() {
|
|
1964
|
-
decay();
|
|
1965
|
-
stats.happiness = Math.min(100, stats.happiness + 15);
|
|
1966
|
-
stats.timesPetted++;
|
|
1967
|
-
const reactions = [
|
|
1968
|
-
`${stats.name} nuzzles your hand.`,
|
|
1969
|
-
`${stats.name} purrs softly.`,
|
|
1970
|
-
`${stats.name}'s tail wags happily.`,
|
|
1971
|
-
`${stats.name} leans into the pets.`,
|
|
1972
|
-
`${stats.name} rolls over for belly rubs.`,
|
|
1973
|
-
`${stats.name} makes a happy chirping sound.`,
|
|
1974
|
-
`${stats.name} bumps your hand with their nose.`
|
|
1975
|
-
];
|
|
1976
|
-
if (stats.happiness >= 95) {
|
|
1977
|
-
return `${stats.name} is absolutely overjoyed! Maximum floof achieved.`;
|
|
1978
|
-
}
|
|
1979
|
-
return reactions[stats.timesPetted % reactions.length];
|
|
1980
|
-
}
|
|
1981
|
-
function feedFox() {
|
|
1982
|
-
decay();
|
|
1983
|
-
stats.hunger = Math.min(100, stats.hunger + 25);
|
|
1984
|
-
stats.happiness = Math.min(100, stats.happiness + 5);
|
|
1985
|
-
stats.timesFed++;
|
|
1986
|
-
const foods = [
|
|
1987
|
-
"a small cookie",
|
|
1988
|
-
"some berries",
|
|
1989
|
-
"a piece of fish",
|
|
1990
|
-
"a tiny sandwich",
|
|
1991
|
-
"some trail mix",
|
|
1992
|
-
"a warm dumpling",
|
|
1993
|
-
"a bit of cheese"
|
|
1994
|
-
];
|
|
1995
|
-
const food = foods[stats.timesFed % foods.length];
|
|
1996
|
-
if (stats.hunger >= 95) {
|
|
1997
|
-
return `${stats.name} nibbles ${food} contentedly. Completely stuffed!`;
|
|
1998
|
-
}
|
|
1999
|
-
return `${stats.name} happily munches on ${food}.`;
|
|
2000
|
-
}
|
|
2001
|
-
function restFox() {
|
|
2002
|
-
decay();
|
|
2003
|
-
stats.energy = Math.min(100, stats.energy + 30);
|
|
2004
|
-
stats.happiness = Math.min(100, stats.happiness + 5);
|
|
2005
|
-
return `${stats.name} curls up for a quick nap... Energy restored!`;
|
|
2006
|
-
}
|
|
2007
|
-
function getFoxComment() {
|
|
2008
|
-
decay();
|
|
2009
|
-
if (stats.hunger < 20) {
|
|
2010
|
-
const hungry = [
|
|
2011
|
-
`${stats.name} looks at you hopefully...`,
|
|
2012
|
-
`${stats.name}'s stomach growls.`,
|
|
2013
|
-
`${stats.name} sniffs around for food.`
|
|
2014
|
-
];
|
|
2015
|
-
return hungry[Math.floor(Math.random() * hungry.length)];
|
|
2016
|
-
}
|
|
2017
|
-
if (stats.energy < 20) {
|
|
2018
|
-
const tired = [
|
|
2019
|
-
`${stats.name} yawns widely.`,
|
|
2020
|
-
`${stats.name}'s eyelids are drooping.`,
|
|
2021
|
-
`${stats.name} stretches and looks sleepy.`
|
|
2022
|
-
];
|
|
2023
|
-
return tired[Math.floor(Math.random() * tired.length)];
|
|
2024
|
-
}
|
|
2025
|
-
if (stats.happiness < 30) {
|
|
2026
|
-
return `${stats.name} looks a bit lonely. Try /pet`;
|
|
2027
|
-
}
|
|
2028
|
-
if (Math.random() < 0.15 && stats.happiness > 60) {
|
|
2029
|
-
const happy = [
|
|
2030
|
-
`${stats.name} watches your code intently.`,
|
|
2031
|
-
`${stats.name} tilts their head curiously.`,
|
|
2032
|
-
`${stats.name} flicks an ear.`,
|
|
2033
|
-
null,
|
|
2034
|
-
null,
|
|
2035
|
-
null
|
|
2036
|
-
];
|
|
2037
|
-
return happy[Math.floor(Math.random() * happy.length)] ?? null;
|
|
2038
|
-
}
|
|
2039
|
-
return null;
|
|
2040
|
-
}
|
|
2041
|
-
function decay() {
|
|
2042
|
-
const now = Date.now();
|
|
2043
|
-
const elapsed = (now - lastDecayTime) / 6e4;
|
|
2044
|
-
if (elapsed < 1) return;
|
|
2045
|
-
lastDecayTime = now;
|
|
2046
|
-
const minutes = Math.floor(elapsed);
|
|
2047
|
-
stats.hunger = Math.max(0, stats.hunger - minutes * 1.5);
|
|
2048
|
-
stats.energy = Math.max(0, stats.energy - minutes * 0.5);
|
|
2049
|
-
if (stats.hunger < 30) stats.happiness = Math.max(0, stats.happiness - minutes * 1);
|
|
2050
|
-
if (stats.energy < 20) stats.happiness = Math.max(0, stats.happiness - minutes * 0.5);
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
// src/components/Fox.tsx
|
|
2054
2071
|
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
2055
|
-
var
|
|
2056
|
-
[
|
|
2057
|
-
" /\\_/\\ ",
|
|
2058
|
-
" ( o.o ) ",
|
|
2059
|
-
" > ^ < ",
|
|
2060
|
-
" /| |\\ ",
|
|
2061
|
-
" (_| |_)",
|
|
2062
|
-
" ~ "
|
|
2063
|
-
],
|
|
2064
|
-
[
|
|
2065
|
-
" /\\_/\\ ",
|
|
2066
|
-
" ( o.o ) ",
|
|
2067
|
-
" > ^ < ",
|
|
2068
|
-
" /| |\\ ",
|
|
2069
|
-
" (_| |_)",
|
|
2070
|
-
" ~ "
|
|
2071
|
-
],
|
|
2072
|
-
[
|
|
2073
|
-
" /\\_/\\ ",
|
|
2074
|
-
" ( o.o ) ",
|
|
2075
|
-
" > < ",
|
|
2076
|
-
" /| |\\ ",
|
|
2077
|
-
" (_| |_)",
|
|
2078
|
-
" ~ "
|
|
2079
|
-
],
|
|
2080
|
-
[
|
|
2081
|
-
" /\\_/\\ ",
|
|
2082
|
-
" ( o.o ) ",
|
|
2083
|
-
" > ^ < ",
|
|
2084
|
-
" /| |\\ ",
|
|
2085
|
-
" (_| |_)",
|
|
2086
|
-
" ~ "
|
|
2087
|
-
],
|
|
2088
|
-
[
|
|
2089
|
-
" /\\_/\\ ",
|
|
2090
|
-
" ( o.o ) ",
|
|
2091
|
-
" > ^ < ",
|
|
2092
|
-
" /| |\\ ",
|
|
2093
|
-
" (_| |_)",
|
|
2094
|
-
" ~ "
|
|
2095
|
-
],
|
|
2096
|
-
[
|
|
2097
|
-
" /\\_/\\ ",
|
|
2098
|
-
" ( o.o ) ",
|
|
2099
|
-
" > < ",
|
|
2100
|
-
" /| |\\ ",
|
|
2101
|
-
" (_| |_)",
|
|
2102
|
-
" ~ "
|
|
2103
|
-
]
|
|
2104
|
-
];
|
|
2105
|
-
var THINKING = [
|
|
2106
|
-
[
|
|
2107
|
-
" /\\_/\\ ",
|
|
2108
|
-
" ( o.o ) ",
|
|
2109
|
-
" > ^ < ",
|
|
2110
|
-
" | | ",
|
|
2111
|
-
" |___| ",
|
|
2112
|
-
" . "
|
|
2113
|
-
],
|
|
2114
|
-
[
|
|
2115
|
-
" /\\_/\\ ",
|
|
2116
|
-
" ( o.- ) ",
|
|
2117
|
-
" > ^ < ",
|
|
2118
|
-
" | | ",
|
|
2119
|
-
" |___| ",
|
|
2120
|
-
" . . "
|
|
2121
|
-
],
|
|
2122
|
-
[
|
|
2123
|
-
" /\\_/\\ ",
|
|
2124
|
-
" ( -.o ) ",
|
|
2125
|
-
" > ^ < ",
|
|
2126
|
-
" | | ",
|
|
2127
|
-
" |___| ",
|
|
2128
|
-
" . . . "
|
|
2129
|
-
],
|
|
2130
|
-
[
|
|
2131
|
-
" /\\_/\\ ",
|
|
2132
|
-
" ( o.o ) ",
|
|
2133
|
-
" > ^ < ",
|
|
2134
|
-
" | | ",
|
|
2135
|
-
" |___| ",
|
|
2136
|
-
" . . "
|
|
2137
|
-
],
|
|
2138
|
-
[
|
|
2139
|
-
" /\\_/\\ ",
|
|
2140
|
-
" ( o.. ) ",
|
|
2141
|
-
" > ^ < ",
|
|
2142
|
-
" | | ",
|
|
2143
|
-
" |___| ",
|
|
2144
|
-
" . "
|
|
2145
|
-
],
|
|
2146
|
-
[
|
|
2147
|
-
" /\\_/\\ ",
|
|
2148
|
-
" ( ..o ) ",
|
|
2149
|
-
" > ^ < ",
|
|
2150
|
-
" | | ",
|
|
2151
|
-
" |___| ",
|
|
2152
|
-
" . . . "
|
|
2153
|
-
]
|
|
2154
|
-
];
|
|
2155
|
-
var WORKING = [
|
|
2156
|
-
[
|
|
2157
|
-
" /\\_/\\ ",
|
|
2158
|
-
" ( >.< ) ",
|
|
2159
|
-
" > ^ < ",
|
|
2160
|
-
" /| |\\ ",
|
|
2161
|
-
" (_| |_)",
|
|
2162
|
-
" ~\\ "
|
|
2163
|
-
],
|
|
2164
|
-
[
|
|
2165
|
-
" /\\_/\\ ",
|
|
2166
|
-
" ( >.< ) ",
|
|
2167
|
-
" > ^ < ",
|
|
2168
|
-
" /| |\\ ",
|
|
2169
|
-
" (_| |_)",
|
|
2170
|
-
" /~ "
|
|
2171
|
-
],
|
|
2172
|
-
[
|
|
2173
|
-
" /\\_/\\ ",
|
|
2174
|
-
" ( >.> ) ",
|
|
2175
|
-
" > ^ < ",
|
|
2176
|
-
" /| |\\ ",
|
|
2177
|
-
" (_| |_)",
|
|
2178
|
-
" ~\\ "
|
|
2179
|
-
],
|
|
2180
|
-
[
|
|
2181
|
-
" /\\_/\\ ",
|
|
2182
|
-
" ( <.< ) ",
|
|
2183
|
-
" > ^ < ",
|
|
2184
|
-
" /| |\\ ",
|
|
2185
|
-
" (_| |_)",
|
|
2186
|
-
" /~ "
|
|
2187
|
-
]
|
|
2188
|
-
];
|
|
2189
|
-
var SUCCESS = [
|
|
2190
|
-
[
|
|
2191
|
-
" /\\_/\\ ",
|
|
2192
|
-
" ( ^.^ ) ",
|
|
2193
|
-
" > w < ",
|
|
2194
|
-
" /| |\\ ",
|
|
2195
|
-
" (_| |_)",
|
|
2196
|
-
" \\~/ * "
|
|
2197
|
-
],
|
|
2198
|
-
[
|
|
2199
|
-
" /\\_/\\ ",
|
|
2200
|
-
" ( ^.^ ) ",
|
|
2201
|
-
" > w < ",
|
|
2202
|
-
" /| * |\\ ",
|
|
2203
|
-
" (_| |_)",
|
|
2204
|
-
" \\~/ "
|
|
2205
|
-
],
|
|
2206
|
-
[
|
|
2207
|
-
" /\\_/\\ ",
|
|
2208
|
-
" ( ^.^ ) ",
|
|
2209
|
-
" > w < ",
|
|
2210
|
-
" /| |\\ ",
|
|
2211
|
-
" (_| |_)",
|
|
2212
|
-
" * \\~/ "
|
|
2213
|
-
]
|
|
2214
|
-
];
|
|
2215
|
-
var ERROR = [
|
|
2216
|
-
[
|
|
2217
|
-
" /\\_/\\ ",
|
|
2218
|
-
" ( ;.; ) ",
|
|
2219
|
-
" > n < ",
|
|
2220
|
-
" | | ",
|
|
2221
|
-
" |___| ",
|
|
2222
|
-
" . "
|
|
2223
|
-
],
|
|
2224
|
-
[
|
|
2225
|
-
" /\\_/\\ ",
|
|
2226
|
-
" ( ;_; ) ",
|
|
2227
|
-
" > n < ",
|
|
2228
|
-
" | | ",
|
|
2229
|
-
" |___| ",
|
|
2230
|
-
" "
|
|
2231
|
-
]
|
|
2232
|
-
];
|
|
2233
|
-
var GREETING = [
|
|
2234
|
-
[
|
|
2235
|
-
" /\\_/\\ ",
|
|
2236
|
-
" ( ^.^ )/",
|
|
2237
|
-
" > w < ",
|
|
2238
|
-
" /| | ",
|
|
2239
|
-
" (_| |) ",
|
|
2240
|
-
" \\~/ "
|
|
2241
|
-
],
|
|
2242
|
-
[
|
|
2243
|
-
" /\\_/\\ ",
|
|
2244
|
-
" ( ^.^ ) ",
|
|
2245
|
-
" > w <| ",
|
|
2246
|
-
" /| | ",
|
|
2247
|
-
" (_| |) ",
|
|
2248
|
-
" \\~/ "
|
|
2249
|
-
],
|
|
2250
|
-
[
|
|
2251
|
-
" /\\_/\\ ",
|
|
2252
|
-
" ( ^.^ )/",
|
|
2253
|
-
" > w < ",
|
|
2254
|
-
" /| | ",
|
|
2255
|
-
" (_| |) ",
|
|
2256
|
-
" \\~/ "
|
|
2257
|
-
],
|
|
2258
|
-
[
|
|
2259
|
-
" /\\_/\\ ",
|
|
2260
|
-
" ( ^.^ ) ",
|
|
2261
|
-
" > w < ",
|
|
2262
|
-
" /| |\\ ",
|
|
2263
|
-
" (_| |_)",
|
|
2264
|
-
" \\~/ "
|
|
2265
|
-
]
|
|
2266
|
-
];
|
|
2267
|
-
var PET = [
|
|
2268
|
-
[
|
|
2269
|
-
" /\\_/\\ ",
|
|
2270
|
-
" ( ^.^ ) ",
|
|
2271
|
-
" > w < ",
|
|
2272
|
-
" /| |\\ ",
|
|
2273
|
-
" (_| ~ |_)",
|
|
2274
|
-
" \\~/ "
|
|
2275
|
-
],
|
|
2276
|
-
[
|
|
2277
|
-
" /\\_/\\ ",
|
|
2278
|
-
" ( >.< ) ",
|
|
2279
|
-
" > w < ",
|
|
2280
|
-
" /| ~ |\\ ",
|
|
2281
|
-
" (_| |_)",
|
|
2282
|
-
" \\~/ "
|
|
2283
|
-
],
|
|
2284
|
-
[
|
|
2285
|
-
" /\\_/\\ ",
|
|
2286
|
-
" ( ^w^ ) ",
|
|
2287
|
-
" > ~ < ",
|
|
2288
|
-
" /| |\\ ",
|
|
2289
|
-
" (_| |_)",
|
|
2290
|
-
" \\~/ * "
|
|
2291
|
-
],
|
|
2292
|
-
[
|
|
2293
|
-
" /\\_/\\ ",
|
|
2294
|
-
" ( ^.^ ) ",
|
|
2295
|
-
" > w < ",
|
|
2296
|
-
" /| |\\ ",
|
|
2297
|
-
" (_| |_)",
|
|
2298
|
-
" * \\~/ "
|
|
2299
|
-
]
|
|
2300
|
-
];
|
|
2301
|
-
var EATING = [
|
|
2302
|
-
[
|
|
2303
|
-
" /\\_/\\ ",
|
|
2304
|
-
" ( o.o )~",
|
|
2305
|
-
" > ^ < o",
|
|
2306
|
-
" /| |\\ ",
|
|
2307
|
-
" (_| |_)",
|
|
2308
|
-
" \\~/ "
|
|
2309
|
-
],
|
|
2310
|
-
[
|
|
2311
|
-
" /\\_/\\ ",
|
|
2312
|
-
" ( >o< ) ",
|
|
2313
|
-
" > ~ < ",
|
|
2314
|
-
" /| |\\ ",
|
|
2315
|
-
" (_| |_)",
|
|
2316
|
-
" \\~/ "
|
|
2317
|
-
],
|
|
2318
|
-
[
|
|
2319
|
-
" /\\_/\\ ",
|
|
2320
|
-
" ( ^.^ ) ",
|
|
2321
|
-
" > o < ",
|
|
2322
|
-
" /| |\\ ",
|
|
2323
|
-
" (_| |_)",
|
|
2324
|
-
" \\~/ "
|
|
2325
|
-
],
|
|
2326
|
-
[
|
|
2327
|
-
" /\\_/\\ ",
|
|
2328
|
-
" ( ^w^ ) ",
|
|
2329
|
-
" > ~ < ",
|
|
2330
|
-
" /| |\\ ",
|
|
2331
|
-
" (_| |_)",
|
|
2332
|
-
" \\~/ "
|
|
2333
|
-
]
|
|
2334
|
-
];
|
|
2335
|
-
var SLEEPING = [
|
|
2336
|
-
[
|
|
2337
|
-
" /\\_/\\ ",
|
|
2338
|
-
" ( -.- ) ",
|
|
2339
|
-
" > ~ < ",
|
|
2340
|
-
" | | ",
|
|
2341
|
-
" |___| ",
|
|
2342
|
-
" z "
|
|
2343
|
-
],
|
|
2344
|
-
[
|
|
2345
|
-
" /\\_/\\ ",
|
|
2346
|
-
" ( -.- ) ",
|
|
2347
|
-
" > ~ < ",
|
|
2348
|
-
" | | ",
|
|
2349
|
-
" |___| ",
|
|
2350
|
-
" z z "
|
|
2351
|
-
],
|
|
2352
|
-
[
|
|
2353
|
-
" /\\_/\\ ",
|
|
2354
|
-
" ( -.- ) ",
|
|
2355
|
-
" > ~ < ",
|
|
2356
|
-
" | | ",
|
|
2357
|
-
" |___| ",
|
|
2358
|
-
" z z z "
|
|
2359
|
-
],
|
|
2360
|
-
[
|
|
2361
|
-
" /\\_/\\ ",
|
|
2362
|
-
" ( -.- ) ",
|
|
2363
|
-
" > ~ < ",
|
|
2364
|
-
" | | ",
|
|
2365
|
-
" |___| ",
|
|
2366
|
-
" z z "
|
|
2367
|
-
]
|
|
2368
|
-
];
|
|
2369
|
-
var FRAME_SETS = {
|
|
2370
|
-
idle: IDLE,
|
|
2371
|
-
thinking: THINKING,
|
|
2372
|
-
working: WORKING,
|
|
2373
|
-
success: SUCCESS,
|
|
2374
|
-
error: ERROR,
|
|
2375
|
-
greeting: GREETING,
|
|
2376
|
-
pet: PET,
|
|
2377
|
-
eating: EATING,
|
|
2378
|
-
sleeping: SLEEPING
|
|
2379
|
-
};
|
|
2380
|
-
var FRAME_SPEEDS = {
|
|
2381
|
-
idle: 800,
|
|
2382
|
-
thinking: 600,
|
|
2383
|
-
working: 500,
|
|
2384
|
-
success: 600,
|
|
2385
|
-
error: 1e3,
|
|
2386
|
-
greeting: 600,
|
|
2387
|
-
pet: 500,
|
|
2388
|
-
eating: 600,
|
|
2389
|
-
sleeping: 1200
|
|
2390
|
-
};
|
|
2391
|
-
var BODY_COLORS = {
|
|
2072
|
+
var MOOD_COLORS = {
|
|
2392
2073
|
idle: "#5eead4",
|
|
2393
2074
|
thinking: "#a78bfa",
|
|
2394
2075
|
working: "#fbbf24",
|
|
2395
2076
|
success: "#4ade80",
|
|
2396
|
-
error: "#f472b6"
|
|
2397
|
-
greeting: "#5eead4",
|
|
2398
|
-
pet: "#f472b6",
|
|
2399
|
-
eating: "#fbbf24",
|
|
2400
|
-
sleeping: "#7e8ea6"
|
|
2401
|
-
};
|
|
2402
|
-
var FACE_COLORS = {
|
|
2403
|
-
idle: "#e2e8f0",
|
|
2404
|
-
thinking: "#5eead4",
|
|
2405
|
-
working: "#5eead4",
|
|
2406
|
-
success: "#4ade80",
|
|
2407
|
-
error: "#f472b6",
|
|
2408
|
-
greeting: "#4ade80",
|
|
2409
|
-
pet: "#f472b6",
|
|
2410
|
-
eating: "#fbbf24",
|
|
2411
|
-
sleeping: "#7e8ea6"
|
|
2077
|
+
error: "#f472b6"
|
|
2412
2078
|
};
|
|
2413
|
-
var
|
|
2414
|
-
const
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
useEffect(() => {
|
|
2420
|
-
setFrameIdx(0);
|
|
2421
|
-
const interval = setInterval(() => {
|
|
2422
|
-
setFrameIdx((prev) => (prev + 1) % frames.length);
|
|
2423
|
-
}, speed);
|
|
2424
|
-
return () => clearInterval(interval);
|
|
2425
|
-
}, [mood, frames.length, speed]);
|
|
2426
|
-
useEffect(() => {
|
|
2427
|
-
if (mood !== "idle") {
|
|
2428
|
-
setBubble(null);
|
|
2429
|
-
return;
|
|
2430
|
-
}
|
|
2431
|
-
const check = () => {
|
|
2432
|
-
const comment = getFoxComment();
|
|
2433
|
-
if (comment) {
|
|
2434
|
-
setBubble(comment);
|
|
2435
|
-
if (bubbleTimer.current) clearTimeout(bubbleTimer.current);
|
|
2436
|
-
bubbleTimer.current = setTimeout(() => setBubble(null), 6e3);
|
|
2437
|
-
}
|
|
2438
|
-
};
|
|
2439
|
-
const interval = setInterval(check, 15e3);
|
|
2440
|
-
return () => {
|
|
2441
|
-
clearInterval(interval);
|
|
2442
|
-
if (bubbleTimer.current) clearTimeout(bubbleTimer.current);
|
|
2443
|
-
};
|
|
2444
|
-
}, [mood]);
|
|
2445
|
-
const frame = frames[frameIdx] ?? frames[0];
|
|
2446
|
-
const bodyColor = BODY_COLORS[mood];
|
|
2447
|
-
const faceColor = FACE_COLORS[mood];
|
|
2448
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2449
|
-
bubble ? /* @__PURE__ */ jsx2(FoxBubble, { text: bubble }) : null,
|
|
2450
|
-
frame.map((line, i) => /* @__PURE__ */ jsx2(Text, { color: i <= 1 ? faceColor : bodyColor, children: line }, i))
|
|
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" })
|
|
2451
2085
|
] });
|
|
2452
2086
|
});
|
|
2453
|
-
function FoxBubble({ text }) {
|
|
2454
|
-
if (!text) return null;
|
|
2455
|
-
const maxW = 28;
|
|
2456
|
-
const display = text.length > maxW ? text.slice(0, maxW - 2) + ".." : text;
|
|
2457
|
-
const w = display.length;
|
|
2458
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
2459
|
-
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "." + "-".repeat(w + 2) + "." }),
|
|
2460
|
-
/* @__PURE__ */ jsxs(Text, { color: theme.dim ?? "#7e8ea6", children: [
|
|
2461
|
-
"| ",
|
|
2462
|
-
/* @__PURE__ */ jsx2(Text, { color: theme.text ?? "#e2e8f0", children: display }),
|
|
2463
|
-
" |"
|
|
2464
|
-
] }),
|
|
2465
|
-
/* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "'" + "-".repeat(w + 2) + "'" })
|
|
2466
|
-
] });
|
|
2467
|
-
}
|
|
2468
2087
|
|
|
2469
2088
|
// src/components/Banner.tsx
|
|
2470
2089
|
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
@@ -2496,26 +2115,20 @@ function gradientBar(width) {
|
|
|
2496
2115
|
color: GRADIENT[Math.round(i / (width - 1) * (GRADIENT.length - 1))]
|
|
2497
2116
|
}));
|
|
2498
2117
|
}
|
|
2499
|
-
var Banner = memo2(function Banner2({ model, cwd, providerName, providerOnline }) {
|
|
2118
|
+
var Banner = memo2(function Banner2({ model, cwd, providerName, providerOnline, mood }) {
|
|
2500
2119
|
const bar = gradientBar(60);
|
|
2501
2120
|
const statusTag = providerOnline ? "[connected]" : "[offline]";
|
|
2502
2121
|
const statusColor = providerOnline ? theme.green ?? "#4ade80" : theme.pink ?? "#f472b6";
|
|
2503
2122
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
|
|
2504
|
-
/* @__PURE__ */ jsxs2(Box2, { children: [
|
|
2505
|
-
/* @__PURE__ */
|
|
2506
|
-
|
|
2507
|
-
/* @__PURE__ */ jsx3(Text2, { color: theme.purple ?? "#a78bfa", bold: true, children: SUBTITLE })
|
|
2508
|
-
] }),
|
|
2509
|
-
/* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, flexDirection: "row", alignItems: "flex-end", children: [
|
|
2510
|
-
/* @__PURE__ */ jsx3(Fox, { mood: "greeting" }),
|
|
2511
|
-
/* @__PURE__ */ jsx3(FoxBubble, { text: "Ready to work." })
|
|
2512
|
-
] })
|
|
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 })
|
|
2513
2126
|
] }),
|
|
2514
|
-
/* @__PURE__ */ jsx3(
|
|
2515
|
-
/* @__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: [
|
|
2516
2129
|
" ",
|
|
2517
2130
|
bar.map((d, i) => /* @__PURE__ */ jsx3(Text2, { color: d.color, children: d.char }, i))
|
|
2518
|
-
] }),
|
|
2131
|
+
] }) }),
|
|
2519
2132
|
/* @__PURE__ */ jsxs2(Box2, { marginTop: 1, marginLeft: 2, children: [
|
|
2520
2133
|
/* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: "model " }),
|
|
2521
2134
|
model ? /* @__PURE__ */ jsx3(Text2, { color: theme.cyan ?? "#5eead4", bold: true, children: model }) : /* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: "--" })
|
|
@@ -2643,11 +2256,10 @@ function MessageRow({ message }) {
|
|
|
2643
2256
|
init_theme();
|
|
2644
2257
|
init_format();
|
|
2645
2258
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
2646
|
-
import Spinner from "ink-spinner";
|
|
2647
2259
|
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
2648
2260
|
function ActiveToolCall({ toolName, args }) {
|
|
2649
2261
|
return /* @__PURE__ */ jsxs4(Box4, { marginLeft: 2, children: [
|
|
2650
|
-
/* @__PURE__ */ jsx5(Text4, { color: theme.cyan, children:
|
|
2262
|
+
/* @__PURE__ */ jsx5(Text4, { color: theme.cyan, children: "..." }),
|
|
2651
2263
|
/* @__PURE__ */ jsxs4(Text4, { bold: true, color: theme.yellow, children: [
|
|
2652
2264
|
" ",
|
|
2653
2265
|
toolName
|
|
@@ -2755,7 +2367,7 @@ var StatusLine = memo4(function StatusLine2({ model, messageCount, tokenEstimate
|
|
|
2755
2367
|
|
|
2756
2368
|
// src/components/UserInput.tsx
|
|
2757
2369
|
init_theme();
|
|
2758
|
-
import { useState
|
|
2370
|
+
import { useState, useCallback, memo as memo5 } from "react";
|
|
2759
2371
|
import { Box as Box6, Text as Text6, useInput } from "ink";
|
|
2760
2372
|
import TextInput from "ink-text-input";
|
|
2761
2373
|
|
|
@@ -2844,12 +2456,15 @@ import { nanoid } from "nanoid";
|
|
|
2844
2456
|
var compactCommand = {
|
|
2845
2457
|
name: "compact",
|
|
2846
2458
|
description: "Compress conversation history to save context",
|
|
2847
|
-
async execute(
|
|
2459
|
+
async execute(args, context) {
|
|
2848
2460
|
if (context.messages.length < 4) {
|
|
2849
2461
|
return { output: "Conversation is too short to compact.", silent: true };
|
|
2850
2462
|
}
|
|
2851
2463
|
const transcript = context.messages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map((m) => `${m.role}: ${m.content}`).join("\n\n");
|
|
2852
|
-
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:
|
|
2853
2468
|
|
|
2854
2469
|
${transcript}`;
|
|
2855
2470
|
try {
|
|
@@ -2923,7 +2538,7 @@ var contextCommand = {
|
|
|
2923
2538
|
}
|
|
2924
2539
|
totalTokens += tokens;
|
|
2925
2540
|
}
|
|
2926
|
-
const sysTokens = 2e3;
|
|
2541
|
+
const sysTokens = context.systemPrompt ? estimateTokens(context.systemPrompt) : 2e3;
|
|
2927
2542
|
totalTokens += sysTokens;
|
|
2928
2543
|
const usage = totalTokens / contextLimit;
|
|
2929
2544
|
const barWidth = 40;
|
|
@@ -2937,7 +2552,7 @@ var contextCommand = {
|
|
|
2937
2552
|
` ~${totalTokens.toLocaleString()} / ${contextLimit.toLocaleString()} tokens`,
|
|
2938
2553
|
"",
|
|
2939
2554
|
" Breakdown:",
|
|
2940
|
-
` System prompt:
|
|
2555
|
+
` System prompt: ${context.systemPrompt ? "" : "~"}${sysTokens.toLocaleString()} tokens`,
|
|
2941
2556
|
...breakdown.map(
|
|
2942
2557
|
(b) => ` ${b.role.padEnd(12)} ${b.count} msgs, ~${b.tokens.toLocaleString()} tokens`
|
|
2943
2558
|
),
|
|
@@ -3027,6 +2642,13 @@ async function saveSession(id, messages, model, cwd) {
|
|
|
3027
2642
|
};
|
|
3028
2643
|
await writeFile2(join2(SESSIONS_DIR, `${id}.json`), JSON.stringify(data, null, 2), "utf-8");
|
|
3029
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
|
+
}
|
|
3030
2652
|
async function loadSession(id) {
|
|
3031
2653
|
try {
|
|
3032
2654
|
const raw = await readFile2(join2(SESSIONS_DIR, `${id}.json`), "utf-8");
|
|
@@ -3689,68 +3311,36 @@ var providerCommand = {
|
|
|
3689
3311
|
}
|
|
3690
3312
|
};
|
|
3691
3313
|
|
|
3692
|
-
// src/commands/
|
|
3693
|
-
var
|
|
3694
|
-
name: "
|
|
3695
|
-
description: "
|
|
3696
|
-
async execute(
|
|
3697
|
-
const
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
}
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
description: "Feed your fox companion",
|
|
3704
|
-
async execute(_args, _context) {
|
|
3705
|
-
const reaction = feedFox();
|
|
3706
|
-
return { output: reaction, silent: true, foxMood: "eating" };
|
|
3707
|
-
}
|
|
3708
|
-
};
|
|
3709
|
-
var restCommand = {
|
|
3710
|
-
name: "rest",
|
|
3711
|
-
aliases: ["sleep", "nap"],
|
|
3712
|
-
description: "Let your fox take a nap",
|
|
3713
|
-
async execute(_args, _context) {
|
|
3714
|
-
const reaction = restFox();
|
|
3715
|
-
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 };
|
|
3716
3325
|
}
|
|
3717
3326
|
};
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
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>)",
|
|
3722
3333
|
async execute(args, _context) {
|
|
3723
|
-
const
|
|
3724
|
-
if (
|
|
3725
|
-
|
|
3726
|
-
setFoxName(newName);
|
|
3727
|
-
return { output: `Your fox is now named "${newName}".`, silent: true };
|
|
3334
|
+
const description = args.trim();
|
|
3335
|
+
if (!description) {
|
|
3336
|
+
return { output: "Usage: /plan <what you want to plan>", silent: true };
|
|
3728
3337
|
}
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
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"
|
|
3734
3343
|
};
|
|
3735
|
-
const age = Math.floor((Date.now() - stats2.createdAt) / 6e4);
|
|
3736
|
-
const ageStr = age < 60 ? `${age}m` : `${Math.floor(age / 60)}h ${age % 60}m`;
|
|
3737
|
-
const lines = [
|
|
3738
|
-
`\x1B[1m\x1B[36m${stats2.name}\x1B[0m`,
|
|
3739
|
-
"",
|
|
3740
|
-
` Happiness [${bar(stats2.happiness)}] ${Math.round(stats2.happiness)}%`,
|
|
3741
|
-
` Hunger [${bar(stats2.hunger)}] ${Math.round(stats2.hunger)}%`,
|
|
3742
|
-
` Energy [${bar(stats2.energy)}] ${Math.round(stats2.energy)}%`,
|
|
3743
|
-
"",
|
|
3744
|
-
` Times petted: ${stats2.timesPetted}`,
|
|
3745
|
-
` Times fed: ${stats2.timesFed}`,
|
|
3746
|
-
` Age: ${ageStr}`,
|
|
3747
|
-
"",
|
|
3748
|
-
"\x1B[2m /pet \u2014 Pet your fox",
|
|
3749
|
-
" /feed \u2014 Feed your fox",
|
|
3750
|
-
" /rest \u2014 Let your fox nap",
|
|
3751
|
-
" /fox name <name> \u2014 Rename\x1B[0m"
|
|
3752
|
-
];
|
|
3753
|
-
return { output: lines.join("\n"), silent: true };
|
|
3754
3344
|
}
|
|
3755
3345
|
};
|
|
3756
3346
|
|
|
@@ -3777,10 +3367,8 @@ var COMMANDS = [
|
|
|
3777
3367
|
filesCommand,
|
|
3778
3368
|
briefCommand,
|
|
3779
3369
|
providerCommand,
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
restCommand,
|
|
3783
|
-
foxCommand
|
|
3370
|
+
renameCommand,
|
|
3371
|
+
planCommand
|
|
3784
3372
|
];
|
|
3785
3373
|
function getCommands() {
|
|
3786
3374
|
return COMMANDS;
|
|
@@ -3818,8 +3406,8 @@ function getCommandNames() {
|
|
|
3818
3406
|
// src/components/UserInput.tsx
|
|
3819
3407
|
import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
3820
3408
|
var UserInput = memo5(function UserInput2({ value, onChange, onSubmit, disabled, history }) {
|
|
3821
|
-
const [historyIdx, setHistoryIdx] =
|
|
3822
|
-
const [suggestion, setSuggestion] =
|
|
3409
|
+
const [historyIdx, setHistoryIdx] = useState(-1);
|
|
3410
|
+
const [suggestion, setSuggestion] = useState("");
|
|
3823
3411
|
useInput((_input, key) => {
|
|
3824
3412
|
if (disabled || !history || history.length === 0) return;
|
|
3825
3413
|
if (key.upArrow) {
|
|
@@ -3906,6 +3494,7 @@ init_system_prompt();
|
|
|
3906
3494
|
init_providers();
|
|
3907
3495
|
init_tools();
|
|
3908
3496
|
init_bash();
|
|
3497
|
+
init_hooks();
|
|
3909
3498
|
init_theme();
|
|
3910
3499
|
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
3911
3500
|
function getContextLimit3(model) {
|
|
@@ -3919,40 +3508,44 @@ function getContextLimit3(model) {
|
|
|
3919
3508
|
return 8192;
|
|
3920
3509
|
}
|
|
3921
3510
|
function REPL({ initialPrompt }) {
|
|
3922
|
-
const { model: initialModel, systemPromptOverride } = useDarkfooContext();
|
|
3511
|
+
const { model: initialModel, systemPromptOverride, maxTurns, initialMessages, initialSessionId } = useDarkfooContext();
|
|
3923
3512
|
const { exit } = useApp();
|
|
3924
|
-
const [model, setModel] =
|
|
3925
|
-
const [messages, setMessages] =
|
|
3926
|
-
const [inputValue, setInputValue] =
|
|
3927
|
-
const [isStreaming, setIsStreaming] =
|
|
3928
|
-
const [streamingText, setStreamingText] =
|
|
3929
|
-
const [activeTool, setActiveTool] =
|
|
3930
|
-
const [toolResults, setToolResults] =
|
|
3931
|
-
const [commandOutput, setCommandOutput] =
|
|
3932
|
-
const [inputHistory, setInputHistory] =
|
|
3933
|
-
const [tokenCounts, setTokenCounts] =
|
|
3934
|
-
const [systemPrompt, setSystemPrompt] =
|
|
3935
|
-
const [
|
|
3936
|
-
const [providerOnline, setProviderOnline] =
|
|
3937
|
-
const abortRef =
|
|
3938
|
-
const hasRun =
|
|
3939
|
-
const sessionId =
|
|
3940
|
-
const messagesRef =
|
|
3513
|
+
const [model, setModel] = useState2(initialModel);
|
|
3514
|
+
const [messages, setMessages] = useState2(initialMessages ?? []);
|
|
3515
|
+
const [inputValue, setInputValue] = useState2("");
|
|
3516
|
+
const [isStreaming, setIsStreaming] = useState2(false);
|
|
3517
|
+
const [streamingText, setStreamingText] = useState2("");
|
|
3518
|
+
const [activeTool, setActiveTool] = useState2(null);
|
|
3519
|
+
const [toolResults, setToolResults] = useState2([]);
|
|
3520
|
+
const [commandOutput, setCommandOutput] = useState2(null);
|
|
3521
|
+
const [inputHistory, setInputHistory] = useState2([]);
|
|
3522
|
+
const [tokenCounts, setTokenCounts] = useState2({ input: 0, output: 0 });
|
|
3523
|
+
const [systemPrompt, setSystemPrompt] = useState2("");
|
|
3524
|
+
const [mascotMood, setMascotMood] = useState2("idle");
|
|
3525
|
+
const [providerOnline, setProviderOnline] = useState2(true);
|
|
3526
|
+
const abortRef = useRef(null);
|
|
3527
|
+
const hasRun = useRef(false);
|
|
3528
|
+
const sessionId = useRef(initialSessionId ?? createSessionId());
|
|
3529
|
+
const messagesRef = useRef(messages);
|
|
3941
3530
|
messagesRef.current = messages;
|
|
3942
|
-
const modelRef =
|
|
3531
|
+
const modelRef = useRef(model);
|
|
3943
3532
|
modelRef.current = model;
|
|
3944
|
-
const systemPromptRef =
|
|
3533
|
+
const systemPromptRef = useRef(systemPrompt);
|
|
3945
3534
|
systemPromptRef.current = systemPrompt;
|
|
3946
3535
|
const tools = getTools();
|
|
3947
3536
|
const cwd = process.cwd();
|
|
3948
|
-
|
|
3537
|
+
useEffect(() => {
|
|
3949
3538
|
if (systemPromptOverride) {
|
|
3950
3539
|
setSystemPrompt(systemPromptOverride);
|
|
3951
3540
|
} else {
|
|
3952
3541
|
buildSystemPrompt(tools, cwd).then(setSystemPrompt);
|
|
3953
3542
|
}
|
|
3954
3543
|
}, []);
|
|
3955
|
-
|
|
3544
|
+
useEffect(() => {
|
|
3545
|
+
executeHooks("session_start", { cwd }).catch(() => {
|
|
3546
|
+
});
|
|
3547
|
+
}, []);
|
|
3548
|
+
useEffect(() => {
|
|
3956
3549
|
const provider = getProvider();
|
|
3957
3550
|
provider.healthCheck().then((ok) => {
|
|
3958
3551
|
setProviderOnline(ok);
|
|
@@ -3985,16 +3578,18 @@ function REPL({ initialPrompt }) {
|
|
|
3985
3578
|
});
|
|
3986
3579
|
}, []);
|
|
3987
3580
|
const clearMessages = useCallback2(() => setMessages([]), []);
|
|
3988
|
-
const commandContextRef =
|
|
3581
|
+
const commandContextRef = useRef({
|
|
3989
3582
|
messages,
|
|
3990
3583
|
model,
|
|
3991
3584
|
cwd,
|
|
3585
|
+
sessionId: sessionId.current,
|
|
3992
3586
|
setModel,
|
|
3993
3587
|
clearMessages,
|
|
3994
3588
|
exit,
|
|
3995
|
-
tokenCounts
|
|
3589
|
+
tokenCounts,
|
|
3590
|
+
systemPrompt
|
|
3996
3591
|
});
|
|
3997
|
-
commandContextRef.current = { messages, model, cwd, setModel, clearMessages, exit, tokenCounts };
|
|
3592
|
+
commandContextRef.current = { messages, model, cwd, sessionId: sessionId.current, setModel, clearMessages, exit, tokenCounts, systemPrompt };
|
|
3998
3593
|
const addToHistory = useCallback2((input) => {
|
|
3999
3594
|
setInputHistory((prev) => {
|
|
4000
3595
|
const filtered = prev.filter((h) => h !== input);
|
|
@@ -4014,42 +3609,45 @@ function REPL({ initialPrompt }) {
|
|
|
4014
3609
|
setStreamingText("");
|
|
4015
3610
|
setToolResults([]);
|
|
4016
3611
|
setCommandOutput(null);
|
|
4017
|
-
|
|
3612
|
+
setMascotMood("thinking");
|
|
4018
3613
|
const controller = new AbortController();
|
|
4019
3614
|
abortRef.current = controller;
|
|
4020
3615
|
const allMessages = [...messagesRef.current, userMsg];
|
|
4021
3616
|
const currentModel = modelRef.current;
|
|
4022
3617
|
const currentSystemPrompt = systemPromptRef.current;
|
|
4023
|
-
setTokenCounts((prev) => ({
|
|
4024
|
-
...prev,
|
|
4025
|
-
input: prev.input + Math.ceil(userMessage.length / 4)
|
|
4026
|
-
}));
|
|
4027
3618
|
try {
|
|
4028
3619
|
for await (const event of query({
|
|
4029
3620
|
model: currentModel,
|
|
4030
3621
|
messages: allMessages,
|
|
4031
3622
|
tools,
|
|
4032
3623
|
systemPrompt: currentSystemPrompt,
|
|
4033
|
-
signal: controller.signal
|
|
3624
|
+
signal: controller.signal,
|
|
3625
|
+
maxTurns
|
|
4034
3626
|
})) {
|
|
4035
3627
|
if (controller.signal.aborted) break;
|
|
4036
3628
|
switch (event.type) {
|
|
4037
3629
|
case "text_delta":
|
|
4038
3630
|
setStreamingText((prev) => prev + event.text);
|
|
4039
|
-
|
|
3631
|
+
setMascotMood("idle");
|
|
4040
3632
|
break;
|
|
4041
3633
|
case "tool_call":
|
|
4042
3634
|
setActiveTool({ name: event.toolCall.function.name, args: event.toolCall.function.arguments });
|
|
4043
|
-
|
|
3635
|
+
setMascotMood("working");
|
|
4044
3636
|
break;
|
|
4045
3637
|
case "tool_result":
|
|
4046
3638
|
setActiveTool(null);
|
|
4047
|
-
|
|
3639
|
+
setMascotMood(event.isError ? "error" : "working");
|
|
4048
3640
|
setToolResults((prev) => [
|
|
4049
3641
|
...prev,
|
|
4050
3642
|
{ id: nanoid6(), toolName: event.toolName, output: event.output, isError: event.isError }
|
|
4051
3643
|
]);
|
|
4052
3644
|
break;
|
|
3645
|
+
case "usage":
|
|
3646
|
+
setTokenCounts((prev) => ({
|
|
3647
|
+
input: prev.input + event.inputTokens,
|
|
3648
|
+
output: prev.output + event.outputTokens
|
|
3649
|
+
}));
|
|
3650
|
+
break;
|
|
4053
3651
|
case "assistant_message":
|
|
4054
3652
|
setMessages((prev) => {
|
|
4055
3653
|
const updated = [...prev, event.message];
|
|
@@ -4057,14 +3655,10 @@ function REPL({ initialPrompt }) {
|
|
|
4057
3655
|
});
|
|
4058
3656
|
return updated;
|
|
4059
3657
|
});
|
|
4060
|
-
setTokenCounts((prev) => ({
|
|
4061
|
-
...prev,
|
|
4062
|
-
output: prev.output + Math.ceil((event.message.content?.length ?? 0) / 4)
|
|
4063
|
-
}));
|
|
4064
3658
|
setStreamingText("");
|
|
4065
3659
|
break;
|
|
4066
3660
|
case "error":
|
|
4067
|
-
|
|
3661
|
+
setMascotMood("error");
|
|
4068
3662
|
setMessages((prev) => [
|
|
4069
3663
|
...prev,
|
|
4070
3664
|
{ id: nanoid6(), role: "assistant", content: `Error: ${event.error}`, timestamp: Date.now() }
|
|
@@ -4085,8 +3679,8 @@ function REPL({ initialPrompt }) {
|
|
|
4085
3679
|
setStreamingText("");
|
|
4086
3680
|
setActiveTool(null);
|
|
4087
3681
|
setToolResults([]);
|
|
4088
|
-
|
|
4089
|
-
setTimeout(() =>
|
|
3682
|
+
setMascotMood((prev) => prev === "error" ? "error" : "success");
|
|
3683
|
+
setTimeout(() => setMascotMood("idle"), 2e3);
|
|
4090
3684
|
abortRef.current = null;
|
|
4091
3685
|
}
|
|
4092
3686
|
},
|
|
@@ -4109,8 +3703,8 @@ function REPL({ initialPrompt }) {
|
|
|
4109
3703
|
setMessages(result.replaceMessages);
|
|
4110
3704
|
}
|
|
4111
3705
|
if (result.foxMood) {
|
|
4112
|
-
|
|
4113
|
-
setTimeout(() =>
|
|
3706
|
+
setMascotMood(result.foxMood);
|
|
3707
|
+
setTimeout(() => setMascotMood("idle"), 3e3);
|
|
4114
3708
|
}
|
|
4115
3709
|
if (result.exit) return;
|
|
4116
3710
|
if (!result.silent && result.output) {
|
|
@@ -4146,14 +3740,14 @@ function REPL({ initialPrompt }) {
|
|
|
4146
3740
|
},
|
|
4147
3741
|
[addToHistory, exit, cwd, runQuery]
|
|
4148
3742
|
);
|
|
4149
|
-
|
|
3743
|
+
useEffect(() => {
|
|
4150
3744
|
if (initialPrompt && !hasRun.current) {
|
|
4151
3745
|
hasRun.current = true;
|
|
4152
3746
|
runQuery(initialPrompt);
|
|
4153
3747
|
}
|
|
4154
3748
|
}, [initialPrompt, runQuery]);
|
|
4155
|
-
const autoCompactRef =
|
|
4156
|
-
|
|
3749
|
+
const autoCompactRef = useRef(false);
|
|
3750
|
+
useEffect(() => {
|
|
4157
3751
|
if (isStreaming || autoCompactRef.current || messages.length < 6) return;
|
|
4158
3752
|
const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
|
|
4159
3753
|
const estTokens = Math.ceil(totalChars / 4) + 2e3;
|
|
@@ -4197,7 +3791,7 @@ ${result.content}
|
|
|
4197
3791
|
}
|
|
4198
3792
|
});
|
|
4199
3793
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", padding: 1, children: [
|
|
4200
|
-
/* @__PURE__ */ jsx8(Banner, { model, cwd, providerName: getActiveProviderName(), providerOnline }),
|
|
3794
|
+
/* @__PURE__ */ jsx8(Banner, { model, cwd, providerName: getActiveProviderName(), providerOnline, mood: mascotMood }),
|
|
4201
3795
|
/* @__PURE__ */ jsx8(Messages, { messages }),
|
|
4202
3796
|
commandOutput ? /* @__PURE__ */ jsx8(Box7, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text7, { children: commandOutput }) }) : null,
|
|
4203
3797
|
toolResults.map((tr) => /* @__PURE__ */ jsx8(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
|
|
@@ -4205,28 +3799,22 @@ ${result.content}
|
|
|
4205
3799
|
isStreaming && streamingText ? /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, children: [
|
|
4206
3800
|
/* @__PURE__ */ jsx8(Text7, { color: theme.cyan, children: "\u23BF " }),
|
|
4207
3801
|
/* @__PURE__ */ jsx8(Text7, { color: theme.text, wrap: "wrap", children: streamingText }),
|
|
4208
|
-
/* @__PURE__ */
|
|
4209
|
-
" ",
|
|
4210
|
-
/* @__PURE__ */ jsx8(Spinner2, { type: "dots" })
|
|
4211
|
-
] })
|
|
3802
|
+
/* @__PURE__ */ jsx8(Text7, { color: theme.dim, children: " ..." })
|
|
4212
3803
|
] }) : null,
|
|
4213
3804
|
isStreaming && !streamingText && !activeTool ? /* @__PURE__ */ jsxs7(Box7, { marginLeft: 2, children: [
|
|
4214
|
-
/* @__PURE__ */ jsx8(Text7, { color: theme.cyan, children:
|
|
4215
|
-
/* @__PURE__ */ jsx8(Text7, { color: theme.dim, children: " Thinking
|
|
3805
|
+
/* @__PURE__ */ jsx8(Text7, { color: theme.cyan, children: "..." }),
|
|
3806
|
+
/* @__PURE__ */ jsx8(Text7, { color: theme.dim, children: " Thinking" })
|
|
4216
3807
|
] }) : null,
|
|
4217
|
-
/* @__PURE__ */
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
) }),
|
|
4228
|
-
/* @__PURE__ */ jsx8(Box7, { marginLeft: 1, children: /* @__PURE__ */ jsx8(Fox, { mood: foxMood }) })
|
|
4229
|
-
] }),
|
|
3808
|
+
/* @__PURE__ */ jsx8(
|
|
3809
|
+
UserInput,
|
|
3810
|
+
{
|
|
3811
|
+
value: inputValue,
|
|
3812
|
+
onChange: setInputValue,
|
|
3813
|
+
onSubmit: handleSubmit,
|
|
3814
|
+
disabled: isStreaming,
|
|
3815
|
+
history: inputHistory
|
|
3816
|
+
}
|
|
3817
|
+
),
|
|
4230
3818
|
/* @__PURE__ */ jsx8(
|
|
4231
3819
|
StatusLine,
|
|
4232
3820
|
{
|
|
@@ -4243,8 +3831,13 @@ ${result.content}
|
|
|
4243
3831
|
init_providers();
|
|
4244
3832
|
import { jsx as jsx9 } from "react/jsx-runtime";
|
|
4245
3833
|
var program = new Command();
|
|
4246
|
-
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) => {
|
|
4247
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;
|
|
4248
3841
|
await loadProviderSettings();
|
|
4249
3842
|
if (provider) {
|
|
4250
3843
|
try {
|
|
@@ -4293,19 +3886,35 @@ program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assista
|
|
|
4293
3886
|
controller.abort();
|
|
4294
3887
|
process.exit(0);
|
|
4295
3888
|
});
|
|
3889
|
+
const jsonMode = options.outputFormat === "json";
|
|
3890
|
+
let jsonContent = "";
|
|
3891
|
+
const jsonToolCalls = [];
|
|
3892
|
+
const jsonToolResults = [];
|
|
4296
3893
|
for await (const event of query2({
|
|
4297
3894
|
model: resolvedModel,
|
|
4298
3895
|
messages: [userMsg],
|
|
4299
3896
|
tools,
|
|
4300
3897
|
systemPrompt: sysPrompt,
|
|
4301
|
-
signal: controller.signal
|
|
3898
|
+
signal: controller.signal,
|
|
3899
|
+
maxTurns
|
|
4302
3900
|
})) {
|
|
4303
3901
|
switch (event.type) {
|
|
4304
3902
|
case "text_delta":
|
|
4305
|
-
|
|
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
|
+
}
|
|
4306
3913
|
break;
|
|
4307
3914
|
case "tool_result":
|
|
4308
|
-
if (
|
|
3915
|
+
if (jsonMode) {
|
|
3916
|
+
jsonToolResults.push({ tool: event.toolName, output: event.output, isError: event.isError });
|
|
3917
|
+
} else if (event.isError) {
|
|
4309
3918
|
process.stderr.write(`
|
|
4310
3919
|
[${event.toolName}] Error: ${event.output}
|
|
4311
3920
|
`);
|
|
@@ -4318,11 +3927,56 @@ Error: ${event.error}
|
|
|
4318
3927
|
break;
|
|
4319
3928
|
}
|
|
4320
3929
|
}
|
|
4321
|
-
|
|
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
|
+
}
|
|
4322
3938
|
process.exit(0);
|
|
4323
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
|
+
}
|
|
4324
3968
|
const { waitUntilExit } = render(
|
|
4325
|
-
/* @__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
|
+
)
|
|
4326
3980
|
);
|
|
4327
3981
|
await waitUntilExit();
|
|
4328
3982
|
});
|