jeo-code 0.5.0 → 0.5.2

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/README.ja.md CHANGED
@@ -150,11 +150,11 @@ CI は `.github/workflows/npm-publish.yml` で公開します — GitHub リリ
150
150
  ## 変更履歴 (Changelog)
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.5.2]** (2026-06-14) — `$skill` prompt invocation with prefix/fuzzy suggestions, and a per-session input-box hue (amber in cmd-mode).
154
+ - **[0.5.1]** (2026-06-14) — cmd-mode `!<command>` shell escape — run a shell command without engaging the agent.
153
155
  - **[0.5.0]** (2026-06-14) — Performance: workspace-scan, workflow-state, and DNA-Claw HUD caches; plus a credential-safety fix that never wipes OAuth over an invalid config.
154
156
  - **[0.4.9]** (2026-06-14) — Live-frame width-clamp (content-sized height) replaces the constant-height approach, typed text shows during a running turn, and a docs/AGENTS refresh.
155
157
  - **[0.4.8]** (2026-06-14) — Live-frame stability: constant-height live turn, renderer self-heal off-by-one fix, and frame-safe child-stdout sanitizing — no more duplicate model bar or torn escapes.
156
- - **[0.4.7]** (2026-06-14) — Detached subagents + `subagent` control tool, live shaded in-flight output, registry-driven providers, fuller `read` budget, styled italics in the final report, and `gjc` retired.
157
- - **[0.4.6]** (2026-06-14) — Width-correct forge cards for CJK/emoji, red borders on failed tool cards, aligned `ooo ralph` monitor HUD, and a per-theme user-card palette.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/README.ko.md CHANGED
@@ -150,11 +150,11 @@ CI는 `.github/workflows/npm-publish.yml`로 배포합니다 — GitHub 릴리
150
150
  ## 변경 이력 (Changelog)
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.5.2]** (2026-06-14) — `$skill` prompt invocation with prefix/fuzzy suggestions, and a per-session input-box hue (amber in cmd-mode).
154
+ - **[0.5.1]** (2026-06-14) — cmd-mode `!<command>` shell escape — run a shell command without engaging the agent.
153
155
  - **[0.5.0]** (2026-06-14) — Performance: workspace-scan, workflow-state, and DNA-Claw HUD caches; plus a credential-safety fix that never wipes OAuth over an invalid config.
154
156
  - **[0.4.9]** (2026-06-14) — Live-frame width-clamp (content-sized height) replaces the constant-height approach, typed text shows during a running turn, and a docs/AGENTS refresh.
155
157
  - **[0.4.8]** (2026-06-14) — Live-frame stability: constant-height live turn, renderer self-heal off-by-one fix, and frame-safe child-stdout sanitizing — no more duplicate model bar or torn escapes.
156
- - **[0.4.7]** (2026-06-14) — Detached subagents + `subagent` control tool, live shaded in-flight output, registry-driven providers, fuller `read` budget, styled italics in the final report, and `gjc` retired.
157
- - **[0.4.6]** (2026-06-14) — Width-correct forge cards for CJK/emoji, red borders on failed tool cards, aligned `ooo ralph` monitor HUD, and a per-theme user-card palette.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/README.md CHANGED
@@ -150,11 +150,11 @@ Required npm token permissions (repository secret `NPM_TOKEN`):
150
150
  ## Changelog
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.5.2]** (2026-06-14) — `$skill` prompt invocation with prefix/fuzzy suggestions, and a per-session input-box hue (amber in cmd-mode).
154
+ - **[0.5.1]** (2026-06-14) — cmd-mode `!<command>` shell escape — run a shell command without engaging the agent.
153
155
  - **[0.5.0]** (2026-06-14) — Performance: workspace-scan, workflow-state, and DNA-Claw HUD caches; plus a credential-safety fix that never wipes OAuth over an invalid config.
154
156
  - **[0.4.9]** (2026-06-14) — Live-frame width-clamp (content-sized height) replaces the constant-height approach, typed text shows during a running turn, and a docs/AGENTS refresh.
155
157
  - **[0.4.8]** (2026-06-14) — Live-frame stability: constant-height live turn, renderer self-heal off-by-one fix, and frame-safe child-stdout sanitizing — no more duplicate model bar or torn escapes.
