praxis-agent 0.12.0 → 0.13.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 CHANGED
@@ -15,6 +15,11 @@ Anthropic/OpenAI-compatible model access. Praxis deliberately excludes
15
15
  accounts, organizations, billing, managed enterprise policy, remote control,
16
16
  IDE surfaces, and telemetry control planes.
17
17
 
18
+ Claude Code 2.1.208 is the compatibility, architecture, and design baseline.
19
+ Every single-user CLI capability remains required unless it falls inside an
20
+ explicit exclusion above. A similar Praxis surface is not a substitute for the
21
+ corresponding Claude command or runtime contract.
22
+
18
23
  ## Requirements
19
24
 
20
25
  - macOS or Linux
@@ -87,8 +92,10 @@ troubleshooting. Run `praxis --help` for the authoritative command surface.
87
92
  `/rewind`, runtime `/cd`, transcript-free
88
93
  `/btw` side questions with background-Agent handoff, interactive
89
94
  `/background` terminal handoff, unified `/status`/`/config`/`/usage` settings
90
- tabs, `/sandbox` mode/dependency/override/config controls, `/mcp`, `/memory` shared
91
- instruction and auto-memory access, and live extension-reload controls,
95
+ tabs, `/sandbox` mode/dependency/override/config controls, local cached
96
+ `/release-notes`, Claude-compatible `/statusline` command execution and setup
97
+ agent, `/mcp`, `/memory` shared instruction and auto-memory access, and live
98
+ extension-reload controls,
92
99
  cursor/history composer, per-session model/effort/permission controls,
93
100
  context/status/skill/task dashboards, prompt stash and continuation shortcuts,
94
101
  filterable `@` file and agent references, composer undo, `Ctrl+G` external
@@ -1,7 +1,7 @@
1
1
  import { type ClaudeSidechainPermissionMode } from '../compatibility/claude/sidechain.js';
2
2
  import { type ContextAssembler } from '../core/context.js';
3
3
  import { type ModelProvider, type ModelToolCall, type ModelToolDefinition, type ModelUsage, type PermissionResolver, type PermissionApproval, type PermissionDecision, type PermissionUpdate, type RuntimeEventSink, type ToolExecutionContext, type ToolExecutionResult, type ToolRegistry } from '../core/runtime.js';
4
- import type { ClaudeExtensionCatalog } from '../extensions/claude-extensions.js';
4
+ import { type ClaudeExtensionCatalog } from '../extensions/claude-extensions.js';
5
5
  import type { ClaudeHookRunner } from '../hooks/claude-hooks.js';
6
6
  import { type BackgroundAgentSnapshot } from './background-agent-manager.js';
7
7
  export interface WorkflowAgentRunOptions {
@@ -11,6 +11,7 @@ import { createClaudeHookAttachmentEntries, translateProviderEvents, } from '../
11
11
  import { injectFirstUserMessageContext, } from '../core/context.js';
12
12
  import { ContextBudget } from '../core/context-budget.js';
13
13
  import { AgentRuntime, } from '../core/runtime.js';
14
+ import { BUILTIN_STATUSLINE_AGENT_PATH, } from '../extensions/claude-extensions.js';
14
15
  import { ClaudeHookToolCoordinator } from '../hooks/claude-hook-tools.js';
15
16
  import { ClaudeSidechainStore } from '../persistence/claude-sidechain-store.js';
16
17
  import { InMemorySidechainStore } from '../persistence/in-memory-sidechain-store.js';
@@ -79,6 +80,29 @@ export class StructuredOutputRegistry {
79
80
  });
80
81
  }
81
82
  }
