praxis-agent 0.43.1 → 0.44.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.
@@ -33,7 +33,7 @@ import { createClaudeModelAutoClassifier, defaultClaudeAutoModeConfig, loadClaud
33
33
  import { ClaudeExtensionPermissionResolver, ClaudeExtensionToolRegistry, } from './extensions/claude-extension-tools.js';
34
34
  import { ClaudeExtensionCatalog } from './extensions/claude-extensions.js';
35
35
  import { ClaudeHookRunner } from './hooks/claude-hooks.js';
36
- import { allowedWorkspaceHookSettings, allowedWorkspaceMcpResources, assessWorkspaceTrust, persistWorkspaceTrust, workspaceTrustDecisionKey, workspaceTrustInventory, } from './security/workspace-trust.js';
36
+ import { allowedWorkspaceHookSettings, allowedWorkspaceMcpResources, assessWorkspaceTrust, hasWorkspaceProviderSelection, persistWorkspaceTrust, workspaceTrustDecisionKey, workspaceTrustInventory, } from './security/workspace-trust.js';
37
37
  import { createWorkspaceTrustDecisionCache, promptWorkspaceTrust, safeWorkspaceTrustDisplayField, } from './cli/workspace-trust-prompt.js';
38
38
  import { ClaudeSessionEnvironment } from './hooks/claude-session-environment.js';
39
39
  import { ClaudeMcpToolRegistry, } from './mcp/claude-mcp-tools.js';
@@ -42,14 +42,17 @@ import { authenticateMcpServer, ClaudeMcpOAuthStore, mcpOAuthServerIdentity, rea
42
42
  import { servePraxisMcpStdio } from './mcp/praxis-mcp-server.js';
43
43
  const VERIFIED_CLAUDE_SCHEMA_VERSION = 'native';
44
44
  const isClaudeSessionId = isSessionId;
45
+ function truthyEnvironmentValue(value) {
46
+ return /^(?:1|true|yes|on)$/iu.test((value ?? '').trim());
47
+ }
45
48
  import { writeFileAtomically } from './platform/atomic-write.js';
46
49
  import { redactSensitiveText, sensitiveEnvironmentValues, } from './platform/sensitive-data.js';
47
- import { AnthropicCompatibleProvider } from './providers/anthropic-compatible.js';
48
- import { createAnthropicPromptCachePolicyResolver } from './providers/anthropic-prompt-cache.js';
49
50
  import { FallbackModelProvider } from './providers/fallback-provider.js';
50
- import { DeadlineModelProvider } from './providers/deadline-provider.js';
51
- import { OpenAICompatibleProvider } from './providers/openai-compatible.js';
51
+ import { ProviderCredentialVault } from './persistence/provider-credential-vault.js';
52
52
  import { parseContextEnvironment, parseProviderEnvironment, } from './providers/environment.js';
53
+ import { ProviderAuthenticationError, resolveProviderCredential, } from './providers/provider-auth.js';
54
+ import { resolveProviderRegistry } from './providers/provider-registry.js';
55
+ import { ProviderSettingsError, resolveProviderTarget, } from './providers/provider-settings.js';
53
56
  import { ModelPricingRegistry, usageCostUsd } from './core/usage.js';
54
57
  import { LocalToolRegistry } from './tools/local-tools.js';
55
58
  import { ClaudeLspToolManager } from './tools/claude-lsp-tool.js';
@@ -63,6 +66,7 @@ import { launchTmuxWorktree } from './platform/tmux-worktree.js';
63
66
  import { claudeSandboxRuntime } from './sandbox/claude-sandbox-runtime.js';
64
67
  import { nativeSandboxTempDirectory, loadClaudeSandboxSettings, } from './sandbox/claude-sandbox-settings.js';
65
68
  import { createErrorResult, createSuccessResult, isHeadlessCostCommand, matchHeadlessColorCommand, parseCliInvocation, readStreamJsonMessages, StreamJsonOutput, } from './cli/protocol.js';
69
+ import { executeProviderAuthCommand } from './cli/provider-auth-command.js';
66
70
  import { describeClaudePlugin, initClaudePlugin, installClaudePlugin, loadClaudePlugins, readPluginRegistry, setClaudePluginEnabled, uninstallClaudePlugin, updateClaudePlugin, validateClaudePlugin, } from './plugins/claude-plugin-runtime.js';
67
71
  import { addClaudeMarketplace, disableAllNativePlugins, installClaudeMarketplacePlugin, listClaudeMarketplaceAvailablePlugins, listNativePluginRecords, readClaudeKnownMarketplaces, removeClaudeMarketplace, setNativePluginEnabled, saveClaudePluginConfig, uninstallNativePlugin, updateClaudeMarketplace, updateNativePlugin, validateClaudeMarketplace, } from './plugins/claude-plugin-marketplace.js';
68
72
  import { executeClaudePluginEvalCommand, PLUGIN_EVAL_HELP, } from './plugins/claude-plugin-eval.js';
@@ -97,60 +101,6 @@ function fileResourceHeaders(environment, providerEnvironment, credential) {
97
101
  : {}),
98
102
  };
99
103
  }
