@mrciphersmith/keryx 0.2.60 → 0.2.62
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/cli.js +649 -263
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10173,7 +10173,90 @@ async function resolveModelsForPicker(fetchFn, provider, env = process.env, opts
|
|
|
10173
10173
|
const apiKey = typeof raw === "string" && raw.length > 0 ? raw : undefined;
|
|
10174
10174
|
return fetchOpenAiCompatModelsDetailed(fetchFn, { ...compat, ...provider.baseUrl !== undefined ? { baseUrl: provider.baseUrl } : {} }, apiKey, opts);
|
|
10175
10175
|
}
|
|
10176
|
-
|
|
10176
|
+
function balanceCapableProvider(name) {
|
|
10177
|
+
const provider = providerByName(name);
|
|
10178
|
+
if (provider === undefined || provider.balancePath === undefined || provider.balanceKind === undefined) {
|
|
10179
|
+
return;
|
|
10180
|
+
}
|
|
10181
|
+
return provider;
|
|
10182
|
+
}
|
|
10183
|
+
function parseDeepSeekBalance(body) {
|
|
10184
|
+
if (typeof body !== "object" || body === null) {
|
|
10185
|
+
return;
|
|
10186
|
+
}
|
|
10187
|
+
const infos = body.balance_infos;
|
|
10188
|
+
if (!Array.isArray(infos)) {
|
|
10189
|
+
return;
|
|
10190
|
+
}
|
|
10191
|
+
for (const info of infos) {
|
|
10192
|
+
if (typeof info !== "object" || info === null) {
|
|
10193
|
+
continue;
|
|
10194
|
+
}
|
|
10195
|
+
const total = Number(info.total_balance);
|
|
10196
|
+
if (Number.isFinite(total)) {
|
|
10197
|
+
const currency = String(info.currency ?? "USD");
|
|
10198
|
+
return { currency, total, exact: true };
|
|
10199
|
+
}
|
|
10200
|
+
}
|
|
10201
|
+
return;
|
|
10202
|
+
}
|
|
10203
|
+
function parseOpenRouterBalance(body) {
|
|
10204
|
+
if (typeof body !== "object" || body === null) {
|
|
10205
|
+
return;
|
|
10206
|
+
}
|
|
10207
|
+
const credits = body.credits;
|
|
10208
|
+
if (typeof credits !== "object" || credits === null) {
|
|
10209
|
+
return;
|
|
10210
|
+
}
|
|
10211
|
+
const total = Number(credits.total);
|
|
10212
|
+
const used = Number(credits.used);
|
|
10213
|
+
if (!Number.isFinite(total)) {
|
|
10214
|
+
return;
|
|
10215
|
+
}
|
|
10216
|
+
const usedField = Number.isFinite(used) ? { used } : {};
|
|
10217
|
+
const remaining = Number.isFinite(used) ? total - used : undefined;
|
|
10218
|
+
return {
|
|
10219
|
+
currency: String(credits.currency ?? "USD"),
|
|
10220
|
+
total,
|
|
10221
|
+
...usedField,
|
|
10222
|
+
...remaining !== undefined ? { remaining } : {},
|
|
10223
|
+
exact: true
|
|
10224
|
+
};
|
|
10225
|
+
}
|
|
10226
|
+
async function fetchProviderBalance(fetchFn, provider, apiKey, opts) {
|
|
10227
|
+
if (provider.balancePath === undefined || provider.balanceKind === undefined) {
|
|
10228
|
+
return;
|
|
10229
|
+
}
|
|
10230
|
+
const url = `${provider.baseUrl.replace(/\/+$/, "")}${provider.balancePath}`;
|
|
10231
|
+
const timeoutMs = opts?.timeoutMs ?? BALANCE_FETCH_TIMEOUT_MS;
|
|
10232
|
+
const controller = new AbortController;
|
|
10233
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
10234
|
+
try {
|
|
10235
|
+
const init = { signal: controller.signal };
|
|
10236
|
+
if (apiKey !== undefined && apiKey.length > 0) {
|
|
10237
|
+
init.headers = { authorization: `Bearer ${apiKey}` };
|
|
10238
|
+
}
|
|
10239
|
+
const res = await fetchFn(url, init);
|
|
10240
|
+
if (!res.ok) {
|
|
10241
|
+
return;
|
|
10242
|
+
}
|
|
10243
|
+
const body = await res.json();
|
|
10244
|
+
return provider.balanceKind === "deepseek" ? parseDeepSeekBalance(body) : parseOpenRouterBalance(body);
|
|
10245
|
+
} catch {
|
|
10246
|
+
return;
|
|
10247
|
+
} finally {
|
|
10248
|
+
clearTimeout(timer);
|
|
10249
|
+
}
|
|
10250
|
+
}
|
|
10251
|
+
function providerApiKey(provider, env = process.env) {
|
|
10252
|
+
const envKey = provider.envKey;
|
|
10253
|
+
if (envKey === undefined) {
|
|
10254
|
+
return;
|
|
10255
|
+
}
|
|
10256
|
+
const raw = env[envKey];
|
|
10257
|
+
return typeof raw === "string" && raw.length > 0 ? raw : undefined;
|
|
10258
|
+
}
|
|
10259
|
+
var DEFAULT_MODELS_PATH = "/v1/models", OPENAI_COMPAT_PROVIDERS, MODELS_FETCH_TIMEOUT_MS = 1e4, BALANCE_FETCH_TIMEOUT_MS = 8000;
|
|
10177
10260
|
var init_providers = __esm(() => {
|
|
10178
10261
|
OPENAI_COMPAT_PROVIDERS = [
|
|
10179
10262
|
{
|
|
@@ -10182,7 +10265,9 @@ var init_providers = __esm(() => {
|
|
|
10182
10265
|
baseUrl: "https://openrouter.ai/api",
|
|
10183
10266
|
envKey: "OPENROUTER_API_KEY",
|
|
10184
10267
|
models: ["openai/gpt-4o-mini", "google/gemini-2.0-flash-001", "qwen/qwen-2.5-7b-instruct", "meta-llama/llama-3.1-8b-instruct"],
|
|
10185
|
-
note: "hosted \xB7 400+ models"
|
|
10268
|
+
note: "hosted \xB7 400+ models",
|
|
10269
|
+
balancePath: "/api/v1/credits",
|
|
10270
|
+
balanceKind: "openrouter"
|
|
10186
10271
|
},
|
|
10187
10272
|
{
|
|
10188
10273
|
name: "deepseek",
|
|
@@ -10190,7 +10275,9 @@ var init_providers = __esm(() => {
|
|
|
10190
10275
|
baseUrl: "https://api.deepseek.com",
|
|
10191
10276
|
envKey: "DEEPSEEK_API_KEY",
|
|
10192
10277
|
models: ["deepseek-chat", "deepseek-reasoner"],
|
|
10193
|
-
note: "cheap per-token"
|
|
10278
|
+
note: "cheap per-token",
|
|
10279
|
+
balancePath: "/user/balance",
|
|
10280
|
+
balanceKind: "deepseek"
|
|
10194
10281
|
},
|
|
10195
10282
|
{
|
|
10196
10283
|
name: "zai",
|
|
@@ -13121,14 +13208,29 @@ async function runModelTurn(input2) {
|
|
|
13121
13208
|
};
|
|
13122
13209
|
let text = "";
|
|
13123
13210
|
let error;
|
|
13211
|
+
let usage;
|
|
13212
|
+
let reasoning = false;
|
|
13213
|
+
const startedAt = performance.now();
|
|
13214
|
+
let firstByteAt;
|
|
13124
13215
|
for await (const event of port.stream(request, { attemptId: request.requestId })) {
|
|
13216
|
+
if (firstByteAt === undefined) {
|
|
13217
|
+
firstByteAt = performance.now();
|
|
13218
|
+
}
|
|
13125
13219
|
if (event.kind === "text_delta" && event.text) {
|
|
13126
13220
|
text += event.text;
|
|
13127
13221
|
} else if (event.kind === "provider_error" && event.error) {
|
|
13128
13222
|
error = event.error;
|
|
13223
|
+
} else if (event.kind === "reasoning_delta") {
|
|
13224
|
+
reasoning = true;
|
|
13225
|
+
} else if (event.kind === "usage_update" && event.usage) {
|
|
13226
|
+
usage = event.usage;
|
|
13129
13227
|
}
|
|
13130
13228
|
}
|
|
13131
|
-
|
|
13229
|
+
const latencyMs = firstByteAt !== undefined ? Math.max(0, Math.round(firstByteAt - startedAt)) : undefined;
|
|
13230
|
+
const usageField = usage !== undefined ? { usage } : {};
|
|
13231
|
+
const latencyField = latencyMs !== undefined ? { latencyMs } : {};
|
|
13232
|
+
const reasoningField = reasoning ? { reasoning } : {};
|
|
13233
|
+
return error ? { provider, model, credentialAvailable, text, error, ...usageField, ...latencyField, ...reasoningField } : { provider, model, credentialAvailable, text, ...usageField, ...latencyField, ...reasoningField };
|
|
13132
13234
|
}
|
|
13133
13235
|
var DEFAULT_PROVIDER = "anthropic", DEFAULT_MODELS;
|
|
13134
13236
|
var init_single_turn = __esm(() => {
|
|
@@ -49502,7 +49604,7 @@ function webSandboxProfile(workspace, home) {
|
|
|
49502
49604
|
mode: "read-only",
|
|
49503
49605
|
network: "on",
|
|
49504
49606
|
writableRoots: [],
|
|
49505
|
-
readDenyList: [workspace, home],
|
|
49607
|
+
readDenyList: [workspace, ...defaultReadDenyList(home)],
|
|
49506
49608
|
allowedDomains: [],
|
|
49507
49609
|
required: true
|
|
49508
49610
|
};
|
|
@@ -53454,7 +53556,7 @@ import { spawnSync as spawnSync2 } from "child_process";
|
|
|
53454
53556
|
// package.json
|
|
53455
53557
|
var package_default = {
|
|
53456
53558
|
name: "@mrciphersmith/keryx",
|
|
53457
|
-
version: "0.2.
|
|
53559
|
+
version: "0.2.62",
|
|
53458
53560
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
53459
53561
|
private: false,
|
|
53460
53562
|
publishConfig: {
|
|
@@ -57032,7 +57134,7 @@ var AGENT_SLASH_COMMANDS = [
|
|
|
57032
57134
|
{ name: "/theme", description: "Open the theme picker \u2014 /theme [name] applies immediately", modes: BOTH },
|
|
57033
57135
|
{
|
|
57034
57136
|
name: "/game",
|
|
57035
|
-
description: "
|
|
57137
|
+
description: "Games against the model \u2014 /game [seconds] raises the model-turn deadline; add games via src/tui/games/",
|
|
57036
57138
|
modes: AGENT_ONLY
|
|
57037
57139
|
},
|
|
57038
57140
|
{
|
|
@@ -57413,8 +57515,75 @@ function openThemePicker(otui, chrome, options) {
|
|
|
57413
57515
|
return presentThemePicker((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
|
|
57414
57516
|
}
|
|
57415
57517
|
|
|
57416
|
-
// src/tui/
|
|
57417
|
-
|
|
57518
|
+
// src/tui/games/registry.ts
|
|
57519
|
+
function createRegistry(games) {
|
|
57520
|
+
return {
|
|
57521
|
+
games,
|
|
57522
|
+
get(id) {
|
|
57523
|
+
return games.find((game) => game.id === id);
|
|
57524
|
+
}
|
|
57525
|
+
};
|
|
57526
|
+
}
|
|
57527
|
+
|
|
57528
|
+
// src/tui/games/stats.ts
|
|
57529
|
+
function emptyTurnTotals() {
|
|
57530
|
+
return {
|
|
57531
|
+
turns: 0,
|
|
57532
|
+
latencyMs: undefined,
|
|
57533
|
+
totalMs: undefined,
|
|
57534
|
+
inputTokens: undefined,
|
|
57535
|
+
outputTokens: undefined,
|
|
57536
|
+
localFallbacks: 0,
|
|
57537
|
+
errors: 0
|
|
57538
|
+
};
|
|
57539
|
+
}
|
|
57540
|
+
function addMaybe(a, b) {
|
|
57541
|
+
if (a === undefined && b === undefined) {
|
|
57542
|
+
return;
|
|
57543
|
+
}
|
|
57544
|
+
return (a ?? 0) + (b ?? 0);
|
|
57545
|
+
}
|
|
57546
|
+
function addTurn(totals, turn) {
|
|
57547
|
+
return {
|
|
57548
|
+
turns: totals.turns + 1,
|
|
57549
|
+
latencyMs: addMaybe(totals.latencyMs, turn.latencyMs),
|
|
57550
|
+
totalMs: addMaybe(totals.totalMs, turn.totalMs),
|
|
57551
|
+
inputTokens: addMaybe(totals.inputTokens, turn.inputTokens),
|
|
57552
|
+
outputTokens: addMaybe(totals.outputTokens, turn.outputTokens),
|
|
57553
|
+
localFallbacks: totals.localFallbacks + (turn.localFallback ? 1 : 0),
|
|
57554
|
+
errors: totals.errors + (turn.error ? 1 : 0)
|
|
57555
|
+
};
|
|
57556
|
+
}
|
|
57557
|
+
function formatMs(ms) {
|
|
57558
|
+
if (ms === undefined) {
|
|
57559
|
+
return "\u2013";
|
|
57560
|
+
}
|
|
57561
|
+
if (ms < 1000) {
|
|
57562
|
+
return `${ms}ms`;
|
|
57563
|
+
}
|
|
57564
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
57565
|
+
}
|
|
57566
|
+
function formatTokens(n) {
|
|
57567
|
+
if (n === undefined) {
|
|
57568
|
+
return "\u2013";
|
|
57569
|
+
}
|
|
57570
|
+
if (n < 1000) {
|
|
57571
|
+
return `${n}`;
|
|
57572
|
+
}
|
|
57573
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
57574
|
+
}
|
|
57575
|
+
|
|
57576
|
+
// src/tui/games/constants.ts
|
|
57577
|
+
var GAME_MODEL_TIMEOUT_MS = 60000;
|
|
57578
|
+
var GAMES_FOOTER = [
|
|
57579
|
+
{ key: "h/l", label: "move" },
|
|
57580
|
+
{ key: "enter", label: "place" },
|
|
57581
|
+
{ key: "r", label: "new game" },
|
|
57582
|
+
{ key: "\u2190/\u2192", label: "games" },
|
|
57583
|
+
{ key: "esc", label: "minimize" }
|
|
57584
|
+
];
|
|
57585
|
+
|
|
57586
|
+
// src/tui/games/tic-tac-toe/core.ts
|
|
57418
57587
|
var WIN_LINES = [
|
|
57419
57588
|
[0, 1, 2],
|
|
57420
57589
|
[3, 4, 5],
|
|
@@ -57444,6 +57613,29 @@ function checkWinner(board) {
|
|
|
57444
57613
|
function freeCells(board) {
|
|
57445
57614
|
return board.flatMap((cell, index) => cell === null ? [index] : []);
|
|
57446
57615
|
}
|
|
57616
|
+
function placeMark(board, index, mark) {
|
|
57617
|
+
if (index < 0 || index > 8 || board[index] !== null) {
|
|
57618
|
+
return;
|
|
57619
|
+
}
|
|
57620
|
+
const next = board.slice();
|
|
57621
|
+
next[index] = mark;
|
|
57622
|
+
const win = checkWinner(next);
|
|
57623
|
+
const draw = win === null && next.every((cell) => cell !== null);
|
|
57624
|
+
return {
|
|
57625
|
+
board: next,
|
|
57626
|
+
turn: win === null && !draw ? mark === "X" ? "O" : "X" : mark,
|
|
57627
|
+
winner: win?.winner ?? null,
|
|
57628
|
+
winLine: win?.line ?? null,
|
|
57629
|
+
draw,
|
|
57630
|
+
cursor: index
|
|
57631
|
+
};
|
|
57632
|
+
}
|
|
57633
|
+
function freshGame() {
|
|
57634
|
+
return { board: emptyBoard(), turn: "X", winner: null, winLine: null, draw: false, cursor: 4 };
|
|
57635
|
+
}
|
|
57636
|
+
function isGameOver(state) {
|
|
57637
|
+
return state.winner !== null || state.draw;
|
|
57638
|
+
}
|
|
57447
57639
|
function bestLocalMove(board, mark) {
|
|
57448
57640
|
const free = freeCells(board);
|
|
57449
57641
|
if (free.length === 0) {
|
|
@@ -57466,28 +57658,6 @@ function bestLocalMove(board, mark) {
|
|
|
57466
57658
|
}
|
|
57467
57659
|
return free[0];
|
|
57468
57660
|
}
|
|
57469
|
-
function placeMark(board, index, mark) {
|
|
57470
|
-
if (index < 0 || index > 8 || board[index] !== null) {
|
|
57471
|
-
return;
|
|
57472
|
-
}
|
|
57473
|
-
const next = board.slice();
|
|
57474
|
-
next[index] = mark;
|
|
57475
|
-
const win = checkWinner(next);
|
|
57476
|
-
const draw = win === null && next.every((cell) => cell !== null);
|
|
57477
|
-
return {
|
|
57478
|
-
board: next,
|
|
57479
|
-
turn: win === null && !draw ? mark === "X" ? "O" : "X" : mark,
|
|
57480
|
-
winner: win?.winner ?? null,
|
|
57481
|
-
winLine: win?.line ?? null,
|
|
57482
|
-
draw
|
|
57483
|
-
};
|
|
57484
|
-
}
|
|
57485
|
-
function freshGame() {
|
|
57486
|
-
return { board: emptyBoard(), turn: "X", winner: null, winLine: null, draw: false };
|
|
57487
|
-
}
|
|
57488
|
-
function isGameOver(state) {
|
|
57489
|
-
return state.winner !== null || state.draw;
|
|
57490
|
-
}
|
|
57491
57661
|
function parseModelMove(reply, board) {
|
|
57492
57662
|
if (reply.length === 0) {
|
|
57493
57663
|
return;
|
|
@@ -57499,6 +57669,11 @@ function parseModelMove(reply, board) {
|
|
|
57499
57669
|
const index = Number(match[1]);
|
|
57500
57670
|
return board[index] === null ? index : undefined;
|
|
57501
57671
|
}
|
|
57672
|
+
function asTtt(state) {
|
|
57673
|
+
return state;
|
|
57674
|
+
}
|
|
57675
|
+
|
|
57676
|
+
// src/tui/games/tic-tac-toe/prompts.ts
|
|
57502
57677
|
function gameSystemPrompt() {
|
|
57503
57678
|
return [
|
|
57504
57679
|
"You are playing tic-tac-toe as O against a human playing X.",
|
|
@@ -57520,31 +57695,43 @@ ${rows.join(`
|
|
|
57520
57695
|
|
|
57521
57696
|
Make your move. Reply with one digit 0-8.`;
|
|
57522
57697
|
}
|
|
57523
|
-
|
|
57524
|
-
|
|
57525
|
-
|
|
57526
|
-
|
|
57527
|
-
|
|
57528
|
-
|
|
57529
|
-
|
|
57530
|
-
|
|
57531
|
-
|
|
57532
|
-
...opts.env !== undefined ? { env: opts.env } : {}
|
|
57533
|
-
});
|
|
57534
|
-
if (turn.error !== undefined) {
|
|
57535
|
-
return { move: undefined, error: `model error: ${turn.error.message}` };
|
|
57698
|
+
|
|
57699
|
+
// src/tui/games/tic-tac-toe/input.ts
|
|
57700
|
+
function wrapCursor(row, col) {
|
|
57701
|
+
return (row + 3) % 3 * 3 + (col + 3) % 3;
|
|
57702
|
+
}
|
|
57703
|
+
function onKey(state, key) {
|
|
57704
|
+
const name = key.name || key.sequence;
|
|
57705
|
+
if (isGameOver(state)) {
|
|
57706
|
+
return;
|
|
57536
57707
|
}
|
|
57537
|
-
|
|
57538
|
-
|
|
57708
|
+
const row = Math.floor(state.cursor / 3);
|
|
57709
|
+
const col = state.cursor % 3;
|
|
57710
|
+
if (name === "up" || name === "k") {
|
|
57711
|
+
return { ...state, cursor: wrapCursor(row - 1, col) };
|
|
57539
57712
|
}
|
|
57540
|
-
|
|
57713
|
+
if (name === "down" || name === "j") {
|
|
57714
|
+
return { ...state, cursor: wrapCursor(row + 1, col) };
|
|
57715
|
+
}
|
|
57716
|
+
if (name === "left" || name === "h") {
|
|
57717
|
+
return { ...state, cursor: wrapCursor(row, col - 1) };
|
|
57718
|
+
}
|
|
57719
|
+
if (name === "right" || name === "l") {
|
|
57720
|
+
return { ...state, cursor: wrapCursor(row, col + 1) };
|
|
57721
|
+
}
|
|
57722
|
+
if (name === "return" || name === "enter" || name === "space" || name === " ") {
|
|
57723
|
+
if (state.turn !== "X") {
|
|
57724
|
+
return;
|
|
57725
|
+
}
|
|
57726
|
+
return placeMark(state.board, state.cursor, "X");
|
|
57727
|
+
}
|
|
57728
|
+
return;
|
|
57541
57729
|
}
|
|
57542
|
-
|
|
57543
|
-
|
|
57544
|
-
|
|
57545
|
-
|
|
57546
|
-
|
|
57547
|
-
];
|
|
57730
|
+
function markFor(player) {
|
|
57731
|
+
return player === "human" ? "X" : "O";
|
|
57732
|
+
}
|
|
57733
|
+
|
|
57734
|
+
// src/tui/games/tic-tac-toe/layout.ts
|
|
57548
57735
|
var GAME_CELL_GAP = 1;
|
|
57549
57736
|
var GAME_BOARD_CHROME_X = 4;
|
|
57550
57737
|
var GAME_CELL_SIZES = {
|
|
@@ -57558,7 +57745,198 @@ function resolveCellSize(availableWidth) {
|
|
|
57558
57745
|
const large = GAME_CELL_SIZES.large;
|
|
57559
57746
|
return gameBoardWidth(large.width) <= availableWidth ? large : GAME_CELL_SIZES.small;
|
|
57560
57747
|
}
|
|
57561
|
-
|
|
57748
|
+
|
|
57749
|
+
// src/tui/games/tic-tac-toe/render.ts
|
|
57750
|
+
function markColor(mark, theme) {
|
|
57751
|
+
return mark === "X" ? theme.ok ?? "" : theme.error ?? "";
|
|
57752
|
+
}
|
|
57753
|
+
function statusText(state) {
|
|
57754
|
+
if (state.winner !== null) {
|
|
57755
|
+
return `${state.winner} wins! (r \u2014 new game)`;
|
|
57756
|
+
}
|
|
57757
|
+
if (state.draw) {
|
|
57758
|
+
return "Draw! (r \u2014 new game)";
|
|
57759
|
+
}
|
|
57760
|
+
return state.turn === "X" ? "Your turn \u2014 X" : "Agent's turn \u2014 O";
|
|
57761
|
+
}
|
|
57762
|
+
function render2(state, ctx) {
|
|
57763
|
+
const core = ctx.core;
|
|
57764
|
+
const r = ctx.renderer;
|
|
57765
|
+
const theme = ctx.theme;
|
|
57766
|
+
const cellSize = resolveCellSize(ctx.width);
|
|
57767
|
+
const wrap2 = new core.BoxRenderable(r, {
|
|
57768
|
+
id: "game-wrap",
|
|
57769
|
+
width: "100%",
|
|
57770
|
+
flexDirection: "column",
|
|
57771
|
+
alignItems: "center"
|
|
57772
|
+
});
|
|
57773
|
+
const legend = new core.BoxRenderable(r, {
|
|
57774
|
+
id: "game-legend",
|
|
57775
|
+
flexDirection: "row",
|
|
57776
|
+
marginBottom: 1
|
|
57777
|
+
});
|
|
57778
|
+
legend.add(new core.TextRenderable(r, { id: "game-legend-x", content: "X you", fg: theme.ok }));
|
|
57779
|
+
legend.add(new core.TextRenderable(r, { id: "game-legend-sep", content: " \xB7 ", fg: theme.muted }));
|
|
57780
|
+
legend.add(new core.TextRenderable(r, { id: "game-legend-o", content: "O model", fg: theme.error }));
|
|
57781
|
+
wrap2.add(legend);
|
|
57782
|
+
const board = new core.BoxRenderable(r, {
|
|
57783
|
+
id: "game-board",
|
|
57784
|
+
flexDirection: "column",
|
|
57785
|
+
flexShrink: 0,
|
|
57786
|
+
border: true,
|
|
57787
|
+
borderStyle: "rounded",
|
|
57788
|
+
borderColor: theme.border,
|
|
57789
|
+
backgroundColor: theme.panel,
|
|
57790
|
+
paddingLeft: 1,
|
|
57791
|
+
paddingRight: 1
|
|
57792
|
+
});
|
|
57793
|
+
for (let row = 0;row < 3; row++) {
|
|
57794
|
+
const rowBox = new core.BoxRenderable(r, {
|
|
57795
|
+
id: `game-row-${row}`,
|
|
57796
|
+
flexDirection: "row",
|
|
57797
|
+
flexShrink: 0,
|
|
57798
|
+
gap: 1
|
|
57799
|
+
});
|
|
57800
|
+
for (let col = 0;col < 3; col++) {
|
|
57801
|
+
const index = row * 3 + col;
|
|
57802
|
+
const cell = state.board[index] ?? null;
|
|
57803
|
+
const isCursor = index === state.cursor && !isGameOver(state);
|
|
57804
|
+
const won = state.winLine?.includes(index) === true;
|
|
57805
|
+
const cellBox = new core.BoxRenderable(r, {
|
|
57806
|
+
id: `game-cell-${index}`,
|
|
57807
|
+
width: cellSize.width,
|
|
57808
|
+
height: cellSize.height,
|
|
57809
|
+
flexShrink: 0,
|
|
57810
|
+
flexGrow: 0,
|
|
57811
|
+
flexDirection: "row",
|
|
57812
|
+
alignItems: "center",
|
|
57813
|
+
justifyContent: "center",
|
|
57814
|
+
border: true,
|
|
57815
|
+
borderStyle: "rounded",
|
|
57816
|
+
borderColor: won ? markColor(state.winner ?? "X", theme) : isCursor ? theme.focus : theme.border,
|
|
57817
|
+
backgroundColor: isCursor || won ? theme.highlight : undefined
|
|
57818
|
+
});
|
|
57819
|
+
const cellText = new core.TextRenderable(r, {
|
|
57820
|
+
id: `game-cell-text-${index}`,
|
|
57821
|
+
content: cell ?? "\xB7",
|
|
57822
|
+
fg: cell === null ? theme.muted : markColor(cell, theme)
|
|
57823
|
+
});
|
|
57824
|
+
cellBox.add(cellText);
|
|
57825
|
+
rowBox.add(cellBox);
|
|
57826
|
+
}
|
|
57827
|
+
board.add(rowBox);
|
|
57828
|
+
}
|
|
57829
|
+
wrap2.add(board);
|
|
57830
|
+
wrap2.add(new core.TextRenderable(r, { id: "game-status", content: statusText(state), marginTop: 1 }));
|
|
57831
|
+
ctx.parent.add(wrap2);
|
|
57832
|
+
}
|
|
57833
|
+
|
|
57834
|
+
// src/tui/games/tic-tac-toe/game.ts
|
|
57835
|
+
function statusOf2(s) {
|
|
57836
|
+
if (s.winner !== null) {
|
|
57837
|
+
return `${s.winner} wins! (r \u2014 new game)`;
|
|
57838
|
+
}
|
|
57839
|
+
if (s.draw) {
|
|
57840
|
+
return "Draw! (r \u2014 new game)";
|
|
57841
|
+
}
|
|
57842
|
+
return s.turn === "X" ? "Your turn \u2014 X" : "Agent's turn \u2014 O";
|
|
57843
|
+
}
|
|
57844
|
+
var ticTacToeGame = {
|
|
57845
|
+
id: "tic-tac-toe",
|
|
57846
|
+
label: "Tic-tac-toe",
|
|
57847
|
+
fresh: () => freshGame(),
|
|
57848
|
+
turn: (state) => asTtt(state).turn === "X" ? "human" : "model",
|
|
57849
|
+
isOver: (state) => isGameOver(asTtt(state)),
|
|
57850
|
+
outcome: (state) => {
|
|
57851
|
+
const s = asTtt(state);
|
|
57852
|
+
if (s.winner === "X") {
|
|
57853
|
+
return "human";
|
|
57854
|
+
}
|
|
57855
|
+
if (s.winner === "O") {
|
|
57856
|
+
return "model";
|
|
57857
|
+
}
|
|
57858
|
+
return s.draw ? "draw" : null;
|
|
57859
|
+
},
|
|
57860
|
+
status: (state) => statusOf2(asTtt(state)),
|
|
57861
|
+
systemPrompt: () => gameSystemPrompt(),
|
|
57862
|
+
stateForModel: (state) => gameUserPrompt(asTtt(state).board),
|
|
57863
|
+
parseMove: (reply, state) => parseModelMove(reply, asTtt(state).board),
|
|
57864
|
+
applyMove: (state, move, by) => placeMark(asTtt(state).board, move, markFor(by)),
|
|
57865
|
+
localMove: (state, by) => bestLocalMove(asTtt(state).board, markFor(by)),
|
|
57866
|
+
pass: (state) => {
|
|
57867
|
+
const s = asTtt(state);
|
|
57868
|
+
return { ...s, turn: s.turn === "X" ? "O" : "X" };
|
|
57869
|
+
},
|
|
57870
|
+
onKey: (state, key) => onKey(asTtt(state), key),
|
|
57871
|
+
render: (state, ctx) => render2(asTtt(state), ctx)
|
|
57872
|
+
};
|
|
57873
|
+
// src/tui/games/model-turn.ts
|
|
57874
|
+
init_single_turn();
|
|
57875
|
+
async function runGameModelTurn(game, state, opts) {
|
|
57876
|
+
const startedAt = performance.now();
|
|
57877
|
+
const turn = await runModelTurn({
|
|
57878
|
+
system: game.systemPrompt(),
|
|
57879
|
+
user: game.stateForModel(state),
|
|
57880
|
+
...opts.provider !== undefined ? { provider: opts.provider } : {},
|
|
57881
|
+
...opts.model !== undefined ? { model: opts.model } : {},
|
|
57882
|
+
maxOutputTokens: 256,
|
|
57883
|
+
requestId: "keryx-game",
|
|
57884
|
+
...opts.providerFactory !== undefined ? { providerFactory: opts.providerFactory } : {},
|
|
57885
|
+
...opts.env !== undefined ? { env: opts.env } : {}
|
|
57886
|
+
});
|
|
57887
|
+
const totalMs = Math.max(0, Math.round(performance.now() - startedAt));
|
|
57888
|
+
const stats = {
|
|
57889
|
+
provider: turn.provider,
|
|
57890
|
+
model: turn.model,
|
|
57891
|
+
latencyMs: turn.latencyMs,
|
|
57892
|
+
totalMs,
|
|
57893
|
+
inputTokens: turn.usage?.inputTokens,
|
|
57894
|
+
outputTokens: turn.usage?.outputTokens,
|
|
57895
|
+
reasoning: turn.reasoning === true,
|
|
57896
|
+
localFallback: false,
|
|
57897
|
+
error: turn.error !== undefined
|
|
57898
|
+
};
|
|
57899
|
+
if (turn.error !== undefined) {
|
|
57900
|
+
return { move: undefined, error: `model error: ${turn.error.message}`, stats };
|
|
57901
|
+
}
|
|
57902
|
+
if (!turn.credentialAvailable && opts.providerFactory === undefined) {
|
|
57903
|
+
return { move: undefined, error: "no model credential \u2014 configure a provider first (/provider)", stats };
|
|
57904
|
+
}
|
|
57905
|
+
return { move: game.parseMove(turn.text, state), error: undefined, stats };
|
|
57906
|
+
}
|
|
57907
|
+
|
|
57908
|
+
// src/tui/games/agent-panel.ts
|
|
57909
|
+
function renderAgentPanel(game, parent, core, renderer, args2) {
|
|
57910
|
+
const theme = getTheme();
|
|
57911
|
+
const line = (content, fg, id) => {
|
|
57912
|
+
parent.add(new core.TextRenderable(renderer, { content, fg, ...id !== undefined ? { id } : {} }));
|
|
57913
|
+
};
|
|
57914
|
+
if (args2.modelBusy) {
|
|
57915
|
+
line("agent is thinking\u2026", theme.focus, "game-notice");
|
|
57916
|
+
} else {
|
|
57917
|
+
line(args2.notice ?? "", theme.error, "game-notice");
|
|
57918
|
+
}
|
|
57919
|
+
const sys = game.systemPrompt().split(`
|
|
57920
|
+
`);
|
|
57921
|
+
line(`system: ${sys.join(" \xB7 ")}`, theme.muted, "game-system");
|
|
57922
|
+
const lt = args2.lastTurn;
|
|
57923
|
+
const tot = args2.totals;
|
|
57924
|
+
const model = lt === undefined ? "\u2013" : `${lt.provider}/${lt.model}`;
|
|
57925
|
+
const parts = [
|
|
57926
|
+
`model: ${model}`,
|
|
57927
|
+
`first byte ${formatMs(lt?.latencyMs)}`,
|
|
57928
|
+
`total ${formatMs(lt?.totalMs)}`,
|
|
57929
|
+
`in ${formatTokens(lt?.inputTokens)}`,
|
|
57930
|
+
`out ${formatTokens(lt?.outputTokens)}`,
|
|
57931
|
+
lt?.reasoning === true ? "reasoning" : undefined,
|
|
57932
|
+
`turns ${tot.turns}`,
|
|
57933
|
+
`fallbacks ${tot.localFallbacks}`,
|
|
57934
|
+
tot.errors > 0 ? `errors ${tot.errors}` : undefined
|
|
57935
|
+
];
|
|
57936
|
+
line(parts.filter((p) => p !== undefined).join(" \xB7 "), theme.muted, "game-stats");
|
|
57937
|
+
}
|
|
57938
|
+
|
|
57939
|
+
// src/tui/games/otui.ts
|
|
57562
57940
|
function asOtui2(otui) {
|
|
57563
57941
|
if (otui === undefined || otui === null) {
|
|
57564
57942
|
return;
|
|
@@ -57569,60 +57947,90 @@ function asOtui2(otui) {
|
|
|
57569
57947
|
}
|
|
57570
57948
|
return cand;
|
|
57571
57949
|
}
|
|
57572
|
-
|
|
57573
|
-
|
|
57574
|
-
|
|
57575
|
-
|
|
57576
|
-
|
|
57577
|
-
|
|
57578
|
-
|
|
57579
|
-
|
|
57580
|
-
}
|
|
57581
|
-
function statusText(state) {
|
|
57582
|
-
if (state.winner !== null) {
|
|
57583
|
-
return `${state.winner} wins! (r \u2014 new game)`;
|
|
57584
|
-
}
|
|
57585
|
-
if (state.draw) {
|
|
57586
|
-
return "Draw! (r \u2014 new game)";
|
|
57950
|
+
|
|
57951
|
+
// src/tui/games/modal.ts
|
|
57952
|
+
var DEFAULT_GAMES = [ticTacToeGame];
|
|
57953
|
+
function presentGamesModal(openModalFn, otui, chrome, options = {}, registry = createRegistry(DEFAULT_GAMES)) {
|
|
57954
|
+
const games = registry.games;
|
|
57955
|
+
const first = games[0];
|
|
57956
|
+
if (first === undefined) {
|
|
57957
|
+
return;
|
|
57587
57958
|
}
|
|
57588
|
-
return state.turn === "X" ? "Your turn \u2014 X" : "Agent's turn \u2014 O";
|
|
57589
|
-
}
|
|
57590
|
-
function presentGame(openModalFn, otui, chrome, options = {}) {
|
|
57591
|
-
const renderer = options.renderer;
|
|
57592
57959
|
const core = asOtui2(otui);
|
|
57960
|
+
const renderer = options.renderer;
|
|
57593
57961
|
let handle;
|
|
57594
|
-
let
|
|
57595
|
-
|
|
57596
|
-
|
|
57597
|
-
|
|
57962
|
+
let activeId = first.id;
|
|
57963
|
+
const states = new Map;
|
|
57964
|
+
const totals = new Map;
|
|
57965
|
+
const lastTurn = new Map;
|
|
57598
57966
|
let notice;
|
|
57967
|
+
let modelBusy = false;
|
|
57599
57968
|
let unsubscribeKey;
|
|
57969
|
+
let bodyRef;
|
|
57970
|
+
let bodyWidth = 0;
|
|
57971
|
+
const stateOf = (id) => {
|
|
57972
|
+
const game = registry.get(id);
|
|
57973
|
+
if (game === undefined) {
|
|
57974
|
+
throw new Error(`unknown game: ${id}`);
|
|
57975
|
+
}
|
|
57976
|
+
let state = states.get(id);
|
|
57977
|
+
if (state === undefined) {
|
|
57978
|
+
state = game.fresh();
|
|
57979
|
+
states.set(id, state);
|
|
57980
|
+
}
|
|
57981
|
+
return state;
|
|
57982
|
+
};
|
|
57983
|
+
const totalsOf = (id) => {
|
|
57984
|
+
let t = totals.get(id);
|
|
57985
|
+
if (t === undefined) {
|
|
57986
|
+
t = emptyTurnTotals();
|
|
57987
|
+
totals.set(id, t);
|
|
57988
|
+
}
|
|
57989
|
+
return t;
|
|
57990
|
+
};
|
|
57600
57991
|
const paint = () => {
|
|
57601
|
-
if (
|
|
57992
|
+
if (core === undefined || bodyRef === undefined) {
|
|
57602
57993
|
return;
|
|
57603
57994
|
}
|
|
57604
|
-
|
|
57605
|
-
|
|
57606
|
-
|
|
57607
|
-
|
|
57608
|
-
noticeBox.content = "agent is thinking\u2026";
|
|
57609
|
-
noticeBox.fg = theme.focus;
|
|
57610
|
-
} else {
|
|
57611
|
-
noticeBox.content = notice ?? "";
|
|
57612
|
-
noticeBox.fg = theme.error;
|
|
57995
|
+
clearTranscriptChildren(bodyRef);
|
|
57996
|
+
const game = registry.get(activeId);
|
|
57997
|
+
if (game === undefined) {
|
|
57998
|
+
return;
|
|
57613
57999
|
}
|
|
57614
|
-
|
|
57615
|
-
|
|
57616
|
-
|
|
57617
|
-
|
|
57618
|
-
|
|
57619
|
-
|
|
57620
|
-
|
|
57621
|
-
|
|
58000
|
+
const state = stateOf(activeId);
|
|
58001
|
+
const ctx = {
|
|
58002
|
+
core,
|
|
58003
|
+
renderer,
|
|
58004
|
+
theme: getTheme(),
|
|
58005
|
+
parent: bodyRef,
|
|
58006
|
+
width: bodyWidth
|
|
58007
|
+
};
|
|
58008
|
+
game.render(state, ctx);
|
|
58009
|
+
renderAgentPanel(game, bodyRef, core, renderer, {
|
|
58010
|
+
notice,
|
|
58011
|
+
modelBusy,
|
|
58012
|
+
lastTurn: lastTurn.get(activeId),
|
|
58013
|
+
totals: totalsOf(activeId)
|
|
58014
|
+
});
|
|
58015
|
+
};
|
|
58016
|
+
const restart = () => {
|
|
58017
|
+
const game = registry.get(activeId);
|
|
58018
|
+
if (game === undefined) {
|
|
58019
|
+
return;
|
|
57622
58020
|
}
|
|
58021
|
+
states.set(activeId, game.fresh());
|
|
58022
|
+
totals.set(activeId, emptyTurnTotals());
|
|
58023
|
+
lastTurn.set(activeId, undefined);
|
|
58024
|
+
notice = undefined;
|
|
58025
|
+
paint();
|
|
57623
58026
|
};
|
|
57624
58027
|
const applyModelMove = async () => {
|
|
57625
|
-
|
|
58028
|
+
const game = registry.get(activeId);
|
|
58029
|
+
if (game === undefined) {
|
|
58030
|
+
return;
|
|
58031
|
+
}
|
|
58032
|
+
const state = stateOf(activeId);
|
|
58033
|
+
if (modelBusy || game.isOver(state) || game.turn(state) !== "model") {
|
|
57626
58034
|
return;
|
|
57627
58035
|
}
|
|
57628
58036
|
modelBusy = true;
|
|
@@ -57633,30 +58041,51 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
|
|
|
57633
58041
|
const deadline = new Promise((resolve3) => {
|
|
57634
58042
|
timer = setTimeout(() => resolve3("timeout"), timeoutMs);
|
|
57635
58043
|
});
|
|
57636
|
-
const outcome = await Promise.race([
|
|
57637
|
-
modelMove(currentGame.board, {
|
|
57638
|
-
...options.provider !== undefined ? { provider: options.provider } : {},
|
|
57639
|
-
...options.model !== undefined ? { model: options.model } : {},
|
|
57640
|
-
...options.providerFactory !== undefined ? { providerFactory: options.providerFactory } : {},
|
|
57641
|
-
...options.env !== undefined ? { env: options.env } : {}
|
|
57642
|
-
}),
|
|
57643
|
-
deadline
|
|
57644
|
-
]);
|
|
58044
|
+
const outcome = await Promise.race([runGameModelTurn(game, state, options), deadline]);
|
|
57645
58045
|
if (timer !== undefined) {
|
|
57646
58046
|
clearTimeout(timer);
|
|
57647
58047
|
}
|
|
57648
58048
|
modelBusy = false;
|
|
57649
58049
|
const timedOut = outcome === "timeout";
|
|
57650
|
-
const result = timedOut ?
|
|
57651
|
-
let
|
|
58050
|
+
const result = timedOut ? undefined : outcome;
|
|
58051
|
+
let stats;
|
|
58052
|
+
if (timedOut) {
|
|
58053
|
+
stats = {
|
|
58054
|
+
provider: "\u2013",
|
|
58055
|
+
model: "\u2013",
|
|
58056
|
+
latencyMs: undefined,
|
|
58057
|
+
totalMs: timeoutMs,
|
|
58058
|
+
inputTokens: undefined,
|
|
58059
|
+
outputTokens: undefined,
|
|
58060
|
+
reasoning: false,
|
|
58061
|
+
localFallback: true,
|
|
58062
|
+
error: false
|
|
58063
|
+
};
|
|
58064
|
+
} else if (result !== undefined) {
|
|
58065
|
+
stats = result.stats;
|
|
58066
|
+
} else {
|
|
58067
|
+
stats = {
|
|
58068
|
+
provider: "\u2013",
|
|
58069
|
+
model: "\u2013",
|
|
58070
|
+
latencyMs: undefined,
|
|
58071
|
+
totalMs: undefined,
|
|
58072
|
+
inputTokens: undefined,
|
|
58073
|
+
outputTokens: undefined,
|
|
58074
|
+
reasoning: false,
|
|
58075
|
+
localFallback: true,
|
|
58076
|
+
error: true
|
|
58077
|
+
};
|
|
58078
|
+
}
|
|
58079
|
+
let move = result?.move;
|
|
57652
58080
|
let playedLocally = false;
|
|
57653
|
-
if (move === undefined && result
|
|
57654
|
-
move =
|
|
58081
|
+
if (move === undefined && result?.error === undefined) {
|
|
58082
|
+
move = game.localMove(state, "model");
|
|
57655
58083
|
playedLocally = move !== undefined;
|
|
58084
|
+
stats = { ...stats, localFallback: playedLocally };
|
|
57656
58085
|
}
|
|
57657
|
-
const placed = move === undefined ? undefined :
|
|
57658
|
-
|
|
57659
|
-
if (result
|
|
58086
|
+
const placed = move === undefined ? undefined : game.applyMove(state, move, "model");
|
|
58087
|
+
const next = placed ?? game.pass(state);
|
|
58088
|
+
if (result?.error !== undefined) {
|
|
57660
58089
|
notice = `agent: ${result.error}`;
|
|
57661
58090
|
} else if (timedOut && playedLocally) {
|
|
57662
58091
|
notice = `agent timed out after ${Math.round(timeoutMs / 1000)}s \u2014 played a local move`;
|
|
@@ -57665,132 +58094,21 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
|
|
|
57665
58094
|
} else {
|
|
57666
58095
|
notice = undefined;
|
|
57667
58096
|
}
|
|
57668
|
-
|
|
57669
|
-
|
|
57670
|
-
|
|
57671
|
-
if (modelBusy || isGameOver(currentGame) || currentGame.turn !== "X") {
|
|
57672
|
-
return;
|
|
57673
|
-
}
|
|
57674
|
-
const placed = placeMark(currentGame.board, cursor, "X");
|
|
57675
|
-
if (placed === undefined) {
|
|
57676
|
-
return;
|
|
57677
|
-
}
|
|
57678
|
-
currentGame = placed;
|
|
57679
|
-
notice = undefined;
|
|
57680
|
-
paint();
|
|
57681
|
-
if (!isGameOver(currentGame) && currentGame.turn === "O") {
|
|
57682
|
-
applyModelMove();
|
|
57683
|
-
}
|
|
57684
|
-
};
|
|
57685
|
-
const moveCursor = (dr, dc) => {
|
|
57686
|
-
if (modelBusy) {
|
|
57687
|
-
return;
|
|
57688
|
-
}
|
|
57689
|
-
const row = Math.floor(cursor / 3);
|
|
57690
|
-
const col = cursor % 3;
|
|
57691
|
-
cursor = (row + dr + 3) % 3 * 3 + (col + dc + 3) % 3;
|
|
57692
|
-
paint();
|
|
57693
|
-
};
|
|
57694
|
-
const restart = () => {
|
|
57695
|
-
resetGame();
|
|
57696
|
-
cursor = 4;
|
|
57697
|
-
notice = undefined;
|
|
58097
|
+
states.set(activeId, next);
|
|
58098
|
+
totals.set(activeId, addTurn(totalsOf(activeId), stats));
|
|
58099
|
+
lastTurn.set(activeId, stats);
|
|
57698
58100
|
paint();
|
|
57699
58101
|
};
|
|
57700
58102
|
handle = openModalFn(otui, chrome, {
|
|
57701
58103
|
title: "/game",
|
|
57702
|
-
tabs:
|
|
57703
|
-
footer:
|
|
57704
|
-
onArrowKeys: (_key, direction) => {
|
|
57705
|
-
moveCursor(0, direction === "left" ? -1 : 1);
|
|
57706
|
-
return true;
|
|
57707
|
-
},
|
|
58104
|
+
tabs: games.map((game) => ({ id: game.id, label: game.label })),
|
|
58105
|
+
footer: GAMES_FOOTER,
|
|
57708
58106
|
renderTab: (_tabId, body, ctx) => {
|
|
57709
58107
|
if (body === undefined || body === null) {
|
|
57710
58108
|
return;
|
|
57711
58109
|
}
|
|
57712
|
-
|
|
57713
|
-
|
|
57714
|
-
return;
|
|
57715
|
-
}
|
|
57716
|
-
const theme = getTheme();
|
|
57717
|
-
const r = renderer;
|
|
57718
|
-
const cellSize = resolveCellSize(ctx.width);
|
|
57719
|
-
const wrap2 = new core.BoxRenderable(r, {
|
|
57720
|
-
id: "game-wrap",
|
|
57721
|
-
width: "100%",
|
|
57722
|
-
flexDirection: "column",
|
|
57723
|
-
alignItems: "center"
|
|
57724
|
-
});
|
|
57725
|
-
const legend = new core.BoxRenderable(r, {
|
|
57726
|
-
id: "game-legend",
|
|
57727
|
-
flexDirection: "row",
|
|
57728
|
-
marginBottom: 1
|
|
57729
|
-
});
|
|
57730
|
-
legend.add(new core.TextRenderable(r, { id: "game-legend-x", content: "X you", fg: theme.ok }));
|
|
57731
|
-
legend.add(new core.TextRenderable(r, { id: "game-legend-sep", content: " \xB7 ", fg: theme.muted }));
|
|
57732
|
-
legend.add(new core.TextRenderable(r, { id: "game-legend-o", content: "O model", fg: theme.error }));
|
|
57733
|
-
wrap2.add(legend);
|
|
57734
|
-
const board = new core.BoxRenderable(r, {
|
|
57735
|
-
id: "game-board",
|
|
57736
|
-
flexDirection: "column",
|
|
57737
|
-
flexShrink: 0,
|
|
57738
|
-
border: true,
|
|
57739
|
-
borderStyle: "rounded",
|
|
57740
|
-
borderColor: theme.border,
|
|
57741
|
-
backgroundColor: theme.panel,
|
|
57742
|
-
paddingLeft: 1,
|
|
57743
|
-
paddingRight: 1
|
|
57744
|
-
});
|
|
57745
|
-
cells = [];
|
|
57746
|
-
for (let row = 0;row < 3; row++) {
|
|
57747
|
-
const rowBox = new core.BoxRenderable(r, {
|
|
57748
|
-
id: `game-row-${row}`,
|
|
57749
|
-
flexDirection: "row",
|
|
57750
|
-
flexShrink: 0,
|
|
57751
|
-
gap: GAME_CELL_GAP
|
|
57752
|
-
});
|
|
57753
|
-
for (let col = 0;col < 3; col++) {
|
|
57754
|
-
const index = row * 3 + col;
|
|
57755
|
-
const cellBox = new core.BoxRenderable(r, {
|
|
57756
|
-
id: `game-cell-${index}`,
|
|
57757
|
-
width: cellSize.width,
|
|
57758
|
-
height: cellSize.height,
|
|
57759
|
-
flexShrink: 0,
|
|
57760
|
-
flexGrow: 0,
|
|
57761
|
-
flexDirection: "row",
|
|
57762
|
-
alignItems: "center",
|
|
57763
|
-
justifyContent: "center",
|
|
57764
|
-
border: true,
|
|
57765
|
-
borderStyle: "rounded",
|
|
57766
|
-
borderColor: theme.border
|
|
57767
|
-
});
|
|
57768
|
-
const cellText = new core.TextRenderable(r, {
|
|
57769
|
-
id: `game-cell-text-${index}`,
|
|
57770
|
-
content: "\xB7",
|
|
57771
|
-
fg: theme.muted
|
|
57772
|
-
});
|
|
57773
|
-
cellBox.add(cellText);
|
|
57774
|
-
rowBox.add(cellBox);
|
|
57775
|
-
cells.push({ box: cellBox, text: cellText });
|
|
57776
|
-
}
|
|
57777
|
-
board.add(rowBox);
|
|
57778
|
-
}
|
|
57779
|
-
wrap2.add(board);
|
|
57780
|
-
const status = new core.TextRenderable(r, {
|
|
57781
|
-
id: "game-status",
|
|
57782
|
-
content: "",
|
|
57783
|
-
marginTop: 1
|
|
57784
|
-
});
|
|
57785
|
-
statusBox = status;
|
|
57786
|
-
wrap2.add(status);
|
|
57787
|
-
const noticeText = new core.TextRenderable(r, {
|
|
57788
|
-
id: "game-notice",
|
|
57789
|
-
content: ""
|
|
57790
|
-
});
|
|
57791
|
-
noticeBox = noticeText;
|
|
57792
|
-
wrap2.add(noticeText);
|
|
57793
|
-
parent.add(wrap2);
|
|
58110
|
+
bodyRef = body;
|
|
58111
|
+
bodyWidth = ctx.width;
|
|
57794
58112
|
paint();
|
|
57795
58113
|
},
|
|
57796
58114
|
onClose: () => {
|
|
@@ -57803,39 +58121,92 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
|
|
|
57803
58121
|
if (options.onKeypress !== undefined) {
|
|
57804
58122
|
unsubscribeKey = options.onKeypress((key) => {
|
|
57805
58123
|
const token = key.name || key.sequence;
|
|
57806
|
-
if (token === "
|
|
57807
|
-
|
|
57808
|
-
return;
|
|
57809
|
-
}
|
|
57810
|
-
if (token === "down" || token === "j") {
|
|
57811
|
-
moveCursor(1, 0);
|
|
57812
|
-
return;
|
|
57813
|
-
}
|
|
57814
|
-
if (token === "h") {
|
|
57815
|
-
moveCursor(0, -1);
|
|
58124
|
+
if (token === "r" || token === "R") {
|
|
58125
|
+
restart();
|
|
57816
58126
|
return;
|
|
57817
58127
|
}
|
|
57818
|
-
|
|
57819
|
-
|
|
58128
|
+
const game = registry.get(activeId);
|
|
58129
|
+
if (game === undefined || modelBusy) {
|
|
57820
58130
|
return;
|
|
57821
58131
|
}
|
|
57822
|
-
|
|
57823
|
-
|
|
58132
|
+
const state = stateOf(activeId);
|
|
58133
|
+
const next = game.onKey(state, key);
|
|
58134
|
+
if (next === undefined) {
|
|
57824
58135
|
return;
|
|
57825
58136
|
}
|
|
57826
|
-
|
|
57827
|
-
|
|
58137
|
+
states.set(activeId, next);
|
|
58138
|
+
paint();
|
|
58139
|
+
if (!game.isOver(next) && game.turn(next) === "model") {
|
|
58140
|
+
applyModelMove();
|
|
57828
58141
|
}
|
|
57829
58142
|
});
|
|
57830
58143
|
}
|
|
57831
58144
|
return {
|
|
57832
58145
|
close: () => handle?.close(),
|
|
57833
58146
|
restart,
|
|
58147
|
+
activeGameId: () => activeId,
|
|
57834
58148
|
modelThinking: () => modelBusy
|
|
57835
58149
|
};
|
|
57836
58150
|
}
|
|
57837
|
-
function
|
|
57838
|
-
return
|
|
58151
|
+
function openGamesModal(otui, chrome, options = {}) {
|
|
58152
|
+
return presentGamesModal((hostOtui, hostChrome, input2) => openModal(hostOtui, hostChrome, input2), otui, chrome, options);
|
|
58153
|
+
}
|
|
58154
|
+
// src/tui/balance-panel.ts
|
|
58155
|
+
init_providers();
|
|
58156
|
+
init_shell_config();
|
|
58157
|
+
function formatBalance(balance) {
|
|
58158
|
+
if (balance === undefined) {
|
|
58159
|
+
return "\u2014";
|
|
58160
|
+
}
|
|
58161
|
+
const amount = balance.remaining ?? balance.total;
|
|
58162
|
+
const symbol = balance.currency === "USD" ? "$" : balance.currency === "EUR" ? "\u20AC" : balance.currency === "GBP" ? "\xA3" : `${balance.currency} `;
|
|
58163
|
+
return `${symbol}${amount.toFixed(2)}`;
|
|
58164
|
+
}
|
|
58165
|
+
function mountBalancePanel(sidebarTop, otui, renderer, options) {
|
|
58166
|
+
const core = otui;
|
|
58167
|
+
const r = renderer;
|
|
58168
|
+
const env = envWithSavedApiKeys(options.env ?? process.env);
|
|
58169
|
+
let current;
|
|
58170
|
+
let textNode;
|
|
58171
|
+
const label = new core.TextRenderable(r, {
|
|
58172
|
+
id: "sb-balance-k",
|
|
58173
|
+
content: core.t`${core.dim("Balance")}`,
|
|
58174
|
+
marginTop: 1
|
|
58175
|
+
});
|
|
58176
|
+
sidebarTop.add(label);
|
|
58177
|
+
const value = new core.TextRenderable(r, {
|
|
58178
|
+
id: "sb-balance-v",
|
|
58179
|
+
content: core.t`${core.dim("\u2026")}`,
|
|
58180
|
+
onMouseDown: () => {
|
|
58181
|
+
refresh();
|
|
58182
|
+
}
|
|
58183
|
+
});
|
|
58184
|
+
textNode = value;
|
|
58185
|
+
sidebarTop.add(value);
|
|
58186
|
+
const paint = (balance) => {
|
|
58187
|
+
current = balance;
|
|
58188
|
+
if (textNode !== undefined) {
|
|
58189
|
+
textNode.content = core.t`${core.dim(formatBalance(balance))}`;
|
|
58190
|
+
}
|
|
58191
|
+
};
|
|
58192
|
+
const refresh = async () => {
|
|
58193
|
+
const provider = balanceCapableProvider(options.provider);
|
|
58194
|
+
if (provider === undefined) {
|
|
58195
|
+
paint(undefined);
|
|
58196
|
+
return;
|
|
58197
|
+
}
|
|
58198
|
+
const apiKey = providerApiKey(provider, env);
|
|
58199
|
+
if (apiKey === undefined) {
|
|
58200
|
+
paint(undefined);
|
|
58201
|
+
return;
|
|
58202
|
+
}
|
|
58203
|
+
const base = resolveProviderBaseUrl(provider, env);
|
|
58204
|
+
const withBase = { ...provider, baseUrl: base };
|
|
58205
|
+
const balance = await fetchProviderBalance(options.fetch ?? globalThis.fetch, withBase, apiKey, { ...options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {} });
|
|
58206
|
+
paint(balance);
|
|
58207
|
+
};
|
|
58208
|
+
refresh();
|
|
58209
|
+
return { refresh, current: () => current };
|
|
57839
58210
|
}
|
|
57840
58211
|
|
|
57841
58212
|
// src/tui/tui-shell.ts
|
|
@@ -58175,7 +58546,7 @@ function showComposerChoice(otui, r, dock, request) {
|
|
|
58175
58546
|
} catch {}
|
|
58176
58547
|
dock.visible = false;
|
|
58177
58548
|
}
|
|
58178
|
-
const
|
|
58549
|
+
const onKey2 = (key) => {
|
|
58179
58550
|
if (key.name === "escape") {
|
|
58180
58551
|
finish(request.cancelId);
|
|
58181
58552
|
key.preventDefault();
|
|
@@ -58222,7 +58593,7 @@ function showComposerChoice(otui, r, dock, request) {
|
|
|
58222
58593
|
key.stopPropagation();
|
|
58223
58594
|
}
|
|
58224
58595
|
};
|
|
58225
|
-
const unsub = onKeypress2(r,
|
|
58596
|
+
const unsub = onKeypress2(r, onKey2);
|
|
58226
58597
|
optionsScroll.focus();
|
|
58227
58598
|
});
|
|
58228
58599
|
}
|
|
@@ -61339,6 +61710,7 @@ function attachUsageIo(io, chrome) {
|
|
|
61339
61710
|
totalOut += usage.outputTokens ?? 0;
|
|
61340
61711
|
chrome.setHeaderMeta(`\u2191${fmtTokens(totalIn)} \u2193${fmtTokens(totalOut)}`);
|
|
61341
61712
|
chrome.setContextTotal(totalIn + totalOut);
|
|
61713
|
+
chrome.setUsage?.(totalIn, totalOut);
|
|
61342
61714
|
};
|
|
61343
61715
|
return Object.assign(io, {
|
|
61344
61716
|
resetUsage() {
|
|
@@ -61835,7 +62207,7 @@ function promptApiKeyStep(otui, r, opts) {
|
|
|
61835
62207
|
const keyInput = new otui.InputRenderable(r, { id: "kp-input", placeholder: opts.placeholder ?? "sk-...", marginTop: 1 });
|
|
61836
62208
|
box.add(keyInput);
|
|
61837
62209
|
keyInput.focus();
|
|
61838
|
-
const
|
|
62210
|
+
const onKey2 = (key) => {
|
|
61839
62211
|
if (key.name === "escape") {
|
|
61840
62212
|
cleanup();
|
|
61841
62213
|
resolve3({ kind: "back" });
|
|
@@ -61843,7 +62215,7 @@ function promptApiKeyStep(otui, r, opts) {
|
|
|
61843
62215
|
key.stopPropagation();
|
|
61844
62216
|
}
|
|
61845
62217
|
};
|
|
61846
|
-
const unsub = onKeypress4(r,
|
|
62218
|
+
const unsub = onKeypress4(r, onKey2);
|
|
61847
62219
|
const cleanup = () => {
|
|
61848
62220
|
unsub();
|
|
61849
62221
|
r.root.remove(box);
|
|
@@ -61875,7 +62247,7 @@ function pickProviderStep(otui, r, detected) {
|
|
|
61875
62247
|
});
|
|
61876
62248
|
box.add(provSelect);
|
|
61877
62249
|
provSelect.focus();
|
|
61878
|
-
const
|
|
62250
|
+
const onKey2 = (key) => {
|
|
61879
62251
|
if (key.name === "escape") {
|
|
61880
62252
|
cleanup();
|
|
61881
62253
|
resolve3(undefined);
|
|
@@ -61883,7 +62255,7 @@ function pickProviderStep(otui, r, detected) {
|
|
|
61883
62255
|
key.stopPropagation();
|
|
61884
62256
|
}
|
|
61885
62257
|
};
|
|
61886
|
-
const unsub = onKeypress4(r,
|
|
62258
|
+
const unsub = onKeypress4(r, onKey2);
|
|
61887
62259
|
const cleanup = () => {
|
|
61888
62260
|
unsub();
|
|
61889
62261
|
r.root.remove(box);
|
|
@@ -61985,7 +62357,7 @@ function pickModelInTui(otui, r, models) {
|
|
|
61985
62357
|
sel.options = matches2.length > 0 ? matches2.map((m) => ({ name: m, description: "" })) : [{ name: NO_MATCH, description: "" }];
|
|
61986
62358
|
filterLine.content = otui.t`${otui.dim(q.length > 0 ? `filter: ${filter} (${matches2.length}/${all.length})` : "type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to go back")}`;
|
|
61987
62359
|
};
|
|
61988
|
-
const
|
|
62360
|
+
const onKey2 = (key) => {
|
|
61989
62361
|
if (key.name === "escape") {
|
|
61990
62362
|
cleanup();
|
|
61991
62363
|
resolve3(undefined);
|
|
@@ -62008,7 +62380,7 @@ function pickModelInTui(otui, r, models) {
|
|
|
62008
62380
|
key.stopPropagation();
|
|
62009
62381
|
}
|
|
62010
62382
|
};
|
|
62011
|
-
const unsub = onKeypress4(r,
|
|
62383
|
+
const unsub = onKeypress4(r, onKey2);
|
|
62012
62384
|
const cleanup = () => {
|
|
62013
62385
|
unsub();
|
|
62014
62386
|
r.root.remove(box);
|
|
@@ -62077,7 +62449,7 @@ function pickSessionInTui(otui, r, sessions) {
|
|
|
62077
62449
|
sel.selectedIndex = 0;
|
|
62078
62450
|
};
|
|
62079
62451
|
apply();
|
|
62080
|
-
const
|
|
62452
|
+
const onKey2 = (key) => {
|
|
62081
62453
|
if (key.name === "escape") {
|
|
62082
62454
|
cleanup();
|
|
62083
62455
|
resolve3(undefined);
|
|
@@ -62100,7 +62472,7 @@ function pickSessionInTui(otui, r, sessions) {
|
|
|
62100
62472
|
key.stopPropagation();
|
|
62101
62473
|
}
|
|
62102
62474
|
};
|
|
62103
|
-
const unsub = onKeypress4(r,
|
|
62475
|
+
const unsub = onKeypress4(r, onKey2);
|
|
62104
62476
|
const cleanup = () => {
|
|
62105
62477
|
unsub();
|
|
62106
62478
|
r.root.remove(box);
|
|
@@ -62220,6 +62592,12 @@ async function launchTuiAgentShell(opts) {
|
|
|
62220
62592
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-model-k", content: otui.t`${otui.dim("Model")}`, marginTop: 1 }));
|
|
62221
62593
|
const sbModelV = new otui.TextRenderable(r, { id: "sb-model-v", content: otui.t`${otui.dim(`${sel.provider}/${sel.model}`)}` });
|
|
62222
62594
|
sidebar.add(sbModelV);
|
|
62595
|
+
sidebar.add(new otui.TextRenderable(r, { id: "sb-usage-k", content: otui.t`${otui.dim("Usage")}`, marginTop: 1 }));
|
|
62596
|
+
const sbUsageV = new otui.TextRenderable(r, { id: "sb-usage-v", content: otui.t`${otui.dim("\u21910 \u21930")}` });
|
|
62597
|
+
sidebar.add(sbUsageV);
|
|
62598
|
+
const balancePanel = mountBalancePanel(sidebar, otui, r, {
|
|
62599
|
+
provider: sel.provider
|
|
62600
|
+
});
|
|
62223
62601
|
mountCwdPanel(otui, r, sidebar, opts.session?.cwd ?? process.cwd());
|
|
62224
62602
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-workspace-k", content: otui.t`${otui.dim("Workspace")}`, marginTop: 1 }));
|
|
62225
62603
|
const sbWorkspaceV = new otui.TextRenderable(r, {
|
|
@@ -62461,6 +62839,9 @@ async function launchTuiAgentShell(opts) {
|
|
|
62461
62839
|
},
|
|
62462
62840
|
onExactUsage: () => {
|
|
62463
62841
|
hasExactUsage = true;
|
|
62842
|
+
},
|
|
62843
|
+
setUsage: (input3, output2) => {
|
|
62844
|
+
sbUsageV.content = otui.t`${otui.dim(`\u2191${fmtTokens(input3)} \u2193${fmtTokens(output2)}`)}`;
|
|
62464
62845
|
}
|
|
62465
62846
|
});
|
|
62466
62847
|
let lastUsage;
|
|
@@ -63015,10 +63396,12 @@ Staying in the current session.
|
|
|
63015
63396
|
});
|
|
63016
63397
|
})();
|
|
63017
63398
|
};
|
|
63018
|
-
const showGame = () => {
|
|
63019
|
-
|
|
63399
|
+
const showGame = (line) => {
|
|
63400
|
+
const timeoutMatch = /\/game\s+(\d+)/.exec(line);
|
|
63401
|
+
openGamesModal(otui, chrome, {
|
|
63020
63402
|
renderer: r,
|
|
63021
|
-
...inspectorKeys
|
|
63403
|
+
...inspectorKeys,
|
|
63404
|
+
...timeoutMatch !== null ? { timeoutMs: Math.max(1, Number(timeoutMatch[1])) * 1000 } : {}
|
|
63022
63405
|
});
|
|
63023
63406
|
};
|
|
63024
63407
|
const showWorkspace = () => {
|
|
@@ -63683,7 +64066,7 @@ Staying in the current session.
|
|
|
63683
64066
|
return;
|
|
63684
64067
|
}
|
|
63685
64068
|
case "game": {
|
|
63686
|
-
showGame();
|
|
64069
|
+
showGame(line);
|
|
63687
64070
|
return;
|
|
63688
64071
|
}
|
|
63689
64072
|
case "deferred": {
|
|
@@ -63946,7 +64329,7 @@ ${formatThemeList(getThemeId())}`);
|
|
|
63946
64329
|
return;
|
|
63947
64330
|
}
|
|
63948
64331
|
if (command.name === "/game") {
|
|
63949
|
-
showGame();
|
|
64332
|
+
showGame(line);
|
|
63950
64333
|
return;
|
|
63951
64334
|
}
|
|
63952
64335
|
if (command.name === "/mode") {
|
|
@@ -64376,6 +64759,9 @@ async function mountChatShell(otui, renderer, opts) {
|
|
|
64376
64759
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-model-k", content: otui.t`${otui.dim("Model")}`, marginTop: 1 }));
|
|
64377
64760
|
const sbModel = new otui.TextRenderable(r, { id: "sb-model-v", content: otui.t`${otui.dim(label())}` });
|
|
64378
64761
|
sidebar.add(sbModel);
|
|
64762
|
+
mountBalancePanel(sidebar, otui, r, {
|
|
64763
|
+
provider: selection.provider
|
|
64764
|
+
});
|
|
64379
64765
|
sidebar.add(new otui.TextRenderable(r, { id: "sb-ctx-k", content: otui.t`${otui.dim("Context")}`, marginTop: 1 }));
|
|
64380
64766
|
const sbContext = new otui.TextRenderable(r, { id: "sb-ctx-v", content: otui.t`${otui.dim("~0 tokens (est)")}` });
|
|
64381
64767
|
sidebar.add(sbContext);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.62",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|