jeo-code 0.5.12 → 0.5.14
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/CHANGELOG.md +19 -0
- package/README.ja.md +2 -2
- package/README.ko.md +2 -2
- package/README.md +2 -2
- package/README.zh.md +2 -2
- package/package.json +3 -2
- package/src/agent/engine.ts +16 -3
- package/src/agent/loop.ts +2 -0
- package/src/agent/tool-schemas.ts +132 -0
- package/src/agent/tools.ts +9 -3
- package/src/ai/model-manager.ts +1 -0
- package/src/ai/providers/anthropic.ts +60 -3
- package/src/ai/providers/antigravity.ts +31 -1
- package/src/ai/providers/openai-responses.ts +55 -0
- package/src/ai/providers/openai.ts +46 -3
- package/src/ai/types.ts +19 -0
- package/src/cli/runner.ts +9 -0
- package/src/commands/launch.ts +207 -256
- package/src/commands/update.ts +12 -0
- package/src/commands/whats-new.ts +3 -2
- package/src/skills/catalog.ts +34 -70
- package/src/tui/app.ts +43 -61
- package/src/tui/components/autocomplete.ts +2 -8
- package/src/tui/components/slash.ts +1 -2
- package/src/util/whats-new.ts +4 -1
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
parseChangelogSections,
|
|
5
5
|
releaseSections,
|
|
6
6
|
renderWhatsNew,
|
|
7
|
+
RECENT_RELEASE_COUNT,
|
|
7
8
|
} from "../util/whats-new";
|
|
8
9
|
import { supportsUnicode } from "../tui/components/capability";
|
|
9
10
|
|
|
@@ -30,7 +31,7 @@ export async function runWhatsNewCommand(args: string[] = []): Promise<void> {
|
|
|
30
31
|
|
|
31
32
|
const md = await loadBundledChangelog();
|
|
32
33
|
const all = md ? releaseSections(parseChangelogSections(md)) : [];
|
|
33
|
-
const sections = hasAll ? all : all.slice(0,
|
|
34
|
+
const sections = hasAll ? all : all.slice(0, RECENT_RELEASE_COUNT);
|
|
34
35
|
|
|
35
36
|
if (hasJson) {
|
|
36
37
|
console.log(JSON.stringify({ version: pkg.version, entries: sections }, null, 2));
|
|
@@ -56,7 +57,7 @@ function printUsage(): void {
|
|
|
56
57
|
console.log("Show the release notes bundled with the installed jeo-code version.");
|
|
57
58
|
console.log("");
|
|
58
59
|
console.log("Options:");
|
|
59
|
-
console.log(
|
|
60
|
+
console.log(` --all Show notes for every released version, not just the recent ${RECENT_RELEASE_COUNT}`);
|
|
60
61
|
console.log(" --json Output the notes as JSON");
|
|
61
62
|
console.log(" -h, --help Show this help message");
|
|
62
63
|
}
|
package/src/skills/catalog.ts
CHANGED
|
@@ -183,50 +183,20 @@ export function skillsPromptSection(skills: SkillDoc[] = SKILLS): string {
|
|
|
183
183
|
import * as fs from "node:fs/promises";
|
|
184
184
|
import * as path from "node:path";
|
|
185
185
|
import * as os from "node:os";
|
|
186
|
-
import { existsSync, statSync, readFileSync } from "node:fs";
|
|
187
|
-
import { jeoEnv } from "../util/env";
|
|
188
186
|
|
|
189
|
-
|
|
190
|
-
try {
|
|
191
|
-
let targetPath = path.resolve(filePath);
|
|
192
|
-
if (!existsSync(targetPath)) {
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
195
|
-
const stat = statSync(targetPath);
|
|
196
|
-
if (stat.isDirectory()) {
|
|
197
|
-
const skillMd = path.join(targetPath, "SKILL.md");
|
|
198
|
-
if (existsSync(skillMd) && statSync(skillMd).isFile()) {
|
|
199
|
-
targetPath = skillMd;
|
|
200
|
-
} else {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
} else if (!stat.isFile() || !targetPath.endsWith(".md")) {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
187
|
+
import { jeoEnv } from "../util/env";
|
|
206
188
|
|
|
207
|
-
const content = readFileSync(targetPath, "utf-8");
|
|
208
|
-
// Determine a name for this skill
|
|
209
|
-
let skillName = path.basename(targetPath, ".md");
|
|
210
|
-
if (skillName.toLowerCase() === "skill" || skillName.toLowerCase() === "readme") {
|
|
211
|
-
// Use the directory name if the filename is generic
|
|
212
|
-
skillName = path.basename(path.dirname(targetPath));
|
|
213
|
-
}
|
|
214
|
-
const parsed = parseSkillMarkdown(skillName, content, { preferMetaName: true });
|
|
215
|
-
return isSupportedExternalSkill(parsed) ? parsed : null;
|
|
216
|
-
} catch {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
189
|
const BUILTIN_SLASH_ALIASES = new Set([
|
|
221
190
|
"/help", "/clear", "/compact", "/model", "/fast", "/provider", "/logout",
|
|
222
191
|
"/agents", "/config", "/roles", "/thinking",
|
|
223
|
-
"/view", "/diff", "/find", "/search",
|
|
192
|
+
"/view", "/diff", "/find", "/search",
|
|
224
193
|
"/exit", "/quit",
|
|
225
194
|
]);
|
|
226
195
|
|
|
227
|
-
const RESERVED_SKILL_NAMES = new Set(
|
|
228
|
-
[...BUILTIN_SLASH_ALIASES].map(alias => alias.slice(1).toLowerCase())
|
|
229
|
-
|
|
196
|
+
const RESERVED_SKILL_NAMES = new Set([
|
|
197
|
+
...[...BUILTIN_SLASH_ALIASES].map(alias => alias.slice(1).toLowerCase()),
|
|
198
|
+
"skill",
|
|
199
|
+
]);
|
|
230
200
|
|
|
231
201
|
function normalizeSlashAlias(raw: string): string | undefined {
|
|
232
202
|
const m = raw.trim().match(/^\/[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*)*$/);
|
|
@@ -410,12 +380,33 @@ export function parseSkillMarkdown(name: string, content: string, opts?: { prefe
|
|
|
410
380
|
};
|
|
411
381
|
}
|
|
412
382
|
|
|
383
|
+
const ALLOWED_SKILL_NAMES = new Set([
|
|
384
|
+
"deep-interview",
|
|
385
|
+
"deep-dive",
|
|
386
|
+
"ralplan",
|
|
387
|
+
"team",
|
|
388
|
+
"ultragoal",
|
|
389
|
+
"research",
|
|
390
|
+
"ultrawork"
|
|
391
|
+
]);
|
|
392
|
+
|
|
413
393
|
function isSupportedExternalSkill(doc: SkillDoc): boolean {
|
|
414
|
-
|
|
394
|
+
const nameLower = doc.name.toLowerCase();
|
|
395
|
+
return !RESERVED_SKILL_NAMES.has(nameLower);
|
|
415
396
|
}
|
|
416
397
|
|
|
417
398
|
/** Bundled skills merged with user skill docs from {@link skillDirs} (user overrides by name). */
|
|
418
399
|
export async function loadSkills(cwd: string = process.cwd()): Promise<SkillDoc[]> {
|
|
400
|
+
try {
|
|
401
|
+
const lockPath = path.join(cwd, "skills-lock.json");
|
|
402
|
+
const lockContent = await fs.readFile(lockPath, "utf-8");
|
|
403
|
+
const lockData = JSON.parse(lockContent);
|
|
404
|
+
if (lockData && lockData.skills) {
|
|
405
|
+
for (const name of Object.keys(lockData.skills)) {
|
|
406
|
+
ALLOWED_SKILL_NAMES.add(name.toLowerCase());
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
} catch {}
|
|
419
410
|
const byName = new Map<string, SkillDoc>(SKILLS.map(s => [s.name.toLowerCase(), s]));
|
|
420
411
|
for (const dir of skillDirs(cwd)) {
|
|
421
412
|
let entries: import("node:fs").Dirent[] = [];
|
|
@@ -512,51 +503,24 @@ export interface SkillInvocation {
|
|
|
512
503
|
intent: string;
|
|
513
504
|
invokedAs?: string;
|
|
514
505
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
*
|
|
518
|
-
*
|
|
506
|
+
/** Parse only an explicit `$skill` invocation. Skills are invokable ONLY via the `$`
|
|
507
|
+
* entrypoint — `/` commands and slash aliases never load a skill file, and pasted SKILL.md
|
|
508
|
+
* paths cannot hijack an ordinary coding request. Only the FIRST token counts, and only
|
|
509
|
+
* when a skill with that exact name (or unique name prefix) is loaded; `$HOME is what?` or
|
|
510
|
+
* any unknown `$word` falls through to the model as an ordinary prompt. */
|
|
519
511
|
export function parseSkillInvocation(input: string, skills: SkillDoc[]): SkillInvocation | null {
|
|
520
512
|
const trimmed = input.trim();
|
|
521
513
|
if (!trimmed) return null;
|
|
522
514
|
|
|
523
|
-
const explicitEntrypoint = trimmed.startsWith("/skill:")
|
|
524
|
-
? "/skill:"
|
|
525
|
-
: (trimmed === "/skill" || trimmed.startsWith("/skill ")) ? "/skill" : "";
|
|
526
|
-
if (explicitEntrypoint) {
|
|
527
|
-
const rest = trimmed.substring(explicitEntrypoint.length).trim();
|
|
528
|
-
if (!rest) return null;
|
|
529
|
-
const [name, ...intentParts] = rest.split(/\s+/);
|
|
530
|
-
let skill = getSkillFrom(skills, name ?? "");
|
|
531
|
-
if (!skill && name) {
|
|
532
|
-
skill = tryResolveSkillFromFilePath(name) ?? undefined;
|
|
533
|
-
}
|
|
534
|
-
return skill ? { skill, intent: intentParts.join(" ").trim() } : null;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
515
|
const command = trimmed.split(/\s+/, 1)[0] ?? "";
|
|
538
|
-
// Codex/gjc-style exact-name entrypoint: `$team [intent]` invokes the skill
|
|
539
|
-
// named "team" directly (case-insensitive). Only the FIRST token counts, and
|
|
540
|
-
// only when a skill with that exact name is loaded — `$HOME is what?` or any
|
|
541
|
-
// unknown `$word` falls through to the model as an ordinary prompt.
|
|
542
516
|
if (command.length > 1 && command.startsWith("$")) {
|
|
543
517
|
const dollarSkill = getSkillFrom(skills, command.slice(1)) ?? uniquePrefixSkill(skills, command.slice(1));
|
|
544
518
|
if (dollarSkill) {
|
|
545
519
|
return { skill: dollarSkill, intent: trimmed.slice(command.length).trim(), invokedAs: command };
|
|
546
520
|
}
|
|
547
521
|
}
|
|
548
|
-
|
|
549
|
-
if (!skill) {
|
|
550
|
-
if (command.startsWith("/") || command.startsWith(".") || command.includes("/")) {
|
|
551
|
-
const resolved = tryResolveSkillFromFilePath(command);
|
|
552
|
-
if (resolved) {
|
|
553
|
-
return { skill: resolved, intent: trimmed.slice(command.length).trim(), invokedAs: command };
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
return skill ? { skill, intent: trimmed.slice(command.length).trim(), invokedAs: command } : null;
|
|
522
|
+
return null;
|
|
558
523
|
}
|
|
559
|
-
|
|
560
524
|
/** Parse a LEADING run of `$skill` tokens into an ordered chain that shares the trailing
|
|
561
525
|
* text as one intent: `$ralplan $team build auth` → [ralplan, team] each with intent
|
|
562
526
|
* "build auth". This is what lets `$` invoke several skills in one line — they all run,
|
package/src/tui/app.ts
CHANGED
|
@@ -25,8 +25,8 @@ import { SECTION_GAP, stackSections } from "./components/section";
|
|
|
25
25
|
import { resolveTheme, themeGradient, accentPaint, accentShadowPaint, diffPaint, mutedPaint, cardFillPaint } from "./components/themes";
|
|
26
26
|
import { detectColorLevel, animatedGradientText, ColorLevel } from "./components/color";
|
|
27
27
|
import { formatForgeBox, summarizeForgeInvocation, summarizeForgeResult, fitForgeBoxes, webSearchCardLines, type ForgeSummary } from "./components/forge";
|
|
28
|
-
import {
|
|
29
|
-
import { costForUsage
|
|
28
|
+
import { renderStatusBar, renderStatusBox, type StatusBoxData } from "./components/status";
|
|
29
|
+
import { costForUsage } from "../ai/pricing";
|
|
30
30
|
import { renderMarkdownTables } from "./components/markdown-table";
|
|
31
31
|
|
|
32
32
|
import { stripMarkdown, renderMarkdownAnsi } from "./components/markdown-text";
|
|
@@ -35,7 +35,7 @@ import { categoryBadge } from "./components/category-index";
|
|
|
35
35
|
import { formatStepTimeline, stepsFromTools, formatStepHeader, formatStepTimelineCompact, formatDuration as formatToolMs, type StepState } from "./components/step-timeline";
|
|
36
36
|
import { formatHintBar } from "./components/hints";
|
|
37
37
|
import { formatDuration, formatUsage } from "./components/duration";
|
|
38
|
-
import { renderHud,
|
|
38
|
+
import { renderHud, type JeoPhase } from "./components/hud";
|
|
39
39
|
import { formatTodoWriteCard } from "./components/todo-card";
|
|
40
40
|
import { renderInputBox } from "./components/input-box";
|
|
41
41
|
import { jeoEnv } from "../util/env";
|
|
@@ -505,9 +505,11 @@ export class LaunchTui {
|
|
|
505
505
|
if (!success) this.flushForgeCard(result, false);
|
|
506
506
|
} else {
|
|
507
507
|
// Light tool: one ✓/✗ line, plus a dim result tree for list-shaped output
|
|
508
|
-
// (find/search/ls) and an error card when the tool failed.
|
|
508
|
+
// (find/search/ls) and an error card when the tool failed. The ledger line
|
|
509
|
+
// stays a clean single line (no ms suffix) — light tools are sub-ms and the
|
|
510
|
+
// duration detail lives on the heavier forge cards instead.
|
|
509
511
|
const { suffix, children } = this.ledgerTree(tool, success, output);
|
|
510
|
-
this.appendLedger(`${paintedMark} ${target}${suffix}
|
|
512
|
+
this.appendLedger(`${paintedMark} ${target}${suffix}\n${children.map(c => `${c}\n`).join("")}`, "tool");
|
|
511
513
|
if (!success) {
|
|
512
514
|
this.rememberForge(result);
|
|
513
515
|
this.flushForgeCard(result, false);
|
|
@@ -1105,6 +1107,40 @@ export class LaunchTui {
|
|
|
1105
1107
|
align: "left",
|
|
1106
1108
|
});
|
|
1107
1109
|
}
|
|
1110
|
+
|
|
1111
|
+
/** Build the live status-box data — the ~20-field payload shared by the inline and
|
|
1112
|
+
* the bottom-pinned (non-inline) frames so they can't drift (color, verify-yellow,
|
|
1113
|
+
* metrics, usage all defined once). Only `cols` differs between callers. */
|
|
1114
|
+
private statusBoxData(args: { cols: number; elapsedMs: number; stepNow: number; phase: number; colorLevel: number; idx: number }): StatusBoxData {
|
|
1115
|
+
const { cols, elapsedMs, stepNow, phase, colorLevel, idx } = args;
|
|
1116
|
+
const grad = themeGradient(this.theme, idx);
|
|
1117
|
+
const verifying = this.runningTool;
|
|
1118
|
+
const stats = this.tools.stats();
|
|
1119
|
+
return {
|
|
1120
|
+
cols,
|
|
1121
|
+
phaseLabel: this.workflowStatus ? `${this.workflowStatus.skill}:${this.workflowStatus.phase}` : this.hudPhase,
|
|
1122
|
+
spinner: verifying && this.theme.color ? chalk.yellow(this.spinner.current()) : this.spinner.current(),
|
|
1123
|
+
activity: this.retryNotice ?? (this.streamingActivity || this.currentActivity()),
|
|
1124
|
+
escHint: true,
|
|
1125
|
+
elapsedMs,
|
|
1126
|
+
stepElapsedMs: this.currentStepStartedAt ? Date.now() - this.currentStepStartedAt : undefined,
|
|
1127
|
+
avgStepMs: stepNow > 0 ? elapsedMs / stepNow : undefined,
|
|
1128
|
+
okCount: stats.ok,
|
|
1129
|
+
failCount: stats.fail,
|
|
1130
|
+
runningCount: stats.running,
|
|
1131
|
+
totalCount: stats.total,
|
|
1132
|
+
mutationGuarded: this.mutationGuarded,
|
|
1133
|
+
unicode: this.unicode,
|
|
1134
|
+
color: this.theme.color,
|
|
1135
|
+
colorLevel,
|
|
1136
|
+
phase,
|
|
1137
|
+
palette: verifying ? [...STATUS_VERIFY_PALETTE] : [grad.from, grad.to],
|
|
1138
|
+
isThinking: true,
|
|
1139
|
+
usage: this.turnUsage,
|
|
1140
|
+
costUsd: costForUsage(this.footer.model, this.turnUsage) ?? undefined,
|
|
1141
|
+
subagentActive: this.subagentActive,
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1108
1144
|
/**
|
|
1109
1145
|
* The gjc-style inline live frame: a flat stack with no outer border —
|
|
1110
1146
|
* <live forge card(s)> · <spinner status line> · <todos> · <hud line> · <model bar>
|
|
@@ -1178,37 +1214,7 @@ export class LaunchTui {
|
|
|
1178
1214
|
// streamed activity is uniform across providers via streamingActivity and keeps
|
|
1179
1215
|
// the ⟦esc⟧ cancel hint visible without trapping the message inside a border.
|
|
1180
1216
|
if (isThinking) {
|
|
1181
|
-
|
|
1182
|
-
// While a tool/process runs (background verification), the status animation turns
|
|
1183
|
-
// amber/yellow — distinct from the cool thinking gradient (gjc warning-color parity).
|
|
1184
|
-
const verifying = this.runningTool;
|
|
1185
|
-
const verifySpin = verifying && this.theme.color ? chalk.yellow(this.spinner.current()) : this.spinner.current();
|
|
1186
|
-
const costUsd = costForUsage(this.footer.model, this.turnUsage) ?? undefined;
|
|
1187
|
-
const stats = this.tools.stats();
|
|
1188
|
-
tail.push(...renderStatusBox({
|
|
1189
|
-
cols: Math.max(24, Math.min(120, cols)),
|
|
1190
|
-
phaseLabel: this.workflowStatus ? `${this.workflowStatus.skill}:${this.workflowStatus.phase}` : this.hudPhase,
|
|
1191
|
-
spinner: verifySpin,
|
|
1192
|
-
activity: this.retryNotice ?? (this.streamingActivity || this.currentActivity()),
|
|
1193
|
-
escHint: true,
|
|
1194
|
-
elapsedMs,
|
|
1195
|
-
stepElapsedMs: this.currentStepStartedAt ? Date.now() - this.currentStepStartedAt : undefined,
|
|
1196
|
-
avgStepMs: stepNow > 0 ? elapsedMs / stepNow : undefined,
|
|
1197
|
-
okCount: stats.ok,
|
|
1198
|
-
failCount: stats.fail,
|
|
1199
|
-
runningCount: stats.running,
|
|
1200
|
-
totalCount: stats.total,
|
|
1201
|
-
mutationGuarded: this.mutationGuarded,
|
|
1202
|
-
unicode: this.unicode,
|
|
1203
|
-
color: this.theme.color,
|
|
1204
|
-
colorLevel,
|
|
1205
|
-
phase,
|
|
1206
|
-
palette: verifying ? [...STATUS_VERIFY_PALETTE] : [grad.from, grad.to],
|
|
1207
|
-
isThinking: true,
|
|
1208
|
-
usage: this.turnUsage,
|
|
1209
|
-
costUsd,
|
|
1210
|
-
subagentActive: this.subagentActive,
|
|
1211
|
-
}));
|
|
1217
|
+
tail.push(...renderStatusBox(this.statusBoxData({ cols: Math.max(24, Math.min(120, cols)), elapsedMs, stepNow, phase, colorLevel, idx })));
|
|
1212
1218
|
}
|
|
1213
1219
|
|
|
1214
1220
|
|
|
@@ -1365,31 +1371,7 @@ export class LaunchTui {
|
|
|
1365
1371
|
// Live status field: unboxed thinking line + compact metrics row. The
|
|
1366
1372
|
// streamed activity is uniform across providers, with the ⟦esc⟧ cancel hint
|
|
1367
1373
|
// right-aligned and no misleading step counter.
|
|
1368
|
-
const
|
|
1369
|
-
for (const line of renderStatusBox({
|
|
1370
|
-
cols: innerWidth,
|
|
1371
|
-
phaseLabel: this.workflowStatus ? `${this.workflowStatus.skill}:${this.workflowStatus.phase}` : this.hudPhase,
|
|
1372
|
-
spinner: this.runningTool && this.theme.color ? chalk.yellow(this.spinner.current()) : this.spinner.current(),
|
|
1373
|
-
activity: this.retryNotice ?? (this.streamingActivity || statusMsg),
|
|
1374
|
-
escHint: true,
|
|
1375
|
-
elapsedMs,
|
|
1376
|
-
stepElapsedMs: this.currentStepStartedAt ? Date.now() - this.currentStepStartedAt : undefined,
|
|
1377
|
-
avgStepMs: stepNow > 0 ? elapsedMs / stepNow : undefined,
|
|
1378
|
-
okCount: stats.ok,
|
|
1379
|
-
failCount: stats.fail,
|
|
1380
|
-
runningCount: stats.running,
|
|
1381
|
-
totalCount: stats.total,
|
|
1382
|
-
mutationGuarded: this.mutationGuarded,
|
|
1383
|
-
unicode: this.unicode,
|
|
1384
|
-
color: this.theme.color,
|
|
1385
|
-
colorLevel,
|
|
1386
|
-
phase,
|
|
1387
|
-
palette: this.runningTool ? [...STATUS_VERIFY_PALETTE] : palette,
|
|
1388
|
-
isThinking: true,
|
|
1389
|
-
usage: this.turnUsage,
|
|
1390
|
-
costUsd,
|
|
1391
|
-
subagentActive: this.subagentActive,
|
|
1392
|
-
})) bottom.push(line);
|
|
1374
|
+
for (const line of renderStatusBox(this.statusBoxData({ cols: innerWidth, elapsedMs, stepNow, phase, colorLevel, idx }))) bottom.push(line);
|
|
1393
1375
|
} else {
|
|
1394
1376
|
// Compact fallback still keeps progress and insight separate: no decorative
|
|
1395
1377
|
// mixed "thinking/status" line, and retry notices never become stream logs.
|
|
@@ -133,11 +133,6 @@ export function complete(line: string, ctx: CompletionContext): CompletionResult
|
|
|
133
133
|
// Completing the command name itself (single token, still typing it).
|
|
134
134
|
if (tokens.length <= 1 && !trailingSpace) {
|
|
135
135
|
const token = tokens[0] ?? "/";
|
|
136
|
-
if (token.toLowerCase().startsWith("/skill:")) {
|
|
137
|
-
const prefix = token.slice("/skill:".length);
|
|
138
|
-
const names = ctx.skillNames ?? skillNames();
|
|
139
|
-
return { completions: dedupeCap(prefixHits(names.map(n => `/skill:${n}`), `/skill:${prefix}`)), token, kind: "command" };
|
|
140
|
-
}
|
|
141
136
|
return { completions: dedupeCap(prefixHits(ctx.slashCommands, token)), token, kind: "command" };
|
|
142
137
|
}
|
|
143
138
|
|
|
@@ -183,8 +178,7 @@ export function complete(line: string, ctx: CompletionContext): CompletionResult
|
|
|
183
178
|
if (argIndex === 2 && (tokens[2]?.toLowerCase() === "maxsteps" || tokens[2]?.toLowerCase() === "steps")) return { completions: [], token, kind: "none" };
|
|
184
179
|
return { completions: [], token, kind: "none" };
|
|
185
180
|
}
|
|
186
|
-
|
|
187
|
-
return argIndex === 0 ? finish(ctx.skillNames ?? skillNames(), "subcommand") : { completions: [], token, kind: "none" };
|
|
181
|
+
|
|
188
182
|
case "/roles": {
|
|
189
183
|
const tiers = ["smol", "slow", "plan"];
|
|
190
184
|
if (argIndex === 0) return finish(tiers, "role");
|
|
@@ -194,7 +188,7 @@ export function complete(line: string, ctx: CompletionContext): CompletionResult
|
|
|
194
188
|
case "/thinking":
|
|
195
189
|
return argIndex === 0 ? finish(ctx.thinkingLevels, "thinking") : { completions: [], token, kind: "none" };
|
|
196
190
|
case "/session":
|
|
197
|
-
return argIndex === 0 ? finish(["info", "delete"], "subcommand") : { completions: [], token, kind: "none" };
|
|
191
|
+
return argIndex === 0 ? finish(["list", "info", "new", "drop", "delete", "rename", "resume"], "subcommand") : { completions: [], token, kind: "none" };
|
|
198
192
|
case "/theme":
|
|
199
193
|
return argIndex === 0 ? finish(listThemes().map(t => t.name), "subcommand") : { completions: [], token, kind: "none" };
|
|
200
194
|
case "/login":
|
|
@@ -46,8 +46,7 @@ export const SLASH_COMMAND_DETAILS: readonly SlashCommandInfo[] = [
|
|
|
46
46
|
{ command: "/diff", usage: "/diff [file]", description: "Render `git diff` with +/- coloring", group: "code" },
|
|
47
47
|
{ command: "/find", usage: "/find <glob>", description: "List files matching a glob", group: "code" },
|
|
48
48
|
{ command: "/search", usage: "/search <pat> [glob]", description: "Search the repo for a pattern", group: "code" },
|
|
49
|
-
|
|
50
|
-
{ command: "/skill:", usage: "/skill:<name> [intent]", description: "Run a workflow skill by GJC-style entrypoint", group: "skills" },
|
|
49
|
+
|
|
51
50
|
{ command: "/sessions", usage: "/sessions", description: "List saved sessions", group: "session" },
|
|
52
51
|
{ command: "/usage", usage: "/usage", description: "Show cumulative token usage for this session", group: "system" },
|
|
53
52
|
{ command: "/context", usage: "/context", description: "Show context token usage breakdown", group: "system" },
|
package/src/util/whats-new.ts
CHANGED
|
@@ -27,6 +27,9 @@ export interface ChangelogSection {
|
|
|
27
27
|
groups: ChangelogGroup[];
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/** Default number of recent releases surfaced as "update news" (whats-new default, post-upgrade notice). Use --all for the full history. */
|
|
31
|
+
export const RECENT_RELEASE_COUNT = 5;
|
|
32
|
+
|
|
30
33
|
const HEADER = /^##\s+\[([^\]]+)\](?:\s*-\s*(\S+))?\s*$/;
|
|
31
34
|
const SUBHEADER = /^###\s+(.+?)\s*$/;
|
|
32
35
|
const BULLET = /^[-*]\s+(.+)$/;
|
|
@@ -266,7 +269,7 @@ export async function consumeLaunchWhatsNew(opts?: WhatsNewRenderOpts): Promise<
|
|
|
266
269
|
const md = await loadBundledChangelog();
|
|
267
270
|
await writeLastSeenVersion(current);
|
|
268
271
|
if (!md) return null;
|
|
269
|
-
const sections = selectNewSections(parseChangelogSections(md), lastSeen, current);
|
|
272
|
+
const sections = selectNewSections(parseChangelogSections(md), lastSeen, current).slice(0, RECENT_RELEASE_COUNT);
|
|
270
273
|
if (sections.length === 0) return null;
|
|
271
274
|
return renderWhatsNew(sections, opts);
|
|
272
275
|
}
|