const std = @import("std");const builtin = @import("builtin");const io_mod = @import("../shared/io.zig");const app_lifecycle = @import("../app/app_lifecycle.zig");const background_record_liveness = @import("../background/background_record_liveness.zig");const background_store = @import("../background/background_store.zig");const acp_runner = @import("acp_runner.zig");const cli_ask = @import("cli_ask.zig");const cli_replay = @import("cli_replay.zig");const command_specs = @import("../slash_commands/command_specs.zig");const collections = @import("../shared/collections.zig");const config_runtime = @import("../config/config_runtime.zig");const devbox_executor = @import("../execution/devbox_executor.zig");const doctor_runtime = @import("doctor_runtime.zig");const gateway_provider = @import("../gateway/gateway_provider.zig");const model_catalog = @import("../gateway/model_catalog.zig");const background_process_provider = @import( "../execution/background_process_provider.zig",);const github_publish = @import("../github/github_publish.zig");const github_workflows = @import("../github/github_workflows.zig");const host = @import("../hosts/host.zig");const login_flow = @import("../auth/login_flow.zig");const oauth_transport = @import("../auth/oauth_transport.zig");const secret = @import("../auth/secret.zig");const output_contracts = @import("../output/output_contracts.zig");const permission_auto_classifier = @import("../permissions/auto_classifier.zig");const prompt_policy = @import("../config/prompt_policy.zig");const sandbox = @import("../permissions/sandbox.zig");const session_store = @import("../session/session_store.zig");const usage_report = @import("../session/usage_report.zig");const skill_contract = @import("../skills/skill_contract.zig");const types = @import("../shared/types.zig");const update_target = @import("../upgrade/update_target.zig");const test_builtin_gateway = if (builtin.is_test) @import("../../builtins/gateway.zig")else struct {};const context_contract = @import("../workspace/context_contract.zig");const mode_registry = @import("../modes/mode_registry.zig");const mcp_contract = @import("../mcp/mcp_contract.zig");const mcp_runtime = @import("../mcp/mcp_runtime.zig");const tool_set_contract = @import("../tooling/tool_set.zig");const workspace_access = @import("../workspace/workspace_access.zig");const workspace_commands = @import("../workspace/workspace_commands.zig");const usage_cli_runtime = @import("usage_cli_runtime.zig"); const Allocator = std.mem.Allocator;const CommandCatalog = command_specs.TopLevelRegistry;const TopLevelKind = command_specs.TopLevelKind; pub const Command = union(enum) { interactive, help, ask: []const [:0]const u8, acp: []const [:0]const u8, pr: []const [:0]const u8, issue: []const [:0]const u8, login: []const [:0]const u8, logout: []const [:0]const u8, setup: []const [:0]const u8, status: []const [:0]const u8, permissions: []const [:0]const u8, models: []const [:0]const u8, doctor: []const [:0]const u8, background: []const [:0]const u8, teams: []const [:0]const u8, session: []const [:0]const u8, sessions: []const [:0]const u8, resume_session: ResumeInvocation, credits: []const [:0]const u8, usage: []const [:0]const u8, upgrade: []const [:0]const u8, replay: []const [:0]const u8, workspace: []const [:0]const u8, unknown: []const u8,}; const ResumeInvocation = struct { args: []const [:0]const u8, top_level_alias: bool = false,}; const resume_id_alias_prefix = "--resume-";pub const upgrade_relaunch_arg = "--upgrade-relaunch"; // The one resume alias that asks which session to open. Every other spelling// names its target, so it resumes without a prompt.const resume_picker_alias = "-r"; pub const ResumeTarget = union(enum) { pick, last, id: []u8, pub fn deinit(self: *ResumeTarget, alloc: Allocator) void { switch (self.*) { .pick, .last => {}, .id => |value| alloc.free(value), } self.* = undefined; }}; pub const LaunchModifiers = struct { context_limit_overrides: []config_runtime.context_limits.Override = &.{}, additional_directories: [][]u8 = &.{}, saved_directories_suppressed: bool = false, pub fn deinit(self: *LaunchModifiers, alloc: Allocator) void { if (self.context_limit_overrides.len > 0) alloc.free(self.context_limit_overrides); for (self.additional_directories) |path| alloc.free(path); if (self.additional_directories.len > 0) alloc.free(self.additional_directories); self.* = .{}; } pub fn hasWorkspaceModifiers(self: LaunchModifiers) bool { return self.additional_directories.len > 0 or self.saved_directories_suppressed; }}; pub const InteractiveLaunch = struct { requested_resume: ?ResumeTarget = null, upgrade_relaunch: bool = false, record_requested: bool = false, modifiers: LaunchModifiers = .{}, pub fn deinit(self: *InteractiveLaunch, alloc: Allocator) void { if (self.requested_resume) |*target| target.deinit(alloc); self.modifiers.deinit(alloc); self.* = undefined; }}; pub const RunResult = union(enum) { interactive: InteractiveLaunch, handled_success, handled_failure, handled_exit: u8,}; pub const record_modifier_usage = "usage: fx --record is only supported for interactive startup\n";const version_usage = "usage: fx --version\n"; pub fn recordRequested(args: []const [:0]const u8) error{RecordModifierRequiresInteractive}!bool { var count: usize = 0; for (args) |arg| { if (std.mem.eql(u8, arg, "--record")) count += 1; } if (count == 0) return false; if (count != 1) return error.RecordModifierRequiresInteractive; if (args.len == 1 and std.mem.eql(u8, args[0], "--record")) return true; if (args.len >= 2 and (std.mem.eql(u8, args[0], "resume") or std.mem.eql(u8, args[0], "--resume")) and std.mem.eql(u8, args[args.len - 1], "--record")) return true; return error.RecordModifierRequiresInteractive;} pub const Config = struct { version: []const u8 = "", revision: []const u8 = "", build_channel: update_target.Channel = .stable, command_catalog: CommandCatalog, default_model: []const u8, default_agent_step_limit: usize, models_path: []const u8, gateway_retry_count: usize, gateway_chat_url: []const u8, gateway_provider: gateway_provider.Provider, background_process_provider: background_process_provider.Provider = background_process_provider.unavailable_provider, url_opener: host.UrlOpener, secret_store: host.SecretStore, prompt_policy: prompt_policy.Policy, skill_root_policy: skill_contract.RootPolicy, ignored_list_entries: []const []const u8, max_list_entries: usize, max_read_file_bytes: usize, max_read_file_lines: usize, max_read_file_line_len: usize, max_command_output_bytes: usize, max_tool_result_bytes: usize, max_history_turns: usize, context_registry: context_contract.Registry, mode_registry: mode_registry.Registry, tool_set: tool_set_contract.ToolSet, inspect_mcp_profile_config: mcp_contract.InspectProfileConfigFn, load_mcp_runtime: mcp_runtime.LoadRuntimeFn, acp_runner: acp_runner.Runner, devbox_provider: ?devbox_executor.Provider = null, permission_reviewer_provider: ?permission_auto_classifier.Provider = null,}; const LocalSurfaceOptions = struct { format: output_contracts.OutputFormat = .text,}; const UpgradeOptions = struct { format: output_contracts.OutputFormat = .text, channel: ?update_target.Channel = null,}; const SessionListOptions = struct { format: output_contracts.OutputFormat = .text, scope: session_store.SessionListScope = .current_workspace, limit: usize = session_store.session_list_default_limit, continuation: ?session_store.ResumableSessionContinuation = null,}; const UsageOptions = struct { format: output_contracts.OutputFormat = .text, scope: usage_report.Scope = .days_30,}; const WorkspaceOptions = struct { format: output_contracts.OutputFormat = .text, action: ?workspace_commands.Action = null,}; const PersistedRecordTarget = union(enum) { last, id: u64,}; const PersistedRecordOptions = struct { format: output_contracts.OutputFormat = .text, target: ?PersistedRecordTarget = null,}; // `fx session` reads one saved session, so it names its target and never// reaches for the picker that `ResumeTarget` carries.const SessionDetailTarget = union(enum) { last, id: []u8, fn deinit(self: *SessionDetailTarget, alloc: Allocator) void { switch (self.*) { .last => {}, .id => |value| alloc.free(value), } self.* = undefined; }}; const SessionDetailOptions = struct { format: output_contracts.OutputFormat = .text, target: ?SessionDetailTarget = null, fn deinit(self: *SessionDetailOptions, alloc: Allocator) void { if (self.target) |*target| target.deinit(alloc); self.* = undefined; }}; const SessionMigrationOptions = struct { format: output_contracts.OutputFormat = .text, session_id: []u8, allow_large: bool = false, fn deinit(self: *SessionMigrationOptions, alloc: Allocator) void { alloc.free(self.session_id); self.* = undefined; }}; const SessionRecoveryOptions = struct { format: output_contracts.OutputFormat = .text, session_id: []u8, fn deinit(self: *SessionRecoveryOptions, alloc: Allocator) void { alloc.free(self.session_id); self.* = undefined; }}; const AcpOptions = struct { model: ?[]const u8 = null, log_file: ?[]const u8 = null,}; const WorkflowOptions = struct { auto_permission: bool, create: bool, context: []u8, fn deinit(self: WorkflowOptions, alloc: Allocator) void { alloc.free(self.context); }}; const WriteFn = *const fn (?*anyopaque, []const u8) anyerror!void;const LoadStartupStateFn = *const fn (Allocator, oauth_transport.Provider, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupState;const LoadCatalogStartupStateFn = *const fn (Allocator, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupState;const LoadStartupStateWithoutCredentialsFn = *const fn (Allocator, []const u8, usize) anyerror!app_lifecycle.StartupState;const LoadStartupStatusFn = *const fn (Allocator, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupStatus;const GetenvFn = *const fn (?*anyopaque, []const u8) ?[]const u8;const EnvironMapFn = *const fn (?*anyopaque) ?*const std.process.Environ.Map;const SelfExePathFn = *const fn (?*anyopaque, Allocator) anyerror![]u8;const ReadMaskedKeyFn = *const fn (?*anyopaque, Allocator, WriteFn, ?*anyopaque) anyerror![]u8;const SetupTerminalAvailableFn = *const fn (?*anyopaque) bool;const RunDeps = struct { stdout_ctx: ?*anyopaque = null, stderr_ctx: ?*anyopaque = null, env_ctx: ?*anyopaque = null, self_exe_ctx: ?*anyopaque = null, setup_ctx: ?*anyopaque = null, write_stdout: WriteFn = writeRealStdout, write_stderr: WriteFn = writeRealStderr, load_startup_state: LoadStartupStateFn = app_lifecycle.loadStartupState, load_catalog_startup_state: LoadCatalogStartupStateFn = app_lifecycle.loadCatalogStartupState, load_startup_state_without_credentials: LoadStartupStateWithoutCredentialsFn = app_lifecycle.loadStartupStateWithoutCredentials, load_startup_status: LoadStartupStatusFn = app_lifecycle.loadStartupStatus, getenv: GetenvFn = getenvDefault, environ_map: EnvironMapFn = environMapDefault, self_exe_path: SelfExePathFn = selfExePathDefault, read_masked_key: ReadMaskedKeyFn = readMaskedKeyDefault, setup_terminal_available: SetupTerminalAvailableFn = setupTerminalAvailableDefault,}; const GlobalLaunchArgs = struct { remaining: []const [:0]const u8, modifiers: LaunchModifiers = .{}, fn deinit(self: *GlobalLaunchArgs, alloc: Allocator) void { self.modifiers.deinit(alloc); self.* = undefined; } fn takeModifiers(self: *GlobalLaunchArgs) LaunchModifiers { const result = self.modifiers; self.modifiers = .{}; return result; }}; fn parseGlobalLaunchArgs( alloc: Allocator, args: []const [:0]const u8,) !GlobalLaunchArgs { var overrides: std.ArrayList(config_runtime.context_limits.Override) = .empty; errdefer overrides.deinit(alloc); var directories: std.ArrayList([]u8) = .empty; errdefer { for (directories.items) |path| alloc.free(path); directories.deinit(alloc); } var suppress_saved = false; var index: usize = 0; while (index < args.len) { const arg = args[index]; if (std.mem.eql(u8, arg, "--context-limit")) { index += 1; if (index >= args.len) return error.MissingContextLimitValue; try overrides.append(alloc, try config_runtime.context_limits.parseOverride(args[index])); } else if (std.mem.startsWith(u8, arg, "--context-limit=")) { try overrides.append(alloc, try config_runtime.context_limits.parseOverride(arg["--context-limit=".len..])); } else if (std.mem.eql(u8, arg, "--add-dir")) { index += 1; if (index >= args.len or args[index].len == 0) return error.MissingAddDirectoryValue; try directories.append(alloc, try alloc.dupe(u8, args[index])); } else if (std.mem.startsWith(u8, arg, "--add-dir=")) { const value = arg["--add-dir=".len..]; if (value.len == 0) return error.MissingAddDirectoryValue; try directories.append(alloc, try alloc.dupe(u8, value)); } else if (std.mem.eql(u8, arg, "--no-additional-dirs")) { if (suppress_saved) return error.DuplicateAdditionalDirectorySuppression; suppress_saved = true; } else { break; } index += 1; } const override_slice = try overrides.toOwnedSlice(alloc); errdefer if (override_slice.len > 0) alloc.free(override_slice); const directory_slice = try directories.toOwnedSlice(alloc); return .{ .remaining = args[index..], .modifiers = .{ .context_limit_overrides = override_slice, .additional_directories = directory_slice, .saved_directories_suppressed = suppress_saved, }, };} /// Returns the command that follows the supported global launch modifiers./// Startup uses this same surface to select the full runtime configuration/// before the allocating parser runs.pub fn commandAfterGlobalLaunchArgs(args: []const [:0]const u8) ?[]const u8 { var index: usize = 0; while (index < args.len) { const arg = args[index]; if (std.mem.eql(u8, arg, "--context-limit") or std.mem.eql(u8, arg, "--add-dir")) { index += 1; if (index >= args.len) return null; } else if (!std.mem.startsWith(u8, arg, "--context-limit=") and !std.mem.startsWith(u8, arg, "--add-dir=") and !std.mem.eql(u8, arg, "--no-additional-dirs")) { return arg; } index += 1; } return null;} pub fn parse(command_catalog: CommandCatalog, args: []const [:0]const u8) Command { @setRuntimeSafety(false); if (args.len == 0) return .interactive; const command = args[0]; if (command.len == 0) return .{ .unknown = command }; switch (command[0]) { '-', 'h' => { if (command_specs.matchesTopLevel(command_catalog, command, .help)) return .help; if (command_specs.matchesTopLevel(command_catalog, command, .@"resume") or std.mem.startsWith(u8, command, resume_id_alias_prefix)) { return .{ .resume_session = .{ .args = args, .top_level_alias = true, } }; } }, 'a' => { if (command_specs.matchesTopLevel(command_catalog, command, .ask)) return .{ .ask = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .acp)) return .{ .acp = args[1..] }; }, 'b' => { if (command_specs.matchesTopLevel(command_catalog, command, .background)) return .{ .background = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .credits)) return .{ .credits = args[1..] }; }, 'c' => { if (command_specs.matchesTopLevel(command_catalog, command, .credits)) return .{ .credits = args[1..] }; }, 'd' => { if (command_specs.matchesTopLevel(command_catalog, command, .doctor)) return .{ .doctor = args[1..] }; }, 'i' => { if (command_specs.matchesTopLevel(command_catalog, command, .issue)) return .{ .issue = args[1..] }; }, 'l' => { if (command_specs.matchesTopLevel(command_catalog, command, .login)) return .{ .login = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .logout)) return .{ .logout = args[1..] }; }, 'm' => { if (command_specs.matchesTopLevel(command_catalog, command, .models)) return .{ .models = args[1..] }; }, 'p' => { if (command_specs.matchesTopLevel(command_catalog, command, .pr)) return .{ .pr = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .permissions)) return .{ .permissions = args[1..] }; }, 'r' => { if (command_specs.matchesTopLevel(command_catalog, command, .@"resume")) return .{ .resume_session = .{ .args = args[1..] } }; if (command_specs.matchesTopLevel(command_catalog, command, .replay)) return .{ .replay = args[1..] }; }, 's' => { if (command_specs.matchesTopLevel(command_catalog, command, .setup)) return .{ .setup = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .status)) return .{ .status = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .sessions)) return .{ .sessions = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .session)) return .{ .session = args[1..] }; }, 't' => { if (command_specs.matchesTopLevel(command_catalog, command, .teams)) return .{ .teams = args[1..] }; }, 'u' => { if (command_specs.matchesTopLevel(command_catalog, command, .usage)) return .{ .usage = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .upgrade)) return .{ .upgrade = args[1..] }; }, 'w' => { if (command_specs.matchesTopLevel(command_catalog, command, .workspace)) return .{ .workspace = args[1..] }; }, else => {}, } return .{ .unknown = command };} pub const NonInteractiveLaunch = struct { global_args: GlobalLaunchArgs, effective_args: []const [:0]const u8, command: Command, pub fn deinit(self: *NonInteractiveLaunch, alloc: Allocator) void { self.global_args.deinit(alloc); self.* = undefined; }}; pub const InteractiveLaunchParseResult = union(enum) { interactive: InteractiveLaunch, noninteractive: NonInteractiveLaunch,}; /// Parses the shared interactive launch language without dispatching commands./// Returned launch values own their allocations and must be deinitialized.pub fn parseInteractiveLaunch( alloc: Allocator, args: []const [:0]const u8,Lines 1–500 of 5134. Load more
const std = @import("std"); const builtin = @import("builtin"); const io_mod = @import("../shared/io.zig"); const app_lifecycle = @import("../app/app_lifecycle.zig"); const background_record_liveness = @import("../background/background_record_liveness.zig"); const background_store = @import("../background/background_store.zig"); const acp_runner = @import("acp_runner.zig"); const cli_ask = @import("cli_ask.zig"); const cli_replay = @import("cli_replay.zig"); const command_specs = @import("../slash_commands/command_specs.zig"); const collections = @import("../shared/collections.zig"); const config_runtime = @import("../config/config_runtime.zig"); const devbox_executor = @import("../execution/devbox_executor.zig"); const doctor_runtime = @import("doctor_runtime.zig"); const gateway_provider = @import("../gateway/gateway_provider.zig"); const model_catalog = @import("../gateway/model_catalog.zig"); const background_process_provider = @import( "../execution/background_process_provider.zig", ); const github_publish = @import("../github/github_publish.zig"); const github_workflows = @import("../github/github_workflows.zig"); const host = @import("../hosts/host.zig"); const login_flow = @import("../auth/login_flow.zig"); const oauth_transport = @import("../auth/oauth_transport.zig"); const secret = @import("../auth/secret.zig"); const output_contracts = @import("../output/output_contracts.zig"); const permission_auto_classifier = @import("../permissions/auto_classifier.zig"); const prompt_policy = @import("../config/prompt_policy.zig"); const sandbox = @import("../permissions/sandbox.zig"); const session_store = @import("../session/session_store.zig"); const usage_report = @import("../session/usage_report.zig"); const skill_contract = @import("../skills/skill_contract.zig"); const types = @import("../shared/types.zig"); const update_target = @import("../upgrade/update_target.zig"); const test_builtin_gateway = if (builtin.is_test) @import("../../builtins/gateway.zig") else struct {}; const context_contract = @import("../workspace/context_contract.zig"); const mode_registry = @import("../modes/mode_registry.zig"); const mcp_contract = @import("../mcp/mcp_contract.zig"); const mcp_runtime = @import("../mcp/mcp_runtime.zig"); const tool_set_contract = @import("../tooling/tool_set.zig"); const workspace_access = @import("../workspace/workspace_access.zig"); const workspace_commands = @import("../workspace/workspace_commands.zig"); const usage_cli_runtime = @import("usage_cli_runtime.zig"); const Allocator = std.mem.Allocator; const CommandCatalog = command_specs.TopLevelRegistry; const TopLevelKind = command_specs.TopLevelKind; pub const Command = union(enum) { interactive, help, ask: []const [:0]const u8, acp: []const [:0]const u8, pr: []const [:0]const u8, issue: []const [:0]const u8, login: []const [:0]const u8, logout: []const [:0]const u8, setup: []const [:0]const u8, status: []const [:0]const u8, permissions: []const [:0]const u8, models: []const [:0]const u8, doctor: []const [:0]const u8, background: []const [:0]const u8, teams: []const [:0]const u8, session: []const [:0]const u8, sessions: []const [:0]const u8, resume_session: ResumeInvocation, credits: []const [:0]const u8, usage: []const [:0]const u8, upgrade: []const [:0]const u8, replay: []const [:0]const u8, workspace: []const [:0]const u8, unknown: []const u8, }; const ResumeInvocation = struct { args: []const [:0]const u8, top_level_alias: bool = false, }; const resume_id_alias_prefix = "--resume-"; pub const upgrade_relaunch_arg = "--upgrade-relaunch"; // The one resume alias that asks which session to open. Every other spelling // names its target, so it resumes without a prompt. const resume_picker_alias = "-r"; pub const ResumeTarget = union(enum) { pick, last, id: []u8, pub fn deinit(self: *ResumeTarget, alloc: Allocator) void { switch (self.*) { .pick, .last => {}, .id => |value| alloc.free(value), } self.* = undefined; } }; pub const LaunchModifiers = struct { context_limit_overrides: []config_runtime.context_limits.Override = &.{}, additional_directories: [][]u8 = &.{}, saved_directories_suppressed: bool = false, pub fn deinit(self: *LaunchModifiers, alloc: Allocator) void { if (self.context_limit_overrides.len > 0) alloc.free(self.context_limit_overrides); for (self.additional_directories) |path| alloc.free(path); if (self.additional_directories.len > 0) alloc.free(self.additional_directories); self.* = .{}; } pub fn hasWorkspaceModifiers(self: LaunchModifiers) bool { return self.additional_directories.len > 0 or self.saved_directories_suppressed; } }; pub const InteractiveLaunch = struct { requested_resume: ?ResumeTarget = null, upgrade_relaunch: bool = false, record_requested: bool = false, modifiers: LaunchModifiers = .{}, pub fn deinit(self: *InteractiveLaunch, alloc: Allocator) void { if (self.requested_resume) |*target| target.deinit(alloc); self.modifiers.deinit(alloc); self.* = undefined; } }; pub const RunResult = union(enum) { interactive: InteractiveLaunch, handled_success, handled_failure, handled_exit: u8, }; pub const record_modifier_usage = "usage: fx --record is only supported for interactive startup\n"; const version_usage = "usage: fx --version\n"; pub fn recordRequested(args: []const [:0]const u8) error{RecordModifierRequiresInteractive}!bool { var count: usize = 0; for (args) |arg| { if (std.mem.eql(u8, arg, "--record")) count += 1; } if (count == 0) return false; if (count != 1) return error.RecordModifierRequiresInteractive; if (args.len == 1 and std.mem.eql(u8, args[0], "--record")) return true; if (args.len >= 2 and (std.mem.eql(u8, args[0], "resume") or std.mem.eql(u8, args[0], "--resume")) and std.mem.eql(u8, args[args.len - 1], "--record")) return true; return error.RecordModifierRequiresInteractive; } pub const Config = struct { version: []const u8 = "", revision: []const u8 = "", build_channel: update_target.Channel = .stable, command_catalog: CommandCatalog, default_model: []const u8, default_agent_step_limit: usize, models_path: []const u8, gateway_retry_count: usize, gateway_chat_url: []const u8, gateway_provider: gateway_provider.Provider, background_process_provider: background_process_provider.Provider = background_process_provider.unavailable_provider, url_opener: host.UrlOpener, secret_store: host.SecretStore, prompt_policy: prompt_policy.Policy, skill_root_policy: skill_contract.RootPolicy, ignored_list_entries: []const []const u8, max_list_entries: usize, max_read_file_bytes: usize, max_read_file_lines: usize, max_read_file_line_len: usize, max_command_output_bytes: usize, max_tool_result_bytes: usize, max_history_turns: usize, context_registry: context_contract.Registry, mode_registry: mode_registry.Registry, tool_set: tool_set_contract.ToolSet, inspect_mcp_profile_config: mcp_contract.InspectProfileConfigFn, load_mcp_runtime: mcp_runtime.LoadRuntimeFn, acp_runner: acp_runner.Runner, devbox_provider: ?devbox_executor.Provider = null, permission_reviewer_provider: ?permission_auto_classifier.Provider = null, }; const LocalSurfaceOptions = struct { format: output_contracts.OutputFormat = .text, }; const UpgradeOptions = struct { format: output_contracts.OutputFormat = .text, channel: ?update_target.Channel = null, }; const SessionListOptions = struct { format: output_contracts.OutputFormat = .text, scope: session_store.SessionListScope = .current_workspace, limit: usize = session_store.session_list_default_limit, continuation: ?session_store.ResumableSessionContinuation = null, }; const UsageOptions = struct { format: output_contracts.OutputFormat = .text, scope: usage_report.Scope = .days_30, }; const WorkspaceOptions = struct { format: output_contracts.OutputFormat = .text, action: ?workspace_commands.Action = null, }; const PersistedRecordTarget = union(enum) { last, id: u64, }; const PersistedRecordOptions = struct { format: output_contracts.OutputFormat = .text, target: ?PersistedRecordTarget = null, }; // `fx session` reads one saved session, so it names its target and never // reaches for the picker that `ResumeTarget` carries. const SessionDetailTarget = union(enum) { last, id: []u8, fn deinit(self: *SessionDetailTarget, alloc: Allocator) void { switch (self.*) { .last => {}, .id => |value| alloc.free(value), } self.* = undefined; } }; const SessionDetailOptions = struct { format: output_contracts.OutputFormat = .text, target: ?SessionDetailTarget = null, fn deinit(self: *SessionDetailOptions, alloc: Allocator) void { if (self.target) |*target| target.deinit(alloc); self.* = undefined; } }; const SessionMigrationOptions = struct { format: output_contracts.OutputFormat = .text, session_id: []u8, allow_large: bool = false, fn deinit(self: *SessionMigrationOptions, alloc: Allocator) void { alloc.free(self.session_id); self.* = undefined; } }; const SessionRecoveryOptions = struct { format: output_contracts.OutputFormat = .text, session_id: []u8, fn deinit(self: *SessionRecoveryOptions, alloc: Allocator) void { alloc.free(self.session_id); self.* = undefined; } }; const AcpOptions = struct { model: ?[]const u8 = null, log_file: ?[]const u8 = null, }; const WorkflowOptions = struct { auto_permission: bool, create: bool, context: []u8, fn deinit(self: WorkflowOptions, alloc: Allocator) void { alloc.free(self.context); } }; const WriteFn = *const fn (?*anyopaque, []const u8) anyerror!void; const LoadStartupStateFn = *const fn (Allocator, oauth_transport.Provider, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupState; const LoadCatalogStartupStateFn = *const fn (Allocator, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupState; const LoadStartupStateWithoutCredentialsFn = *const fn (Allocator, []const u8, usize) anyerror!app_lifecycle.StartupState; const LoadStartupStatusFn = *const fn (Allocator, host.SecretStore, []const u8, usize) anyerror!app_lifecycle.StartupStatus; const GetenvFn = *const fn (?*anyopaque, []const u8) ?[]const u8; const EnvironMapFn = *const fn (?*anyopaque) ?*const std.process.Environ.Map; const SelfExePathFn = *const fn (?*anyopaque, Allocator) anyerror![]u8; const ReadMaskedKeyFn = *const fn (?*anyopaque, Allocator, WriteFn, ?*anyopaque) anyerror![]u8; const SetupTerminalAvailableFn = *const fn (?*anyopaque) bool; const RunDeps = struct { stdout_ctx: ?*anyopaque = null, stderr_ctx: ?*anyopaque = null, env_ctx: ?*anyopaque = null, self_exe_ctx: ?*anyopaque = null, setup_ctx: ?*anyopaque = null, write_stdout: WriteFn = writeRealStdout, write_stderr: WriteFn = writeRealStderr, load_startup_state: LoadStartupStateFn = app_lifecycle.loadStartupState, load_catalog_startup_state: LoadCatalogStartupStateFn = app_lifecycle.loadCatalogStartupState, load_startup_state_without_credentials: LoadStartupStateWithoutCredentialsFn = app_lifecycle.loadStartupStateWithoutCredentials, load_startup_status: LoadStartupStatusFn = app_lifecycle.loadStartupStatus, getenv: GetenvFn = getenvDefault, environ_map: EnvironMapFn = environMapDefault, self_exe_path: SelfExePathFn = selfExePathDefault, read_masked_key: ReadMaskedKeyFn = readMaskedKeyDefault, setup_terminal_available: SetupTerminalAvailableFn = setupTerminalAvailableDefault, }; const GlobalLaunchArgs = struct { remaining: []const [:0]const u8, modifiers: LaunchModifiers = .{}, fn deinit(self: *GlobalLaunchArgs, alloc: Allocator) void { self.modifiers.deinit(alloc); self.* = undefined; } fn takeModifiers(self: *GlobalLaunchArgs) LaunchModifiers { const result = self.modifiers; self.modifiers = .{}; return result; } }; fn parseGlobalLaunchArgs( alloc: Allocator, args: []const [:0]const u8, ) !GlobalLaunchArgs { var overrides: std.ArrayList(config_runtime.context_limits.Override) = .empty; errdefer overrides.deinit(alloc); var directories: std.ArrayList([]u8) = .empty; errdefer { for (directories.items) |path| alloc.free(path); directories.deinit(alloc); } var suppress_saved = false; var index: usize = 0; while (index < args.len) { const arg = args[index]; if (std.mem.eql(u8, arg, "--context-limit")) { index += 1; if (index >= args.len) return error.MissingContextLimitValue; try overrides.append(alloc, try config_runtime.context_limits.parseOverride(args[index])); } else if (std.mem.startsWith(u8, arg, "--context-limit=")) { try overrides.append(alloc, try config_runtime.context_limits.parseOverride(arg["--context-limit=".len..])); } else if (std.mem.eql(u8, arg, "--add-dir")) { index += 1; if (index >= args.len or args[index].len == 0) return error.MissingAddDirectoryValue; try directories.append(alloc, try alloc.dupe(u8, args[index])); } else if (std.mem.startsWith(u8, arg, "--add-dir=")) { const value = arg["--add-dir=".len..]; if (value.len == 0) return error.MissingAddDirectoryValue; try directories.append(alloc, try alloc.dupe(u8, value)); } else if (std.mem.eql(u8, arg, "--no-additional-dirs")) { if (suppress_saved) return error.DuplicateAdditionalDirectorySuppression; suppress_saved = true; } else { break; } index += 1; } const override_slice = try overrides.toOwnedSlice(alloc); errdefer if (override_slice.len > 0) alloc.free(override_slice); const directory_slice = try directories.toOwnedSlice(alloc); return .{ .remaining = args[index..], .modifiers = .{ .context_limit_overrides = override_slice, .additional_directories = directory_slice, .saved_directories_suppressed = suppress_saved, }, }; } /// Returns the command that follows the supported global launch modifiers. /// Startup uses this same surface to select the full runtime configuration /// before the allocating parser runs. pub fn commandAfterGlobalLaunchArgs(args: []const [:0]const u8) ?[]const u8 { var index: usize = 0; while (index < args.len) { const arg = args[index]; if (std.mem.eql(u8, arg, "--context-limit") or std.mem.eql(u8, arg, "--add-dir")) { index += 1; if (index >= args.len) return null; } else if (!std.mem.startsWith(u8, arg, "--context-limit=") and !std.mem.startsWith(u8, arg, "--add-dir=") and !std.mem.eql(u8, arg, "--no-additional-dirs")) { return arg; } index += 1; } return null; } pub fn parse(command_catalog: CommandCatalog, args: []const [:0]const u8) Command { @setRuntimeSafety(false); if (args.len == 0) return .interactive; const command = args[0]; if (command.len == 0) return .{ .unknown = command }; switch (command[0]) { '-', 'h' => { if (command_specs.matchesTopLevel(command_catalog, command, .help)) return .help; if (command_specs.matchesTopLevel(command_catalog, command, .@"resume") or std.mem.startsWith(u8, command, resume_id_alias_prefix)) { return .{ .resume_session = .{ .args = args, .top_level_alias = true, } }; } }, 'a' => { if (command_specs.matchesTopLevel(command_catalog, command, .ask)) return .{ .ask = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .acp)) return .{ .acp = args[1..] }; }, 'b' => { if (command_specs.matchesTopLevel(command_catalog, command, .background)) return .{ .background = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .credits)) return .{ .credits = args[1..] }; }, 'c' => { if (command_specs.matchesTopLevel(command_catalog, command, .credits)) return .{ .credits = args[1..] }; }, 'd' => { if (command_specs.matchesTopLevel(command_catalog, command, .doctor)) return .{ .doctor = args[1..] }; }, 'i' => { if (command_specs.matchesTopLevel(command_catalog, command, .issue)) return .{ .issue = args[1..] }; }, 'l' => { if (command_specs.matchesTopLevel(command_catalog, command, .login)) return .{ .login = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .logout)) return .{ .logout = args[1..] }; }, 'm' => { if (command_specs.matchesTopLevel(command_catalog, command, .models)) return .{ .models = args[1..] }; }, 'p' => { if (command_specs.matchesTopLevel(command_catalog, command, .pr)) return .{ .pr = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .permissions)) return .{ .permissions = args[1..] }; }, 'r' => { if (command_specs.matchesTopLevel(command_catalog, command, .@"resume")) return .{ .resume_session = .{ .args = args[1..] } }; if (command_specs.matchesTopLevel(command_catalog, command, .replay)) return .{ .replay = args[1..] }; }, 's' => { if (command_specs.matchesTopLevel(command_catalog, command, .setup)) return .{ .setup = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .status)) return .{ .status = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .sessions)) return .{ .sessions = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .session)) return .{ .session = args[1..] }; }, 't' => { if (command_specs.matchesTopLevel(command_catalog, command, .teams)) return .{ .teams = args[1..] }; }, 'u' => { if (command_specs.matchesTopLevel(command_catalog, command, .usage)) return .{ .usage = args[1..] }; if (command_specs.matchesTopLevel(command_catalog, command, .upgrade)) return .{ .upgrade = args[1..] }; }, 'w' => { if (command_specs.matchesTopLevel(command_catalog, command, .workspace)) return .{ .workspace = args[1..] }; }, else => {}, } return .{ .unknown = command }; } pub const NonInteractiveLaunch = struct { global_args: GlobalLaunchArgs, effective_args: []const [:0]const u8, command: Command, pub fn deinit(self: *NonInteractiveLaunch, alloc: Allocator) void { self.global_args.deinit(alloc); self.* = undefined; } }; pub const InteractiveLaunchParseResult = union(enum) { interactive: InteractiveLaunch, noninteractive: NonInteractiveLaunch, }; /// Parses the shared interactive launch language without dispatching commands. /// Returned launch values own their allocations and must be deinitialized. pub fn parseInteractiveLaunch( alloc: Allocator, args: []const [:0]const u8,