156
- - **[0.4.7]** (2026-06-14) — Detached subagents + `subagent` control tool, live shaded in-flight output, registry-driven providers, fuller `read` budget, styled italics in the final report, and `gjc` retired.
157
- - **[0.4.6]** (2026-06-14) — Width-correct forge cards for CJK/emoji, red borders on failed tool cards, aligned `ooo ralph` monitor HUD, and a per-theme user-card palette.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/README.zh.md CHANGED
@@ -150,11 +150,11 @@ CI 通过 `.github/workflows/npm-publish.yml` 发布 — GitHub 发布 release
150
150
  ## 更新日志 (Changelog)
151
151
 
152
152
  <!-- CHANGELOG:START (auto-generated from CHANGELOG.md — run `bun run changelog:sync`) -->
153
+ - **[0.5.2]** (2026-06-14) — `$skill` prompt invocation with prefix/fuzzy suggestions, and a per-session input-box hue (amber in cmd-mode).
154
+ - **[0.5.1]** (2026-06-14) — cmd-mode `!<command>` shell escape — run a shell command without engaging the agent.
153
155
  - **[0.5.0]** (2026-06-14) — Performance: workspace-scan, workflow-state, and DNA-Claw HUD caches; plus a credential-safety fix that never wipes OAuth over an invalid config.
154
156
  - **[0.4.9]** (2026-06-14) — Live-frame width-clamp (content-sized height) replaces the constant-height approach, typed text shows during a running turn, and a docs/AGENTS refresh.
155
157
  - **[0.4.8]** (2026-06-14) — Live-frame stability: constant-height live turn, renderer self-heal off-by-one fix, and frame-safe child-stdout sanitizing — no more duplicate model bar or torn escapes.
156
- - **[0.4.7]** (2026-06-14) — Detached subagents + `subagent` control tool, live shaded in-flight output, registry-driven providers, fuller `read` budget, styled italics in the final report, and `gjc` retired.
157
- - **[0.4.6]** (2026-06-14) — Width-correct forge cards for CJK/emoji, red borders on failed tool cards, aligned `ooo ralph` monitor HUD, and a per-theme user-card palette.
158
158
 
159
159
  See [CHANGELOG.md](CHANGELOG.md) for the full history.
160
160
  <!-- CHANGELOG:END -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jeo-code",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Clean, highly optimized AI coding agent using spec-first loop",
5
5
  "type": "module",
6
6
  "main": "src/cli.ts",
@@ -66,7 +66,7 @@ import { stripMarkdown } from "../tui/components/markdown-text";
66
66
  import { summarizeForgeInvocation } from "../tui/components/forge";
67
67
  import { formatDuration, formatUsage } from "../tui/components/duration";
68
68
 
69
- import { findTool, searchTool } from "../agent/tools";
69
+ import { findTool, searchTool, bashTool } from "../agent/tools";
70
70
  import { loadProjectContext, withProjectContext } from "../agent/context-files";
71
71
  import { maybeCompact, historyTokens } from "../agent/compaction";
72
72
  import * as path from "node:path";
@@ -2148,6 +2148,27 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2148
2148
  let uiTheme = resolveTheme(process.env);
2149
2149
  let uiAccent = accentPaint(uiTheme);
2150
2150
  let uiAccentShadow = accentShadowPaint(uiTheme);
