@mrciphersmith/keryx 0.2.65 → 0.2.67

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 +605 -34
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -28105,6 +28105,332 @@ function looksLikeUnifiedDiff(text) {
28105
28105
  }
28106
28106
  return false;
28107
28107
  }
28108
+ var CODE_LANG_ALIASES = {
28109
+ js: "javascript",
28110
+ mjs: "javascript",
28111
+ cjs: "javascript",
28112
+ jsx: "javascript",
28113
+ ts: "typescript",
28114
+ tsx: "typescript",
28115
+ py: "python",
28116
+ py3: "python",
28117
+ sh: "bash",
28118
+ shell: "bash",
28119
+ zsh: "bash",
28120
+ console: "bash",
28121
+ yml: "yaml",
28122
+ rb: "ruby",
28123
+ rs: "rust",
28124
+ kt: "kotlin",
28125
+ cs: "csharp",
28126
+ "c++": "cpp",
28127
+ cc: "cpp",
28128
+ cxx: "cpp",
28129
+ hpp: "cpp"
28130
+ };
28131
+ var GENERIC_KEYWORDS = new Set([
28132
+ "if",
28133
+ "else",
28134
+ "for",
28135
+ "while",
28136
+ "return",
28137
+ "function",
28138
+ "class",
28139
+ "const",
28140
+ "let",
28141
+ "var",
28142
+ "import",
28143
+ "export",
28144
+ "from",
28145
+ "true",
28146
+ "false",
28147
+ "null",
28148
+ "new",
28149
+ "this",
28150
+ "try",
28151
+ "catch",
28152
+ "throw",
28153
+ "switch",
28154
+ "case",
28155
+ "break",
28156
+ "continue",
28157
+ "default",
28158
+ "static",
28159
+ "public",
28160
+ "private"
28161
+ ]);
28162
+ var CODE_LANG_KEYWORDS = {
28163
+ javascript: new Set([
28164
+ ...GENERIC_KEYWORDS,
28165
+ "async",
28166
+ "await",
28167
+ "yield",
28168
+ "typeof",
28169
+ "instanceof",
28170
+ "in",
28171
+ "of",
28172
+ "void",
28173
+ "undefined",
28174
+ "super",
28175
+ "extends",
28176
+ "finally",
28177
+ "do",
28178
+ "delete"
28179
+ ]),
28180
+ typescript: new Set([
28181
+ ...GENERIC_KEYWORDS,
28182
+ "async",
28183
+ "await",
28184
+ "yield",
28185
+ "typeof",
28186
+ "instanceof",
28187
+ "in",
28188
+ "of",
28189
+ "void",
28190
+ "undefined",
28191
+ "super",
28192
+ "extends",
28193
+ "implements",
28194
+ "interface",
28195
+ "type",
28196
+ "enum",
28197
+ "namespace",
28198
+ "declare",
28199
+ "readonly",
28200
+ "abstract",
28201
+ "as",
28202
+ "satisfies",
28203
+ "finally",
28204
+ "do",
28205
+ "delete"
28206
+ ]),
28207
+ python: new Set([
28208
+ "def",
28209
+ "return",
28210
+ "if",
28211
+ "elif",
28212
+ "else",
28213
+ "for",
28214
+ "while",
28215
+ "break",
28216
+ "continue",
28217
+ "class",
28218
+ "import",
28219
+ "from",
28220
+ "as",
28221
+ "try",
28222
+ "except",
28223
+ "finally",
28224
+ "raise",
28225
+ "with",
28226
+ "lambda",
28227
+ "yield",
28228
+ "pass",
28229
+ "None",
28230
+ "True",
28231
+ "False",
28232
+ "and",
28233
+ "or",
28234
+ "not",
28235
+ "in",
28236
+ "is",
28237
+ "global",
28238
+ "nonlocal",
28239
+ "assert",
28240
+ "async",
28241
+ "await",
28242
+ "del",
28243
+ "self"
28244
+ ]),
28245
+ bash: new Set([
28246
+ "if",
28247
+ "then",
28248
+ "else",
28249
+ "elif",
28250
+ "fi",
28251
+ "for",
28252
+ "while",
28253
+ "do",
28254
+ "done",
28255
+ "case",
28256
+ "esac",
28257
+ "function",
28258
+ "return",
28259
+ "local",
28260
+ "export",
28261
+ "exit",
28262
+ "break",
28263
+ "continue",
28264
+ "in",
28265
+ "select",
28266
+ "until"
28267
+ ]),
28268
+ go: new Set([
28269
+ "func",
28270
+ "return",
28271
+ "if",
28272
+ "else",
28273
+ "for",
28274
+ "range",
28275
+ "switch",
28276
+ "case",
28277
+ "default",
28278
+ "break",
28279
+ "continue",
28280
+ "package",
28281
+ "import",
28282
+ "var",
28283
+ "const",
28284
+ "type",
28285
+ "struct",
28286
+ "interface",
28287
+ "map",
28288
+ "chan",
28289
+ "go",
28290
+ "defer",
28291
+ "select",
28292
+ "fallthrough",
28293
+ "nil",
28294
+ "true",
28295
+ "false"
28296
+ ]),
28297
+ rust: new Set([
28298
+ "fn",
28299
+ "let",
28300
+ "mut",
28301
+ "return",
28302
+ "if",
28303
+ "else",
28304
+ "for",
28305
+ "while",
28306
+ "loop",
28307
+ "match",
28308
+ "struct",
28309
+ "enum",
28310
+ "impl",
28311
+ "trait",
28312
+ "pub",
28313
+ "use",
28314
+ "mod",
28315
+ "crate",
28316
+ "self",
28317
+ "Self",
28318
+ "super",
28319
+ "const",
28320
+ "static",
28321
+ "async",
28322
+ "await",
28323
+ "move",
28324
+ "ref",
28325
+ "dyn",
28326
+ "where",
28327
+ "unsafe",
28328
+ "true",
28329
+ "false",
28330
+ "None",
28331
+ "Some",
28332
+ "Ok",
28333
+ "Err"
28334
+ ]),
28335
+ json: new Set(["true", "false", "null"])
28336
+ };
28337
+ function codeCommentPrefix(normalizedLang) {
28338
+ switch (normalizedLang) {
28339
+ case "javascript":
28340
+ case "typescript":
28341
+ case "go":
28342
+ case "rust":
28343
+ case "java":
28344
+ case "c":
28345
+ case "cpp":
28346
+ case "csharp":
28347
+ case "swift":
28348
+ case "kotlin":
28349
+ case "scala":
28350
+ case "php":
28351
+ return "//";
28352
+ case "python":
28353
+ case "bash":
28354
+ case "ruby":
28355
+ case "yaml":
28356
+ case "toml":
28357
+ case "r":
28358
+ case "perl":
28359
+ return "#";
28360
+ case "sql":
28361
+ case "lua":
28362
+ case "haskell":
28363
+ return "--";
28364
+ default:
28365
+ return "";
28366
+ }
28367
+ }
28368
+ function normalizeCodeLang(lang) {
28369
+ const key = lang.trim().toLowerCase();
28370
+ return CODE_LANG_ALIASES[key] ?? key;
28371
+ }
28372
+ var CODE_WORD_OR_NUMBER = /[A-Za-z_$][A-Za-z0-9_$]*|\d+(?:\.\d+)?/g;
28373
+ function tokenizeCodeWords(text, keywords) {
28374
+ const tokens = [];
28375
+ let last = 0;
28376
+ CODE_WORD_OR_NUMBER.lastIndex = 0;
28377
+ let m;
28378
+ while ((m = CODE_WORD_OR_NUMBER.exec(text)) !== null) {
28379
+ if (m.index > last) {
28380
+ tokens.push({ kind: "plain", text: text.slice(last, m.index) });
28381
+ }
28382
+ const word = m[0];
28383
+ if (/^[0-9]/.test(word)) {
28384
+ tokens.push({ kind: "number", text: word });
28385
+ } else if (keywords.has(word)) {
28386
+ tokens.push({ kind: "keyword", text: word });
28387
+ } else {
28388
+ tokens.push({ kind: "plain", text: word });
28389
+ }
28390
+ last = m.index + word.length;
28391
+ }
28392
+ if (last < text.length) {
28393
+ tokens.push({ kind: "plain", text: text.slice(last) });
28394
+ }
28395
+ return tokens;
28396
+ }
28397
+ function tokenizeCodeLine(line, lang) {
28398
+ const normalized = normalizeCodeLang(lang);
28399
+ const keywords = CODE_LANG_KEYWORDS[normalized] ?? GENERIC_KEYWORDS;
28400
+ const commentPrefix = codeCommentPrefix(normalized);
28401
+ const tokens = [];
28402
+ let plainBuf = "";
28403
+ const flushPlain = () => {
28404
+ if (plainBuf.length > 0) {
28405
+ tokens.push(...tokenizeCodeWords(plainBuf, keywords));
28406
+ plainBuf = "";
28407
+ }
28408
+ };
28409
+ let i = 0;
28410
+ while (i < line.length) {
28411
+ if (commentPrefix.length > 0 && line.startsWith(commentPrefix, i)) {
28412
+ flushPlain();
28413
+ tokens.push({ kind: "comment", text: line.slice(i) });
28414
+ break;
28415
+ }
28416
+ const ch = line[i];
28417
+ if (ch === '"' || ch === "'" || ch === "`") {
28418
+ flushPlain();
28419
+ let j = i + 1;
28420
+ while (j < line.length && line[j] !== ch) {
28421
+ j += line[j] === "\\" ? 2 : 1;
28422
+ }
28423
+ j = Math.min(j + 1, line.length);
28424
+ tokens.push({ kind: "string", text: line.slice(i, j) });
28425
+ i = j;
28426
+ continue;
28427
+ }
28428
+ plainBuf += ch;
28429
+ i += 1;
28430
+ }
28431
+ flushPlain();
28432
+ return tokens;
28433
+ }
28108
28434
  function payloadKind(lang, _lineCount) {
28109
28435
  const normalized = lang.toLowerCase();
28110
28436
  if (MARKDOWN_LANGS.has(normalized)) {
@@ -28121,6 +28447,18 @@ function blockLabel({ kind, lineCount, collapsed, hint }) {
28121
28447
  const suffix = hint !== undefined && hint.length > 0 ? ` \xB7 ${hint}` : "";
28122
28448
  return `${marker} ${kind} (${lineCount} ${unit})${suffix}`;
28123
28449
  }
28450
+ function summarizeSubmittedLine(line) {
28451
+ const normalized = line.replace(/\r\n/g, `
28452
+ `).replace(/\r/g, `
28453
+ `);
28454
+ const nonEmpty = normalized.split(`
28455
+ `).filter((linePart) => linePart.length > 0);
28456
+ if (nonEmpty.length <= 1) {
28457
+ return line;
28458
+ }
28459
+ const [first, ...rest] = nonEmpty;
28460
+ return `${first} [+ ${rest.length} pasted line${rest.length === 1 ? "" : "s"}]`;
28461
+ }
28124
28462
  var ZERO_WIDTH = /^[\u0300-\u036F\u200B-\u200F\uFE00-\uFE0F\u2060-\u2064]$/u;
28125
28463
  var WIDE_RANGES = [
28126
28464
  [4352, 4447],
@@ -53148,6 +53486,7 @@ ${lines.join(`
53148
53486
 
53149
53487
  // src/tui/tui-shell.ts
53150
53488
  init_agent();
53489
+ init_single_turn();
53151
53490
  init_slate_lifecycle();
53152
53491
  init_slate();
53153
53492
 
@@ -53556,7 +53895,7 @@ import { spawnSync as spawnSync2 } from "child_process";
53556
53895
  // package.json
53557
53896
  var package_default = {
53558
53897
  name: "@mrciphersmith/keryx",
53559
- version: "0.2.65",
53898
+ version: "0.2.67",
53560
53899
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
53561
53900
  private: false,
53562
53901
  publishConfig: {
@@ -53919,7 +54258,7 @@ function diffChunks(otui, text) {
53919
54258
  }
53920
54259
  return out;
53921
54260
  }
53922
- function codeChunks(otui, text) {
54261
+ function flatDimChunks(otui, text) {
53923
54262
  const out = [];
53924
54263
  for (const [index, line] of splitLines(text).entries()) {
53925
54264
  if (index > 0) {
@@ -53930,6 +54269,34 @@ function codeChunks(otui, text) {
53930
54269
  }
53931
54270
  return out;
53932
54271
  }
54272
+ function codeChunks(otui, text, lang) {
54273
+ const out = [];
54274
+ for (const [index, line] of splitLines(text).entries()) {
54275
+ if (index > 0) {
54276
+ out.push(...otui.stringToStyledText(`
54277
+ `).chunks);
54278
+ }
54279
+ for (const token of tokenizeCodeLine(line, lang)) {
54280
+ switch (token.kind) {
54281
+ case "comment":
54282
+ out.push(otui.dim(token.text));
54283
+ break;
54284
+ case "string":
54285
+ out.push(otui.green(token.text));
54286
+ break;
54287
+ case "number":
54288
+ out.push(otui.yellow(token.text));
54289
+ break;
54290
+ case "keyword":
54291
+ out.push(otui.cyan(token.text));
54292
+ break;
54293
+ default:
54294
+ out.push(...otui.stringToStyledText(token.text).chunks);
54295
+ }
54296
+ }
54297
+ }
54298
+ return out;
54299
+ }
53933
54300
  function payloadChunks(otui, text, lang = "") {
53934
54301
  const kind2 = payloadKind(lang, lineCountOf(text));
53935
54302
  if (kind2 === "diff" || looksLikeUnifiedDiff(text)) {
@@ -53938,7 +54305,7 @@ function payloadChunks(otui, text, lang = "") {
53938
54305
  if (kind2 === "markdown" || lang.length === 0) {
53939
54306
  return markdownToChunks(otui, text);
53940
54307
  }
53941
- return codeChunks(otui, text);
54308
+ return codeChunks(otui, text, lang);
53942
54309
  }
53943
54310
  function createSegmentView(otui, renderer, parent, segment) {
53944
54311
  viewSeq += 1;
@@ -53966,7 +54333,7 @@ function createSegmentView(otui, renderer, parent, segment) {
53966
54333
  }
53967
54334
  const tag = (lang, body2) => {
53968
54335
  const n = lineCountOf(body2);
53969
- return `${lang.length > 0 ? lang : "text"} \xB7 ${n} ${n === 1 ? "line" : "lines"}`;
54336
+ return `${lang.length > 0 ? lang : "text"} \xB7 ${n} ${n === 1 ? "line" : "lines"} \xB7 y copy`;
53970
54337
  };
53971
54338
  const frameWidth = (lang, body2) => Math.max(hugWidth(body2, FRAME_CHROME), hugWidth(tag(lang, body2), FRAME_CHROME));
53972
54339
  const frame = new otui.BoxRenderable(renderer, {
@@ -54012,6 +54379,7 @@ function createSegmentView(otui, renderer, parent, segment) {
54012
54379
  var messageSeq = 0;
54013
54380
  function createAssistantMessageStream(otui, renderer, parent) {
54014
54381
  let message2;
54382
+ let lastCode;
54015
54383
  const start = () => {
54016
54384
  messageSeq += 1;
54017
54385
  const container = new otui.BoxRenderable(renderer, {
@@ -54040,6 +54408,11 @@ function createAssistantMessageStream(otui, renderer, parent) {
54040
54408
  stale.destroy();
54041
54409
  }
54042
54410
  m.frozen = frozen;
54411
+ for (const segment of segments2) {
54412
+ if (segment.kind === "code") {
54413
+ lastCode = { lang: segment.lang, body: segment.body };
54414
+ }
54415
+ }
54043
54416
  };
54044
54417
  return {
54045
54418
  push: (chunk) => {
@@ -54077,7 +54450,8 @@ function createAssistantMessageStream(otui, renderer, parent) {
54077
54450
  parent.remove(message2.container);
54078
54451
  } catch {}
54079
54452
  message2 = undefined;
54080
- }
54453
+ },
54454
+ lastCodeSegment: () => lastCode
54081
54455
  };
54082
54456
  }
54083
54457
  function createBlockView(otui, renderer, parent, block, options = {}) {
@@ -54134,7 +54508,7 @@ function createBlockView(otui, renderer, parent, block, options = {}) {
54134
54508
  return;
54135
54509
  }
54136
54510
  const shown = clipBody(text, options.maxLines ?? MAX_BODY_LINES);
54137
- const content = new otui.StyledText(options.dim === true ? codeChunks(otui, shown) : payloadChunks(otui, shown));
54511
+ const content = new otui.StyledText(options.dim === true ? flatDimChunks(otui, shown) : payloadChunks(otui, shown));
54138
54512
  painted = text;
54139
54513
  if (bodyText !== undefined) {
54140
54514
  bodyText.content = content;
@@ -54987,10 +55361,13 @@ async function readExternalUnboundCandidates(cwd) {
54987
55361
  }
54988
55362
  return groups;
54989
55363
  }, []));
55364
+ const metaPath2 = path150.join(extDir, `${id}.json`);
55365
+ if (await pathExists(unboundDismissedReceiptPath(metaPath2)))
55366
+ continue;
54990
55367
  candidates.push({
54991
55368
  type: "unbound-candidate",
54992
55369
  externalSessionId: id,
54993
- evidencePath: path150.join(extDir, `${id}.json`),
55370
+ evidencePath: metaPath2,
54994
55371
  summary
54995
55372
  });
54996
55373
  }
@@ -55008,6 +55385,8 @@ async function readNewestUnboundCandidateForExternal(cwd, externalSessionId) {
55008
55385
  entries.sort();
55009
55386
  for (let i = entries.length - 1;i >= 0; i--) {
55010
55387
  const evidencePath = path150.join(evidenceDir, entries[i]);
55388
+ if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
55389
+ continue;
55011
55390
  const result = readConfigFile(evidencePath);
55012
55391
  if (!result.ok) {
55013
55392
  continue;
@@ -55216,6 +55595,55 @@ async function readTerminalState(dir) {
55216
55595
  return;
55217
55596
  }
55218
55597
  }
55598
+ function unboundDismissedReceiptPath(candidatePath) {
55599
+ if (/-unbound-candidate\.json$/.test(candidatePath)) {
55600
+ return candidatePath.replace(/-unbound-candidate\.json$/, "-unbound-dismissed.json");
55601
+ }
55602
+ return candidatePath.replace(/\.json$/, ".dismissed.json");
55603
+ }
55604
+ async function resolveUnboundCandidateTarget(cwd, target) {
55605
+ if (target.endsWith("-unbound-candidate.json") || target.endsWith(".json")) {
55606
+ return target;
55607
+ }
55608
+ const session = findSession(cwd, target);
55609
+ if (session === undefined) {
55610
+ throw new Error(`No session or evidence path matches "${target}"`);
55611
+ }
55612
+ const archiveDir = path150.join(sessionDir(session.projectPath, session.id), "slate-archive");
55613
+ let entries;
55614
+ try {
55615
+ entries = (await readdir26(archiveDir)).filter((name) => name.endsWith("-unbound-candidate.json"));
55616
+ } catch {
55617
+ throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
55618
+ }
55619
+ if (entries.length === 0)
55620
+ throw new Error(`No unbound-candidate artifacts for session ${session.id}`);
55621
+ entries.sort();
55622
+ return path150.join(archiveDir, entries[entries.length - 1]);
55623
+ }
55624
+ async function dismissUnboundByTarget(cwd, target, reason) {
55625
+ const evidencePath = await resolveUnboundCandidateTarget(cwd, target);
55626
+ return dismissUnboundCandidate(evidencePath, reason);
55627
+ }
55628
+ async function dismissUnboundCandidate(candidatePath, reason) {
55629
+ const { rm: rm10, writeFile: writeFile49 } = await import("fs/promises");
55630
+ const { dirname } = await import("path");
55631
+ await rm10(candidatePath, { force: true });
55632
+ const receiptPath = unboundDismissedReceiptPath(candidatePath);
55633
+ const receipt = {
55634
+ recordType: "unbound-dismissed",
55635
+ dismissedAt: new Date().toISOString(),
55636
+ candidatePath: candidatePath.split("/").pop(),
55637
+ ...reason !== undefined ? { reason } : {}
55638
+ };
55639
+ await writeFile49(receiptPath, `${JSON.stringify(receipt, null, 2)}
55640
+ `, "utf8");
55641
+ const parent = dirname(candidatePath);
55642
+ try {
55643
+ await rm10(parent, { force: true, recursive: false });
55644
+ } catch {}
55645
+ return { removed: candidatePath, receipt: receiptPath };
55646
+ }
55219
55647
  async function readNewestUnboundCandidate(dir) {
55220
55648
  const archiveDir = path150.join(dir, "slate-archive");
55221
55649
  let entries;
@@ -55227,6 +55655,8 @@ async function readNewestUnboundCandidate(dir) {
55227
55655
  entries.sort();
55228
55656
  for (let i = entries.length - 1;i >= 0; i--) {
55229
55657
  const evidencePath = path150.join(archiveDir, entries[i]);
55658
+ if (await pathExists(unboundDismissedReceiptPath(evidencePath)))
55659
+ continue;
55230
55660
  const result = readConfigFile(evidencePath);
55231
55661
  if (!result.ok) {
55232
55662
  continue;
@@ -58998,6 +59428,53 @@ function composerMaxRowsForViewport(viewportRows) {
58998
59428
  }
58999
59429
  return Math.max(COMPOSER_MIN_ROWS, Math.floor(viewportRows / 3));
59000
59430
  }
59431
+ function themeColorToHex(value) {
59432
+ if (typeof value === "string") {
59433
+ return /^#[0-9a-fA-F]{6}$/.test(value) ? value.toLowerCase() : undefined;
59434
+ }
59435
+ if (value !== null && typeof value === "object" && typeof value.toInts === "function") {
59436
+ const [r, g, b, a] = value.toInts();
59437
+ if (a !== 255) {
59438
+ return;
59439
+ }
59440
+ return `#${[r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("")}`;
59441
+ }
59442
+ return;
59443
+ }
59444
+ function themeColorRemap(from, to) {
59445
+ const remap = new Map;
59446
+ for (const slot of Object.keys(from)) {
59447
+ if (slot === "name") {
59448
+ continue;
59449
+ }
59450
+ const oldColor = from[slot];
59451
+ const newColor = to[slot];
59452
+ if (typeof oldColor === "string" && typeof newColor === "string" && oldColor !== newColor) {
59453
+ remap.set(oldColor, newColor);
59454
+ }
59455
+ }
59456
+ return remap;
59457
+ }
59458
+ function recolorThemeTree(node, remap) {
59459
+ if (node === null || node === undefined) {
59460
+ return;
59461
+ }
59462
+ const target = node;
59463
+ for (const prop of ["borderColor", "backgroundColor", "fg"]) {
59464
+ const hex = themeColorToHex(target[prop]);
59465
+ if (hex !== undefined) {
59466
+ const next = remap.get(hex);
59467
+ if (next !== undefined) {
59468
+ target[prop] = next;
59469
+ }
59470
+ }
59471
+ }
59472
+ if (typeof target.getChildren === "function") {
59473
+ for (const child of target.getChildren()) {
59474
+ recolorThemeTree(child, remap);
59475
+ }
59476
+ }
59477
+ }
59001
59478
  function wrappedLineCount(text, width) {
59002
59479
  const inner = Number.isFinite(width) ? Math.floor(width) : 0;
59003
59480
  const paragraphs = text.length === 0 ? [""] : text.split(`
@@ -59035,6 +59512,7 @@ async function createShellChrome(otui, renderer, opts) {
59035
59512
  const filter = opts.filterCommands ?? ((query) => prefixFilter(opts.commands, query));
59036
59513
  let uid = 0;
59037
59514
  let alive = true;
59515
+ let appliedTheme = getTheme();
59038
59516
  const rootRow = new otui.BoxRenderable(r, { id: "root-row", flexGrow: 1, flexDirection: "row" });
59039
59517
  r.root.add(rootRow);
59040
59518
  const main = new otui.BoxRenderable(r, { id: "main", flexGrow: 1, minWidth: 0, flexDirection: "column" });
@@ -59432,6 +59910,12 @@ async function createShellChrome(otui, renderer, opts) {
59432
59910
  input2.value = "";
59433
59911
  hideMenu();
59434
59912
  syncComposerHeight();
59913
+ if (line.length === 0 && suggestion !== null) {
59914
+ const next = suggestion;
59915
+ clearSuggestion();
59916
+ emitSubmit(next);
59917
+ return;
59918
+ }
59435
59919
  emitSubmit(line);
59436
59920
  };
59437
59921
  const unsubscribeMenuKeys = onKeypress3(r, (key) => {
@@ -59470,7 +59954,51 @@ async function createShellChrome(otui, renderer, opts) {
59470
59954
  key.stopPropagation();
59471
59955
  }
59472
59956
  });
59957
+ const defaultPlaceholder = opts.placeholder;
59958
+ let suggestion = null;
59959
+ const syncPlaceholder = () => {
59960
+ textarea.placeholder = suggestion !== null && input2.value.length === 0 ? suggestion : defaultPlaceholder;
59961
+ };
59962
+ const showSuggestion = (text) => {
59963
+ if (text.length === 0)
59964
+ return;
59965
+ suggestion = text;
59966
+ syncPlaceholder();
59967
+ };
59968
+ const clearSuggestion = () => {
59969
+ if (suggestion === null)
59970
+ return;
59971
+ suggestion = null;
59972
+ syncPlaceholder();
59973
+ };
59974
+ const prevContentChange = textarea.onContentChange;
59975
+ textarea.onContentChange = (event) => {
59976
+ if (suggestion !== null)
59977
+ syncPlaceholder();
59978
+ prevContentChange?.(event);
59979
+ };
59980
+ const unsubscribeSuggestionKeys = onKeypress3(r, (key) => {
59981
+ if (suggestion === null || overlayActive())
59982
+ return;
59983
+ if (menu.visible && menuNav)
59984
+ return;
59985
+ if (key.name === "tab" || key.name === "right") {
59986
+ if (input2.value.length === 0) {
59987
+ input2.value = suggestion;
59988
+ clearSuggestion();
59989
+ key.preventDefault();
59990
+ key.stopPropagation();
59991
+ return;
59992
+ }
59993
+ return;
59994
+ }
59995
+ const ch = key.sequence;
59996
+ if (!key.ctrl && !key.meta && typeof ch === "string" && ch.length === 1 && ch >= " ") {
59997
+ clearSuggestion();
59998
+ }
59999
+ });
59473
60000
  const applyTheme = (theme = getTheme()) => {
60001
+ const remap = themeColorRemap(appliedTheme, theme);
59474
60002
  try {
59475
60003
  r.setBackgroundColor(theme.bg);
59476
60004
  } catch {}
@@ -59488,6 +60016,15 @@ async function createShellChrome(otui, renderer, opts) {
59488
60016
  menu.selectedTextColor = theme.focus;
59489
60017
  menu.descriptionColor = theme.muted;
59490
60018
  menu.selectedDescriptionColor = theme.muted;
60019
+ recolorThemeTree(transcript, remap);
60020
+ recolorThemeTree(dock, remap);
60021
+ recolorThemeTree(queueDock, remap);
60022
+ recolorThemeTree(sidebarTop, remap);
60023
+ recolorThemeTree(menu, remap);
60024
+ recolorThemeTree(composer, remap);
60025
+ recolorThemeTree(header3, remap);
60026
+ recolorThemeTree(footer, remap);
60027
+ appliedTheme = theme;
59491
60028
  };
59492
60029
  const unsubTheme = onThemeChange((theme) => applyTheme(theme));
59493
60030
  return {
@@ -59534,6 +60071,9 @@ async function createShellChrome(otui, renderer, opts) {
59534
60071
  setTitle: (text) => paintDim(headerLeft, text),
59535
60072
  setStatus: (text) => paintDim(footerRight, text),
59536
60073
  setHeaderMeta: (text) => paintDim(headerRight, text),
60074
+ showSuggestion,
60075
+ clearSuggestion,
60076
+ suggestionActive: () => suggestion !== null,
59537
60077
  onSubmit: (handler) => {
59538
60078
  submitHandlers.add(handler);
59539
60079
  return () => {
@@ -59547,6 +60087,7 @@ async function createShellChrome(otui, renderer, opts) {
59547
60087
  clearBusyTimer();
59548
60088
  clearToastTimer();
59549
60089
  unsubscribeMenuKeys();
60090
+ unsubscribeSuggestionKeys();
59550
60091
  try {
59551
60092
  textarea.off(otui.LayoutEvents.RESIZED, onComposerResized);
59552
60093
  } catch {}
@@ -61740,7 +62281,8 @@ function createTuiAgentIo(otui, renderer, transcript) {
61740
62281
  onSystem: (text) => append(text.includes("[error]") ? otui.t`${otui.red(text)}` : otui.t`${otui.dim(text)}`),
61741
62282
  resetStream: () => {
61742
62283
  messages.reset();
61743
- }
62284
+ },
62285
+ lastCodeSegment: () => messages.lastCodeSegment()
61744
62286
  };
61745
62287
  }
61746
62288
  function attachBlockIo(io, addBlock, chrome = {}) {
@@ -62926,6 +63468,23 @@ async function launchTuiAgentShell(opts) {
62926
63468
  const newestBlock = (kind2) => nav.newest(kind2);
62927
63469
  const toggleNewestBlock = (kind2) => nav.toggleNewest(kind2);
62928
63470
  const copyBlock = (id) => nav.copy(id);
63471
+ const copyNewestOrLastCode = () => {
63472
+ const target = newestBlock();
63473
+ if (target !== undefined) {
63474
+ return copyBlock(target.id);
63475
+ }
63476
+ const code = io.lastCodeSegment();
63477
+ if (code === undefined) {
63478
+ return false;
63479
+ }
63480
+ try {
63481
+ r.copyToClipboardOSC52(code.body);
63482
+ chrome.showToast("Copied to clipboard");
63483
+ return true;
63484
+ } catch {
63485
+ return false;
63486
+ }
63487
+ };
62929
63488
  const addBlock = (input3, options = {}) => {
62930
63489
  const id = blockMount.add(input3, options);
62931
63490
  nav.paint(id);
@@ -63917,17 +64476,6 @@ Staying in the current session.
63917
64476
  } catch {}
63918
64477
  }, 12000);
63919
64478
  };
63920
- const summarizeSubmittedLine = (line) => {
63921
- const normalized = line.replace(/\r\n/g, `
63922
- `).replace(/\r/g, `
63923
- `);
63924
- const count = normalized.split(`
63925
- `).filter((linePart) => linePart.length > 0).length;
63926
- if (count <= 1) {
63927
- return line;
63928
- }
63929
- return `[pasted ${count} lines]`;
63930
- };
63931
64479
  const spawnSideWorker = (question, displayQuestion = question) => {
63932
64480
  sideQueue.push({ question, displayQuestion });
63933
64481
  if (sideQueue.length > 1 || sideWorkerRunning) {
@@ -64140,8 +64688,7 @@ Staying in the current session.
64140
64688
  return;
64141
64689
  }
64142
64690
  case "copy": {
64143
- const target = newestBlock();
64144
- if (target === undefined || !copyBlock(target.id)) {
64691
+ if (!copyNewestOrLastCode()) {
64145
64692
  io.onSystem?.(`Nothing to copy yet.
64146
64693
  `);
64147
64694
  }
@@ -64400,8 +64947,7 @@ Staying in the current session.
64400
64947
  return;
64401
64948
  }
64402
64949
  if (command.name === "/copy") {
64403
- const target = newestBlock();
64404
- if (target === undefined || !copyBlock(target.id)) {
64950
+ if (!copyNewestOrLastCode()) {
64405
64951
  io.onSystem?.(`Nothing to copy yet.
64406
64952
  `);
64407
64953
  }
@@ -64658,6 +65204,28 @@ ${formatThemeList(getThemeId())}`);
64658
65204
  };
64659
65205
  const controller = new AbortController;
64660
65206
  mainTurnAbortController = controller;
65207
+ const suggestNextStep = async () => {
65208
+ try {
65209
+ const lastUser = [...history].reverse().find((m) => m.role === "user")?.content ?? "";
65210
+ const lastAssistant = [...history].reverse().find((m) => m.role === "assistant")?.content ?? "";
65211
+ const tail = lastAssistant.slice(-3000);
65212
+ const result = await runModelTurn({
65213
+ provider: sel.provider,
65214
+ model: sel.model,
65215
+ system: "You are the next-step advisor of a coding assistant terminal. Based on the user's last request and the assistant's final reply, propose ONE short follow-up the user could do next: imperative, no quotes, no markdown, at most 80 characters. If nothing useful exists, reply with exactly one dot: .",
65216
+ user: `User: ${lastUser.slice(-800)}
65217
+
65218
+ Assistant reply (tail):
65219
+ ${tail}`,
65220
+ maxOutputTokens: 40,
65221
+ requestId: `suggest-next-step-${Date.now()}`
65222
+ });
65223
+ if (!result.credentialAvailable || result.text.trim().length === 0 || result.text.trim() === ".") {
65224
+ return;
65225
+ }
65226
+ chrome.showSuggestion(result.text.trim().split(/\s+/).slice(0, 20).join(" "));
65227
+ } catch {}
65228
+ };
64661
65229
  runAgentTurn(io, deps, history, line, {
64662
65230
  signal: controller.signal,
64663
65231
  ...slateSession !== undefined ? { slateSession } : {}
@@ -64678,6 +65246,9 @@ ${formatThemeList(getThemeId())}`);
64678
65246
  sbContext.content = otui.t`${otui.dim(`~${est.toLocaleString()} tokens (est)`)}`;
64679
65247
  }
64680
65248
  focusComposer();
65249
+ if (priorityMainQuestion === undefined && mainQueue.length === 0 && !turnFailed) {
65250
+ suggestNextStep();
65251
+ }
64681
65252
  if (priorityMainQuestion !== undefined) {
64682
65253
  const next = priorityMainQuestion;
64683
65254
  priorityMainQuestion = undefined;
@@ -64884,17 +65455,6 @@ async function mountChatShell(otui, renderer, opts) {
64884
65455
  chrome.setStatus(label());
64885
65456
  sbModel.content = otui.t`${otui.dim(label())}`;
64886
65457
  };
64887
- const summarizeSubmittedLine = (line) => {
64888
- const normalized = line.replace(/\r\n/g, `
64889
- `).replace(/\r/g, `
64890
- `);
64891
- const count = normalized.split(`
64892
- `).filter((linePart) => linePart.length > 0).length;
64893
- if (count <= 1) {
64894
- return line;
64895
- }
64896
- return `[pasted ${count} lines]`;
64897
- };
64898
65458
  const bridge = createChatBridge({
64899
65459
  onAccepted: (line) => {
64900
65460
  const displayLine = line.startsWith("/") ? line : summarizeSubmittedLine(line);
@@ -73004,6 +73564,17 @@ async function workspaceCommand(args2) {
73004
73564
  console.log(JSON.stringify(normalizeProposalLifecycleResult(result), null, 2));
73005
73565
  return;
73006
73566
  }
73567
+ if (subcommand === "dismiss-candidate") {
73568
+ rejectUnknownOptions(args2.slice(2), new Set(["--reason", "--evidence"]));
73569
+ const target = args2[1];
73570
+ const reason = optionValue(args2, "--reason");
73571
+ const evidence = optionValue(args2, "--evidence");
73572
+ if (!target)
73573
+ throw new Error("Usage: keryx workspace dismiss-candidate <evidence-path|session-id> [--reason <reason>] [--evidence <path>]");
73574
+ const result = await dismissUnboundByTarget(process.cwd(), evidence ?? target, reason);
73575
+ console.log(JSON.stringify(result, null, 2));
73576
+ return;
73577
+ }
73007
73578
  if (subcommand === "collaboration") {
73008
73579
  rejectUnknownOptions(args2.slice(2), new Set);
73009
73580
  const workspaceId = args2[1];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.2.65",
3
+ "version": "0.2.67",
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": {
@@ -72,4 +72,4 @@
72
72
  "protobufjs",
73
73
  "sharp"
74
74
  ]
75
- }
75
+ }