83
+ class RestrictedToolRegistry {
84
+ base;
85
+ allowed;
86
+ constructor(base, names) {
87
+ this.base = base;
88
+ this.allowed = new Set(names);
89
+ }
90
+ definitions() {
91
+ return this.base
92
+ .definitions()
93
+ .filter((definition) => this.allowed.has(definition.name));
94
+ }
95
+ prepare(call, context) {
96
+ if (!this.allowed.has(call.name))
97
+ throw new Error(`Tool ${call.name} is unavailable to this agent`);
98
+ return this.base.prepare(call, context);
99
+ }
100
+ execute(call, context) {
101
+ if (!this.allowed.has(call.name))
102
+ throw new Error(`Tool ${call.name} is unavailable to this agent`);
103
+ return this.base.execute(call, context);
104
+ }
105
+ }
82
106
  function parseAgentInput(call) {
83
107
  const allowed = new Set([
84
108
  'description',
@@ -942,9 +966,16 @@ export class ClaudeSubagentExecutor {
942
966
  permission_mode: options.input.permissionMode ?? 'default',
943
967
  };
944
968
  const nestedTools = this.registry(String(options.root.sessionId), options.spawnDepth, () => options.promptId);
945
- const agentTools = options.outputSchema
946
- ? new StructuredOutputRegistry(structuredOnlyTools, options.outputSchema, options.structuredOutput)
969
+ const builtInStatusLineAgent = this.options.extensions?.agent(options.input.subagentType)?.path ===
970
+ BUILTIN_STATUSLINE_AGENT_PATH;
971
+ const scopedTools = builtInStatusLineAgent
972
+ ? new RestrictedToolRegistry(nestedTools, ['Read', 'Edit'])
947
973
  : nestedTools;
974
+ const agentTools = options.outputSchema
975
+ ? new StructuredOutputRegistry(builtInStatusLineAgent
976
+ ? new RestrictedToolRegistry(structuredOnlyTools, ['Read', 'Edit'])
977
+ : structuredOnlyTools, options.outputSchema, options.structuredOutput)
978
+ : scopedTools;
948
979
  const runtimeTools = this.options.hooks
949
980
  ? new ClaudeHookToolCoordinator({
950
981
  tools: agentTools,
@@ -175,8 +175,10 @@ interface InteractiveAppProps {
175
175
  runtimeSettingsTarget?: ConfigSettingsTarget;
176
176
  notificationWriter?: TuiNotificationWriter;
177
177
  elicitationUrlOpener?: (url: string) => void | Promise<void>;
178
+ releaseNotesLoader?: (configRoot: string) => Promise<string>;
179
+ settingSources?: readonly ClaudeResourceScope[];
178
180
  }
179
- 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, }: InteractiveAppProps): import("react").JSX.Element;
181
+ 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;
180
182
  export declare function runInteractive(options: {
181
183
  factory: InteractiveServiceFactory;
182
184
  initialPrompt?: string;
@@ -192,6 +194,7 @@ export declare function runInteractive(options: {
192
194
  onBackground?: (request: InteractiveBackgroundRequest) => Promise<InteractiveBackgroundResult>;
193
195
  onBackgrounded?: (result: InteractiveBackgroundResult) => void;
194
196
  runtimeSettings?: PraxisRuntimeSettings;
197
+ settingSources?: readonly ClaudeResourceScope[];
195
198
  }): Promise<number>;
196
199
  export {};
197
200
  //# sourceMappingURL=interactive.d.ts.map
@@ -12,6 +12,8 @@ import { claudePermissionActionKey, claudePermissionRuleMatches, } from '../perm
12
12
  import { redactSensitiveText, sensitiveEnvironmentValues, } from '../platform/sensitive-data.js';
13
13
  import { CommandPalette, BtwPanel, Composer, DiffDashboard, DialogFrame, ExternalEditorWait, HelpMenu, HookDashboard, ListDashboard, MemoryDashboard, MentionPicker, ModelMenu, PermissionDashboard, SelectionMenu, SessionPicker, ThemePicker, CustomThemeEditor, Transcript, WelcomePanel, useTerminalWidth, } from './tui/claude-style.js';
14
14
  import { loadTuiMemoryFiles, openTuiMemoryFolder, } from './tui/memory-files.js';
15
+ import { loadClaudeReleaseNotes } from './tui/release-notes.js';
16
+ import { createClaudeStatusLineInput, StatusLine } from './tui/status-line.js';
15
17
  import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
16
18
  import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
17
19
  import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
@@ -245,7 +247,7 @@ const HIDDEN_TUI_SLASH_COMMANDS = new Set([
245
247
  'update',
246
248
  'usage',
247
249
  ]);
248
- 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, }) {
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, }) {
249
251
  const { exit, suspendTerminal, waitUntilRenderFlush } = useApp();
250
252
  const width = useTerminalWidth(terminalWidth);
251
253
  const keybindingsRoot = useMemo(() => resolve(keybindingsConfigRoot ??
@@ -299,6 +301,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
299
301
  const [selectedIndex, setSelectedIndex] = useState(0);
300
302
  const selectedIndexRef = useRef(0);
301
303
  const [sessionId, setSessionId] = useState(resume?.sessionId ?? null);
304
+ const statusLineSessionId = useRef(resume?.sessionId ?? randomUUID());
302
305
  const sessionIdRef = useRef(resume?.sessionId ?? null);
303
306
  sessionIdRef.current = sessionId;
304
307
  const [sessionName, setSessionName] = useState(null);
@@ -4799,11 +4802,13 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4799
4802
  updateMenu({ kind: 'help', tabIndex: 0, selectedIndex: 0 });
4800
4803
  }
4801
4804
  else if (prompt === '/new') {
4805
+ statusLineSessionId.current = randomUUID();
4802
4806
  setSessionId(null);
4803
4807
  setPendingFork(false);
4804
4808
  append({ kind: 'notice', text: 'Started a new session.' });
4805
4809
  }
4806
4810
  else if (prompt === '/clear') {
4811
+ statusLineSessionId.current = randomUUID();
4807
4812
  setSessionId(null);
4808
4813
  setPendingFork(false);
4809
4814
  setHistory([]);
@@ -5176,6 +5181,27 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
5176
5181
  else if (prompt === '/status') {
5177
5182
  openSettings('status');
5178
5183
  }
5184
+ else if (prompt === '/release-notes') {
5185
+ const loading = (async () => {
5186
+ setBusy(true);
5187
+ setStatus('loading release notes');
5188
+ try {
5189
+ append({
5190
+ kind: 'local-result',
5191
+ text: await releaseNotesLoader(keybindingsRoot),
5192
+ });
5193
+ }
5194
+ catch (error) {
5195
+ warn(error);
5196
+ }
5197
+ finally {
5198
+ setBusy(false);
5199
+ setStatus('ready');
5200
+ }
5201
+ })();
5202
+ onTurnChange?.(loading);
5203
+ void loading.finally(() => onTurnChange?.(null));
5204
+ }
5179
5205
  else if (prompt === '/config') {
5180
5206
  openSettings('config');
5181
5207
  }
@@ -5395,7 +5421,39 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
5395
5421
  ? { prStatus: `PR #${activeSessionSummary.prNumber}` }
5396
5422
  : {}), shortcutsVisible: shortcutsVisible, ...(editorFooterMessage === undefined
5397
5423
  ? {}
5398
- : { footerMessage: editorFooterMessage }) })] }))] })) }) }));
5424
+ : { footerMessage: editorFooterMessage }) }), _jsx(StatusLine, { configRoot: keybindingsRoot, cwd: runtimeCwd, input: createClaudeStatusLineInput({
5425
+ configRoot: keybindingsRoot,
5426
+ cwd: runtimeCwd,
5427
+ projectDir: display.cwd,
5428
+ sessionId: sessionId ?? statusLineSessionId.current,
5429
+ sessionName,
5430
+ ...(runtimeDisplay.model === undefined
5431
+ ? {}
5432
+ : { model: runtimeDisplay.model }),
5433
+ version: runtimeDisplay.version,
5434
+ outputStyle: runtimeSettings.outputStyle,
5435
+ permissionMode: runtimePreferences.permissionMode,
5436
+ additionalDirectories: runtimePreferences.additionalDirectories,
5437
+ ...(usage === undefined ? {} : { usage }),
5438
+ ...(costUsd === undefined ? {} : { costUsd }),
5439
+ ...(runtimeDisplay.contextWindowTokens === undefined
5440
+ ? {}
5441
+ : {
5442
+ contextWindowTokens: runtimeDisplay.contextWindowTokens,
5443
+ }),
5444
+ ...(runtimeSettings.editor === 'vim'
5445
+ ? { vimMode: vimInsertMode ? 'INSERT' : 'NORMAL' }
5446
+ : {}),
5447
+ }), refreshKey: [
5448
+ history.length,
5449
+ runtimePreferences.permissionMode,
5450
+ runtimeDisplay.model,
5451
+ runtimeSettings.outputStyle,
5452
+ vimInsertMode,
5453
+ sessionName,
5454
+ usage?.inputTokens,
5455
+ usage?.outputTokens,
5456
+ ].join(':'), ...(settingSources === undefined ? {} : { settingSources }) })] }))] })) }) }));
5399
5457
  }
