praxis-agent 0.16.0 → 0.17.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.
Files changed (43) hide show
  1. package/README.md +11 -5
  2. package/dist/application/background-agent-manager.d.ts +7 -1
  3. package/dist/application/background-agent-manager.js +148 -5
  4. package/dist/application/metered-model-completion.d.ts +15 -0
  5. package/dist/application/metered-model-completion.js +76 -0
  6. package/dist/application/model-compactor.js +25 -1
  7. package/dist/application/session-cost-tracker.d.ts +50 -0
  8. package/dist/application/session-cost-tracker.js +254 -0
  9. package/dist/application/session-service.d.ts +13 -0
  10. package/dist/application/session-service.js +594 -69
  11. package/dist/application/subagent-service.d.ts +11 -1
  12. package/dist/application/subagent-service.js +41 -1
  13. package/dist/application/tool-use-summary.d.ts +7 -2
  14. package/dist/application/tool-use-summary.js +42 -10
  15. package/dist/application/workflow-manager.d.ts +9 -4
  16. package/dist/application/workflow-manager.js +151 -7
  17. package/dist/cli/interactive.d.ts +5 -1
  18. package/dist/cli/interactive.js +227 -15
  19. package/dist/cli/protocol.d.ts +11 -0
  20. package/dist/cli/protocol.js +16 -1
  21. package/dist/cli/tui/claude-command-inventory.d.ts +27 -27
  22. package/dist/cli/tui/claude-command-inventory.js +26 -26
  23. package/dist/cli/tui/config-dashboard.d.ts +2 -9
  24. package/dist/cli/tui/config-dashboard.js +6 -6
  25. package/dist/cli/tui/cost-summary.d.ts +34 -0
  26. package/dist/cli/tui/cost-summary.js +192 -0
  27. package/dist/cli/tui/doctor-dashboard.d.ts +15 -0
  28. package/dist/cli/tui/doctor-dashboard.js +81 -0
  29. package/dist/cli/tui/slash-commands.js +10 -0
  30. package/dist/cli.d.ts +2 -0
  31. package/dist/cli.js +192 -15
  32. package/dist/core/compaction.d.ts +2 -0
  33. package/dist/core/runtime.d.ts +38 -1
  34. package/dist/core/runtime.js +265 -11
  35. package/dist/persistence/claude-cost-state-store.d.ts +48 -0
  36. package/dist/persistence/claude-cost-state-store.js +335 -0
  37. package/dist/providers/anthropic-compatible.js +34 -9
  38. package/dist/providers/fallback-provider.d.ts +2 -1
  39. package/dist/providers/fallback-provider.js +28 -3
  40. package/dist/tools/line-changes.d.ts +8 -0
  41. package/dist/tools/line-changes.js +35 -0
  42. package/dist/tools/local-tools.js +30 -1
  43. package/package.json +2 -1
@@ -4,6 +4,7 @@ import { existsSync } from 'node:fs';
4
4
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
5
5
  import { homedir, tmpdir } from 'node:os';
6
6
  import { join, resolve } from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
7
8
  import { useEffect, useMemo, useRef, useState } from 'react';
8
9
  import { Box, Text, render, useApp, useInput } from 'ink';
9
10
  import { permissionRuleValueToString } from '../permissions/permission-updates.js';
@@ -19,6 +20,8 @@ import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule,
19
20
  import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
20
21
  import { agentColorMessage, parseAgentColorInput, } from '../compatibility/claude/agent-color.js';
21
22
  import { filterTuiSlashCommands, mergeTuiSlashCommands, slashCommandQuery, } from './tui/slash-commands.js';
23
+ import { runDoctor } from '../maintenance/doctor.js';
24
+ import { canonicalClaudeCostModelName, } from './tui/cost-summary.js';
22
25
  import { createComposerEditor, deleteComposerBackward, deleteComposerForward, deleteComposerToEnd, deleteComposerToStart, deleteComposerWordBackward, insertComposerText, moveComposerCursor, moveComposerCursorByWord, } from './tui/composer-editor.js';
23
26
  import { applyMentionReference, fileReferenceAtCursor, filterTuiMentionEntries, loadTuiFileEntries, } from './tui/file-picker.js';