100
- function createProviderForModel({ apiKey, environment, provider, context, controls, explicitThinkingControls, }) {
101
- const resolvePromptCachePolicy = createAnthropicPromptCachePolicyResolver(environment);
102
- return (selectedModel) => {
103
- const providerOptions = {
104
- apiKey,
105
- model: selectedModel,
106
- baseUrl: provider.baseUrl,
107
- ...('contextWindowTokens' in context
108
- ? { contextWindowTokens: context.contextWindowTokens }
109
- : {}),
110
- };
111
- const concrete = provider.provider === 'anthropic'
112
- ? new AnthropicCompatibleProvider({
113
- ...providerOptions,
114
- promptCaching: resolvePromptCachePolicy({
115
- baseUrl: provider.baseUrl,
116
- model: selectedModel,
117
- }),
118
- thinking: {
119
- mode: controls.thinking ?? 'enabled',
120
- ...(controls.maxThinkingTokens === undefined
121
- ? {}
122
- : { maxTokens: controls.maxThinkingTokens }),
123
- },
124
- ...('maxOutputTokens' in provider
125
- ? { maxOutputTokens: provider.maxOutputTokens }
126
- : {}),
127
- ...('anthropicVersion' in provider
128
- ? { anthropicVersion: provider.anthropicVersion }
129
- : {}),
130
- ...('webSearch' in provider
131
- ? { webSearch: provider.webSearch }
132
- : {}),
133
- })
134
- : new OpenAICompatibleProvider({
135
- ...providerOptions,
136
- ...(explicitThinkingControls.thinking === undefined &&
137
- explicitThinkingControls.maxThinkingTokens === undefined
138
- ? {}
139
- : {
140
- thinking: {
141
- mode: controls.thinking ?? 'enabled',
142
- ...(controls.maxThinkingTokens === undefined
143
- ? {}
144
- : { maxTokens: controls.maxThinkingTokens }),
145
- },
146
- }),
147
- });
148
- return new DeadlineModelProvider({
149
- provider: concrete,
150
- deadlineMs: provider.deadlineMs,
151
- });
152
- };
153
- }
154
104
  const HELP = `Praxis — local-first general agent
155
105
 
156
106
  Usage:
@@ -174,6 +124,7 @@ Usage:
174
124
  praxis auto-mode <config|defaults|critique>
175
125
  praxis plugin|plugins <details|list|install|uninstall|enable|disable|update|init|prune|tag|validate|marketplace> ...
176
126
  praxis doctor [--json]
127
+ praxis auth <status|set-key|login|logout> ...
177
128
  praxis import [options] [source]
178
129
  praxis install [--force] [stable|latest|version]
179
130
  praxis update|upgrade
@@ -189,6 +140,8 @@ Options:
189
140
  --resume-session-at <message-id> Resume at an active conversation message
190
141
  --session-id <uuid> Use an explicit ID for a new session
191
142
  -n, --name <name> Set session display name
143
+ --provider <id> Select provider for this session
144
+ --provider-profile <id> Select provider profile for this session
192
145
  --model <model> Select model for this session
193
146
  --effort <level> low, medium, high, xhigh, or max
194
147
  --environment <environment_id> Unsupported: Praxis runs sessions locally
@@ -225,7 +178,7 @@ Options:
225
178
  --setting-sources <sources> user, project, local, or an empty list
226
179
  --safe-mode Disable shared customizations
227
180
  --bare Use only explicitly supplied context
228
- --trust-project Trust current workspace executables
181
+ --trust-project Trust current workspace-controlled resources/configuration
229
182
  --system-prompt <prompt> Set system prompt
230
183
  --append-system-prompt <prompt> Append system prompt
231
184
  --exclude-dynamic-system-prompt-sections
@@ -259,13 +212,65 @@ Options:
259
212
  --tmux[=classic] Launch worktree in an iTerm2 native pane when available; classic forces tmux
260
213
 
261
214
  Provider environment:
262
- PRAXIS_PROVIDER=openai|anthropic, PRAXIS_API_KEY, PRAXIS_MODEL
215
+ PRAXIS_PROVIDER=<provider-id>, PRAXIS_API_KEY, PRAXIS_MODEL
216
+ PRAXIS_PROVIDER_PROFILE
263
217
  PRAXIS_BASE_URL, PRAXIS_MAX_OUTPUT_TOKENS, PRAXIS_ANTHROPIC_VERSION
264
218
  PRAXIS_ANTHROPIC_WEB_SEARCH=true|false
265
219
  PRAXIS_ANTHROPIC_PROMPT_CACHING=true|false
266
220
  PRAXIS_ANTHROPIC_PROMPT_CACHE_TTL=5m|1h
267
221
  PRAXIS_CONTEXT_WINDOW_TOKENS, PRAXIS_CONTEXT_RESERVE_TOKENS
268
222
  `;
223
+ const AUTH_HELP = `Usage: praxis auth <command>
224
+
225
+ Commands:
226
+ status [provider] [--profile <profile>] [--json]
227
+ set-key <provider> [--profile <profile>] [--json]
228
+ login openai-codex [--profile <profile>] [--device] [--no-browser] [--json]
229
+ logout <provider> [--profile <profile>] [--json]
230
+
231
+ Options:
232
+ -h, --help Display help for command
233
+ `;
234
+ const AUTH_ACTION_HELP = {
235
+ status: `Usage: praxis auth status [provider] [--profile <profile>] [--json]
236
+
237
+ List credential metadata without exposing secrets.
238
+
239
+ Options:
240
+ --profile <profile> Filter by provider profile
241
+ --json Output machine-readable metadata
242
+ -h, --help Display help for command
243
+ `,
244
+ 'set-key': `Usage: praxis auth set-key <provider> [--profile <profile>] [--json]
245
+
246
+ Read an API key securely from the terminal or stdin and store it in the native Vault.
247
+
248
+ Options:
249
+ --profile <profile> Select provider profile (default: default)
250
+ --json Output a machine-readable confirmation
251
+ -h, --help Display help for command
252
+ `,
253
+ login: `Usage: praxis auth login openai-codex [--profile <profile>] [--device] [--no-browser] [--json]
254
+
255
+ Authorize an experimental Codex subscription profile with OpenAI OAuth.
256
+
257
+ Options:
258
+ --profile <profile> Select Codex profile (default: default)
259
+ --device Use the device authorization flow
260
+ --no-browser Print instructions without opening a browser
261
+ --json Keep stdout machine-readable
262
+ -h, --help Display help for command
263
+ `,
264
+ logout: `Usage: praxis auth logout <provider> [--profile <profile>] [--json]
265
+
266
+ Delete exactly one native Vault credential.
267
+
268
+ Options:
269
+ --profile <profile> Select provider profile (default: default)
270
+ --json Output a machine-readable confirmation
271
+ -h, --help Display help for command
272
+ `,
273
+ };
269
274
  const AGENTS_HELP = `Usage: praxis agents [options]
270
275
 
271
276
  Manage background agents
@@ -296,6 +301,10 @@ Options:
296
301
  dispatched sessions (repeatable)
297
302
  --model <model> Default model for sessions dispatched
298
303
  from agent view
304
+ --provider <id> Default provider for sessions dispatched
305
+ from agent view
306
+ --provider-profile <id> Default provider profile for dispatched
307
+ sessions
299
308
  --permission-mode <mode> Default permission mode for sessions
300
309
  dispatched from agent view
301
310
  --plugin-dir <path> Load plugins from specified directory
@@ -759,11 +768,119 @@ Commands:
759
768
  attach <team-id> [--json]
760
769
  `;
761
770
  export { parseContextEnvironment, parseProviderEnvironment };