5400
5458
  export async function runInteractive(options) {
5401
5459
  const controller = new AbortController();
@@ -5484,7 +5542,9 @@ export async function runInteractive(options) {
5484
5542
  let cleanup = null;
5485
5543
  let backgrounded;
5486
5544
  rendererChange = null;
5487
- const instance = render(_jsx(InteractiveApp, { factory: options.factory, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: history, runtimeSettings: currentRuntimeSettings, initialThemeSettings: initialThemeSettings, ...(initialThemeLoadError === undefined
5545
+ const instance = render(_jsx(InteractiveApp, { factory: options.factory, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: history, runtimeSettings: currentRuntimeSettings, ...(options.settingSources === undefined
5546
+ ? {}
5547
+ : { settingSources: options.settingSources }), initialThemeSettings: initialThemeSettings, ...(initialThemeLoadError === undefined
5488
5548
  ? {}
5489
5549
  : { initialThemeLoadError }), ...(currentInitialPrompt === undefined
5490
5550
  ? {}
@@ -1,4 +1,4 @@
1
- export type ClaudeCommandDisposition = 'included' | 'excluded';
1
+ export type ClaudeCommandDisposition = 'included' | 'deferred' | 'excluded';
2
2
  export type ClaudeCommandVisibility = 'visible' | 'hidden' | 'conditional';
3
3
  export interface ClaudeCommandInventoryEntry {
4
4
  name: string;
@@ -12,8 +12,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
12
12
  readonly visibility: "visible";
13
13
  }, {
14
14
  readonly name: "advisor";
15
- readonly disposition: "included";
15
+ readonly disposition: "deferred";
16
16
  readonly visibility: "conditional";
17
+ readonly reason: "Conditional advice mode is required parity work and is not implemented yet.";
17
18
  }, {
18
19
  readonly name: "agents";
19
20
  readonly disposition: "included";
@@ -28,17 +29,18 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
28
29
  readonly visibility: "visible";
29
30
  }, {
30
31
  readonly name: "chrome";
31
- readonly disposition: "excluded";
32
+ readonly disposition: "deferred";
32
33
  readonly visibility: "conditional";
33
- readonly reason: "Chrome integration is outside the CLI-only product boundary.";
34
+ readonly reason: "The CLI-driven Chrome integration is required parity work and is not implemented yet.";
34
35
  }, {
35
36
  readonly name: "clear";
36
37
  readonly disposition: "included";
37
38
  readonly visibility: "visible";
38
39
  }, {
39
40
  readonly name: "color";
40
- readonly disposition: "included";
41
+ readonly disposition: "deferred";
41
42
  readonly visibility: "visible";
43
+ readonly reason: "The dedicated /color contract is required; /theme is not a substitute.";
42
44
  }, {
43
45
  readonly name: "compact";
44
46
  readonly disposition: "included";
@@ -62,16 +64,18 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
62
64
  readonly visibility: "conditional";
63
65
  }, {
64
66
  readonly name: "cost";
65
- readonly disposition: "included";
67
+ readonly disposition: "deferred";
66
68
  readonly visibility: "conditional";
69
+ readonly reason: "The dedicated /cost contract is required; /status is not a substitute.";
67
70
  }, {
68
71
  readonly name: "diff";
69
72
  readonly disposition: "included";
70
73
  readonly visibility: "visible";
71
74
  }, {
72
75
  readonly name: "doctor";
73
- readonly disposition: "included";
76
+ readonly disposition: "deferred";
74
77
  readonly visibility: "conditional";
78
+ readonly reason: "Interactive /doctor is required; the top-level command is not a substitute.";
75
79
  }, {
76
80
  readonly name: "effort";
77
81
  readonly disposition: "included";
@@ -82,8 +86,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
82
86
  readonly visibility: "visible";
83
87
  }, {
84
88
  readonly name: "fast";
85
- readonly disposition: "included";
89
+ readonly disposition: "deferred";
86
90
  readonly visibility: "conditional";
91
+ readonly reason: "The /fast state flow is required; model and effort controls are not a substitute.";
87
92
  }, {
88
93
  readonly name: "files";
89
94
  readonly disposition: "excluded";
@@ -91,8 +96,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
91
96
  readonly reason: "The source command is restricted to the internal Ant user type.";
92
97
  }, {
93
98
  readonly name: "heapdump";
94
- readonly disposition: "included";
99
+ readonly disposition: "deferred";
95
100
  readonly visibility: "hidden";
101
+ readonly reason: "The hidden heap-diagnostic contract is required parity work and is not implemented yet.";
96
102
  }, {
97
103
  readonly name: "help";
98
104
  readonly disposition: "included";
@@ -181,8 +187,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
181
187
  readonly visibility: "visible";
182
188
  }, {
183
189
  readonly name: "stats";
184
- readonly disposition: "included";
190
+ readonly disposition: "deferred";
185
191
  readonly visibility: "visible";
192
+ readonly reason: "Historical usage statistics are required parity work and are not implemented yet.";
186
193
  }, {
187
194
  readonly name: "status";
188
195
  readonly disposition: "included";
@@ -253,8 +260,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
253
260
  readonly reason: "The source command is the Claude subscription plan-usage panel.";
254
261
  }, {
255
262
  readonly name: "insights";
256
- readonly disposition: "included";
263
+ readonly disposition: "deferred";
257
264
  readonly visibility: "visible";
265
+ readonly reason: "Retrospective insights are required parity work and are not implemented yet.";
258
266
  }, {
259
267
  readonly name: "vim";
260
268
  readonly disposition: "included";
@@ -270,20 +278,24 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
270
278
  readonly visibility: "conditional";
271
279
  }, {
272
280
  readonly name: "buddy";
273
- readonly disposition: "included";
281
+ readonly disposition: "deferred";
274
282
  readonly visibility: "conditional";
283
+ readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
275
284
  }, {
276
285
  readonly name: "proactive";
277
- readonly disposition: "included";
286
+ readonly disposition: "deferred";
278
287
  readonly visibility: "conditional";
288
+ readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
279
289
  }, {
280
290
  readonly name: "brief";
281
- readonly disposition: "included";
291
+ readonly disposition: "deferred";
282
292
  readonly visibility: "conditional";
293
+ readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
283
294
  }, {
284
295
  readonly name: "assistant";
285
- readonly disposition: "included";
296
+ readonly disposition: "deferred";
286
297
  readonly visibility: "conditional";
298
+ readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
287
299
  }, {
288
300
  readonly name: "remote-control";
289
301
  readonly disposition: "excluded";
@@ -296,16 +308,19 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
296
308
  readonly reason: "Remote Control is outside the local-only boundary.";
297
309
  }, {
298
310
  readonly name: "voice";
299
- readonly disposition: "included";
311
+ readonly disposition: "deferred";
300
312
  readonly visibility: "conditional";
313
+ readonly reason: "Conditional voice input is required parity work and is not implemented yet.";
301
314
  }, {
302
315
  readonly name: "think-back";
303
- readonly disposition: "included";
316
+ readonly disposition: "deferred";
304
317
  readonly visibility: "conditional";
318
+ readonly reason: "The conditional retrospective flow is required parity work and is not implemented yet.";
305
319
  }, {
306
320
  readonly name: "thinkback-play";
307
- readonly disposition: "included";
321
+ readonly disposition: "deferred";
308
322
  readonly visibility: "hidden";
323
+ readonly reason: "The hidden retrospective playback flow is required parity work and is not implemented yet.";
309
324
  }, {
310
325
  readonly name: "permissions";
311
326
  readonly disposition: "included";
@@ -361,8 +376,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
361
376
  readonly visibility: "conditional";
362
377
  }, {
363
378
  readonly name: "torch";
364
- readonly disposition: "included";
379
+ readonly disposition: "deferred";
365
380
  readonly visibility: "conditional";
381
+ readonly reason: "This source-gated behavior is required conditional parity work and is not implemented yet.";
366
382
  }];
367
383
  export declare const CLAUDE_2_1_208_COMMAND_BY_NAME: ReadonlyMap<string, ClaudeCommandInventoryEntry>;
368
384
  //# sourceMappingURL=claude-command-inventory.d.ts.map
@@ -4,18 +4,28 @@
4
4
  // disappearing from the parity inventory.
5
5
  export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
6
6
  { name: 'add-dir', disposition: 'included', visibility: 'visible' },
7
- { name: 'advisor', disposition: 'included', visibility: 'conditional' },
7
+ {
8
+ name: 'advisor',
9
+ disposition: 'deferred',
10
+ visibility: 'conditional',
11
+ reason: 'Conditional advice mode is required parity work and is not implemented yet.',
12
+ },
8
13
  { name: 'agents', disposition: 'included', visibility: 'visible' },
9
14
  { name: 'branch', disposition: 'included', visibility: 'visible' },
10
15
  { name: 'btw', disposition: 'included', visibility: 'visible' },
11
16
  {
12
17
  name: 'chrome',
13
- disposition: 'excluded',
18
+ disposition: 'deferred',
14
19
  visibility: 'conditional',
15
- reason: 'Chrome integration is outside the CLI-only product boundary.',
20
+ reason: 'The CLI-driven Chrome integration is required parity work and is not implemented yet.',
16
21
  },
17
22
  { name: 'clear', disposition: 'included', visibility: 'visible' },
18
- { name: 'color', 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
+ },
19
29
  { name: 'compact', disposition: 'included', visibility: 'visible' },
20
30
  { name: 'config', disposition: 'included', visibility: 'visible' },
21
31
  { name: 'copy', disposition: 'included', visibility: 'visible' },
@@ -26,19 +36,39 @@ export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
26
36
  reason: 'Desktop handoff/import is outside the CLI-only product boundary.',
27
37
  },
28
38
  { name: 'context', disposition: 'included', visibility: 'conditional' },
29
- { name: 'cost', disposition: 'included', visibility: 'conditional' },
39
+ {
40
+ name: 'cost',
41
+ disposition: 'deferred',
42
+ visibility: 'conditional',
43
+ reason: 'The dedicated /cost contract is required; /status is not a substitute.',
44
+ },
30
45
  { name: 'diff', disposition: 'included', visibility: 'visible' },
31
- { name: 'doctor', disposition: 'included', visibility: 'conditional' },
46
+ {
47
+ name: 'doctor',
48
+ disposition: 'deferred',
49
+ visibility: 'conditional',
50
+ reason: 'Interactive /doctor is required; the top-level command is not a substitute.',
51
+ },
32
52
  { name: 'effort', disposition: 'included', visibility: 'visible' },
33
53
  { name: 'exit', disposition: 'included', visibility: 'visible' },
34
- { name: 'fast', disposition: 'included', visibility: 'conditional' },
54
+ {
55
+ name: 'fast',
56
+ disposition: 'deferred',
57
+ visibility: 'conditional',
58
+ reason: 'The /fast state flow is required; model and effort controls are not a substitute.',
59
+ },
35
60
  {
36
61
  name: 'files',
37
62
  disposition: 'excluded',
38
63
  visibility: 'conditional',
39
64
  reason: 'The source command is restricted to the internal Ant user type.',
40
65
  },
41
- { name: 'heapdump', disposition: 'included', visibility: 'hidden' },
66
+ {
67
+ name: 'heapdump',
68
+ disposition: 'deferred',
69
+ visibility: 'hidden',
70
+ reason: 'The hidden heap-diagnostic contract is required parity work and is not implemented yet.',
71
+ },
42
72
  { name: 'help', disposition: 'included', visibility: 'visible' },
43
73
  {
44
74
  name: 'ide',
@@ -89,7 +119,12 @@ export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
89
119
  reason: 'Remote-session URLs and QR codes are outside the local-only boundary.',
90
120
  },
91
121
  { name: 'skills', disposition: 'included', visibility: 'visible' },
92
- { name: 'stats', disposition: 'included', visibility: 'visible' },
122
+ {
123
+ name: 'stats',
124
+ disposition: 'deferred',
125
+ visibility: 'visible',
126
+ reason: 'Historical usage statistics are required parity work and are not implemented yet.',
127
+ },
93
128
  { name: 'status', disposition: 'included', visibility: 'visible' },
94
129
  { name: 'statusline', disposition: 'included', visibility: 'visible' },
95
130
  {
@@ -149,7 +184,12 @@ export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
149
184
  visibility: 'conditional',
150
185
  reason: 'The source command is the Claude subscription plan-usage panel.',
151
186
  },
152
- { name: 'insights', disposition: 'included', visibility: 'visible' },
187
+ {
188
+ name: 'insights',
189
+ disposition: 'deferred',
190
+ visibility: 'visible',
191
+ reason: 'Retrospective insights are required parity work and are not implemented yet.',
192
+ },
153
193
  { name: 'vim', disposition: 'included', visibility: 'visible' },
154
194
  {
155
195
  name: 'web-setup',
@@ -158,10 +198,30 @@ export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
158
198
  reason: 'Remote setup is outside the local-only boundary.',
159
199
  },
160
200
  { name: 'fork', disposition: 'included', visibility: 'conditional' },
161
- { name: 'buddy', disposition: 'included', visibility: 'conditional' },
162
- { name: 'proactive', disposition: 'included', visibility: 'conditional' },
163
- { name: 'brief', disposition: 'included', visibility: 'conditional' },
164
- { name: 'assistant', disposition: 'included', visibility: 'conditional' },
201
+ {
202
+ name: 'buddy',
203
+ disposition: 'deferred',
204
+ visibility: 'conditional',
205
+ reason: 'This conditional source mode is required parity work and is not implemented yet.',
206
+ },
207
+ {
208
+ name: 'proactive',
209
+ disposition: 'deferred',
210
+ visibility: 'conditional',
211
+ reason: 'This conditional source mode is required parity work and is not implemented yet.',
212
+ },
213
+ {
214
+ name: 'brief',
215
+ disposition: 'deferred',
216
+ visibility: 'conditional',
217
+ reason: 'This conditional source mode is required parity work and is not implemented yet.',
218
+ },
219
+ {
220
+ name: 'assistant',
221
+ disposition: 'deferred',
222
+ visibility: 'conditional',
223
+ reason: 'This conditional source mode is required parity work and is not implemented yet.',
224
+ },
165
225
  {
166
226
  name: 'remote-control',
167
227
  disposition: 'excluded',
@@ -174,9 +234,24 @@ export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
174
234
  visibility: 'conditional',
175
235
  reason: 'Remote Control is outside the local-only boundary.',
176
236
  },
177
- { name: 'voice', disposition: 'included', visibility: 'conditional' },
178
- { name: 'think-back', disposition: 'included', visibility: 'conditional' },
179
- { name: 'thinkback-play', disposition: 'included', visibility: 'hidden' },
237
+ {
238
+ name: 'voice',
239
+ disposition: 'deferred',
240
+ visibility: 'conditional',
241
+ reason: 'Conditional voice input is required parity work and is not implemented yet.',
242
+ },
243
+ {
244
+ name: 'think-back',
245
+ disposition: 'deferred',
246
+ visibility: 'conditional',
247
+ reason: 'The conditional retrospective flow is required parity work and is not implemented yet.',
248
+ },
249
+ {
250
+ name: 'thinkback-play',
251
+ disposition: 'deferred',
252
+ visibility: 'hidden',
253
+ reason: 'The hidden retrospective playback flow is required parity work and is not implemented yet.',
254
+ },
180
255
  { name: 'permissions', disposition: 'included', visibility: 'visible' },
181
256
  { name: 'plan', disposition: 'included', visibility: 'visible' },
182
257
  {
@@ -214,7 +289,12 @@ export const CLAUDE_2_1_208_COMMAND_INVENTORY = [
214
289
  },
215
290
  { name: 'tasks', disposition: 'included', visibility: 'visible' },
216
291
  { name: 'workflows', disposition: 'included', visibility: 'conditional' },
217
- { name: 'torch', disposition: 'included', visibility: 'conditional' },
292
+ {
293
+ name: 'torch',
294
+ disposition: 'deferred',
295
+ visibility: 'conditional',
296
+ reason: 'This source-gated behavior is required conditional parity work and is not implemented yet.',
297
+ },
218
298
  ];
219
299
  export const CLAUDE_2_1_208_COMMAND_BY_NAME = new Map(CLAUDE_2_1_208_COMMAND_INVENTORY.map((entry) => [entry.name, entry]));
220
300
  //# sourceMappingURL=claude-command-inventory.js.map
@@ -0,0 +1,10 @@
1
+ export declare const CLAUDE_CHANGELOG_URL = "https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md";
2
+ export interface ReleaseNotesOptions {
3
+ configRoot: string;
4
+ fetcher?: typeof fetch;
5
+ timeoutMs?: number;
6
+ }
7
+ export declare function parseClaudeChangelog(content: string): readonly [version: string, notes: readonly string[]][];
8
+ export declare function formatClaudeReleaseNotes(content: string): string | null;
9
+ export declare function loadClaudeReleaseNotes({ configRoot, fetcher, timeoutMs, }: ReleaseNotesOptions): Promise<string>;
10
+ //# sourceMappingURL=release-notes.d.ts.map
@@ -0,0 +1,60 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ export const CLAUDE_CHANGELOG_URL = 'https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md';
4
+ const CLAUDE_RAW_CHANGELOG_URL = 'https://raw.githubusercontent.com/anthropics/claude-code/refs/heads/main/CHANGELOG.md';
5
+ export function parseClaudeChangelog(content) {
6
+ const releases = [];
7
+ for (const section of content.split(/^## /gmu).slice(1)) {
8
+ const [heading, ...lines] = section.trim().split(/\r?\n/u);
9
+ const version = heading?.split(' - ')[0]?.trim();
10
+ if (!version)
11
+ continue;
12
+ const notes = lines
13
+ .map((line) => line.trim())
14
+ .filter((line) => line.startsWith('- '))
15
+ .map((line) => line.slice(2).trim())
16
+ .filter(Boolean);
17
+ if (notes.length > 0)
18
+ releases.push([version, notes]);
19
+ }
20
+ return releases.sort(([left], [right]) => left.localeCompare(right, undefined, { numeric: true }));
21
+ }
22
+ export function formatClaudeReleaseNotes(content) {
23
+ const releases = parseClaudeChangelog(content);
24
+ if (releases.length === 0)
25
+ return null;
26
+ return releases
27
+ .map(([version, notes]) => `Version ${version}:\n${notes.map((note) => `· ${note}`).join('\n')}`)
28
+ .join('\n\n');
29
+ }
30
+ async function cachedChangelog(path) {
31
+ try {
32
+ return await readFile(path, 'utf8');
33
+ }
34
+ catch (error) {
35
+ if (error.code === 'ENOENT')
36
+ return '';
37
+ throw error;
38
+ }
39
+ }
40
+ export async function loadClaudeReleaseNotes({ configRoot, fetcher = fetch, timeoutMs = 500, }) {
41
+ const cachePath = join(configRoot, 'cache', 'changelog.md');
42
+ let fresh = '';
43
+ try {
44
+ const response = await fetcher(CLAUDE_RAW_CHANGELOG_URL, {
45
+ signal: AbortSignal.timeout(timeoutMs),
46
+ });
47
+ if (response.ok) {
48
+ fresh = await response.text();
49
+ await mkdir(dirname(cachePath), { recursive: true });
50
+ await writeFile(cachePath, fresh, 'utf8');
51
+ }
52
+ }
53
+ catch {
54
+ // A release-notes lookup is best-effort; the shared cache is authoritative
55
+ // whenever the network is unavailable or slower than the UI budget.
56
+ }
57
+ const formatted = formatClaudeReleaseNotes(fresh || (await cachedChangelog(cachePath)));
58
+ return formatted ?? `See the full changelog at: ${CLAUDE_CHANGELOG_URL}`;
59
+ }
60
+ //# sourceMappingURL=release-notes.js.map
@@ -124,6 +124,11 @@ export const BUILTIN_TUI_SLASH_COMMANDS = [
124
124
  description: 'Activate pending plugin changes in the current session',
125
125
  source: 'builtin',
126
126
  },
127
+ {
128
+ name: 'release-notes',
129
+ description: 'View release notes',
130
+ source: 'builtin',
131
+ },
127
132
  {
128
133
  name: 'rename',
129
134
  description: 'Rename the current conversation',
@@ -0,0 +1,88 @@
1
+ import type { ClaudeResourceScope } from '../../compatibility/claude/shared-resources.js';
2
+ import type { ModelUsage } from '../../core/runtime.js';
3
+ export interface ClaudeStatusLineSetting {
4
+ type: 'command';
5
+ command: string;
6
+ padding?: number;
7
+ }
8
+ export interface ClaudeStatusLineInput {
9
+ session_id: string;
10
+ session_name?: string;
11
+ transcript_path: string;
12
+ cwd: string;
13
+ permission_mode?: string;
14
+ model: {
15
+ id: string;
16
+ display_name: string;
17
+ };
18
+ workspace: {
19
+ current_dir: string;
20
+ project_dir: string;
21
+ added_dirs: readonly string[];
22
+ };
23
+ version: string;
24
+ output_style: {
25
+ name: string;
26
+ };
27
+ cost: {
28
+ total_cost_usd: number;
29
+ total_duration_ms: number;
30
+ total_api_duration_ms: number;
31
+ total_lines_added: number;
32
+ total_lines_removed: number;
33
+ };
34
+ context_window: {
35
+ total_input_tokens: number;
36
+ total_output_tokens: number;
37
+ context_window_size: number;
38
+ current_usage: {
39
+ input_tokens: number;
40
+ output_tokens: number;
41
+ cache_creation_input_tokens: number;
42
+ cache_read_input_tokens: number;
43
+ } | null;
44
+ used_percentage: number | null;
45
+ remaining_percentage: number | null;
46
+ };
47
+ exceeds_200k_tokens: boolean;
48
+ vim?: {
49
+ mode: 'INSERT' | 'NORMAL';
50
+ };
51
+ }
52
+ export declare function loadClaudeStatusLineSetting(options: {
53
+ configRoot: string;
54
+ cwd: string;
55
+ settingSources?: readonly ClaudeResourceScope[];
56
+ }): Promise<{
57
+ setting?: ClaudeStatusLineSetting;
58
+ disabled: boolean;
59
+ }>;
60
+ export declare function executeClaudeStatusLine(setting: ClaudeStatusLineSetting, input: ClaudeStatusLineInput, options?: {
61
+ cwd: string;
62
+ signal?: AbortSignal;
63
+ timeoutMs?: number;
64
+ }): Promise<string | undefined>;
65
+ export declare function createClaudeStatusLineInput(options: {
66
+ configRoot: string;
67
+ cwd: string;
68
+ projectDir: string;
69
+ sessionId: string;
70
+ sessionName?: string | null;
71
+ model?: string;
72
+ version: string;
73
+ outputStyle: string;
74
+ permissionMode?: string;
75
+ additionalDirectories: readonly string[];
76
+ usage?: ModelUsage;
77
+ costUsd?: number;
78
+ contextWindowTokens?: number;
79
+ vimMode?: 'INSERT' | 'NORMAL';
80
+ }): ClaudeStatusLineInput;
81
+ export declare function StatusLine({ configRoot, cwd, input, refreshKey, settingSources, }: {
82
+ configRoot: string;
83
+ cwd: string;
84
+ input: ClaudeStatusLineInput;
85
+ refreshKey: string;
86
+ settingSources?: readonly ClaudeResourceScope[];
87
+ }): import("react").JSX.Element | null;
88
+ //# sourceMappingURL=status-line.d.ts.map
@@ -0,0 +1,234 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { spawn } from 'node:child_process';
3
+ import { watchFile, unwatchFile } from 'node:fs';
4
+ import { join, resolve } from 'node:path';
5
+ import { Box, Text } from 'ink';
6
+ import { useCallback, useEffect, useRef, useState } from 'react';
7
+ import { isClaudeSessionId, resolveClaudePaths, sanitizeClaudeProjectPath, } from '../../compatibility/claude/paths.js';
8
+ import { loadClaudeSettings } from '../../compatibility/claude/shared-resources.js';
9
+ import { commandShell, commandShellArguments, } from '../../platform/command-shell.js';
10
+ function record(value) {
11
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
12
+ ? value
13
+ : null;
14
+ }
15
+ export async function loadClaudeStatusLineSetting(options) {
16
+ const resources = await loadClaudeSettings(options);
17
+ const merged = Object.assign({}, ...resources.map((resource) => record(resource.value) ?? {}));
18
+ const value = record(merged.statusLine);
19
+ const setting = value?.type === 'command' && typeof value.command === 'string'
20
+ ? {
21
+ type: 'command',
22
+ command: value.command,
23
+ ...(typeof value.padding === 'number' &&
24
+ Number.isFinite(value.padding)
25
+ ? { padding: value.padding }
26
+ : {}),
27
+ }
28
+ : undefined;
29
+ return {
30
+ ...(setting ? { setting } : {}),
31
+ disabled: merged.disableAllHooks === true,
32
+ };
33
+ }
34
+ export async function executeClaudeStatusLine(setting, input, options = {
35
+ cwd: process.cwd(),
36
+ }) {
37
+ const timeout = AbortSignal.timeout(options.timeoutMs ?? 5000);
38
+ const signal = options.signal
39
+ ? AbortSignal.any([options.signal, timeout])
40
+ : timeout;
41
+ return new Promise((resolve) => {
42
+ let settled = false;
43
+ let stdout = '';
44
+ const finish = (value) => {
45
+ if (settled)
46
+ return;
47
+ settled = true;
48
+ resolve(value);
49
+ };
50
+ let child;
51
+ try {
52
+ child = spawn(commandShell(), commandShellArguments(setting.command), {
53
+ cwd: options.cwd,
54
+ detached: process.platform !== 'win32',
55
+ env: process.env,
56
+ stdio: ['pipe', 'pipe', 'ignore'],
57
+ });
58
+ }
59
+ catch {
60
+ finish();
61
+ return;
62
+ }
63
+ child.stdout.setEncoding('utf8');
64
+ const terminate = () => {
65
+ if (child.pid === undefined)
66
+ return;
67
+ try {
68
+ if (process.platform === 'win32')
69
+ child.kill('SIGKILL');
70
+ else
71
+ process.kill(-child.pid, 'SIGKILL');
72
+ }
73
+ catch (error) {
74
+ if (error.code !== 'ESRCH')
75
+ finish();
76
+ }
77
+ };
78
+ if (signal.aborted)
79
+ terminate();
80
+ else
81
+ signal.addEventListener('abort', terminate, { once: true });
82
+ child.stdout.on('data', (chunk) => {
83
+ if (stdout.length <= 1024 * 1024)
84
+ stdout += chunk;
85
+ });
86
+ child.on('error', () => {
87
+ signal.removeEventListener('abort', terminate);
88
+ finish();
89
+ });
90
+ child.on('close', (code) => {
91
+ signal.removeEventListener('abort', terminate);
92
+ if (code !== 0 || signal.aborted) {
93
+ finish();
94
+ return;
95
+ }
96
+ const output = stdout
97
+ .trim()
98
+ .split(/\r?\n/u)
99
+ .map((line) => line.trim())
100
+ .filter(Boolean)
101
+ .join('\n');
102
+ finish(output || undefined);
103
+ });
104
+ child.stdin.on('error', () => undefined);
105
+ child.stdin.end(JSON.stringify(input));
106
+ });
107
+ }
108
+ export function createClaudeStatusLineInput(options) {
109
+ const usage = options.usage;
110
+ const contextInputTokens = usage
111
+ ? usage.inputTokens +
112
+ (usage.cacheReadInputTokens ?? 0) +
113
+ (usage.cacheCreationInputTokens ?? 0)
114
+ : null;
115
+ const usedTokens = contextInputTokens === null || usage === undefined
116
+ ? null
117
+ : contextInputTokens + usage.outputTokens;
118
+ const contextWindowSize = options.contextWindowTokens ?? 0;
119
+ const usedPercentage = contextInputTokens === null || contextWindowSize <= 0
120
+ ? null
121
+ : Math.min(100, Math.max(0, Math.round((contextInputTokens / contextWindowSize) * 100)));
122
+ const model = options.model ?? 'default';
123
+ const transcriptPath = isClaudeSessionId(options.sessionId)
124
+ ? resolveClaudePaths({
125
+ configDir: options.configRoot,
126
+ cwd: options.cwd,
127
+ sessionId: options.sessionId,
128
+ }).sessionFile
129
+ : resolve(options.configRoot, 'projects', sanitizeClaudeProjectPath(options.cwd), `${options.sessionId}.jsonl`);
130
+ return {
131
+ session_id: options.sessionId,
132
+ ...(options.sessionName ? { session_name: options.sessionName } : {}),
133
+ transcript_path: transcriptPath,
134
+ cwd: options.cwd,
135
+ ...(options.permissionMode
136
+ ? { permission_mode: options.permissionMode }
137
+ : {}),
138
+ model: { id: model, display_name: model },
139
+ workspace: {
140
+ current_dir: options.cwd,
141
+ project_dir: options.projectDir,
142
+ added_dirs: options.additionalDirectories,
143
+ },
144
+ version: options.version,
145
+ output_style: { name: options.outputStyle },
146
+ cost: {
147
+ total_cost_usd: options.costUsd ?? 0,
148
+ total_duration_ms: 0,
149
+ total_api_duration_ms: 0,
150
+ total_lines_added: 0,
151
+ total_lines_removed: 0,
152
+ },
153
+ context_window: {
154
+ total_input_tokens: usage?.inputTokens ?? 0,
155
+ total_output_tokens: usage?.outputTokens ?? 0,
156
+ context_window_size: contextWindowSize,
157
+ current_usage: usage
158
+ ? {
159
+ input_tokens: usage.inputTokens,
160
+ output_tokens: usage.outputTokens,
161
+ cache_creation_input_tokens: usage.cacheCreationInputTokens ?? 0,
162
+ cache_read_input_tokens: usage.cacheReadInputTokens ?? 0,
163
+ }
164
+ : null,
165
+ used_percentage: usedPercentage,
166
+ remaining_percentage: usedPercentage === null ? null : 100 - usedPercentage,
167
+ },
168
+ exceeds_200k_tokens: usedTokens !== null && usedTokens > 200_000,
169
+ ...(options.vimMode ? { vim: { mode: options.vimMode } } : {}),
170
+ };
171
+ }
172
+ export function StatusLine({ configRoot, cwd, input, refreshKey, settingSources, }) {
173
+ const [text, setText] = useState();
174
+ const [padding, setPadding] = useState(0);
175
+ const timer = useRef(undefined);
176
+ const controller = useRef(undefined);
177
+ const latestInput = useRef(input);
178
+ latestInput.current = input;
179
+ const update = useCallback(async () => {
180
+ controller.current?.abort();
181
+ const current = new AbortController();
182
+ controller.current = current;
183
+ try {
184
+ const loaded = await loadClaudeStatusLineSetting({
185
+ configRoot,
186
+ cwd,
187
+ ...(settingSources === undefined ? {} : { settingSources }),
188
+ });
189
+ if (current.signal.aborted)
190
+ return;
191
+ setPadding(loaded.setting?.padding ?? 0);
192
+ if (loaded.disabled || !loaded.setting) {
193
+ setText(undefined);
194
+ return;
195
+ }
196
+ const result = await executeClaudeStatusLine(loaded.setting, latestInput.current, { cwd, signal: current.signal });
197
+ if (!current.signal.aborted)
198
+ setText(result);
199
+ }
200
+ catch {
201
+ if (!current.signal.aborted)
202
+ setText(undefined);
203
+ }
204
+ }, [configRoot, cwd, settingSources]);
205
+ const schedule = useCallback(() => {
206
+ if (timer.current)
207
+ clearTimeout(timer.current);
208
+ timer.current = setTimeout(() => void update(), 300);
209
+ }, [update]);
210
+ useEffect(() => {
211
+ schedule();
212
+ }, [refreshKey, schedule]);
213
+ useEffect(() => {
214
+ const paths = [
215
+ join(configRoot, 'settings.json'),
216
+ join(cwd, '.claude', 'settings.json'),
217
+ join(cwd, '.claude', 'settings.local.json'),
218
+ ];
219
+ const changed = () => schedule();
220
+ for (const path of paths)
221
+ watchFile(path, { interval: 500 }, changed);
222
+ return () => {
223
+ for (const path of paths)
224
+ unwatchFile(path, changed);
225
+ controller.current?.abort();
226
+ if (timer.current)
227
+ clearTimeout(timer.current);
228
+ };
229
+ }, [configRoot, cwd, schedule]);
230
+ if (!text)
231
+ return null;
232
+ return (_jsx(Box, { paddingX: padding, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: text }) }));
233
+ }
234
+ //# sourceMappingURL=status-line.js.map
package/dist/cli.d.ts CHANGED
@@ -96,6 +96,7 @@ interface TopLevelAgentCommands {
96
96
  attach(id: string, input: AsyncIterable<string | Uint8Array>, output: (text: string) => void, signal?: AbortSignal): Promise<void>;
97
97
  }
98
98
  export interface CliDependencies extends InteractiveServiceFactory {
99
+ loadReleaseNotes?(configRoot: string): Promise<string>;
99
100
  createService(options: {
100
101
  eventSink: RuntimeEventSink;
101
102
  requireProvider: boolean;
package/dist/cli.js CHANGED
@@ -14,6 +14,7 @@ import { isClaudeSessionId, resolveClaudePaths, } from './compatibility/claude/p
14
14
  import { loadClaudeContextResources, loadClaudeSettings, loadClaudeSharedResources, resolveClaudeProjectMemoryDirectory, } from './compatibility/claude/shared-resources.js';
15
15
  import { AgentRunCancelledError, } from './core/runtime.js';
16
16
  import { persistTuiPermissionUpdates } from './cli/tui/permission-settings.js';
17
+ import { loadClaudeReleaseNotes } from './cli/tui/release-notes.js';
17
18
  import { projectTuiHooks, } from './cli/tui/hook-settings.js';
18
19
  import { DEFAULT_CLI_CONTROLS, resolveCliControls } from './cli/controls.js';
19
20
  import { applyRuntimeSettingDefaults, loadRuntimeSettings, runtimeSettingsSystemPrompt, } from './cli/tui/runtime-settings.js';
@@ -1609,6 +1610,9 @@ const defaultDependencies = {
1609
1610
  ? { allowDangerouslySkipPermissions: true }
1610
1611
  : {}),
1611
1612
  additionalDirectories: initialAdditionalDirectories,
1613
+ ...(interactiveControls.settingSources === undefined
1614
+ ? {}
1615
+ : { settingSources: interactiveControls.settingSources }),
1612
1616
  display: {
1613
1617
  version: VERSION,
1614
1618
  cwd: process.cwd(),
@@ -3856,6 +3860,48 @@ async function execute(argv, io, dependencies, signal) {
3856
3860
  : {}),
3857
3861
  };
3858
3862
  };
3863
+ const headlessPromptArgs = command === 'resume' ? args.slice(2) : knownCommand ? args.slice(1) : args;
3864
+ const headlessPrompt = inputFormat === 'text' && headlessPromptArgs.length > 0
3865
+ ? promptFrom(headlessPromptArgs)
3866
+ : undefined;
3867
+ if (headlessPrompt === '/release-notes' && !invocation.disableSlashCommands) {
3868
+ const startedAt = Date.now();
3869
+ const sessionId = invocation.sessionId ?? randomUUID();
3870
+ const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'));
3871
+ const text = dependencies.loadReleaseNotes
3872
+ ? await dependencies.loadReleaseNotes(configRoot)
3873
+ : await loadClaudeReleaseNotes({ configRoot });
3874
+ const result = {
3875
+ sessionId,
3876
+ text,
3877
+ usage: { inputTokens: 0, outputTokens: 0 },
3878
+ };
3879
+ const info = {
3880
+ cwd: process.cwd(),
3881
+ model: invocation.model ?? process.env.PRAXIS_MODEL ?? 'unknown',
3882
+ tools: [],
3883
+ mcpServers: [],
3884
+ permissionMode: invocation.permissionMode,
3885
+ slashCommands: ['release-notes'],
3886
+ agents: [],
3887
+ skills: [],
3888
+ claudeCodeVersion: '2.1.208',
3889
+ };
3890
+ if (outputFormat === 'stream-json') {
3891
+ const output = new StreamJsonOutput((value) => writeJson(io, value), info, sessionId, includePartialMessages, invocation.includeHookEvents);
3892
+ output.init();
3893
+ output.sink({ type: 'text-delta', delta: text });
3894
+ output.sink({ type: 'usage', usage: result.usage });
3895
+ output.result(result, startedAt);
3896
+ }
3897
+ else if (outputFormat === 'json' || invocation.legacyJson) {
3898
+ writeJson(io, createSuccessResult(result, info, startedAt, 0));
3899
+ }
3900
+ else {
3901
+ io.stdout(`${text}\n`);
3902
+ }
3903
+ return 0;
3904
+ }
3859
3905
  const service = await dependencies.createService({
3860
3906
  eventSink: outputFormat === 'stream-json' && !invocation.legacyJson
3861
3907
  ? (event) => {
@@ -28,6 +28,7 @@ export interface ClaudePromptExpansionMessage {
28
28
  contentBlocks?: readonly ModelContentBlock[];
29
29
  images?: readonly ModelImage[];
30
30
  }
31
+ export declare const BUILTIN_STATUSLINE_AGENT_PATH = "/__praxis_builtin__/agents/statusline-setup.md";
31
32
  export declare function validateClaudeExtensions(resources: readonly ClaudeTextResource[]): void;
32
33
  export declare class ClaudeExtensionCatalog {
33
34
  private readonly commands;
@@ -34,6 +34,49 @@ Call CronCreate with the derived cron, the parsed prompt verbatim, and recurring
34
34
  Input:
35
35
  $ARGUMENTS`,
36
36
  };
37
+ const BUILTIN_STATUSLINE_COMMAND = {
38
+ path: '/__praxis_builtin__/commands/statusline.md',
39
+ scope: 'user',
40
+ content: '',
41
+ kind: 'command',
42
+ name: 'statusline',
43
+ description: "Set up Claude Code's status line UI",
44
+ modelInvocable: true,
45
+ permissionSafe: true,
46
+ body: `Create an Agent with subagent_type "statusline-setup" and the prompt "$ARGUMENTS"`,
47
+ };
48
+ export const BUILTIN_STATUSLINE_AGENT_PATH = '/__praxis_builtin__/agents/statusline-setup.md';
49
+ const BUILTIN_STATUSLINE_AGENT = {
50
+ path: BUILTIN_STATUSLINE_AGENT_PATH,
51
+ scope: 'user',
52
+ content: '',
53
+ kind: 'agent',
54
+ name: 'statusline-setup',
55
+ description: "Configure the user's Claude Code status line setting.",
56
+ modelInvocable: false,
57
+ permissionSafe: false,
58
+ body: `You configure the statusLine command in the user's shared Claude Code settings.
59
+
60
+ Importing a shell prompt:
61
+ 1. Inspect shell configuration in this order: ~/.zshrc, ~/.bashrc, ~/.bash_profile, then ~/.profile.
62
+ 2. Find an exported or unexported quoted PS1 assignment. Preserve the prompt's information and colors.
63
+ 3. Translate prompt escapes into commands: user to $(whoami), short/full host to $(hostname -s)/$(hostname), working directory to $(pwd), basename to $(basename "$(pwd)"), and time/date escapes to the corresponding date command. Preserve newlines and literal prompt counters.
64
+ 4. Emit ANSI sequences with printf because the result is rendered by a terminal in dimmed colors.
65
+ 5. Remove a trailing prompt marker such as "$" or ">" from the displayed status line.
66
+ 6. If no PS1 exists and the request gives no other design, ask what information should be displayed.
67
+
68
+ The status command receives one JSON document on stdin. It contains session_id, optional session_name, transcript_path, cwd, model.id and model.display_name, workspace.current_dir/project_dir/added_dirs, version, output_style.name, cost totals, context_window totals/current_usage/used_percentage/remaining_percentage, exceeds_200k_tokens, and optional vim, agent, and worktree objects. Read stdin once and reuse it when extracting multiple values. Typical jq selectors include .model.display_name, .workspace.current_dir, .output_style.name, .context_window.remaining_percentage, and .context_window.used_percentage. Optional values must be tested before printing them.
69
+
70
+ Preserve every unrelated setting and update ~/.claude/settings.json with this shape:
71
+ {
72
+ "statusLine": {
73
+ "type": "command",
74
+ "command": "the command"
75
+ }
76
+ }
77
+
78
+ Resolve a symlinked settings file and edit its target. Complex implementations may live in a script under ~/.claude and be referenced by the setting. Git commands in that script must avoid optional locks. Report the exact configuration and script path, if any. End by telling the parent agent that future status-line changes must use the statusline-setup agent and that the user can request further changes.`,
79
+ };
37
80
  const GENERAL_PURPOSE_AGENT = {
38
81
  name: 'general-purpose',
39
82
  description: 'General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks.',
@@ -175,7 +218,10 @@ export class ClaudeExtensionCatalog {
175
218
  this.disableSlashCommands = options.disableSlashCommands === true;
176
219
  this.commands = options.disableSlashCommands
177
220
  ? new Map()
178
- : new Map([['loop', BUILTIN_LOOP]]);
221
+ : new Map([
222
+ ['loop', BUILTIN_LOOP],
223
+ ['statusline', BUILTIN_STATUSLINE_COMMAND],
224
+ ]);
179
225
  if (!options.disableSlashCommands) {
180
226
  for (const [name, command] of indexed('command', resources.commands)) {
181
227
  this.commands.set(name, command);
@@ -184,7 +230,10 @@ export class ClaudeExtensionCatalog {
184
230
  this.skills = options.disableSlashCommands
185
231
  ? new Map()
186
232
  : indexed('skill', resources.skills);
187
- this.agents = indexed('agent', resources.agents);
233
+ this.agents = new Map([
234
+ ['statusline-setup', BUILTIN_STATUSLINE_AGENT],
235
+ ...indexed('agent', resources.agents),
236
+ ]);
188
237
  }
189
238
  setMcpPrompts(prompts) {
190
239
  this.mcpPrompts.clear();
@@ -208,6 +257,9 @@ export class ClaudeExtensionCatalog {
208
257
  if (!definition) {
209
258
  return { userMessages: [prompt] };
210
259
  }
260
+ const invocationArguments = definition === BUILTIN_STATUSLINE_COMMAND && argumentsText.length === 0
261
+ ? 'Configure my statusLine from my shell PS1 configuration'
262
+ : argumentsText;
211
263
  return {
212
264
  userMessages: [
213
265
  [
@@ -217,7 +269,7 @@ export class ClaudeExtensionCatalog {
217
269
  ? [`<command-args>${argumentsText}</command-args>`]
218
270
  : []),
219
271
  ].join('\n'),
220
- renderInvocation(definition, argumentsText),
272
+ renderInvocation(definition, invocationArguments),
221
273
  ],
222
274
  };
223
275
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "praxis-agent",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Local-first, single-user general agent for the command line.",
5
5
  "license": "MIT",
6
6
  "author": "wuqisen",