24
27
  import { editTuiPrompt, openTuiEditorFile, } from './tui/external-editor.js';
@@ -36,6 +39,7 @@ import { commitElicitationText, createTuiElicitationForm, elicitationFormIsValid
36
39
  import { openTuiUrl } from './tui/open-url.js';
37
40
  import { projectTuiToolPermission, ToolPermissionDialog, } from './tui/tool-permission.js';
38
41
  import { ConfigDashboard, projectConfigRows } from './tui/config-dashboard.js';
42
+ import { DoctorDashboard } from './tui/doctor-dashboard.js';
39
43
  import { SandboxDashboard, tuiSandboxTabs } from './tui/sandbox-dashboard.js';
40
44
  import { createTuiSandboxStore, } from './tui/sandbox-settings.js';
41
45
  import { loadConfigSettings, saveConfigSetting, configSettingDefinition, resolveConfigSettingsLocation, } from './tui/config-settings.js';
@@ -93,6 +97,15 @@ const PERMISSION_OPTIONS = [
93
97
  description: 'Restrict changes to the plan file until approval.',
94
98
  },
95
99
  ];
100
+ const ZERO_COST_SUMMARY = {
101
+ totalCostUsd: 0,
102
+ apiDurationMs: 0,
103
+ wallDurationMs: 0,
104
+ linesAdded: 0,
105
+ linesRemoved: 0,
106
+ hasUnknownModelCost: false,
107
+ modelUsage: [],
108
+ };
96
109
  function selectionPrefix(selected, screenReader) {
97
110
  if (selected)
98
111
  return screenReader ? 'Selected: ' : '❯ ';
@@ -248,7 +261,7 @@ const HIDDEN_TUI_SLASH_COMMANDS = new Set([
248
261
  'update',
249
262
  'usage',
250
263
  ]);
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, }) {
264
+ 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, doctorLoader, 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, }) {
252
265
  const { exit, suspendTerminal, waitUntilRenderFlush } = useApp();
253
266
  const width = useTerminalWidth(terminalWidth);
254
267
  const keybindingsRoot = useMemo(() => resolve(keybindingsConfigRoot ??
@@ -260,6 +273,25 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
260
273
  const runtimeCwdRef = useRef(display.cwd);
261
274
  const runtimeGitignoreRef = useRef(true);
262
275
  const loadDiffSnapshot = useMemo(() => diffLoader ?? (() => loadGitDiff(runtimeCwd)), [diffLoader, runtimeCwd]);
276
+ const loadDoctorReport = useMemo(() => doctorLoader ??
277
+ (() => {
278
+ const configuredRoot = process.env.CLAUDE_CONFIG_DIR || undefined;
279
+ const configRoot = resolve(configuredRoot ?? resolve(homedir(), '.claude'));
280
+ const claudeStatePath = configuredRoot
281
+ ? join(configRoot, '.claude.json')
282
+ : resolve(homedir(), '.claude.json');
283
+ return runDoctor({
284
+ version: display.version,
285
+ executablePath: resolve(process.argv[1] ??
286
+ fileURLToPath(new URL('../cli.js', import.meta.url))),
287
+ nodeExecutablePath: process.execPath,
288
+ nodeVersion: process.version,
289
+ configRoot,
290
+ claudeStatePath,
291
+ cwd: runtimeCwd,
292
+ environment: process.env,
293
+ });
294
+ }), [doctorLoader, runtimeCwd, display.version]);
263
295
  const loadFiles = useMemo(() => fileLoader ??
264
296
  (() => loadTuiFileEntries(runtimeCwd, {
265
297
  respectGitignore: runtimeGitignoreRef.current,
@@ -355,6 +387,11 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
355
387
  const btwCopiedTimerRef = useRef(null);
356
388
  const [menu, setMenu] = useState(null);
357
389
  const menuRef = useRef(null);
390
+ const configOpenGenerationRef = useRef(0);
391
+ const configUsageRequestRef = useRef(0);
392
+ const configOperationRef = useRef(null);
393
+ const doctorMenuGenerationRef = useRef(0);
394
+ const doctorOperationRef = useRef(null);
358
395
  const [busy, setBusy] = useState(false);
359
396
  const taskEntriesRef = useRef([]);
360
397
  const mcpControllerRef = useRef(null);
@@ -924,6 +961,25 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
924
961
  })();
925
962
  }, [processSuspendRequested]);
926
963
  const updateMenu = (next) => {
964
+ if (next === null && menuRef.current?.kind === 'config') {
965
+ configOpenGenerationRef.current += 1;
966
+ configUsageRequestRef.current += 1;
967
+ const closingConfigOperation = configOperationRef.current;
968
+ configOperationRef.current = null;
969
+ if (closingConfigOperation) {
970
+ setBusy(false);
971
+ onTurnChange?.(null);
972
+ }
973
+ }
974
+ if (next === null && menuRef.current?.kind === 'doctor') {
975
+ doctorMenuGenerationRef.current += 1;
976
+ const closingDoctorOperation = doctorOperationRef.current;
977
+ doctorOperationRef.current = null;
978
+ if (closingDoctorOperation) {
979
+ setBusy(false);
980
+ onTurnChange?.(null);
981
+ }
982
+ }
927
983
  menuRef.current = next;
928
984
  setMenu(next);
929
985
  };
@@ -1634,32 +1690,181 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1634
1690
  messageAge: position - 1,
1635
1691
  });
1636
1692
  };
