praxis-agent 0.43.1 → 0.45.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,249 @@
1
+ import { ProviderCredentialVault, } from '../persistence/provider-credential-vault.js';
2
+ import { deviceLoginWithCodexOAuth, loginWithCodexOAuth, } from '../providers/codex-oauth.js';
3
+ import { resolveProviderTarget } from '../providers/provider-settings.js';
4
+ import { resolveDataPlaneRoot } from '../persistence/data-plane.js';
5
+ const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
6
+ const MAX_SECRET_BYTES = 64 * 1024;
7
+ function fail(message) {
8
+ throw new Error(message);
9
+ }
10
+ function identifier(value, label) {
11
+ if (!IDENTIFIER.test(value))
12
+ fail(`${label} must match [A-Za-z0-9][A-Za-z0-9._-]{0,127}`);
13
+ return value;
14
+ }
15
+ function parse(args, options) {
16
+ if (args[0] === 'auth')
17
+ args = args.slice(1);
18
+ const action = args[0];
19
+ if (action !== 'status' &&
20
+ action !== 'set-key' &&
21
+ action !== 'login' &&
22
+ action !== 'logout') {
23
+ fail('auth requires status, set-key, login, or logout');
24
+ }
25
+ let provider;
26
+ let profile;
27
+ let json = options.json ?? false;
28
+ let noBrowser = options.noBrowser ?? false;
29
+ let device = options.device ?? false;
30
+ for (let index = 1; index < args.length; index += 1) {
31
+ const value = args[index];
32
+ if (value === '--json') {
33
+ if (json)
34
+ fail('--json may only be specified once');
35
+ json = true;
36
+ continue;
37
+ }
38
+ if (value === '--device') {
39
+ if (device)
40
+ fail('--device may only be specified once');
41
+ device = true;
42
+ continue;
43
+ }
44
+ if (value === '--no-browser') {
45
+ if (noBrowser)
46
+ fail('--no-browser may only be specified once');
47
+ noBrowser = true;
48
+ continue;
49
+ }
50
+ if (value === '--profile' || value?.startsWith('--profile=')) {
51
+ if (profile !== undefined)
52
+ fail('--profile may only be specified once');
53
+ const selected = value === '--profile' ? args[++index] : value.slice('--profile='.length);
54
+ if (typeof selected !== 'string' || selected.length === 0)
55
+ fail('--profile requires a value');
56
+ profile = identifier(selected, 'profile');
57
+ continue;
58
+ }
59
+ if (value?.startsWith('-'))
60
+ fail(`Unknown option: ${value}`);
61
+ if (typeof value !== 'string')
62
+ fail(`Unexpected operand for auth ${action}`);
63
+ if (provider !== undefined)
64
+ fail(`Unexpected operand for auth ${action}: ${value}`);
65
+ provider = identifier(value, 'provider');
66
+ }
67
+ if (options.profile !== undefined &&
68
+ profile !== undefined &&
69
+ options.profile !== profile)
70
+ fail('auth --profile conflicts with the selected profile');
71
+ if (options.providerProfile !== undefined &&
72
+ profile !== undefined &&
73
+ options.providerProfile !== profile)
74
+ fail('auth --profile conflicts with --provider-profile');
75
+ if (options.profile !== undefined &&
76
+ options.providerProfile !== undefined &&
77
+ options.profile !== options.providerProfile)
78
+ fail('auth --profile conflicts with --provider-profile');
79
+ const selectedProfile = profile ?? options.profile ?? options.providerProfile;
80
+ profile =
81
+ action === 'status' && selectedProfile === undefined
82
+ ? undefined
83
+ : identifier(selectedProfile ?? 'default', 'profile');
84
+ if (action === 'status' && provider === undefined) {
85
+ // status accepts zero or one provider operand.
86
+ }
87
+ else if (action !== 'status' && provider === undefined) {
88
+ fail(`auth ${action} requires a provider`);
89
+ }
90
+ if (action === 'set-key' && provider === 'openai-codex')
91
+ fail('auth set-key does not support openai-codex; use auth login');
92
+ if (action === 'login' && provider !== 'openai-codex')
93
+ fail('auth login only supports openai-codex');
94
+ if (action !== 'login' && (device || noBrowser))
95
+ fail('--device and --no-browser are only valid with auth login');
96
+ return {
97
+ action,
98
+ ...(provider === undefined ? {} : { provider }),
99
+ ...(profile === undefined ? {} : { profile }),
100
+ json,
101
+ device,
102
+ noBrowser,
103
+ };
104
+ }
105
+ function defaultVault(options) {
106
+ const environment = options.environment ?? process.env;
107
+ const vaultOptions = {
108
+ configRoot: options.configRoot ?? resolveDataPlaneRoot({ environment }),
109
+ environment,
110
+ };
111
+ return new ProviderCredentialVault(vaultOptions);
112
+ }
113
+ async function assertCodexLoginEnabled(options, profileId) {
114
+ const environment = options.environment ?? process.env;
115
+ await resolveProviderTarget({
116
+ configRoot: options.configRoot ?? resolveDataPlaneRoot({ environment }),
117
+ cwd: options.cwd ?? process.cwd(),
118
+ environment,
119
+ provider: 'openai-codex',
120
+ profile: profileId,
121
+ model: 'codex-authentication',
122
+ });
123
+ }
124
+ function json(io, value) {
125
+ io.stdout(`${JSON.stringify(value)}\n`);
126
+ }
127
+ function text(io, message) {
128
+ io.stdout(`${message}\n`);
129
+ }
130
+ function metadata(value) {
131
+ return {
132
+ provider: value.key.providerId,
133
+ profile: value.key.profileId,
134
+ type: value.type,
135
+ updatedAt: value.updatedAt,
136
+ ...(value.expiresAt === undefined
137
+ ? {}
138
+ : { expiry: value.expiresAt > Date.now() ? 'valid' : 'expired' }),
139
+ };
140
+ }
141
+ function sortedMetadata(records, provider, profile) {
142
+ return records
143
+ .filter((record) => (provider === undefined || record.key.providerId === provider) &&
144
+ (profile === undefined || record.key.profileId === profile))
145
+ .sort((left, right) => {
146
+ const providerOrder = left.key.providerId.localeCompare(right.key.providerId);
147
+ return (providerOrder || left.key.profileId.localeCompare(right.key.profileId));
148
+ })
149
+ .map(metadata);
150
+ }
151
+ export function secretLine(value) {
152
+ if (Buffer.byteLength(value, 'utf8') > MAX_SECRET_BYTES)
153
+ fail('Credential exceeds the 64 KiB limit');
154
+ const stripped = value.endsWith('\r\n')
155
+ ? value.slice(0, -2)
156
+ : value.endsWith('\r') || value.endsWith('\n')
157
+ ? value.slice(0, -1)
158
+ : value;
159
+ if (/[\r\n]/u.test(stripped) || stripped.trim().length === 0)
160
+ fail('Credential must be exactly one non-blank line');
161
+ return stripped;
162
+ }
163
+ export async function executeProviderAuthCommand(args, options) {
164
+ const parsed = parse(args, options);
165
+ const vault = options.vault ?? defaultVault(options);
166
+ if (parsed.action === 'status') {
167
+ const credentials = sortedMetadata(await vault.list(), parsed.provider, parsed.profile);
168
+ if (parsed.json) {
169
+ json(options.io, { type: 'provider-auth-status', credentials });
170
+ }
171
+ else if (credentials.length === 0) {
172
+ text(options.io, 'No provider credentials configured.');
173
+ }
174
+ else {
175
+ text(options.io, credentials
176
+ .map((credential) => `${credential.provider}/${credential.profile}: ${credential.type}${credential.expiry === undefined ? '' : ` (${credential.expiry})`}`)
177
+ .join('\n'));
178
+ }
179
+ return 0;
180
+ }
181
+ const provider = parsed.provider;
182
+ if (provider === undefined)
183
+ fail(`auth ${parsed.action} requires a provider`);
184
+ const key = {
185
+ providerId: provider,
186
+ profileId: parsed.profile ?? 'default',
187
+ };
188
+ if (parsed.action === 'set-key') {
189
+ if (!options.io.readSecret)
190
+ fail('auth set-key requires an interactive secret reader');
191
+ const secret = secretLine(await options.io.readSecret('API key: ', options.signal));
192
+ await vault.modify(key, () => ({ type: 'api-key', secret }));
193
+ const result = {
194
+ provider: key.providerId,
195
+ profile: key.profileId,
196
+ type: 'api-key',
197
+ };
198
+ if (parsed.json)
199
+ json(options.io, result);
200
+ else
201
+ text(options.io, `Stored api-key credential for ${key.providerId}/${key.profileId}.`);
202
+ return 0;
203
+ }
204
+ if (parsed.action === 'logout') {
205
+ const existing = await vault.read(key);
206
+ if (existing !== undefined)
207
+ await vault.delete(key);
208
+ const result = {
209
+ provider: key.providerId,
210
+ profile: key.profileId,
211
+ deleted: true,
212
+ };
213
+ if (parsed.json)
214
+ json(options.io, result);
215
+ else
216
+ text(options.io, `Logged out ${key.providerId}/${key.profileId}.`);
217
+ return 0;
218
+ }
219
+ await (options.assertCodexLoginEnabled ??
220
+ ((profileId) => assertCodexLoginEnabled(options, profileId)))(key.profileId);
221
+ const loginOptions = {
222
+ profileId: key.profileId,
223
+ vault: createCodexOAuthVault(vault),
224
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
225
+ ...(parsed.noBrowser ? { noBrowser: true } : {}),
226
+ write: (message) => options.io.stderr?.(message),
227
+ };
228
+ const login = parsed.device
229
+ ? (options.deviceLoginWithCodexOAuth ?? deviceLoginWithCodexOAuth)
230
+ : (options.loginWithCodexOAuth ?? loginWithCodexOAuth);
231
+ await login(loginOptions);
232
+ const result = {
233
+ provider: key.providerId,
234
+ profile: key.profileId,
235
+ type: 'oauth',
236
+ };
237
+ if (parsed.json)
238
+ json(options.io, result);
239
+ else
240
+ text(options.io, `Logged in ${key.providerId}/${key.profileId}.`);
241
+ return 0;
242
+ }
243
+ function createCodexOAuthVault(vault) {
244
+ return {
245
+ read: (key) => vault.read(key),
246
+ modify: (key, callback) => vault.modify(key, async (current) => callback(current)),
247
+ };
248
+ }
249
+ //# sourceMappingURL=provider-auth-command.js.map
@@ -272,18 +272,18 @@ export function projectQuietFrame(input) {
272
272
  : cleanActiveTail(body.transcript.active.text, viewportRows === undefined ? 200 : Math.max(1, viewportRows - 2));
273
273
  const activeText = activeTail.lines;
274
274
  if (activeText.some(({ text }) => Boolean(text)))
275
- activeText.forEach(({ text: part, start }, index) => lines.push(createQuietFrameRow(`quiet:active:text:${start}`, `${index === 0 ? 'praxis> ' : ' '}${index === 0 && activeTail.hidden ? '… ' : ''}${part}`, 'active', 'body', `${index === 0 ? 'Praxis: ' : ''}${part}`)));
275
+ activeText.forEach(({ text: part, start }, index) => lines.push(createQuietFrameRow(`quiet:active:text:${start}`, `${index === 0 ? '⏺ ' : ' '}${index === 0 && activeTail.hidden ? '… ' : ''}${part}`, 'active', 'body', `${index === 0 ? 'Praxis: ' : ''}${part}`)));
276
276
  const thinking = screenReader
277
277
  ? cleanActiveTail(body.transcript.active.thinking, Number.POSITIVE_INFINITY)
278
278
  : cleanActiveTail(body.transcript.active.thinking, viewportRows === undefined ? 200 : Math.max(1, viewportRows - 2));
279
279
  if (thinking.lines.some(({ text }) => Boolean(text)))
280
- thinking.lines.forEach(({ text: part, start }, index) => lines.push(createQuietFrameRow(`quiet:active:thinking:${start}`, `${index === 0 ? '… ' : ' '}${part}`, 'active', 'muted', `${index === 0 ? 'Thinking: ' : ''}${part}`)));
280
+ thinking.lines.forEach(({ text: part, start }, index) => lines.push(createQuietFrameRow(`quiet:active:thinking:${start}`, `${index === 0 ? '✻ ' : ' '}${part}`, 'active', 'muted', `${index === 0 ? 'Thinking: ' : ''}${part}`)));
281
281
  }