771
+ export async function readConsoleSecret(prompt, signal, input = process.stdin, output = process.stderr) {
772
+ const limit = 64 * 1024;
773
+ if (signal?.aborted)
774
+ throw new Error('Credential input cancelled');
775
+ if (!input.isTTY || typeof input.setRawMode !== 'function') {
776
+ const chunks = [];
777
+ let size = 0;
778
+ for await (const chunk of input) {
779
+ if (signal?.aborted)
780
+ throw new Error('Credential input cancelled');
781
+ const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : Buffer.from(chunk);
782
+ size += bytes.byteLength;
783
+ if (size > limit)
784
+ throw new Error('Credential exceeds the 64 KiB limit');
785
+ chunks.push(bytes);
786
+ }
787
+ if (signal?.aborted)
788
+ throw new Error('Credential input cancelled');
789
+ return Buffer.concat(chunks, size).toString('utf8');
790
+ }
791
+ output.write(prompt);
792
+ const wasRaw = input.isRaw;
793
+ const wasPaused = input.isPaused();
794
+ const value = [];
795
+ return await new Promise((resolveSecret, reject) => {
796
+ let settled = false;
797
+ let rawModeChanged = false;
798
+ let listenerAttached = false;
799
+ const restore = () => {
800
+ let failure;
801
+ if (listenerAttached) {
802
+ input.removeListener('data', onData);
803
+ listenerAttached = false;
804
+ }
805
+ signal?.removeEventListener('abort', onAbort);
806
+ if (rawModeChanged) {
807
+ try {
808
+ input.setRawMode?.(Boolean(wasRaw));
809
+ }
810
+ catch (error) {
811
+ failure = error instanceof Error ? error : new Error(String(error));
812
+ }
813
+ rawModeChanged = false;
814
+ }
815
+ if (wasPaused) {
816
+ try {
817
+ input.pause();
818
+ }
819
+ catch (error) {
820
+ failure ??= error instanceof Error ? error : new Error(String(error));
821
+ }
822
+ }
823
+ return failure;
824
+ };
825
+ const finish = (error) => {
826
+ if (settled)
827
+ return;
828
+ settled = true;
829
+ const restorationError = restore();
830
+ let outputError;
831
+ try {
832
+ output.write('\n');
833
+ }
834
+ catch (writeError) {
835
+ outputError =
836
+ writeError instanceof Error
837
+ ? writeError
838
+ : new Error(String(writeError));
839
+ }
840
+ const failure = error ?? restorationError ?? outputError;
841
+ if (failure)
842
+ reject(failure);
843
+ else
844
+ resolveSecret(Buffer.from(value).toString('utf8'));
845
+ };
846
+ const onAbort = () => finish(new Error('Credential input cancelled'));
847
+ const onData = (chunk) => {
848
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
849
+ for (const byte of bytes) {
850
+ if (byte === 3)
851
+ return finish(new Error('Credential input cancelled'));
852
+ if (byte === 13 || byte === 10)
853
+ return finish();
854
+ if (byte === 8 || byte === 127) {
855
+ value.pop();
856
+ continue;
857
+ }
858
+ value.push(byte);
859
+ if (value.length > limit)
860
+ return finish(new Error('Credential exceeds the 64 KiB limit'));
861
+ }
862
+ };
863
+ signal?.addEventListener('abort', onAbort, { once: true });
864
+ if (signal?.aborted)
865
+ return onAbort();
866
+ try {
867
+ input.setRawMode?.(true);
868
+ rawModeChanged = true;
869
+ input.resume();
870
+ input.on('data', onData);
871
+ listenerAttached = true;
872
+ }
873
+ catch (error) {
874
+ finish(error instanceof Error ? error : new Error(String(error)));
875
+ }
876
+ });
877
+ }
762
878
  const consoleIO = {
763
879
  stdout: (message) => process.stdout.write(message),
764
880
  stderr: (message) => process.stderr.write(message),
765
881
  isTTY: Boolean(process.stdin.isTTY && process.stdout.isTTY),
766
882
  readStdinLines: () => process.stdin,
883
+ readSecret: readConsoleSecret,
767
884
  };
768
885
  function warningEventSink(io) {
769
886
  return (event) => {
@@ -801,6 +918,34 @@ export function resolveInteractiveRuntimeSettingsLocation(dataPlane, environment
801
918
  statePath: join(configRoot, 'state.json'),
802
919
  };
803
920
  }
921
+ async function assessCurrentWorkspaceProviderSelection(options) {
922
+ const shared = await loadNativeSharedResources({
923
+ root: options.configRoot,
924
+ cwd: options.cwd,
925
+ environment: options.environment,
926
+ includeProjectMemory: false,
927
+ });
928
+ const plugins = await loadClaudePlugins({
929
+ configRoot: options.configRoot,
930
+ cwd: options.cwd,
931
+ pluginDirectories: options.pluginDirectories ?? [],
932
+ pluginUrls: options.pluginUrls ?? [],
933
+ strictPluginDirectories: (options.pluginDirectories?.length ?? 0) +
934
+ (options.pluginUrls?.length ?? 0) >
935
+ 0,
936
+ loadInstalled: true,
937
+ readOnlyExecutables: true,
938
+ environment: options.environment,
939
+ });
940
+ const settings = [...shared.settings, ...plugins.settings];
941
+ if (!hasWorkspaceProviderSelection(settings))
942
+ return undefined;
943
+ return assessWorkspaceTrust(await workspaceTrustInventory({
944
+ cwd: options.cwd,
945
+ settings,
946
+ mcp: options.strictMcpConfig ? [] : [...shared.mcp, ...plugins.mcp],
947
+ }), options.statePath);
948
+ }
804
949
  export function resolveUnknownCostSidecarPath(dataPlane, configRoot) {
805
950
  assertNativeDataPlane(dataPlane);
806
951
  return join(configRoot, 'state', 'unknown-cost-sidecar.json');
@@ -820,7 +965,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
820
965
  });
821
966
  const claudeStatePath = join(configRoot, 'state.json');
822
967
  const simpleMode = controls.bare ||
823
- /^(?:1|true|yes|on)$/iu.test((runtimeEnvironment.CLAUDE_CODE_SIMPLE ?? '').trim());
968
+ truthyEnvironmentValue(runtimeEnvironment.CLAUDE_CODE_SIMPLE);
824
969
  const runtimeSettings = controls.safeMode || simpleMode
825
970
  ? undefined
826
971
  : await loadRuntimeSettings({ configRoot, statePath: claudeStatePath });
@@ -881,48 +1026,145 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
881
1026
  let provider;
882
1027
  let providerForModel;
883
1028
  let providerForMainModel;
1029
+ let providerBillingMode;
884
1030
  const context = parseContextEnvironment(runtimeEnvironment);
885
1031
  const apiKey = runtimeEnvironment.PRAXIS_API_KEY;
886
- const model = resolveRuntimeModel(interactiveModel ?? controls.model, runtimeEnvironment, runtimeSettings);
887
- const providerEnvironment = apiKey && model
1032
+ let model = resolveRuntimeModel(interactiveModel ?? controls.model, runtimeEnvironment, runtimeSettings);
1033
+ const providerEnvironment = cli.fileResources.length > 0
888
1034
  ? parseProviderEnvironment(runtimeEnvironment)
