chati-dev 4.5.3 → 4.5.5

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.
package/README.md CHANGED
@@ -67,15 +67,14 @@ Open a terminal in your project directory:
67
67
  npx chati-dev init
68
68
  ```
69
69
 
70
- The wizard guides you through 4 steps:
70
+ The wizard guides you through these steps:
71
71
 
72
72
  | Step | What it asks |
73
73
  |------|-------------|
74
74
  | **Language** | English, Portuguese, Spanish, or French |
75
75
  | **Project Type** | Greenfield (new) or Brownfield (existing) |
76
- | **AI Providers** | Select any combination of Claude, Codex, and Grok |
77
- | **Primary Harness and Models** | Choose the main CLI and the exact signed-catalog models the project may use |
78
- | **Execution Profile** | Standard, or Focus AI internal with mandatory ClickUp tracking and completion metrics |
76
+ | **AI Providers** | Select any combination of Claude, Codex, and Grok; models are routed automatically within this provider pool |
77
+ | **Installation Mode** | Open public framework, or Internal with an authorized license and required company integrations |
79
78
  | **Confirm** | Review summary and proceed |
80
79
 
81
80
  ### 2. Activate the orchestrator
@@ -1,8 +1,8 @@
1
1
  # chati.dev Configuration
2
- version: "4.5.3"
2
+ version: "4.5.5"
3
3
  installed_at: "2026-02-07T10:00:00Z"
4
- updated_at: "2026-06-11T00:00:00Z"
5
- installer_version: "4.5.3"
4
+ updated_at: "2026-08-27T00:00:00Z"
5
+ installer_version: "4.5.5"
6
6
  project_type: greenfield
7
7
  language: en
8
8
  ides: [claude-code]
@@ -1,7 +1,7 @@
1
1
  # Chati.dev System Context
2
2
 
3
3
  ## Framework
4
- - **Version**: 4.5.3
4
+ - **Version**: 4.5.5
5
5
  - **Agents**: Specialized discovery and planning agents feeding provider-neutral RAIL execution and a sealed release lane
6
6
  - **Constitution**: 25 Articles + Preamble
7
7
  - **Quality**: 5 pipeline gates + 3-tier verdicts + Fault Vector Protocol (Article XXII)
@@ -24,10 +24,13 @@ installer:
24
24
  ide_selection_title: "Select your IDEs / CLIs (multiple allowed):"
25
25
  installation_mode_title: "Select the installation mode"
26
26
  installation_mode_open: "Open"
27
- installation_mode_open_hint: "Public framework without mandatory company integrations"
27
+ installation_mode_open_hint: "Public framework"
28
28
  installation_mode_internal: "Internal"
29
- installation_mode_internal_hint: "Requires an authorized license and enables ClickUp tracking plus internal policies"
29
+ installation_mode_internal_hint: "Requires an authorized license"
30
30
  installation_mode_label: "Installation mode"
31
+ back: "Back"
32
+ back_hint: "Return to the previous step"
33
+ install: "Install"
31
34
  model_routing_label: "Model routing"
32
35
  model_routing_automatic: "Automatic per task across all enabled models"
33
36
  providers_label: "Providers"
@@ -24,10 +24,13 @@ installer:
24
24
  ide_selection_title: "Seleccione sus IDEs / CLIs (multiples permitidos):"
25
25
  installation_mode_title: "Seleccione el modo de instalacion"
26
26
  installation_mode_open: "Open"
27
- installation_mode_open_hint: "Framework publico sin integraciones corporativas obligatorias"
27
+ installation_mode_open_hint: "Framework publico"
28
28
  installation_mode_internal: "Internal"
29
- installation_mode_internal_hint: "Requiere una licencia autorizada y activa el tracking en ClickUp y las politicas internas"
29
+ installation_mode_internal_hint: "Requiere una licencia autorizada"
30
30
  installation_mode_label: "Modo de instalacion"
31
+ back: "Volver"
32
+ back_hint: "Regresar al paso anterior"
33
+ install: "Instalar"
31
34
  model_routing_label: "Enrutamiento de modelos"
32
35
  model_routing_automatic: "Automatico por tarea entre todos los modelos habilitados"
33
36
  providers_label: "Proveedores"
@@ -24,10 +24,13 @@ installer:
24
24
  ide_selection_title: "Selectionnez vos IDEs / CLIs (multiples autorises):"
25
25
  installation_mode_title: "Selectionnez le mode d'installation"
26
26
  installation_mode_open: "Open"
27
- installation_mode_open_hint: "Framework public sans integrations d'entreprise obligatoires"
27
+ installation_mode_open_hint: "Framework public"
28
28
  installation_mode_internal: "Internal"
29
- installation_mode_internal_hint: "Necessite une licence autorisee et active le suivi ClickUp et les politiques internes"
29
+ installation_mode_internal_hint: "Necessite une licence autorisee"
30
30
  installation_mode_label: "Mode d'installation"
31
+ back: "Retour"
32
+ back_hint: "Revenir a l'etape precedente"
33
+ install: "Installer"
31
34
  model_routing_label: "Routage des modeles"
32
35
  model_routing_automatic: "Automatique par tache entre tous les modeles actives"
33
36
  providers_label: "Fournisseurs"
@@ -24,10 +24,13 @@ installer:
24
24
  ide_selection_title: "Selecione suas IDEs / CLIs (multiplos permitidos):"
25
25
  installation_mode_title: "Selecione o modo de instalacao"
26
26
  installation_mode_open: "Open"
27
- installation_mode_open_hint: "Framework publico sem integracoes corporativas obrigatorias"
27
+ installation_mode_open_hint: "Framework publico"
28
28
  installation_mode_internal: "Internal"
29
- installation_mode_internal_hint: "Exige uma licenca autorizada e ativa o tracking no ClickUp e as politicas internas"
29
+ installation_mode_internal_hint: "Exige uma licenca autorizada"
30
30
  installation_mode_label: "Modo de instalacao"
31
+ back: "Voltar"
32
+ back_hint: "Retornar a etapa anterior"
33
+ install: "Instalar"
31
34
  model_routing_label: "Roteamento de modelos"
32
35
  model_routing_automatic: "Automatico por task entre todos os modelos habilitados"
33
36
  providers_label: "Provedores"
@@ -1,5 +1,5 @@
1
1
  {
2
- "createdAt": "2026-08-27T07:35:51.100Z",
2
+ "createdAt": "2026-08-27T08:47:47.618Z",
3
3
  "files": {
4
4
  "agents/build/dev.md": {
5
5
  "hash": "5c8da2eee3357bbe84a6c468c76fe60a0c77357575503c3797aa94128282c565",
@@ -97,7 +97,7 @@
97
97
  "type": "md"
98
98
  },
99
99
  "config.yaml": {
100
- "hash": "49815a639e15886001dbcd8fcf778212e29b28dc14e960cbce1e6bef9cf60c0e",
100
+ "hash": "46c9b387f94306f10f2d997d4434ee2dbf1d61f33ea5dc8dd3fec4222d4600a5",
101
101
  "size": 2841,
102
102
  "type": "yaml"
103
103
  },
@@ -122,7 +122,7 @@
122
122
  "type": "md"
123
123
  },
124
124
  "context/root.md": {
125
- "hash": "51499195d5281ee92d6414f65c7847fda86cf4a4faea006403f5b1d5b6a575ad",
125
+ "hash": "833b0c98763b9d6b6418c33ca2b8333a67bb1ef177c71a9e58cc1c7b0ac2059c",
126
126
  "size": 1217,
127
127
  "type": "md"
128
128
  },
@@ -377,23 +377,23 @@
377
377
  "type": "js"
378
378
  },
379
379
  "i18n/en.yaml": {
380
- "hash": "5eb0e9dfa7baec166fc12ca8a1c0e9623c9a023c51c09455b80a6d8df1e0205f",
381
- "size": 4941,
380
+ "hash": "b5e9bf16c930a61543b17088a7267caea0c94eb32bfe0ae2e874ba15f5cdb4aa",
381
+ "size": 4929,
382
382
  "type": "yaml"
383
383
  },
384
384
  "i18n/es.yaml": {
385
- "hash": "1ed8c732c13c23e3fc2d5578526c1285a2a1ffc1d79b85d8e12365c3bcbbd882",
386
- "size": 5225,
385
+ "hash": "5407e4c6adba697da8bf2e2dbdf39f1fd32935326166852d5b488487148320f1",
386
+ "size": 5204,
387
387
  "type": "yaml"
388
388
  },
389
389
  "i18n/fr.yaml": {
390
- "hash": "ae88b914ea39d6613879aae3a3fcdf92a771d3df0f207def6cbb2cc76455cfe9",
391
- "size": 5290,
390
+ "hash": "2e3b50c56ee5f05f81c4e48fd048c679baae2280e3a9c03c589f8f82989eded4",
391
+ "size": 5276,
392
392
  "type": "yaml"
393
393
  },
394
394
  "i18n/pt.yaml": {
395
- "hash": "9e766ca8c7eba6362816bdc9d1f6162d615f1b5ca1a9b2f75071a53b7ab9f6b3",
396
- "size": 5163,
395
+ "hash": "da65aff46c8f16b1c6fd6be10b89b566253eec71fea0a42094878dcc9e4671fa",
396
+ "size": 5147,
397
397
  "type": "yaml"
398
398
  },
399
399
  "intelligence/confidence.yaml": {
@@ -1347,5 +1347,5 @@
1347
1347
  "type": "yaml"
1348
1348
  }
1349
1349
  },
1350
- "version": "4.5.3"
1350
+ "version": "4.5.5"
1351
1351
  }
@@ -1 +1 @@
1
- 6nwMwMBZREoj26RzRPuaKZahTh6Vxi/THZcqOmJpLZ+F07b60aSvXLp6vVyRDPnCw6XizfMHx81hdg0iQzcVDw==
1
+ XENxdEvoK5ATS/SOccXjzQKNM31wPtbN4KdEjNsp8aZvUGdCU2OKZPDJUh1vFC+i4ZvElD0DSFR17EPrYkmPAw==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chati-dev",
3
- "version": "4.5.3",
3
+ "version": "4.5.5",
4
4
  "description": "AI-Powered Multi-Agent Orchestration System - Structured vibe coding for Full Stack Development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "catalog": {
3
- "snapshot_id": "chati-model-catalog-2026-08-27",
3
+ "snapshot_id": "chati-model-catalog-2026-08-27-v2",
4
4
  "published_at": "2026-08-27T00:00:00.000Z",
5
5
  "expires_at": "2026-11-25T00:00:00.000Z",
6
6
  "models": [
7
7
  {
8
8
  "provider_id": "anthropic",
9
- "model_id": "fable",
9
+ "model_id": "claude-fable-5",
10
10
  "actions": [
11
11
  "discovery",
12
12
  "planning",
@@ -31,7 +31,7 @@
31
31
  },
32
32
  {
33
33
  "provider_id": "anthropic",
34
- "model_id": "claude-opus-4-8",
34
+ "model_id": "claude-opus-5",
35
35
  "actions": [
36
36
  "discovery",
37
37
  "planning",
@@ -122,7 +122,7 @@
122
122
  },
123
123
  {
124
124
  "provider_id": "openai",
125
- "model_id": "terra",
125
+ "model_id": "gpt-5.6-terra",
126
126
  "actions": [
127
127
  "discovery",
128
128
  "planning",
@@ -146,7 +146,7 @@
146
146
  },
147
147
  {
148
148
  "provider_id": "openai",
149
- "model_id": "luna",
149
+ "model_id": "gpt-5.6-luna",
150
150
  "actions": [
151
151
  "discovery",
152
152
  "planning",
@@ -163,21 +163,6 @@
163
163
  },
164
164
  "adjudication_priority": 30
165
165
  },
166
- {
167
- "provider_id": "openai",
168
- "model_id": "nano",
169
- "actions": [
170
- "discovery",
171
- "build"
172
- ],
173
- "tier": "utility",
174
- "natural_role": "classification, extraction and cleanup",
175
- "routing_priority": {
176
- "discovery": 35,
177
- "build": 40
178
- },
179
- "adjudication_priority": 5
180
- },
181
166
  {
182
167
  "provider_id": "xai",
183
168
  "model_id": "grok-4.6",
@@ -223,56 +208,8 @@
223
208
  },
224
209
  "adjudication_priority": 70,
225
210
  "highest_reasoning_configuration": "xhigh"
226
- },
227
- {
228
- "provider_id": "xai",
229
- "model_id": "grok-4.3",
230
- "actions": [
231
- "discovery",
232
- "planning",
233
- "build",
234
- "review"
235
- ],
236
- "tier": "worker",
237
- "natural_role": "cost-efficient parallel execution",
238
- "routing_priority": {
239
- "discovery": 65,
240
- "planning": 60,
241
- "build": 78,
242
- "review": 60
243
- },
244
- "adjudication_priority": 35
245
- },
246
- {
247
- "provider_id": "xai",
248
- "model_id": "grok-4.20",
249
- "actions": [
250
- "discovery",
251
- "build"
252
- ],
253
- "tier": "worker",
254
- "natural_role": "high-volume structured tasks",
255
- "routing_priority": {
256
- "discovery": 55,
257
- "build": 70
258
- },
259
- "adjudication_priority": 20
260
- },
261
- {
262
- "provider_id": "xai",
263
- "model_id": "grok-build-0.1",
264
- "actions": [
265
- "build"
266
- ],
267
- "tier": "specialist",
268
- "natural_role": "speed-first implementation in Grok Build",
269
- "routing_priority": {
270
- "build": 90,
271
- "dev": 92
272
- },
273
- "adjudication_priority": 10
274
211
  }
275
212
  ]
276
213
  },
277
- "signature": "rNiDCOiVFtrfGUrQWXw3dqR1cYAh/xZElyt0AfLXjhgykWzcw4o0NygK0ciHNXMDwhJUqhsp5fKIPpwli/YdDA=="
214
+ "signature": "trEytAQJkukerJTwpNvEQ4UO49b+Yth5jtuYNy9WXVSBvKpJCmfuv3tBWyhNgwYG9w3/oKpKDJqJQwIb/oiMAQ=="
278
215
  }
@@ -1,11 +1,11 @@
1
1
  {
2
- "snapshot_id": "chati-model-catalog-2026-08-27",
2
+ "snapshot_id": "chati-model-catalog-2026-08-27-v2",
3
3
  "published_at": "2026-08-27T00:00:00.000Z",
4
4
  "expires_at": "2026-11-25T00:00:00.000Z",
5
5
  "models": [
6
6
  {
7
7
  "provider_id": "anthropic",
8
- "model_id": "fable",
8
+ "model_id": "claude-fable-5",
9
9
  "actions": ["discovery", "planning", "build", "review", "adjudication"],
10
10
  "tier": "frontier",
11
11
  "natural_role": "architecture and design",
@@ -15,7 +15,7 @@
15
15
  },
16
16
  {
17
17
  "provider_id": "anthropic",
18
- "model_id": "claude-opus-4-8",
18
+ "model_id": "claude-opus-5",
19
19
  "actions": ["discovery", "planning", "build", "review", "adjudication"],
20
20
  "tier": "frontier",
21
21
  "natural_role": "quality rules and judgment-heavy work",
@@ -53,7 +53,7 @@
53
53
  },
54
54
  {
55
55
  "provider_id": "openai",
56
- "model_id": "terra",
56
+ "model_id": "gpt-5.6-terra",
57
57
  "actions": ["discovery", "planning", "build", "review", "adjudication"],
58
58
  "tier": "workhorse",
59
59
  "natural_role": "scoped implementation and efficient review",
@@ -63,22 +63,13 @@
63
63
  },
64
64
  {
65
65
  "provider_id": "openai",
66
- "model_id": "luna",
66
+ "model_id": "gpt-5.6-luna",
67
67
  "actions": ["discovery", "planning", "build", "review"],
68
68
  "tier": "worker",
69
69
  "natural_role": "low-cost workers and routine tasks",
70
70
  "routing_priority": { "discovery": 60, "planning": 55, "build": 72, "review": 60 },
71
71
  "adjudication_priority": 30
72
72
  },
73
- {
74
- "provider_id": "openai",
75
- "model_id": "nano",
76
- "actions": ["discovery", "build"],
77
- "tier": "utility",
78
- "natural_role": "classification, extraction and cleanup",
79
- "routing_priority": { "discovery": 35, "build": 40 },
80
- "adjudication_priority": 5
81
- },
82
73
  {
83
74
  "provider_id": "xai",
84
75
  "model_id": "grok-4.6",
@@ -98,33 +89,6 @@
98
89
  "routing_priority": { "discovery": 82, "planning": 84, "build": 98, "review": 82, "adjudication": 70, "dev": 98 },
99
90
  "adjudication_priority": 70,
100
91
  "highest_reasoning_configuration": "xhigh"
101
- },
102
- {
103
- "provider_id": "xai",
104
- "model_id": "grok-4.3",
105
- "actions": ["discovery", "planning", "build", "review"],
106
- "tier": "worker",
107
- "natural_role": "cost-efficient parallel execution",
108
- "routing_priority": { "discovery": 65, "planning": 60, "build": 78, "review": 60 },
109
- "adjudication_priority": 35
110
- },
111
- {
112
- "provider_id": "xai",
113
- "model_id": "grok-4.20",
114
- "actions": ["discovery", "build"],
115
- "tier": "worker",
116
- "natural_role": "high-volume structured tasks",
117
- "routing_priority": { "discovery": 55, "build": 70 },
118
- "adjudication_priority": 20
119
- },
120
- {
121
- "provider_id": "xai",
122
- "model_id": "grok-build-0.1",
123
- "actions": ["build"],
124
- "tier": "specialist",
125
- "natural_role": "speed-first implementation in Grok Build",
126
- "routing_priority": { "build": 90, "dev": 92 },
127
- "adjudication_priority": 10
128
92
  }
129
93
  ]
130
94
  }
@@ -1 +1 @@
1
- rNiDCOiVFtrfGUrQWXw3dqR1cYAh/xZElyt0AfLXjhgykWzcw4o0NygK0ciHNXMDwhJUqhsp5fKIPpwli/YdDA==
1
+ trEytAQJkukerJTwpNvEQ4UO49b+Yth5jtuYNy9WXVSBvKpJCmfuv3tBWyhNgwYG9w3/oKpKDJqJQwIb/oiMAQ==
@@ -1,7 +1,7 @@
1
1
  import { sha256 } from '@chati/core';
2
2
 
3
3
  const PROVIDER_BINDINGS = Object.freeze({
4
- claude: Object.freeze({ provider_id: 'anthropic', harness_id: 'claude', default_model: 'fable' }),
4
+ claude: Object.freeze({ provider_id: 'anthropic', harness_id: 'claude', default_model: 'claude-fable-5' }),
5
5
  codex: Object.freeze({ provider_id: 'openai', harness_id: 'codex', default_model: 'gpt-5.6-sol' }),
6
6
  grok: Object.freeze({ provider_id: 'xai', harness_id: 'grok', default_model: 'grok-4.6' }),
7
7
  });
@@ -141,6 +141,26 @@ export function hasLicenseEntitlement(status, entitlement) {
141
141
  return status?.status === 'VALID' && normalizeEntitlements(status.entitlements).includes(entitlement);
142
142
  }
143
143
 
144
+ /**
145
+ * Verifies that a saved key is registered for the current machine. A copied
146
+ * global license file may contain a valid key associated with another host.
147
+ */
148
+ export async function ensureCurrentMachineLicense({
149
+ key = getLicenseKey(),
150
+ validate = validateLicense,
151
+ activate = activateLicense,
152
+ } = {}) {
153
+ if (!key) return { status: 'MISSING' };
154
+ const status = await validate(null);
155
+ if (status.status === 'VALID' || status.reason !== 'Machine not authorized') return status;
156
+ const activated = await activate(null, key);
157
+ return {
158
+ ...activated,
159
+ status: 'VALID',
160
+ entitlements: normalizeEntitlements(activated.entitlements),
161
+ };
162
+ }
163
+
144
164
  /**
145
165
  * Refreshes the server-authoritative license status and requires one
146
166
  * entitlement before an internal installation can proceed.
@@ -148,7 +168,10 @@ export function hasLicenseEntitlement(status, entitlement) {
148
168
  export async function requireLicenseEntitlement(entitlement, { validate = validateLicense } = {}) {
149
169
  const status = await validate(null);
150
170
  if (!hasLicenseEntitlement(status, entitlement)) {
151
- throw Object.assign(new Error(`The active license is not authorized for ${entitlement} mode.`), {
171
+ const message = status?.status !== 'VALID' && status?.reason
172
+ ? `License validation failed: ${status.reason}.`
173
+ : `The active license is not authorized for ${entitlement} mode.`;
174
+ throw Object.assign(new Error(message), {
152
175
  code: 'LICENSE_ENTITLEMENT_REQUIRED',
153
176
  entitlement,
154
177
  });
@@ -32,10 +32,13 @@ const FALLBACK_EN = {
32
32
  editor_selection_title: 'Which editor IDEs should get rules files? (optional)',
33
33
  installation_mode_title: 'Select the installation mode',
34
34
  installation_mode_open: 'Open',
35
- installation_mode_open_hint: 'Public framework without mandatory company integrations',
35
+ installation_mode_open_hint: 'Public framework',
36
36
  installation_mode_internal: 'Internal',
37
- installation_mode_internal_hint: 'Requires an authorized license and enables ClickUp tracking plus internal policies',
37
+ installation_mode_internal_hint: 'Requires an authorized license',
38
38
  installation_mode_label: 'Installation mode',
39
+ back: 'Back',
40
+ back_hint: 'Return to the previous step',
41
+ install: 'Install',
39
42
  model_routing_label: 'Model routing',
40
43
  model_routing_automatic: 'Automatic per task across all enabled models',
41
44
  providers_label: 'Providers',
@@ -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, stepModelSelection, stepInstallationMode, stepConfirmation, stepTermsOfUse } from './questions.js';
6
+ import { WIZARD_BACK, stepLanguage, stepProjectType, stepProviderSelection, 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,7 +14,7 @@ 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, requireLicenseEntitlement } from '../license/client.js';
17
+ import { getLicenseKey, activateLicense, ensureCurrentMachineLicense, requireLicenseEntitlement } from '../license/client.js';
18
18
  import { DEFAULT_MCPS } from '../config/mcp-configs.js';
19
19
  import { IDE_CONFIGS } from '../config/ide-configs.js';
20
20
 
@@ -56,81 +56,147 @@ export async function runWizard(targetDir, options = {}) {
56
56
  p.log.success(`System check:\n${preflightFmt}`);
57
57
 
58
58
  // Step 2: Language Selection
59
- const language = options.language || await stepLanguage();
59
+ let language = options.language || await stepLanguage();
60
60
 
61
61
  // Step 2: Terms of Use (must accept to continue)
62
62
  if (options.telemetry === undefined) {
63
63
  await stepTermsOfUse();
64
64
  }
65
65
 
66
- // Step 3: Project Type
67
- const projectType = options.projectType || await stepProjectType(targetDir);
68
-
69
- // Step 3a: Provider Selection (which AI providers to use)
70
- const selectedProviders = options.providers || await stepProviderSelection();
71
-
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.
74
66
  if (options.capabilityCatalog) {
75
67
  throw Object.assign(new Error('unsigned capabilityCatalog injection is forbidden; provide signedCapabilityCatalogEnvelope'), { code: 'UNSIGNED_CATALOG_FORBIDDEN' });
76
68
  }
77
- const catalogResolution = options.signedCapabilityCatalogEnvelope
78
- ? { source: 'provided-signed', catalog: verifySignedCapabilityCatalog(options.signedCapabilityCatalogEnvelope, { publicKeyPem: options.catalogPublicKeyPem }) }
79
- : await resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch });
80
- const modelSelections = options.modelSelections || await stepModelSelection(selectedProviders, catalogResolution.catalog);
81
-
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
69
 
89
- // Step 3e: Editor Selection (which editor IDEs get rules files)
90
- const selectedEditors = options.editors || await stepEditorSelection();
91
-
92
- // Combine providers + editors into selectedIDEs for backward compatibility
93
- const providerToIDE = { claude: 'claude-code', gemini: 'gemini-cli', codex: 'codex-cli', grok: 'grok-cli' };
94
- const selectedIDEs = options.ides || [
95
- ...selectedProviders.map(p => providerToIDE[p]).filter(Boolean),
96
- ...selectedEditors,
97
- ];
98
-
99
- // CLI providers derived from selected providers
100
- const cliProviders = selectedProviders;
101
-
102
- const selectedMCPs = options.mcps || DEFAULT_MCPS;
103
-
104
- // Step 4: Confirmation
105
- const projectName = basename(targetDir);
106
- const config = {
107
- projectName,
108
- projectType,
109
- language,
110
- allProviders: cliProviders,
111
- selectedIDEs,
112
- selectedMCPs,
113
- installationMode,
114
- modelSelections,
115
- targetDir,
116
- version: VERSION,
70
+ let projectType;
71
+ let selectedProviders;
72
+ let catalogResolution;
73
+ let modelSelections;
74
+ let installationMode;
75
+ // Editor rule files are a legacy programmatic integration. The interactive
76
+ // installer configures provider CLIs only, which are the actual runtimes.
77
+ const selectedEditors = options.editors || [];
78
+ let config;
79
+ let v2InstallationInput;
80
+ let stage = 'project';
81
+
82
+ const stageOrder = ['language', 'project', 'providers', 'mode', 'confirm'];
83
+ const optionKey = {
84
+ language: 'language',
85
+ project: 'projectType',
86
+ providers: 'providers',
87
+ mode: 'installationMode',
117
88
  };
118
- const v2InstallationInput = buildWizardV2InstallationInput({
119
- projectName,
120
- selectedProviders: cliProviders,
121
- modelSelections,
122
- capabilityCatalog: catalogResolution.catalog,
123
- installationMode,
124
- });
125
- // Validate the complete v2 material and refuse a repeated init before the
126
- // legacy installer writes anything. Reconfiguration uses its own backup and
127
- // atomic replacement path below.
128
- dryRunV2(v2InstallationInput, { projectDir: targetDir });
129
- if (existsSync(join(targetDir, INSTALLATION_ARTIFACT_PATH)) && options.reconfigure !== true) {
130
- throw Object.assign(new Error('a v2 installation already exists; use --reconfigure to replace it safely'), { code: 'INSTALLATION_ALREADY_EXISTS' });
89
+ const previousInteractiveStage = (current) => {
90
+ for (let index = stageOrder.indexOf(current) - 1; index >= 0; index -= 1) {
91
+ const candidate = stageOrder[index];
92
+ if (candidate === 'language' ? options.language === undefined : options[optionKey[candidate]] === undefined) {
93
+ return candidate;
94
+ }
95
+ }
96
+ return null;
97
+ };
98
+
99
+ while (stage !== 'complete') {
100
+ if (stage === 'language') {
101
+ language = await stepLanguage();
102
+ stage = 'project';
103
+ continue;
104
+ }
105
+
106
+ if (stage === 'project') {
107
+ const value = options.projectType !== undefined
108
+ ? options.projectType
109
+ : await stepProjectType(targetDir, { allowBack: previousInteractiveStage('project') !== null });
110
+ if (value === WIZARD_BACK) {
111
+ stage = previousInteractiveStage('project');
112
+ continue;
113
+ }
114
+ projectType = value;
115
+ stage = 'providers';
116
+ continue;
117
+ }
118
+
119
+ if (stage === 'providers') {
120
+ const value = options.providers !== undefined
121
+ ? options.providers
122
+ : await stepProviderSelection({ allowBack: previousInteractiveStage('providers') !== null });
123
+ if (value === WIZARD_BACK) {
124
+ stage = previousInteractiveStage('providers');
125
+ continue;
126
+ }
127
+ selectedProviders = value;
128
+ catalogResolution ??= options.signedCapabilityCatalogEnvelope
129
+ ? { source: 'provided-signed', catalog: verifySignedCapabilityCatalog(options.signedCapabilityCatalogEnvelope, { publicKeyPem: options.catalogPublicKeyPem }) }
130
+ : await resolveCapabilityCatalog({ projectDir: targetDir, catalogUrl: options.catalogUrl, fetchImpl: options.catalogFetch });
131
+ // Provider selection defines the permitted routing pool. Model discovery
132
+ // and task-specific choice stay inside the control plane.
133
+ modelSelections = options.modelSelections;
134
+ stage = 'mode';
135
+ continue;
136
+ }
137
+
138
+ if (stage === 'mode') {
139
+ const value = options.installationMode !== undefined
140
+ ? options.installationMode
141
+ : await stepInstallationMode({ allowBack: previousInteractiveStage('mode') !== null });
142
+ if (value === WIZARD_BACK) {
143
+ stage = previousInteractiveStage('mode');
144
+ continue;
145
+ }
146
+ installationMode = value;
147
+ if (installationMode === 'internal') {
148
+ try {
149
+ await requireLicenseEntitlement('internal', { validate: options.licenseValidator });
150
+ } catch (error) {
151
+ if (options.installationMode !== undefined) throw error;
152
+ p.log.error(error.message);
153
+ continue;
154
+ }
155
+ }
156
+ stage = 'confirm';
157
+ continue;
158
+ }
159
+
160
+ const providerToIDE = { claude: 'claude-code', gemini: 'gemini-cli', codex: 'codex-cli', grok: 'grok-cli' };
161
+ const selectedIDEs = options.ides || [
162
+ ...selectedProviders.map(provider => providerToIDE[provider]).filter(Boolean),
163
+ ...selectedEditors,
164
+ ];
165
+ const cliProviders = selectedProviders;
166
+ const selectedMCPs = options.mcps || DEFAULT_MCPS;
167
+ const projectName = basename(targetDir);
168
+ config = {
169
+ projectName,
170
+ projectType,
171
+ language,
172
+ allProviders: cliProviders,
173
+ selectedIDEs,
174
+ selectedMCPs,
175
+ installationMode,
176
+ modelSelections,
177
+ targetDir,
178
+ version: VERSION,
179
+ };
180
+ v2InstallationInput = buildWizardV2InstallationInput({
181
+ projectName,
182
+ selectedProviders: cliProviders,
183
+ modelSelections,
184
+ capabilityCatalog: catalogResolution.catalog,
185
+ installationMode,
186
+ });
187
+ dryRunV2(v2InstallationInput, { projectDir: targetDir });
188
+ if (existsSync(join(targetDir, INSTALLATION_ARTIFACT_PATH)) && options.reconfigure !== true) {
189
+ throw Object.assign(new Error('a v2 installation already exists; use --reconfigure to replace it safely'), { code: 'INSTALLATION_ALREADY_EXISTS' });
190
+ }
191
+ const confirmation = await stepConfirmation(config, { allowBack: previousInteractiveStage('confirm') !== null });
192
+ if (confirmation === WIZARD_BACK) {
193
+ stage = previousInteractiveStage('confirm');
194
+ continue;
195
+ }
196
+ stage = 'complete';
131
197
  }
132
198
 
133
- await stepConfirmation(config);
199
+ const { selectedIDEs, selectedMCPs, allProviders: cliProviders } = config;
134
200
 
135
201
  // Telemetry: enabled by default (opt-out model via ToS)
136
202
  config.telemetryEnabled = options.telemetry !== undefined ? options.telemetry : true;
@@ -250,7 +316,15 @@ export async function runWizard(targetDir, options = {}) {
250
316
 
251
317
  async function runLicenseActivationStep() {
252
318
  const existingKey = getLicenseKey();
253
- if (existingKey) return; // already activated, proceed silently
319
+ if (existingKey) {
320
+ try {
321
+ const status = await ensureCurrentMachineLicense({ key: existingKey });
322
+ if (status.status === 'VALID') return;
323
+ p.log.warn(`Saved license is not valid on this machine: ${status.reason || status.status}`);
324
+ } catch (error) {
325
+ p.log.warn(`Could not verify the saved license: ${error.message}`);
326
+ }
327
+ }
254
328
 
255
329
  console.log('\n ┌─ License Activation ─────────────────────────────────────────┐');
256
330
  console.log(' │ Your license key works across ALL projects on this machine. │');
@@ -7,6 +7,12 @@ import { isProviderAvailable } from '../terminal/cli-registry.js';
7
7
  import { IDE_CONFIGS, IDE_TO_PROVIDER } from '../config/ide-configs.js';
8
8
  import { supportedV2WizardProviders, wizardModelSuggestions } from '../installer-v2/wizard-installation.js';
9
9
 
10
+ export const WIZARD_BACK = '__chati_wizard_back__';
11
+
12
+ function backOption() {
13
+ return { value: WIZARD_BACK, label: `← ${t('installer.back')}`, hint: t('installer.back_hint') };
14
+ }
15
+
10
16
  /**
11
17
  * Step 1: Language Selection (always in English)
12
18
  */
@@ -33,7 +39,7 @@ export async function stepLanguage() {
33
39
  /**
34
40
  * Step 2: Project Type
35
41
  */
36
- export async function stepProjectType(targetDir) {
42
+ export async function stepProjectType(targetDir, { allowBack = false } = {}) {
37
43
  const detection = detectProjectType(targetDir);
38
44
 
39
45
  if (detection.suggestion === 'brownfield' && detection.confidence !== 'low') {
@@ -48,6 +54,7 @@ export async function stepProjectType(targetDir) {
48
54
  options: [
49
55
  { value: 'greenfield', label: t('installer.greenfield') },
50
56
  { value: 'brownfield', label: t('installer.brownfield') },
57
+ ...(allowBack ? [backOption()] : []),
51
58
  ],
52
59
  initialValue: detection.suggestion,
53
60
  });
@@ -76,7 +83,7 @@ const PROVIDER_DISPLAY_NAMES = {
76
83
  * Asks the user which AI providers they want to use (Claude, Gemini, Codex).
77
84
  * Returns an array of provider names (e.g. ['claude', 'gemini']).
78
85
  */
79
- export async function stepProviderSelection() {
86
+ export async function stepProviderSelection({ allowBack = false } = {}) {
80
87
  const cliIDEs = Object.entries(IDE_CONFIGS).filter(([key, c]) => c.group === 'cli' && supportedV2WizardProviders.includes(IDE_TO_PROVIDER[key]));
81
88
  const options = [];
82
89
 
@@ -100,7 +107,7 @@ export async function stepProviderSelection() {
100
107
 
101
108
  const selected = await p.multiselect({
102
109
  message: `${t('installer.provider_selection_title')} ${dim('(space to select, enter to confirm)')}`,
103
- options,
110
+ options: allowBack ? [...options, backOption()] : options,
104
111
  required: true,
105
112
  });
106
113
 
@@ -109,19 +116,20 @@ export async function stepProviderSelection() {
109
116
  process.exit(0);
110
117
  }
111
118
 
112
- return selected;
119
+ return selected.includes(WIZARD_BACK) ? WIZARD_BACK : selected;
113
120
  }
114
121
 
115
122
  /**
116
123
  * Selects the operational policy profile. Focus AI internal projects require
117
124
  * ClickUp tracking; standard projects do not gain that external dependency.
118
125
  */
119
- export async function stepInstallationMode() {
126
+ export async function stepInstallationMode({ allowBack = false } = {}) {
120
127
  const mode = await p.select({
121
128
  message: t('installer.installation_mode_title'),
122
129
  options: [
123
130
  { value: 'open', label: t('installer.installation_mode_open'), hint: t('installer.installation_mode_open_hint') },
124
131
  { value: 'internal', label: t('installer.installation_mode_internal'), hint: t('installer.installation_mode_internal_hint') },
132
+ ...(allowBack ? [backOption()] : []),
125
133
  ],
126
134
  initialValue: 'open',
127
135
  });
@@ -142,7 +150,7 @@ export const stepExecutionProfile = stepInstallationMode;
142
150
  * Asks the user which editor IDEs should receive rules files.
143
151
  * Returns an array of IDE keys (e.g. ['vscode', 'cursor']).
144
152
  */
145
- export async function stepEditorSelection() {
153
+ export async function stepEditorSelection({ allowBack = false } = {}) {
146
154
  const editorIDEs = Object.entries(IDE_CONFIGS).filter(([, c]) => c.group === 'editor');
147
155
 
148
156
  if (editorIDEs.length === 0) return [];
@@ -155,7 +163,7 @@ export async function stepEditorSelection() {
155
163
 
156
164
  const selected = await p.multiselect({
157
165
  message: `${t('installer.editor_selection_title')} ${dim('(space to select, enter to confirm)')}`,
158
- options,
166
+ options: allowBack ? [...options, backOption()] : options,
159
167
  required: false,
160
168
  });
161
169
 
@@ -164,7 +172,7 @@ export async function stepEditorSelection() {
164
172
  process.exit(0);
165
173
  }
166
174
 
167
- return selected;
175
+ return selected.includes(WIZARD_BACK) ? WIZARD_BACK : selected;
168
176
  }
169
177
 
170
178
  /**
@@ -236,23 +244,19 @@ export async function stepIDESelection() {
236
244
  */
237
245
  export async function stepModelSelection(providers, capabilityCatalog) {
238
246
  const selections = {};
239
- const summary = [];
240
247
 
241
248
  for (const provider of providers) {
242
249
  const models = wizardModelSuggestions(provider, capabilityCatalog);
243
250
  selections[provider] = [...models];
244
- summary.push(`${PROVIDER_DISPLAY_NAMES[provider] || provider}: ${models.join(', ')}`);
245
251
  }
246
252
 
247
- p.note(summary.join('\n'), t('installer.model_routing_label'));
248
-
249
253
  return selections;
250
254
  }
251
255
 
252
256
  /**
253
257
  * Step 4: Confirmation
254
258
  */
255
- export async function stepConfirmation(config) {
259
+ export async function stepConfirmation(config, { allowBack = false } = {}) {
256
260
  const { projectName, projectType, language, selectedMCPs, selectedIDEs, allProviders, installationMode } = config;
257
261
 
258
262
  const langName = SUPPORTED_LANGUAGES.find(l => l.value === language)?.label || language;
@@ -274,7 +278,6 @@ export async function stepConfirmation(config) {
274
278
  [t('installer.project_label')]: `${projectName} (${projectType === 'greenfield' ? 'Greenfield' : 'Brownfield'})`,
275
279
  [t('installer.language_label')]: langName,
276
280
  [t('installer.providers_label')]: providersDisplay,
277
- [t('installer.model_routing_label')]: t('installer.model_routing_automatic'),
278
281
  [t('installer.installation_mode_label')]: installationMode === 'internal'
279
282
  ? t('installer.installation_mode_internal')
280
283
  : t('installer.installation_mode_open'),
@@ -305,10 +308,21 @@ export async function stepConfirmation(config) {
305
308
  console.log();
306
309
  console.log(` ${dim('Target:')} ${config.targetDir}`);
307
310
 
308
- const proceed = await p.confirm({
309
- message: t('installer.proceed'),
310
- initialValue: true,
311
- });
311
+ const proceed = allowBack
312
+ ? await p.select({
313
+ message: t('installer.proceed'),
314
+ options: [
315
+ { value: true, label: t('installer.install') },
316
+ backOption(),
317
+ ],
318
+ initialValue: true,
319
+ })
320
+ : await p.confirm({
321
+ message: t('installer.proceed'),
322
+ initialValue: true,
323
+ });
324
+
325
+ if (proceed === WIZARD_BACK) return WIZARD_BACK;
312
326
 
313
327
  if (p.isCancel(proceed) || !proceed) {
314
328
  p.cancel('Installation cancelled.');