praxis-agent 0.14.0 → 0.16.0
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.md +5 -2
- package/dist/application/session-service.d.ts +18 -0
- package/dist/application/session-service.js +180 -41
- package/dist/application/subagent-service.d.ts +7 -1
- package/dist/application/subagent-service.js +238 -53
- package/dist/cli/interactive.d.ts +7 -1
- package/dist/cli/interactive.js +77 -3
- package/dist/cli/protocol.d.ts +13 -0
- package/dist/cli/protocol.js +34 -0
- package/dist/cli/tui/claude-command-inventory.d.ts +1 -2
- package/dist/cli/tui/claude-command-inventory.js +1 -6
- package/dist/cli/tui/claude-style.d.ts +4 -1
- package/dist/cli/tui/claude-style.js +8 -3
- package/dist/cli/tui/slash-commands.d.ts +1 -0
- package/dist/cli/tui/slash-commands.js +11 -0
- package/dist/cli/tui/theme.d.ts +2 -0
- package/dist/cli/tui/theme.js +69 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +95 -27
- package/dist/compatibility/claude/agent-color.d.ts +25 -0
- package/dist/compatibility/claude/agent-color.js +79 -0
- package/dist/compatibility/claude/fork.js +5 -1
- package/dist/compatibility/claude/schema.js +13 -1
- package/dist/extensions/claude-extensions.d.ts +19 -1
- package/dist/extensions/claude-extensions.js +126 -1
- package/dist/hooks/claude-hooks.d.ts +3 -1
- package/dist/hooks/claude-hooks.js +14 -0
- package/dist/mcp/claude-mcp-tools.d.ts +18 -0
- package/dist/mcp/claude-mcp-tools.js +46 -0
- package/dist/persistence/claude-transcript-store.js +1 -0
- package/dist/tools/claude-interactive-tools.d.ts +1 -0
- package/dist/tools/claude-interactive-tools.js +3 -0
- package/package.json +2 -1
package/dist/cli/interactive.js
CHANGED
|
@@ -17,6 +17,7 @@ import { createClaudeStatusLineInput, StatusLine } from './tui/status-line.js';
|
|
|
17
17
|
import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
|
|
18
18
|
import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
|
|
19
19
|
import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
|
|
20
|
+
import { agentColorMessage, parseAgentColorInput, } from '../compatibility/claude/agent-color.js';
|
|
20
21
|
import { filterTuiSlashCommands, mergeTuiSlashCommands, slashCommandQuery, } from './tui/slash-commands.js';
|
|
21
22
|
import { createComposerEditor, deleteComposerBackward, deleteComposerForward, deleteComposerToEnd, deleteComposerToStart, deleteComposerWordBackward, insertComposerText, moveComposerCursor, moveComposerCursorByWord, } from './tui/composer-editor.js';
|
|
22
23
|
import { applyMentionReference, fileReferenceAtCursor, filterTuiMentionEntries, loadTuiFileEntries, } from './tui/file-picker.js';
|
|
@@ -247,7 +248,7 @@ const HIDDEN_TUI_SLASH_COMMANDS = new Set([
|
|
|
247
248
|
'update',
|
|
248
249
|
'usage',
|
|
249
250
|
]);
|
|
250
|
-
export function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory = [], signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader = (configRoot, cwd) => loadTuiMemoryFiles({ configRoot, cwd }), memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener = openTuiUrl, releaseNotesLoader = (configRoot) => loadClaudeReleaseNotes({ configRoot }), settingSources, }) {
|
|
251
|
+
export function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory = [], initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader = (configRoot, cwd) => loadTuiMemoryFiles({ configRoot, cwd }), memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener = openTuiUrl, releaseNotesLoader = (configRoot) => loadClaudeReleaseNotes({ configRoot }), settingSources, }) {
|
|
251
252
|
const { exit, suspendTerminal, waitUntilRenderFlush } = useApp();
|
|
252
253
|
const width = useTerminalWidth(terminalWidth);
|
|
253
254
|
const keybindingsRoot = useMemo(() => resolve(keybindingsConfigRoot ??
|
|
@@ -304,6 +305,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
304
305
|
const statusLineSessionId = useRef(resume?.sessionId ?? randomUUID());
|
|
305
306
|
const sessionIdRef = useRef(resume?.sessionId ?? null);
|
|
306
307
|
sessionIdRef.current = sessionId;
|
|
308
|
+
const [sessionColor, setSessionColor] = useState(initialSessionColor);
|
|
307
309
|
const [sessionName, setSessionName] = useState(null);
|
|
308
310
|
const [activeSessionSummary, setActiveSessionSummary] = useState(() => resume?.sessionId
|
|
309
311
|
? initialSessions.find((session) => session.sessionId === resume.sessionId)
|
|
@@ -443,6 +445,17 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
443
445
|
const matchingSlashCommands = useMemo(() => commandQuery === null
|
|
444
446
|
? []
|
|
445
447
|
: filterTuiSlashCommands(allSlashCommands, commandQuery), [allSlashCommands, commandQuery]);
|
|
448
|
+
const commandArgumentHint = useMemo(() => {
|
|
449
|
+
if (shellMode || !input.startsWith('/'))
|
|
450
|
+
return undefined;
|
|
451
|
+
if (inputCursor !== input.length)
|
|
452
|
+
return undefined;
|
|
453
|
+
const match = /^\/(\S+) $/u.exec(input);
|
|
454
|
+
if (!match?.[1])
|
|
455
|
+
return undefined;
|
|
456
|
+
const command = allSlashCommands.find((candidate) => candidate.name === match[1]);
|
|
457
|
+
return command?.argumentHint;
|
|
458
|
+
}, [allSlashCommands, input, inputCursor, shellMode]);
|
|
446
459
|
const commandPaletteVisible = !busy &&
|
|
447
460
|
!permission &&
|
|
448
461
|
!planApproval &&
|
|
@@ -1443,14 +1456,19 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
1443
1456
|
sessionLoadRef.current = loadId;
|
|
1444
1457
|
if (nextSessionId === null) {
|
|
1445
1458
|
setHistory([]);
|
|
1459
|
+
setSessionColor(undefined);
|
|
1446
1460
|
return;
|
|
1447
1461
|
}
|
|
1448
1462
|
const loading = (async () => {
|
|
1449
1463
|
try {
|
|
1450
1464
|
const commands = await service();
|
|
1451
1465
|
const transcript = await commands.transcript?.(nextSessionId);
|
|
1466
|
+
const agentColor = commands.agentColor === undefined
|
|
1467
|
+
? undefined
|
|
1468
|
+
: await commands.agentColor(nextSessionId);
|
|
1452
1469
|
if (sessionLoadRef.current === loadId) {
|
|
1453
1470
|
setHistory(transcript ? [...transcript] : []);
|
|
1471
|
+
setSessionColor(agentColor);
|
|
1454
1472
|
}
|
|
1455
1473
|
}
|
|
1456
1474
|
catch (error) {
|
|
@@ -1816,6 +1834,32 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
1816
1834
|
onTurnChange?.(showing);
|
|
1817
1835
|
void showing.finally(() => onTurnChange?.(null));
|
|
1818
1836
|
};
|
|
1837
|
+
const changeSessionColor = (display, selection) => {
|
|
1838
|
+
appendPromptHistory(display);
|
|
1839
|
+
if (selection.kind === 'color')
|
|
1840
|
+
setSessionColor(selection.color);
|
|
1841
|
+
else if (selection.kind === 'reset')
|
|
1842
|
+
setSessionColor(undefined);
|
|
1843
|
+
const changing = (async () => {
|
|
1844
|
+
try {
|
|
1845
|
+
const activeSessionId = await withLocalCommands(async (commands) => {
|
|
1846
|
+
if (!commands.recordColorUsage) {
|
|
1847
|
+
throw new Error('Session color is unavailable.');
|
|
1848
|
+
}
|
|
1849
|
+
return commands.recordColorUsage(sessionId ?? undefined, selection, display, runtimePreferencesRef.current.permissionMode);
|
|
1850
|
+
});
|
|
1851
|
+
if (activeSessionId)
|
|
1852
|
+
setSessionId(activeSessionId);
|
|
1853
|
+
append({ kind: 'user', text: display });
|
|
1854
|
+
append({ kind: 'local-result', text: agentColorMessage(selection) });
|
|
1855
|
+
}
|
|
1856
|
+
catch (error) {
|
|
1857
|
+
warn(error);
|
|
1858
|
+
}
|
|
1859
|
+
})();
|
|
1860
|
+
onTurnChange?.(changing);
|
|
1861
|
+
void changing.finally(() => onTurnChange?.(null));
|
|
1862
|
+
};
|
|
1819
1863
|
const backgroundSession = () => {
|
|
1820
1864
|
appendPromptHistory('/background');
|
|
1821
1865
|
const backgrounding = (async () => {
|
|
@@ -4794,6 +4838,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
4794
4838
|
if (!prompt || prompt === '!')
|
|
4795
4839
|
return;
|
|
4796
4840
|
const copyCommand = /^\/copy(?:\s+(\d+))?$/u.exec(prompt);
|
|
4841
|
+
const colorCommand = /^\/color(?:\s+([\s\S]+))?$/u.exec(prompt);
|
|
4797
4842
|
const sandboxCommand = /^\/sandbox(?:\s+([\s\S]+))?$/u.exec(prompt);
|
|
4798
4843
|
const tuiCommand = /^\/tui(?:\s+(default|fullscreen))?$/u.exec(prompt);
|
|
4799
4844
|
const renameCommand = /^\/rename(?:\s+(.+))?$/u.exec(prompt);
|
|
@@ -4808,12 +4853,14 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
4808
4853
|
else if (prompt === '/new') {
|
|
4809
4854
|
statusLineSessionId.current = randomUUID();
|
|
4810
4855
|
setSessionId(null);
|
|
4856
|
+
setSessionColor(undefined);
|
|
4811
4857
|
setPendingFork(false);
|
|
4812
4858
|
append({ kind: 'notice', text: 'Started a new session.' });
|
|
4813
4859
|
}
|
|
4814
4860
|
else if (prompt === '/clear') {
|
|
4815
4861
|
statusLineSessionId.current = randomUUID();
|
|
4816
4862
|
setSessionId(null);
|
|
4863
|
+
setSessionColor(undefined);
|
|
4817
4864
|
setPendingFork(false);
|
|
4818
4865
|
setHistory([]);
|
|
4819
4866
|
setUsage(undefined);
|
|
@@ -4944,6 +4991,16 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
4944
4991
|
else if (prompt === '/add-dir') {
|
|
4945
4992
|
openWorkspaceDirectoryInput();
|
|
4946
4993
|
}
|
|
4994
|
+
else if (prompt === '/agents') {
|
|
4995
|
+
append({ kind: 'user', text: prompt });
|
|
4996
|
+
append({
|
|
4997
|
+
kind: 'local-result',
|
|
4998
|
+
text: 'The /agents wizard has been removed.\n\nAsk Claude to create or update subagents for you (e.g. "create a code-reviewer subagent that ..."),\nor edit the files directly:\n • .claude/agents/ (this project)\n • ~/.claude/agents/ (all projects)\n\nDocs: https://code.claude.com/docs/en/sub-agents',
|
|
4999
|
+
});
|
|
5000
|
+
}
|
|
5001
|
+
else if (colorCommand) {
|
|
5002
|
+
changeSessionColor(prompt, parseAgentColorInput(colorCommand[1] ?? ''));
|
|
5003
|
+
}
|
|
4947
5004
|
else if (btwCommand) {
|
|
4948
5005
|
const sideQuestion = btwCommand[1]?.trim();
|
|
4949
5006
|
if (sideQuestion)
|
|
@@ -5413,7 +5470,9 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
5413
5470
|
label: 'Save to file',
|
|
5414
5471
|
description: 'Save the conversation to a file in the current directory',
|
|
5415
5472
|
},
|
|
5416
|
-
], selectedIndex: menu.selectedIndex, footer: "Esc to cancel", width: width, screenReader: axScreenReader })) : menu.kind === 'copy' ? (_jsx(SelectionMenu, { title: "Copy", description: "Select content to copy:", options: menu.candidates, selectedIndex: menu.selectedIndex, footer: "Enter to copy \u00B7 w to write to /tmp/claude \u00B7 Esc to cancel", width: width, screenReader: axScreenReader })) : null) : (_jsxs(_Fragment, { children: [commandPaletteVisible ? (_jsx(CommandPalette, { commands: matchingSlashCommands, selectedIndex: selectedSlashCommandIndex, width: width, screenReader: axScreenReader })) : null, filePickerVisible ? (_jsx(MentionPicker, { entries: matchingMentionEntries, selectedIndex: selectedFileIndex, width: width, screenReader: axScreenReader })) : null, exitConfirmation ? (_jsx(Text, { color: activePalette.warning, children: "Press Ctrl-C again to exit" })) : null, _jsx(Composer, { input: shellMode ? input.slice(1) : input, cursor: shellMode ? Math.max(0, inputCursor - 1) : inputCursor, shellMode: shellMode,
|
|
5473
|
+
], selectedIndex: menu.selectedIndex, footer: "Esc to cancel", width: width, screenReader: axScreenReader })) : menu.kind === 'copy' ? (_jsx(SelectionMenu, { title: "Copy", description: "Select content to copy:", options: menu.candidates, selectedIndex: menu.selectedIndex, footer: "Enter to copy \u00B7 w to write to /tmp/claude \u00B7 Esc to cancel", width: width, screenReader: axScreenReader })) : null) : (_jsxs(_Fragment, { children: [commandPaletteVisible ? (_jsx(CommandPalette, { commands: matchingSlashCommands, selectedIndex: selectedSlashCommandIndex, width: width, screenReader: axScreenReader })) : null, filePickerVisible ? (_jsx(MentionPicker, { entries: matchingMentionEntries, selectedIndex: selectedFileIndex, width: width, screenReader: axScreenReader })) : null, exitConfirmation ? (_jsx(Text, { color: activePalette.warning, children: "Press Ctrl-C again to exit" })) : null, _jsx(Composer, { input: shellMode ? input.slice(1) : input, cursor: shellMode ? Math.max(0, inputCursor - 1) : inputCursor, shellMode: shellMode, ...(sessionColor === undefined ? {} : { sessionColor }), ...(commandArgumentHint === undefined
|
|
5474
|
+
? {}
|
|
5475
|
+
: { commandArgumentHint }), busy: busy, clipboardBusy: clipboardBusy, status: status, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), width: width, screenReader: axScreenReader, hasThinking: hasDetailedTranscript, thinkingExpanded: thinkingExpanded, reduceMotion: runtimeSettings.reduceMotion, progressBar: runtimeSettings.progressBar, ...(runtimeSettings.turnDuration
|
|
5417
5476
|
? (() => {
|
|
5418
5477
|
const duration = formatTurnDuration(turnDuration);
|
|
5419
5478
|
return duration === undefined
|
|
@@ -5466,6 +5525,7 @@ export async function runInteractive(options) {
|
|
|
5466
5525
|
: controller.signal;
|
|
5467
5526
|
let currentResume = options.resume;
|
|
5468
5527
|
let currentInitialPrompt = options.initialPrompt;
|
|
5528
|
+
let initialAgentPromptResolved = false;
|
|
5469
5529
|
let currentRuntimeSettings = options.runtimeSettings ?? (await loadRuntimeSettings());
|
|
5470
5530
|
let currentRenderer = currentRuntimeSettings.tui;
|
|
5471
5531
|
let rendererChange = null;
|
|
@@ -5516,12 +5576,26 @@ export async function runInteractive(options) {
|
|
|
5516
5576
|
const resume = canonicalResumeSession
|
|
5517
5577
|
? { ...currentResume, sessionId: canonicalResumeSession.sessionId }
|
|
5518
5578
|
: currentResume;
|
|
5579
|
+
if (!initialAgentPromptResolved) {
|
|
5580
|
+
const agentInitialPrompt = resume === undefined ? listing.initialAgentPrompt?.() : undefined;
|
|
5581
|
+
if (agentInitialPrompt) {
|
|
5582
|
+
currentInitialPrompt = currentInitialPrompt
|
|
5583
|
+
? `${agentInitialPrompt}\n\n${currentInitialPrompt}`
|
|
5584
|
+
: agentInitialPrompt;
|
|
5585
|
+
}
|
|
5586
|
+
initialAgentPromptResolved = true;
|
|
5587
|
+
}
|
|
5519
5588
|
let initialHistory = [];
|
|
5589
|
+
let initialSessionColor;
|
|
5520
5590
|
try {
|
|
5521
5591
|
initialHistory =
|
|
5522
5592
|
resume?.sessionId === undefined || listing.transcript === undefined
|
|
5523
5593
|
? []
|
|
5524
5594
|
: await listing.transcript(resume.sessionId);
|
|
5595
|
+
initialSessionColor =
|
|
5596
|
+
resume?.sessionId === undefined || listing.agentColor === undefined
|
|
5597
|
+
? undefined
|
|
5598
|
+
: await listing.agentColor(resume.sessionId);
|
|
5525
5599
|
}
|
|
5526
5600
|
catch (error) {
|
|
5527
5601
|
try {
|
|
@@ -5546,7 +5620,7 @@ export async function runInteractive(options) {
|
|
|
5546
5620
|
let cleanup = null;
|
|
5547
5621
|
let backgrounded;
|
|
5548
5622
|
rendererChange = null;
|
|
5549
|
-
const instance = render(_jsx(InteractiveApp, { factory: options.factory, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: history, runtimeSettings: currentRuntimeSettings, ...(options.settingSources === undefined
|
|
5623
|
+
const instance = render(_jsx(InteractiveApp, { factory: options.factory, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: history, ...(initialSessionColor === undefined ? {} : { initialSessionColor }), runtimeSettings: currentRuntimeSettings, ...(options.settingSources === undefined
|
|
5550
5624
|
? {}
|
|
5551
5625
|
: { settingSources: options.settingSources }), initialThemeSettings: initialThemeSettings, ...(initialThemeLoadError === undefined
|
|
5552
5626
|
? {}
|
package/dist/cli/protocol.d.ts
CHANGED
|
@@ -206,6 +206,13 @@ export interface ProtocolResult {
|
|
|
206
206
|
}
|
|
207
207
|
export declare function createSuccessResult(result: ProtocolResult, info: CliRuntimeInfo, startedAt: number, modelTurns: number): Record<string, unknown>;
|
|
208
208
|
export declare function createErrorResult(message: string, sessionId: string, startedAt: number, modelTurns: number): Record<string, unknown>;
|
|
209
|
+
/**
|
|
210
|
+
* Returns the argument text of a headless `/color` prompt, or undefined when
|
|
211
|
+
* the prompt is not a bare `/color` command. A trailing space matches with an
|
|
212
|
+
* empty argument (random color), while `/colorblue` and `/colorful` are not
|
|
213
|
+
* the command.
|
|
214
|
+
*/
|
|
215
|
+
export declare function matchHeadlessColorCommand(prompt: string): string | undefined;
|
|
209
216
|
export declare function parseCliInvocation(argv: readonly string[]): CliInvocation;
|
|
210
217
|
export declare function readStreamJsonMessages(input: AsyncIterable<string | Uint8Array>): AsyncGenerator<StreamJsonMessage>;
|
|
211
218
|
export declare function readStreamUserMessages(input: AsyncIterable<string | Uint8Array>): AsyncGenerator<StreamUserMessage>;
|
|
@@ -232,6 +239,12 @@ export declare class StreamJsonOutput {
|
|
|
232
239
|
constructor(write: (value: unknown) => void, info: CliRuntimeInfo, sessionId: string, includePartialMessages: boolean, includeHookEvents?: boolean);
|
|
233
240
|
init(): void;
|
|
234
241
|
replayUser(message: StreamUserMessage['message']): void;
|
|
242
|
+
/**
|
|
243
|
+
* Emits the synthetic assistant message for a provider-free local command
|
|
244
|
+
* (e.g. /color). It must not touch the turn state, so the following result
|
|
245
|
+
* keeps num_turns 0 and no stream_event records are produced.
|
|
246
|
+
*/
|
|
247
|
+
syntheticAssistant(text: string): void;
|
|
235
248
|
controlRequest(request: {
|
|
236
249
|
request_id: string;
|
|
237
250
|
request: Record<string, unknown>;
|
package/dist/cli/protocol.js
CHANGED
|
@@ -80,6 +80,18 @@ export function createErrorResult(message, sessionId, startedAt, modelTurns) {
|
|
|
80
80
|
uuid: randomUUID(),
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns the argument text of a headless `/color` prompt, or undefined when
|
|
85
|
+
* the prompt is not a bare `/color` command. A trailing space matches with an
|
|
86
|
+
* empty argument (random color), while `/colorblue` and `/colorful` are not
|
|
87
|
+
* the command.
|
|
88
|
+
*/
|
|
89
|
+
export function matchHeadlessColorCommand(prompt) {
|
|
90
|
+
const match = /^\/color(?:\s+([\s\S]*))?$/u.exec(prompt);
|
|
91
|
+
if (match === null)
|
|
92
|
+
return undefined;
|
|
93
|
+
return match[1] ?? '';
|
|
94
|
+
}
|
|
83
95
|
const INPUT_FORMATS = ['text', 'stream-json'];
|
|
84
96
|
const OUTPUT_FORMATS = ['text', 'json', 'stream-json'];
|
|
85
97
|
const PERMISSION_MODES = [
|
|
@@ -1594,6 +1606,28 @@ export class StreamJsonOutput {
|
|
|
1594
1606
|
replayUser(message) {
|
|
1595
1607
|
this.write({ type: 'user', message, session_id: this.sessionId });
|
|
1596
1608
|
}
|
|
1609
|
+
/**
|
|
1610
|
+
* Emits the synthetic assistant message for a provider-free local command
|
|
1611
|
+
* (e.g. /color). It must not touch the turn state, so the following result
|
|
1612
|
+
* keeps num_turns 0 and no stream_event records are produced.
|
|
1613
|
+
*/
|
|
1614
|
+
syntheticAssistant(text) {
|
|
1615
|
+
this.write({
|
|
1616
|
+
type: 'assistant',
|
|
1617
|
+
message: {
|
|
1618
|
+
id: randomUUID(),
|
|
1619
|
+
type: 'message',
|
|
1620
|
+
role: 'assistant',
|
|
1621
|
+
model: '<synthetic>',
|
|
1622
|
+
content: [{ type: 'text', text }],
|
|
1623
|
+
stop_reason: 'stop_sequence',
|
|
1624
|
+
stop_sequence: '',
|
|
1625
|
+
usage: { input_tokens: 0, output_tokens: 0 },
|
|
1626
|
+
},
|
|
1627
|
+
parent_tool_use_id: null,
|
|
1628
|
+
session_id: this.sessionId,
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1597
1631
|
controlRequest(request) {
|
|
1598
1632
|
this.write({ type: 'control_request', ...request });
|
|
1599
1633
|
}
|
|
@@ -38,9 +38,8 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
|
|
|
38
38
|
readonly visibility: "visible";
|
|
39
39
|
}, {
|
|
40
40
|
readonly name: "color";
|
|
41
|
-
readonly disposition: "
|
|
41
|
+
readonly disposition: "included";
|
|
42
42
|
readonly visibility: "visible";
|
|
43
|
-
readonly reason: "The dedicated /color contract is required; /theme is not a substitute.";
|
|
44
43
|
}, {
|
|
45
44
|
readonly name: "compact";
|
|
46
45
|
readonly disposition: "included";
|
|
@@ -20,12 +20,7 @@ export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
|
|
|
20
20
|
reason: 'The CLI-driven Chrome integration is required parity work and is not implemented yet.',
|
|
21
21
|
},
|
|
22
22
|
{ name: 'clear', disposition: 'included', visibility: 'visible' },
|
|
23
|
-
{
|
|
24
|
-
name: 'color',
|
|
25
|
-
disposition: 'deferred',
|
|
26
|
-
visibility: 'visible',
|
|
27
|
-
reason: 'The dedicated /color contract is required; /theme is not a substitute.',
|
|
28
|
-
},
|
|
23
|
+
{ name: 'color', disposition: 'included', visibility: 'visible' },
|
|
29
24
|
{ name: 'compact', disposition: 'included', visibility: 'visible' },
|
|
30
25
|
{ name: 'config', disposition: 'included', visibility: 'visible' },
|
|
31
26
|
{ name: 'copy', disposition: 'included', visibility: 'visible' },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ModelToolCall, ModelUsage } from '../../core/runtime.js';
|
|
2
|
+
import type { AgentColorName } from '../../compatibility/claude/agent-color.js';
|
|
2
3
|
import type { TuiFileEntry, TuiMentionEntry } from './file-picker.js';
|
|
3
4
|
import { type TuiDiffSnapshot } from './git-diff.js';
|
|
4
5
|
import { type TuiHookConfiguration } from './hook-settings.js';
|
|
@@ -230,7 +231,7 @@ export declare function ModelMenu({ options, effort, selectedIndex, width, scree
|
|
|
230
231
|
export declare function ExternalEditorWait({ screenReader, }: {
|
|
231
232
|
screenReader: boolean;
|
|
232
233
|
}): import("react").JSX.Element;
|
|
233
|
-
export declare function Composer({ input, cursor, busy, clipboardBusy, status, display, usage, costUsd, width, screenReader, hasThinking, thinkingExpanded, shortcutsVisible, shellMode, footerMessage, reduceMotion, progressBar, turnDuration, editorMode, prStatus, }: {
|
|
234
|
+
export declare function Composer({ input, cursor, busy, clipboardBusy, status, display, usage, costUsd, width, screenReader, hasThinking, thinkingExpanded, shortcutsVisible, shellMode, footerMessage, reduceMotion, progressBar, turnDuration, editorMode, prStatus, sessionColor, commandArgumentHint, }: {
|
|
234
235
|
input: string;
|
|
235
236
|
cursor?: number;
|
|
236
237
|
busy: boolean;
|
|
@@ -254,6 +255,8 @@ export declare function Composer({ input, cursor, busy, clipboardBusy, status, d
|
|
|
254
255
|
turnDuration?: string;
|
|
255
256
|
editorMode?: 'normal' | 'vim';
|
|
256
257
|
prStatus?: string;
|
|
258
|
+
sessionColor?: AgentColorName;
|
|
259
|
+
commandArgumentHint?: string;
|
|
257
260
|
}): import("react").JSX.Element;
|
|
258
261
|
export declare function DialogFrame({ title, children, screenReader, }: {
|
|
259
262
|
title: string;
|
|
@@ -659,7 +659,7 @@ export function ExternalEditorWait({ screenReader, }) {
|
|
|
659
659
|
return _jsx(Text, { children: "External editor open. Save and close it to continue." });
|
|
660
660
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }), _jsx(Text, { children: "Save and close editor to continue..." }), _jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" })] }));
|
|
661
661
|
}
|
|
662
|
-
export function Composer({ input, cursor, busy, clipboardBusy = false, status, display, usage, costUsd, width, screenReader, hasThinking = false, thinkingExpanded = false, shortcutsVisible = false, shellMode = false, footerMessage, reduceMotion = false, progressBar = true, turnDuration, editorMode = 'normal', prStatus, }) {
|
|
662
|
+
export function Composer({ input, cursor, busy, clipboardBusy = false, status, display, usage, costUsd, width, screenReader, hasThinking = false, thinkingExpanded = false, shortcutsVisible = false, shellMode = false, footerMessage, reduceMotion = false, progressBar = true, turnDuration, editorMode = 'normal', prStatus, sessionColor, commandArgumentHint, }) {
|
|
663
663
|
const palette = useTuiPalette();
|
|
664
664
|
const [spinnerIndex, setSpinnerIndex] = useState(0);
|
|
665
665
|
useEffect(() => {
|
|
@@ -677,15 +677,20 @@ export function Composer({ input, cursor, busy, clipboardBusy = false, status, d
|
|
|
677
677
|
? `Shell command: ${input}`
|
|
678
678
|
: `Prompt: ${input}` }));
|
|
679
679
|
const line = '─'.repeat(Math.max(12, Math.min(100, width)));
|
|
680
|
+
const separatorColor = sessionColor === undefined ? undefined : palette.sessionColors[sessionColor];
|
|
680
681
|
return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [usage ? (_jsxs(Text, { dimColor: true, children: ["Context \u00B7 ", usage.inputTokens + usage.outputTokens, " tokens", display.contextWindowTokens
|
|
681
682
|
? ` / ${display.contextWindowTokens} (${Math.min(100, Math.round(((usage.inputTokens + usage.outputTokens) /
|
|
682
683
|
display.contextWindowTokens) *
|
|
683
684
|
100))}%)`
|
|
684
685
|
: '', usage.cacheReadInputTokens
|
|
685
686
|
? ` · ${usage.cacheReadInputTokens} cached`
|
|
686
|
-
: '', costUsd === undefined ? '' : ` · $${costUsd.toFixed(6)}`] })) : null, _jsx(Text, {
|
|
687
|
+
: '', costUsd === undefined ? '' : ` · $${costUsd.toFixed(6)}`] })) : null, _jsx(Text, { ...(separatorColor === undefined
|
|
688
|
+
? { dimColor: true }
|
|
689
|
+
: { color: separatorColor }), children: line }), clipboardBusy ? (_jsx(Text, { children: "Pasting\u2026" })) : busy ? (_jsxs(Text, { children: [progressBar ? (_jsx(Text, { color: palette.accent, children: reduceMotion ? '•' : SPINNER[spinnerIndex] })) : null, ' ', status, "\u2026 ", _jsx(Text, { dimColor: true, children: "\u00B7 esc to interrupt" })] })) : (_jsxs(Text, { children: [_jsx(Text, { ...(shellMode ? {} : { color: palette.brand }), bold: true, children: shellMode ? '! ' : '❯ ' }), input ? (_jsxs(Text, { children: [_jsx(ComposerInput, { cursor: cursor ?? Array.from(input).length, input: input }), commandArgumentHint ? (_jsxs(Text, { dimColor: true, children: [input.endsWith(' ') ? '' : ' ', commandArgumentHint] })) : null] })) : (_jsx(Text, { dimColor: true, children: shellMode
|
|
687
690
|
? 'Enter a shell command'
|
|
688
|
-
: 'Try "review this project"' }))] })), _jsx(Text, {
|
|
691
|
+
: 'Try "review this project"' }))] })), _jsx(Text, { ...(separatorColor === undefined
|
|
692
|
+
? { dimColor: true }
|
|
693
|
+
: { color: separatorColor }), children: line }), shortcutsVisible ? (_jsx(ShortcutHelp, { width: width })) : (_jsxs(Box, { width: Math.min(100, width), children: [_jsx(Text, { dimColor: true, children: shellMode ? ('! for bash mode') : (_jsxs(_Fragment, { children: ["\u23F5\u23F5 ", permissionLabel(display.permissionMode), " \u00B7", ' ', busy ? 'esc to interrupt' : '? for shortcuts', " \u00B7 \u2190 for agents", hasThinking
|
|
689
694
|
? ` · ctrl+o ${thinkingExpanded ? 'collapse' : 'expand'}`
|
|
690
695
|
: ''] })) }), _jsx(Box, { flexGrow: 1 }), footerMessage ? (footerMessage.isError ? (_jsx(Text, { color: palette.error, children: footerMessage.text })) : (_jsx(Text, { dimColor: true, children: footerMessage.text }))) : prStatus ? (_jsx(Text, { dimColor: true, children: prStatus })) : turnDuration ? (_jsxs(Text, { dimColor: true, children: ["Cooked for ", turnDuration] })) : display.effort ? (_jsxs(Text, { children: [_jsxs(Text, { color: palette.accent, children: ["\u25CF ", display.effort] }), _jsxs(Text, { dimColor: true, children: [' ', "\u00B7 ", editorMode === 'vim' ? 'vim' : '/effort'] })] })) : null] }))] }));
|
|
691
696
|
}
|
|
@@ -4,6 +4,11 @@ export const BUILTIN_TUI_SLASH_COMMANDS = [
|
|
|
4
4
|
description: 'Add a new working directory',
|
|
5
5
|
source: 'builtin',
|
|
6
6
|
},
|
|
7
|
+
{
|
|
8
|
+
name: 'agents',
|
|
9
|
+
description: '(removed) Ask Claude to create/manage subagents, or edit .claude/agents/',
|
|
10
|
+
source: 'builtin',
|
|
11
|
+
},
|
|
7
12
|
{
|
|
8
13
|
name: 'branch',
|
|
9
14
|
description: 'Create a branch of the current conversation at this point',
|
|
@@ -29,6 +34,12 @@ export const BUILTIN_TUI_SLASH_COMMANDS = [
|
|
|
29
34
|
description: 'Start a new session with empty context; previous session stays on disk (resumable with /resume)',
|
|
30
35
|
source: 'builtin',
|
|
31
36
|
},
|
|
37
|
+
{
|
|
38
|
+
name: 'color',
|
|
39
|
+
description: 'Set the prompt bar color for this session',
|
|
40
|
+
argumentHint: '[red|blue|green|yellow|purple|orange|pink|cyan|default]',
|
|
41
|
+
source: 'builtin',
|
|
42
|
+
},
|
|
32
43
|
{
|
|
33
44
|
name: 'compact',
|
|
34
45
|
description: 'Clear conversation history but keep a summary in context',
|
package/dist/cli/tui/theme.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import type { AgentColorName } from '../../compatibility/claude/agent-color.js';
|
|
2
3
|
import type { TuiCustomTheme } from './custom-themes.js';
|
|
3
4
|
export declare const TUI_THEMES: readonly ["auto", "dark", "light", "dark-daltonized", "light-daltonized", "dark-ansi", "light-ansi"];
|
|
4
5
|
export type TuiTheme = (typeof TUI_THEMES)[number];
|
|
@@ -31,6 +32,7 @@ export interface TuiPalette {
|
|
|
31
32
|
warning: string;
|
|
32
33
|
muted: string;
|
|
33
34
|
selectionText: string;
|
|
35
|
+
sessionColors: Readonly<Record<AgentColorName, string>>;
|
|
34
36
|
syntaxTheme: 'Monokai Extended' | 'GitHub' | 'ansi';
|
|
35
37
|
syntax: TuiSyntaxPalette;
|
|
36
38
|
}
|
package/dist/cli/tui/theme.js
CHANGED
|
@@ -44,6 +44,56 @@ const LIGHT_SYNTAX = {
|
|
|
44
44
|
addedBackground: '#d7ffd7',
|
|
45
45
|
addedHighlight: '#afffaf',
|
|
46
46
|
};
|
|
47
|
+
const NORMAL_SESSION_COLORS = {
|
|
48
|
+
red: '#dc2626',
|
|
49
|
+
blue: '#2563eb',
|
|
50
|
+
green: '#16a34a',
|
|
51
|
+
yellow: '#ca8a04',
|
|
52
|
+
purple: '#9333ea',
|
|
53
|
+
orange: '#ea580c',
|
|
54
|
+
pink: '#db2777',
|
|
55
|
+
cyan: '#0891b2',
|
|
56
|
+
};
|
|
57
|
+
const LIGHT_ANSI_SESSION_COLORS = {
|
|
58
|
+
red: 'red',
|
|
59
|
+
blue: 'blue',
|
|
60
|
+
green: 'green',
|
|
61
|
+
yellow: 'yellow',
|
|
62
|
+
purple: 'magenta',
|
|
63
|
+
orange: 'redBright',
|
|
64
|
+
pink: 'magentaBright',
|
|
65
|
+
cyan: 'cyan',
|
|
66
|
+
};
|
|
67
|
+
const DARK_ANSI_SESSION_COLORS = {
|
|
68
|
+
red: 'redBright',
|
|
69
|
+
blue: 'blueBright',
|
|
70
|
+
green: 'greenBright',
|
|
71
|
+
yellow: 'yellowBright',
|
|
72
|
+
purple: 'magentaBright',
|
|
73
|
+
orange: 'redBright',
|
|
74
|
+
pink: 'magentaBright',
|
|
75
|
+
cyan: 'cyanBright',
|
|
76
|
+
};
|
|
77
|
+
const LIGHT_DALTONIZED_SESSION_COLORS = {
|
|
78
|
+
red: '#cc0000',
|
|
79
|
+
blue: '#0066cc',
|
|
80
|
+
green: '#00cc00',
|
|
81
|
+
yellow: '#ffcc00',
|
|
82
|
+
purple: '#800080',
|
|
83
|
+
orange: '#ff8000',
|
|
84
|
+
pink: '#ff66b2',
|
|
85
|
+
cyan: '#00b2b2',
|
|
86
|
+
};
|
|
87
|
+
const DARK_DALTONIZED_SESSION_COLORS = {
|
|
88
|
+
red: '#ff6666',
|
|
89
|
+
blue: '#66b2ff',
|
|
90
|
+
green: '#66ff66',
|
|
91
|
+
yellow: '#ffff66',
|
|
92
|
+
purple: '#b266ff',
|
|
93
|
+
orange: '#ffb266',
|
|
94
|
+
pink: '#ff99cc',
|
|
95
|
+
cyan: '#66cccc',
|
|
96
|
+
};
|
|
47
97
|
function automaticDark(environment) {
|
|
48
98
|
const background = environment.COLORFGBG?.split(';').at(-1);
|
|
49
99
|
return background === undefined || Number(background) < 8;
|
|
@@ -195,11 +245,30 @@ export function tuiPalette(profile, syntaxHighlightingDisabled = false, environm
|
|
|
195
245
|
? adaptSyntaxColors(syntaxBase, adaptAutoColor)
|
|
196
246
|
: syntaxBase;
|
|
197
247
|
const autoColor = (color) => profile === 'auto' ? adaptAutoColor(color) : color;
|
|
248
|
+
const sessionColors = ansiOnly
|
|
249
|
+
? dark
|
|
250
|
+
? DARK_ANSI_SESSION_COLORS
|
|
251
|
+
: LIGHT_ANSI_SESSION_COLORS
|
|
252
|
+
: daltonized
|
|
253
|
+
? dark
|
|
254
|
+
? DARK_DALTONIZED_SESSION_COLORS
|
|
255
|
+
: LIGHT_DALTONIZED_SESSION_COLORS
|
|
256
|
+
: {
|
|
257
|
+
red: autoColor(NORMAL_SESSION_COLORS.red),
|
|
258
|
+
blue: autoColor(NORMAL_SESSION_COLORS.blue),
|
|
259
|
+
green: autoColor(NORMAL_SESSION_COLORS.green),
|
|
260
|
+
yellow: autoColor(NORMAL_SESSION_COLORS.yellow),
|
|
261
|
+
purple: autoColor(NORMAL_SESSION_COLORS.purple),
|
|
262
|
+
orange: autoColor(NORMAL_SESSION_COLORS.orange),
|
|
263
|
+
pink: autoColor(NORMAL_SESSION_COLORS.pink),
|
|
264
|
+
cyan: autoColor(NORMAL_SESSION_COLORS.cyan),
|
|
265
|
+
};
|
|
198
266
|
const palette = {
|
|
199
267
|
profile,
|
|
200
268
|
dark,
|
|
201
269
|
ansiOnly,
|
|
202
270
|
syntaxHighlightingDisabled,
|
|
271
|
+
sessionColors,
|
|
203
272
|
brand: ansiOnly ? 'redBright' : autoColor('#D97757'),
|
|
204
273
|
accent: ansiOnly
|
|
205
274
|
? 'magentaBright'
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type ForkResult, type ManualCompactResult, type ManualCompactSelection, type RewindPoint, type SessionForkCheckpoint, type SessionInspection, type SessionRunResult, type SessionSummary, type SideQuestionForkResult, type SideQuestionResult } from './application/session-service.js';
|
|
3
|
+
import { type AgentColorName, type AgentColorSelection } from './compatibility/claude/agent-color.js';
|
|
3
4
|
import type { ClaudeDisplayTranscriptItem } from './compatibility/claude/projection.js';
|
|
4
5
|
import { type ModelDocument, type ModelImage, type ModelProvider, type ModelToolCall, type PermissionApproval, type PermissionDecision, type ToolRegistry, type RuntimeEventSink } from './core/runtime.js';
|
|
5
6
|
import type { InteractiveResumeOptions, InteractiveServiceFactory } from './cli/interactive.js';
|
|
@@ -40,6 +41,10 @@ interface SessionCommands {
|
|
|
40
41
|
retryRecentlyDenied?(sessionId: string, display: string, signal?: AbortSignal): Promise<SessionRunResult>;
|
|
41
42
|
answerSideQuestion?(sessionId: string | undefined, question: string, signal?: AbortSignal, onDelta?: (delta: string) => void, permissionMode?: ClaudePermissionMode): Promise<SideQuestionResult>;
|
|
42
43
|
recordBtwUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
44
|
+
recordColorUsage?(sessionId: string | undefined, selection: AgentColorSelection, display: string, permissionMode?: ClaudePermissionMode, options?: {
|
|
45
|
+
createSession?: boolean;
|
|
46
|
+
}): Promise<string>;
|
|
47
|
+
agentColor?(sessionId: string): Promise<AgentColorName | undefined>;
|
|
43
48
|
recordBackgroundUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
44
49
|
recordBackgroundLaunch?(sessionId: string): Promise<SessionForkCheckpoint>;
|
|
45
50
|
forkSideQuestion?(sessionId: string, question: string, signal?: AbortSignal): Promise<SideQuestionForkResult>;
|
|
@@ -67,6 +72,7 @@ interface SessionCommands {
|
|
|
67
72
|
mcpTools?(name: string): Promise<readonly ClaudeMcpToolInspection[]>;
|
|
68
73
|
close?(): Promise<void>;
|
|
69
74
|
runtimeInfo?(): CliRuntimeInfo;
|
|
75
|
+
initialAgentPrompt?(): string | undefined;
|
|
70
76
|
agentDefinitions?(): readonly {
|
|
71
77
|
name: string;
|
|
72
78
|
description: string;
|