@zergai/ztc 0.2.6-dev.36102446685 → 0.3.0-beta.1

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.
Files changed (112) hide show
  1. package/README.md +43 -17
  2. package/bin/zmux-darwin-arm64 +0 -0
  3. package/bin/ztc-tui-darwin-arm64 +0 -0
  4. package/bin/zterm-darwin-arm64 +0 -0
  5. package/dist/App.js +19 -5
  6. package/dist/agent/agent.d.ts +5 -1
  7. package/dist/agent/agent.js +51 -8
  8. package/dist/agent/backends/account_pool.d.ts +5 -6
  9. package/dist/agent/backends/account_pool.js +39 -14
  10. package/dist/agent/backends/account_rotation.d.ts +3 -0
  11. package/dist/agent/backends/account_rotation.js +14 -3
  12. package/dist/agent/backends/codex_account_status.d.ts +42 -0
  13. package/dist/agent/backends/codex_account_status.js +196 -0
  14. package/dist/agent/backends/codex_app_server.d.ts +12 -2
  15. package/dist/agent/backends/codex_app_server.js +66 -6
  16. package/dist/agent/backends/index.d.ts +2 -0
  17. package/dist/agent/backends/index.js +13 -5
  18. package/dist/agent/backends/openai.d.ts +2 -2
  19. package/dist/agent/backends/openai.js +4 -3
  20. package/dist/agent/backends/openai_compatible.js +10 -10
  21. package/dist/agent/backends/openai_responses.d.ts +17 -5
  22. package/dist/agent/backends/openai_responses.js +2 -4
  23. package/dist/agent/backends/token_usage.js +1 -1
  24. package/dist/agent/backends/types.d.ts +3 -0
  25. package/dist/agent/commands/accounts.d.ts +7 -1
  26. package/dist/agent/commands/accounts.js +299 -101
  27. package/dist/agent/commands/config.js +11 -3
  28. package/dist/agent/commands/config_profile_switch.d.ts +2 -0
  29. package/dist/agent/commands/config_profile_switch.js +22 -0
  30. package/dist/agent/commands/context.js +4 -3
  31. package/dist/agent/commands/index.js +1 -2
  32. package/dist/agent/commands/input_mode.js +1 -1
  33. package/dist/agent/commands/model.js +7 -2
  34. package/dist/agent/commands/plan.d.ts +12 -0
  35. package/dist/agent/commands/plan.js +39 -28
  36. package/dist/agent/commands/profile.d.ts +1 -1
  37. package/dist/agent/commands/profile.js +34 -257
  38. package/dist/agent/commands/provider_choices.d.ts +12 -1
  39. package/dist/agent/commands/provider_choices.js +29 -2
  40. package/dist/agent/commands/provider_selection.d.ts +3 -0
  41. package/dist/agent/commands/provider_selection.js +63 -0
  42. package/dist/agent/commands/settings.d.ts +2 -0
  43. package/dist/agent/commands/settings.js +655 -50
  44. package/dist/agent/commands/types.d.ts +19 -2
  45. package/dist/agent/tools/ask_user.js +2 -2
  46. package/dist/app/agent_runner.d.ts +1 -1
  47. package/dist/app/agent_runner.js +2 -2
  48. package/dist/app/ask_user_response.d.ts +3 -0
  49. package/dist/app/ask_user_response.js +16 -0
  50. package/dist/app/command_context.js +29 -13
  51. package/dist/app/controller.d.ts +17 -2
  52. package/dist/app/controller.js +24 -1
  53. package/dist/app/selection_completion.d.ts +9 -3
  54. package/dist/app/selection_completion.js +54 -22
  55. package/dist/cli_args.js +10 -1
  56. package/dist/cli_main.js +85 -2
  57. package/dist/cli_ratatui.d.ts +22 -4
  58. package/dist/cli_ratatui.js +421 -146
  59. package/dist/config/defaults.d.ts +6 -0
  60. package/dist/config/defaults.js +20 -0
  61. package/dist/config/profiles.d.ts +9 -0
  62. package/dist/config/profiles.js +87 -0
  63. package/dist/config/types.d.ts +2 -0
  64. package/dist/config.d.ts +7 -0
  65. package/dist/config.js +283 -40
  66. package/dist/headless.js +14 -0
  67. package/dist/panes/model_catalog.d.ts +1 -1
  68. package/dist/panes/model_catalog.js +24 -2
  69. package/dist/panes/ztc_launch.js +6 -0
  70. package/dist/router_auth.js +3 -2
  71. package/dist/session.d.ts +2 -0
  72. package/dist/session.js +3 -0
  73. package/dist/types.d.ts +23 -2
  74. package/dist/ui/views/app.d.ts +3 -1
  75. package/dist/ui/views/app.js +3 -2
  76. package/dist/ui/views/completion_menu.js +6 -6
  77. package/dist/ui/views/input_area.d.ts +2 -1
  78. package/dist/ui/views/input_area.js +9 -8
  79. package/dist/ui/views/status_bar.d.ts +2 -0
  80. package/dist/ui/views/status_bar.js +15 -4
  81. package/dist/ui/widgets/color_panel.d.ts +9 -0
  82. package/dist/ui/widgets/color_panel.js +41 -26
  83. package/dist/ui/widgets/navigator_panel.d.ts +9 -0
  84. package/dist/ui/widgets/navigator_panel.js +17 -16
  85. package/dist/ui/widgets/panel_frame.d.ts +83 -3
  86. package/dist/ui/widgets/panel_frame.js +107 -9
  87. package/dist/ui/widgets/questionnaire.d.ts +9 -0
  88. package/dist/ui/widgets/questionnaire.js +214 -16
  89. package/dist/ui/widgets/resume_panel.d.ts +9 -0
  90. package/dist/ui/widgets/resume_panel.js +18 -17
  91. package/dist/ui/widgets/rewind_panel.d.ts +9 -0
  92. package/dist/ui/widgets/rewind_panel.js +15 -16
  93. package/dist/ui/widgets/select.d.ts +6 -0
  94. package/dist/ui/widgets/select.js +47 -12
  95. package/dist/ui/widgets/settings_panel.d.ts +75 -0
  96. package/dist/ui/widgets/settings_panel.js +197 -39
  97. package/dist/ui/widgets/status_line_panel.d.ts +9 -0
  98. package/dist/ui/widgets/status_line_panel.js +18 -19
  99. package/dist/ui/widgets/transcript_preview.d.ts +8 -0
  100. package/dist/ui/widgets/transcript_preview.js +10 -0
  101. package/dist/utils/context.js +2 -0
  102. package/dist/utils/fireworks.d.ts +20 -0
  103. package/dist/utils/fireworks.js +23 -0
  104. package/dist/utils/models.d.ts +2 -1
  105. package/dist/utils/models.js +19 -0
  106. package/dist/web/accounts_mirror.d.ts +7 -0
  107. package/dist/web/accounts_mirror.js +59 -7
  108. package/dist/web/mirror_stream.js +19 -5
  109. package/dist/zlm-embed.mjs +5734 -5362
  110. package/package.json +5 -5
  111. package/dist/agent/commands/provider.d.ts +0 -2
  112. package/dist/agent/commands/provider.js +0 -75
