darkfoo-code 0.1.4 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +772 -149
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1785,7 +1785,7 @@ var init_tasks = __esm({
1785
1785
  async call(_input, _context) {
1786
1786
  const { tasks } = getAppState();
1787
1787
  if (tasks.length === 0) return { output: "No tasks." };
1788
- const statusIcon = { pending: "\u25CB", in_progress: "\u25D1", completed: "\u25CF" };
1788
+ const statusIcon = { pending: "[ ]", in_progress: "[~]", completed: "[x]" };
1789
1789
  const lines = tasks.map(
1790
1790
  (t) => `${statusIcon[t.status]} #${t.id} [${t.status}] ${t.subject}`
1791
1791
  );
@@ -1926,15 +1926,547 @@ function App({ model, systemPromptOverride, children }) {
1926
1926
  }
1927
1927
 
1928
1928
  // src/repl.tsx
1929
- import { useState as useState2, useCallback as useCallback2, useEffect, useRef } from "react";
1930
- import { Box as Box6, Text as Text6, useApp, useInput as useInput2 } from "ink";
1929
+ import { useState as useState3, useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2 } from "react";
1930
+ import { Box as Box7, Text as Text7, useApp, useInput as useInput2 } from "ink";
1931
1931
  import Spinner2 from "ink-spinner";
1932
1932
  import { nanoid as nanoid6 } from "nanoid";
1933
1933
 
1934
1934
  // src/components/Banner.tsx
1935
1935
  init_theme();
1936
+ import { Box as Box2, Text as Text2 } from "ink";
1937
+
1938
+ // src/components/Fox.tsx
1939
+ init_theme();
1940
+ import { useState, useEffect, useRef } from "react";
1936
1941
  import { Box, Text } from "ink";
1942
+
1943
+ // src/fox-state.ts
1944
+ var DEFAULT_STATS = {
1945
+ name: "DarkFox",
1946
+ happiness: 70,
1947
+ hunger: 80,
1948
+ energy: 90,
1949
+ timesPetted: 0,
1950
+ timesFed: 0,
1951
+ createdAt: Date.now()
1952
+ };
1953
+ var stats = { ...DEFAULT_STATS };
1954
+ var lastDecayTime = Date.now();
1955
+ function getFoxStats() {
1956
+ decay();
1957
+ return { ...stats };
1958
+ }
1959
+ function setFoxName(name) {
1960
+ stats.name = name;
1961
+ }
1962
+ function petFox() {
1963
+ decay();
1964
+ stats.happiness = Math.min(100, stats.happiness + 15);
1965
+ stats.timesPetted++;
1966
+ const reactions = [
1967
+ `${stats.name} nuzzles your hand.`,
1968
+ `${stats.name} purrs softly.`,
1969
+ `${stats.name}'s tail wags happily.`,
1970
+ `${stats.name} leans into the pets.`,
1971
+ `${stats.name} rolls over for belly rubs.`,
1972
+ `${stats.name} makes a happy chirping sound.`,
1973
+ `${stats.name} bumps your hand with their nose.`
1974
+ ];
1975
+ if (stats.happiness >= 95) {
1976
+ return `${stats.name} is absolutely overjoyed! Maximum floof achieved.`;
1977
+ }
1978
+ return reactions[stats.timesPetted % reactions.length];
1979
+ }
1980
+ function feedFox() {
1981
+ decay();
1982
+ stats.hunger = Math.min(100, stats.hunger + 25);
1983
+ stats.happiness = Math.min(100, stats.happiness + 5);
1984
+ stats.timesFed++;
1985
+ const foods = [
1986
+ "a small cookie",
1987
+ "some berries",
1988
+ "a piece of fish",
1989
+ "a tiny sandwich",
1990
+ "some trail mix",
1991
+ "a warm dumpling",
1992
+ "a bit of cheese"
1993
+ ];
1994
+ const food = foods[stats.timesFed % foods.length];
1995
+ if (stats.hunger >= 95) {
1996
+ return `${stats.name} nibbles ${food} contentedly. Completely stuffed!`;
1997
+ }
1998
+ return `${stats.name} happily munches on ${food}.`;
1999
+ }
2000
+ function restFox() {
2001
+ decay();
2002
+ stats.energy = Math.min(100, stats.energy + 30);
2003
+ stats.happiness = Math.min(100, stats.happiness + 5);
2004
+ return `${stats.name} curls up for a quick nap... Energy restored!`;
2005
+ }
2006
+ function getFoxComment() {
2007
+ decay();
2008
+ if (stats.hunger < 20) {
2009
+ const hungry = [
2010
+ `${stats.name} looks at you hopefully...`,
2011
+ `${stats.name}'s stomach growls.`,
2012
+ `${stats.name} sniffs around for food.`
2013
+ ];
2014
+ return hungry[Math.floor(Math.random() * hungry.length)];
2015
+ }
2016
+ if (stats.energy < 20) {
2017
+ const tired = [
2018
+ `${stats.name} yawns widely.`,
2019
+ `${stats.name}'s eyelids are drooping.`,
2020
+ `${stats.name} stretches and looks sleepy.`
2021
+ ];
2022
+ return tired[Math.floor(Math.random() * tired.length)];
2023
+ }
2024
+ if (stats.happiness < 30) {
2025
+ return `${stats.name} looks a bit lonely. Try /pet`;
2026
+ }
2027
+ if (Math.random() < 0.15 && stats.happiness > 60) {
2028
+ const happy = [
2029
+ `${stats.name} watches your code intently.`,
2030
+ `${stats.name} tilts their head curiously.`,
2031
+ `${stats.name} flicks an ear.`,
2032
+ null,
2033
+ null,
2034
+ null
2035
+ ];
2036
+ return happy[Math.floor(Math.random() * happy.length)] ?? null;
2037
+ }
2038
+ return null;
2039
+ }
2040
+ function decay() {
2041
+ const now = Date.now();
2042
+ const elapsed = (now - lastDecayTime) / 6e4;
2043
+ if (elapsed < 1) return;
2044
+ lastDecayTime = now;
2045
+ const minutes = Math.floor(elapsed);
2046
+ stats.hunger = Math.max(0, stats.hunger - minutes * 1.5);
2047
+ stats.energy = Math.max(0, stats.energy - minutes * 0.5);
2048
+ if (stats.hunger < 30) stats.happiness = Math.max(0, stats.happiness - minutes * 1);
2049
+ if (stats.energy < 20) stats.happiness = Math.max(0, stats.happiness - minutes * 0.5);
2050
+ }
2051
+
2052
+ // src/components/Fox.tsx
1937
2053
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
2054
+ var IDLE = [
2055
+ [
2056
+ " /\\_/\\ ",
2057
+ " ( o.o ) ",
2058
+ " > ^ < ",
2059
+ " /| |\\ ",
2060
+ " (_| |_)",
2061
+ " ~ "
2062
+ ],
2063
+ [
2064
+ " /\\_/\\ ",
2065
+ " ( o.o ) ",
2066
+ " > ^ < ",
2067
+ " /| |\\ ",
2068
+ " (_| |_)",
2069
+ " ~ "
2070
+ ],
2071
+ [
2072
+ " /\\_/\\ ",
2073
+ " ( o.o ) ",
2074
+ " > < ",
2075
+ " /| |\\ ",
2076
+ " (_| |_)",
2077
+ " ~ "
2078
+ ],
2079
+ [
2080
+ " /\\_/\\ ",
2081
+ " ( o.o ) ",
2082
+ " > ^ < ",
2083
+ " /| |\\ ",
2084
+ " (_| |_)",
2085
+ " ~ "
2086
+ ],
2087
+ [
2088
+ " /\\_/\\ ",
2089
+ " ( o.o ) ",
2090
+ " > ^ < ",
2091
+ " /| |\\ ",
2092
+ " (_| |_)",
2093
+ " ~ "
2094
+ ],
2095
+ [
2096
+ " /\\_/\\ ",
2097
+ " ( o.o ) ",
2098
+ " > < ",
2099
+ " /| |\\ ",
2100
+ " (_| |_)",
2101
+ " ~ "
2102
+ ]
2103
+ ];
2104
+ var THINKING = [
2105
+ [
2106
+ " /\\_/\\ ",
2107
+ " ( o.o ) ",
2108
+ " > ^ < ",
2109
+ " | | ",
2110
+ " |___| ",
2111
+ " . "
2112
+ ],
2113
+ [
2114
+ " /\\_/\\ ",
2115
+ " ( o.- ) ",
2116
+ " > ^ < ",
2117
+ " | | ",
2118
+ " |___| ",
2119
+ " . . "
2120
+ ],
2121
+ [
2122
+ " /\\_/\\ ",
2123
+ " ( -.o ) ",
2124
+ " > ^ < ",
2125
+ " | | ",
2126
+ " |___| ",
2127
+ " . . . "
2128
+ ],
2129
+ [
2130
+ " /\\_/\\ ",
2131
+ " ( o.o ) ",
2132
+ " > ^ < ",
2133
+ " | | ",
2134
+ " |___| ",
2135
+ " . . "
2136
+ ],
2137
+ [
2138
+ " /\\_/\\ ",
2139
+ " ( o.. ) ",
2140
+ " > ^ < ",
2141
+ " | | ",
2142
+ " |___| ",
2143
+ " . "
2144
+ ],
2145
+ [
2146
+ " /\\_/\\ ",
2147
+ " ( ..o ) ",
2148
+ " > ^ < ",
2149
+ " | | ",
2150
+ " |___| ",
2151
+ " . . . "
2152
+ ]
2153
+ ];
2154
+ var WORKING = [
2155
+ [
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
+ var SUCCESS = [
2189
+ [
2190
+ " /\\_/\\ ",
2191
+ " ( ^.^ ) ",
2192
+ " > w < ",
2193
+ " /| |\\ ",
2194
+ " (_| |_)",
2195
+ " \\~/ * "
2196
+ ],
2197
+ [
2198
+ " ",
2199
+ " /\\_/\\ ",
2200
+ " ( ^.^ ) ",
2201
+ " > w < ",
2202
+ " /| * |\\ ",
2203
+ " \\~/ "
2204
+ ],
2205
+ [
2206
+ " /\\_/\\ ",
2207
+ " ( ^.^ ) ",
2208
+ " > w < ",
2209
+ " /| |\\ ",
2210
+ " (_| |_)",
2211
+ " * \\~/ "
2212
+ ]
2213
+ ];
2214
+ var ERROR = [
2215
+ [
2216
+ " /\\_/\\ ",
2217
+ " ( ;.; ) ",
2218
+ " > n < ",
2219
+ " | | ",
2220
+ " |___| ",
2221
+ " . "
2222
+ ],
2223
+ [
2224
+ " /\\_/\\ ",
2225
+ " ( ;_; ) ",
2226
+ " > n < ",
2227
+ " | | ",
2228
+ " |___| ",
2229
+ " "
2230
+ ]
2231
+ ];
2232
+ var GREETING = [
2233
+ [
2234
+ " /\\_/\\ ",
2235
+ " ( ^.^ )/",
2236
+ " > w < ",
2237
+ " /| | ",
2238
+ " (_| |) ",
2239
+ " \\~/ "
2240
+ ],
2241
+ [
2242
+ " /\\_/\\ ",
2243
+ " ( ^.^ ) ",
2244
+ " > w <| ",
2245
+ " /| | ",
2246
+ " (_| |) ",
2247
+ " \\~/ "
2248
+ ],
2249
+ [
2250
+ " /\\_/\\ ",
2251
+ " ( ^.^ )/",
2252
+ " > w < ",
2253
+ " /| | ",
2254
+ " (_| |) ",
2255
+ " \\~/ "
2256
+ ],
2257
+ [
2258
+ " /\\_/\\ ",
2259
+ " ( ^.^ ) ",
2260
+ " > w < ",
2261
+ " /| |\\ ",
2262
+ " (_| |_)",
2263
+ " \\~/ "
2264
+ ]
2265
+ ];
2266
+ var PET = [
2267
+ [
2268
+ " /\\_/\\ ",
2269
+ " ( ^.^ ) ",
2270
+ " > w < ",
2271
+ " /| |\\ ",
2272
+ " (_| ~ |_)",
2273
+ " \\~/ "
2274
+ ],
2275
+ [
2276
+ " /\\_/\\ ",
2277
+ " ( >.< ) ",
2278
+ " > w < ",
2279
+ " /| ~ |\\ ",
2280
+ " (_| |_)",
2281
+ " \\~/ "
2282
+ ],
2283
+ [
2284
+ " /\\_/\\ ",
2285
+ " ( ^w^ ) ",
2286
+ " > ~ < ",
2287
+ " /| |\\ ",
2288
+ " (_| |_)",
2289
+ " \\~/ * "
2290
+ ],
2291
+ [
2292
+ " /\\_/\\ ",
2293
+ " ( ^.^ ) ",
2294
+ " > w < ",
2295
+ " /| |\\ ",
2296
+ " (_| |_)",
2297
+ " * \\~/ "
2298
+ ]
2299
+ ];
2300
+ var EATING = [
2301
+ [
2302
+ " /\\_/\\ ",
2303
+ " ( o.o )~",
2304
+ " > ^ < o",
2305
+ " /| |\\ ",
2306
+ " (_| |_)",
2307
+ " \\~/ "
2308
+ ],
2309
+ [
2310
+ " /\\_/\\ ",
2311
+ " ( >o< ) ",
2312
+ " > ~ < ",
2313
+ " /| |\\ ",
2314
+ " (_| |_)",
2315
+ " \\~/ "
2316
+ ],
2317
+ [
2318
+ " /\\_/\\ ",
2319
+ " ( ^.^ ) ",
2320
+ " > o < ",
2321
+ " /| |\\ ",
2322
+ " (_| |_)",
2323
+ " \\~/ "
2324
+ ],
2325
+ [
2326
+ " /\\_/\\ ",
2327
+ " ( ^w^ ) ",
2328
+ " > ~ < ",
2329
+ " /| |\\ ",
2330
+ " (_| |_)",
2331
+ " \\~/ "
2332
+ ]
2333
+ ];
2334
+ var SLEEPING = [
2335
+ [
2336
+ " /\\_/\\ ",
2337
+ " ( -.- ) ",
2338
+ " > ~ < ",
2339
+ " | | ",
2340
+ " |___| ",
2341
+ " z "
2342
+ ],
2343
+ [
2344
+ " /\\_/\\ ",
2345
+ " ( -.- ) ",
2346
+ " > ~ < ",
2347
+ " | | ",
2348
+ " |___| ",
2349
+ " z z "
2350
+ ],
2351
+ [
2352
+ " /\\_/\\ ",
2353
+ " ( -.- ) ",
2354
+ " > ~ < ",
2355
+ " | | ",
2356
+ " |___| ",
2357
+ " z z z "
2358
+ ],
2359
+ [
2360
+ " /\\_/\\ ",
2361
+ " ( -.- ) ",
2362
+ " > ~ < ",
2363
+ " | | ",
2364
+ " |___| ",
2365
+ " z z "
2366
+ ]
2367
+ ];
2368
+ var FRAME_SETS = {
2369
+ idle: IDLE,
2370
+ thinking: THINKING,
2371
+ working: WORKING,
2372
+ success: SUCCESS,
2373
+ error: ERROR,
2374
+ greeting: GREETING,
2375
+ pet: PET,
2376
+ eating: EATING,
2377
+ sleeping: SLEEPING
2378
+ };
2379
+ var FRAME_SPEEDS = {
2380
+ idle: 500,
2381
+ thinking: 350,
2382
+ working: 200,
2383
+ success: 400,
2384
+ error: 800,
2385
+ greeting: 350,
2386
+ pet: 300,
2387
+ eating: 350,
2388
+ sleeping: 900
2389
+ };
2390
+ var BODY_COLORS = {
2391
+ idle: "#5eead4",
2392
+ thinking: "#a78bfa",
2393
+ working: "#fbbf24",
2394
+ success: "#4ade80",
2395
+ error: "#f472b6",
2396
+ greeting: "#5eead4",
2397
+ pet: "#f472b6",
2398
+ eating: "#fbbf24",
2399
+ sleeping: "#7e8ea6"
2400
+ };
2401
+ var FACE_COLORS = {
2402
+ idle: "#e2e8f0",
2403
+ thinking: "#5eead4",
2404
+ working: "#5eead4",
2405
+ success: "#4ade80",
2406
+ error: "#f472b6",
2407
+ greeting: "#4ade80",
2408
+ pet: "#f472b6",
2409
+ eating: "#fbbf24",
2410
+ sleeping: "#7e8ea6"
2411
+ };
2412
+ function Fox({ mood = "idle" }) {
2413
+ const [frameIdx, setFrameIdx] = useState(0);
2414
+ const [bubble, setBubble] = useState(null);
2415
+ const frames = FRAME_SETS[mood];
2416
+ const speed = FRAME_SPEEDS[mood];
2417
+ const bubbleTimer = useRef(null);
2418
+ useEffect(() => {
2419
+ setFrameIdx(0);
2420
+ const interval = setInterval(() => {
2421
+ setFrameIdx((prev) => (prev + 1) % frames.length);
2422
+ }, speed);
2423
+ return () => clearInterval(interval);
2424
+ }, [mood, frames.length, speed]);
2425
+ useEffect(() => {
2426
+ if (mood !== "idle") {
2427
+ setBubble(null);
2428
+ return;
2429
+ }
2430
+ const check = () => {
2431
+ const comment = getFoxComment();
2432
+ if (comment) {
2433
+ setBubble(comment);
2434
+ if (bubbleTimer.current) clearTimeout(bubbleTimer.current);
2435
+ bubbleTimer.current = setTimeout(() => setBubble(null), 6e3);
2436
+ }
2437
+ };
2438
+ const interval = setInterval(check, 15e3);
2439
+ return () => {
2440
+ clearInterval(interval);
2441
+ if (bubbleTimer.current) clearTimeout(bubbleTimer.current);
2442
+ };
2443
+ }, [mood]);
2444
+ const frame = frames[frameIdx] ?? frames[0];
2445
+ const bodyColor = BODY_COLORS[mood];
2446
+ const faceColor = FACE_COLORS[mood];
2447
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2448
+ bubble ? /* @__PURE__ */ jsx2(FoxBubble, { text: bubble }) : null,
2449
+ frame.map((line, i) => /* @__PURE__ */ jsx2(Text, { color: i <= 1 ? faceColor : bodyColor, children: line }, i))
2450
+ ] });
2451
+ }
2452
+ function FoxBubble({ text }) {
2453
+ if (!text) return null;
2454
+ const maxW = 28;
2455
+ const display = text.length > maxW ? text.slice(0, maxW - 2) + ".." : text;
2456
+ const w = display.length;
2457
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2458
+ /* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "." + "-".repeat(w + 2) + "." }),
2459
+ /* @__PURE__ */ jsxs(Text, { color: theme.dim ?? "#7e8ea6", children: [
2460
+ "| ",
2461
+ /* @__PURE__ */ jsx2(Text, { color: theme.text ?? "#e2e8f0", children: display }),
2462
+ " |"
2463
+ ] }),
2464
+ /* @__PURE__ */ jsx2(Text, { color: theme.dim ?? "#7e8ea6", children: "'" + "-".repeat(w + 2) + "'" })
2465
+ ] });
2466
+ }
2467
+
2468
+ // src/components/Banner.tsx
2469
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1938
2470
  var LOGO_LINES = [
1939
2471
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ",
1940
2472
  " \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2554\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557",
@@ -1943,69 +2475,71 @@ var LOGO_LINES = [
1943
2475
  " \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2557 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
1944
2476
  " \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D "
1945
2477
  ];
1946
- var SUBTITLE = " \u2584\u2580 C O D E \u2580\u2584";
2478
+ var SUBTITLE = " -- C O D E --";
1947
2479
  var GRADIENT = [
1948
2480
  "#5eead4",
1949
- // cyan
1950
2481
  "#6ee0c8",
1951
- // cyan-light
1952
2482
  "#82c8d0",
1953
- // cyan-blue
1954
2483
  "#96b0d8",
1955
- // blue-ish
1956
2484
  "#a78bfa",
1957
- // purple
1958
2485
  "#c47ee8",
1959
- // purple-pink
1960
2486
  "#f472b6"
1961
- // pink
1962
2487
  ];
1963
2488
  function lineColor(index) {
1964
2489
  const t = index / (LOGO_LINES.length - 1);
1965
- const gradientIdx = Math.round(t * (GRADIENT.length - 1));
1966
- return GRADIENT[gradientIdx];
1967
- }
1968
- function gradientDivider(width) {
1969
- const chars = [];
1970
- for (let i = 0; i < width; i++) {
1971
- const t = i / (width - 1);
1972
- const gradientIdx = Math.round(t * (GRADIENT.length - 1));
1973
- chars.push({ char: "\u2501", color: GRADIENT[gradientIdx] });
1974
- }
1975
- return chars;
1976
- }
1977
- function Banner({ model, cwd }) {
1978
- const divider = gradientDivider(60);
1979
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
1980
- LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx2(Text, { color: lineColor(i), bold: true, children: line }, i)),
1981
- /* @__PURE__ */ jsx2(Text, { color: theme.purple, bold: true, children: SUBTITLE }),
1982
- /* @__PURE__ */ jsx2(Text, { children: " " }),
1983
- /* @__PURE__ */ jsxs(Text, { children: [
2490
+ return GRADIENT[Math.round(t * (GRADIENT.length - 1))];
2491
+ }
2492
+ function gradientBar(width) {
2493
+ return Array.from({ length: width }, (_, i) => ({
2494
+ char: "\u2501",
2495
+ color: GRADIENT[Math.round(i / (width - 1) * (GRADIENT.length - 1))]
2496
+ }));
2497
+ }
2498
+ function Banner({ model, cwd, providerName, providerOnline }) {
2499
+ const bar = gradientBar(60);
2500
+ const statusTag = providerOnline ? "[connected]" : "[offline]";
2501
+ const statusColor = providerOnline ? theme.green ?? "#4ade80" : theme.pink ?? "#f472b6";
2502
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginBottom: 1, children: [
2503
+ /* @__PURE__ */ jsxs2(Box2, { children: [
2504
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
2505
+ LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx3(Text2, { color: lineColor(i), bold: true, children: line }, i)),
2506
+ /* @__PURE__ */ jsx3(Text2, { color: theme.purple ?? "#a78bfa", bold: true, children: SUBTITLE })
2507
+ ] }),
2508
+ /* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, flexDirection: "row", alignItems: "flex-end", children: [
2509
+ /* @__PURE__ */ jsx3(Fox, { mood: "greeting" }),
2510
+ /* @__PURE__ */ jsx3(FoxBubble, { text: "Ready to work." })
2511
+ ] })
2512
+ ] }),
2513
+ /* @__PURE__ */ jsx3(Text2, { children: " " }),
2514
+ /* @__PURE__ */ jsxs2(Text2, { children: [
1984
2515
  " ",
1985
- divider.map((d, i) => /* @__PURE__ */ jsx2(Text, { color: d.color, children: d.char }, i))
2516
+ bar.map((d, i) => /* @__PURE__ */ jsx3(Text2, { color: d.color, children: d.char }, i))
2517
+ ] }),
2518
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, marginLeft: 2, children: [
2519
+ /* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: "model " }),
2520
+ /* @__PURE__ */ jsx3(Text2, { color: theme.cyan ?? "#5eead4", bold: true, children: model })
1986
2521
  ] }),
