@ordewell/cli 0.4.15 → 0.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/main.js +78 -30
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -3035,12 +3035,39 @@ function findCommand(name) {
3035
3035
  const key = name.replace(/^\//, "").toLowerCase();
3036
3036
  return BY_NAME.get(key) ?? skillByName.get(key);
3037
3037
  }
3038
- function completions(text) {
3039
- if (!text.startsWith("/")) return [];
3040
- const typed = text.slice(1);
3041
- if (/\s/.test(typed)) return [];
3042
- const prefix = typed.toLowerCase();
3043
- return [...SLASH_COMMANDS, ...skillCommands].filter((c) => c.name.startsWith(prefix));
3038
+ var TRAILING_PUNCT = /[,.!?;:]+$/;
3039
+ function slashTokens(text) {
3040
+ const tokens = [];
3041
+ const re = /(^|\s)\/(\S*)/g;
3042
+ let match;
3043
+ while (match = re.exec(text)) {
3044
+ const start = match.index + match[1].length;
3045
+ const raw = match[2];
3046
+ const punct = raw.match(TRAILING_PUNCT)?.[0] ?? "";
3047
+ const nameLen = raw.length - punct.length;
3048
+ tokens.push({
3049
+ start,
3050
+ end: start + 1 + raw.length,
3051
+ name: raw.slice(0, nameLen).toLowerCase(),
3052
+ nameEnd: start + 1 + nameLen
3053
+ });
3054
+ }
3055
+ return tokens;
3056
+ }
3057
+ function activeToken(text, cursor) {
3058
+ return slashTokens(text).find((t) => t.end === cursor) ?? null;
3059
+ }
3060
+ function skillMatchKind(name) {
3061
+ if (name.length === 0) return null;
3062
+ if (skillByName.has(name)) return "exact";
3063
+ for (const key of skillByName.keys()) {
3064
+ if (key.startsWith(name)) return "prefix";
3065
+ }
3066
+ return null;
3067
+ }
3068
+ function tokenCompletions(token) {
3069
+ const pool = token.start === 0 ? [...SLASH_COMMANDS, ...skillCommands] : skillCommands;
3070
+ return pool.filter((c) => c.name.startsWith(token.name));
3044
3071
  }
3045
3072
 
3046
3073
  // src/tui/markdown.ts
@@ -3768,17 +3795,32 @@ function renderStatus(state, cols) {
3768
3795
  const tail = thinking.length > room ? `\u2026${thinking.slice(-(room - 1)).trimStart()}` : thinking;
3769
3796
  return truncate(`${head}${style.grey(` \xB7 ${tail}`)}`, cols);
3770
3797
  }
3771
- var LEADING_TOKEN = /^\/\S*/;
3772
- function skillTokenLength(text) {
3773
- const match = LEADING_TOKEN.exec(text);
3774
- if (!match) return 0;
3775
- return findCommand(match[0])?.source === "skill" ? match[0].length : 0;
3798
+ function highlightSpans(text) {
3799
+ const spans = [];
3800
+ const expandedExact = /* @__PURE__ */ new Set();
3801
+ for (const token of slashTokens(text)) {
3802
+ const kind = skillMatchKind(token.name);
3803
+ if (kind === null) continue;
3804
+ if (kind === "exact") {
3805
+ if (expandedExact.has(token.name)) continue;
3806
+ expandedExact.add(token.name);
3807
+ }
3808
+ spans.push({ start: token.start, end: token.nameEnd });
3809
+ }
3810
+ return spans;
3776
3811
  }
3777
- function paintRange(text, base, tokenEnd) {
3778
- const localEnd = Math.max(0, Math.min(text.length, tokenEnd - base));
3779
- if (localEnd <= 0) return text;
3780
- if (localEnd >= text.length) return style.cyan(text);
3781
- return style.cyan(text.slice(0, localEnd)) + text.slice(localEnd);
3812
+ function paintSpans(text, base, spans) {
3813
+ if (spans.length === 0) return text;
3814
+ let out = "";
3815
+ let cursor = 0;
3816
+ for (const span of spans) {
3817
+ const localStart = Math.max(cursor, Math.min(text.length, span.start - base));
3818
+ const localEnd = Math.max(0, Math.min(text.length, span.end - base));
3819
+ if (localEnd <= localStart) continue;
3820
+ out += text.slice(cursor, localStart) + style.cyan(text.slice(localStart, localEnd));
3821
+ cursor = localEnd;
3822
+ }
3823
+ return out + text.slice(cursor);
3782
3824
  }
3783
3825
  function renderInput(state, cols) {
3784
3826
  const prompt = state.focus === "plan" ? style.grey("\u276F ") : style.cyan("\u276F ");
@@ -3786,28 +3828,31 @@ function renderInput(state, cols) {
3786
3828
  const room = chatEditorRoom(cols, lines !== null);
3787
3829
  const { text, cursor } = state.editor;
3788
3830
  const continuation = " ".repeat(width(prompt));
3789
- const tokenEnd = skillTokenLength(text);
3831
+ const spans = highlightSpans(text);
3790
3832
  if (!lines) {
3791
3833
  const visible = text.replace(/\n/g, "\xB6").replace(/\t/g, " ");
3792
- const painted = tokenEnd > 0 ? paintRange(visible, 0, tokenEnd) : visible;
3834
+ const painted = paintSpans(visible, 0, spans);
3793
3835
  return [prompt + painted + suggest(state, room - width(visible))];
3794
3836
  }
3795
3837
  const { line: lineIndex, col } = cursorInLines(lines, cursor, text.length);
3796
- const rawLines = lines.map((l) => l.line);
3797
- return rawLines.map((raw, i) => {
3838
+ return lines.map(({ line: raw, start: base }, i) => {
3798
3839
  const prefix = i === 0 ? prompt : continuation;
3799
3840
  const line = raw.replace(/\t/g, " ");
3800
3841
  const isCursorLine = i === lineIndex;
3801
- const lineTokenEnd = i === 0 ? tokenEnd : 0;
3802
- const rendered = isCursorLine ? paintRange(line.slice(0, col), 0, lineTokenEnd) + style.inverse(line.slice(col, col + 1) || " ") + paintRange(line.slice(col + 1), col + 1, lineTokenEnd) : paintRange(line, 0, lineTokenEnd);
3803
- const used = width(line) + (isCursorLine && col >= line.length ? 1 : 0);
3804
- const hint = i === rawLines.length - 1 ? suggest(state, room - used) : "";
3805
- return prefix + rendered + hint;
3842
+ if (!isCursorLine) return prefix + paintSpans(line, base, spans);
3843
+ const before = paintSpans(line.slice(0, col), base, spans);
3844
+ const caretChar = line.slice(col, col + 1) || " ";
3845
+ const after = paintSpans(line.slice(col + 1), base + col + 1, spans);
3846
+ const used = width(line.slice(0, col)) + width(caretChar);
3847
+ const hint = suggest(state, room - used);
3848
+ return prefix + before + style.inverse(caretChar) + hint + after;
3806
3849
  });
3807
3850
  }
3808
3851
  function suggest(state, room) {
3809
3852
  if (state.focus === "plan" || room <= 2) return "";
3810
- const matches = completions(state.editor.text);
3853
+ const token = activeToken(state.editor.text, state.editor.cursor);
3854
+ if (!token) return "";
3855
+ const matches = tokenCompletions(token);
3811
3856
  if (matches.length === 0) return "";
3812
3857
  const names = matches.map((c) => c.name).join(" ");
3813
3858
  return style.grey(` ${truncate(names, Math.max(0, room - 2))}`);
@@ -4434,10 +4479,13 @@ function handleKey2(state, key) {
4434
4479
  if (isMouseSelect(key)) return handleMouseSelect(state, key);
4435
4480
  if (key.name === "wheelignored") return step(state);
4436
4481
  if (key.name === "tab" && state.focus === "chat") {
4437
- const matches = completions(state.editor.text);
4438
- if (matches.length > 0) {
4439
- const text = `/${matches[0].name} `;
4440
- return step({ ...state, editor: { ...state.editor, text, cursor: text.length } });
4482
+ const token = activeToken(state.editor.text, state.editor.cursor);
4483
+ const matches = token ? tokenCompletions(token) : [];
4484
+ if (token && matches.length > 0) {
4485
+ const { text: full } = state.editor;
4486
+ const completed = `/${matches[0].name} `;
4487
+ const text = full.slice(0, token.start) + completed + full.slice(token.end);
4488
+ return step({ ...state, editor: { ...state.editor, text, cursor: token.start + completed.length } });
4441
4489
  }
4442
4490
  }
4443
4491
  if (key.name === "tab") return step({ ...state, focus: state.focus === "chat" ? "plan" : "chat" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ordewell/cli",
3
- "version": "0.4.15",
3
+ "version": "0.4.16",
4
4
  "description": "Ordewell CLI and TUI — plan, run and verify coding-agent tasks from the terminal.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Alessandro Costanzo Ciano",