praxis-agent 0.11.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.
Files changed (31) hide show
  1. package/README.md +13 -2
  2. package/dist/application/subagent-service.d.ts +1 -1
  3. package/dist/application/subagent-service.js +33 -2
  4. package/dist/cli/interactive.d.ts +6 -1
  5. package/dist/cli/interactive.js +197 -5
  6. package/dist/cli/tui/claude-command-inventory.d.ts +35 -19
  7. package/dist/cli/tui/claude-command-inventory.js +98 -18
  8. package/dist/cli/tui/release-notes.d.ts +10 -0
  9. package/dist/cli/tui/release-notes.js +60 -0
  10. package/dist/cli/tui/sandbox-dashboard.d.ts +10 -0
  11. package/dist/cli/tui/sandbox-dashboard.js +78 -0
  12. package/dist/cli/tui/sandbox-settings.d.ts +32 -0
  13. package/dist/cli/tui/sandbox-settings.js +146 -0
  14. package/dist/cli/tui/slash-commands.js +10 -0
  15. package/dist/cli/tui/status-line.d.ts +88 -0
  16. package/dist/cli/tui/status-line.js +234 -0
  17. package/dist/cli.d.ts +2 -0
  18. package/dist/cli.js +73 -3
  19. package/dist/extensions/claude-extensions.d.ts +1 -0
  20. package/dist/extensions/claude-extensions.js +55 -3
  21. package/dist/permissions/bash-path-safety.d.ts +2 -0
  22. package/dist/permissions/bash-path-safety.js +7 -0
  23. package/dist/permissions/claude-permission-resolver.d.ts +11 -0
  24. package/dist/permissions/claude-permission-resolver.js +15 -0
  25. package/dist/sandbox/claude-sandbox-runtime.d.ts +54 -0
  26. package/dist/sandbox/claude-sandbox-runtime.js +175 -0
  27. package/dist/sandbox/claude-sandbox-settings.d.ts +23 -0
  28. package/dist/sandbox/claude-sandbox-settings.js +286 -0
  29. package/dist/tools/local-tools.d.ts +18 -0
  30. package/dist/tools/local-tools.js +47 -8
  31. package/package.json +2 -1
