praxis-agent 0.15.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 +2 -1
- package/dist/application/session-service.d.ts +6 -0
- package/dist/application/session-service.js +62 -3
- package/dist/cli/interactive.d.ts +6 -1
- package/dist/cli/interactive.js +60 -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 +6 -0
- package/dist/cli/tui/theme.d.ts +2 -0
- package/dist/cli/tui/theme.js +69 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.js +59 -20
- 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/persistence/claude-transcript-store.js +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -95,7 +95,8 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
|
|
|
95
95
|
tabs, `/sandbox` mode/dependency/override/config controls, local cached
|
|
96
96
|
`/release-notes`, Claude-compatible `/statusline` command execution and setup
|
|
97
97
|
agent, source-aligned `/init` project-instruction onboarding with its enhanced
|
|
98
|
-
skills/hooks flow,
|
|
98
|
+
skills/hooks flow, provider-free per-session `/color` prompt-bar styling,
|
|
99
|
+
`/mcp`, `/memory` shared instruction and auto-memory
|
|
99
100
|
access, and live extension-reload controls,
|
|
100
101
|
cursor/history composer, per-session model/effort/permission controls,
|
|
101
102
|
context/status/skill/task dashboards, prompt stash and continuation shortcuts,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AgentColorName, type AgentColorSelection } from '../compatibility/claude/agent-color.js';
|
|
1
2
|
import type { ClaudeConditionalRuleResolver } from '../compatibility/claude/context.js';
|
|
2
3
|
import { type ClaudeFileResource, type ClaudeFileResourceConfig } from '../compatibility/claude/file-resources.js';
|
|
3
4
|
import { type ClaudeDisplayTranscriptItem } from '../compatibility/claude/projection.js';
|
|
@@ -182,6 +183,7 @@ export declare class ClaudeSessionService {
|
|
|
182
183
|
sessionNameSuggestion(sessionId: string, signal?: AbortSignal): Promise<string | null>;
|
|
183
184
|
sessions(): Promise<SessionSummary[]>;
|
|
184
185
|
inspect(sessionId: string): Promise<SessionInspection>;
|
|
186
|
+
readEffectiveAgentColor(sessionId: string): Promise<AgentColorName | undefined>;
|
|
185
187
|
export(sessionId: string): Promise<Buffer>;
|
|
186
188
|
transcript(sessionId: string, resumeSessionAt?: string): Promise<ClaudeDisplayTranscriptItem[]>;
|
|
187
189
|
rename(sessionId: string, name: string): Promise<void>;
|
|
@@ -190,6 +192,9 @@ export declare class ClaudeSessionService {
|
|
|
190
192
|
approveRecentlyDenied(sessionId: string, display: string): Promise<void>;
|
|
191
193
|
retryRecentlyDenied(sessionId: string, display: string, signal?: AbortSignal): Promise<SessionRunResult>;
|
|
192
194
|
recordBtwUsage(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
195
|
+
recordColorUsage(sessionId: string | undefined, selection: AgentColorSelection, display: string, permissionMode?: ClaudePermissionMode, options?: {
|
|
196
|
+
createSession?: boolean;
|
|
197
|
+
}): Promise<string>;
|
|
193
198
|
recordBackgroundUsage(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
194
199
|
recordBackgroundLaunch(sessionId: string): Promise<SessionForkCheckpoint>;
|
|
195
200
|
compact(sessionId: string, signal?: AbortSignal, selection?: ManualCompactSelection): Promise<ManualCompactResult>;
|
|
@@ -214,6 +219,7 @@ export declare class ClaudeSessionService {
|
|
|
214
219
|
private restoreWorktree;
|
|
215
220
|
private paths;
|
|
216
221
|
private appendCdCommand;
|
|
222
|
+
private appendAgentColorUsage;
|
|
217
223
|
private appendSystemLocalCommand;
|
|
218
224
|
private ensureLocalSession;
|
|
219
225
|
private appendInputHistory;
|
|
@@ -2,6 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { appendFile, copyFile, link, lstat, mkdir, readdir, realpath, stat, unlink, } from 'node:fs/promises';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, extname, isAbsolute, join, relative } from 'node:path';
|
|
5
|
+
import { AGENT_COLOR_DEFAULT, agentColorMessage, getClaudeEffectiveAgentColor, } from '../compatibility/claude/agent-color.js';
|
|
5
6
|
import { createClaudeCompactEntries, formatClaudeCompactSummary, getCumulativeDroppedTokens, } from '../compatibility/claude/compaction.js';
|
|
6
7
|
import { isClaudeSessionId, resolveClaudePaths, resolveClaudeScheduledTaskFile, } from '../compatibility/claude/paths.js';
|
|
7
8
|
import { downloadClaudeFileResources, } from '../compatibility/claude/file-resources.js';
|
|
@@ -696,6 +697,11 @@ export class ClaudeSessionService {
|
|
|
696
697
|
: {}),
|
|
697
698
|
};
|
|
698
699
|
}
|
|
700
|
+
async readEffectiveAgentColor(sessionId) {
|
|
701
|
+
this.assertSessionPersistence();
|
|
702
|
+
const recovery = await this.store(sessionId).loadReadOnly();
|
|
703
|
+
return getClaudeEffectiveAgentColor(recovery.entries, sessionId);
|
|
704
|
+
}
|
|
699
705
|
async export(sessionId) {
|
|
700
706
|
this.assertSessionPersistence();
|
|
701
707
|
try {
|
|
@@ -934,6 +940,21 @@ export class ClaudeSessionService {
|
|
|
934
940
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
935
941
|
}
|
|
936
942
|
}
|
|
943
|
+
async recordColorUsage(sessionId, selection, display, permissionMode = 'default', options = {}) {
|
|
944
|
+
const output = agentColorMessage(selection);
|
|
945
|
+
const agentColor = selection.kind === 'color'
|
|
946
|
+
? selection.color
|
|
947
|
+
: selection.kind === 'reset'
|
|
948
|
+
? AGENT_COLOR_DEFAULT
|
|
949
|
+
: undefined;
|
|
950
|
+
const createLocalSession = sessionId === undefined || options.createSession === true;
|
|
951
|
+
const activeSessionId = await this.ensureLocalSession(sessionId, permissionMode, createLocalSession ? agentColor : undefined, options.createSession === true);
|
|
952
|
+
if (this.options.sessionPersistence !== false) {
|
|
953
|
+
await this.appendInputHistory(display, activeSessionId);
|
|
954
|
+
}
|
|
955
|
+
await this.appendAgentColorUsage(activeSessionId, display, output, createLocalSession ? undefined : agentColor);
|
|
956
|
+
return activeSessionId;
|
|
957
|
+
}
|
|
937
958
|
async recordBackgroundUsage(sessionId, permissionMode = 'default') {
|
|
938
959
|
this.assertWritable();
|
|
939
960
|
const activeSessionId = await this.ensureLocalSession(sessionId, permissionMode);
|
|
@@ -2566,6 +2587,35 @@ export class ClaudeSessionService {
|
|
|
2566
2587
|
throw new Error(`Claude local command conflict: ${result.reason}`);
|
|
2567
2588
|
}
|
|
2568
2589
|
}
|
|
2590
|
+
async appendAgentColorUsage(sessionId, display, output, agentColor) {
|
|
2591
|
+
const args = display.replace(/^\/color\s*/u, '').trim();
|
|
2592
|
+
while (true) {
|
|
2593
|
+
const result = await this.turnStore(sessionId).withLease(async (lease) => {
|
|
2594
|
+
const snapshot = await lease.load();
|
|
2595
|
+
if (snapshot.entries.length === 0) {
|
|
2596
|
+
throw new Error(`Claude session not found: ${sessionId}`);
|
|
2597
|
+
}
|
|
2598
|
+
const appended = await lease.appendMany(snapshot.tail, [
|
|
2599
|
+
...(agentColor === undefined
|
|
2600
|
+
? []
|
|
2601
|
+
: [
|
|
2602
|
+
{
|
|
2603
|
+
type: 'agent-color',
|
|
2604
|
+
agentColor,
|
|
2605
|
+
sessionId,
|
|
2606
|
+
},
|
|
2607
|
+
]),
|
|
2608
|
+
...this.localCommandEntries(sessionId, this.activeCwd(), this.logicalTailUuid(snapshot.tail), 'color', args, output),
|
|
2609
|
+
]);
|
|
2610
|
+
if (appended.status === 'conflict') {
|
|
2611
|
+
throw new Error(`Claude color local command append conflict: ${appended.reason}`);
|
|
2612
|
+
}
|
|
2613
|
+
});
|
|
2614
|
+
if (result.status === 'completed')
|
|
2615
|
+
return;
|
|
2616
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2569
2619
|
async appendSystemLocalCommand(sessionId, command, args, output) {
|
|
2570
2620
|
while (true) {
|
|
2571
2621
|
const result = await this.turnStore(sessionId).withLease(async (lease) => {
|
|
@@ -2583,13 +2633,22 @@ export class ClaudeSessionService {
|
|
|
2583
2633
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
2584
2634
|
}
|
|
2585
2635
|
}
|
|
2586
|
-
async ensureLocalSession(sessionId, permissionMode) {
|
|
2587
|
-
if (sessionId)
|
|
2636
|
+
async ensureLocalSession(sessionId, permissionMode, agentColor, createExplicit = false) {
|
|
2637
|
+
if (sessionId !== undefined && !createExplicit)
|
|
2588
2638
|
return sessionId;
|
|
2589
2639
|
this.assertWritable();
|
|
2590
|
-
const createdSessionId = randomUUID();
|
|
2640
|
+
const createdSessionId = sessionId ?? randomUUID();
|
|
2591
2641
|
const store = this.turnStore(createdSessionId);
|
|
2592
2642
|
const created = await store.create([
|
|
2643
|
+
...(agentColor === undefined
|
|
2644
|
+
? []
|
|
2645
|
+
: [
|
|
2646
|
+
{
|
|
2647
|
+
type: 'agent-color',
|
|
2648
|
+
agentColor,
|
|
2649
|
+
sessionId: createdSessionId,
|
|
2650
|
+
},
|
|
2651
|
+
]),
|
|
2593
2652
|
{ type: 'mode', mode: 'normal', sessionId: createdSessionId },
|
|
2594
2653
|
{
|
|
2595
2654
|
type: 'permission-mode',
|
|
@@ -8,6 +8,8 @@ import { type TuiMemoryFiles } from './tui/memory-files.js';
|
|
|
8
8
|
import { type TuiDiffSnapshot } from './tui/git-diff.js';
|
|
9
9
|
import { type TuiPermissionBehavior, type TuiPermissionRule } from './tui/permission-settings.js';
|
|
10
10
|
import { type RecentlyDeniedStore } from './tui/recently-denied.js';
|
|
11
|
+
import { type AgentColorSelection } from '../compatibility/claude/agent-color.js';
|
|
12
|
+
import type { AgentColorName } from '../compatibility/claude/agent-color.js';
|
|
11
13
|
import type { ClaudeResourceScope } from '../compatibility/claude/shared-resources.js';
|
|
12
14
|
import type { TuiHookConfiguration } from './tui/hook-settings.js';
|
|
13
15
|
import { type TuiSlashCommand } from './tui/slash-commands.js';
|
|
@@ -41,6 +43,8 @@ interface InteractiveSessionCommands {
|
|
|
41
43
|
retryRecentlyDenied?(sessionId: string, display: string, signal?: AbortSignal): Promise<SessionRunResult>;
|
|
42
44
|
answerSideQuestion?(sessionId: string | undefined, question: string, signal?: AbortSignal, onDelta?: (delta: string) => void, permissionMode?: ClaudePermissionMode): Promise<SideQuestionResult>;
|
|
43
45
|
recordBtwUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
46
|
+
recordColorUsage?(sessionId: string | undefined, selection: AgentColorSelection, display: string, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
47
|
+
agentColor?(sessionId: string): Promise<AgentColorName | undefined>;
|
|
44
48
|
recordBackgroundUsage?(sessionId: string | undefined, permissionMode?: ClaudePermissionMode): Promise<string>;
|
|
45
49
|
recordBackgroundLaunch?(sessionId: string): Promise<SessionForkCheckpoint>;
|
|
46
50
|
forkSideQuestion?(sessionId: string, question: string, signal?: AbortSignal): Promise<SideQuestionForkResult>;
|
|
@@ -111,6 +115,7 @@ interface InteractiveAppProps {
|
|
|
111
115
|
initialSessions: readonly SessionSummary[];
|
|
112
116
|
initialPrompt?: string;
|
|
113
117
|
initialHistory?: readonly TranscriptItem[];
|
|
118
|
+
initialSessionColor?: AgentColorName;
|
|
114
119
|
signal?: AbortSignal;
|
|
115
120
|
onCancel?: () => void;
|
|
116
121
|
onTurnChange?: (turn: Promise<void> | null) => void;
|
|
@@ -179,7 +184,7 @@ interface InteractiveAppProps {
|
|
|
179
184
|
releaseNotesLoader?: (configRoot: string) => Promise<string>;
|
|
180
185
|
settingSources?: readonly ClaudeResourceScope[];
|
|
181
186
|
}
|
|
182
|
-
export declare function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
|
|
187
|
+
export declare function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory, initialSessionColor, signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader, allowNewSession, resume, display, terminalWidth, slashCommands, agents, allowDangerouslySkipPermissions, additionalDirectories, diffLoader, fileLoader, externalEditor, keybindingsConfigRoot, keybindingsFile, keybindingsLoader, keybindingsEditor, memoryFilesLoader, memoryEditor, memoryFolderOpener, suspendProcess, clipboardReader, clipboardWriter, sideQuestionClipboardWriter, exportWriter, permissionRuleStore, sandboxStore: suppliedSandboxStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver, workspaceDirectoryCompleter, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, elicitationUrlOpener, releaseNotesLoader, settingSources, }: InteractiveAppProps): import("react").JSX.Element;
|
|
183
188
|
export declare function runInteractive(options: {
|
|
184
189
|
factory: InteractiveServiceFactory;
|
|
185
190
|
initialPrompt?: string;
|
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);
|
|
@@ -4951,6 +4998,9 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
4951
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',
|
|
4952
4999
|
});
|
|
4953
5000
|
}
|
|
5001
|
+
else if (colorCommand) {
|
|
5002
|
+
changeSessionColor(prompt, parseAgentColorInput(colorCommand[1] ?? ''));
|
|
5003
|
+
}
|
|
4954
5004
|
else if (btwCommand) {
|
|
4955
5005
|
const sideQuestion = btwCommand[1]?.trim();
|
|
4956
5006
|
if (sideQuestion)
|
|
@@ -5420,7 +5470,9 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
|
|
|
5420
5470
|
label: 'Save to file',
|
|
5421
5471
|
description: 'Save the conversation to a file in the current directory',
|
|
5422
5472
|
},
|
|
5423
|
-
], 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
|
|
5424
5476
|
? (() => {
|
|
5425
5477
|
const duration = formatTurnDuration(turnDuration);
|
|
5426
5478
|
return duration === undefined
|
|
@@ -5534,11 +5586,16 @@ export async function runInteractive(options) {
|
|
|
5534
5586
|
initialAgentPromptResolved = true;
|
|
5535
5587
|
}
|
|
5536
5588
|
let initialHistory = [];
|
|
5589
|
+
let initialSessionColor;
|
|
5537
5590
|
try {
|
|
5538
5591
|
initialHistory =
|
|
5539
5592
|
resume?.sessionId === undefined || listing.transcript === undefined
|
|
5540
5593
|
? []
|
|
5541
5594
|
: await listing.transcript(resume.sessionId);
|
|
5595
|
+
initialSessionColor =
|
|
5596
|
+
resume?.sessionId === undefined || listing.agentColor === undefined
|
|
5597
|
+
? undefined
|
|
5598
|
+
: await listing.agentColor(resume.sessionId);
|
|
5542
5599
|
}
|
|
5543
5600
|
catch (error) {
|
|
5544
5601
|
try {
|
|
@@ -5563,7 +5620,7 @@ export async function runInteractive(options) {
|
|
|
5563
5620
|
let cleanup = null;
|
|
5564
5621
|
let backgrounded;
|
|
5565
5622
|
rendererChange = null;
|
|
5566
|
-
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
|
|
5567
5624
|
? {}
|
|
5568
5625
|
: { settingSources: options.settingSources }), initialThemeSettings: initialThemeSettings, ...(initialThemeLoadError === undefined
|
|
5569
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
|
}
|
|
@@ -34,6 +34,12 @@ export const BUILTIN_TUI_SLASH_COMMANDS = [
|
|
|
34
34
|
description: 'Start a new session with empty context; previous session stays on disk (resumable with /resume)',
|
|
35
35
|
source: 'builtin',
|
|
36
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
|
+
},
|
|
37
43
|
{
|
|
38
44
|
name: 'compact',
|
|
39
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>;
|
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { dirname, join, resolve } from 'node:path';
|
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
8
|
import { randomUUID } from 'node:crypto';
|
|
9
9
|
import { ClaudeSessionService, } from './application/session-service.js';
|
|
10
|
+
import { agentColorMessage, parseAgentColorInput, } from './compatibility/claude/agent-color.js';
|
|
10
11
|
import { ClaudeConditionalRuleResolver, ClaudeContextAssembler, } from './compatibility/claude/context.js';
|
|
11
12
|
import { loadClaudeDynamicContext } from './compatibility/claude/dynamic-context.js';
|
|
12
13
|
import { createClaudePrSessionFilter, filterClaudePrLinkedSessions, } from './compatibility/claude/pr-links.js';
|
|
@@ -46,7 +47,7 @@ import { WorkspaceContext } from './application/session-worktree.js';
|
|
|
46
47
|
import { launchTmuxWorktree } from './platform/tmux-worktree.js';
|
|
47
48
|
import { claudeSandboxRuntime } from './sandbox/claude-sandbox-runtime.js';
|
|
48
49
|
import { claudeSandboxTempDirectory, loadClaudeSandboxSettings, } from './sandbox/claude-sandbox-settings.js';
|
|
49
|
-
import { createErrorResult, createSuccessResult, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
|
|
50
|
+
import { createErrorResult, createSuccessResult, matchHeadlessColorCommand, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
|
|
50
51
|
import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaudePlugins, readPluginRegistry, setClaudePluginEnabled, uninstallClaudePlugin, updateClaudePlugin, validateClaudePlugin, } from './plugins/claude-plugin-runtime.js';
|
|
51
52
|
import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
|
|
52
53
|
import { executeClaudePluginEvalCommand, PLUGIN_EVAL_HELP, } from './plugins/claude-plugin-eval.js';
|
|
@@ -1343,6 +1344,8 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1343
1344
|
retryRecentlyDenied: (sessionId, display, retrySignal) => service.retryRecentlyDenied(sessionId, display, retrySignal),
|
|
1344
1345
|
answerSideQuestion: (sessionId, question, sideSignal, onDelta, permissionMode) => service.answerSideQuestion(sessionId, question, sideSignal, onDelta, permissionMode),
|
|
1345
1346
|
recordBtwUsage: (sessionId, permissionMode) => service.recordBtwUsage(sessionId, permissionMode),
|
|
1347
|
+
recordColorUsage: (sessionId, selection, display, permissionMode, options) => service.recordColorUsage(sessionId, selection, display, permissionMode, options),
|
|
1348
|
+
agentColor: (sessionId) => service.readEffectiveAgentColor(sessionId),
|
|
1346
1349
|
recordBackgroundUsage: (sessionId, permissionMode) => service.recordBackgroundUsage(sessionId, permissionMode),
|
|
1347
1350
|
recordBackgroundLaunch: (sessionId) => service.recordBackgroundLaunch(sessionId),
|
|
1348
1351
|
forkSideQuestion: (sessionId, question, sideSignal) => service.forkSideQuestion(sessionId, question, sideSignal),
|
|
@@ -3735,6 +3738,7 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3735
3738
|
const pendingEvents = [];
|
|
3736
3739
|
let streamIterator;
|
|
3737
3740
|
let firstStreamMessage;
|
|
3741
|
+
let streamInputExhausted = false;
|
|
3738
3742
|
const queuedStreamUsers = [];
|
|
3739
3743
|
const earlyControlResponses = new Map();
|
|
3740
3744
|
let currentTurnAbort;
|
|
@@ -3934,6 +3938,19 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3934
3938
|
}
|
|
3935
3939
|
return 0;
|
|
3936
3940
|
}
|
|
3941
|
+
const headlessTurnReached = invocation.rewindFiles === undefined &&
|
|
3942
|
+
!['sessions', 'fork', 'inspect', 'export'].includes(command ?? 'run');
|
|
3943
|
+
if (streamIterator && headlessTurnReached) {
|
|
3944
|
+
const first = await nextStreamUser();
|
|
3945
|
+
if (first)
|
|
3946
|
+
firstStreamMessage = first;
|
|
3947
|
+
else
|
|
3948
|
+
streamInputExhausted = true;
|
|
3949
|
+
}
|
|
3950
|
+
const firstHeadlessPrompt = firstStreamMessage?.prompt ?? headlessPrompt;
|
|
3951
|
+
const firstTurnIsLocalColor = !invocation.disableSlashCommands &&
|
|
3952
|
+
firstHeadlessPrompt !== undefined &&
|
|
3953
|
+
matchHeadlessColorCommand(firstHeadlessPrompt) !== undefined;
|
|
3937
3954
|
const service = await dependencies.createService({
|
|
3938
3955
|
eventSink: outputFormat === 'stream-json' && !invocation.legacyJson
|
|
3939
3956
|
? (event) => {
|
|
@@ -3958,8 +3975,7 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3958
3975
|
}
|
|
3959
3976
|
}
|
|
3960
3977
|
: eventSink(io, outputFormat, invocation.legacyJson),
|
|
3961
|
-
requireProvider:
|
|
3962
|
-
!['fork', 'sessions', 'inspect', 'export'].includes(command ?? 'run'),
|
|
3978
|
+
requireProvider: !streamInputExhausted && !firstTurnIsLocalColor && headlessTurnReached,
|
|
3963
3979
|
...(retryInterruptedTools ? { approveRecovery: () => true } : {}),
|
|
3964
3980
|
...(streamIterator ? { approveTool: approveStreamTool } : {}),
|
|
3965
3981
|
...(streamIterator ? { onElicitation: respondStreamElicitation } : {}),
|
|
@@ -4067,12 +4083,8 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4067
4083
|
if (outputFormat === 'stream-json' && !invocation.legacyJson) {
|
|
4068
4084
|
streamOutput = new StreamJsonOutput((value) => writeJson(io, value), runtimeInfo, activeSessionId, includePartialMessages, invocation.includeHookEvents);
|
|
4069
4085
|
}
|
|
4070
|
-
if (
|
|
4071
|
-
|
|
4072
|
-
if (!first)
|
|
4073
|
-
return 0;
|
|
4074
|
-
firstStreamMessage = first;
|
|
4075
|
-
}
|
|
4086
|
+
if (streamInputExhausted)
|
|
4087
|
+
return 0;
|
|
4076
4088
|
const initialPrompt = firstStreamMessage?.prompt ??
|
|
4077
4089
|
promptFrom(command === 'resume'
|
|
4078
4090
|
? args.slice(2)
|
|
@@ -4099,15 +4111,38 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4099
4111
|
streamOutput.replayUser(streamMessage.message);
|
|
4100
4112
|
}
|
|
4101
4113
|
let result;
|
|
4114
|
+
let colorArgs;
|
|
4102
4115
|
try {
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4116
|
+
colorArgs = invocation.disableSlashCommands
|
|
4117
|
+
? undefined
|
|
4118
|
+
: matchHeadlessColorCommand(prompt);
|
|
4119
|
+
if (colorArgs !== undefined) {
|
|
4120
|
+
if (!service.recordColorUsage) {
|
|
4121
|
+
throw new Error('Session color is unavailable.');
|
|
4122
|
+
}
|
|
4123
|
+
const selection = parseAgentColorInput(colorArgs);
|
|
4124
|
+
const localSessionId = await service.recordColorUsage(activeSessionId, selection, prompt, invocation.permissionMode, isFirstTurn && existingSessionId === undefined
|
|
4125
|
+
? { createSession: true }
|
|
4126
|
+
: undefined);
|
|
4127
|
+
result = {
|
|
4128
|
+
sessionId: localSessionId,
|
|
4129
|
+
text: agentColorMessage(selection),
|
|
4130
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
4131
|
+
durationApiMs: 0,
|
|
4132
|
+
costUsd: 0,
|
|
4133
|
+
modelUsage: {},
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
else {
|
|
4137
|
+
result =
|
|
4138
|
+
existingSessionId !== undefined || !isFirstTurn
|
|
4139
|
+
? runSignal
|
|
4140
|
+
? await service.resume(activeSessionId, prompt, runSignal, undefined, streamMessage?.images, streamMessage?.documents)
|
|
4141
|
+
: await service.resume(activeSessionId, prompt, undefined, undefined, streamMessage?.images, streamMessage?.documents)
|
|
4142
|
+
: runSignal
|
|
4143
|
+
? await service.run(prompt, runSignal, activeSessionId, undefined, streamMessage?.images, streamMessage?.documents)
|
|
4144
|
+
: await service.run(prompt, undefined, activeSessionId, undefined, streamMessage?.images, streamMessage?.documents);
|
|
4145
|
+
}
|
|
4111
4146
|
}
|
|
4112
4147
|
catch (error) {
|
|
4113
4148
|
if (isCancellation(error, turnAbort.signal)) {
|
|
@@ -4134,16 +4169,20 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
4134
4169
|
return false;
|
|
4135
4170
|
}
|
|
4136
4171
|
activeSessionId = result.sessionId;
|
|
4137
|
-
if (streamOutput)
|
|
4172
|
+
if (streamOutput) {
|
|
4173
|
+
if (colorArgs !== undefined) {
|
|
4174
|
+
streamOutput.syntheticAssistant(result.text);
|
|
4175
|
+
}
|
|
4138
4176
|
streamOutput.result(result, startedAt);
|
|
4177
|
+
}
|
|
4139
4178
|
else if (outputFormat === 'json') {
|
|
4140
4179
|
const resultRuntimeInfo = service.runtimeInfo?.() ?? runtimeInfo;
|
|
4141
|
-
writeJson(io, createSuccessResult(result, resultRuntimeInfo, startedAt, Math.max(1, jsonModelTurns)));
|
|
4180
|
+
writeJson(io, createSuccessResult(result, resultRuntimeInfo, startedAt, colorArgs !== undefined ? 0 : Math.max(1, jsonModelTurns)));
|
|
4142
4181
|
}
|
|
4143
4182
|
else if (outputFormat !== 'text')
|
|
4144
4183
|
writeJson(io, { type: 'result', ...result });
|
|
4145
4184
|
else
|
|
4146
|
-
io.stdout('\n');
|
|
4185
|
+
io.stdout(colorArgs !== undefined ? `${result.text}\n` : '\n');
|
|
4147
4186
|
if (streamOutput && invocation.promptSuggestions) {
|
|
4148
4187
|
try {
|
|
4149
4188
|
const suggestion = await service.promptSuggestion?.(activeSessionId, runSignal);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { ClaudeTranscriptEntry } from './schema.js';
|
|
2
|
+
export declare const AGENT_COLORS: readonly ["red", "blue", "green", "yellow", "purple", "orange", "pink", "cyan"];
|
|
3
|
+
export type AgentColorName = (typeof AGENT_COLORS)[number];
|
|
4
|
+
export declare const AGENT_COLOR_DEFAULT: "default";
|
|
5
|
+
export type AgentColorValue = AgentColorName | typeof AGENT_COLOR_DEFAULT;
|
|
6
|
+
export declare const AGENT_COLOR_VALUES: readonly AgentColorValue[];
|
|
7
|
+
export declare const AGENT_COLOR_CHOICES: string;
|
|
8
|
+
export declare const RESET_AGENT_COLOR_ALIASES: readonly ["default", "reset", "none", "gray", "grey"];
|
|
9
|
+
export declare function isAgentColorName(value: unknown): value is AgentColorName;
|
|
10
|
+
export declare function isAgentColorValue(value: unknown): value is AgentColorValue;
|
|
11
|
+
export declare function normalizeAgentColorInput(input: string): string;
|
|
12
|
+
export type AgentColorSelection = {
|
|
13
|
+
kind: 'color';
|
|
14
|
+
color: AgentColorName;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'reset';
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'invalid';
|
|
19
|
+
input: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function parseAgentColorInput(input: string): AgentColorSelection;
|
|
22
|
+
export declare function randomAgentColor(): AgentColorName;
|
|
23
|
+
export declare function agentColorMessage(selection: AgentColorSelection): string;
|
|
24
|
+
export declare function getClaudeEffectiveAgentColor(entries: readonly ClaudeTranscriptEntry[], sessionId: string): AgentColorName | undefined;
|
|
25
|
+
//# sourceMappingURL=agent-color.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const AGENT_COLORS = [
|
|
2
|
+
'red',
|
|
3
|
+
'blue',
|
|
4
|
+
'green',
|
|
5
|
+
'yellow',
|
|
6
|
+
'purple',
|
|
7
|
+
'orange',
|
|
8
|
+
'pink',
|
|
9
|
+
'cyan',
|
|
10
|
+
];
|
|
11
|
+
export const AGENT_COLOR_DEFAULT = 'default';
|
|
12
|
+
export const AGENT_COLOR_VALUES = [
|
|
13
|
+
...AGENT_COLORS,
|
|
14
|
+
AGENT_COLOR_DEFAULT,
|
|
15
|
+
];
|
|
16
|
+
export const AGENT_COLOR_CHOICES = `${AGENT_COLORS.join(', ')}, ${AGENT_COLOR_DEFAULT}`;
|
|
17
|
+
export const RESET_AGENT_COLOR_ALIASES = [
|
|
18
|
+
AGENT_COLOR_DEFAULT,
|
|
19
|
+
'reset',
|
|
20
|
+
'none',
|
|
21
|
+
'gray',
|
|
22
|
+
'grey',
|
|
23
|
+
];
|
|
24
|
+
export function isAgentColorName(value) {
|
|
25
|
+
return (typeof value === 'string' &&
|
|
26
|
+
AGENT_COLORS.includes(value));
|
|
27
|
+
}
|
|
28
|
+
export function isAgentColorValue(value) {
|
|
29
|
+
return (typeof value === 'string' &&
|
|
30
|
+
AGENT_COLOR_VALUES.includes(value));
|
|
31
|
+
}
|
|
32
|
+
export function normalizeAgentColorInput(input) {
|
|
33
|
+
return input.trim().toLowerCase();
|
|
34
|
+
}
|
|
35
|
+
export function parseAgentColorInput(input) {
|
|
36
|
+
const normalized = normalizeAgentColorInput(input);
|
|
37
|
+
if (normalized === '') {
|
|
38
|
+
return { kind: 'color', color: randomAgentColor() };
|
|
39
|
+
}
|
|
40
|
+
if (RESET_AGENT_COLOR_ALIASES.includes(normalized)) {
|
|
41
|
+
return { kind: 'reset' };
|
|
42
|
+
}
|
|
43
|
+
if (isAgentColorName(normalized)) {
|
|
44
|
+
return { kind: 'color', color: normalized };
|
|
45
|
+
}
|
|
46
|
+
return { kind: 'invalid', input: normalized };
|
|
47
|
+
}
|
|
48
|
+
export function randomAgentColor() {
|
|
49
|
+
const index = Math.floor(Math.random() * AGENT_COLORS.length);
|
|
50
|
+
const color = AGENT_COLORS[index];
|
|
51
|
+
if (color === undefined)
|
|
52
|
+
throw new Error('AGENT_COLORS must not be empty');
|
|
53
|
+
return color;
|
|
54
|
+
}
|
|
55
|
+
export function agentColorMessage(selection) {
|
|
56
|
+
switch (selection.kind) {
|
|
57
|
+
case 'color':
|
|
58
|
+
return `Session color set to: ${selection.color}`;
|
|
59
|
+
case 'reset':
|
|
60
|
+
return 'Session color reset to default';
|
|
61
|
+
case 'invalid':
|
|
62
|
+
return `Invalid color "${selection.input}". Available colors: ${AGENT_COLOR_CHOICES}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function getClaudeEffectiveAgentColor(entries, sessionId) {
|
|
66
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
67
|
+
const entry = entries[index];
|
|
68
|
+
if (entry?.type !== 'agent-color' || entry.sessionId !== sessionId) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const color = entry.agentColor;
|
|
72
|
+
if (color === AGENT_COLOR_DEFAULT)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (isAgentColorName(color))
|
|
75
|
+
return color;
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=agent-color.js.map
|
|
@@ -152,6 +152,7 @@ function validateNativeHistory(entries) {
|
|
|
152
152
|
return undefined;
|
|
153
153
|
}
|
|
154
154
|
export function createClaudeNativeFork({ source, sourceSessionId, sessionId, resumeSessionAt, }) {
|
|
155
|
+
const agentColors = [];
|
|
155
156
|
const titles = [];
|
|
156
157
|
const modes = [];
|
|
157
158
|
const permissionModes = [];
|
|
@@ -204,7 +205,9 @@ export function createClaudeNativeFork({ source, sourceSessionId, sessionId, res
|
|
|
204
205
|
throw new Error('Claude fork source entry has the wrong sessionId');
|
|
205
206
|
}
|
|
206
207
|
const copied = copyClaudeEntryWithSessionId(entry, sessionId);
|
|
207
|
-
if (entry.type === '
|
|
208
|
+
if (entry.type === 'agent-color')
|
|
209
|
+
agentColors.push(copied);
|
|
210
|
+
else if (entry.type === 'ai-title')
|
|
208
211
|
titles.push(copied);
|
|
209
212
|
else if (entry.type === 'mode')
|
|
210
213
|
modes.push(copied);
|
|
@@ -232,6 +235,7 @@ export function createClaudeNativeFork({ source, sourceSessionId, sessionId, res
|
|
|
232
235
|
lastPrompt = undefined;
|
|
233
236
|
}
|
|
234
237
|
return [
|
|
238
|
+
...agentColors.slice(-1),
|
|
235
239
|
...titles.slice(-1),
|
|
236
240
|
...modes.slice(-1),
|
|
237
241
|
...permissionModes.slice(-1),
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { isAgentColorValue } from './agent-color.js';
|
|
1
2
|
const SUPPORTED_VERSION = '2.1.208';
|
|
2
3
|
const APPENDABLE_ENTRY_TYPES = new Set([
|
|
4
|
+
'agent-color',
|
|
3
5
|
'agent-name',
|
|
4
6
|
'agent-setting',
|
|
5
7
|
'assistant',
|
|
@@ -17,6 +19,7 @@ const APPENDABLE_ENTRY_TYPES = new Set([
|
|
|
17
19
|
'worktree-state',
|
|
18
20
|
]);
|
|
19
21
|
const FORKABLE_ENTRY_TYPES = new Set([
|
|
22
|
+
'agent-color',
|
|
20
23
|
'agent-name',
|
|
21
24
|
'agent-setting',
|
|
22
25
|
'ai-title',
|
|
@@ -717,6 +720,13 @@ function validateAppendableEntry(entry) {
|
|
|
717
720
|
}
|
|
718
721
|
return;
|
|
719
722
|
}
|
|
723
|
+
if (entry.type === 'agent-color') {
|
|
724
|
+
if (!isAgentColorValue(entry.agentColor) ||
|
|
725
|
+
!isNonEmptyString(entry.sessionId)) {
|
|
726
|
+
throw new Error('Claude agent-color entry has invalid metadata');
|
|
727
|
+
}
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
720
730
|
if (entry.type === 'agent-name') {
|
|
721
731
|
if (!isNonEmptyString(entry.agentName) ||
|
|
722
732
|
!isNonEmptyString(entry.sessionId)) {
|
|
@@ -868,7 +878,9 @@ function validateSidechainEntry(entry) {
|
|
|
868
878
|
}
|
|
869
879
|
}
|
|
870
880
|
function validateForkableEntry(entry) {
|
|
871
|
-
if (entry.type === 'custom-title' ||
|
|
881
|
+
if (entry.type === 'custom-title' ||
|
|
882
|
+
entry.type === 'agent-name' ||
|
|
883
|
+
entry.type === 'agent-color') {
|
|
872
884
|
validateAppendableEntry(entry);
|
|
873
885
|
return;
|
|
874
886
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "praxis-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Local-first, single-user general agent for the command line.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "wuqisen",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"test:subagent-compat": "npm run build && node scripts/verify-subagent-compatibility.mjs",
|
|
94
94
|
"test:background-agent-compat": "npm run build && node scripts/verify-background-agent-compatibility.mjs",
|
|
95
95
|
"test:background-command-compat": "npm run build && node scripts/verify-background-command-compatibility.mjs",
|
|
96
|
+
"test:color-command-compat": "npm run build && node scripts/verify-color-command-compatibility.mjs",
|
|
96
97
|
"test:task-compat": "npm run build && node scripts/verify-task-compatibility.mjs",
|
|
97
98
|
"test:scheduled-compat": "npm run build && node scripts/verify-scheduled-tools-compatibility.mjs",
|
|
98
99
|
"test:top-level-agent-compat": "npm run build && node scripts/verify-top-level-agent-compatibility.mjs",
|