1987
- /* @__PURE__ */ jsxs(Box, { marginTop: 1, marginLeft: 2, children: [
1988
- /* @__PURE__ */ jsx2(Text, { color: theme.dim, children: "\u26A1 " }),
1989
- /* @__PURE__ */ jsx2(Text, { color: theme.cyan, bold: true, children: model }),
1990
- /* @__PURE__ */ jsx2(Text, { color: theme.dim, children: " \u2502 " }),
1991
- /* @__PURE__ */ jsx2(Text, { color: theme.text, children: cwd })
2522
+ /* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, children: [
2523
+ /* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: "via " }),
2524
+ /* @__PURE__ */ jsx3(Text2, { color: theme.text ?? "#e2e8f0", children: providerName }),
2525
+ /* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: " " }),
2526
+ /* @__PURE__ */ jsx3(Text2, { color: statusColor, children: statusTag })
1992
2527
  ] }),
1993
- /* @__PURE__ */ jsxs(Box, { marginLeft: 2, children: [
1994
- /* @__PURE__ */ jsx2(Text, { color: theme.dim, children: " Type a message to begin. " }),
1995
- /* @__PURE__ */ jsx2(Text, { color: theme.cyanDim, children: "Ctrl+C" }),
1996
- /* @__PURE__ */ jsx2(Text, { color: theme.dim, children: " to abort or exit." })
2528
+ /* @__PURE__ */ jsxs2(Box2, { marginLeft: 2, children: [
2529
+ /* @__PURE__ */ jsx3(Text2, { color: theme.dim ?? "#7e8ea6", children: "cwd " }),
2530
+ /* @__PURE__ */ jsx3(Text2, { color: theme.text ?? "#e2e8f0", children: cwd })
1997
2531
  ] }),
1998
- /* @__PURE__ */ jsxs(Text, { children: [
2532
+ /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
1999
2533
  " ",
2000
- divider.map((d, i) => /* @__PURE__ */ jsx2(Text, { color: d.color, children: d.char }, i))
2001
- ] })
2534
+ bar.map((d, i) => /* @__PURE__ */ jsx3(Text2, { color: d.color, children: d.char }, i))
2535
+ ] }) })
2002
2536
  ] });
