praxis-agent 0.12.0 → 0.14.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 -4
- package/dist/application/subagent-service.d.ts +1 -1
- package/dist/application/subagent-service.js +33 -2
- package/dist/cli/interactive.d.ts +4 -1
- package/dist/cli/interactive.js +69 -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/slash-commands.d.ts +1 -0
- package/dist/cli/tui/slash-commands.js +5 -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 +1 -0
- package/dist/cli.js +50 -1
- package/dist/extensions/claude-extensions.d.ts +5 -0
- package/dist/extensions/claude-extensions.js +78 -3
- package/dist/extensions/claude-init-command.d.ts +4 -0
- package/dist/extensions/claude-init-command.js +72 -0
- package/package.json +1 -1
|
@@ -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
|
|
@@ -3,6 +3,7 @@ export interface TuiSlashCommand {
|
|
|
3
3
|
name: string;
|
|
4
4
|
description: string;
|
|
5
5
|
source: TuiSlashCommandSource;
|
|
6
|
+
progressMessage?: string;
|
|
6
7
|
}
|
|
7
8
|
export declare const BUILTIN_TUI_SLASH_COMMANDS: readonly TuiSlashCommand[];
|
|
8
9
|
export declare function mergeTuiSlashCommands(commands: readonly TuiSlashCommand[]): readonly TuiSlashCommand[];
|
|
@@ -124,6 +124,11 @@ export const BUILTIN_TUI_SLASH_COMMANDS = [
|
|
|
124
124
|
description: 'Activate pending plugin changes in the current session',
|
|
125
125
|
source: 'builtin',
|
|
126
126
|
},
|
|
127
|
+
{
|
|
128
|
+
name: 'release-notes',
|
|
129
|
+
description: 'View release notes',
|
|
130
|
+
source: 'builtin',
|
|
131
|
+
},
|
|
127
132
|
{
|
|
128
133
|
name: 'rename',
|
|
129
134
|
description: 'Rename the current conversation',
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { ClaudeResourceScope } from '../../compatibility/claude/shared-resources.js';
|
|
2
|
+
import type { ModelUsage } from '../../core/runtime.js';
|
|
3
|
+
export interface ClaudeStatusLineSetting {
|
|
4
|
+
type: 'command';
|
|
5
|
+
command: string;
|
|
6
|
+
padding?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ClaudeStatusLineInput {
|
|
9
|
+
session_id: string;
|
|
10
|
+
session_name?: string;
|
|
11
|
+
transcript_path: string;
|
|
12
|
+
cwd: string;
|
|
13
|
+
permission_mode?: string;
|
|
14
|
+
model: {
|
|
15
|
+
id: string;
|
|
16
|
+
display_name: string;
|
|
17
|
+
};
|
|
18
|
+
workspace: {
|
|
19
|
+
current_dir: string;
|
|
20
|
+
project_dir: string;
|
|
21
|
+
added_dirs: readonly string[];
|
|
22
|
+
};
|
|
23
|
+
version: string;
|
|
24
|
+
output_style: {
|
|
25
|
+
name: string;
|
|
26
|
+
};
|
|
27
|
+
cost: {
|
|
28
|
+
total_cost_usd: number;
|
|
29
|
+
total_duration_ms: number;
|
|
30
|
+
total_api_duration_ms: number;
|
|
31
|
+
total_lines_added: number;
|
|
32
|
+
total_lines_removed: number;
|
|
33
|
+
};
|
|
34
|
+
context_window: {
|
|
35
|
+
total_input_tokens: number;
|
|
36
|
+
total_output_tokens: number;
|
|
37
|
+
context_window_size: number;
|
|
38
|
+
current_usage: {
|
|
39
|
+
input_tokens: number;
|
|
40
|
+
output_tokens: number;
|
|
41
|
+
cache_creation_input_tokens: number;
|
|
42
|
+
cache_read_input_tokens: number;
|
|
43
|
+
} | null;
|
|
44
|
+
used_percentage: number | null;
|
|
45
|
+
remaining_percentage: number | null;
|
|
46
|
+
};
|
|
47
|
+
exceeds_200k_tokens: boolean;
|
|
48
|
+
vim?: {
|
|
49
|
+
mode: 'INSERT' | 'NORMAL';
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export declare function loadClaudeStatusLineSetting(options: {
|
|
53
|
+
configRoot: string;
|
|
54
|
+
cwd: string;
|
|
55
|
+
settingSources?: readonly ClaudeResourceScope[];
|
|
56
|
+
}): Promise<{
|
|
57
|
+
setting?: ClaudeStatusLineSetting;
|
|
58
|
+
disabled: boolean;
|
|
59
|
+
}>;
|
|
60
|
+
export declare function executeClaudeStatusLine(setting: ClaudeStatusLineSetting, input: ClaudeStatusLineInput, options?: {
|
|
61
|
+
cwd: string;
|
|
62
|
+
signal?: AbortSignal;
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
}): Promise<string | undefined>;
|
|
65
|
+
export declare function createClaudeStatusLineInput(options: {
|
|
66
|
+
configRoot: string;
|
|
67
|
+
cwd: string;
|
|
68
|
+
projectDir: string;
|
|
69
|
+
sessionId: string;
|
|
70
|
+
sessionName?: string | null;
|
|
71
|
+
model?: string;
|
|
72
|
+
version: string;
|
|
73
|
+
outputStyle: string;
|
|
74
|
+
permissionMode?: string;
|
|
75
|
+
additionalDirectories: readonly string[];
|
|
76
|
+
usage?: ModelUsage;
|
|
77
|
+
costUsd?: number;
|
|
78
|
+
contextWindowTokens?: number;
|
|
79
|
+
vimMode?: 'INSERT' | 'NORMAL';
|
|
80
|
+
}): ClaudeStatusLineInput;
|
|
81
|
+
export declare function StatusLine({ configRoot, cwd, input, refreshKey, settingSources, }: {
|
|
82
|
+
configRoot: string;
|
|
83
|
+
cwd: string;
|
|
84
|
+
input: ClaudeStatusLineInput;
|
|
85
|
+
refreshKey: string;
|
|
86
|
+
settingSources?: readonly ClaudeResourceScope[];
|
|
87
|
+
}): import("react").JSX.Element | null;
|
|
88
|
+
//# sourceMappingURL=status-line.d.ts.map
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { watchFile, unwatchFile } from 'node:fs';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { Box, Text } from 'ink';
|
|
6
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
7
|
+
import { isClaudeSessionId, resolveClaudePaths, sanitizeClaudeProjectPath, } from '../../compatibility/claude/paths.js';
|
|
8
|
+
import { loadClaudeSettings } from '../../compatibility/claude/shared-resources.js';
|
|
9
|
+
import { commandShell, commandShellArguments, } from '../../platform/command-shell.js';
|
|
10
|
+
function record(value) {
|
|
11
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
12
|
+
? value
|
|
13
|
+
: null;
|
|
14
|
+
}
|
|
15
|
+
export async function loadClaudeStatusLineSetting(options) {
|
|
16
|
+
const resources = await loadClaudeSettings(options);
|
|
17
|
+
const merged = Object.assign({}, ...resources.map((resource) => record(resource.value) ?? {}));
|
|
18
|
+
const value = record(merged.statusLine);
|
|
19
|
+
const setting = value?.type === 'command' && typeof value.command === 'string'
|
|
20
|
+
? {
|
|
21
|
+
type: 'command',
|
|
22
|
+
command: value.command,
|
|
23
|
+
...(typeof value.padding === 'number' &&
|
|
24
|
+
Number.isFinite(value.padding)
|
|
25
|
+
? { padding: value.padding }
|
|
26
|
+
: {}),
|
|
27
|
+
}
|
|
28
|
+
: undefined;
|
|
29
|
+
return {
|
|
30
|
+
...(setting ? { setting } : {}),
|
|
31
|
+
disabled: merged.disableAllHooks === true,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function executeClaudeStatusLine(setting, input, options = {
|
|
35
|
+
cwd: process.cwd(),
|
|
36
|
+
}) {
|
|
37
|
+
const timeout = AbortSignal.timeout(options.timeoutMs ?? 5000);
|
|
38
|
+
const signal = options.signal
|
|
39
|
+
? AbortSignal.any([options.signal, timeout])
|
|
40
|
+
: timeout;
|
|
41
|
+
return new Promise((resolve) => {
|
|
42
|
+
let settled = false;
|
|
43
|
+
let stdout = '';
|
|
44
|
+
const finish = (value) => {
|
|
45
|
+
if (settled)
|
|
46
|
+
return;
|
|
47
|
+
settled = true;
|
|
48
|
+
resolve(value);
|
|
49
|
+
};
|
|
50
|
+
let child;
|
|
51
|
+
try {
|
|
52
|
+
child = spawn(commandShell(), commandShellArguments(setting.command), {
|
|
53
|
+
cwd: options.cwd,
|
|
54
|
+
detached: process.platform !== 'win32',
|
|
55
|
+
env: process.env,
|
|
56
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
finish();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
child.stdout.setEncoding('utf8');
|
|
64
|
+
const terminate = () => {
|
|
65
|
+
if (child.pid === undefined)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
if (process.platform === 'win32')
|
|
69
|
+
child.kill('SIGKILL');
|
|
70
|
+
else
|
|
71
|
+
process.kill(-child.pid, 'SIGKILL');
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
if (error.code !== 'ESRCH')
|
|
75
|
+
finish();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
if (signal.aborted)
|
|
79
|
+
terminate();
|
|
80
|
+
else
|
|
81
|
+
signal.addEventListener('abort', terminate, { once: true });
|
|
82
|
+
child.stdout.on('data', (chunk) => {
|
|
83
|
+
if (stdout.length <= 1024 * 1024)
|
|
84
|
+
stdout += chunk;
|
|
85
|
+
});
|
|
86
|
+
child.on('error', () => {
|
|
87
|
+
signal.removeEventListener('abort', terminate);
|
|
88
|
+
finish();
|
|
89
|
+
});
|
|
90
|
+
child.on('close', (code) => {
|
|
91
|
+
signal.removeEventListener('abort', terminate);
|
|
92
|
+
if (code !== 0 || signal.aborted) {
|
|
93
|
+
finish();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const output = stdout
|
|
97
|
+
.trim()
|
|
98
|
+
.split(/\r?\n/u)
|
|
99
|
+
.map((line) => line.trim())
|
|
100
|
+
.filter(Boolean)
|
|
101
|
+
.join('\n');
|
|
102
|
+
finish(output || undefined);
|
|
103
|
+
});
|
|
104
|
+
child.stdin.on('error', () => undefined);
|
|
105
|
+
child.stdin.end(JSON.stringify(input));
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
export function createClaudeStatusLineInput(options) {
|
|
109
|
+
const usage = options.usage;
|
|
110
|
+
const contextInputTokens = usage
|
|
111
|
+
? usage.inputTokens +
|
|
112
|
+
(usage.cacheReadInputTokens ?? 0) +
|
|
113
|
+
(usage.cacheCreationInputTokens ?? 0)
|
|
114
|
+
: null;
|
|
115
|
+
const usedTokens = contextInputTokens === null || usage === undefined
|
|
116
|
+
? null
|
|
117
|
+
: contextInputTokens + usage.outputTokens;
|
|
118
|
+
const contextWindowSize = options.contextWindowTokens ?? 0;
|
|
119
|
+
const usedPercentage = contextInputTokens === null || contextWindowSize <= 0
|
|
120
|
+
? null
|
|
121
|
+
: Math.min(100, Math.max(0, Math.round((contextInputTokens / contextWindowSize) * 100)));
|
|
122
|
+
const model = options.model ?? 'default';
|
|
123
|
+
const transcriptPath = isClaudeSessionId(options.sessionId)
|
|
124
|
+
? resolveClaudePaths({
|
|
125
|
+
configDir: options.configRoot,
|
|
126
|
+
cwd: options.cwd,
|
|
127
|
+
sessionId: options.sessionId,
|
|
128
|
+
}).sessionFile
|
|
129
|
+
: resolve(options.configRoot, 'projects', sanitizeClaudeProjectPath(options.cwd), `${options.sessionId}.jsonl`);
|
|
130
|
+
return {
|
|
131
|
+
session_id: options.sessionId,
|
|
132
|
+
...(options.sessionName ? { session_name: options.sessionName } : {}),
|
|
133
|
+
transcript_path: transcriptPath,
|
|
134
|
+
cwd: options.cwd,
|
|
135
|
+
...(options.permissionMode
|
|
136
|
+
? { permission_mode: options.permissionMode }
|
|
137
|
+
: {}),
|
|
138
|
+
model: { id: model, display_name: model },
|
|
139
|
+
workspace: {
|
|
140
|
+
current_dir: options.cwd,
|
|
141
|
+
project_dir: options.projectDir,
|
|
142
|
+
added_dirs: options.additionalDirectories,
|
|
143
|
+
},
|
|
144
|
+
version: options.version,
|
|
145
|
+
output_style: { name: options.outputStyle },
|
|
146
|
+
cost: {
|
|
147
|
+
total_cost_usd: options.costUsd ?? 0,
|
|
148
|
+
total_duration_ms: 0,
|
|
149
|
+
total_api_duration_ms: 0,
|
|
150
|
+
total_lines_added: 0,
|
|
151
|
+
total_lines_removed: 0,
|
|
152
|
+
},
|
|
153
|
+
context_window: {
|
|
154
|
+
total_input_tokens: usage?.inputTokens ?? 0,
|
|
155
|
+
total_output_tokens: usage?.outputTokens ?? 0,
|
|
156
|
+
context_window_size: contextWindowSize,
|
|
157
|
+
current_usage: usage
|
|
158
|
+
? {
|
|
159
|
+
input_tokens: usage.inputTokens,
|
|
160
|
+
output_tokens: usage.outputTokens,
|
|
161
|
+
cache_creation_input_tokens: usage.cacheCreationInputTokens ?? 0,
|
|
162
|
+
cache_read_input_tokens: usage.cacheReadInputTokens ?? 0,
|
|
163
|
+
}
|
|
164
|
+
: null,
|
|
165
|
+
used_percentage: usedPercentage,
|
|
166
|
+
remaining_percentage: usedPercentage === null ? null : 100 - usedPercentage,
|
|
167
|
+
},
|
|
168
|
+
exceeds_200k_tokens: usedTokens !== null && usedTokens > 200_000,
|
|
169
|
+
...(options.vimMode ? { vim: { mode: options.vimMode } } : {}),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
export function StatusLine({ configRoot, cwd, input, refreshKey, settingSources, }) {
|
|
173
|
+
const [text, setText] = useState();
|
|
174
|
+
const [padding, setPadding] = useState(0);
|
|
175
|
+
const timer = useRef(undefined);
|
|
176
|
+
const controller = useRef(undefined);
|
|
177
|
+
const latestInput = useRef(input);
|
|
178
|
+
latestInput.current = input;
|
|
179
|
+
const update = useCallback(async () => {
|
|
180
|
+
controller.current?.abort();
|
|
181
|
+
const current = new AbortController();
|
|
182
|
+
controller.current = current;
|
|
183
|
+
try {
|
|
184
|
+
const loaded = await loadClaudeStatusLineSetting({
|
|
185
|
+
configRoot,
|
|
186
|
+
cwd,
|
|
187
|
+
...(settingSources === undefined ? {} : { settingSources }),
|
|
188
|
+
});
|
|
189
|
+
if (current.signal.aborted)
|
|
190
|
+
return;
|
|
191
|
+
setPadding(loaded.setting?.padding ?? 0);
|
|
192
|
+
if (loaded.disabled || !loaded.setting) {
|
|
193
|
+
setText(undefined);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const result = await executeClaudeStatusLine(loaded.setting, latestInput.current, { cwd, signal: current.signal });
|
|
197
|
+
if (!current.signal.aborted)
|
|
198
|
+
setText(result);
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
if (!current.signal.aborted)
|
|
202
|
+
setText(undefined);
|
|
203
|
+
}
|
|
204
|
+
}, [configRoot, cwd, settingSources]);
|
|
205
|
+
const schedule = useCallback(() => {
|
|
206
|
+
if (timer.current)
|
|
207
|
+
clearTimeout(timer.current);
|
|
208
|
+
timer.current = setTimeout(() => void update(), 300);
|
|
209
|
+
}, [update]);
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
schedule();
|
|
212
|
+
}, [refreshKey, schedule]);
|
|
213
|
+
useEffect(() => {
|
|
214
|
+
const paths = [
|
|
215
|
+
join(configRoot, 'settings.json'),
|
|
216
|
+
join(cwd, '.claude', 'settings.json'),
|
|
217
|
+
join(cwd, '.claude', 'settings.local.json'),
|
|
218
|
+
];
|
|
219
|
+
const changed = () => schedule();
|
|
220
|
+
for (const path of paths)
|
|
221
|
+
watchFile(path, { interval: 500 }, changed);
|
|
222
|
+
return () => {
|
|
223
|
+
for (const path of paths)
|
|
224
|
+
unwatchFile(path, changed);
|
|
225
|
+
controller.current?.abort();
|
|
226
|
+
if (timer.current)
|
|
227
|
+
clearTimeout(timer.current);
|
|
228
|
+
};
|
|
229
|
+
}, [configRoot, cwd, schedule]);
|
|
230
|
+
if (!text)
|
|
231
|
+
return null;
|
|
232
|
+
return (_jsx(Box, { paddingX: padding, children: _jsx(Text, { dimColor: true, wrap: "truncate", children: text }) }));
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=status-line.js.map
|
package/dist/cli.d.ts
CHANGED
|
@@ -96,6 +96,7 @@ interface TopLevelAgentCommands {
|
|
|
96
96
|
attach(id: string, input: AsyncIterable<string | Uint8Array>, output: (text: string) => void, signal?: AbortSignal): Promise<void>;
|
|
97
97
|
}
|
|
98
98
|
export interface CliDependencies extends InteractiveServiceFactory {
|
|
99
|
+
loadReleaseNotes?(configRoot: string): Promise<string>;
|
|
99
100
|
createService(options: {
|
|
100
101
|
eventSink: RuntimeEventSink;
|
|
101
102
|
requireProvider: boolean;
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { isClaudeSessionId, resolveClaudePaths, } from './compatibility/claude/p
|
|
|
14
14
|
import { loadClaudeContextResources, loadClaudeSettings, loadClaudeSharedResources, resolveClaudeProjectMemoryDirectory, } from './compatibility/claude/shared-resources.js';
|
|
15
15
|
import { AgentRunCancelledError, } from './core/runtime.js';
|
|
16
16
|
import { persistTuiPermissionUpdates } from './cli/tui/permission-settings.js';
|
|
17
|
+
import { loadClaudeReleaseNotes } from './cli/tui/release-notes.js';
|
|
17
18
|
import { projectTuiHooks, } from './cli/tui/hook-settings.js';
|
|
18
19
|
import { DEFAULT_CLI_CONTROLS, resolveCliControls } from './cli/controls.js';
|
|
19
20
|
import { applyRuntimeSettingDefaults, loadRuntimeSettings, runtimeSettingsSystemPrompt, } from './cli/tui/runtime-settings.js';
|
|
@@ -1358,7 +1359,10 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
|
|
|
1358
1359
|
slashCommands: () => extensions.slashCommandDefinitions().map((definition) => ({
|
|
1359
1360
|
name: definition.name,
|
|
1360
1361
|
description: definition.description,
|
|
1361
|
-
source: definition.kind,
|
|
1362
|
+
source: definition.builtin === true ? 'builtin' : definition.kind,
|
|
1363
|
+
...(definition.progressMessage === undefined
|
|
1364
|
+
? {}
|
|
1365
|
+
: { progressMessage: definition.progressMessage }),
|
|
1362
1366
|
})),
|
|
1363
1367
|
hookConfiguration: async () => projectTuiHooks(settings),
|
|
1364
1368
|
mcpInspect: () => service.mcpInspect(),
|
|
@@ -1609,6 +1613,9 @@ const defaultDependencies = {
|
|
|
1609
1613
|
? { allowDangerouslySkipPermissions: true }
|
|
1610
1614
|
: {}),
|
|
1611
1615
|
additionalDirectories: initialAdditionalDirectories,
|
|
1616
|
+
...(interactiveControls.settingSources === undefined
|
|
1617
|
+
? {}
|
|
1618
|
+
: { settingSources: interactiveControls.settingSources }),
|
|
1612
1619
|
display: {
|
|
1613
1620
|
version: VERSION,
|
|
1614
1621
|
cwd: process.cwd(),
|
|
@@ -3856,6 +3863,48 @@ async function execute(argv, io, dependencies, signal) {
|
|
|
3856
3863
|
: {}),
|
|
3857
3864
|
};
|
|
3858
3865
|
};
|
|
3866
|
+
const headlessPromptArgs = command === 'resume' ? args.slice(2) : knownCommand ? args.slice(1) : args;
|
|
3867
|
+
const headlessPrompt = inputFormat === 'text' && headlessPromptArgs.length > 0
|
|
3868
|
+
? promptFrom(headlessPromptArgs)
|
|
3869
|
+
: undefined;
|
|
3870
|
+
if (headlessPrompt === '/release-notes' && !invocation.disableSlashCommands) {
|
|
3871
|
+
const startedAt = Date.now();
|
|
3872
|
+
const sessionId = invocation.sessionId ?? randomUUID();
|
|
3873
|
+
const configRoot = resolve(process.env.CLAUDE_CONFIG_DIR ?? join(homedir(), '.claude'));
|
|
3874
|
+
const text = dependencies.loadReleaseNotes
|
|
3875
|
+
? await dependencies.loadReleaseNotes(configRoot)
|
|
3876
|
+
: await loadClaudeReleaseNotes({ configRoot });
|
|
3877
|
+
const result = {
|
|
3878
|
+
sessionId,
|
|
3879
|
+
text,
|
|
3880
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
3881
|
+
};
|
|
3882
|
+
const info = {
|
|
3883
|
+
cwd: process.cwd(),
|
|
3884
|
+
model: invocation.model ?? process.env.PRAXIS_MODEL ?? 'unknown',
|
|
3885
|
+
tools: [],
|
|
3886
|
+
mcpServers: [],
|
|
3887
|
+
permissionMode: invocation.permissionMode,
|
|
3888
|
+
slashCommands: ['release-notes'],
|
|
3889
|
+
agents: [],
|
|
3890
|
+
skills: [],
|
|
3891
|
+
claudeCodeVersion: '2.1.208',
|
|
3892
|
+
};
|
|
3893
|
+
if (outputFormat === 'stream-json') {
|
|
3894
|
+
const output = new StreamJsonOutput((value) => writeJson(io, value), info, sessionId, includePartialMessages, invocation.includeHookEvents);
|
|
3895
|
+
output.init();
|
|
3896
|
+
output.sink({ type: 'text-delta', delta: text });
|
|
3897
|
+
output.sink({ type: 'usage', usage: result.usage });
|
|
3898
|
+
output.result(result, startedAt);
|
|
3899
|
+
}
|
|
3900
|
+
else if (outputFormat === 'json' || invocation.legacyJson) {
|
|
3901
|
+
writeJson(io, createSuccessResult(result, info, startedAt, 0));
|
|
3902
|
+
}
|
|
3903
|
+
else {
|
|
3904
|
+
io.stdout(`${text}\n`);
|
|
3905
|
+
}
|
|
3906
|
+
return 0;
|
|
3907
|
+
}
|
|
3859
3908
|
const service = await dependencies.createService({
|
|
3860
3909
|
eventSink: outputFormat === 'stream-json' && !invocation.legacyJson
|
|
3861
3910
|
? (event) => {
|
|
@@ -9,11 +9,15 @@ export interface ClaudeExtensionDefinition extends ClaudeTextResource {
|
|
|
9
9
|
body: string;
|
|
10
10
|
modelInvocable: boolean;
|
|
11
11
|
permissionSafe: boolean;
|
|
12
|
+
progressMessage?: string;
|
|
13
|
+
builtin?: boolean;
|
|
12
14
|
}
|
|
13
15
|
export interface ClaudeSlashCommandDefinition {
|
|
14
16
|
name: string;
|
|
15
17
|
description: string;
|
|
16
18
|
kind: 'command' | 'skill' | 'mcp';
|
|
19
|
+
progressMessage?: string;
|
|
20
|
+
builtin?: boolean;
|
|
17
21
|
}
|
|
18
22
|
export interface ClaudeAgentDefinition {
|
|
19
23
|
name: string;
|
|
@@ -28,6 +32,7 @@ export interface ClaudePromptExpansionMessage {
|
|
|
28
32
|
contentBlocks?: readonly ModelContentBlock[];
|
|
29
33
|
images?: readonly ModelImage[];
|
|
30
34
|
}
|
|
35
|
+
export declare const BUILTIN_STATUSLINE_AGENT_PATH = "/__praxis_builtin__/agents/statusline-setup.md";
|
|
31
36
|
export declare function validateClaudeExtensions(resources: readonly ClaudeTextResource[]): void;
|
|
32
37
|
export declare class ClaudeExtensionCatalog {
|
|
33
38
|
private readonly commands;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { basename, dirname, extname } from 'node:path';
|
|
2
2
|
import { parse as parseYaml } from 'yaml';
|
|
3
|
+
import { claudeInitDescription, claudeInitPrompt, } from './claude-init-command.js';
|
|
3
4
|
const BUILTIN_LOOP = {
|
|
4
5
|
path: '/__praxis_builtin__/commands/loop.md',
|
|
5
6
|
scope: 'user',
|
|
@@ -9,6 +10,7 @@ const BUILTIN_LOOP = {
|
|
|
9
10
|
description: 'Run a prompt or slash command on a recurring interval; defaults to 10 minutes.',
|
|
10
11
|
modelInvocable: true,
|
|
11
12
|
permissionSafe: true,
|
|
13
|
+
builtin: true,
|
|
12
14
|
body: `# /loop — schedule a recurring prompt
|
|
13
15
|
|
|
14
16
|
Parse the input into an optional interval followed by a prompt, then schedule it with CronCreate.
|
|
@@ -34,6 +36,65 @@ Call CronCreate with the derived cron, the parsed prompt verbatim, and recurring
|
|
|
34
36
|
Input:
|
|
35
37
|
$ARGUMENTS`,
|
|
36
38
|
};
|
|
39
|
+
function builtinInitCommand() {
|
|
40
|
+
return {
|
|
41
|
+
path: '/__praxis_builtin__/commands/init.md',
|
|
42
|
+
scope: 'user',
|
|
43
|
+
content: '',
|
|
44
|
+
kind: 'command',
|
|
45
|
+
name: 'init',
|
|
46
|
+
description: claudeInitDescription(),
|
|
47
|
+
modelInvocable: false,
|
|
48
|
+
permissionSafe: true,
|
|
49
|
+
progressMessage: 'analyzing your codebase',
|
|
50
|
+
builtin: true,
|
|
51
|
+
body: claudeInitPrompt(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const BUILTIN_STATUSLINE_COMMAND = {
|
|
55
|
+
path: '/__praxis_builtin__/commands/statusline.md',
|
|
56
|
+
scope: 'user',
|
|
57
|
+
content: '',
|
|
58
|
+
kind: 'command',
|
|
59
|
+
name: 'statusline',
|
|
60
|
+
description: "Set up Claude Code's status line UI",
|
|
61
|
+
modelInvocable: false,
|
|
62
|
+
permissionSafe: true,
|
|
63
|
+
builtin: true,
|
|
64
|
+
body: `Create an Agent with subagent_type "statusline-setup" and the prompt "$ARGUMENTS"`,
|
|
65
|
+
};
|
|
66
|
+
export const BUILTIN_STATUSLINE_AGENT_PATH = '/__praxis_builtin__/agents/statusline-setup.md';
|
|
67
|
+
const BUILTIN_STATUSLINE_AGENT = {
|
|
68
|
+
path: BUILTIN_STATUSLINE_AGENT_PATH,
|
|
69
|
+
scope: 'user',
|
|
70
|
+
content: '',
|
|
71
|
+
kind: 'agent',
|
|
72
|
+
name: 'statusline-setup',
|
|
73
|
+
description: "Configure the user's Claude Code status line setting.",
|
|
74
|
+
modelInvocable: false,
|
|
75
|
+
permissionSafe: false,
|
|
76
|
+
body: `You configure the statusLine command in the user's shared Claude Code settings.
|
|
77
|
+
|
|
78
|
+
Importing a shell prompt:
|
|
79
|
+
1. Inspect shell configuration in this order: ~/.zshrc, ~/.bashrc, ~/.bash_profile, then ~/.profile.
|
|
80
|
+
2. Find an exported or unexported quoted PS1 assignment. Preserve the prompt's information and colors.
|
|
81
|
+
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.
|
|
82
|
+
4. Emit ANSI sequences with printf because the result is rendered by a terminal in dimmed colors.
|
|
83
|
+
5. Remove a trailing prompt marker such as "$" or ">" from the displayed status line.
|
|
84
|
+
6. If no PS1 exists and the request gives no other design, ask what information should be displayed.
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
Preserve every unrelated setting and update ~/.claude/settings.json with this shape:
|
|
89
|
+
{
|
|
90
|
+
"statusLine": {
|
|
91
|
+
"type": "command",
|
|
92
|
+
"command": "the command"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
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.`,
|
|
97
|
+
};
|
|
37
98
|
const GENERAL_PURPOSE_AGENT = {
|
|
38
99
|
name: 'general-purpose',
|
|
39
100
|
description: 'General-purpose agent for researching complex questions, searching for code, and executing multi-step tasks.',
|
|
@@ -175,7 +236,11 @@ export class ClaudeExtensionCatalog {
|
|
|
175
236
|
this.disableSlashCommands = options.disableSlashCommands === true;
|
|
176
237
|
this.commands = options.disableSlashCommands
|
|
177
238
|
? new Map()
|
|
178
|
-
: new Map([
|
|
239
|
+
: new Map([
|
|
240
|
+
['loop', BUILTIN_LOOP],
|
|
241
|
+
['init', builtinInitCommand()],
|
|
242
|
+
['statusline', BUILTIN_STATUSLINE_COMMAND],
|
|
243
|
+
]);
|
|
179
244
|
if (!options.disableSlashCommands) {
|
|
180
245
|
for (const [name, command] of indexed('command', resources.commands)) {
|
|
181
246
|
this.commands.set(name, command);
|
|
@@ -184,7 +249,10 @@ export class ClaudeExtensionCatalog {
|
|
|
184
249
|
this.skills = options.disableSlashCommands
|
|
185
250
|
? new Map()
|
|
186
251
|
: indexed('skill', resources.skills);
|
|
187
|
-
this.agents =
|
|
252
|
+
this.agents = new Map([
|
|
253
|
+
['statusline-setup', BUILTIN_STATUSLINE_AGENT],
|
|
254
|
+
...indexed('agent', resources.agents),
|
|
255
|
+
]);
|
|
188
256
|
}
|
|
189
257
|
setMcpPrompts(prompts) {
|
|
190
258
|
this.mcpPrompts.clear();
|
|
@@ -208,6 +276,9 @@ export class ClaudeExtensionCatalog {
|
|
|
208
276
|
if (!definition) {
|
|
209
277
|
return { userMessages: [prompt] };
|
|
210
278
|
}
|
|
279
|
+
const invocationArguments = definition === BUILTIN_STATUSLINE_COMMAND && argumentsText.length === 0
|
|
280
|
+
? 'Configure my statusLine from my shell PS1 configuration'
|
|
281
|
+
: argumentsText;
|
|
211
282
|
return {
|
|
212
283
|
userMessages: [
|
|
213
284
|
[
|
|
@@ -217,7 +288,7 @@ export class ClaudeExtensionCatalog {
|
|
|
217
288
|
? [`<command-args>${argumentsText}</command-args>`]
|
|
218
289
|
: []),
|
|
219
290
|
].join('\n'),
|
|
220
|
-
renderInvocation(definition,
|
|
291
|
+
renderInvocation(definition, invocationArguments),
|
|
221
292
|
],
|
|
222
293
|
};
|
|
223
294
|
}
|
|
@@ -270,6 +341,10 @@ export class ClaudeExtensionCatalog {
|
|
|
270
341
|
name: definition.name,
|
|
271
342
|
description: definition.description,
|
|
272
343
|
kind: definition.kind === 'skill' ? 'skill' : 'command',
|
|
344
|
+
...(definition.progressMessage === undefined
|
|
345
|
+
? {}
|
|
346
|
+
: { progressMessage: definition.progressMessage }),
|
|
347
|
+
...(definition.builtin === true ? { builtin: true } : {}),
|
|
273
348
|
})),
|
|
274
349
|
...[...this.mcpPrompts.values()].map((prompt) => ({
|
|
275
350
|
name: prompt.userFacingName,
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function enhancedClaudeInitEnabled(environment?: NodeJS.ProcessEnv): boolean;
|
|
2
|
+
export declare function claudeInitDescription(environment?: NodeJS.ProcessEnv): string;
|
|
3
|
+
export declare function claudeInitPrompt(environment?: NodeJS.ProcessEnv): string;
|
|
4
|
+
//# sourceMappingURL=claude-init-command.d.ts.map
|