@mrciphersmith/keryx 0.2.58 → 0.2.60

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/cli.js +169 -52
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -53454,7 +53454,7 @@ import { spawnSync as spawnSync2 } from "child_process";
53454
53454
  // package.json
53455
53455
  var package_default = {
53456
53456
  name: "@mrciphersmith/keryx",
53457
- version: "0.2.58",
53457
+ version: "0.2.60",
53458
53458
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
53459
53459
  private: false,
53460
53460
  publishConfig: {
@@ -57441,6 +57441,31 @@ function checkWinner(board) {
57441
57441
  }
57442
57442
  return null;
57443
57443
  }
57444
+ function freeCells(board) {
57445
+ return board.flatMap((cell, index) => cell === null ? [index] : []);
57446
+ }
57447
+ function bestLocalMove(board, mark) {
57448
+ const free = freeCells(board);
57449
+ if (free.length === 0) {
57450
+ return;
57451
+ }
57452
+ const opponent = mark === "X" ? "O" : "X";
57453
+ for (const candidate of [mark, opponent]) {
57454
+ for (const index of free) {
57455
+ const next = board.slice();
57456
+ next[index] = candidate;
57457
+ if (checkWinner(next)?.winner === candidate) {
57458
+ return index;
57459
+ }
57460
+ }
57461
+ }
57462
+ for (const index of [4, 0, 2, 6, 8]) {
57463
+ if (board[index] === null) {
57464
+ return index;
57465
+ }
57466
+ }
57467
+ return free[0];
57468
+ }
57444
57469
  function placeMark(board, index, mark) {
57445
57470
  if (index < 0 || index > 8 || board[index] !== null) {
57446
57471
  return;
@@ -57482,7 +57507,8 @@ function gameSystemPrompt() {
57482
57507
  "3 4 5",
57483
57508
  "6 7 8",
57484
57509
  "Reply with ONLY the index of the cell you choose, as a single digit 0-8.",
57485
- "Choose an empty cell. Prefer winning, then blocking, then center/corner."
57510
+ "Choose an empty cell. Prefer winning, then blocking, then center/corner.",
57511
+ "Answer immediately. No explanation, no working out \u2014 one character."
57486
57512
  ].join(`
57487
57513
  `);
57488
57514
  }
@@ -57500,7 +57526,7 @@ async function modelMove(board, opts = {}) {
57500
57526
  user: gameUserPrompt(board),
57501
57527
  ...opts.provider !== undefined ? { provider: opts.provider } : {},
57502
57528
  ...opts.model !== undefined ? { model: opts.model } : {},
57503
- maxOutputTokens: 16,
57529
+ maxOutputTokens: 256,
57504
57530
  requestId: "keryx-game",
57505
57531
  ...opts.providerFactory !== undefined ? { providerFactory: opts.providerFactory } : {},
57506
57532
  ...opts.env !== undefined ? { env: opts.env } : {}
@@ -57519,6 +57545,20 @@ var GAME_FOOTER = [
57519
57545
  { key: "r", label: "new game" },
57520
57546
  { key: "esc", label: "minimize" }
57521
57547
  ];
57548
+ var GAME_CELL_GAP = 1;
57549
+ var GAME_BOARD_CHROME_X = 4;
57550
+ var GAME_CELL_SIZES = {
57551
+ large: { width: 9, height: 5 },
57552
+ small: { width: 5, height: 3 }
57553
+ };
57554
+ function gameBoardWidth(cellWidth) {
57555
+ return cellWidth * 3 + GAME_CELL_GAP * 2 + GAME_BOARD_CHROME_X;
57556
+ }
57557
+ function resolveCellSize(availableWidth) {
57558
+ const large = GAME_CELL_SIZES.large;
57559
+ return gameBoardWidth(large.width) <= availableWidth ? large : GAME_CELL_SIZES.small;
57560
+ }
57561
+ var GAME_MODEL_TIMEOUT_MS = 12000;
57522
57562
  function asOtui2(otui) {
57523
57563
  if (otui === undefined || otui === null) {
57524
57564
  return;
@@ -57545,37 +57585,40 @@ function statusText(state) {
57545
57585
  if (state.draw) {
57546
57586
  return "Draw! (r \u2014 new game)";
57547
57587
  }
57548
- return `Your turn \u2014 ${state.turn}`;
57588
+ return state.turn === "X" ? "Your turn \u2014 X" : "Agent's turn \u2014 O";
57549
57589
  }
57550
57590
  function presentGame(openModalFn, otui, chrome, options = {}) {
57551
57591
  const renderer = options.renderer;
57552
57592
  const core = asOtui2(otui);
57553
57593
  let handle;
57554
- let boardBox;
57594
+ let cells = [];
57555
57595
  let statusBox;
57556
- let hintBox;
57596
+ let noticeBox;
57557
57597
  let cursor = 4;
57598
+ let notice;
57558
57599
  let unsubscribeKey;
57559
57600
  const paint = () => {
57560
- if (core === undefined || boardBox === undefined || statusBox === undefined || hintBox === undefined) {
57601
+ if (statusBox === undefined || noticeBox === undefined || cells.length !== 9) {
57561
57602
  return;
57562
57603
  }
57563
57604
  const theme = getTheme();
57564
- clearTranscriptChildren(boardBox);
57565
57605
  statusBox.content = statusText(currentGame);
57566
- hintBox.content = modelBusy ? "agent is thinking\u2026" : "\u2190\u2191\u2193\u2192 move \xB7 enter place \xB7 r new game \xB7 esc minimize";
57567
- for (let i = 0;i < 9; i++) {
57568
- const cell = currentGame.board[i] ?? null;
57569
- const isCursor = i === cursor && !isGameOver(currentGame) && !modelBusy;
57570
- const win = currentGame.winLine?.includes(i) === true;
57571
- const content = cell === null ? isCursor ? "\xB7" : "." : cell;
57572
- const fg = cell === null ? isCursor ? theme.focus : theme.muted : markColor(cell);
57573
- const styled2 = win && core.bold !== undefined ? core.bold(content) : content;
57574
- boardBox.add(new core.TextRenderable(renderer, {
57575
- id: `game-cell-${i}`,
57576
- content: styled2,
57577
- fg
57578
- }));
57606
+ statusBox.fg = theme.text;
57607
+ if (modelBusy) {
57608
+ noticeBox.content = "agent is thinking\u2026";
57609
+ noticeBox.fg = theme.focus;
57610
+ } else {
57611
+ noticeBox.content = notice ?? "";
57612
+ noticeBox.fg = theme.error;
57613
+ }
57614
+ for (const [index, view] of cells.entries()) {
57615
+ const cell = currentGame.board[index] ?? null;
57616
+ const isCursor = index === cursor && !isGameOver(currentGame) && !modelBusy;
57617
+ const won = currentGame.winLine?.includes(index) === true;
57618
+ view.text.content = cell ?? "\xB7";
57619
+ view.text.fg = cell === null ? theme.muted : markColor(cell);
57620
+ view.box.borderColor = won ? markColor(currentGame.winner ?? "X") : isCursor ? theme.focus : theme.border;
57621
+ view.box.backgroundColor = isCursor || won ? theme.highlight : undefined;
57579
57622
  }
57580
57623
  };
57581
57624
  const applyModelMove = async () => {
@@ -57583,26 +57626,44 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
57583
57626
  return;
57584
57627
  }
57585
57628
  modelBusy = true;
57629
+ notice = undefined;
57586
57630
  paint();
57587
- const result = await modelMove(currentGame.board, {
57588
- ...options.provider !== undefined ? { provider: options.provider } : {},
57589
- ...options.model !== undefined ? { model: options.model } : {},
57590
- ...options.providerFactory !== undefined ? { providerFactory: options.providerFactory } : {},
57591
- ...options.env !== undefined ? { env: options.env } : {}
57631
+ const timeoutMs = options.timeoutMs ?? GAME_MODEL_TIMEOUT_MS;
57632
+ let timer;
57633
+ const deadline = new Promise((resolve3) => {
57634
+ timer = setTimeout(() => resolve3("timeout"), timeoutMs);
57592
57635
  });
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
+ ]);
57645
+ if (timer !== undefined) {
57646
+ clearTimeout(timer);
57647
+ }
57593
57648
  modelBusy = false;
57594
- if (result.move !== undefined) {
57595
- const placed = placeMark(currentGame.board, result.move, "O");
57596
- if (placed !== undefined) {
57597
- currentGame = placed;
57598
- } else {
57599
- currentGame = { ...currentGame, turn: "X" };
57600
- }
57649
+ const timedOut = outcome === "timeout";
57650
+ const result = timedOut ? { move: undefined, error: undefined } : outcome;
57651
+ let move = result.move;
57652
+ let playedLocally = false;
57653
+ if (move === undefined && result.error === undefined) {
57654
+ move = bestLocalMove(currentGame.board, "O");
57655
+ playedLocally = move !== undefined;
57656
+ }
57657
+ const placed = move === undefined ? undefined : placeMark(currentGame.board, move, "O");
57658
+ currentGame = placed ?? { ...currentGame, turn: "X" };
57659
+ if (result.error !== undefined) {
57660
+ notice = `agent: ${result.error}`;
57661
+ } else if (timedOut && playedLocally) {
57662
+ notice = `agent timed out after ${Math.round(timeoutMs / 1000)}s \u2014 played a local move`;
57663
+ } else if (playedLocally) {
57664
+ notice = "agent gave no usable answer \u2014 played a local move";
57601
57665
  } else {
57602
- currentGame = { ...currentGame, turn: "X" };
57603
- if (result.error !== undefined) {
57604
- statusBox !== undefined && (statusBox.content = `agent: ${result.error}`);
57605
- }
57666
+ notice = undefined;
57606
57667
  }
57607
57668
  paint();
57608
57669
  };
@@ -57615,6 +57676,7 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
57615
57676
  return;
57616
57677
  }
57617
57678
  currentGame = placed;
57679
+ notice = undefined;
57618
57680
  paint();
57619
57681
  if (!isGameOver(currentGame) && currentGame.turn === "O") {
57620
57682
  applyModelMove();
@@ -57632,13 +57694,18 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
57632
57694
  const restart = () => {
57633
57695
  resetGame();
57634
57696
  cursor = 4;
57697
+ notice = undefined;
57635
57698
  paint();
57636
57699
  };
57637
57700
  handle = openModalFn(otui, chrome, {
57638
57701
  title: "/game",
57639
57702
  tabs: [{ id: "game", label: "Tic-tac-toe" }],
57640
57703
  footer: GAME_FOOTER,
57641
- renderTab: (_tabId, body) => {
57704
+ onArrowKeys: (_key, direction) => {
57705
+ moveCursor(0, direction === "left" ? -1 : 1);
57706
+ return true;
57707
+ },
57708
+ renderTab: (_tabId, body, ctx) => {
57642
57709
  if (body === undefined || body === null) {
57643
57710
  return;
57644
57711
  }
@@ -57647,10 +57714,27 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
57647
57714
  return;
57648
57715
  }
57649
57716
  const theme = getTheme();
57650
- const board = new core.BoxRenderable(renderer, {
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, {
57651
57735
  id: "game-board",
57652
- width: 15,
57653
57736
  flexDirection: "column",
57737
+ flexShrink: 0,
57654
57738
  border: true,
57655
57739
  borderStyle: "rounded",
57656
57740
  borderColor: theme.border,
@@ -57658,22 +57742,55 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
57658
57742
  paddingLeft: 1,
57659
57743
  paddingRight: 1
57660
57744
  });
57661
- boardBox = board;
57662
- parent.add(board);
57663
- const status = new core.TextRenderable(renderer, {
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, {
57664
57781
  id: "game-status",
57665
57782
  content: "",
57666
57783
  marginTop: 1
57667
57784
  });
57668
57785
  statusBox = status;
57669
- parent.add(status);
57670
- const hint = new core.TextRenderable(renderer, {
57671
- id: "game-hint",
57672
- content: "",
57673
- marginTop: 1
57786
+ wrap2.add(status);
57787
+ const noticeText = new core.TextRenderable(r, {
57788
+ id: "game-notice",
57789
+ content: ""
57674
57790
  });
57675
- hintBox = hint;
57676
- parent.add(hint);
57791
+ noticeBox = noticeText;
57792
+ wrap2.add(noticeText);
57793
+ parent.add(wrap2);
57677
57794
  paint();
57678
57795
  },
57679
57796
  onClose: () => {
@@ -57694,11 +57811,11 @@ function presentGame(openModalFn, otui, chrome, options = {}) {
57694
57811
  moveCursor(1, 0);
57695
57812
  return;
57696
57813
  }
57697
- if (token === "left" || token === "h") {
57814
+ if (token === "h") {
57698
57815
  moveCursor(0, -1);
57699
57816
  return;
57700
57817
  }
57701
- if (token === "right" || token === "l") {
57818
+ if (token === "l") {
57702
57819
  moveCursor(0, 1);
57703
57820
  return;
57704
57821
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.58",
3
+ "version": "0.2.60",
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": {