chati-dev 4.5.1 → 4.5.2

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.
@@ -30,14 +30,14 @@ const FALLBACK_EN = {
30
30
  ide_selection_title: 'Select your IDEs / CLIs (multiple allowed):',
31
31
  provider_selection_title: 'Which AI providers will you use?',
32
32
  editor_selection_title: 'Which editor IDEs should get rules files? (optional)',
33
- primary_provider_title: 'Which is your primary CLI provider?',
34
- execution_profile_title: 'Select the execution profile',
35
- execution_profile_standard: 'Standard',
36
- execution_profile_standard_hint: 'Provider-neutral local project workflow',
37
- execution_profile_internal: 'Focus AI internal',
38
- execution_profile_internal_hint: 'Requires ClickUp tracking and completion metrics',
39
- execution_profile_label: 'Execution profile',
40
- primary_provider_label: 'Primary Provider',
33
+ installation_mode_title: 'Select the installation mode',
34
+ installation_mode_open: 'Open',
35
+ installation_mode_open_hint: 'Public framework without mandatory company integrations',
36
+ installation_mode_internal: 'Internal',
37
+ installation_mode_internal_hint: 'Requires an authorized license and enables ClickUp tracking plus internal policies',
38
+ installation_mode_label: 'Installation mode',
39
+ model_routing_label: 'Model routing',
40
+ model_routing_automatic: 'Automatic per task across all enabled models',
41
41
  providers_label: 'Providers',
42
42
  quick_start_switch_hint: 'Switch CLIs anytime — your session continues from where you left off',
43
43
  created_overlays: 'Created provider overlay directories',
@@ -3,7 +3,7 @@ import { existsSync, readFileSync } from 'fs';
3
3
  import { join, dirname, basename } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { logBanner } from '../utils/logger.js';
6
- import { stepLanguage, stepProjectType, stepProviderSelection, stepEditorSelection, stepPrimaryProvider, stepModelSelection, stepExecutionProfile, stepConfirmation, stepTermsOfUse } from './questions.js';
6
+ import { stepLanguage, stepProjectType, stepProviderSelection, stepEditorSelection, stepModelSelection, stepInstallationMode, stepConfirmation, stepTermsOfUse } from './questions.js';
7
7
  import { createSpinner, showStep, showValidation, showQuickStart } from './feedback.js';
8
8
  import { installFramework } from '../installer/core.js';
9
9
  import { INSTALLATION_ARTIFACT_PATH, dryRunV2, installV2, reconfigureV2 } from '../installer-v2/index.js';
@@ -14,9 +14,9 @@ import { initCollector, track as telemetryTrack, flush as telemetryFlush } from
14
14
  import { sendEvents } from '../telemetry/sender.js';
15
15
  import { getTelemetryConfig } from '../telemetry/config.js';
16
16
  import { t } from './i18n.js';
17
- import { getLicenseKey, activateLicense } from '../license/client.js';
17
+ import { getLicenseKey, activateLicense, requireLicenseEntitlement } from '../license/client.js';
18
18
  import { DEFAULT_MCPS } from '../config/mcp-configs.js';
19
- import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
19
+ import { IDE_CONFIGS } from '../config/ide-configs.js';
20
20
 
21
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
22
22
  const VERSION = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8')).version;
@@ -69,11 +69,8 @@ export async function runWizard(targetDir, options = {}) {
69
69
  // Step 3a: Provider Selection (which AI providers to use)
70
70
  const selectedProviders = options.providers || await stepProviderSelection();
71
71
 
72
- // Step 3b: Primary provider (only if multiple)
73
- const primaryProvider = options.llmProvider || await stepPrimaryProvider(selectedProviders);
74
-
75
- // Step 3c: Models allowed for each selected provider. Never add a provider
76
- // or model that the user did not explicitly bind to this installation.
72
+ // Step 3b: Enable the full signed lineup for every selected provider. Runtime
73
+ // routing selects the right model per task instead of pinning one at install.
77
74
  if (options.capabilityCatalog) {
78
75
  throw Object.assign(new Error('unsigned capabilityCatalog injection is forbidden; provide signedCapabilityCatalogEnvelope'), { code: 'UNSIGNED_CATALOG_FORBIDDEN' });
79
76
  }
@@ -82,9 +79,12 @@ export async function runWizard(targetDir, options = {}) {
82
79
  : await resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch });
83
80
  const modelSelections = options.modelSelections || await stepModelSelection(selectedProviders, catalogResolution.catalog);
84
81
 
85
- // Step 3d: Profile controls whether ClickUp and internal completion metrics
86
- // are mandatory. It must never be silently forced on public installations.
87
- const executionProfile = options.executionProfile || await stepExecutionProfile();
82
+ // Step 3c: Internal mode is a server-authorized entitlement. It enables the
83
+ // internal policy bundle, including mandatory ClickUp tracking.
84
+ const installationMode = options.installationMode || await stepInstallationMode();
85
+ if (installationMode === 'internal') {
86
+ await requireLicenseEntitlement('internal', { validate: options.licenseValidator });
87
+ }
88
88
 
89
89
  // Step 3e: Editor Selection (which editor IDEs get rules files)
90
90
  const selectedEditors = options.editors || await stepEditorSelection();
@@ -107,21 +107,20 @@ export async function runWizard(targetDir, options = {}) {
107
107
  projectName,
108
108
  projectType,
109
109
  language,
110
- llmProvider: primaryProvider,
111
110
  allProviders: cliProviders,
112
111
  selectedIDEs,
113
112
  selectedMCPs,
114
- executionProfile,
113
+ installationMode,
114
+ modelSelections,
115
115
  targetDir,
116
116
  version: VERSION,
117
117
  };
118
118
  const v2InstallationInput = buildWizardV2InstallationInput({
119
119
  projectName,
120
120
  selectedProviders: cliProviders,
121
- primaryProvider,
122
121
  modelSelections,
123
122
  capabilityCatalog: catalogResolution.catalog,
124
- profile: executionProfile,
123
+ installationMode,
125
124
  });
126
125
  // Validate the complete v2 material and refuse a repeated init before the
127
126
  // legacy installer writes anything. Reconfiguration uses its own backup and
@@ -137,8 +136,7 @@ export async function runWizard(targetDir, options = {}) {
137
136
  config.telemetryEnabled = options.telemetry !== undefined ? options.telemetry : true;
138
137
 
139
138
  // Installation + Validation
140
- const primaryIDE = selectedIDEs.find(ide => IDE_TO_PROVIDER[ide] === primaryProvider) || selectedIDEs[0];
141
- const primaryIDEName = IDE_CONFIGS[primaryIDE]?.name || primaryIDE;
139
+ const selectedCLIIDEs = selectedIDEs.filter(ide => IDE_CONFIGS[ide]?.group === 'cli');
142
140
 
143
141
  const installStart = Date.now();
144
142
 
@@ -161,8 +159,9 @@ export async function runWizard(targetDir, options = {}) {
161
159
  'gemini-cli': 'Created .gemini/commands/ (TOML command)',
162
160
  'codex-cli': 'Created .agents/skills/chati/ (Codex skill)',
163
161
  };
164
- const commandStep = commandStepMap[primaryIDE] || t('installer.created_commands');
165
- showStep(commandStep);
162
+ for (const cliIDE of selectedCLIIDEs) {
163
+ showStep(commandStepMap[cliIDE] || t('installer.created_commands'));
164
+ }
166
165
 
167
166
  showStep(t('installer.installed_constitution'));
168
167
  showStep(t('installer.created_session'));
@@ -211,10 +210,9 @@ export async function runWizard(targetDir, options = {}) {
211
210
  const invokeCmdMap = {
212
211
  'codex-cli': '$chati',
213
212
  };
214
- const invokeCmd = invokeCmdMap[primaryIDE] || '/chati';
215
213
  const quickStartSteps = [
216
- `${t('installer.quick_start_1')} (${primaryIDEName})`,
217
- `Type: ${invokeCmd}`,
214
+ t('installer.quick_start_1'),
215
+ ...selectedCLIIDEs.map((cliIDE) => `${IDE_CONFIGS[cliIDE]?.name || cliIDE}: ${invokeCmdMap[cliIDE] || '/chati'}`),
218
216
  t('installer.quick_start_3'),
219
217
  ];
220
218
 
@@ -232,7 +230,6 @@ export async function runWizard(targetDir, options = {}) {
232
230
  editors: selectedEditors,
233
231
  projectType,
234
232
  language,
235
- primaryProvider,
236
233
  installDuration: Date.now() - installStart,
237
234
  });
238
235
  const installEvents = telemetryFlush();
@@ -5,7 +5,7 @@ import { showSummary, showChecklist } from './feedback.js';
5
5
  import { brand, dim, success } from '../utils/colors.js';
6
6
  import { isProviderAvailable } from '../terminal/cli-registry.js';
7
7
  import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
8
- import { supportedV2WizardProviders, wizardDefaultModels, wizardModelSuggestions } from '../installer-v2/wizard-installation.js';
8
+ import { supportedV2WizardProviders, wizardModelSuggestions } from '../installer-v2/wizard-installation.js';
9
9
 
10
10
  /**
11
11
  * Step 1: Language Selection (always in English)
@@ -93,7 +93,7 @@ export async function stepProviderSelection() {
93
93
  const providerName = PROVIDER_DISPLAY_NAMES[provider] || config.name;
94
94
  options.push({
95
95
  value: provider,
96
- label: `${providerName}${config.recommended ? ' (Recommended)' : ''}${suffix}`,
96
+ label: `${providerName}${suffix}`,
97
97
  hint: config.description,
98
98
  });
99
99
  }
@@ -116,22 +116,26 @@ export async function stepProviderSelection() {
116
116
  * Selects the operational policy profile. Focus AI internal projects require
117
117
  * ClickUp tracking; standard projects do not gain that external dependency.
118
118
  */
119
- export async function stepExecutionProfile() {
120
- const profile = await p.select({
121
- message: t('installer.execution_profile_title'),
119
+ export async function stepInstallationMode() {
120
+ const mode = await p.select({
121
+ message: t('installer.installation_mode_title'),
122
122
  options: [
123
- { value: 'other-supported-profile', label: t('installer.execution_profile_standard'), hint: t('installer.execution_profile_standard_hint') },
124
- { value: 'focus-ai-internal', label: t('installer.execution_profile_internal'), hint: t('installer.execution_profile_internal_hint') },
123
+ { value: 'open', label: t('installer.installation_mode_open'), hint: t('installer.installation_mode_open_hint') },
124
+ { value: 'internal', label: t('installer.installation_mode_internal'), hint: t('installer.installation_mode_internal_hint') },
125
125
  ],
126
- initialValue: 'other-supported-profile',
126
+ initialValue: 'open',
127
127
  });
128
- if (p.isCancel(profile)) {
128
+ if (p.isCancel(mode)) {
129
129
  p.cancel('Installation cancelled.');
130
130
  process.exit(0);
131
131
  }
132
- return profile;
132
+ return mode;
133
133
  }
134
134
 
135
+ // Backward-compatible programmatic alias. The interactive wizard no longer
136
+ // exposes an abstract execution profile to users.
137
+ export const stepExecutionProfile = stepInstallationMode;
138
+
135
139
  /**
136
140
  * Step 3b: Editor Selection (multi-select, optional)
137
141
  *
@@ -226,59 +230,22 @@ export async function stepIDESelection() {
226
230
  }
227
231
 
228
232
  /**
229
- * Step 3b: Primary CLI Provider Selection
230
- *
231
- * Only shown when the user selected multiple CLI-based IDEs.
232
- * Determines which provider's adapted files go in chati.dev/ (main).
233
- */
234
- export async function stepPrimaryProvider(cliProviders) {
235
- if (cliProviders.length <= 1) return cliProviders[0] || 'claude';
236
-
237
- const primary = await p.select({
238
- message: t('installer.primary_provider_title'),
239
- options: cliProviders.map(prov => ({
240
- value: prov,
241
- label: PROVIDER_DISPLAY_NAMES[prov] || prov,
242
- })),
243
- });
244
-
245
- if (p.isCancel(primary)) {
246
- p.cancel('Installation cancelled.');
247
- process.exit(0);
248
- }
249
-
250
- return primary;
251
- }
252
-
253
- /**
254
- * Records exactly which models the installation is allowed to orchestrate.
255
- * A catalog refresh can replace the suggestion, but the user remains the
256
- * authority for the final binding stored in the installation artifact.
233
+ * Enables every catalog model for each selected provider. Runtime routing
234
+ * chooses a model per task, so installation never collapses a provider to one
235
+ * supposedly universal "recommended" model.
257
236
  */
258
237
  export async function stepModelSelection(providers, capabilityCatalog) {
259
238
  const selections = {};
239
+ const summary = [];
260
240
 
261
241
  for (const provider of providers) {
262
- const suggestions = wizardModelSuggestions(provider, capabilityCatalog);
263
- const suggested = suggestions[0] || wizardDefaultModels[provider];
264
- const answer = await p.text({
265
- message: `Models for ${PROVIDER_DISPLAY_NAMES[provider] || provider} (comma-separated):`,
266
- placeholder: suggestions.join(', '),
267
- initialValue: suggested,
268
- validate: (value) => {
269
- const models = String(value || '').split(',').map((item) => item.trim()).filter(Boolean);
270
- return models.length > 0 ? undefined : 'Select at least one model.';
271
- },
272
- });
273
-
274
- if (p.isCancel(answer)) {
275
- p.cancel('Installation cancelled.');
276
- process.exit(0);
277
- }
278
-
279
- selections[provider] = String(answer).split(',').map((item) => item.trim()).filter(Boolean);
242
+ const models = wizardModelSuggestions(provider, capabilityCatalog);
243
+ selections[provider] = [...models];
244
+ summary.push(`${PROVIDER_DISPLAY_NAMES[provider] || provider}: ${models.join(', ')}`);
280
245
  }
281
246
 
247
+ p.note(summary.join('\n'), t('installer.model_routing_label'));
248
+
282
249
  return selections;
283
250
  }
284
251
 
@@ -286,16 +253,13 @@ export async function stepModelSelection(providers, capabilityCatalog) {
286
253
  * Step 4: Confirmation
287
254
  */
288
255
  export async function stepConfirmation(config) {
289
- const { projectName, projectType, language, llmProvider, selectedMCPs, selectedIDEs, allProviders, executionProfile } = config;
256
+ const { projectName, projectType, language, selectedMCPs, selectedIDEs, allProviders, installationMode } = config;
290
257
 
291
258
  const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
292
259
 
293
- // Show all providers with primary indicator
294
- const providers = allProviders || [llmProvider || 'claude'];
295
- const providersDisplay = providers.map(prov => {
296
- const name = PROVIDER_DISPLAY_NAMES[prov] || prov;
297
- return prov === llmProvider ? `${name} (Primary)` : name;
298
- }).join(', ');
260
+ // Show all enabled providers symmetrically.
261
+ const providers = allProviders || [];
262
+ const providersDisplay = providers.map(prov => PROVIDER_DISPLAY_NAMES[prov] || prov).join(', ');
299
263
 
300
264
  // Separate editors from CLI IDEs for display
301
265
  const editorIDEs = (selectedIDEs || []).filter(id => IDE_CONFIGS[id]?.group === 'editor');
@@ -310,9 +274,10 @@ export async function stepConfirmation(config) {
310
274
  [t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
311
275
  [t('installer.language_label')]: langName,
312
276
  [t('installer.providers_label')]: providersDisplay,
313
- [t('installer.execution_profile_label')]: executionProfile === 'focus-ai-internal'
314
- ? `${t('installer.execution_profile_internal')} (ClickUp required)`
315
- : t('installer.execution_profile_standard'),
277
+ [t('installer.model_routing_label')]: t('installer.model_routing_automatic'),
278
+ [t('installer.installation_mode_label')]: installationMode === 'internal'
279
+ ? t('installer.installation_mode_internal')
280
+ : t('installer.installation_mode_open'),
316
281
  };
317
282
 
318
283
  // Only show editor IDEs line if any were selected