1693
+ const loadCostUsage = async (sessionId) => {
1694
+ if (!sessionId)
1695
+ return ZERO_COST_SUMMARY;
1696
+ return withLocalCommands(async (commands) => {
1697
+ if (!commands.costSnapshot) {
1698
+ throw new Error('This interactive service cannot report session cost.');
1699
+ }
1700
+ const snapshot = await commands.costSnapshot(sessionId);
1701
+ return {
1702
+ totalCostUsd: snapshot.totalCostUsd,
1703
+ apiDurationMs: snapshot.apiDurationMs,
1704
+ wallDurationMs: snapshot.wallDurationMs,
1705
+ linesAdded: snapshot.linesAdded,
1706
+ linesRemoved: snapshot.linesRemoved,
1707
+ hasUnknownModelCost: snapshot.hasUnknownModelCost,
1708
+ modelUsage: Object.entries(snapshot.modelUsage).map(([model, usage]) => ({
1709
+ model,
1710
+ canonicalName: canonicalClaudeCostModelName(model),
1711
+ inputTokens: usage.inputTokens,
1712
+ outputTokens: usage.outputTokens,
1713
+ cacheReadInputTokens: usage.cacheReadInputTokens,
1714
+ cacheCreationInputTokens: usage.cacheCreationInputTokens,
1715
+ webSearchRequests: usage.webSearchRequests,
1716
+ costUsd: usage.costUsd,
1717
+ })),
1718
+ };
1719
+ });
1720
+ };
1721
+ const loadConfigMenuUsage = (generation, sessionId) => {
1722
+ const requestId = ++configUsageRequestRef.current;
1723
+ const loading = (async () => {
1724
+ setBusy(true);
1725
+ try {
1726
+ const usage = await loadCostUsage(sessionId);
1727
+ const current = menuRef.current;
1728
+ if (current?.kind === 'config' &&
1729
+ current.generation === generation &&
1730
+ current.tab === 'usage' &&
1731
+ sessionIdRef.current === sessionId &&
1732
+ configUsageRequestRef.current === requestId) {
1733
+ updateMenu({ ...current, usage });
1734
+ }
1735
+ }
1736
+ catch (error) {
1737
+ const current = menuRef.current;
1738
+ if (current?.kind === 'config' &&
1739
+ current.generation === generation &&
1740
+ current.tab === 'usage' &&
1741
+ sessionIdRef.current === sessionId &&
1742
+ configUsageRequestRef.current === requestId) {
1743
+ warn(error);
1744
+ }
1745
+ }
1746
+ })();
1747
+ configOperationRef.current = loading;
1748
+ onTurnChange?.(loading);
1749
+ void loading.finally(() => {
1750
+ if (configOperationRef.current === loading) {
1751
+ configOperationRef.current = null;
1752
+ setBusy(false);
1753
+ onTurnChange?.(null);
1754
+ }
1755
+ });
1756
+ };
1637
1757
  const openSettings = (tab) => {
1758
+ const generation = ++configOpenGenerationRef.current;
1759
+ const sessionId = sessionIdRef.current;
1760
+ const requestId = tab === 'usage' ? ++configUsageRequestRef.current : 0;
1638
1761
  const initial = {
1639
1762
  kind: 'config',
1763
+ generation,
1640
1764
  snapshot: { settings: {}, state: {} },
1641
1765
  tab,
1642
1766
  selectedIndex: 0,
1643
1767
  query: '',
1644
1768
  searchFocused: tab === 'config',
1769
+ usage: ZERO_COST_SUMMARY,
1645
1770
  };
1646
1771
  updateMenu(initial);
1647
1772
  const loading = (async () => {
1648
1773
  setBusy(true);
1649
1774
  try {
1650
- const snapshot = await loadConfigSettings(configTarget);
1651
- if (menuRef.current?.kind === 'config')
1652
- updateMenu({ ...menuRef.current, snapshot });
1775
+ const [snapshot, usage] = await Promise.all([
1776
+ loadConfigSettings(configTarget),
1777
+ tab === 'usage'
1778
+ ? loadCostUsage(sessionId)
1779
+ : Promise.resolve(ZERO_COST_SUMMARY),
1780
+ ]);
1781
+ const current = menuRef.current;
1782
+ if (current?.kind === 'config' && current.generation === generation) {
1783
+ const next = { ...current, snapshot };
1784
+ if (current.tab === 'usage' &&
1785
+ sessionIdRef.current === sessionId &&
1786
+ configUsageRequestRef.current === requestId) {
1787
+ next.usage = usage;
1788
+ }
1789
+ updateMenu(next);
1790
+ }
1653
1791
  }
1654
1792
  catch (error) {
1655
- warn(error);
1793
+ const current = menuRef.current;
1794
+ if (tab === 'usage') {
1795
+ if (current?.kind === 'config' &&
1796
+ current.generation === generation &&
1797
+ current.tab === 'usage' &&
1798
+ sessionIdRef.current === sessionId &&
1799
+ configUsageRequestRef.current === requestId) {
1800
+ warn(error);
1801
+ }
1802
+ }
1803
+ else if (configOpenGenerationRef.current === generation) {
1804
+ warn(error);
1805
+ }
1656
1806
  }
1657
- finally {
1807
+ })();
1808
+ configOperationRef.current = loading;
1809
+ onTurnChange?.(loading);
1810
+ void loading.finally(() => {
1811
+ if (configOperationRef.current === loading) {
1812
+ configOperationRef.current = null;
1658
1813
  setBusy(false);
1814
+ onTurnChange?.(null);
1815
+ }
1816
+ });
1817
+ };
1818
+ const openDoctor = () => {
1819
+ const generation = ++doctorMenuGenerationRef.current;
1820
+ updateMenu({
1821
+ kind: 'doctor',
1822
+ generation,
1823
+ loading: true,
1824
+ report: null,
1825
+ error: null,
1826
+ });
1827
+ const loading = (async () => {
1828
+ setBusy(true);
1829
+ try {
1830
+ const report = await loadDoctorReport();
1831
+ const current = menuRef.current;
1832
+ if (current?.kind === 'doctor' && current.generation === generation) {
1833
+ updateMenu({
1834
+ kind: 'doctor',
1835
+ generation,
1836
+ loading: false,
1837
+ report,
1838
+ error: null,
1839
+ });
1840
+ }
1841
+ }
1842
+ catch (error) {
1843
+ const current = menuRef.current;
1844
+ if (current?.kind === 'doctor' && current.generation === generation) {
1845
+ updateMenu({
1846
+ kind: 'doctor',
1847
+ generation,
1848
+ loading: false,
1849
+ report: null,
1850
+ error: redactSensitiveText(error instanceof Error ? error.message : String(error), sensitiveValues),
1851
+ });
1852
+ }
1853
+ }
1854
+ finally {
1855
+ if (doctorMenuGenerationRef.current === generation)
1856
+ setBusy(false);
1659
1857
  }
1660
1858
  })();
1859
+ doctorOperationRef.current = loading;
1661
1860
  onTurnChange?.(loading);
1662
- void loading.finally(() => onTurnChange?.(null));
1861
+ void loading.finally(() => {
1862
+ if (doctorOperationRef.current === loading) {
1863
+ doctorOperationRef.current = null;
1864
+ setBusy(false);
1865
+ onTurnChange?.(null);
1866
+ }
1867
+ });
1663
1868
  };
1664
1869
  const exportConversation = (method) => {
1665
1870
  if (method === 'file') {
@@ -3316,6 +3521,8 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3316
3521
  selectedIndex: 0,
3317
3522
  searchFocused: tab === 'config',
3318
3523
  });
3524
+ if (tab === 'usage')
3525
+ loadConfigMenuUsage(earlyMenu.generation, sessionIdRef.current);
3319
3526
  return;
3320
3527
  }