2151
+ // Input-box border colors. Each opened session gets a DISTINCT hue (so several jeo
2152
+ // sessions are tellable apart at a glance), and cmd-mode (`!`) overrides it with a
2153
+ // caution amber so entering the shell escape is unmistakable.
2154
+ const SESSION_BOX_ACCENTS = ["#48dbfb", "#39ff14", "#a29bfe", "#1dd1a1", "#ff9ff3", "#54a0ff", "#ff6b81", "#c8d6e5"];
2155
+ const CMD_MODE_BOX_ACCENT = "#ffb300";
2156
+ const hexPaint = (hex: string) => (s: string) => chalk.hex(hex)(s);
2157
+ const hexShadowPaint = (hex: string) => (s: string) => chalk.dim(chalk.hex(hex)(s));
2158
+ // Per-process random start so different jeo processes differ at a glance; advanced on
2159
+ // each newly opened session (advanceSessionBoxColor) so consecutive sessions never match.
2160
+ let sessionBoxColorIdx = Math.floor(Math.random() * SESSION_BOX_ACCENTS.length);
2161
+ const advanceSessionBoxColor = (): void => {
2162
+ sessionBoxColorIdx = (sessionBoxColorIdx + 1) % SESSION_BOX_ACCENTS.length;
2163
+ };
2164
+ // Resolve the box painters for the current draft: cmd-mode amber when it starts with
2165
+ // `!`, else the per-session hue, else the theme accent (colorless theme / no session).
2166
+ const boxAccents = (line: string): { accent: (s: string) => string; shadow: (s: string) => string } => {
2167
+ if (!uiTheme.color) return { accent: uiAccent, shadow: uiAccentShadow };
2168
+ if (line.startsWith("!")) return { accent: hexPaint(CMD_MODE_BOX_ACCENT), shadow: hexShadowPaint(CMD_MODE_BOX_ACCENT) };
2169
+ if (sessionId) { const hex = SESSION_BOX_ACCENTS[sessionBoxColorIdx]!; return { accent: hexPaint(hex), shadow: hexShadowPaint(hex) }; }
2170
+ return { accent: uiAccent, shadow: uiAccentShadow };
2171
+ };
2151
2172
  const refreshUiTheme = (): void => {
2152
2173
  uiTheme = resolveTheme(process.env);
2153
2174
  uiAccent = accentPaint(uiTheme);
@@ -2182,12 +2203,13 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2182
2203
  // sits at the end of the text.
2183
2204
  const rli = rl as unknown as { line?: string; cursor?: number };
2184
2205
  const caret = rli.line === line && typeof rli.cursor === "number" ? rli.cursor : line.length;
2206
+ const { accent: boxAccent, shadow: boxShadow } = boxAccents(line);
2185
2207
  const frame = renderInputFrame(line, {
2186
2208
  cols,
2187
2209
  color: true,
2188
2210
  unicode: true,
2189
- accent: uiAccent,
2190
- accentShadow: uiAccentShadow,
2211
+ accent: boxAccent,
2212
+ accentShadow: boxShadow,
2191
2213
  cwdLabel: currentAtLabel(line),
2192
2214
  attachmentLabel: pendingImages.length
2193
2215
  ? `⧉ ${pendingImages.length} image${pendingImages.length > 1 ? "s" : ""} attached — sent with the next message`
@@ -2998,9 +3020,29 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
2998
3020
  if (pendingImages.length === 0) continue;
2999
3021
  input = "Please look at the attached image(s)."; // image-only submit
3000
3022
  }
3023
+ // gjc-parity shell escape: `!<cmd>` runs the command directly in cmd-mode and
3024
+ // prints its output WITHOUT engaging the agent (history untouched), like a REPL
3025
+ // shell escape. The user is explicitly driving their own shell, so the deep-interview
3026
+ // mutation guard (which gates the AGENT's tools) does not apply here.
3027
+ if (input.startsWith("!")) {
3028
+ const cmd = input.slice(1).trim();
3029
+ if (!cmd) {
3030
+ console.log("Usage: !<shell command> (run a command in cmd-mode; the agent and history are untouched)");
3031
+ continue;
3032
+ }
3033
+ try {
3034
+ const res = await bashTool(cmd, cwd);
3035
+ if (res.output) console.log(res.output);
3036
+ if (!res.success && res.error) console.log(chalk.red(res.error));
3037
+ } catch (err) {
3038
+ console.log(chalk.red(`! command failed: ${(err as Error).message}`));
3039
+ }
3040
+ continue;
3041
+ }
3001
3042
  if (input === "/" || input === "/?" || input === "/help") {
3002
3043
  logLines(formatSlashCommandList(input === "/help" ? "/" : input, skillSlashDetails));
3003
3044
  console.log("Tools: read / write / edit / bash / find / search. Sessions persist to .jeo/sessions/.");
3045
+ console.log("Shell: !<command> runs a command in cmd-mode directly (agent/history untouched).");
3004
3046
  const tip = getEvolutionTip(history.length, flags.maxSteps > 0 ? flags.maxSteps : initialStepLimit);
3005
3047
  console.log(`\n${chalk.cyan("Evolutionary Tip:")} ${tip}`);
3006
3048
  continue;
@@ -3042,6 +3084,7 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
3042
3084
  history.length = 1;
3043
3085
  if (!flags.noSession) {
3044
3086
  sessionId = (await createSession(cwd)).id;
3087
+ advanceSessionBoxColor(); // distinct input-box hue per newly opened session
3045
3088
  console.log(`(${verb} — new session ${sessionId})`);
3046
3089
  } else {
3047
3090
  sessionId = undefined;
@@ -4095,6 +4138,25 @@ export async function runLaunchCommand(args: string[]): Promise<void> {
4095
4138
  }
4096
4139
  continue;
4097
4140
  }
4141
+ // Unresolved `$skill` → suggest precisely, never silently send the typo to the model.
4142
+ // `$exact`/`$prefix` already ran above; a leftover `$word` is a missed skill attempt,
4143
+ // EXCEPT `$UPPERCASE` env-var-style tokens (e.g. `$HOME`), which pass through untouched.
4144
+ if (input.startsWith("$")) {
4145
+ const token = (input.split(/\s+/, 1)[0] ?? "").slice(1);
4146
+ if (token && !/^[A-Z_][A-Z0-9_]*$/.test(token)) {
4147
+ const lc = token.toLowerCase();
4148
+ const prefix = resolvedSkills.filter(s => s.name.toLowerCase().startsWith(lc));
4149
+ if (prefix.length) {
4150
+ console.log(`Ambiguous skill '$${token}'. Did you mean: ${prefix.slice(0, 6).map(s => `$${s.name}`).join(", ")}?`);
4151
+ } else {
4152
+ const names = resolvedSkills.map(s => `$${s.name}`);
4153
+ const shown = names.slice(0, 12).join(", ");
4154
+ const more = names.length > 12 ? ` … +${names.length - 12} more` : "";
4155
+ console.log(`No skill '$${token}'. ${names.length ? `Available: ${shown}${more}` : "No skills are loaded."} (Type $ to autocomplete.)`);
4156
+ }
4157
+ continue;
4158
+ }
4159
+ }
4098
4160
  // Unhandled slash attempt → suggest, don't send the typo to the model.
4099
4161
  if (isSlashAttempt(input)) {
4100
4162
  const m = matchSlash(input, [...completionContext().slashCommands]);
@@ -475,6 +475,32 @@ export function getSkillFrom(skills: SkillDoc[], name: string): SkillDoc | undef
475
475
  return skills.find(s => s.name.toLowerCase() === name.toLowerCase());
476
476
  }
477
477
 
478
+ /** The single skill whose name PREFIX-matches `query` (case-insensitive), or undefined
479
+ * when zero or many match. Lets `$te` precisely resolve to `$team` without full spelling. */
480
+ export function uniquePrefixSkill(skills: SkillDoc[], query: string): SkillDoc | undefined {
481
+ const q = query.toLowerCase();
482
+ if (!q) return undefined;
483
+ const hits = skills.filter(s => s.name.toLowerCase().startsWith(q));
484
+ return hits.length === 1 ? hits[0] : undefined;
485
+ }
486
+
487
+ /** Prefix-first, then fuzzy-subsequence skill suggestions for a `$query` that did NOT
488
+ * resolve — drives the REPL's clear "did you mean / available" feedback. */
489
+ export function suggestSkills(skills: SkillDoc[], query: string): SkillDoc[] {
490
+ const q = query.toLowerCase();
491
+ const prefix = skills.filter(s => s.name.toLowerCase().startsWith(q));
492
+ const seen = new Set(prefix.map(s => s.name));
493
+ const fuzzy = skills.filter(s => !seen.has(s.name) && skillNameSubsequence(q, s.name.toLowerCase()));
494
+ return [...prefix, ...fuzzy];
495
+ }
496
+
497
+ /** Order-preserving subsequence test (every char of `needle` appears in `hay` L→R). */
498
+ function skillNameSubsequence(needle: string, hay: string): boolean {
499
+ let i = 0;
500
+ for (let j = 0; j < hay.length && i < needle.length; j++) if (hay[j] === needle[i]) i++;
501
+ return i === needle.length;
502
+ }
503
+
478
504
  /** Case-insensitive lookup by direct slash alias, e.g. `/speckit.plan`. */
479
505
  export function getSkillBySlash(skills: SkillDoc[], command: string): SkillDoc | undefined {
480
506
  const q = command.toLowerCase();
@@ -514,7 +540,7 @@ export function parseSkillInvocation(input: string, skills: SkillDoc[]): SkillIn
514
540
  // only when a skill with that exact name is loaded — `$HOME is what?` or any
515
541
  // unknown `$word` falls through to the model as an ordinary prompt.
516
542
  if (command.length > 1 && command.startsWith("$")) {
517
- const dollarSkill = getSkillFrom(skills, command.slice(1));
543
+ const dollarSkill = getSkillFrom(skills, command.slice(1)) ?? uniquePrefixSkill(skills, command.slice(1));
518
544
  if (dollarSkill) {
519
545
  return { skill: dollarSkill, intent: trimmed.slice(command.length).trim(), invokedAs: command };
520
546
  }