2003
2537
  }
2004
2538
 
2005
2539
  // src/components/Messages.tsx
2006
2540
  init_theme();
2007
2541
  init_format();
2008
- import { Box as Box2, Text as Text2 } from "ink";
2542
+ import { Box as Box3, Text as Text3 } from "ink";
2009
2543
 
2010
2544
  // src/utils/markdown.ts
2011
2545
  function renderMarkdown(text) {
@@ -2069,31 +2603,31 @@ function renderInline(text) {
2069
2603
  }
2070
2604
 
2071
2605
  // src/components/Messages.tsx
2072
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
2606
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2073
2607
  function Messages({ messages }) {
2074
- return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ jsx3(MessageRow, { message: msg }, msg.id)) });
2608
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ jsx4(MessageRow, { message: msg }, msg.id)) });
2075
2609
  }
2076
2610
  function MessageRow({ message }) {
2077
2611
  switch (message.role) {
2078
2612
  case "user":
2079
- return /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, marginBottom: 1, children: [
2080
- /* @__PURE__ */ jsx3(Text2, { color: theme.cyan, bold: true, children: "\u276F " }),
2081
- /* @__PURE__ */ jsx3(Text2, { bold: true, color: theme.text, children: message.content })
2613
+ return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, marginBottom: 1, children: [
2614
+ /* @__PURE__ */ jsx4(Text3, { color: theme.cyan, bold: true, children: "\u276F " }),
2615
+ /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.text, children: message.content })
2082
2616
  ] });