282
282
  }
283
283
  const focus = input.focusRows.length > 0
284
284
  ? input.focusRows.map((item) => withRegion(item, 'focus'))
285
285
  : [
286
- createQuietFrameRow('quiet:composer', `${input.shellMode ? 'shell>' : 'you>'} ${clean(input.composerText)}`, 'composer', 'input', `${input.shellMode ? 'Shell' : 'Composer'} input field`),
286
+ createQuietFrameRow('quiet:composer', `${input.shellMode ? '! ' : '❯ '}${clean(input.composerText)}`, 'composer', 'input', `${input.shellMode ? 'Shell' : 'Composer'} input field`),
287
287
  ];
288
288
  lines.push(...focus);
289
289
  const statusText = `${input.busy ? 'Working · ' : ''}${clean(input.status) || (input.busy ? 'In progress' : 'Ready')}`;
@@ -302,7 +302,7 @@ export function projectQuietFrame(input) {
302
302
  const compact = [...retained, ...focusRows, status];
303
303
  lines.splice(0, lines.length, ...compact);
304
304
  }
305
- const composerPrefix = `${input.shellMode ? 'shell>' : 'you>'} `;
305
+ const composerPrefix = input.shellMode ? '! ' : '❯ ';
306
306
  if (!screenReader) {
307
307
  for (let index = 0; index < lines.length; index += 1) {
308
308
  const row = lines[index];
@@ -497,7 +497,7 @@ function estimateRenderedTranscriptEntryLines(entry, width, mode) {
497
497
  if (item.kind === 'user')
498
498
  return (1 +
499
499
  (mode === 'screen-reader' ? 1 : 0) +
500
- wrappedLineCount(`${mode === 'screen-reader' ? 'You: ' : 'you> '}${item.text}`, width));
500
+ wrappedLineCount(`${mode === 'screen-reader' ? 'You: ' : '❯ '}${item.text}`, width));
501
501
  if (item.kind === 'assistant')
502
502
  return (1 +
503
503
  (mode === 'screen-reader' ? 1 : 0) +
@@ -667,7 +667,7 @@ function entryViewportRows(entry, width, mode) {
667
667
  return [
668
668
  '',
669
669
  ...(mode === 'screen-reader' ? [''] : []),
670
- ...textVisualRows(`${mode === 'screen-reader' ? 'You: ' : 'you> '}${item.text}`, width),
670
+ ...textVisualRows(`${mode === 'screen-reader' ? 'You: ' : '❯ '}${item.text}`, width),
671
671
  ];
672
672
  if (item.kind === 'assistant')
673
673
  return [
@@ -818,7 +818,7 @@ function assistantMarkdownProjectionRows(text, width, mode) {
818
818
  fenceAfter: false,
819
819
  fenceLabel: 'code',
820
820
  };
821
- const prefixed = textVisualRows(`praxis> ${first.text.trimEnd()}`, width).map((rowText) => ({ ...first, text: rowText }));
821
+ const prefixed = textVisualRows(`⏺ ${first.text.trimEnd()}`, width).map((rowText) => ({ ...first, text: rowText }));
822
822
  return [...prefixed, ...rows.slice(1)];
823
823
  }
824
824
  export function createTranscriptEntryViewportIndex(entry, width, mode) {
@@ -23,10 +23,10 @@ export async function promptWorkspaceTrust(inventory, io = {}) {
23
23
  const output = io.output ?? ((text) => process.stderr.write(text));
24
24
  if (io.signal?.aborted)
25
25
  return false;
26
- output(`Workspace executable resources found for ${safeWorkspaceTrustDisplayField(inventory.canonicalPath)}\n`);
26
+ output(`Workspace-controlled resources/configuration found for ${safeWorkspaceTrustDisplayField(inventory.canonicalPath)}\n`);
27
27
  for (const origin of inventory.origins)
28
28
  output(` ${origin.kind} (${origin.scope}) ${safeWorkspaceTrustDisplayField(origin.path)}: ${safeWorkspaceTrustDisplayField(origin.label)}\n`);
29
- output('Trust these workspace executables? [y/N] ');
29
+ output('Trust these workspace-controlled resources/configuration? [y/N] ');
30
30
  const input = io.input ?? process.stdin;
31
31
  const iterator = input[Symbol.asyncIterator]();
32
32
  const abortedResult = Symbol('workspace-trust-prompt-aborted');
@@ -11,7 +11,7 @@ import type { TuiSlashCommand } from './cli/tui/slash-commands.js';
11
11
  import { type TuiHookConfiguration } from './cli/tui/hook-settings.js';
12
12
  import { type PraxisRuntimeSettings } from './cli/tui/runtime-settings.js';
13
13
  import { type ClaudePermissionMode } from './permissions/claude-permission-resolver.js';
14
- import { type WorkspaceTrustInventory } from './security/workspace-trust.js';
14
+ import { type WorkspaceTrustAssessment, type WorkspaceTrustInventory } from './security/workspace-trust.js';
15
15
  import { type ClaudeMcpServerStatus, type ClaudeMcpToolInspection } from './mcp/claude-mcp-tools.js';
16
16
  import { authenticateMcpServer } from './mcp/claude-mcp-oauth.js';
17
17
  import { servePraxisMcpStdio } from './mcp/praxis-mcp-server.js';
@@ -20,6 +20,7 @@ import { type ClaudeInteractiveToolCallbacks } from './tools/claude-interactive-
20
20
  import { type TopLevelAgentSummary } from './application/top-level-agent-manager.js';
21
21
  import { launchTmuxWorktree } from './platform/tmux-worktree.js';
22
22
  import { type CliControls, type CliRuntimeInfo, type CliElicitationRequest, type CliElicitationResult } from './cli/protocol.js';
23
+ import { executeProviderAuthCommand } from './cli/provider-auth-command.js';
23
24
  import { type PluginEvalDependencies } from './plugins/claude-plugin-eval.js';
24
25
  import { type SelfUpdateResult } from './maintenance/self-update.js';
25
26
  export { parseContextEnvironment, parseProviderEnvironment };
@@ -28,6 +29,7 @@ export interface CliIO {
28
29
  stderr(message: string): void;
29
30
  isTTY?: boolean;
30
31
  readStdinLines?: () => AsyncIterable<string | Uint8Array>;
32
+ readSecret?: (prompt: string, signal?: AbortSignal) => Promise<string>;
31
33
  }
32
34
  interface SessionCommands {
33
35
  teamLeadOperations?: TeamLeadOperations;
@@ -143,6 +145,8 @@ export interface CliDependencies extends InteractiveServiceFactory {
143
145
  }): Promise<SessionCommands>;
144
146
  createAutoModeCritic?(options: {
145
147
  model?: string;
148
+ provider?: string;
149
+ providerProfile?: string;
146
150
  dataPlane?: DataPlane;
147
151
  configRoot?: string;
148
152
  statePath?: string;
@@ -170,6 +174,7 @@ export interface CliDependencies extends InteractiveServiceFactory {
170
174
  launchTmux?: typeof launchTmuxWorktree;
171
175
  mcpAuthenticate?: typeof authenticateMcpServer;
172
176
  mcpServe?: typeof servePraxisMcpStdio;
177
+ executeProviderAuthCommand?: typeof executeProviderAuthCommand;
173
178
  selfUpdate?: (options: {
174
179
  operation: 'install' | 'update';
175
180
  target?: string;
@@ -177,6 +182,20 @@ export interface CliDependencies extends InteractiveServiceFactory {
177
182
  signal?: AbortSignal;
178
183
  }) => Promise<SelfUpdateResult>;
179
184
  }
185
+ interface ConsoleSecretInput extends AsyncIterable<string | Uint8Array> {
186
+ readonly isTTY?: boolean;
187
+ readonly isRaw?: boolean;
188
+ isPaused(): boolean;
189
+ setRawMode?(enabled: boolean): unknown;
190
+ pause(): unknown;
191
+ resume(): unknown;
192
+ on(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
193
+ removeListener(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
194
+ }
195
+ interface ConsoleSecretOutput {
196
+ write(message: string): unknown;
197
+ }
198
+ export declare function readConsoleSecret(prompt: string, signal?: AbortSignal, input?: ConsoleSecretInput, output?: ConsoleSecretOutput): Promise<string>;
180
199
  /**
181
200
  * Shared runtime model precedence used by every consumer (provider
182
201
  * construction, status/doctor output, and the interactive display):
@@ -194,9 +213,23 @@ export declare function resolveInteractiveRuntimeSettingsLocation(dataPlane: Dat
194
213
  statePath: string;
195
214
  };
196
215
  export declare function resolveUnknownCostSidecarPath(dataPlane: DataPlane, configRoot: string): string;
216
+ export declare function resolveInteractiveProviderStartup(options: {
217
+ controls: CliControls;
218
+ configRoot: string;
219
+ statePath: string;
220
+ cwd: string;
221
+ environment?: NodeJS.ProcessEnv;
222
+ approveWorkspaceTrust?: (assessment: WorkspaceTrustAssessment) => boolean | Promise<boolean>;
223
+ }): Promise<{
224
+ effectiveModel: string | undefined;
225
+ trustProjectRequestAvailable: boolean;
226
+ }>;
197
227
  export declare function createDefaultDependencies(entrypoint?: string): CliDependencies;
198
228
  export declare function createBackgroundWorkerRuntime(workerSink: RuntimeEventSink, dispatch: {
199
229
  argv: string[];
200
230
  }, createService?: CliDependencies['createService']): Promise<Awaited<ReturnType<CliDependencies['createService']>>>;
231
+ export declare function agentDashboardWorkerArgv(invocation: CliControls & {
232
+ agent: string | undefined;
233
+ }): string[];
201
234
  export declare function run(argv: readonly string[], io?: CliIO, dependencies?: CliDependencies, signal?: AbortSignal): Promise<number>;
202
235
  //# sourceMappingURL=cli-runtime.d.ts.map