@@ -13,7 +13,6 @@ import { shellCommand, cdCommand } from './shell.js';
13
13
  import { clipboardCommand } from './clipboard.js';
14
14
  import { modelsCommand } from './models.js';
15
15
  import { modelCommand } from './model.js';
16
- import { providerCommand } from './provider.js';
17
16
  import { permissionsCommand } from './permissions.js';
18
17
  import { skillsCommand } from './skills.js';
19
18
  import { retryCommand } from './retry.js';
@@ -66,7 +65,7 @@ import { effortCommand, thinkingCommand, fastCommand } from './effort.js';
66
65
  import { runtimePluginRegistry } from '../../plugin/runtime_registry.js';
67
66
  const commandList = [];
68
67
  const helpCommand = makeHelpCommand(() => commandList);
69
- commandList.push(helpCommand, authCommand, loginCommand, logoutCommand, accountsCommand, zergCommand, zergsCommand, configCommand, debugCommand, clearCommand, statusCommand, runtimeCommand, hooksCommand, emulationCommand, shellCommand, cdCommand, clipboardCommand, modelsCommand, modelCommand, providerCommand, permissionsCommand, initCommand, forkCommand, sideCommand, diffCommand, rawCommand, themeCommand, doctorCommand, intentCommand, profileCommand, skillsCommand, keybindingsCommand, keymapCommand, shortcutsCommand, updateCommand, restartCommand, reloadCommand, inputModeCommand, retryCommand, dictationStatusCommand, embeddedCommand, benchCommand, voiceCommand, sfxCommand, compactCommand, automodeCommand, sessionsCommand, resumeCommand, renameCommand, contextCommand, shannonRunnerCommand, planCommand, executeCommand, modeCommand, modeListCommand, exitModeCommand, validateCommand, previewCommand, saveCommand, dcaigCommand, manifestCommand, swarmCommand, swarmStopCommand, swarmStatusCommand, superviseCommand, superviseStopCommand, superviseStatusCommand, zlickyCommand, zlickyStopCommand, zlickyStatusCommand, zlickyActCommand, zlickyPresenceCommand, zlickyPttCommand, simCommand, simStopCommand, simStatusCommand, simRunCommand, simCheckpointCommand, simResumeCommand, evolveCommand, evolveStopCommand, evolveStatusCommand, evolveBestCommand, optimizeCommand, batchCommand, batchStatusCommand, batchStopCommand, workflowsCommand, mirrorCommand, settingsCommand, mcpCommand, loopCommand, goalCommand, effortCommand, thinkingCommand, fastCommand, exitCommand);
68
+ commandList.push(helpCommand, authCommand, loginCommand, logoutCommand, accountsCommand, zergCommand, zergsCommand, configCommand, debugCommand, clearCommand, statusCommand, runtimeCommand, hooksCommand, emulationCommand, shellCommand, cdCommand, clipboardCommand, modelsCommand, modelCommand, permissionsCommand, initCommand, forkCommand, sideCommand, diffCommand, rawCommand, themeCommand, doctorCommand, intentCommand, profileCommand, skillsCommand, keybindingsCommand, keymapCommand, shortcutsCommand, updateCommand, restartCommand, reloadCommand, inputModeCommand, retryCommand, dictationStatusCommand, embeddedCommand, benchCommand, voiceCommand, sfxCommand, compactCommand, automodeCommand, sessionsCommand, resumeCommand, renameCommand, contextCommand, shannonRunnerCommand, planCommand, executeCommand, modeCommand, modeListCommand, exitModeCommand, validateCommand, previewCommand, saveCommand, dcaigCommand, manifestCommand, swarmCommand, swarmStopCommand, swarmStatusCommand, superviseCommand, superviseStopCommand, superviseStatusCommand, zlickyCommand, zlickyStopCommand, zlickyStatusCommand, zlickyActCommand, zlickyPresenceCommand, zlickyPttCommand, simCommand, simStopCommand, simStatusCommand, simRunCommand, simCheckpointCommand, simResumeCommand, evolveCommand, evolveStopCommand, evolveStatusCommand, evolveBestCommand, optimizeCommand, batchCommand, batchStatusCommand, batchStopCommand, workflowsCommand, mirrorCommand, settingsCommand, mcpCommand, loopCommand, goalCommand, effortCommand, thinkingCommand, fastCommand, exitCommand);
70
69
  export const commands = commandList;