2083
2617
  case "assistant": {
2084
2618
  if (!message.content && message.toolCalls) return null;
2085
2619
  if (!message.content) return null;
2086
2620
  const rendered = renderMarkdown(message.content);
2087
- return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs2(Box2, { children: [
2088
- /* @__PURE__ */ jsx3(Text2, { color: theme.cyan, children: "\u23BF " }),
2089
- /* @__PURE__ */ jsx3(Text2, { wrap: "wrap", children: rendered })
2621
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsxs3(Box3, { children: [
2622
+ /* @__PURE__ */ jsx4(Text3, { color: theme.cyan, children: "\u23BF " }),
2623
+ /* @__PURE__ */ jsx4(Text3, { wrap: "wrap", children: rendered })
2090
2624
  ] }) });
2091
2625
  }
2092
2626
  case "tool": {
2093
2627
  const lines = message.content.split("\n");
2094
2628
  const preview = lines.length > 8 ? lines.slice(0, 8).join("\n") + `
2095
2629
  ... (${lines.length - 8} more lines)` : message.content;
2096
- return /* @__PURE__ */ jsx3(Box2, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
2630
+ return /* @__PURE__ */ jsx4(Box3, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2097
2631
  "\u23BF ",
2098
2632
  truncate(preview, 1200)
2099
2633
  ] }) });