889
- : cli.fileResources.length > 0
890
- ? parseProviderEnvironment(runtimeEnvironment)
891
- : undefined;
1035
+ : undefined;
892
1036
  const fileCredential = runtimeEnvironment.PRAXIS_FILES_BEARER_TOKEN ??
893
1037
  runtimeEnvironment.PRAXIS_FILES_API_KEY ??
894
1038
  apiKey;
895
1039
  if (cli.fileResources.length > 0 && !fileCredential) {
896
1040
  throw new Error('--file requires PRAXIS_FILES_BEARER_TOKEN, PRAXIS_FILES_API_KEY, or PRAXIS_API_KEY');
897
1041
  }
898
- if (requireProvider && (!apiKey || !model)) {
899
- throw new Error('PRAXIS_API_KEY and a model (--model or PRAXIS_MODEL) are required');
900
- }
901
- if (!hooksOnly && apiKey && model) {
902
- if (!providerEnvironment) {
903
- throw new Error('Provider environment is unavailable');
1042
+ const warnedWorkspaceFingerprints = new Set();
1043
+ let trustProjectRequestAvailable = cli.trustProject;
1044
+ const authorizeWorkspaceResources = async (automaticSettings, automaticMcp, runtimeCwd, precomputedAssessment) => {
1045
+ if (cli.safeMode || simpleMode)
1046
+ return true;
1047
+ const trustRequested = trustProjectRequestAvailable;
1048
+ trustProjectRequestAvailable = false;
1049
+ const assessment = precomputedAssessment ??
1050
+ (await assessWorkspaceTrust(await workspaceTrustInventory({
1051
+ cwd: runtimeCwd,
1052
+ settings: automaticSettings,
1053
+ mcp: automaticMcp,
1054
+ }), claudeStatePath));
1055
+ if (assessment.status !== 'untrusted')
1056
+ return true;
1057
+ let approved = trustRequested;
1058
+ if (!approved && approveWorkspaceTrust) {
1059
+ try {
1060
+ approved = await approveWorkspaceTrust(assessment);
1061
+ }
1062
+ catch (error) {
1063
+ if (!(error instanceof Error &&
1064
+ (error.name === 'AbortError' || error.name === 'CancellationError'))) {
1065
+ throw error;
1066
+ }
1067
+ approved = false;
1068
+ }
1069
+ }
1070
+ if (approved) {
1071
+ await persistWorkspaceTrust(assessment, claudeStatePath);
1072
+ return true;
904
1073
  }
905
- providerForModel = createProviderForModel({
906
- apiKey,
1074
+ const warningKey = workspaceTrustDecisionKey(assessment);
1075
+ if (!warnedWorkspaceFingerprints.has(warningKey)) {
1076
+ warnedWorkspaceFingerprints.add(warningKey);
1077
+ runtimeEventSink({
1078
+ type: 'warning',
1079
+ message: `Workspace-controlled resources/configuration blocked for ${safeWorkspaceTrustDisplayField(assessment.canonicalPath)}; restart to review them interactively, or rerun with --trust-project to approve the current fingerprint.`,
1080
+ });
1081
+ }
1082
+ return false;
1083
+ };
1084
+ let workspaceProviderSettingsTrusted = false;
1085
+ if (!hooksOnly && !cli.safeMode && !simpleMode) {
1086
+ const assessment = await assessCurrentWorkspaceProviderSelection({
1087
+ configRoot,
1088
+ statePath: claudeStatePath,
1089
+ cwd,
907
1090
  environment: runtimeEnvironment,
908
- dataPlane,
909
- provider: providerEnvironment,
910
- context,
911
- controls: cli,
912
- explicitThinkingControls: controls,
1091
+ pluginDirectories: cli.pluginDirectories,
1092
+ pluginUrls: cli.pluginUrls,
1093
+ strictMcpConfig: cli.strictMcpConfig,
913
1094
  });
914
- const createProvider = providerForModel;
915
- providerForMainModel = (primaryModel) => {
916
- const models = [primaryModel, ...(cli.fallbackModels ?? [])].filter((candidate, index, all) => all.indexOf(candidate) === index);
917
- const providers = models.map((candidate) => createProvider(candidate));
918
- const selected = providers[0];
919
- if (!selected)
920
- throw new Error('A primary model is required');
921
- return providers.length > 1
922
- ? new FallbackModelProvider({ providers })
923
- : selected;
924
- };
925
- provider = providerForMainModel(model);
1095
+ if (assessment) {
1096
+ workspaceProviderSettingsTrusted = await authorizeWorkspaceResources([], [], cwd, assessment);
1097
+ }
1098
+ }
1099
+ if (!hooksOnly) {
1100
+ try {
1101
+ const registry = await resolveProviderRegistry({
1102
+ configRoot,
1103
+ cwd,
1104
+ environment: runtimeEnvironment,
1105
+ ...((interactiveModel ?? controls.model) === undefined
1106
+ ? {}
1107
+ : { model: interactiveModel ?? controls.model }),
1108
+ ...(controls.provider === undefined
1109
+ ? {}
1110
+ : { provider: controls.provider }),
1111
+ ...(controls.providerProfile === undefined
1112
+ ? {}
1113
+ : { profile: controls.providerProfile }),
1114
+ includeSettings: !(controls.safeMode || simpleMode),
1115
+ includeProjectSettings: workspaceProviderSettingsTrusted,
1116
+ context,
1117
+ vault: new ProviderCredentialVault({
1118
+ configRoot,
1119
+ environment: runtimeEnvironment,
1120
+ }),
1121
+ anthropicThinking: {
1122
+ mode: cli.thinking ?? 'enabled',
1123
+ ...(cli.maxThinkingTokens === undefined
1124
+ ? {}
1125
+ : { maxTokens: cli.maxThinkingTokens }),
1126
+ },
1127
+ codexThinking: {
1128
+ mode: cli.thinking ?? 'enabled',
1129
+ ...(cli.maxThinkingTokens === undefined
1130
+ ? {}
1131
+ : { maxTokens: cli.maxThinkingTokens }),
1132
+ },
1133
+ ...(controls.thinking === undefined &&
1134
+ controls.maxThinkingTokens === undefined
1135
+ ? {}
1136
+ : {
1137
+ openAiThinking: {
1138
+ mode: cli.thinking ?? 'enabled',
1139
+ ...(cli.maxThinkingTokens === undefined
1140
+ ? {}
1141
+ : { maxTokens: cli.maxThinkingTokens }),
1142
+ },
1143
+ }),
1144
+ });
1145
+ model = registry.target.modelId;
1146
+ providerBillingMode = registry.target.billingMode;
1147
+ providerForModel = (selectedModel) => registry.create(selectedModel);
1148
+ const createProvider = providerForModel;
1149
+ providerForMainModel = (primaryModel) => {
1150
+ const models = [primaryModel, ...(cli.fallbackModels ?? [])].filter((candidate, index, all) => all.indexOf(candidate) === index);
1151
+ const providers = models.map((candidate) => createProvider(candidate));
1152
+ const selected = providers[0];
1153
+ if (!selected)
1154
+ throw new Error('A primary model is required');
1155
+ return providers.length > 1
1156
+ ? new FallbackModelProvider({ providers })
1157
+ : selected;
1158
+ };
1159
+ provider = providerForMainModel(model);
1160
+ }
1161
+ catch (error) {
1162
+ const optionalProviderError = error instanceof ProviderAuthenticationError ||
1163
+ (error instanceof ProviderSettingsError &&
1164
+ error.code === 'model_required');
1165
+ if (requireProvider || !optionalProviderError)
1166
+ throw error;
1167
+ }
926
1168
  }
927
1169
  const costStateStore = new ClaudeCostStateStore({
928
1170
  statePath: claudeStatePath,
@@ -957,7 +1199,11 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
957
1199
  ...(cli.maxBudgetUsd === undefined
958
1200
  ? {}
959
1201
  : { maxBudgetUsd: cli.maxBudgetUsd }),
960
- pricing: ModelPricingRegistry.fromEnvironment(runtimeEnvironment.PRAXIS_PRICING_JSON),
1202
+ ...(providerBillingMode === 'subscription'
1203
+ ? {}
1204
+ : {
1205
+ pricing: ModelPricingRegistry.fromEnvironment(runtimeEnvironment.PRAXIS_PRICING_JSON),
1206
+ }),
961
1207
  collectMetrics: true,
962
1208
  ...(!experimentalNativeTranscriptWrites && sessionMemoryProviderFactory
963
1209
  ? { sessionMemoryProviderFactory }
@@ -987,48 +1233,6 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
987
1233
  }
988
1234
  : {}),
989
1235
  };
990
- const warnedWorkspaceFingerprints = new Set();
991
- let trustProjectRequestAvailable = cli.trustProject;
992
- const authorizeWorkspaceExecutables = async (automaticSettings, automaticMcp, runtimeCwd) => {
993
- if (cli.safeMode || simpleMode)
994
- return true;
995
- const trustRequested = trustProjectRequestAvailable;
996
- trustProjectRequestAvailable = false;
997
- const inventory = await workspaceTrustInventory({
998
- cwd: runtimeCwd,
999
- settings: automaticSettings,
1000
- mcp: automaticMcp,
1001
- });
1002
- const assessment = await assessWorkspaceTrust(inventory, claudeStatePath);
1003
- if (assessment.status !== 'untrusted')
1004
- return true;
1005
- let approved = trustRequested;
1006
- if (!approved && approveWorkspaceTrust) {
1007
- try {
1008
- approved = await approveWorkspaceTrust(assessment);
1009
- }
1010
- catch (error) {
1011
- if (!(error instanceof Error &&
1012
- (error.name === 'AbortError' || error.name === 'CancellationError'))) {
1013
- throw error;
1014
- }
1015
- approved = false;
1016
- }
1017
- }
1018
- if (approved) {
1019
- await persistWorkspaceTrust(assessment, claudeStatePath);
1020
- return true;
1021
- }
1022
- const warningKey = workspaceTrustDecisionKey(assessment);
1023
- if (!warnedWorkspaceFingerprints.has(warningKey)) {
1024
- warnedWorkspaceFingerprints.add(warningKey);
1025
- runtimeEventSink({
1026
- type: 'warning',
1027
- message: `Workspace executable resources blocked for ${safeWorkspaceTrustDisplayField(assessment.canonicalPath)}; restart to review them interactively, or rerun with --trust-project to approve the current fingerprint.`,
1028
- });
1029
- }
1030
- return false;
1031
- };
1032
1236
  const hookConfiguration = async () => {
1033
1237
  const [sharedResources, pluginResources] = await Promise.all([
1034
1238
  cli.safeMode || simpleMode
@@ -1057,7 +1261,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1057
1261
  const automaticMcp = cli.strictMcpConfig
1058
1262
  ? []
1059
1263
  : [...sharedResources.mcp, ...pluginResources.mcp];
1060
- const trusted = await authorizeWorkspaceExecutables(automaticSettings, automaticMcp, cwd);
1264
+ const trusted = await authorizeWorkspaceResources(automaticSettings, automaticMcp, cwd);
1061
1265
  return projectTuiHooks([
1062
1266
  ...allowedWorkspaceHookSettings(automaticSettings, trusted),
1063
1267
  ...(cli.additionalSettings ? [cli.additionalSettings] : []),
@@ -1155,7 +1359,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1155
1359
  const trustMcp = cli.strictMcpConfig
1156
1360
  ? []
1157
1361
  : [...loadedResources.mcp, ...executablePluginResources.mcp];
1158
- const workspaceExecutablesTrusted = await authorizeWorkspaceExecutables(trustSettings, trustMcp, cwd);
1362
+ const workspaceExecutablesTrusted = await authorizeWorkspaceResources(trustSettings, trustMcp, cwd);
1159
1363
  const loadedPluginResources = await loadClaudePlugins({
1160
1364
  ...pluginLoadOptions,
1161
1365
  allowWorkspaceMcpb: workspaceExecutablesTrusted,
@@ -1406,7 +1610,7 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1406
1610
  ...pluginResources.settings,
1407
1611
  ];
1408
1612
  const refreshedMcp = [...refreshed.mcp, ...pluginResources.mcp];
1409
- const trusted = await authorizeWorkspaceExecutables(refreshedSettings, refreshedMcp, workspace.cwd());
1613
+ const trusted = await authorizeWorkspaceResources(refreshedSettings, refreshedMcp, workspace.cwd());
1410
1614
  return runtimeMcpResources([
1411
1615
  ...allowedWorkspaceMcpResources(refreshedMcp, trusted),
1412
1616
  ...cli.mcpResources,
@@ -1994,30 +2198,31 @@ const createDefaultService = async ({ eventSink, requireProvider, hooksOnly = fa
1994
2198
  throw error;
1995
2199
  }
1996
2200
  };
1997
- const createDefaultAutoModeCritic = async ({ model, dataPlane, configRoot, statePath }) => {
1998
- const apiKey = process.env.PRAXIS_API_KEY;
2201
+ const createDefaultAutoModeCritic = async ({ model, provider, providerProfile, dataPlane, configRoot, statePath, }) => {
1999
2202
  const resolvedDataPlane = dataPlane ?? resolveDataPlane();
2000
2203
  const location = resolveInteractiveRuntimeSettingsLocation(resolvedDataPlane);
2001
2204
  const resolvedConfigRoot = configRoot ?? location.configRoot;
2002
- const resolvedStatePath = statePath ?? location.statePath;
2003
- const runtimeSettings = await loadRuntimeSettings({
2205
+ const assessment = await assessCurrentWorkspaceProviderSelection({
2004
2206
  configRoot: resolvedConfigRoot,
2005
- statePath: resolvedStatePath,
2207
+ statePath: statePath ?? join(resolvedConfigRoot, 'state.json'),
2208
+ cwd: process.cwd(),
2209
+ environment: process.env,
2006
2210
  });
2007
- const selectedModel = resolveRuntimeModel(model, process.env, runtimeSettings);
2008
- if (!apiKey || !selectedModel) {
2009
- throw new Error('PRAXIS_API_KEY and a model (--model or PRAXIS_MODEL) are required');
2010
- }
2011
- const providerEnvironment = parseProviderEnvironment(process.env);
2012
- return createProviderForModel({
2013
- apiKey,
2211
+ const registry = await resolveProviderRegistry({
2212
+ configRoot: resolvedConfigRoot,
2213
+ cwd: process.cwd(),
2014
2214
  environment: process.env,
2015
- dataPlane: resolvedDataPlane,
2016
- provider: providerEnvironment,
2215
+ ...(model === undefined ? {} : { model }),
2216
+ ...(provider === undefined ? {} : { provider }),
2217
+ ...(providerProfile === undefined ? {} : { profile: providerProfile }),
2218
+ includeProjectSettings: assessment?.status === 'trusted',
2017
2219
  context: parseContextEnvironment(process.env),
2018
- controls: {},
2019
- explicitThinkingControls: {},
2020
- })(selectedModel);
2220
+ vault: new ProviderCredentialVault({
2221
+ configRoot: resolvedConfigRoot,
2222
+ environment: process.env,
2223
+ }),
2224
+ });
2225
+ return registry.create(model ?? registry.target.modelId);
2021
2226
  };
2022
2227
  const defaultPluginEvalRuntimeFactory = {
2023
2228
  create: async (options) => {
@@ -2108,19 +2313,29 @@ const defaultPluginEvalRuntimeFactory = {
2108
2313
  const defaultPluginEvalJudge = {
2109
2314
  vote: async ({ criteria, focus, baseline, model, signal }) => {
2110
2315
  const environment = process.env;
2111
- const apiKey = environment.PRAXIS_API_KEY;
2112
- if (!apiKey)
2113
- throw new Error('PRAXIS_API_KEY is required for paid eval graders');
2114
- const providerEnvironment = parseProviderEnvironment(environment);
2115
- const provider = createProviderForModel({
2116
- apiKey,
2316
+ const configRoot = resolveDataPlaneRoot({ environment });
2317
+ const assessment = await assessCurrentWorkspaceProviderSelection({
2318
+ configRoot,
2319
+ statePath: join(configRoot, 'state.json'),
2320
+ cwd: process.cwd(),
2117
2321
  environment,
2118
- dataPlane: resolveDataPlane(environment),
2119
- provider: providerEnvironment,
2322
+ });
2323
+ const registry = await resolveProviderRegistry({
2324
+ configRoot,
2325
+ cwd: process.cwd(),
2326
+ environment,
2327
+ ...(model === undefined ? {} : { model }),
2328
+ includeProjectSettings: assessment?.status === 'trusted',
2120
2329
  context: parseContextEnvironment(environment),
2121
- controls: {},
2122
- explicitThinkingControls: {},
2123
- })(model);
2330
+ vault: new ProviderCredentialVault({
2331
+ configRoot,
2332
+ environment,
2333
+ }),
2334
+ });
2335
+ if (registry.target.billingMode === 'subscription') {
2336
+ throw new Error('Plugin eval LLM judges require API-billed cost; subscription cost is unavailable');
2337
+ }
2338
+ const provider = registry.create(model ?? registry.target.modelId);
2124
2339
  const prompt = `You are an eval judge. Return only JSON matching {"passed":boolean,"explanation":string}.
2125
2340
 
2126
2341
  Criteria:
@@ -2170,6 +2385,135 @@ ${focus}${baseline === undefined ? '' : `\n\nBaseline:\n${baseline}`}`;
2170
2385
  };
2171
2386
  },
2172
2387
  };
2388
+ async function resolveDetachedWorkerProviderEnvironment(request) {
2389
+ const environment = process.env;
2390
+ const invocation = parseCliInvocation(request.argv);
2391
+ const configRoot = resolveDataPlaneRoot({ environment });
2392
+ const statePath = join(configRoot, 'state.json');
2393
+ const safe = invocation.safeMode ||
2394
+ invocation.bare ||
2395
+ truthyEnvironmentValue(environment.CLAUDE_CODE_SIMPLE);
2396
+ let includeProjectSettings = false;
2397
+ if (!safe) {
2398
+ const assessment = await assessCurrentWorkspaceProviderSelection({
2399
+ configRoot,
2400
+ statePath,
2401
+ cwd: request.cwd,
2402
+ environment,
2403
+ pluginDirectories: invocation.pluginDirectories,
2404
+ pluginUrls: invocation.pluginUrls,
2405
+ strictMcpConfig: invocation.strictMcpConfig,
2406
+ });
2407
+ if (assessment?.status === 'trusted')
2408
+ includeProjectSettings = true;
2409
+ else if (assessment?.status === 'untrusted' && invocation.trustProject) {
2410
+ await persistWorkspaceTrust(assessment, statePath);
2411
+ includeProjectSettings = true;
2412
+ }
2413
+ }
2414
+ const target = await resolveProviderTarget({
2415
+ configRoot,
2416
+ cwd: request.cwd,
2417
+ environment,
2418
+ ...(invocation.model === undefined ? {} : { model: invocation.model }),
2419
+ ...(invocation.provider === undefined
2420
+ ? {}
2421
+ : { provider: invocation.provider }),
2422
+ ...(invocation.providerProfile === undefined
2423
+ ? {}
2424
+ : { profile: invocation.providerProfile }),
2425
+ includeSettings: !safe,
2426
+ includeProjectSettings,
2427
+ });
2428
+ const credential = await resolveProviderCredential({
2429
+ target,
2430
+ environment,
2431
+ vault: new ProviderCredentialVault({ configRoot, environment }),
2432
+ });
2433
+ return credential.type === 'api-key'
2434
+ ? { PRAXIS_API_KEY: credential.secret }
2435
+ : {};
2436
+ }
2437
+ export async function resolveInteractiveProviderStartup(options) {
2438
+ const environment = options.environment ?? process.env;
2439
+ const disabled = options.controls.safeMode ||
2440
+ options.controls.bare ||
2441
+ truthyEnvironmentValue(environment.CLAUDE_CODE_SIMPLE);
2442
+ if (disabled) {
2443
+ let effectiveModel;
2444
+ try {
2445
+ effectiveModel = (await resolveProviderTarget({
2446
+ configRoot: options.configRoot,
2447
+ cwd: options.cwd,
2448
+ environment,
2449
+ ...(options.controls.model === undefined
2450
+ ? {}
2451
+ : { model: options.controls.model }),
2452
+ ...(options.controls.provider === undefined
2453
+ ? {}
2454
+ : { provider: options.controls.provider }),
2455
+ ...(options.controls.providerProfile === undefined
2456
+ ? {}
2457
+ : { profile: options.controls.providerProfile }),
2458
+ includeSettings: false,
2459
+ })).modelId;
2460
+ }
2461
+ catch (error) {
2462
+ if (!(error instanceof ProviderSettingsError &&
2463
+ error.code === 'model_required'))
2464
+ throw error;
2465
+ }
2466
+ return {
2467
+ effectiveModel,
2468
+ trustProjectRequestAvailable: options.controls.trustProject,
2469
+ };
2470
+ }
2471
+ const assessment = await assessCurrentWorkspaceProviderSelection({
2472
+ configRoot: options.configRoot,
2473
+ statePath: options.statePath,
2474
+ cwd: options.cwd,
2475
+ environment,
2476
+ pluginDirectories: options.controls.pluginDirectories,
2477
+ pluginUrls: options.controls.pluginUrls,
2478
+ strictMcpConfig: options.controls.strictMcpConfig,
2479
+ });
2480
+ let includeProjectSettings = false;
2481
+ let trustProjectRequestAvailable = options.controls.trustProject;
2482
+ if (assessment) {
2483
+ trustProjectRequestAvailable = false;
2484
+ if (assessment.status === 'trusted')
2485
+ includeProjectSettings = true;
2486
+ else if (options.controls.trustProject ||
2487
+ (await options.approveWorkspaceTrust?.(assessment))) {
2488
+ await persistWorkspaceTrust(assessment, options.statePath);
2489
+ includeProjectSettings = true;
2490
+ }
2491
+ }
2492
+ let effectiveModel;
2493
+ try {
2494
+ effectiveModel = (await resolveProviderTarget({
2495
+ configRoot: options.configRoot,
2496
+ cwd: options.cwd,
2497
+ environment,
2498
+ ...(options.controls.model === undefined
2499
+ ? {}
2500
+ : { model: options.controls.model }),
2501
+ ...(options.controls.provider === undefined
2502
+ ? {}
2503
+ : { provider: options.controls.provider }),
2504
+ ...(options.controls.providerProfile === undefined
2505
+ ? {}
2506
+ : { profile: options.controls.providerProfile }),
2507
+ includeSettings: true,
2508
+ includeProjectSettings,
2509
+ })).modelId;
2510
+ }
2511
+ catch (error) {
2512
+ if (!(error instanceof ProviderSettingsError && error.code === 'model_required'))
2513
+ throw error;
2514
+ }
2515
+ return { effectiveModel, trustProjectRequestAvailable };
2516
+ }
2173
2517
  export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta.url)) {
2174
2518
  const dependencies = {
2175
2519
  createService: createDefaultService,
@@ -2193,13 +2537,15 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
2193
2537
  const initialAdditionalDirectories = interactiveControls.addDirectories.map((directory) => realpathSync(resolve(process.cwd(), directory)));
2194
2538
  const interactiveDataPlane = interactiveControls.dataPlane ?? resolveDataPlane();
2195
2539
  const { configRoot: interactiveConfigRoot, statePath: interactiveStatePath, } = resolveInteractiveRuntimeSettingsLocation(interactiveDataPlane);
2196
- const interactiveRuntimeSettings = interactiveControls.safeMode || interactiveControls.bare
2197
- ? undefined
2198
- : await loadRuntimeSettings({
2199
- configRoot: interactiveConfigRoot,
2200
- statePath: interactiveStatePath,
2201
- });
2202
- const effectiveModel = resolveRuntimeModel(interactiveControls.model, process.env, interactiveRuntimeSettings);
2540
+ const startup = await resolveInteractiveProviderStartup({
2541
+ controls: interactiveControls,
2542
+ configRoot: interactiveConfigRoot,
2543
+ statePath: interactiveStatePath,
2544
+ cwd: process.cwd(),
2545
+ approveWorkspaceTrust: cachedWorkspaceTrustDecision,
2546
+ });
2547
+ const effectiveModel = startup.effectiveModel;
2548
+ const trustProjectRequestAvailable = startup.trustProjectRequestAvailable;
2203
2549
  const resumePath = typeof resume?.sessionSelector === 'string' &&
2204
2550
  isResumePathSelector(resume.sessionSelector)
2205
2551
  ? resume.sessionSelector
@@ -2218,7 +2564,7 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
2218
2564
  ...(agent === undefined ? {} : { agent }),
2219
2565
  controls: {
2220
2566
  ...interactiveControls,
2221
- trustProject: workspaceTrustPreflightOpen && interactiveControls.trustProject,
2567
+ trustProject: workspaceTrustPreflightOpen && trustProjectRequestAvailable,
2222
2568
  addDirectories: options.additionalDirectories ??
2223
2569
  interactiveControls.addDirectories,
2224
2570
  },
@@ -2237,7 +2583,7 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
2237
2583
  statePath: interactiveStatePath,
2238
2584
  factory: {
2239
2585
  createService: createInteractiveService,
2240
- scheduledPrompts: Boolean(process.env.PRAXIS_API_KEY && effectiveModel),
2586
+ scheduledPrompts: Boolean(effectiveModel),
2241
2587
  },
2242
2588
  ...(signal ? { signal } : {}),
2243
2589
  ...(initialPrompt === undefined ? {} : { initialPrompt }),
@@ -2311,6 +2657,7 @@ export function createDefaultDependencies(entrypoint = fileURLToPath(import.meta
2311
2657
  dataPlane,
2312
2658
  cwd: process.cwd(),
2313
2659
  cliPath: entrypoint,
2660
+ resolveProviderEnvironment: resolveDetachedWorkerProviderEnvironment,
2314
2661
  version: VERSION,
2315
2662
  }),
2316
2663
  launchTmux: launchTmuxWorktree,
@@ -2689,6 +3036,12 @@ async function executeAutoModeCommand(args, invocation, io, dependencies, signal
2689
3036
  io.stdout('Analyzing your auto mode rules…\n\n');
2690
3037
  const provider = await (dependencies.createAutoModeCritic ?? createDefaultAutoModeCritic)({
2691
3038
  ...(invocation.model === undefined ? {} : { model: invocation.model }),
3039
+ ...(invocation.provider === undefined
3040
+ ? {}
3041
+ : { provider: invocation.provider }),
3042
+ ...(invocation.providerProfile === undefined
3043
+ ? {}
3044
+ : { providerProfile: invocation.providerProfile }),
2692
3045
  dataPlane,
2693
3046
  configRoot,
2694
3047
  statePath,
@@ -2760,21 +3113,27 @@ async function executeDoctorCommand(args, invocation, io) {
2760
3113
  configRoot,
2761
3114
  statePath: claudeStatePath,
2762
3115
  });
2763
- const effectiveModel = resolveRuntimeModel(invocation.model, process.env, runtimeSettings);
2764
3116
  const report = await runDoctor({
2765
3117
  version: VERSION,
2766
3118
  executablePath: fileURLToPath(import.meta.url),
2767
3119
  nodeExecutablePath: process.execPath,
2768
3120
  nodeVersion: process.version,
2769
3121
  configRoot,
3122
+ claudeStatePath,
2770
3123
  cwd: process.cwd(),
2771
- environment: effectiveModel === undefined
2772
- ? { ...process.env, PRAXIS_DATA_PLANE: dataPlane }
2773
- : {
2774
- ...process.env,
2775
- PRAXIS_DATA_PLANE: dataPlane,
2776
- PRAXIS_MODEL: effectiveModel,
2777
- },
3124
+ environment: {
3125
+ ...process.env,
3126
+ PRAXIS_DATA_PLANE: dataPlane,
3127
+ ...(invocation.provider === undefined
3128
+ ? {}
3129
+ : { PRAXIS_PROVIDER: invocation.provider }),
3130
+ ...(invocation.providerProfile === undefined
3131
+ ? {}
3132
+ : { PRAXIS_PROVIDER_PROFILE: invocation.providerProfile }),
3133
+ ...(invocation.model === undefined
3134
+ ? {}
3135
+ : { PRAXIS_MODEL: invocation.model }),
3136
+ },
2778
3137
  autoUpdateChannel: runtimeSettings.autoUpdatesChannel,
2779
3138
  ...(process.argv[1] === undefined
2780
3139
  ? {}
@@ -3939,8 +4298,16 @@ function backgroundWorkerArgv(argv) {
3939
4298
  }
3940
4299
  return filtered;
3941
4300
  }
3942
- function agentDashboardWorkerArgv(invocation) {
4301
+ export function agentDashboardWorkerArgv(invocation) {
3943
4302
  const argv = [];
4303
+ if (invocation.safeMode)
4304
+ argv.push('--safe-mode');
4305
+ if (invocation.bare)
4306
+ argv.push('--bare');
4307
+ if (invocation.provider !== undefined)
4308
+ argv.push('--provider', invocation.provider);
4309
+ if (invocation.providerProfile !== undefined)
4310
+ argv.push('--provider-profile', invocation.providerProfile);
3944
4311
  if (invocation.model !== undefined)
3945
4312
  argv.push('--model', invocation.model);
3946
4313
  if (invocation.effort !== undefined)
@@ -3973,6 +4340,8 @@ function agentDashboardWorkerArgv(invocation) {
3973
4340
  for (const directory of invocation.pluginDirectories) {
3974
4341
  argv.push('--plugin-dir', directory);
3975
4342
  }
4343
+ for (const url of invocation.pluginUrls)
4344
+ argv.push('--plugin-url', url);
3976
4345
  return argv;
3977
4346
  }
3978
4347
  function backgroundLaunchMessage(id) {
@@ -3995,6 +4364,8 @@ function assertAgentsOptionAllowlist(argv) {
3995
4364
  '--effort',
3996
4365
  '--mcp-config',
3997
4366
  '--model',
4367
+ '--provider',
4368
+ '--provider-profile',
3998
4369
  '--permission-mode',
3999
4370
  '--plugin-dir',
4000
4371
  '--setting-sources',
@@ -4164,6 +4535,7 @@ function printCommandHelp(argv, io) {
4164
4535
  'auto-mode',
4165
4536
  'project',
4166
4537
  'import',
4538
+ 'auth',
4167
4539
  'team',
4168
4540
  ].includes(value));
4169
4541
  if (commandIndex < 0)
@@ -4252,6 +4624,19 @@ function printCommandHelp(argv, io) {
4252
4624
  io.stdout(IMPORT_HELP);
4253
4625
  return true;
4254
4626
  }
4627
+ if (command === 'auth') {
4628
+ const action = helpActionAt(argv, commandIndex, [
4629
+ 'help',
4630
+ ...Object.keys(AUTH_ACTION_HELP),
4631
+ ]);
4632
+ if (!hasHelpFlag && action?.value !== 'help')
4633
+ return false;
4634
+ const target = action?.value === 'help'
4635
+ ? helpActionAt(argv, action.index, Object.keys(AUTH_ACTION_HELP))
4636
+ : action;
4637
+ io.stdout(AUTH_ACTION_HELP[target?.value ?? ''] ?? AUTH_HELP);
4638
+ return true;
4639
+ }
4255
4640
  if (command === 'team') {
4256
4641
  if (!hasHelpFlag)
4257
4642
  return false;
@@ -4649,7 +5034,25 @@ async function execute(argv, io, dependencies, signal) {
4649
5034
  'upgrade',
4650
5035
  'project',
4651
5036
  'import',
5037
+ 'auth',
4652
5038
  ].includes(command ?? '');
5039
+ if (command === 'auth') {
5040
+ return (dependencies.executeProviderAuthCommand ?? executeProviderAuthCommand)(args, {
5041
+ io,
5042
+ configRoot: resolveDataPlaneRoot({ environment: process.env }),
5043
+ environment: process.env,
5044
+ ...(signal === undefined ? {} : { signal }),
5045
+ ...(invocation.authProfile === undefined
5046
+ ? {}
5047
+ : { profile: invocation.authProfile }),
5048
+ ...(invocation.providerProfile === undefined
5049
+ ? {}
5050
+ : { providerProfile: invocation.providerProfile }),
5051
+ noBrowser: invocation.mcpNoBrowser,
5052
+ json: invocation.legacyJson,
5053
+ device: invocation.authDevice,
5054
+ });
5055
+ }
4653
5056
  if (args[0] === 'agents')
4654
5057
  assertAgentsOptionAllowlist(argv);
4655
5058
  const interactiveResume = invocation.resumeSelector !== undefined &&