71
70
  export function refreshPluginCommands() {
72
71
  const existing = new Set(commandList.map(command => command.name));
@@ -1,6 +1,6 @@
1
1
  export const inputModeCommand = {
2
2
  name: 'inputmode',
3
- description: 'Set input mode while an agent is running',
3
+ description: 'Set busy Enter behavior: queue sends after the next tool, interrupt restarts the turn; Tab queues after the turn',
4
4
  usage: '<queue|interrupt>',
5
5
  handler: (args, ctx) => {
6
6
  const mode = (args[0] || '').toLowerCase();
@@ -1,16 +1,21 @@
1
1
  import { configCommand } from './config.js';
2
2
  import { modelsCommand } from './models.js';
3
+ import { canonicalProvider } from './provider_choices.js';
4
+ import { selectProviderModel } from './provider_selection.js';
3
5
  const MODEL_SUBCOMMANDS = ['list', 'latest', 'set'];
4
6
  export const modelCommand = {
5
7
  name: 'model',
6
- description: 'Set model, or use list/latest/set as alias for /models',
7
- usage: '<model-id|list [provider]|latest [provider]|set <model-id>>',
8
+ description: 'Select provider and model, or set a model on the current provider',
9
+ usage: '<provider> [model] | zergrouter <provider> [model] | <model-id> | list [provider] | latest [provider] | set <model-id>',
8
10
  agentRunnable: true,
9
11
  category: 'config',
10
12
  handler: (args, ctx) => {
11
13
  if (args.length > 0 && MODEL_SUBCOMMANDS.includes(args[0].toLowerCase())) {
12
14
  return modelsCommand.handler(args, ctx);
13
15
  }
16
+ if (args.length > 1 || (args.length === 1 && canonicalProvider(args[0]))) {
17
+ return selectProviderModel(args, ctx);
18
+ }
14
19
  return configCommand.handler(['model', ...args], ctx);
15
20
  }
16
21
  };
@@ -74,6 +74,18 @@ export declare const planCommand: Command;
74
74
  * Commit plan: extract tasks, exit plan mode, begin implementation
75
75
  */
76
76
  export declare function commitPlan(planFile: string, planContent: string, tasks: string[], ctx: CommandContext, autoApprove: boolean): void;
77
+ export type PlanApprovalDecision = {
78
+ kind: 'approve';
79
+ } | {
80
+ kind: 'edit';
81
+ } | {
82
+ kind: 'cancel';
83
+ } | {
84
+ kind: 'feedback';
85
+ text: string;
86
+ };
87
+ /** Both plan entry points ask through the same question surface and answer shape. */
88
+ export declare function askPlanApproval(ctx: CommandContext, onDecision: (decision: PlanApprovalDecision) => void): void;
77
89
  /**
78
90
  * /execute command - Exit plan mode and begin implementation
79
91
  *
@@ -473,6 +473,39 @@ ${planContent}`,
473
473
  });
474
474
  }
475
475
  }
476
+ /** Both plan entry points ask through the same question surface and answer shape. */
477
+ export function askPlanApproval(ctx, onDecision) {
478
+ const approveLabel = 'Yes, begin implementation (Recommended)';
479
+ const editLabel = 'Edit plan first';
480
+ ctx.questionnaire.showQuestionnaire({
481
+ title: 'Plan Approval',
482
+ questions: [{
483
+ name: 'decision',
484
+ question: 'Ready to begin implementation?',
485
+ type: 'select',
486
+ required: true,
487
+ options: [
488
+ { value: 'approve', label: approveLabel, description: 'Approve the plan, create tasks, and start coding' },
489
+ { value: 'edit', label: editLabel, description: 'Return to plan mode and continue editing' },
490
+ ],
491
+ }],
492
+ allowOther: true,
493
+ }, answers => {
494
+ const answer = answers?.decision;
495
+ if (typeof answer !== 'string' || !answer.trim()) {
496
+ onDecision({ kind: 'cancel' });
497
+ }
498
+ else if (answer === 'approve' || answer === approveLabel) {
499
+ onDecision({ kind: 'approve' });
500
+ }
501
+ else if (answer === 'edit' || answer === editLabel) {
502
+ onDecision({ kind: 'edit' });
503
+ }
504
+ else {
505
+ onDecision({ kind: 'feedback', text: answer.trim() });
506
+ }
507
+ });
508
+ }
476
509
  /**
477
510
  * /execute command - Exit plan mode and begin implementation
478
511
  *
@@ -525,54 +558,32 @@ Tasks: ${tasks.length} implementation step${tasks.length !== 1 ? 's' : ''} found
525
558
  role: 'system',
526
559
  content: planSummary,
527
560
  });
528
- // Show approval select widget
529
- ctx.select.showSelect({
530
- title: 'Plan Approval',
531
- question: 'Ready to begin implementation?',
532
- type: 'select',
533
- options: [
534
- {
535
- value: 'approve',
536
- label: 'Yes, begin implementation (Recommended)',
537
- description: 'Approve the plan, create tasks, and start coding',
538
- },
539
- {
540
- value: 'edit',
541
- label: 'Edit plan first',
542
- description: 'Return to plan mode and continue editing',
543
- },
544
- ],
545
- allowOther: true,
546
- }, (value) => {
547
- if (value === null) {
548
- // Cancelled - stay in plan mode
561
+ askPlanApproval(ctx, decision => {
562
+ if (decision.kind === 'cancel') {
549
563
  ctx.addMessage({
550
564
  role: 'system',
551
565
  content: 'Plan execution cancelled. Still in plan mode.',
552
566
  });
553
567
  return;
554
568
  }
555
- const selection = typeof value === 'string' ? value : '';
556
- if (selection === 'approve') {
569
+ if (decision.kind === 'approve') {
557
570
  commitPlan(planFile, planContent, tasks, ctx, false);
558
571
  ctx.triggerAgentTurn?.();
559
572
  }
560
- else if (selection === 'edit') {
573
+ else if (decision.kind === 'edit') {
561
574
  ctx.addMessage({
562
575
  role: 'system',
563
576
  content: 'Returning to plan mode. Continue editing your plan, then run /execute when ready.',
564
577
  });
565
578
  }
566
579
  else {
567
- // Custom "Other" text - treat as feedback, stay in plan mode
568
580
  ctx.addMessage({
569
581
  role: 'system',
570
- content: `Feedback noted. Still in plan mode.\n\nFeedback: ${selection}`,
582
+ content: `Feedback noted. Still in plan mode.\n\nFeedback: ${decision.text}`,
571
583
  });
572
- // Add feedback as user message so the agent can see it
573
584
  ctx.addMessage({
574
585
  role: 'user',
575
- content: `Please update the plan based on this feedback: ${selection}`,
586
+ content: `Please update the plan based on this feedback: ${decision.text}`,
576
587
  });
577
588
  }
578
589
  });
@@ -1,2 +1,2 @@
1
- import { Command } from './types.js';
1
+ import type { Command } from './types.js';
2
2
  export declare const profileCommand: Command;
@@ -1,274 +1,51 @@
1
- import { mkdirSync, writeFileSync } from 'fs';
2
- import { homedir } from 'os';
3
- import { join } from 'path';
4
- import { optimizeCommand } from './optimize.js';
5
- import { buildProfileOptimizationCommand, getProfileGateCommand, getProfileManifest, listProfileManifests, } from '../../emulation/profile_gates.js';
6
- function helpText() {
7
- return [
8
- 'Manage ZTC prompt profiles and benchmark gates.',
9
- '',
10
- 'Usage:',
11
- ' /profile list',
12
- ' /profile show <ztc|ztc-coder|ztc-datascience|scientist>',
13
- ' /profile gates <profile>',
14
- ' /profile command <profile> <gate> [--tier smoke|initial|expanded|sweep] [--limit N]',
15
- ' /profile run <profile> <gate> [--tier smoke|initial|expanded|sweep] [--limit N]',
16
- ' /profile optimize <profile> [--profile quick|balanced|deep|exhaustive] [--run]',
17
- ' /profile sweep <profile> [--tier smoke|initial|expanded|sweep]',
18
- ].join('\n');
19
- }
20
- function formatGate(gate) {
21
- return [
22
- `- ${gate.name} [${gate.status}]`,
23
- ` benchmark: ${gate.benchmark}`,
24
- ` purpose: ${gate.purpose}`,
25
- gate.command ? ` command: ${gate.command.join(' ')}` : undefined,
26
- gate.tierCommands?.expanded ? ` expanded: ${gate.tierCommands.expanded.join(' ')}` : undefined,
27
- gate.tierCommands?.sweep ? ` sweep: ${gate.tierCommands.sweep.join(' ')}` : undefined,
28
- gate.notes?.length ? ` notes: ${gate.notes.join(' ')}` : undefined,
29
- ].filter(Boolean).join('\n');
30
- }
31
- function replaceLimit(command, limit) {
32
- if (!limit) {
33
- return command;
34
- }
35
- const idx = command.indexOf('--limit');
36
- if (idx >= 0) {
37
- return [...command.slice(0, idx + 1), limit, ...command.slice(idx + 2)];
38
- }
39
- return [...command, '--limit', limit];
40
- }
41
- function findFlagValue(args, flag) {
42
- const idx = args.indexOf(flag);
43
- if (idx < 0) {
44
- return undefined;
45
- }
46
- const value = args[idx + 1];
47
- return value && !value.startsWith('--') ? value : undefined;
48
- }
49
- function shellQuote(value) {
50
- if (/^[A-Za-z0-9_./:=,@+-]+$/.test(value)) {
51
- return value;
52
- }
53
- return `'${value.replace(/'/g, "'\\''")}'`;
54
- }
55
- function normalizeTier(value) {
56
- if (value === 'smoke' || value === 'expanded' || value === 'sweep') {
57
- return value;
58
- }
59
- return 'initial';
60
- }
61
- function gateCommand(profileId, gateName, limit, tier) {
62
- const manifest = getProfileManifest(profileId);
63
- if (!manifest) {
64
- return [];
65
- }
66
- const normalizedGate = gateName.toLowerCase();
67
- const gate = manifest.gates.find(candidate => candidate.benchmark.toLowerCase() === normalizedGate ||
68
- candidate.name.toLowerCase().replace(/\s+/g, '-') === normalizedGate ||
69
- candidate.name.toLowerCase() === normalizedGate);
70
- if (!gate?.command) {
71
- return [];
72
- }
73
- return replaceLimit(getProfileGateCommand(gate, normalizeTier(tier)), limit);
74
- }
75
- function profileRunDir(profileId, tier) {
76
- const stamp = new Date().toISOString().replace(/[:.]/g, '-');
77
- return join(homedir(), '.ztc', 'profile-runs', `${stamp}-${profileId}-${tier}`);
78
- }
79
- function tierLimit(tier, benchmark) {
80
- if (tier === 'smoke') {
81
- return '2';
82
- }
83
- if (tier === 'initial') {
84
- return '5';
85
- }
86
- if (tier === 'expanded') {
87
- return benchmark === 'polyglot' ? '10' : '25';
88
- }
89
- if (tier === 'sweep') {
90
- if (benchmark === 'ds1000')
91
- return '100';
92
- if (benchmark === 'polyglot')
93
- return '25';
94
- if (benchmark === 'hotpotqa')
95
- return '50';
96
- if (benchmark === 'swebench')
97
- return '10';
98
- return '25';
99
- }
100
- return undefined;
101
- }
102
- function tierOptimizationProfile(tier) {
103
- if (tier === 'smoke') {
104
- return 'quick';
105
- }
106
- if (tier === 'expanded') {
107
- return 'deep';
108
- }
109
- if (tier === 'sweep') {
110
- return 'exhaustive';
111
- }
112
- return 'balanced';
113
- }
114
- function writeJson(path, value) {
115
- writeFileSync(path, JSON.stringify(value, null, 2), 'utf-8');
116
- }
1
+ import { activeProfile, builtinProfileEmulation, listConfigProfiles } from '../../config/profiles.js';
2
+ import { switchConfigProfile } from './config_profile_switch.js';
3
+ const USAGE = [
4
+ 'Configuration profiles:',
5
+ ' /profile list list built-in and saved profiles',
6
+ ' /profile <name> switch this session to an existing profile',
7
+ ' /profile use <name> same as above',
8
+ ' /profile create <name> create and switch to a custom profile',
9
+ ].join('\n');
10
+ const OLD_BENCHMARK_COMMANDS = new Set(['show', 'gates', 'command', 'run', 'optimize', 'sweep']);
117
11
  export const profileCommand = {
118
12
  name: 'profile',
119
- description: 'Inspect and run ZTC prompt-profile benchmark gates',
120
- usage: '<list|show|gates|command|run|optimize>',
13
+ description: 'List and switch configuration profiles',
14
+ usage: '[list|<name>|use <name>|create <name>]',
121
15
  handler: async (args, ctx) => {
122
- const [subcommand, ...rest] = args;
123
- if (!subcommand) {
124
- ctx.addMessage({ role: 'system', content: helpText() });
125
- return;
126
- }
127
- if (subcommand === 'list') {
128
- ctx.addMessage({
129
- role: 'system',
130
- content: [
131
- 'ZTC profiles:',
132
- ...listProfileManifests().map(profile => `- ${profile.id}: ${profile.name} — ${profile.description}`),
133
- ].join('\n'),
134
- });
135
- return;
136
- }
137
- if (subcommand === 'show') {
138
- const id = rest[0];
139
- const manifest = id ? getProfileManifest(id) : undefined;
140
- if (!manifest) {
141
- ctx.addMessage({ role: 'system', content: 'Usage: /profile show <profile>' });
16
+ const [action, ...rest] = args;
17
+ if (!action || action === 'list' || action === 'configs' || action === 'help') {
18
+ if (rest.length > 0) {
19
+ ctx.addMessage({ role: 'system', content: USAGE });
142
20
  return;
143
21
  }
144
- ctx.addMessage({
145
- role: 'system',
146
- content: [
147
- `${manifest.name} (${manifest.id})`,
148
- manifest.description,
149
- '',
150
- `source: ${manifest.source.repo} ${manifest.source.promptPath}`,
151
- `runtime: compatibility ${manifest.runtime.compatibilityVersion}, ${manifest.runtime.promptProfile}`,
152
- `benchmark plan: ${manifest.runtime.benchmarkPlan.join(', ')}`,
153
- `failure policy: ${manifest.failurePolicy}`,
154
- '',
155
- 'Gates:',
156
- ...manifest.gates.map(formatGate),
157
- ].join('\n'),
158
- });
159
- return;
160
- }
161
- if (subcommand === 'gates') {
162
- const id = rest[0];
163
- const manifest = id ? getProfileManifest(id) : undefined;
164
- if (!manifest) {
165
- ctx.addMessage({ role: 'system', content: 'Usage: /profile gates <profile>' });
22
+ if (action === 'help') {
23
+ ctx.addMessage({ role: 'system', content: USAGE });
166
24
  return;
167
25
  }
168
- ctx.addMessage({
169
- role: 'system',
170
- content: [`Profile gates for ${manifest.id}:`, ...manifest.gates.map(formatGate)].join('\n'),
26
+ const names = await listConfigProfiles();
27
+ const lines = names.map(name => {
28
+ const emulation = builtinProfileEmulation(name);
29
+ return ` ${name === activeProfile() ? '*' : ' '} ${name}${emulation ? ` (built-in: ${emulation} emulation)` : ''}`;
171
30
  });
31
+ ctx.addMessage({ role: 'system', content: `Config profiles (* active):\n${lines.join('\n')}${!action ? `\n\n${USAGE}` : ''}` });
172
32
  return;
173
33
  }
174
- if (subcommand === 'command' || subcommand === 'run') {
175
- const [profileId, gateName] = rest;
176
- const limit = findFlagValue(rest, '--limit');
177
- const tier = findFlagValue(rest, '--tier');
178
- const command = profileId && gateName ? gateCommand(profileId, gateName, limit, tier) : [];
179
- if (command.length === 0) {
180
- ctx.addMessage({ role: 'system', content: 'Usage: /profile command <profile> <gate> [--limit N]' });
181
- return;
182
- }
183
- const rendered = command.map(shellQuote).join(' ');
184
- if (subcommand === 'command') {
185
- ctx.addMessage({ role: 'system', content: rendered });
186
- return;
187
- }
188
- const result = await ctx.shell.run(rendered);
189
- ctx.addMessage({
190
- role: 'system',
191
- content: [
192
- `Profile gate command: ${rendered}`,
193
- `Exit code: ${result.exitCode}`,
194
- result.stdout,
195
- result.stderr,
196
- ].filter(Boolean).join('\n'),
197
- });
34
+ if (OLD_BENCHMARK_COMMANDS.has(action)) {
35
+ ctx.addMessage({ role: 'system', content: 'Use /emulation to inspect prompt modes. Profile benchmark gates remain available through npm run profiles:baseline.' });
198
36
  return;
199
37
  }
200
- if (subcommand === 'optimize') {
201
- const id = rest[0];
202
- const optimizationProfile = findFlagValue(rest, '--profile');
203
- const command = id ? buildProfileOptimizationCommand(id, optimizationProfile ? { profile: optimizationProfile } : {}) : [];
204
- if (command.length === 0) {
205
- ctx.addMessage({ role: 'system', content: 'Usage: /profile optimize <profile> [--profile quick|balanced|deep|exhaustive] [--run]' });
206
- return;
207
- }
208
- const rendered = `/${command.join(' ')}`;
209
- if (!rest.includes('--run')) {
210
- ctx.addMessage({ role: 'system', content: rendered });
211
- return;
212
- }
213
- await optimizeCommand.handler(command.slice(1), ctx);
38
+ const namedCommand = action === 'use' || action === 'switch' || action === 'create';
39
+ const name = namedCommand ? rest[0] : action;
40
+ if (!name || (namedCommand ? rest.length !== 1 : rest.length !== 0)) {
41
+ ctx.addMessage({ role: 'system', content: USAGE });
214
42
  return;
215
43
  }
216
- if (subcommand === 'sweep') {
217
- const id = rest[0];
218
- const tier = findFlagValue(rest, '--tier') || 'smoke';
219
- const manifest = id ? getProfileManifest(id) : undefined;
220
- if (!manifest || !['smoke', 'initial', 'expanded', 'sweep'].includes(tier)) {
221
- ctx.addMessage({ role: 'system', content: 'Usage: /profile sweep <profile> [--tier smoke|initial|expanded|sweep]' });
222
- return;
223
- }
224
- const runDir = profileRunDir(manifest.id, tier);
225
- mkdirSync(runDir, { recursive: true });
226
- const results = [];
227
- for (const gate of manifest.gates.filter(gate => gate.status === 'available' && gate.command)) {
228
- const limit = tierLimit(tier, gate.benchmark);
229
- const command = replaceLimit(getProfileGateCommand(gate, normalizeTier(tier)), limit).map(shellQuote).join(' ');
230
- const result = await ctx.shell.run(command);
231
- const record = {
232
- gate: gate.name,
233
- benchmark: gate.benchmark,
234
- command,
235
- exit_code: result.exitCode,
236
- stdout_path: join(runDir, `${gate.benchmark}-stdout.txt`),
237
- stderr_path: join(runDir, `${gate.benchmark}-stderr.txt`),
238
- };
239
- writeFileSync(record.stdout_path, result.stdout || '', 'utf-8');
240
- writeFileSync(record.stderr_path, result.stderr || '', 'utf-8');
241
- results.push(record);
242
- }
243
- let optimizeCommandLine = null;
244
- if (tier !== 'smoke') {
245
- const command = buildProfileOptimizationCommand(manifest.id, { profile: tierOptimizationProfile(tier) });
246
- optimizeCommandLine = `/${command.join(' ')} --run`;
247
- await optimizeCommand.handler([...command.slice(1), '--run'], ctx);
248
- }
249
- const summaryPath = join(runDir, 'summary.json');
250
- writeJson(summaryPath, {
251
- schema_version: 1,
252
- created_at: new Date().toISOString(),
253
- profile: manifest.id,
254
- tier,
255
- provider: ctx.config.getProvider(),
256
- model: ctx.config.getModel?.() || ctx.config.get().model,
257
- optimize_command: optimizeCommandLine,
258
- results,
259
- });
260
- ctx.addMessage({
261
- role: 'system',
262
- content: [
263
- `Profile sweep complete: ${manifest.id} (${tier})`,
264
- `Run dir: ${runDir}`,
265
- `Summary: ${summaryPath}`,
266
- `Gates: ${results.length}`,
267
- optimizeCommandLine ? `Optimize: ${optimizeCommandLine}` : 'Optimize: skipped for smoke tier',
268
- ].join('\n'),
269
- });
270
- return;
44
+ try {
45
+ await switchConfigProfile(ctx, name, action === 'create');
46
+ }
47
+ catch (error) {
48
+ ctx.addMessage({ role: 'system', content: `Profile switch failed: ${error.message}` });
271
49
  }
272
- ctx.addMessage({ role: 'system', content: helpText() });
273
50
  },
274
51
  };
@@ -9,6 +9,17 @@ export declare const PROVIDER_CHOICES: ({
9
9
  description: string;
10
10
  aliases?: undefined;
11
11
  })[];
12
- export declare const ROUTER_PROVIDERS: string[];
12
+ /**
13
+ * Providers ZTC will never hand to the Router branch, either because Router
14
+ * cannot serve them over HTTP or because nesting them would be wrong.
15
+ *
16
+ * Router's own `listProviderCatalog` (GET /v1/models) is the source of truth for
17
+ * what it offers. This is deliberately an exclusion list: when Router adds a
18
+ * provider, ZTC offers it without a ZTC change. Only add an entry here to block
19
+ * one, never to enable one.
20
+ */
21
+ export declare const ROUTER_EXCLUDED_PROVIDERS: string[];
22
+ /** True when a provider can be dispatched as a Router upstream. */
23
+ export declare function canRouteThroughRouter(provider: string): boolean;
13
24
  export declare function canonicalProvider(value: string): string | undefined;
14
25
  export declare function isLocalSubscription(provider: string): boolean;
@@ -6,6 +6,7 @@ export const PROVIDER_CHOICES = [
6
6
  { id: 'gemini', label: 'Google Gemini', description: 'Direct API key', aliases: ['google'] },
7
7
  { id: 'deepseek', label: 'DeepSeek', description: 'Direct API key' },
8
8
  { id: 'kimi', label: 'Kimi (Moonshot)', description: 'Direct API key', aliases: ['moonshot'] },
9
+ { id: 'fireworks', label: 'Fireworks', description: 'Direct API key or Zerg Router', aliases: ['fireworks-ai'] },
9
10
  { id: 'inception', label: 'Inception', description: 'Direct API key' },
10
11
  { id: 'clarifai', label: 'Clarifai', description: 'Direct API key' },
11
12
  { id: 'openai_compatible', label: 'OpenAI-compatible', description: 'Local or custom endpoint', aliases: ['openai-compatible', 'local', 'ollama'] },
@@ -15,8 +16,34 @@ export const PROVIDER_CHOICES = [
15
16
  { id: 'codex_legacy', label: 'Codex Legacy', description: 'Local Codex credentials' },
16
17
  { id: 'kimi_code', label: 'Kimi Code', description: 'Local Kimi credentials', aliases: ['kimi-code'] },
17
18
  ];
18
- // These use HTTP inference through Router; subscription backends stay local.
19
- export const ROUTER_PROVIDERS = ['anthropic', 'openai', 'gemini', 'deepseek', 'kimi', 'modal'];
19
+ /**
20
+ * Providers ZTC will never hand to the Router branch, either because Router
21
+ * cannot serve them over HTTP or because nesting them would be wrong.
22
+ *
23
+ * Router's own `listProviderCatalog` (GET /v1/models) is the source of truth for
24
+ * what it offers. This is deliberately an exclusion list: when Router adds a
25
+ * provider, ZTC offers it without a ZTC change. Only add an entry here to block
26
+ * one, never to enable one.
27
+ */
28
+ export const ROUTER_EXCLUDED_PROVIDERS = [
29
+ // Local subscription logins: inference runs on this machine, not Router, and
30
+ // Router holds no credential for them.
31
+ 'claude_code',
32
+ 'claude_code_direct',
33
+ 'codex_app_server',
34
+ 'codex_legacy',
35
+ 'kimi_code',
36
+ // Routing Router through Router would nest the same gateway in itself.
37
+ 'zergrouter',
38
+ // A custom OpenAI-compatible endpoint names its own base URL and models.
39
+ 'openai_compatible',
40
+ ];
41
+ const ROUTER_EXCLUDED = new Set(ROUTER_EXCLUDED_PROVIDERS);
42
+ /** True when a provider can be dispatched as a Router upstream. */
43
+ export function canRouteThroughRouter(provider) {
44
+ const normalized = normalizeProviderId(provider);
45
+ return normalized.length > 0 && !ROUTER_EXCLUDED.has(normalized);
46
+ }
20
47
  export function canonicalProvider(value) {
21
48
  const normalized = normalizeProviderId(value);
22
49
  return PROVIDER_CHOICES.find(choice => choice.id === normalized || choice.aliases?.includes(normalized))?.id;
@@ -0,0 +1,3 @@
1
+ import type { CommandContext } from './types.js';
2
+ /** Apply a provider and optional model in one validated config transition. */
3
+ export declare function selectProviderModel(args: string[], ctx: CommandContext): Promise<void>;
@@ -0,0 +1,63 @@
1
+ import { getDefaultModelForProvider } from '../../config/defaults.js';
2
+ import { isBuildableResolvedConfig } from './config.js';
3
+ import { applyRuntimeModelSelection, getEffectiveModel } from './runtime_model.js';
4
+ import { canRouteThroughRouter, canonicalProvider } from './provider_choices.js';
5
+ import { resolveModelAndProvider } from '../../panes/model_catalog.js';
6
+ import { getDefaultModel } from '../../utils/models.js';
7
+ /** Apply a provider and optional model in one validated config transition. */
8
+ export async function selectProviderModel(args, ctx) {
9
+ const values = args.filter(Boolean);
10
+ const current = ctx.config.getProvider();
11
+ const choice = canonicalProvider(values[0]);
12
+ const routed = choice === 'zergrouter' || choice === 'modal';
13
+ // Router's catalog is the source of truth, so a Router upstream ZTC has no
14
+ // PROVIDER_CHOICES entry for still selects; the exclusion rule gates it.
15
+ const upstream = values[1] ? (canonicalProvider(values[1]) ?? values[1]) : undefined;
16
+ const provider = choice === 'zergrouter'
17
+ ? (upstream ?? (canRouteThroughRouter(current) ? current : 'anthropic'))
18
+ : choice;
19
+ if (!provider || (routed && !canRouteThroughRouter(provider))) {
20
+ ctx.addMessage({ role: 'system', content: `Unknown provider or unsupported Router provider: ${values.join(' ')}. Run /model to see the choices.` });
21
+ return;
22
+ }
23
+ if (values.length > (choice === 'zergrouter' ? 3 : 2)) {
24
+ ctx.addMessage({ role: 'system', content: 'Usage: /model <provider> [model] or /model zergrouter <upstream-provider> [model]' });
25
+ return;
26
+ }
27
+ const authMode = routed ? 'zrouter' : 'direct';
28
+ const defaultModel = provider === 'inception' ? getDefaultModel(provider) : getDefaultModelForProvider(provider);
29
+ const explicitModel = values[choice === 'zergrouter' ? 2 : 1];
30
+ const model = explicitModel || (provider === current ? getEffectiveModel(ctx)
31
+ : provider === 'openai_compatible'
32
+ ? (ctx.config.getOpenAICompatibleSettings?.().defaultModel || getDefaultModelForProvider(provider))
33
+ : resolveModelAndProvider(defaultModel, provider).model);
34
+ try {
35
+ const llm = ctx.config.resolveLLMBackendConfig({ provider, model,
36
+ config: { ...ctx.config.get(), provider, model, authMode } });
37
+ if (llm.authMode !== authMode) {
38
+ throw new Error('Routing is pinned by ZTC_LLM_AUTH_MODE/ZERG_LLM_AUTH_MODE. Unset it before switching routes.');
39
+ }
40
+ if (!isBuildableResolvedConfig(llm)) {
41
+ throw new Error(provider === 'openai_compatible'
42
+ ? 'Configure a local or custom endpoint with /config openai-compat, then select /model openai_compatible.'
43
+ : `No usable direct credentials for ${provider}. Set /config key ${provider} <api-key> or configure this provider's local login.`);
44
+ }
45
+ }
46
+ catch (error) {
47
+ ctx.addMessage({ role: 'system', content: `✗ ${error instanceof Error ? error.message : String(error)} Keeping the current selection.` });
48
+ return;
49
+ }
50
+ ctx.config.set('authMode', authMode);
51
+ ctx.config.setProvider(provider);
52
+ // CLI overrides can still expose the previous provider/model until hot-swap.
53
+ ctx.config.set('model', model);
54
+ await ctx.config.save();
55
+ applyRuntimeModelSelection(ctx, provider, model);
56
+ ctx.addMessage({ role: 'system', content: [
57
+ '✓ Model selected',
58
+ `provider: ${provider}`,
59
+ `model: ${model}`,
60
+ `route: ${routed ? 'zergrouter' : 'direct'}`,
61
+ `scope: ${ctx.config.getConfigScope?.() || 'global'}`,
62
+ ].join('\n') });
63
+ }
@@ -1,3 +1,5 @@
1
1
  import { Command, CommandContext, SettingCategory } from './types.js';
2
2
  export declare function buildSettingsCategories(ctx: CommandContext): Promise<SettingCategory[]>;
3
3
  export declare const settingsCommand: Command;
4
+ /** A separate confirmation page keeps destructive reset out of ordinary row editing. */
5
+ export declare function openFactoryResetConfirmation(ctx: CommandContext, onDone?: () => void | Promise<void>): void;