@@ -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
@@ -0,0 +1,10 @@
1
+ import type { TuiSandboxSnapshot, TuiSandboxTab } from './sandbox-settings.js';
2
+ export declare function tuiSandboxTabs(snapshot: TuiSandboxSnapshot): readonly TuiSandboxTab[];
3
+ export declare function SandboxDashboard({ snapshot, tab, selectedIndex, width, screenReader, }: {
4
+ snapshot: TuiSandboxSnapshot;
5
+ tab: TuiSandboxTab;
6
+ selectedIndex: number;
7
+ width: number;
8
+ screenReader: boolean;
9
+ }): import("react").JSX.Element;
10
+ //# sourceMappingURL=sandbox-dashboard.d.ts.map
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ const TABS = {
4
+ mode: 'Mode',
5
+ dependencies: 'Dependencies',
6
+ overrides: 'Overrides',
7
+ config: 'Config',
8
+ };
9
+ export function tuiSandboxTabs(snapshot) {
10
+ if (snapshot.dependencies.errors.length > 0)
11
+ return ['dependencies'];
12
+ return [
13
+ 'mode',
14
+ ...(snapshot.dependencies.warnings.length > 0
15
+ ? ['dependencies']
16
+ : []),
17
+ 'overrides',
18
+ 'config',
19
+ ];
20
+ }
21
+ function prefix(selected, screenReader) {
22
+ if (screenReader)
23
+ return selected ? 'Selected: ' : '';
24
+ return selected ? '❯ ' : ' ';
25
+ }
26
+ export function SandboxDashboard({ snapshot, tab, selectedIndex, width, screenReader, }) {
27
+ const tabs = tuiSandboxTabs(snapshot);
28
+ const mode = !snapshot.settings.enabled
29
+ ? 'disabled'
30
+ : snapshot.settings.autoAllowBashIfSandboxed
31
+ ? 'auto-allow'
32
+ : 'regular';
33
+ const modeRows = [
34
+ {
35
+ value: 'auto-allow',
36
+ label: 'Sandbox BashTool, with auto-allow',
37
+ },
38
+ {
39
+ value: 'regular',
40
+ label: 'Sandbox BashTool, with regular permissions',
41
+ },
42
+ { value: 'disabled', label: 'No Sandbox' },
43
+ ];
44
+ const overrideRows = [
45
+ {
46
+ value: true,
47
+ label: 'Allow unsandboxed fallback',
48
+ },
49
+ {
50
+ value: false,
51
+ label: 'Strict sandbox mode',
52
+ },
53
+ ];
54
+ const filesystem = snapshot.settings.runtimeConfig.filesystem;
55
+ const network = snapshot.settings.runtimeConfig.network;
56
+ return (_jsxs(Box, { flexDirection: "column", width: Math.min(100, width), children: [!screenReader ? (_jsx(Text, { dimColor: true, children: '─'.repeat(Math.min(100, width)) })) : null, _jsx(Text, { bold: true, children: " Sandbox:" }), _jsxs(Text, { children: [' ', tabs.map((candidate) => (_jsxs(Text, { inverse: !screenReader && candidate === tab, children: [screenReader && candidate === tab ? 'Current tab: ' : ' ', TABS[candidate], ' '] }, candidate)))] }), _jsx(Text, { children: " " }), tab === 'mode' ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: " Configure Mode:" }), _jsx(Text, { children: " " }), modeRows.map((row, index) => (_jsxs(Text, { inverse: !screenReader && selectedIndex === index, children: [prefix(selectedIndex === index, screenReader), index + 1, ". ", row.label, row.value === mode ? ' (current)' : ''] }, row.value))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Auto-allow mode runs Bash in the sandbox automatically. Explicit ask and deny rules are always respected." })] })) : tab === 'dependencies' ? (_jsxs(Box, { flexDirection: "column", children: [snapshot.platform === 'macos' ? (_jsxs(Text, { children: ["seatbelt: ", _jsx(Text, { color: "green", children: "built-in (macOS)" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { children: ["bubblewrap (bwrap):", ' ', _jsx(Text, { color: snapshot.dependencies.errors.some((error) => error.includes('bwrap'))
57
+ ? 'red'
58
+ : 'green', children: snapshot.dependencies.errors.some((error) => error.includes('bwrap'))
59
+ ? 'not installed'
60
+ : 'installed' })] }), _jsxs(Text, { children: ["socat:", ' ', _jsx(Text, { color: snapshot.dependencies.errors.some((error) => error.includes('socat'))
61
+ ? 'red'
62
+ : 'green', children: snapshot.dependencies.errors.some((error) => error.includes('socat'))
63
+ ? 'not installed'
64
+ : 'installed' })] }), _jsxs(Text, { children: ["seccomp filter:", ' ', _jsx(Text, { color: snapshot.dependencies.warnings.length > 0
65
+ ? 'yellow'
66
+ : 'green', children: snapshot.dependencies.warnings.length > 0
67
+ ? 'not installed'
68
+ : 'installed' })] })] })), _jsxs(Text, { children: ["ripgrep (rg):", ' ', _jsx(Text, { color: snapshot.dependencies.errors.some((error) => error.includes('ripgrep'))
69
+ ? 'red'
70
+ : 'green', children: snapshot.dependencies.errors.some((error) => error.includes('ripgrep'))
71
+ ? 'not found'
72
+ : 'found' })] }), snapshot.dependencies.errors.map((error) => (_jsx(Text, { color: "red", children: error }, error))), snapshot.dependencies.warnings.map((warning) => (_jsx(Text, { color: "yellow", children: warning }, warning))), snapshot.unavailableReason ? (_jsx(Text, { color: "red", children: snapshot.unavailableReason })) : null] })) : tab === 'overrides' ? (_jsxs(_Fragment, { children: [!snapshot.settings.enabled ? (_jsx(Text, { dimColor: true, children: "Sandbox is not enabled" })) : (overrideRows.map((row, index) => (_jsxs(Text, { inverse: !screenReader && selectedIndex === index, children: [prefix(selectedIndex === index, screenReader), index + 1, ". ", row.label, row.value === snapshot.settings.allowUnsandboxedCommands
73
+ ? ' (current)'
74
+ : ''] }, String(row.value))))), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "Closed mode ignores dangerouslyDisableSandbox and requires commands to remain sandboxed." })] })) : !snapshot.settings.enabled ? (_jsx(Text, { dimColor: true, children: "Sandbox is not enabled" })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Excluded Commands:" }), _jsx(Text, { dimColor: true, children: snapshot.settings.excludedCommands.join(', ') || 'None' }), _jsx(Text, { bold: true, children: "Filesystem Read Restrictions:" }), _jsxs(Text, { dimColor: true, children: ["Denied: ", filesystem?.denyRead?.join(', ') || 'None'] }), _jsxs(Text, { dimColor: true, children: ["Allowed within denied: ", filesystem?.allowRead?.join(', ') || 'None'] }), _jsx(Text, { bold: true, children: "Filesystem Write Restrictions:" }), _jsxs(Text, { dimColor: true, children: ["Allowed: ", filesystem?.allowWrite?.join(', ') || 'None'] }), _jsxs(Text, { dimColor: true, children: ["Denied within allowed: ", filesystem?.denyWrite?.join(', ') || 'None'] }), _jsx(Text, { bold: true, children: "Network Restrictions:" }), _jsxs(Text, { dimColor: true, children: ["Allowed: ", network?.allowedDomains?.join(', ') || 'None'] }), _jsxs(Text, { dimColor: true, children: ["Denied: ", network?.deniedDomains?.join(', ') || 'None'] }), _jsx(Text, { bold: true, children: "Allowed Unix Sockets:" }), _jsx(Text, { dimColor: true, children: network?.allowUnixSockets?.join(', ') || 'None' }), (snapshot.globPatternWarnings?.length ?? 0) > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, color: "yellow", children: "\u26A0 Warning: Glob patterns not fully supported on Linux" }), _jsx(Text, { dimColor: true, children: snapshot.globPatternWarnings?.slice(0, 3).join(', ') })] })) : null] })), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: tab === 'mode' || (tab === 'overrides' && snapshot.settings.enabled)
75
+ ? '↑/↓ navigate · Enter to select · ←/→ switch tabs · Esc to cancel'
76
+ : '←/→ switch tabs · Esc to cancel' })] }));
77
+ }
78
+ //# sourceMappingURL=sandbox-dashboard.js.map
@@ -0,0 +1,32 @@
1
+ import type { SandboxDependencyCheck } from '@anthropic-ai/sandbox-runtime';
2
+ import { loadClaudeSettings } from '../../compatibility/claude/shared-resources.js';
3
+ import { type ClaudeSandboxSettings } from '../../sandbox/claude-sandbox-settings.js';
4
+ export type TuiSandboxMode = 'auto-allow' | 'regular' | 'disabled';
5
+ export type TuiSandboxTab = 'mode' | 'dependencies' | 'overrides' | 'config';
6
+ export interface TuiSandboxSnapshot {
7
+ settings: ClaudeSandboxSettings;
8
+ dependencies: SandboxDependencyCheck;
9
+ supported: boolean;
10
+ platform: 'macos' | 'linux' | 'windows' | 'wsl';
11
+ globPatternWarnings?: readonly string[];
12
+ unavailableReason?: string;
13
+ }
14
+ export interface TuiSandboxStore {
15
+ load(): Promise<TuiSandboxSnapshot>;
16
+ setMode(mode: TuiSandboxMode): Promise<TuiSandboxSnapshot>;
17
+ setAllowUnsandboxedCommands(allow: boolean): Promise<TuiSandboxSnapshot>;
18
+ exclude(pattern: string): Promise<{
19
+ pattern: string;
20
+ settingsPath: string;
21
+ snapshot: TuiSandboxSnapshot;
22
+ }>;
23
+ }
24
+ export declare function linuxGlobPatternWarnings(resources: Awaited<ReturnType<typeof loadClaudeSettings>>, platform: 'macos' | 'linux' | 'windows' | 'wsl'): string[];
25
+ export declare function createTuiSandboxStore({ configRoot, cwd, homeDirectory, additionalDirectories, environment, }: {
26
+ configRoot: string;
27
+ cwd: string;
28
+ homeDirectory: string;
29
+ additionalDirectories?: readonly string[];
30
+ environment?: NodeJS.ProcessEnv;
31
+ }): TuiSandboxStore;
32
+ //# sourceMappingURL=sandbox-settings.d.ts.map
@@ -0,0 +1,146 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { loadClaudeSettings } from '../../compatibility/claude/shared-resources.js';
4
+ import { writeFileAtomically } from '../../platform/atomic-write.js';
5
+ import { claudeSandboxRuntime } from '../../sandbox/claude-sandbox-runtime.js';
6
+ import { claudeSandboxTempDirectory, loadClaudeSandboxSettings, } from '../../sandbox/claude-sandbox-settings.js';
7
+ function isRecord(value) {
8
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
9
+ }
10
+ async function readLocalSettings(path) {
11
+ try {
12
+ const value = JSON.parse(await readFile(path, 'utf8'));
13
+ if (!isRecord(value))
14
+ throw new Error(`Settings must be an object: ${path}`);
15
+ return value;
16
+ }
17
+ catch (error) {
18
+ if (error.code === 'ENOENT')
19
+ return {};
20
+ throw error;
21
+ }
22
+ }
23
+ async function updateLocalSandbox(path, update) {
24
+ for (let attempt = 0; attempt < 3; attempt += 1) {
25
+ let source;
26
+ try {
27
+ source = await readFile(path, 'utf8');
28
+ }
29
+ catch (error) {
30
+ if (error.code !== 'ENOENT')
31
+ throw error;
32
+ }
33
+ const settings = await readLocalSettings(path);
34
+ const current = settings.sandbox;
35
+ if (current !== undefined && !isRecord(current)) {
36
+ throw new Error(`sandbox must be an object: ${path}`);
37
+ }
38
+ const committed = await writeFileAtomically(path, `${JSON.stringify({ ...settings, sandbox: update(current ?? {}) }, null, 2)}\n`, {
39
+ beforeCommit: async () => {
40
+ try {
41
+ return (await readFile(path, 'utf8')) === source;
42
+ }
43
+ catch (error) {
44
+ if (error.code === 'ENOENT')
45
+ return source === undefined;
46
+ throw error;
47
+ }
48
+ },
49
+ });
50
+ if (committed)
51
+ return;
52
+ }
53
+ throw new Error(`Settings changed concurrently: ${path}`);
54
+ }
55
+ export function linuxGlobPatternWarnings(resources, platform) {
56
+ if (platform !== 'linux' && platform !== 'wsl')
57
+ return [];
58
+ const warnings = [];
59
+ for (const resource of resources) {
60
+ if (!isRecord(resource.value) || !isRecord(resource.value.permissions))
61
+ continue;
62
+ for (const behavior of ['allow', 'deny']) {
63
+ const rules = resource.value.permissions[behavior];
64
+ if (!Array.isArray(rules))
65
+ continue;
66
+ for (const rule of rules) {
67
+ if (typeof rule !== 'string')
68
+ continue;
69
+ const match = /^(?:Edit|Read)\((.*)\)$/u.exec(rule);
70
+ const pattern = match?.[1]?.replace(/\/\*\*$/u, '');
71
+ if (pattern && /[*?[\]]/u.test(pattern))
72
+ warnings.push(rule);
73
+ }
74
+ }
75
+ }
76
+ return [...new Set(warnings)];
77
+ }
78
+ export function createTuiSandboxStore({ configRoot, cwd, homeDirectory, additionalDirectories = [], environment = process.env, }) {
79
+ const localSettingsPath = join(cwd, '.claude', 'settings.local.json');
80
+ const load = async () => {
81
+ const resources = await loadClaudeSettings({ configRoot, cwd });
82
+ const settings = loadClaudeSandboxSettings({
83
+ resources,
84
+ cwd,
85
+ configRoot,
86
+ homeDirectory,
87
+ additionalDirectories,
88
+ tempDirectory: claudeSandboxTempDirectory(environment),
89
+ });
90
+ await claudeSandboxRuntime.initialize(settings);
91
+ const unavailableReason = claudeSandboxRuntime.unavailableReason(settings);
92
+ const platform = claudeSandboxRuntime.platformName();
93
+ return {
94
+ settings,
95
+ dependencies: claudeSandboxRuntime.dependencyCheck(settings),
96
+ supported: claudeSandboxRuntime.isSupportedPlatform(),
97
+ platform,
98
+ globPatternWarnings: linuxGlobPatternWarnings(resources, platform),
99
+ ...(unavailableReason ? { unavailableReason } : {}),
100
+ };
101
+ };
102
+ return {
103
+ load,
104
+ async setMode(mode) {
105
+ await updateLocalSandbox(localSettingsPath, (sandbox) => ({
106
+ ...sandbox,
107
+ enabled: mode !== 'disabled',
108
+ autoAllowBashIfSandboxed: mode === 'auto-allow',
109
+ }));
110
+ return load();
111
+ },
112
+ async setAllowUnsandboxedCommands(allow) {
113
+ await updateLocalSandbox(localSettingsPath, (sandbox) => ({
114
+ ...sandbox,
115
+ allowUnsandboxedCommands: allow,
116
+ }));
117
+ return load();
118
+ },
119
+ async exclude(pattern) {
120
+ const cleanPattern = pattern.trim().replace(/^["']|["']$/gu, '');
121
+ if (!cleanPattern) {
122
+ throw new Error('Please provide a command pattern to exclude');
123
+ }
124
+ await updateLocalSandbox(localSettingsPath, (sandbox) => {
125
+ const excluded = sandbox.excludedCommands;
126
+ if (excluded !== undefined &&
127
+ (!Array.isArray(excluded) ||
128
+ excluded.some((value) => typeof value !== 'string'))) {
129
+ throw new Error(`sandbox.excludedCommands must be an array of strings: ${localSettingsPath}`);
130
+ }
131
+ return {
132
+ ...sandbox,
133
+ excludedCommands: [
134
+ ...new Set([...(excluded ?? []), cleanPattern]),
135
+ ],
136
+ };
137
+ });
138
+ return {
139
+ pattern: cleanPattern,
140
+ settingsPath: relative(cwd, localSettingsPath),
141
+ snapshot: await load(),
142
+ };
143
+ },
144
+ };
145
+ }
146
+ //# sourceMappingURL=sandbox-settings.js.map
@@ -64,6 +64,11 @@ export const BUILTIN_TUI_SLASH_COMMANDS = [
64
64
  description: 'Manage allow and deny tool permission rules',
65
65
  source: 'builtin',
66
66
  },
67
+ {
68
+ name: 'sandbox',
69
+ description: 'Configure sandbox mode and command isolation',
70
+ source: 'builtin',
71
+ },
67
72
  {
68
73
  name: 'resume',
69
74
  description: 'Resume a previous conversation',
@@ -119,6 +124,11 @@ export const BUILTIN_TUI_SLASH_COMMANDS = [
119
124
  description: 'Activate pending plugin changes in the current session',
120
125
  source: 'builtin',
121
126
  },
127
+ {
128
+ name: 'release-notes',
129
+ description: 'View release notes',
130
+ source: 'builtin',
131
+ },
122
132
  {
123
133
  name: 'rename',
124
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