3321
3528
  if (earlyMenu.tab !== 'config')
@@ -4647,6 +4854,12 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
4647
4854
  }
4648
4855
  return;
4649
4856
  }
4857
+ if (activeMenu.kind === 'doctor') {
4858
+ if (key.escape || value === '\u001B' || key.return) {
4859
+ updateMenu(null);
4860
+ }
4861
+ return;
4862
+ }
4650
4863
  if (key.escape) {
4651
4864
  updateMenu(null);
4652
4865
  return;
@@ -5239,6 +5452,12 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
5239
5452
  onTurnChange?.(loading);
5240
5453
  void loading.finally(() => onTurnChange?.(null));
5241
5454
  }
5455
+ else if (prompt === '/cost') {
5456
+ openSettings('usage');
5457
+ }
5458
+ else if (prompt === '/doctor') {
5459
+ openDoctor();
5460
+ }
5242
5461
  else if (prompt === '/status') {
5243
5462
  openSettings('status');
5244
5463
  }
@@ -5446,14 +5665,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
5446
5665
  ...(statusProxy ? { proxy: statusProxy } : {}),
5447
5666
  model: runtimeDisplay.model ?? 'default',
5448
5667
  settingSources: statusSettingSources.split(', '),
5449
- }, usage: {
5450
- costUsd: costUsd ?? 0,
5451
- apiDurationMs: 0,
5452
- wallDurationMs: 0,
5453
- linesAdded: 0,
5454
- linesRemoved: 0,
5455
- usage: usage ?? { inputTokens: 0, outputTokens: 0 },
5456
- }, width: width, screenReader: axScreenReader })) : menu.kind === 'mcp' ? (_jsx(McpPanel, { model: menu.model, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'tasks' ? (_jsx(TaskPanel, { tasks: menu.tasks, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'memory' ? (_jsx(MemoryDashboard, { autoMemoryEnabled: menu.autoMemoryEnabled, entries: menu.entries, selectedIndex: menu.selectedIndex, openedIndex: menu.openedIndex, loading: menu.loading, width: width, screenReader: axScreenReader })) : menu.kind === 'hooks' ? (_jsx(HookDashboard, { configuration: menu.configuration, depth: menu.depth, eventIndex: menu.eventIndex, matcherIndex: menu.matcherIndex, hookIndex: menu.hookIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'list' ? (_jsx(ListDashboard, { title: menu.title, rows: menu.rows, emptyText: menu.emptyText, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'model' ? (_jsx(ModelMenu, { options: modelOptions, effort: runtimePreferences.effort, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'effort' ? (_jsx(SelectionMenu, { title: "Select effort", description: "Controls how much reasoning effort the provider should use.", options: EFFORT_OPTIONS.map((option) => ({
5668
+ }, usage: menu.usage, width: width, screenReader: axScreenReader })) : menu.kind === 'doctor' ? (_jsx(DoctorDashboard, { loading: menu.loading, report: menu.report, error: menu.error, width: width, screenReader: axScreenReader })) : menu.kind === 'mcp' ? (_jsx(McpPanel, { model: menu.model, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'tasks' ? (_jsx(TaskPanel, { tasks: menu.tasks, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'memory' ? (_jsx(MemoryDashboard, { autoMemoryEnabled: menu.autoMemoryEnabled, entries: menu.entries, selectedIndex: menu.selectedIndex, openedIndex: menu.openedIndex, loading: menu.loading, width: width, screenReader: axScreenReader })) : menu.kind === 'hooks' ? (_jsx(HookDashboard, { configuration: menu.configuration, depth: menu.depth, eventIndex: menu.eventIndex, matcherIndex: menu.matcherIndex, hookIndex: menu.hookIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'list' ? (_jsx(ListDashboard, { title: menu.title, rows: menu.rows, emptyText: menu.emptyText, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'model' ? (_jsx(ModelMenu, { options: modelOptions, effort: runtimePreferences.effort, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'effort' ? (_jsx(SelectionMenu, { title: "Select effort", description: "Controls how much reasoning effort the provider should use.", options: EFFORT_OPTIONS.map((option) => ({
5457
5669
  label: option,
5458
5670
  description: option === 'low'
5459
5671
  ? 'Fastest and least deliberative.'
@@ -180,6 +180,7 @@ export interface CliRuntimeInfo {
180
180
  cwd: string;
181
181
  model: string;
182
182
  contextWindowTokens?: number;
183
+ maxOutputTokens?: number;
183
184
  tools: readonly string[];
184
185
  mcpServers: readonly {
185
186
  name: string;
@@ -203,6 +204,11 @@ export interface ProtocolResult {
203
204
  durationApiMs?: number;
204
205
  costUsd?: number;
205
206
  modelUsage?: Readonly<Record<string, ModelUsage>>;
207
+ /**
208
+ * Exact per-model cost in USD. When present, each model's costUSD is taken
209
+ * from this map instead of assigning the total cost to runtimeInfo.model.
210
+ */
211
+ modelCostUsd?: Readonly<Record<string, number>>;
206
212
  }
207
213
  export declare function createSuccessResult(result: ProtocolResult, info: CliRuntimeInfo, startedAt: number, modelTurns: number): Record<string, unknown>;
208
214
  export declare function createErrorResult(message: string, sessionId: string, startedAt: number, modelTurns: number): Record<string, unknown>;
@@ -213,6 +219,11 @@ export declare function createErrorResult(message: string, sessionId: string, st
213
219
  * the command.
214
220
  */
215
221
  export declare function matchHeadlessColorCommand(prompt: string): string | undefined;
222
+ /**
223
+ * Returns true only for a trimmed exact `/cost` command. `/costs`, `/cost
224
+ * extra`, and any non-slash text are model prompts.
225
+ */
226
+ export declare function isHeadlessCostCommand(prompt: string): boolean;
216
227
  export declare function parseCliInvocation(argv: readonly string[]): CliInvocation;
217
228
  export declare function readStreamJsonMessages(input: AsyncIterable<string | Uint8Array>): AsyncGenerator<StreamJsonMessage>;
218
229
  export declare function readStreamUserMessages(input: AsyncIterable<string | Uint8Array>): AsyncGenerator<StreamUserMessage>;
@@ -35,7 +35,15 @@ export function createSuccessResult(result, info, startedAt, modelTurns) {
35
35
  outputTokens: modelUsage.outputTokens,
36
36
  cacheReadInputTokens: modelUsage.cacheReadInputTokens ?? 0,
37
37
  cacheCreationInputTokens: modelUsage.cacheCreationInputTokens ?? 0,
38
- costUSD: model === info.model ? (result.costUsd ?? null) : null,
38
+ costUSD: result.modelCostUsd?.[model] ??
39
+ (model === info.model ? (result.costUsd ?? null) : null),
40
+ ...(modelUsage.webSearchRequests === undefined
41
+ ? {}
42
+ : { webSearchRequests: modelUsage.webSearchRequests }),
43
+ contextWindow: modelUsage.contextWindow ??
44
+ (model === info.model ? (info.contextWindowTokens ?? 0) : 0),
45
+ maxOutputTokens: modelUsage.maxOutputTokens ??
46
+ (model === info.model ? (info.maxOutputTokens ?? 0) : 0),
39
47
  },
40
48
  ])),
41
49
  permission_denials: [],
@@ -92,6 +100,13 @@ export function matchHeadlessColorCommand(prompt) {
92
100
  return undefined;
93
101
  return match[1] ?? '';
94
102
  }
103
+ /**
104
+ * Returns true only for a trimmed exact `/cost` command. `/costs`, `/cost
105
+ * extra`, and any non-slash text are model prompts.
106
+ */
107
+ export function isHeadlessCostCommand(prompt) {
108
+ return prompt.trim() === '/cost';
109
+ }
95
110
  const INPUT_FORMATS = ['text', 'stream-json'];
96
111
  const OUTPUT_FORMATS = ['text', 'json', 'stream-json'];
97
112
  const PERMISSION_MODES = [
@@ -1,4 +1,4 @@
1
- export type ClaudeCommandDisposition = 'included' | 'deferred' | 'excluded';
1
+ export type ClaudeCommandDisposition = 'included' | 'required' | 'deferred' | 'excluded';
2
2
  export type ClaudeCommandVisibility = 'visible' | 'hidden' | 'conditional';
3
3
  export interface ClaudeCommandInventoryEntry {
4
4
  name: string;
@@ -14,7 +14,7 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
14
14
  readonly name: "advisor";
15
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
+ readonly reason: "Conditional advice mode is an optional, demand-driven feature that does not block developer-core closure.";
18
18
  }, {
19
19
  readonly name: "agents";
20
20
  readonly disposition: "included";
@@ -29,9 +29,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
29
29
  readonly visibility: "visible";
30
30
  }, {
31
31
  readonly name: "chrome";
32
- readonly disposition: "deferred";
32
+ readonly disposition: "excluded";
33
33
  readonly visibility: "conditional";
34
- readonly reason: "The CLI-driven Chrome integration is required parity work and is not implemented yet.";
34
+ readonly reason: "Claude-AI subscription-gated Chrome Beta; a future provider-neutral browser feature is separate from this command.";
35
35
  }, {
36
36
  readonly name: "clear";
37
37
  readonly disposition: "included";
@@ -63,16 +63,16 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
63
63
  readonly visibility: "conditional";
64
64
  }, {
65
65
  readonly name: "cost";
66
- readonly disposition: "deferred";
66
+ readonly disposition: "included";
67
67
  readonly visibility: "conditional";
68
- readonly reason: "The dedicated /cost contract is required; /status is not a substitute.";
68
+ readonly reason: "The dedicated /cost contract is included; /status is not a substitute.";
69
69
  }, {
70
70
  readonly name: "diff";
71
71
  readonly disposition: "included";
72
72
  readonly visibility: "visible";
73
73
  }, {
74
74
  readonly name: "doctor";
75
- readonly disposition: "deferred";
75
+ readonly disposition: "required";
76
76
  readonly visibility: "conditional";
77
77
  readonly reason: "Interactive /doctor is required; the top-level command is not a substitute.";
78
78
  }, {
@@ -87,7 +87,7 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
87
87
  readonly name: "fast";
88
88
  readonly disposition: "deferred";
89
89
  readonly visibility: "conditional";
90
- readonly reason: "The /fast state flow is required; model and effort controls are not a substitute.";
90
+ readonly reason: "The /fast state flow is an optional convenience; model and effort controls cover the core flow, so it is deferred without blocking developer-core closure.";
91
91
  }, {
92
92
  readonly name: "files";
93
93
  readonly disposition: "excluded";
@@ -95,9 +95,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
95
95
  readonly reason: "The source command is restricted to the internal Ant user type.";
96
96
  }, {
97
97
  readonly name: "heapdump";
98
- readonly disposition: "deferred";
98
+ readonly disposition: "excluded";
99
99
  readonly visibility: "hidden";
100
- readonly reason: "The hidden heap-diagnostic contract is required parity work and is not implemented yet.";
100
+ readonly reason: "Hidden V8 maintainer diagnostic that writes a heap dump to Desktop; not a developer-core command.";
101
101
  }, {
102
102
  readonly name: "help";
103
103
  readonly disposition: "included";
@@ -188,7 +188,7 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
188
188
  readonly name: "stats";
189
189
  readonly disposition: "deferred";
190
190
  readonly visibility: "visible";
191
- readonly reason: "Historical usage statistics are required parity work and are not implemented yet.";
191
+ readonly reason: "Historical usage statistics are an optional, demand-driven feature that does not block developer-core closure.";
192
192
  }, {
193
193
  readonly name: "status";
194
194
  readonly disposition: "included";
@@ -261,7 +261,7 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
261
261
  readonly name: "insights";
262
262
  readonly disposition: "deferred";
263
263
  readonly visibility: "visible";
264
- readonly reason: "Retrospective insights are required parity work and are not implemented yet.";
264
+ readonly reason: "Retrospective insights are an optional, demand-driven feature that does not block developer-core closure.";
265
265
  }, {
266
266
  readonly name: "vim";
267
267
  readonly disposition: "included";
@@ -277,24 +277,24 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
277
277
  readonly visibility: "conditional";
278
278
  }, {
279
279
  readonly name: "buddy";
280
- readonly disposition: "deferred";
280
+ readonly disposition: "excluded";
281
281
  readonly visibility: "conditional";
282
- readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
282
+ readonly reason: "Compile/build-feature-gated experiment, not a stable single-user developer-core command.";
283
283
  }, {
284
284
  readonly name: "proactive";
285
- readonly disposition: "deferred";
285
+ readonly disposition: "excluded";
286
286
  readonly visibility: "conditional";
287
- readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
287
+ readonly reason: "Compile/build-feature-gated experiment, not a stable single-user developer-core command.";
288
288
  }, {
289
289
  readonly name: "brief";
290
- readonly disposition: "deferred";
290
+ readonly disposition: "excluded";
291
291
  readonly visibility: "conditional";
292
- readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
292
+ readonly reason: "Compile/build-feature-gated experiment, not a stable single-user developer-core command.";
293
293
  }, {
294
294
  readonly name: "assistant";
295
- readonly disposition: "deferred";
295
+ readonly disposition: "excluded";
296
296
  readonly visibility: "conditional";
297
- readonly reason: "This conditional source mode is required parity work and is not implemented yet.";
297
+ readonly reason: "Compile/build-feature-gated experiment, not a stable single-user developer-core command.";
298
298
  }, {
299
299
  readonly name: "remote-control";
300
300
  readonly disposition: "excluded";
@@ -309,17 +309,17 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
309
309
  readonly name: "voice";
310
310
  readonly disposition: "deferred";
311
311
  readonly visibility: "conditional";
312
- readonly reason: "Conditional voice input is required parity work and is not implemented yet.";
312
+ readonly reason: "Conditional voice input is an optional, demand-driven feature that does not block developer-core closure.";
313
313
  }, {
314
314
  readonly name: "think-back";
315
- readonly disposition: "deferred";
315
+ readonly disposition: "excluded";
316
316
  readonly visibility: "conditional";
317
- readonly reason: "The conditional retrospective flow is required parity work and is not implemented yet.";
317
+ readonly reason: "The 2025 year-in-review campaign surface; a marketing flow, not a stable developer-core command.";
318
318
  }, {
319
319
  readonly name: "thinkback-play";
320
- readonly disposition: "deferred";
320
+ readonly disposition: "excluded";
321
321
  readonly visibility: "hidden";
322
- readonly reason: "The hidden retrospective playback flow is required parity work and is not implemented yet.";
322
+ readonly reason: "Hidden animation for the 2025 year-in-review campaign; a marketing flow, not a stable developer-core command.";
323
323
  }, {
324
324
  readonly name: "permissions";
325
325
  readonly disposition: "included";
@@ -375,9 +375,9 @@ export declare const CLAUDE_2_1_208_COMMAND_INVENTORY: readonly [{
375
375
  readonly visibility: "conditional";
376
376
  }, {
377
377
  readonly name: "torch";
378
- readonly disposition: "deferred";
378
+ readonly disposition: "excluded";
379
379
  readonly visibility: "conditional";
380
- readonly reason: "This source-gated behavior is required conditional parity work and is not implemented yet.";
380
+ readonly reason: "Compile/build-feature-gated experiment, not a stable single-user developer-core command.";
381
381
  }];
382
382
  export declare const CLAUDE_2_1_208_COMMAND_BY_NAME: ReadonlyMap<string, ClaudeCommandInventoryEntry>;
383
383
  //# sourceMappingURL=claude-command-inventory.d.ts.map