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.
@@ -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([['loop', BUILTIN_LOOP]]);
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 = indexed('agent', resources.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, argumentsText),
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