@@ -2106,17 +2640,17 @@ function MessageRow({ message }) {
2106
2640
  // src/components/ToolCall.tsx
2107
2641
  init_theme();
2108
2642
  init_format();
2109
- import { Box as Box3, Text as Text3 } from "ink";
2643
+ import { Box as Box4, Text as Text4 } from "ink";
2110
2644
  import Spinner from "ink-spinner";
2111
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
2645
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2112
2646
  function ActiveToolCall({ toolName, args }) {
2113
- return /* @__PURE__ */ jsxs3(Box3, { marginLeft: 2, children: [
2114
- /* @__PURE__ */ jsx4(Text3, { color: theme.cyan, children: /* @__PURE__ */ jsx4(Spinner, { type: "dots" }) }),
2115
- /* @__PURE__ */ jsxs3(Text3, { bold: true, color: theme.yellow, children: [
2647
+ return /* @__PURE__ */ jsxs4(Box4, { marginLeft: 2, children: [
2648
+ /* @__PURE__ */ jsx5(Text4, { color: theme.cyan, children: /* @__PURE__ */ jsx5(Spinner, { type: "dots" }) }),
2649
+ /* @__PURE__ */ jsxs4(Text4, { bold: true, color: theme.yellow, children: [
2116
2650
  " ",
2117
2651
  toolName
2118
2652
  ] }),
2119
- args ? /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2653
+ args ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2120
2654
  " ",
2121
2655
  formatToolArgs(args)
2122
2656
  ] }) : null
@@ -2128,15 +2662,15 @@ function ToolResultDisplay({ toolName, output, isError }) {
2128
2662
  const lines = output.split("\n");
2129
2663
  const preview = lines.length > 6 ? lines.slice(0, 6).join("\n") + `
2130
2664
  ... (${lines.length - 6} more lines)` : output;
2131
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
2132
- /* @__PURE__ */ jsxs3(Box3, { children: [
2133
- /* @__PURE__ */ jsxs3(Text3, { color: iconColor, children: [
2665
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginLeft: 2, marginBottom: 1, children: [
2666
+ /* @__PURE__ */ jsxs4(Box4, { children: [
2667
+ /* @__PURE__ */ jsxs4(Text4, { color: iconColor, children: [
2134
2668
  icon,
2135
2669
  " "
2136
2670
  ] }),
2137
- /* @__PURE__ */ jsx4(Text3, { bold: true, color: theme.yellow, children: toolName })
2671
+ /* @__PURE__ */ jsx5(Text4, { bold: true, color: theme.yellow, children: toolName })
2138
2672
  ] }),
2139
- /* @__PURE__ */ jsx4(Box3, { marginLeft: 2, children: /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2673
+ /* @__PURE__ */ jsx5(Box4, { marginLeft: 2, children: /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2140
2674
  "\u23BF ",
2141
2675
  truncate(preview, 1200)
2142
2676
  ] }) })
@@ -2157,8 +2691,8 @@ function formatToolArgs(args) {
2157
2691
  // src/components/StatusLine.tsx
2158
2692
  init_theme();
2159
2693
  init_state();
2160
- import { Box as Box4, Text as Text4 } from "ink";
2161
- import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2694
+ import { Box as Box5, Text as Text5 } from "ink";
2695
+ import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2162
2696
  function getContextLimit(model) {
2163
2697
  const lower = model.toLowerCase();
2164
2698
  if (lower.includes("llama3.1")) return 131072;
@@ -2176,40 +2710,40 @@ function StatusLine({ model, messageCount, tokenEstimate, isStreaming }) {
2176
2710
  const pct = (usage * 100).toFixed(0);
2177
2711
  const usageColor = usage > 0.8 ? theme.pink : usage > 0.5 ? theme.yellow : theme.cyan;
2178
2712
  const activeTasks = state2.tasks.filter((t) => t.status === "in_progress").length;
2179
- return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
2180
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2713
+ return /* @__PURE__ */ jsxs5(Box5, { marginTop: 1, children: [
2714
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2181
2715
  "\u2500".repeat(2),
2182
2716
  " "
2183
2717
  ] }),
2184
- /* @__PURE__ */ jsx5(Text4, { color: theme.cyan, bold: true, children: model.split(":")[0] }),
2185
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2186
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2718
+ /* @__PURE__ */ jsx6(Text5, { color: theme.cyan, bold: true, children: model.split(":")[0] }),
2719
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2720
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2187
2721
  messageCount,
2188
2722
  " msgs"
2189
2723
  ] }),
2190
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2191
- /* @__PURE__ */ jsxs4(Text4, { color: usageColor, children: [
2724
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2725
+ /* @__PURE__ */ jsxs5(Text5, { color: usageColor, children: [
2192
2726
  "ctx ",
2193
2727
  pct,
2194
2728
  "%"
2195
2729
  ] }),
2196
- state2.planMode ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2197
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2198
- /* @__PURE__ */ jsx5(Text4, { color: theme.yellow, bold: true, children: "PLAN" })
2730
+ state2.planMode ? /* @__PURE__ */ jsxs5(Fragment, { children: [
2731
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2732
+ /* @__PURE__ */ jsx6(Text5, { color: theme.yellow, bold: true, children: "PLAN" })
2199
2733
  ] }) : null,
2200
- activeTasks > 0 ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2201
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2202
- /* @__PURE__ */ jsxs4(Text4, { color: theme.green, children: [
2734
+ activeTasks > 0 ? /* @__PURE__ */ jsxs5(Fragment, { children: [
2735
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2736
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.green, children: [
2203
2737
  activeTasks,
2204
2738
  " task",
2205
2739
  activeTasks > 1 ? "s" : ""
2206
2740
  ] })
2207
2741
  ] }) : null,
2208
- isStreaming ? /* @__PURE__ */ jsxs4(Fragment, { children: [
2209
- /* @__PURE__ */ jsx5(Text4, { color: theme.dim, children: " \u2502 " }),
2210
- /* @__PURE__ */ jsx5(Text4, { color: theme.cyan, children: "streaming" })
2742
+ isStreaming ? /* @__PURE__ */ jsxs5(Fragment, { children: [
2743
+ /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: " \u2502 " }),
2744
+ /* @__PURE__ */ jsx6(Text5, { color: theme.cyan, children: "streaming" })
2211
2745
  ] }) : null,
2212
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2746
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2213
2747
  " ",
2214
2748
  "\u2500".repeat(2)
2215
2749
  ] })
@@ -2218,8 +2752,8 @@ function StatusLine({ model, messageCount, tokenEstimate, isStreaming }) {
2218
2752
 
2219
2753
  // src/components/UserInput.tsx
2220
2754
  init_theme();
2221
- import { useState, useCallback } from "react";
2222
- import { Box as Box5, Text as Text5, useInput } from "ink";
2755
+ import { useState as useState2, useCallback } from "react";
2756
+ import { Box as Box6, Text as Text6, useInput } from "ink";
2223
2757
  import TextInput from "ink-text-input";
2224
2758
 
2225
2759
  // src/commands/help.ts
@@ -2408,7 +2942,7 @@ var contextCommand = {
2408
2942
  `\x1B[2m Model: ${context.model} (${contextLimit.toLocaleString()} ctx)\x1B[0m`
2409
2943
  ];
2410
2944
  if (usage > 0.8) {
2411
- lines.push("", "\x1B[33m \u26A0 Context is getting full. Consider /compact to free space.\x1B[0m");
2945
+ lines.push("", "\x1B[33m [!] Context is getting full. Consider /compact to free space.\x1B[0m");
2412
2946
  }
2413
2947
  return { output: lines.join("\n"), silent: true };
2414
2948
  }
@@ -2614,7 +3148,7 @@ ${truncatedDiff}`
2614
3148
  const output = await gitExec(["commit", "-m", commitMsg], context.cwd);
2615
3149
  return {
2616
3150
  output: [
2617
- `\x1B[32m\u2713\x1B[0m Committed: ${commitMsg}`,
3151
+ `\x1B[32m[ok]\x1B[0m Committed: ${commitMsg}`,
2618
3152
  "",
2619
3153
  output.trim()
2620
3154
  ].join("\n"),
@@ -2858,7 +3392,7 @@ var themeCommand = {
2858
3392
  const names = Object.keys(THEMES);
2859
3393
  const lines = names.map((name2) => {
2860
3394
  const t = THEMES[name2];
2861
- const swatch = `\x1B[38;2;${hexToRgb(t.cyan)}m\u25CF\x1B[0m\x1B[38;2;${hexToRgb(t.pink)}m\u25CF\x1B[0m\x1B[38;2;${hexToRgb(t.green)}m\u25CF\x1B[0m\x1B[38;2;${hexToRgb(t.yellow)}m\u25CF\x1B[0m\x1B[38;2;${hexToRgb(t.purple)}m\u25CF\x1B[0m`;
3395
+ const swatch = `\x1B[38;2;${hexToRgb(t.cyan)}m#\x1B[0m\x1B[38;2;${hexToRgb(t.pink)}m#\x1B[0m\x1B[38;2;${hexToRgb(t.green)}m#\x1B[0m\x1B[38;2;${hexToRgb(t.yellow)}m#\x1B[0m\x1B[38;2;${hexToRgb(t.purple)}m#\x1B[0m`;
2862
3396
  return ` ${name2.padEnd(10)} ${swatch}`;
2863
3397
  });
2864
3398
  return {
@@ -3083,7 +3617,7 @@ var providerCommand = {
3083
3617
  ""
3084
3618
  ];
3085
3619
  for (const { config, online } of results) {
3086
- const status = online ? "\x1B[32m\u25CF online\x1B[0m" : "\x1B[31m\u25CF offline\x1B[0m";
3620
+ const status = online ? "\x1B[32m[online]\x1B[0m" : "\x1B[31m[offline]\x1B[0m";
3087
3621
  const isActive = config.name === active ? " \x1B[36m\u2190 active\x1B[0m" : "";
3088
3622
  lines.push(` ${config.name.padEnd(14)} ${config.label.padEnd(14)} ${config.baseUrl.padEnd(30)} ${status}${isActive}`);
3089
3623
  }
@@ -3152,6 +3686,71 @@ var providerCommand = {
3152
3686
  }
3153
3687
  };
3154
3688
 
3689
+ // src/commands/fox.ts
3690
+ var petCommand = {
3691
+ name: "pet",
3692
+ description: "Pet your fox companion",
3693
+ async execute(_args, _context) {
3694
+ const reaction = petFox();
3695
+ return { output: reaction, silent: true, foxMood: "pet" };
3696
+ }
3697
+ };
3698
+ var feedCommand = {
3699
+ name: "feed",
3700
+ description: "Feed your fox companion",
3701
+ async execute(_args, _context) {
3702
+ const reaction = feedFox();
3703
+ return { output: reaction, silent: true, foxMood: "eating" };
3704
+ }
3705
+ };
3706
+ var restCommand = {
3707
+ name: "rest",
3708
+ aliases: ["sleep", "nap"],
3709
+ description: "Let your fox take a nap",
3710
+ async execute(_args, _context) {
3711
+ const reaction = restFox();
3712
+ return { output: reaction, silent: true, foxMood: "sleeping" };
3713
+ }
3714
+ };
3715
+ var foxCommand = {
3716
+ name: "fox",
3717
+ aliases: ["companion", "buddy"],
3718
+ description: "Check on your fox companion (usage: /fox, /fox name <name>)",
3719
+ async execute(args, _context) {
3720
+ const parts = args.trim().split(/\s+/);
3721
+ if (parts[0] === "name" && parts[1]) {
3722
+ const newName = parts.slice(1).join(" ");
3723
+ setFoxName(newName);
3724
+ return { output: `Your fox is now named "${newName}".`, silent: true };
3725
+ }
3726
+ const stats2 = getFoxStats();
3727
+ const bar = (val) => {
3728
+ const filled = Math.round(val / 5);
3729
+ const empty = 20 - filled;
3730
+ return "\x1B[36m" + "#".repeat(filled) + "\x1B[0m\x1B[2m" + "-".repeat(empty) + "\x1B[0m";
3731
+ };
3732
+ const age = Math.floor((Date.now() - stats2.createdAt) / 6e4);
3733
+ const ageStr = age < 60 ? `${age}m` : `${Math.floor(age / 60)}h ${age % 60}m`;
3734
+ const lines = [
3735
+ `\x1B[1m\x1B[36m${stats2.name}\x1B[0m`,
3736
+ "",
3737
+ ` Happiness [${bar(stats2.happiness)}] ${Math.round(stats2.happiness)}%`,
3738
+ ` Hunger [${bar(stats2.hunger)}] ${Math.round(stats2.hunger)}%`,
3739
+ ` Energy [${bar(stats2.energy)}] ${Math.round(stats2.energy)}%`,
3740
+ "",
3741
+ ` Times petted: ${stats2.timesPetted}`,
3742
+ ` Times fed: ${stats2.timesFed}`,
3743
+ ` Age: ${ageStr}`,
3744
+ "",
3745
+ "\x1B[2m /pet \u2014 Pet your fox",
3746
+ " /feed \u2014 Feed your fox",
3747
+ " /rest \u2014 Let your fox nap",
3748
+ " /fox name <name> \u2014 Rename\x1B[0m"
3749
+ ];
3750
+ return { output: lines.join("\n"), silent: true };
3751
+ }
3752
+ };
3753
+
3155
3754
  // src/commands/index.ts
3156
3755
  var COMMANDS = [
3157
3756
  helpCommand,
@@ -3174,7 +3773,11 @@ var COMMANDS = [
3174
3773
  statusCommand,
3175
3774
  filesCommand,
3176
3775
  briefCommand,
3177
- providerCommand
3776
+ providerCommand,
3777
+ petCommand,
3778
+ feedCommand,
3779
+ restCommand,
3780
+ foxCommand
3178
3781
  ];
3179
3782
  function getCommands() {
3180
3783
  return COMMANDS;
@@ -3210,10 +3813,10 @@ function getCommandNames() {
3210
3813
  }
3211
3814
 
3212
3815
  // src/components/UserInput.tsx
3213
- import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
3816
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3214
3817
  function UserInput({ value, onChange, onSubmit, disabled, history }) {
3215
- const [historyIdx, setHistoryIdx] = useState(-1);
3216
- const [suggestion, setSuggestion] = useState("");
3818
+ const [historyIdx, setHistoryIdx] = useState2(-1);
3819
+ const [suggestion, setSuggestion] = useState2("");
3217
3820
  useInput((_input, key) => {
3218
3821
  if (disabled || !history || history.length === 0) return;
3219
3822
  if (key.upArrow) {
@@ -3259,20 +3862,20 @@ function UserInput({ value, onChange, onSubmit, disabled, history }) {
3259
3862
  const borderColor = disabled ? theme.dim : isBash ? theme.yellow : isCommand ? theme.purple : theme.cyan;
3260
3863
  const promptChar = isBash ? "!" : "\u276F";
3261
3864
  const promptColor = isBash ? theme.yellow : theme.cyan;
3262
- return /* @__PURE__ */ jsxs5(
3263
- Box5,
3865
+ return /* @__PURE__ */ jsxs6(
3866
+ Box6,
3264
3867
  {
3265
3868
  borderStyle: "round",
3266
3869
  borderColor,
3267
3870
  paddingLeft: 1,
3268
3871
  paddingRight: 1,
3269
3872
  children: [
3270
- /* @__PURE__ */ jsxs5(Text5, { color: disabled ? theme.dim : promptColor, bold: true, children: [
3873
+ /* @__PURE__ */ jsxs6(Text6, { color: disabled ? theme.dim : promptColor, bold: true, children: [
3271
3874
  promptChar,
3272
3875
  " "
3273
3876
  ] }),
3274
- disabled ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: "..." }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
3275
- /* @__PURE__ */ jsx6(
3877
+ disabled ? /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: "..." }) : /* @__PURE__ */ jsxs6(Fragment2, { children: [
3878
+ /* @__PURE__ */ jsx7(
3276
3879
  TextInput,
3277
3880
  {
3278
3881
  value,
@@ -3286,7 +3889,7 @@ function UserInput({ value, onChange, onSubmit, disabled, history }) {
3286
3889
  }
3287
3890
  }
3288
3891
  ),
3289
- suggestion ? /* @__PURE__ */ jsx6(Text5, { color: theme.dim, children: suggestion }) : null
3892
+ suggestion ? /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: suggestion }) : null
3290
3893
  ] })
3291
3894
  ]
3292
3895
  }
@@ -3294,13 +3897,14 @@ function UserInput({ value, onChange, onSubmit, disabled, history }) {
3294
3897
  }
3295
3898
 
3296
3899
  // src/repl.tsx
3900
+ init_providers();
3297
3901
  init_query();
3298
3902
  init_system_prompt();
3299
3903
  init_providers();
3300
3904
  init_tools();
3301
3905
  init_bash();
3302
3906
  init_theme();
3303
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
3907
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
3304
3908
  function getContextLimit3(model) {
3305
3909
  const lower = model.toLowerCase();
3306
3910
  if (lower.includes("llama3.1")) return 131072;
@@ -3314,29 +3918,34 @@ function getContextLimit3(model) {
3314
3918
  function REPL({ initialPrompt }) {
3315
3919
  const { model: initialModel, systemPromptOverride } = useDarkfooContext();
3316
3920
  const { exit } = useApp();
3317
- const [model, setModel] = useState2(initialModel);
3318
- const [messages, setMessages] = useState2([]);
3319
- const [inputValue, setInputValue] = useState2("");
3320
- const [isStreaming, setIsStreaming] = useState2(false);
3321
- const [streamingText, setStreamingText] = useState2("");
3322
- const [activeTool, setActiveTool] = useState2(null);
3323
- const [toolResults, setToolResults] = useState2([]);
3324
- const [commandOutput, setCommandOutput] = useState2(null);
3325
- const [inputHistory, setInputHistory] = useState2([]);
3326
- const [tokenCounts, setTokenCounts] = useState2({ input: 0, output: 0 });
3327
- const [systemPrompt, setSystemPrompt] = useState2("");
3328
- const abortRef = useRef(null);
3329
- const hasRun = useRef(false);
3330
- const sessionId = useRef(createSessionId());
3921
+ const [model, setModel] = useState3(initialModel);
3922
+ const [messages, setMessages] = useState3([]);
3923
+ const [inputValue, setInputValue] = useState3("");
3924
+ const [isStreaming, setIsStreaming] = useState3(false);
3925
+ const [streamingText, setStreamingText] = useState3("");
3926
+ const [activeTool, setActiveTool] = useState3(null);
3927
+ const [toolResults, setToolResults] = useState3([]);
3928
+ const [commandOutput, setCommandOutput] = useState3(null);
3929
+ const [inputHistory, setInputHistory] = useState3([]);
3930
+ const [tokenCounts, setTokenCounts] = useState3({ input: 0, output: 0 });
3931
+ const [systemPrompt, setSystemPrompt] = useState3("");
3932
+ const [foxMood, setFoxMood] = useState3("idle");
3933
+ const [providerOnline, setProviderOnline] = useState3(true);
3934
+ const abortRef = useRef2(null);
3935
+ const hasRun = useRef2(false);
3936
+ const sessionId = useRef2(createSessionId());
3331
3937
  const tools = getTools();
3332
3938
  const cwd = process.cwd();
3333
- useEffect(() => {
3939
+ useEffect2(() => {
3334
3940
  if (systemPromptOverride) {
3335
3941
  setSystemPrompt(systemPromptOverride);
3336
3942
  } else {
3337
3943
  buildSystemPrompt(tools, cwd).then(setSystemPrompt);
3338
3944
  }
3339
3945
  }, []);
3946
+ useEffect2(() => {
3947
+ getProvider().healthCheck().then((ok) => setProviderOnline(ok)).catch(() => setProviderOnline(false));
3948
+ }, [model]);
3340
3949
  const commandContext = {
3341
3950
  messages,
3342
3951
  model,
@@ -3365,6 +3974,7 @@ function REPL({ initialPrompt }) {
3365
3974
  setStreamingText("");
3366
3975
  setToolResults([]);
3367
3976
  setCommandOutput(null);
3977
+ setFoxMood("thinking");
3368
3978
  const controller = new AbortController();
3369
3979
  abortRef.current = controller;
3370
3980
  const allMessages = [...messages, userMsg];
@@ -3384,12 +3994,15 @@ function REPL({ initialPrompt }) {
3384
3994
  switch (event.type) {
3385
3995
  case "text_delta":
3386
3996
  setStreamingText((prev) => prev + event.text);
3997
+ setFoxMood("idle");
3387
3998
  break;
3388
3999
  case "tool_call":
3389
4000
  setActiveTool({ name: event.toolCall.function.name, args: event.toolCall.function.arguments });
4001
+ setFoxMood("working");
3390
4002
  break;
3391
4003
  case "tool_result":
3392
4004
  setActiveTool(null);
4005
+ setFoxMood(event.isError ? "error" : "working");
3393
4006
  setToolResults((prev) => [
3394
4007
  ...prev,
3395
4008
  { id: nanoid6(), toolName: event.toolName, output: event.output, isError: event.isError }
@@ -3409,6 +4022,7 @@ function REPL({ initialPrompt }) {
3409
4022
  setStreamingText("");
3410
4023
  break;
3411
4024
  case "error":
4025
+ setFoxMood("error");
3412
4026
  setMessages((prev) => [
3413
4027
  ...prev,
3414
4028
  { id: nanoid6(), role: "assistant", content: `Error: ${event.error}`, timestamp: Date.now() }
@@ -3429,6 +4043,8 @@ function REPL({ initialPrompt }) {
3429
4043
  setStreamingText("");
3430
4044
  setActiveTool(null);
3431
4045
  setToolResults([]);
4046
+ setFoxMood((prev) => prev === "error" ? "error" : "success");
4047
+ setTimeout(() => setFoxMood("idle"), 2e3);
3432
4048
  abortRef.current = null;
3433
4049
  }
3434
4050
  },
@@ -3450,6 +4066,10 @@ function REPL({ initialPrompt }) {
3450
4066
  if (result.replaceMessages) {
3451
4067
  setMessages(result.replaceMessages);
3452
4068
  }
4069
+ if (result.foxMood) {
4070
+ setFoxMood(result.foxMood);
4071
+ setTimeout(() => setFoxMood("idle"), 3e3);
4072
+ }
3453
4073
  if (result.exit) return;
3454
4074
  if (!result.silent && result.output) {
3455
4075
  runQuery(result.output);
@@ -3484,14 +4104,14 @@ function REPL({ initialPrompt }) {
3484
4104
  },
3485
4105
  [addToHistory, commandContext, cwd, runQuery]
3486
4106
  );
3487
- useEffect(() => {
4107
+ useEffect2(() => {
3488
4108
  if (initialPrompt && !hasRun.current) {
3489
4109
  hasRun.current = true;
3490
4110
  runQuery(initialPrompt);
3491
4111
  }
3492
4112
  }, [initialPrompt, runQuery]);
3493
- const autoCompactRef = useRef(false);
3494
- useEffect(() => {
4113
+ const autoCompactRef = useRef2(false);
4114
+ useEffect2(() => {
3495
4115
  if (isStreaming || autoCompactRef.current || messages.length < 6) return;
3496
4116
  const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0);
3497
4117
  const estTokens = Math.ceil(totalChars / 4) + 2e3;
@@ -3499,7 +4119,7 @@ function REPL({ initialPrompt }) {
3499
4119
  const usage = estTokens / limit;
3500
4120
  if (usage > 0.85) {
3501
4121
  autoCompactRef.current = true;
3502
- setCommandOutput("\x1B[33m\u26A0 Context 85%+ full \u2014 auto-compacting...\x1B[0m");
4122
+ setCommandOutput("\x1B[33m[!] Context 85%+ full \u2014 auto-compacting...\x1B[0m");
3503
4123
  const transcript = messages.filter((m) => m.role === "user" || m.role === "assistant" && m.content).map((m) => `${m.role}: ${m.content}`).join("\n\n");
3504
4124
  getProvider().chat({
3505
4125
  model,
@@ -3517,10 +4137,10 @@ ${result.content}
3517
4137
  };
3518
4138
  setMessages([summaryMsg]);
3519
4139
  setTokenCounts({ input: Math.ceil(result.content.length / 4), output: 0 });
3520
- setCommandOutput("\x1B[32m\u2713 Auto-compacted conversation.\x1B[0m");
4140
+ setCommandOutput("\x1B[32m[ok] Auto-compacted conversation.\x1B[0m");
3521
4141
  autoCompactRef.current = false;
3522
4142
  }).catch(() => {
3523
- setCommandOutput("\x1B[31m\u2717 Auto-compact failed.\x1B[0m");
4143
+ setCommandOutput("\x1B[31m[err] Auto-compact failed.\x1B[0m");
3524
4144
  autoCompactRef.current = false;
3525
4145
  });
3526
4146
  }
@@ -3534,35 +4154,38 @@ ${result.content}
3534
4154
  }
3535
4155
  }
3536
4156
  });
3537
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", padding: 1, children: [
3538
- /* @__PURE__ */ jsx7(Banner, { model, cwd }),
3539
- /* @__PURE__ */ jsx7(Messages, { messages }),
3540
- commandOutput ? /* @__PURE__ */ jsx7(Box6, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx7(Text6, { children: commandOutput }) }) : null,
3541
- toolResults.map((tr) => /* @__PURE__ */ jsx7(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
3542
- activeTool ? /* @__PURE__ */ jsx7(ActiveToolCall, { toolName: activeTool.name, args: activeTool.args }) : null,
3543
- isStreaming && streamingText ? /* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, children: [
3544
- /* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: "\u23BF " }),
3545
- /* @__PURE__ */ jsx7(Text6, { color: theme.text, wrap: "wrap", children: streamingText }),
3546
- /* @__PURE__ */ jsxs6(Text6, { color: theme.cyan, children: [
4157
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", padding: 1, children: [
4158
+ /* @__PURE__ */ jsx8(Banner, { model, cwd, providerName: getActiveProviderName(), providerOnline }),
4159
+ /* @__PURE__ */ jsx8(Messages, { messages }),
4160
+ commandOutput ? /* @__PURE__ */ jsx8(Box7, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text7, { children: commandOutput }) }) : null,
4161
+ toolResults.map((tr) => /* @__PURE__ */ jsx8(ToolResultDisplay, { toolName: tr.toolName, output: tr.output, isError: tr.isError }, tr.id)),
4162
+ activeTool ? /* @__PURE__ */ jsx8(ActiveToolCall, { toolName: activeTool.name, args: activeTool.args }) : null,
4163
+ isStreaming && streamingText ? /* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, children: [
4164
+ /* @__PURE__ */ jsx8(Text7, { color: theme.cyan, children: "\u23BF " }),
4165
+ /* @__PURE__ */ jsx8(Text7, { color: theme.text, wrap: "wrap", children: streamingText }),
4166
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.cyan, children: [
3547
4167
  " ",
3548
- /* @__PURE__ */ jsx7(Spinner2, { type: "dots" })
4168
+ /* @__PURE__ */ jsx8(Spinner2, { type: "dots" })
3549
4169
  ] })
3550
4170
  ] }) : null,
3551
- isStreaming && !streamingText && !activeTool ? /* @__PURE__ */ jsxs6(Box6, { marginLeft: 2, children: [
3552
- /* @__PURE__ */ jsx7(Text6, { color: theme.cyan, children: /* @__PURE__ */ jsx7(Spinner2, { type: "dots" }) }),
3553
- /* @__PURE__ */ jsx7(Text6, { color: theme.dim, children: " Thinking..." })
4171
+ isStreaming && !streamingText && !activeTool ? /* @__PURE__ */ jsxs7(Box7, { marginLeft: 2, children: [
4172
+ /* @__PURE__ */ jsx8(Text7, { color: theme.cyan, children: /* @__PURE__ */ jsx8(Spinner2, { type: "dots" }) }),
4173
+ /* @__PURE__ */ jsx8(Text7, { color: theme.dim, children: " Thinking..." })
3554
4174
  ] }) : null,
3555
- /* @__PURE__ */ jsx7(
3556
- UserInput,
3557
- {
3558
- value: inputValue,
3559
- onChange: setInputValue,
3560
- onSubmit: handleSubmit,
3561
- disabled: isStreaming,
3562
- history: inputHistory
3563
- }
3564
- ),
3565
- /* @__PURE__ */ jsx7(
4175
+ /* @__PURE__ */ jsxs7(Box7, { children: [
4176
+ /* @__PURE__ */ jsx8(Box7, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx8(
4177
+ UserInput,
4178
+ {
4179
+ value: inputValue,
4180
+ onChange: setInputValue,
4181
+ onSubmit: handleSubmit,
4182
+ disabled: isStreaming,
4183
+ history: inputHistory
4184
+ }
4185
+ ) }),
4186
+ /* @__PURE__ */ jsx8(Box7, { marginLeft: 1, children: /* @__PURE__ */ jsx8(Fox, { mood: foxMood }) })
4187
+ ] }),
4188
+ /* @__PURE__ */ jsx8(
3566
4189
  StatusLine,
3567
4190
  {
3568
4191
  model,
@@ -3576,9 +4199,9 @@ ${result.content}
3576
4199
 
3577
4200
  // src/main.tsx
3578
4201
  init_providers();
3579
- import { jsx as jsx8 } from "react/jsx-runtime";
4202
+ import { jsx as jsx9 } from "react/jsx-runtime";
3580
4203
  var program = new Command();
3581
- program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.1.4").option("-m, --model <model>", "Model to use", "llama3.1:8b").option("-p, --prompt <prompt>", "Run a single prompt (non-interactive)").option("--provider <name>", "LLM provider backend (ollama, llama-cpp, vllm, tgi, etc.)").option("--system-prompt <prompt>", "Override the system prompt").action(async (options) => {
4204
+ program.name("darkfoo").description("Darkfoo Code \u2014 local AI coding assistant powered by local LLM providers").version("0.2.1").option("-m, --model <model>", "Model to use", "llama3.1:8b").option("-p, --prompt <prompt>", "Run a single prompt (non-interactive)").option("--provider <name>", "LLM provider backend (ollama, llama-cpp, vllm, tgi, etc.)").option("--system-prompt <prompt>", "Override the system prompt").action(async (options) => {
3582
4205
  const { model, prompt, provider, systemPrompt } = options;
3583
4206
  await loadProviderSettings();
3584
4207
  if (provider) {
@@ -3638,7 +4261,7 @@ Error: ${event.error}
3638
4261
  process.exit(0);
3639
4262
  }
3640
4263
  const { waitUntilExit } = render(
3641
- /* @__PURE__ */ jsx8(App, { model, systemPromptOverride: systemPrompt, children: /* @__PURE__ */ jsx8(REPL, {}) })
4264
+ /* @__PURE__ */ jsx9(App, { model, systemPromptOverride: systemPrompt, children: /* @__PURE__ */ jsx9(REPL, {}) })
3642
4265
  );
3643
4266
  await waitUntilExit();
3644
4267
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "darkfoo-code",
3
- "version": "0.1.4",
3
+ "version": "0.2.1",
4
4
  "description": "Darkfoo Code — local AI coding assistant powered by Ollama, vLLM, llama.cpp, and other LLM providers",
5
5
  "type": "module",
6
6
  "license": "MIT",