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.
- package/README.md +13 -2
- package/dist/application/subagent-service.d.ts +1 -1
- package/dist/application/subagent-service.js +33 -2
- package/dist/cli/interactive.d.ts +6 -1
- package/dist/cli/interactive.js +197 -5
- package/dist/cli/tui/claude-command-inventory.d.ts +35 -19
- package/dist/cli/tui/claude-command-inventory.js +98 -18
- package/dist/cli/tui/release-notes.d.ts +10 -0
- package/dist/cli/tui/release-notes.js +60 -0
- package/dist/cli/tui/sandbox-dashboard.d.ts +10 -0
- package/dist/cli/tui/sandbox-dashboard.js +78 -0
- package/dist/cli/tui/sandbox-settings.d.ts +32 -0
- package/dist/cli/tui/sandbox-settings.js +146 -0
- package/dist/cli/tui/slash-commands.js +10 -0
- package/dist/cli/tui/status-line.d.ts +88 -0
- package/dist/cli/tui/status-line.js +234 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +73 -3
- package/dist/extensions/claude-extensions.d.ts +1 -0
- package/dist/extensions/claude-extensions.js +55 -3
- package/dist/permissions/bash-path-safety.d.ts +2 -0
- package/dist/permissions/bash-path-safety.js +7 -0
- package/dist/permissions/claude-permission-resolver.d.ts +11 -0
- package/dist/permissions/claude-permission-resolver.js +15 -0
- package/dist/sandbox/claude-sandbox-runtime.d.ts +54 -0
- package/dist/sandbox/claude-sandbox-runtime.js +175 -0
- package/dist/sandbox/claude-sandbox-settings.d.ts +23 -0
- package/dist/sandbox/claude-sandbox-settings.js +286 -0
- package/dist/tools/local-tools.d.ts +18 -0
- package/dist/tools/local-tools.js +47 -8
- package/package.json +2 -1
|
@@ -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;
|
|
@@ -114,6 +115,7 @@ export interface CliDependencies extends InteractiveServiceFactory {
|
|
|
114
115
|
exposeToolRegistry?: boolean;
|
|
115
116
|
emitToolUseSummaries?: boolean;
|
|
116
117
|
cwd?: string;
|
|
118
|
+
sandboxOriginalCwd?: string;
|
|
117
119
|
configRoot?: string;
|
|
118
120
|
environment?: Readonly<Record<string, string>>;
|
|
119
121
|
providerEnvironment?: NodeJS.ProcessEnv;
|
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';
|
|
@@ -43,6 +44,8 @@ import { FilteredToolRegistry } from './tools/filtered-tool-registry.js';
|
|
|
43
44
|
import { WebToolRegistry } from './tools/web.js';
|
|
44
45
|
import { WorkspaceContext } from './application/session-worktree.js';
|
|
45
46
|
import { launchTmuxWorktree } from './platform/tmux-worktree.js';
|
|
47
|
+
import { claudeSandboxRuntime } from './sandbox/claude-sandbox-runtime.js';
|
|
48
|
+
import { claudeSandboxTempDirectory, loadClaudeSandboxSettings, } from './sandbox/claude-sandbox-settings.js';
|
|
46
49
|
import { createErrorResult, createSuccessResult, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
|
|
47
50
|
import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaudePlugins, readPluginRegistry, setClaudePluginEnabled, uninstallClaudePlugin, updateClaudePlugin, validateClaudePlugin, } from './plugins/claude-plugin-runtime.js';
|
|
48
51
|
import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
|
|
@@ -688,8 +691,9 @@ const consoleIO = {
|
|
|
688
691
|
isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
689
692
|
readStdinLines: () => process.stdin,
|
|
690
693
|
};
|
|
691
|
-
const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = false, approveRecovery, approveTool, agent, model: interactiveModel, effort: interactiveEffort, permissionMode: interactivePermissionMode, isSessionActionApproved, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, emitToolUseSummaries = false, cwd: requestedCwd, configRoot: requestedConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
|
|
694
|
+
const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = false, approveRecovery, approveTool, agent, model: interactiveModel, effort: interactiveEffort, permissionMode: interactivePermissionMode, isSessionActionApproved, controls = DEFAULT_CLI_CONTROLS, interactive = false, sessionKind, signal, exposeToolRegistry = false, onElicitation, askUser, approvePlan, emitToolUseSummaries = false, cwd: requestedCwd, sandboxOriginalCwd, configRoot: requestedConfigRoot, environment, providerEnvironment: requestedProviderEnvironment, }) => {
|
|
692
695
|
const runtimeEnvironment = requestedProviderEnvironment ?? process.env;
|
|
696
|
+
const sandboxEnvironment = { ...runtimeEnvironment, ...environment };
|
|
693
697
|
const claudeVersion = await detectInstalledClaudeVersion();
|
|
694
698
|
const cwd = requestedCwd ?? process.cwd();
|
|
695
699
|
const workspace = new WorkspaceContext(cwd);
|
|
@@ -948,9 +952,26 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
948
952
|
...cli.additionalDirectories,
|
|
949
953
|
...(exposePlanDirectory ? [resolve(configRoot, 'plans')] : []),
|
|
950
954
|
];
|
|
955
|
+
const sandboxSettings = loadClaudeSandboxSettings({
|
|
956
|
+
resources: settings.filter((resource) => resource.plugin !== true),
|
|
957
|
+
cwd: workspace.cwd(),
|
|
958
|
+
originalCwd: sandboxOriginalCwd ?? workspace.cwd(),
|
|
959
|
+
configRoot,
|
|
960
|
+
homeDirectory: sandboxEnvironment.HOME ?? sandboxEnvironment.USERPROFILE ?? homedir(),
|
|
961
|
+
additionalDirectories: initialAdditionalDirectories,
|
|
962
|
+
tempDirectory: claudeSandboxTempDirectory(sandboxEnvironment),
|
|
963
|
+
});
|
|
964
|
+
await claudeSandboxRuntime.initialize(sandboxSettings);
|
|
965
|
+
const sandboxUnavailableReason = claudeSandboxRuntime.unavailableReason();
|
|
966
|
+
if (sandboxUnavailableReason && !sandboxSettings.failIfUnavailable) {
|
|
967
|
+
runtimeEventSink({ type: 'warning', message: sandboxUnavailableReason });
|
|
968
|
+
}
|
|
951
969
|
const permissionAdditionalDirectories = [
|
|
952
|
-
...
|
|
953
|
-
|
|
970
|
+
...new Set([
|
|
971
|
+
...initialAdditionalDirectories,
|
|
972
|
+
...controls.addDirectories.map((directory) => resolve(cwd, directory)),
|
|
973
|
+
...(memoryDirectory ? [memoryDirectory] : []),
|
|
974
|
+
]),
|
|
954
975
|
];
|
|
955
976
|
const initialAdditionalReadDirectories = [claudeBackgroundTaskParent(cwd)];
|
|
956
977
|
const permissionResolverForMode = (permissionMode) => new ClaudeExtensionPermissionResolver(new ClaudePermissionResolver({
|
|
@@ -962,6 +983,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
962
983
|
disallowedTools: cli.disallowedTools,
|
|
963
984
|
additionalDirectories: permissionAdditionalDirectories,
|
|
964
985
|
additionalReadDirectories: initialAdditionalReadDirectories,
|
|
986
|
+
sandbox: claudeSandboxRuntime,
|
|
965
987
|
permissionMode,
|
|
966
988
|
...(isSessionActionApproved ? { isSessionActionApproved } : {}),
|
|
967
989
|
...(permissionMode === 'auto'
|
|
@@ -978,6 +1000,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
978
1000
|
...(memoryDirectory ? { sharedMemoryDirectory: memoryDirectory } : {}),
|
|
979
1001
|
additionalDirectories: initialAdditionalDirectories,
|
|
980
1002
|
additionalReadDirectories: initialAdditionalReadDirectories,
|
|
1003
|
+
sandbox: claudeSandboxRuntime,
|
|
981
1004
|
...(environment ? { environment } : {}),
|
|
982
1005
|
});
|
|
983
1006
|
const runtimeMcpResources = async (candidates) => {
|
|
@@ -1463,6 +1486,7 @@ const defaultPluginEvalRuntimeFactory = {
|
|
|
1463
1486
|
USERPROFILE: options.home,
|
|
1464
1487
|
},
|
|
1465
1488
|
providerEnvironment: process.env,
|
|
1489
|
+
isSessionActionApproved: (call) => options.allowedTools.includes(call.name),
|
|
1466
1490
|
controls: {
|
|
1467
1491
|
...DEFAULT_CLI_CONTROLS,
|
|
1468
1492
|
sessionPersistence: false,
|
|
@@ -1568,6 +1592,7 @@ const defaultDependencies = {
|
|
|
1568
1592
|
createService: ({ additionalDirectories, cwd, ...options }) => createDefaultService({
|
|
1569
1593
|
...options,
|
|
1570
1594
|
...(cwd === undefined ? {} : { cwd }),
|
|
1595
|
+
sandboxOriginalCwd: process.cwd(),
|
|
1571
1596
|
...(agent === undefined ? {} : { agent }),
|
|
1572
1597
|
controls: {
|
|
1573
1598
|
...interactiveControls,
|
|
@@ -1585,6 +1610,9 @@ const defaultDependencies = {
|
|
|
1585
1610
|
? { allowDangerouslySkipPermissions: true }
|
|
1586
1611
|
: {}),
|
|
1587
1612
|
additionalDirectories: initialAdditionalDirectories,
|
|
1613
|
+
...(interactiveControls.settingSources === undefined
|
|
1614
|
+
? {}
|
|
1615
|
+
: { settingSources: interactiveControls.settingSources }),
|
|
1588
1616
|
display: {
|
|
1589
1617
|
version: VERSION,
|
|
1590
1618
|
cwd: process.cwd(),
|
|
@@ -3832,6 +3860,48 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3832
3860
|
: {}),
|
|
3833
3861
|
};
|
|
3834
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
|
+
}
|
|
3835
3905
|
const service = await dependencies.createService({
|
|
3836
3906
|
eventSink: outputFormat === 'stream-json' && !invocation.legacyJson
|
|
3837
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([
|
|
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 =
|
|
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,
|
|
272
|
+
renderInvocation(definition, invocationArguments),
|
|
221
273
|
],
|
|
222
274
|
};
|
|
223
275
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FsWriteRestrictionConfig } from '@anthropic-ai/sandbox-runtime';
|
|
1
2
|
import type { PermissionUpdate } from '../core/runtime.js';
|
|
2
3
|
type FileOperation = 'read' | 'write' | 'create';
|
|
3
4
|
type RuleOutcome = 'allow' | 'deny' | null;
|
|
@@ -8,6 +9,7 @@ export interface BashPathSafetyOptions {
|
|
|
8
9
|
writeRoots: readonly string[];
|
|
9
10
|
internalEditableRoots?: readonly string[];
|
|
10
11
|
internalReadableRoots?: readonly string[];
|
|
12
|
+
sandboxWriteConfig?: FsWriteRestrictionConfig;
|
|
11
13
|
permissionMode: 'acceptEdits' | 'auto' | 'bypassPermissions' | 'manual' | 'dontAsk' | 'plan' | 'default';
|
|
12
14
|
fileRule?: (operation: FileOperation, absolutePath: string) => RuleOutcome;
|
|
13
15
|
platform?: NodeJS.Platform;
|
|
@@ -477,6 +477,13 @@ function pathFailure(rawPath, operation, options, dangerous = false) {
|
|
|
477
477
|
pathIsInsideRoots(absolutePath, options.internalReadableRoots ?? [])) {
|
|
478
478
|
return { safe: true };
|
|
479
479
|
}
|
|
480
|
+
if (operation !== 'read' &&
|
|
481
|
+
!inRoot &&
|
|
482
|
+
options.sandboxWriteConfig &&
|
|
483
|
+
!pathIsInsideRoots(absolutePath, options.sandboxWriteConfig.denyWithinAllow) &&
|
|
484
|
+
pathIsInsideRoots(absolutePath, options.sandboxWriteConfig.allowOnly)) {
|
|
485
|
+
return { safe: true };
|
|
486
|
+
}
|
|
480
487
|
if (rules.length > 0 && rules.every((rule) => rule === 'allow')) {
|
|
481
488
|
return { safe: true };
|
|
482
489
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { FsWriteRestrictionConfig } from '@anthropic-ai/sandbox-runtime';
|
|
1
2
|
import type { ClaudeJsonResource } from '../compatibility/claude/shared-resources.js';
|
|
2
3
|
import type { ModelToolCall, PermissionDecision, PermissionResolutionContext, PermissionResolver } from '../core/runtime.js';
|
|
3
4
|
import { type ClaudeAutoClassifier, type ClaudeAutoModeConfig } from './claude-auto-classifier.js';
|
|
@@ -15,6 +16,15 @@ export interface ClaudePermissionResolverOptions {
|
|
|
15
16
|
isSessionActionApproved?: (call: ModelToolCall) => boolean;
|
|
16
17
|
additionalDirectories?: readonly string[];
|
|
17
18
|
additionalReadDirectories?: readonly string[];
|
|
19
|
+
sandbox?: ClaudePermissionSandbox;
|
|
20
|
+
}
|
|
21
|
+
export interface ClaudePermissionSandbox {
|
|
22
|
+
autoAllowsBash(): boolean;
|
|
23
|
+
shouldUseSandbox(input: {
|
|
24
|
+
command: string;
|
|
25
|
+
dangerouslyDisableSandbox?: boolean;
|
|
26
|
+
}): boolean;
|
|
27
|
+
getFsWriteConfig(): FsWriteRestrictionConfig | undefined;
|
|
18
28
|
}
|
|
19
29
|
export type ClaudePermissionMode = 'acceptEdits' | 'auto' | 'bypassPermissions' | 'manual' | 'dontAsk' | 'plan' | 'default';
|
|
20
30
|
export declare function claudePermissionActionKey(call: ModelToolCall): string;
|
|
@@ -32,6 +42,7 @@ export declare class ClaudePermissionResolver implements PermissionResolver {
|
|
|
32
42
|
private readonly isSessionActionApproved;
|
|
33
43
|
private readonly additionalDirectories;
|
|
34
44
|
private readonly additionalReadDirectories;
|
|
45
|
+
private readonly sandbox;
|
|
35
46
|
constructor(options: ClaudePermissionResolverOptions);
|
|
36
47
|
resolve(call: ModelToolCall, context?: PermissionResolutionContext): Promise<PermissionDecision>;
|
|
37
48
|
private askDecision;
|
|
@@ -317,6 +317,7 @@ export class ClaudePermissionResolver {
|
|
|
317
317
|
isSessionActionApproved;
|
|
318
318
|
additionalDirectories;
|
|
319
319
|
additionalReadDirectories;
|
|
320
|
+
sandbox;
|
|
320
321
|
constructor(options) {
|
|
321
322
|
this.cwd = resolve(options.cwd);
|
|
322
323
|
this.cwdProvider = options.cwdProvider;
|
|
@@ -336,6 +337,7 @@ export class ClaudePermissionResolver {
|
|
|
336
337
|
this.isSessionActionApproved = options.isSessionActionApproved;
|
|
337
338
|
this.additionalDirectories = (options.additionalDirectories ?? []).map((directory) => resolve(directory));
|
|
338
339
|
this.additionalReadDirectories = (options.additionalReadDirectories ?? []).map((directory) => resolve(directory));
|
|
340
|
+
this.sandbox = options.sandbox;
|
|
339
341
|
if (this.permissionMode === 'auto') {
|
|
340
342
|
if (!this.autoClassifier) {
|
|
341
343
|
throw new Error('Permission mode auto requires a classifier');
|
|
@@ -444,7 +446,19 @@ export class ClaudePermissionResolver {
|
|
|
444
446
|
.find((rule) => rule !== undefined);
|
|
445
447
|
if (asked)
|
|
446
448
|
return this.askDecision(call, cwd, permissionMode, context, 'rule', undefined, true);
|
|
449
|
+
if (command &&
|
|
450
|
+
call.name === 'Bash' &&
|
|
451
|
+
this.sandbox?.autoAllowsBash() &&
|
|
452
|
+
this.sandbox.shouldUseSandbox({
|
|
453
|
+
command,
|
|
454
|
+
...(call.input.dangerouslyDisableSandbox === true
|
|
455
|
+
? { dangerouslyDisableSandbox: true }
|
|
456
|
+
: {}),
|
|
457
|
+
})) {
|
|
458
|
+
return annotatePermissionDecision({ behavior: 'allow' }, 'default');
|
|
459
|
+
}
|
|
447
460
|
if (command && call.name === 'Bash') {
|
|
461
|
+
const sandboxWriteConfig = this.sandbox?.getFsWriteConfig();
|
|
448
462
|
const writeRoots = [
|
|
449
463
|
cwd,
|
|
450
464
|
...effectiveAdditionalDirectories(updates, this.additionalDirectories).map((directory) => resolve(cwd, directory)),
|
|
@@ -458,6 +472,7 @@ export class ClaudePermissionResolver {
|
|
|
458
472
|
readRoots: [...writeRoots, ...this.additionalReadDirectories],
|
|
459
473
|
internalEditableRoots,
|
|
460
474
|
internalReadableRoots,
|
|
475
|
+
...(sandboxWriteConfig ? { sandboxWriteConfig } : {}),
|
|
461
476
|
permissionMode,
|
|
462
477
|
fileRule: (operation, absolutePath) => {
|
|
463
478
|
const candidate = {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type FsWriteRestrictionConfig, type SandboxDependencyCheck, type SandboxRuntimeConfig } from '@anthropic-ai/sandbox-runtime';
|
|
2
|
+
import type { ClaudeSandboxSettings } from './claude-sandbox-settings.js';
|
|
3
|
+
export type ClaudeSandboxPlatform = 'macos' | 'linux' | 'windows' | 'wsl';
|
|
4
|
+
export interface SandboxBackend {
|
|
5
|
+
initialize(config: SandboxRuntimeConfig): Promise<void>;
|
|
6
|
+
isSupportedPlatform(): boolean;
|
|
7
|
+
checkDependencies(ripgrep?: {
|
|
8
|
+
command: string;
|
|
9
|
+
args?: string[];
|
|
10
|
+
}): SandboxDependencyCheck;
|
|
11
|
+
wrapWithSandbox(command: string, shell?: string, config?: Partial<SandboxRuntimeConfig>, signal?: AbortSignal, options?: {
|
|
12
|
+
commandId?: string;
|
|
13
|
+
commandText?: string;
|
|
14
|
+
}): Promise<string>;
|
|
15
|
+
getFsWriteConfig(): FsWriteRestrictionConfig;
|
|
16
|
+
updateConfig(config: SandboxRuntimeConfig): void;
|
|
17
|
+
cleanupAfterCommand(): void;
|
|
18
|
+
annotateStderrWithSandboxFailures(commandId: string, stderr: string): string;
|
|
19
|
+
reset(): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export interface SandboxCommandInput {
|
|
22
|
+
command: string;
|
|
23
|
+
dangerouslyDisableSandbox?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare class ClaudeSandboxRuntime {
|
|
26
|
+
private readonly backend;
|
|
27
|
+
private readonly platform;
|
|
28
|
+
private active;
|
|
29
|
+
private initialized;
|
|
30
|
+
private initializationError;
|
|
31
|
+
private settings;
|
|
32
|
+
constructor(backend?: SandboxBackend, platform?: () => ClaudeSandboxPlatform);
|
|
33
|
+
initialize(settings: ClaudeSandboxSettings): Promise<void>;
|
|
34
|
+
isActive(): boolean;
|
|
35
|
+
isEnabledInSettings(): boolean;
|
|
36
|
+
isSupportedPlatform(): boolean;
|
|
37
|
+
platformName(): ClaudeSandboxPlatform;
|
|
38
|
+
dependencyCheck(settings?: ClaudeSandboxSettings): SandboxDependencyCheck;
|
|
39
|
+
autoAllowsBash(): boolean;
|
|
40
|
+
unavailableReason(settings?: ClaudeSandboxSettings): string | undefined;
|
|
41
|
+
shouldUseSandbox(input: SandboxCommandInput): boolean;
|
|
42
|
+
wrapCommand(input: SandboxCommandInput, options?: {
|
|
43
|
+
shell?: string;
|
|
44
|
+
signal?: AbortSignal;
|
|
45
|
+
commandId?: string;
|
|
46
|
+
}): Promise<string>;
|
|
47
|
+
annotateStderr(commandId: string, stderr: string): string;
|
|
48
|
+
cleanupAfterCommand(): void;
|
|
49
|
+
getFsWriteConfig(): FsWriteRestrictionConfig | undefined;
|
|
50
|
+
reset(): Promise<void>;
|
|
51
|
+
private requireSettings;
|
|
52
|
+
}
|
|
53
|
+
export declare const claudeSandboxRuntime: ClaudeSandboxRuntime;
|
|
54
|
+
//# sourceMappingURL=claude-sandbox-runtime.d.ts.map
|