longer-agent 0.1.0 → 0.1.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.
Files changed (96) hide show
  1. package/README.md +84 -43
  2. package/README.zh-CN.md +83 -42
  3. package/agent_templates/main/agent.yaml +1 -0
  4. package/dist/auth/openai-oauth.d.ts.map +1 -1
  5. package/dist/auth/openai-oauth.js +6 -10
  6. package/dist/auth/openai-oauth.js.map +1 -1
  7. package/dist/cli.d.ts +1 -2
  8. package/dist/cli.d.ts.map +1 -1
  9. package/dist/cli.js +81 -60
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands.d.ts +6 -1
  12. package/dist/commands.d.ts.map +1 -1
  13. package/dist/commands.js +115 -26
  14. package/dist/commands.js.map +1 -1
  15. package/dist/config.d.ts +19 -26
  16. package/dist/config.d.ts.map +1 -1
  17. package/dist/config.js +80 -120
  18. package/dist/config.js.map +1 -1
  19. package/dist/dotenv.d.ts +18 -0
  20. package/dist/dotenv.d.ts.map +1 -0
  21. package/dist/dotenv.js +91 -0
  22. package/dist/dotenv.js.map +1 -0
  23. package/dist/home-path.d.ts +3 -0
  24. package/dist/home-path.d.ts.map +1 -0
  25. package/dist/home-path.js +7 -0
  26. package/dist/home-path.js.map +1 -0
  27. package/dist/index.d.ts +6 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +10 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/init-wizard.d.ts +2 -3
  32. package/dist/init-wizard.d.ts.map +1 -1
  33. package/dist/init-wizard.js +349 -145
  34. package/dist/init-wizard.js.map +1 -1
  35. package/dist/managed-provider-credentials.d.ts +23 -0
  36. package/dist/managed-provider-credentials.d.ts.map +1 -0
  37. package/dist/managed-provider-credentials.js +56 -0
  38. package/dist/managed-provider-credentials.js.map +1 -0
  39. package/dist/mcp-client.d.ts.map +1 -1
  40. package/dist/mcp-client.js +2 -1
  41. package/dist/mcp-client.js.map +1 -1
  42. package/dist/mcp-config.d.ts +13 -0
  43. package/dist/mcp-config.d.ts.map +1 -0
  44. package/dist/mcp-config.js +64 -0
  45. package/dist/mcp-config.js.map +1 -0
  46. package/dist/model-discovery.d.ts +20 -0
  47. package/dist/model-discovery.d.ts.map +1 -0
  48. package/dist/model-discovery.js +47 -0
  49. package/dist/model-discovery.js.map +1 -0
  50. package/dist/model-selection.d.ts +1 -1
  51. package/dist/model-selection.d.ts.map +1 -1
  52. package/dist/model-selection.js +20 -10
  53. package/dist/model-selection.js.map +1 -1
  54. package/dist/persistence.d.ts +12 -0
  55. package/dist/persistence.d.ts.map +1 -1
  56. package/dist/persistence.js +8 -6
  57. package/dist/persistence.js.map +1 -1
  58. package/dist/provider-credential-flow.d.ts +28 -0
  59. package/dist/provider-credential-flow.d.ts.map +1 -0
  60. package/dist/provider-credential-flow.js +112 -0
  61. package/dist/provider-credential-flow.js.map +1 -0
  62. package/dist/provider-presets.d.ts +4 -0
  63. package/dist/provider-presets.d.ts.map +1 -1
  64. package/dist/provider-presets.js +26 -9
  65. package/dist/provider-presets.js.map +1 -1
  66. package/dist/providers/registry.d.ts.map +1 -1
  67. package/dist/providers/registry.js +3 -2
  68. package/dist/providers/registry.js.map +1 -1
  69. package/dist/session.d.ts +6 -15
  70. package/dist/session.d.ts.map +1 -1
  71. package/dist/session.js +34 -60
  72. package/dist/session.js.map +1 -1
  73. package/dist/settings.d.ts +8 -27
  74. package/dist/settings.d.ts.map +1 -1
  75. package/dist/settings.js +4 -108
  76. package/dist/settings.js.map +1 -1
  77. package/dist/tui/app.d.ts.map +1 -1
  78. package/dist/tui/app.js +116 -2
  79. package/dist/tui/app.js.map +1 -1
  80. package/dist/tui/components/command-prompt-panel.d.ts +22 -0
  81. package/dist/tui/components/command-prompt-panel.d.ts.map +1 -0
  82. package/dist/tui/components/command-prompt-panel.js +26 -0
  83. package/dist/tui/components/command-prompt-panel.js.map +1 -0
  84. package/dist/tui/components/logo-panel.d.ts.map +1 -1
  85. package/dist/tui/components/logo-panel.js +2 -4
  86. package/dist/tui/components/logo-panel.js.map +1 -1
  87. package/dist/update-check.d.ts +19 -0
  88. package/dist/update-check.d.ts.map +1 -0
  89. package/dist/update-check.js +116 -0
  90. package/dist/update-check.js.map +1 -0
  91. package/dist/version.d.ts +2 -0
  92. package/dist/version.d.ts.map +1 -0
  93. package/dist/version.js +7 -0
  94. package/dist/version.js.map +1 -0
  95. package/package.json +7 -3
  96. package/configExample.yaml +0 -83
@@ -2,67 +2,183 @@
2
2
  * Initialization wizard for LongerAgent.
3
3
  *
4
4
  * Provides an interactive first-run setup experience using @inquirer/prompts.
5
+ * Saves provider configuration to ~/.longeragent/tui-preferences.json.
5
6
  * Supports Ctrl+C / ESC to go back to the previous step.
6
7
  */
7
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from "node:fs";
8
9
  import { join } from "node:path";
9
- import { homedir } from "node:os";
10
10
  import { checkbox, select, input, confirm } from "@inquirer/prompts";
11
- import * as yaml from "js-yaml";
12
- import { LONGERAGENT_HOME_DIR } from "./config.js";
13
- import { PROVIDER_PRESETS, buildProviderPresetRawConfig, } from "./provider-presets.js";
11
+ import { getLongerAgentHomeDir } from "./home-path.js";
12
+ import { PROVIDER_PRESETS, } from "./provider-presets.js";
13
+ import { fetchModelsFromServer } from "./model-discovery.js";
14
+ import { setDotenvKey } from "./dotenv.js";
15
+ import { detectManagedCredentialCandidates, hasAnyManagedCredential, hasManagedCredential, isManagedProvider, } from "./managed-provider-credentials.js";
16
+ import { ensureManagedProviderCredential, } from "./provider-credential-flow.js";
14
17
  // ------------------------------------------------------------------
15
18
  // Helpers
16
19
  // ------------------------------------------------------------------
17
20
  function isUserCancel(err) {
18
21
  if (!err || typeof err !== "object")
19
22
  return false;
20
- // @inquirer/prompts throws ExitPromptError on Ctrl+C
21
23
  return err.name === "ExitPromptError" ||
22
24
  err.code === "ERR_USE_AFTER_CLOSE";
23
25
  }
24
- function generateConfigYaml(defaultModelName, models) {
25
- const payload = {
26
- default_model: defaultModelName,
27
- models: Object.fromEntries(models.map((model) => [model.configName, model.rawConfig])),
28
- };
29
- const lines = [
30
- "# LongerAgent configuration",
31
- "# Generated by 'longeragent init'",
32
- "",
33
- yaml.dump(payload, { lineWidth: -1 }).trimEnd(),
34
- "",
35
- ];
36
- return lines.join("\n");
26
+ /**
27
+ * Load existing preferences from tui-preferences.json (if present).
28
+ */
29
+ function loadExistingPreferences(homeDir) {
30
+ const prefsPath = join(homeDir, "tui-preferences.json");
31
+ if (!existsSync(prefsPath))
32
+ return null;
33
+ try {
34
+ const raw = JSON.parse(readFileSync(prefsPath, "utf-8"));
35
+ return {
36
+ version: raw.version ?? 1,
37
+ modelConfigName: raw.model_config_name ?? undefined,
38
+ modelProvider: raw.model_provider ?? undefined,
39
+ modelSelectionKey: raw.model_selection_key ?? undefined,
40
+ modelId: raw.model_id ?? undefined,
41
+ thinkingLevel: raw.thinking_level ?? "default",
42
+ cacheHitEnabled: raw.cache_hit_enabled ?? true,
43
+ accentColor: raw.accent_color ?? undefined,
44
+ disabledSkills: Array.isArray(raw.disabled_skills) ? raw.disabled_skills : undefined,
45
+ providerEnvVars: raw.provider_env_vars ?? undefined,
46
+ localProviders: raw.local_providers ?? undefined,
47
+ contextRatio: typeof raw.context_ratio === "number" ? raw.context_ratio : undefined,
48
+ };
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ /**
55
+ * Save preferences to tui-preferences.json (atomic write).
56
+ */
57
+ function savePreferences(homeDir, prefs) {
58
+ mkdirSync(homeDir, { recursive: true });
59
+ const file = join(homeDir, "tui-preferences.json");
60
+ const tmp = file + ".tmp";
61
+ writeFileSync(tmp, JSON.stringify({
62
+ version: prefs.version ?? 1,
63
+ model_config_name: prefs.modelConfigName ?? null,
64
+ model_provider: prefs.modelProvider ?? null,
65
+ model_selection_key: prefs.modelSelectionKey ?? null,
66
+ model_id: prefs.modelId ?? null,
67
+ thinking_level: prefs.thinkingLevel ?? "default",
68
+ cache_hit_enabled: prefs.cacheHitEnabled ?? true,
69
+ accent_color: prefs.accentColor ?? null,
70
+ disabled_skills: prefs.disabledSkills ?? null,
71
+ provider_env_vars: prefs.providerEnvVars ?? null,
72
+ local_providers: prefs.localProviders ?? null,
73
+ context_ratio: prefs.contextRatio ?? null,
74
+ }, null, 2));
75
+ renameSync(tmp, file);
37
76
  }
38
77
  // ------------------------------------------------------------------
39
- // Step functions (each returns result or throws on cancel)
78
+ // Step functions
40
79
  // ------------------------------------------------------------------
80
+ /**
81
+ * Build a deduplicated top-level choice list.
82
+ * Grouped presets (kimi, minimax, glm) appear once using their groupLabel.
83
+ * The value is either a preset ID or a group key prefixed with "group:".
84
+ */
85
+ function buildProviderChoices() {
86
+ const choices = [];
87
+ const seenGroups = new Set();
88
+ for (const p of PROVIDER_PRESETS) {
89
+ if (p.group) {
90
+ if (seenGroups.has(p.group))
91
+ continue;
92
+ seenGroups.add(p.group);
93
+ // Check if any preset in this group has a detected key
94
+ const groupPresets = PROVIDER_PRESETS.filter((pp) => pp.group === p.group);
95
+ const anyKeyDetected = groupPresets.some((pp) => isManagedProvider(pp.id)
96
+ ? hasManagedCredential(pp.id) || detectManagedCredentialCandidates(pp.id).length > 0
97
+ : Boolean(process.env[pp.envVar]));
98
+ const suffix = anyKeyDetected ? " ✓ key detected" : "";
99
+ choices.push({
100
+ name: `${p.groupLabel ?? p.group}${suffix}`,
101
+ value: `group:${p.group}`,
102
+ });
103
+ }
104
+ else {
105
+ const suffix = p.localServer
106
+ ? ""
107
+ : (isManagedProvider(p.id)
108
+ ? (hasManagedCredential(p.id) || detectManagedCredentialCandidates(p.id).length > 0)
109
+ : Boolean(process.env[p.envVar]))
110
+ ? " ✓ key detected"
111
+ : "";
112
+ choices.push({
113
+ name: `${p.name}${suffix}`,
114
+ value: p.id,
115
+ });
116
+ }
117
+ }
118
+ return choices;
119
+ }
41
120
  async function stepSelectProviders() {
42
- return await checkbox({
121
+ const topLevel = await checkbox({
43
122
  message: "Select providers (space to toggle, enter to confirm)",
44
- choices: PROVIDER_PRESETS.map((p) => {
45
- const envSet = process.env[p.envVar] ? " ✓ key detected" : "";
46
- return {
47
- name: `${p.name}${envSet}`,
48
- value: p.id,
49
- };
50
- }),
123
+ choices: buildProviderChoices(),
51
124
  required: true,
52
125
  });
126
+ // Expand group selections into concrete preset IDs via sub-select
127
+ const resolved = [];
128
+ for (const selection of topLevel) {
129
+ if (selection.startsWith("group:")) {
130
+ const groupKey = selection.slice("group:".length);
131
+ const members = PROVIDER_PRESETS.filter((p) => p.group === groupKey);
132
+ if (members.length === 1) {
133
+ resolved.push(members[0].id);
134
+ }
135
+ else {
136
+ const subChoice = await checkbox({
137
+ message: `${members[0].groupLabel ?? groupKey}: Select variants`,
138
+ choices: members.map((m) => ({
139
+ name: `${m.subLabel ?? m.name}${isManagedProvider(m.id)
140
+ ? (hasManagedCredential(m.id) || detectManagedCredentialCandidates(m.id).length > 0)
141
+ ? " ✓ key detected"
142
+ : ""
143
+ : process.env[m.envVar]
144
+ ? " ✓ key detected"
145
+ : ""}`,
146
+ value: m.id,
147
+ })),
148
+ required: true,
149
+ });
150
+ resolved.push(...subChoice);
151
+ }
152
+ }
153
+ else {
154
+ resolved.push(selection);
155
+ }
156
+ }
157
+ return resolved;
53
158
  }
54
- /**
55
- * Derive a config name from a model ID.
56
- * e.g. "anthropic/claude-sonnet-4.6" → "or-claude-sonnet-4.6"
57
- * "openai/gpt-5.4" → "or-gpt-5.4"
58
- */
59
- function openRouterConfigName(model) {
60
- const base = model.key.includes("/") ? model.key.split("/").pop() : model.key;
61
- return `or-${base.replace(/[^a-z0-9.-]/gi, "-").toLowerCase()}`;
159
+ function createInitPromptAdapter() {
160
+ return {
161
+ select: async (request) => {
162
+ return select({
163
+ message: request.message,
164
+ choices: request.options.map((option) => ({
165
+ name: option.description ? `${option.label} ${option.description}` : option.label,
166
+ value: option.value,
167
+ })),
168
+ });
169
+ },
170
+ secret: async (request) => {
171
+ const value = await input({
172
+ message: request.message,
173
+ });
174
+ if (!request.allowEmpty && value.trim() === "")
175
+ return "";
176
+ return value;
177
+ },
178
+ };
62
179
  }
63
180
  async function stepConfigureProvider(provider) {
64
- let apiKey;
65
- // ── OpenAI Codex (OAuth) — browser or device code login ──
181
+ // ── OpenAI Codex (OAuth) ──
66
182
  if (provider.id === "openai-codex") {
67
183
  console.log(` ${provider.name}: Logging in with your ChatGPT account...\n`);
68
184
  const { browserLogin, deviceCodeLogin, saveOAuthTokens, hasOAuthTokens } = await import("./auth/openai-oauth.js");
@@ -96,126 +212,167 @@ async function stepConfigureProvider(provider) {
96
212
  saveOAuthTokens(tokens);
97
213
  console.log("\n Login successful!\n");
98
214
  }
99
- apiKey = "oauth:openai-codex";
100
- // Model selection for Codex
101
- const selectedModelId = await select({
215
+ // Select model
216
+ const selectedModelKey = await select({
102
217
  message: `${provider.name}: Select model`,
103
218
  choices: provider.models.map((m) => ({
104
219
  name: m.label,
105
220
  value: m.key,
106
221
  })),
107
222
  });
108
- const selectedModel = provider.models.find((model) => model.key === selectedModelId);
109
- const configName = "my-openai-codex";
110
- return [{
111
- configName,
112
- rawConfig: buildProviderPresetRawConfig(provider.id, selectedModel, apiKey),
113
- }];
223
+ return {
224
+ providerId: provider.id,
225
+ envVar: "_OPENAI_CODEX_OAUTH",
226
+ defaultModelConfigName: `${provider.id}:${selectedModelKey}`,
227
+ };
228
+ }
229
+ // ── Local inference servers (oMLX, LM Studio) ──
230
+ if (provider.localServer && provider.defaultBaseUrl) {
231
+ console.log(` Default: ${provider.defaultBaseUrl} (press Enter to use)\n`);
232
+ const baseUrl = await input({
233
+ message: `${provider.name}: Server URL`,
234
+ default: provider.defaultBaseUrl,
235
+ });
236
+ console.log(` Connecting to ${baseUrl} ...`);
237
+ const discovered = await fetchModelsFromServer(baseUrl);
238
+ let modelId;
239
+ let contextLength;
240
+ if (discovered.length > 0) {
241
+ console.log(` Found ${discovered.length} model(s)\n`);
242
+ modelId = await select({
243
+ message: `${provider.name}: Select model`,
244
+ choices: discovered.map((m) => ({
245
+ name: m.contextLength
246
+ ? `${m.id} (${Math.round(m.contextLength / 1024)}K ctx)`
247
+ : m.id,
248
+ value: m.id,
249
+ })),
250
+ });
251
+ contextLength = discovered.find((m) => m.id === modelId)?.contextLength;
252
+ }
253
+ else {
254
+ console.log(" Could not reach server or no models loaded.\n" +
255
+ " Please make sure the server is running and has at least one model loaded.\n");
256
+ modelId = await input({
257
+ message: `${provider.name}: Enter model name manually`,
258
+ });
259
+ }
260
+ if (!contextLength) {
261
+ const ctxInput = await input({
262
+ message: `${provider.name}: Context length (tokens, e.g. 32768)`,
263
+ default: "32768",
264
+ });
265
+ contextLength = parseInt(ctxInput, 10) || 32768;
266
+ }
267
+ return {
268
+ providerId: provider.id,
269
+ localProvider: { baseUrl, model: modelId, contextLength },
270
+ defaultModelConfigName: `${provider.id}:${modelId}`,
271
+ };
272
+ }
273
+ if (isManagedProvider(provider.id)) {
274
+ const result = await ensureManagedProviderCredential(provider.id, createInitPromptAdapter(), { mode: "init", allowReplaceExisting: true });
275
+ if (result.status === "skipped") {
276
+ return { providerId: provider.id, skipped: true };
277
+ }
278
+ console.log(` ✓ Saved to ~/.longeragent/.env as ${result.envVar}\n`);
279
+ let defaultModelConfigName;
280
+ if (provider.models.length > 0) {
281
+ const selectedModelKey = await select({
282
+ message: `${provider.name}: Select default model`,
283
+ choices: provider.models.map((m) => ({
284
+ name: m.label,
285
+ value: m.key,
286
+ })),
287
+ });
288
+ defaultModelConfigName = `${provider.id}:${selectedModelKey}`;
289
+ }
290
+ return {
291
+ providerId: provider.id,
292
+ envVar: result.envVar,
293
+ defaultModelConfigName,
294
+ };
114
295
  }
115
296
  // ── Standard API key providers ──
116
- const envValue = process.env[provider.envVar];
297
+ const envVarName = provider.envVar;
298
+ const envValue = process.env[envVarName];
117
299
  if (envValue) {
118
300
  const choice = await select({
119
- message: `${provider.name}: ${provider.envVar} detected in environment`,
301
+ message: `${provider.name}: ${envVarName} detected in environment`,
120
302
  choices: [
121
303
  { name: "Use it", value: "use" },
122
- { name: "Enter a different key for LongerAgent", value: "new" },
304
+ { name: "Paste a different key for LongerAgent", value: "paste" },
123
305
  ],
124
306
  });
125
- if (choice === "use") {
126
- apiKey = `\${${provider.envVar}}`;
127
- }
128
- else {
129
- apiKey = await input({ message: `${provider.name}: Paste API key` });
307
+ if (choice === "paste") {
308
+ const key = await input({ message: `${provider.name}: Paste API key` });
309
+ if (key.trim()) {
310
+ setDotenvKey(envVarName, key.trim());
311
+ console.log(` ✓ Saved to ~/.longeragent/.env\n`);
312
+ }
130
313
  }
131
314
  }
132
315
  else {
133
- apiKey = await input({
134
- message: `${provider.name}: Paste API key (or Enter to set ${provider.envVar} later)`,
316
+ const key = await input({
317
+ message: `${provider.name}: Paste API key (Enter to skip, set ${envVarName} later)`,
135
318
  });
136
- if (!apiKey) {
137
- apiKey = `\${${provider.envVar}}`;
319
+ if (key.trim()) {
320
+ setDotenvKey(envVarName, key.trim());
321
+ console.log(` ✓ Saved to ~/.longeragent/.env\n`);
138
322
  }
139
323
  }
140
- // Model selection — OpenRouter supports multi-select (multi-model gateway)
141
- if (provider.id === "openrouter") {
142
- const selectedModelIds = await checkbox({
143
- message: `${provider.name}: Select models (space to toggle, enter to confirm)`,
324
+ // Model selection
325
+ let defaultModelConfigName;
326
+ if (provider.models.length > 0) {
327
+ const selectedModelKey = await select({
328
+ message: `${provider.name}: Select default model`,
144
329
  choices: provider.models.map((m) => ({
145
330
  name: m.label,
146
331
  value: m.key,
147
332
  })),
148
- required: true,
149
333
  });
150
- return selectedModelIds.map((modelId) => ({
151
- configName: openRouterConfigName(provider.models.find((model) => model.key === modelId)),
152
- rawConfig: buildProviderPresetRawConfig(provider.id, provider.models.find((model) => model.key === modelId), apiKey),
153
- }));
334
+ defaultModelConfigName = `${provider.id}:${selectedModelKey}`;
154
335
  }
155
- // Other providers single model selection
156
- const selectedModelId = await select({
157
- message: `${provider.name}: Select model`,
158
- choices: provider.models.map((m) => ({
159
- name: m.label,
160
- value: m.key,
161
- })),
162
- });
163
- const selectedModel = provider.models.find((model) => model.key === selectedModelId);
164
- const configName = `my-${provider.id.replace(/[^a-z0-9]/g, "")}`;
165
- return [{
166
- configName,
167
- rawConfig: buildProviderPresetRawConfig(provider.id, selectedModel, apiKey),
168
- }];
169
- }
170
- async function stepSelectDefault(models) {
171
- if (models.length <= 1)
172
- return models[0]?.configName ?? "";
173
- return await select({
174
- message: "Select default model",
175
- choices: models.map((m) => ({
176
- name: `${m.configName} (${m.rawConfig["provider"]}/${m.rawConfig["model"]})`,
177
- value: m.configName,
178
- })),
179
- });
336
+ return { providerId: provider.id, envVar: envVarName, defaultModelConfigName };
180
337
  }
181
338
  export async function runInitWizard() {
182
- const homeDir = join(homedir(), LONGERAGENT_HOME_DIR);
183
- const configPath = join(homeDir, "config.yaml");
339
+ const homeDir = getLongerAgentHomeDir();
340
+ const existing = loadExistingPreferences(homeDir);
341
+ const hasLegacyCloudProviders = Boolean(existing?.providerEnvVars
342
+ && Object.keys(existing.providerEnvVars).some((providerId) => !isManagedProvider(providerId)));
343
+ const hasExisting = existing &&
344
+ (hasLegacyCloudProviders
345
+ || (existing.localProviders && Object.keys(existing.localProviders).length > 0)
346
+ || hasAnyManagedCredential());
184
347
  console.log();
185
348
  console.log(" ╔══════════════════════════════════════╗");
186
349
  console.log(" ║ Welcome to LongerAgent Setup! ║");
187
350
  console.log(" ╚══════════════════════════════════════╝");
188
351
  console.log(" (Ctrl+C to go back, double Ctrl+C to quit)\n");
189
- let step = existsSync(configPath) ? 0 /* Step.CHECK_EXISTING */ : 1 /* Step.SELECT_PROVIDERS */;
352
+ let step = hasExisting ? 0 /* Step.CHECK_EXISTING */ : 1 /* Step.SELECT_PROVIDERS */;
190
353
  let selectedProviderIds = [];
191
- let models = [];
354
+ let results = [];
192
355
  let providerIdx = 0;
193
- let defaultModelName = "";
194
- // Track how many models each provider step added (for correct Ctrl+C rollback)
195
- const modelsPerStep = [];
356
+ let defaultModelConfigName = "";
196
357
  while (step !== 4 /* Step.WRITE */) {
197
358
  try {
198
359
  switch (step) {
199
360
  case 0 /* Step.CHECK_EXISTING */: {
200
- console.log(` Existing config found: ${configPath}`);
361
+ console.log(` Existing configuration found.`);
201
362
  const useExisting = await confirm({
202
363
  message: "Use existing configuration?",
203
364
  default: true,
204
365
  });
205
366
  if (useExisting) {
206
367
  console.log("\n ✓ Using existing configuration.\n");
207
- return {
208
- configPath,
209
- templatesPath: join(homeDir, "agent_templates"),
210
- skillsPath: join(homeDir, "skills"),
211
- };
368
+ return { homeDir };
212
369
  }
213
370
  step = 1 /* Step.SELECT_PROVIDERS */;
214
371
  break;
215
372
  }
216
373
  case 1 /* Step.SELECT_PROVIDERS */: {
217
374
  selectedProviderIds = await stepSelectProviders();
218
- models = [];
375
+ results = [];
219
376
  providerIdx = 0;
220
377
  step = 2 /* Step.CONFIGURE_PROVIDERS */;
221
378
  break;
@@ -227,18 +384,28 @@ export async function runInitWizard() {
227
384
  break;
228
385
  }
229
386
  console.log();
230
- const results = await stepConfigureProvider(selectedProviders[providerIdx]);
231
- models.push(...results);
232
- modelsPerStep.push(results.length);
387
+ const result = await stepConfigureProvider(selectedProviders[providerIdx]);
388
+ results.push(result);
233
389
  providerIdx++;
234
- // Stay in CONFIGURE_PROVIDERS until all done
235
390
  if (providerIdx >= selectedProviders.length) {
236
391
  step = 3 /* Step.SELECT_DEFAULT */;
237
392
  }
238
393
  break;
239
394
  }
240
395
  case 3 /* Step.SELECT_DEFAULT */: {
241
- defaultModelName = await stepSelectDefault(models);
396
+ // Collect all possible default model config names
397
+ const candidates = results
398
+ .filter((r) => r.defaultModelConfigName)
399
+ .map((r) => r.defaultModelConfigName);
400
+ if (candidates.length <= 1) {
401
+ defaultModelConfigName = candidates[0] ?? "";
402
+ }
403
+ else {
404
+ defaultModelConfigName = await select({
405
+ message: "Select default model",
406
+ choices: candidates.map((c) => ({ name: c, value: c })),
407
+ });
408
+ }
242
409
  step = 4 /* Step.WRITE */;
243
410
  break;
244
411
  }
@@ -247,14 +414,12 @@ export async function runInitWizard() {
247
414
  catch (err) {
248
415
  if (!isUserCancel(err))
249
416
  throw err;
250
- // Go back one step
251
417
  switch (step) {
252
418
  case 0 /* Step.CHECK_EXISTING */:
253
- // Can't go further back — quit
254
419
  console.log("\n Setup cancelled.\n");
255
420
  throw err;
256
421
  case 1 /* Step.SELECT_PROVIDERS */:
257
- if (existsSync(configPath)) {
422
+ if (hasExisting) {
258
423
  step = 0 /* Step.CHECK_EXISTING */;
259
424
  }
260
425
  else {
@@ -264,20 +429,15 @@ export async function runInitWizard() {
264
429
  break;
265
430
  case 2 /* Step.CONFIGURE_PROVIDERS */:
266
431
  if (providerIdx > 0) {
267
- // Go back to previous provider — pop all models it added
268
- const popCount = modelsPerStep.pop() ?? 1;
269
- models.splice(models.length - popCount, popCount);
432
+ results.pop();
270
433
  providerIdx--;
271
434
  }
272
435
  else {
273
- // Go back to provider selection
274
436
  step = 1 /* Step.SELECT_PROVIDERS */;
275
437
  }
276
438
  break;
277
439
  case 3 /* Step.SELECT_DEFAULT */: {
278
- // Go back to re-configure the last provider — pop all models it added
279
- const popCount = modelsPerStep.pop() ?? 1;
280
- models.splice(models.length - popCount, popCount);
440
+ results.pop();
281
441
  providerIdx = Math.max(0, providerIdx - 1);
282
442
  step = 2 /* Step.CONFIGURE_PROVIDERS */;
283
443
  break;
@@ -286,43 +446,87 @@ export async function runInitWizard() {
286
446
  console.log();
287
447
  }
288
448
  }
289
- // Write config
290
- mkdirSync(homeDir, { recursive: true });
291
- const configContent = generateConfigYaml(defaultModelName, models);
292
- writeFileSync(configPath, configContent, "utf-8");
293
- const templatesPath = join(homeDir, "agent_templates");
294
- const skillsPath = join(homeDir, "skills");
295
- // Prepare user override directories so users can customize templates/skills directly.
296
- mkdirSync(templatesPath, { recursive: true });
297
- mkdirSync(skillsPath, { recursive: true });
449
+ // Build preferences from results
450
+ const providerEnvVars = {};
451
+ const localProviders = {};
452
+ for (const r of results) {
453
+ if (r.localProvider) {
454
+ localProviders[r.providerId] = r.localProvider;
455
+ }
456
+ else if (r.envVar && !isManagedProvider(r.providerId)) {
457
+ providerEnvVars[r.providerId] = r.envVar;
458
+ }
459
+ }
460
+ // Parse default model config name into provider + model
461
+ let modelProvider;
462
+ let modelSelectionKey;
463
+ let modelId;
464
+ if (defaultModelConfigName) {
465
+ const colonIdx = defaultModelConfigName.indexOf(":");
466
+ if (colonIdx > 0) {
467
+ modelProvider = defaultModelConfigName.slice(0, colonIdx);
468
+ modelSelectionKey = defaultModelConfigName.slice(colonIdx + 1);
469
+ modelId = modelSelectionKey;
470
+ }
471
+ }
472
+ // Merge with existing preferences (keep accent color, thinking level, etc.)
473
+ const prefs = {
474
+ version: 1,
475
+ thinkingLevel: existing?.thinkingLevel ?? "default",
476
+ cacheHitEnabled: existing?.cacheHitEnabled ?? true,
477
+ accentColor: existing?.accentColor,
478
+ disabledSkills: existing?.disabledSkills,
479
+ providerEnvVars,
480
+ localProviders: Object.keys(localProviders).length > 0 ? localProviders : undefined,
481
+ contextRatio: existing?.contextRatio,
482
+ modelConfigName: defaultModelConfigName || undefined,
483
+ modelProvider,
484
+ modelSelectionKey,
485
+ modelId,
486
+ };
487
+ // Save preferences
488
+ savePreferences(homeDir, prefs);
489
+ // Ensure user override directories
490
+ mkdirSync(join(homeDir, "agent_templates"), { recursive: true });
491
+ mkdirSync(join(homeDir, "skills"), { recursive: true });
298
492
  // Summary
299
493
  console.log();
300
494
  console.log(" ✓ Configuration saved");
301
- console.log(` Config: ${configPath}`);
495
+ console.log(` Preferences: ${join(homeDir, "tui-preferences.json")}`);
302
496
  console.log();
303
- console.log(` Default model: ${defaultModelName}`);
304
- // Show env var status
305
- const selectedPresets = PROVIDER_PRESETS.filter((p) => models.some((m) => m.rawConfig["provider"] === p.id));
306
- const missing = [];
307
- for (const p of selectedPresets) {
308
- if (process.env[p.envVar]) {
309
- console.log(` ${p.envVar}`);
497
+ if (defaultModelConfigName) {
498
+ console.log(` Default model: ${defaultModelConfigName}`);
499
+ }
500
+ for (const r of results) {
501
+ const preset = PROVIDER_PRESETS.find((p) => p.id === r.providerId);
502
+ if (r.skipped) {
503
+ console.log(` - ${preset?.name ?? r.providerId} (skipped)`);
504
+ continue;
310
505
  }
311
- else {
312
- console.log(` ${p.envVar} (not set)`);
313
- missing.push(p.envVar);
506
+ if (r.localProvider) {
507
+ console.log(` ${preset?.name ?? r.providerId} (no API key needed)`);
508
+ }
509
+ else if (isManagedProvider(r.providerId) && r.envVar && process.env[r.envVar]) {
510
+ console.log(` ✓ ${preset?.name ?? r.providerId} (${r.envVar})`);
511
+ }
512
+ else if (r.envVar && process.env[r.envVar]) {
513
+ console.log(` ✓ ${r.envVar}`);
514
+ }
515
+ else if (r.envVar) {
516
+ console.log(` ✗ ${r.envVar} (not set)`);
314
517
  }
315
518
  }
519
+ const missing = results.filter((r) => r.envVar && !r.localProvider && !r.skipped && !process.env[r.envVar]);
316
520
  if (missing.length > 0) {
317
521
  console.log();
318
522
  console.log(" Set the missing keys before starting:");
319
- for (const v of missing) {
320
- console.log(` export ${v}="your-key-here"`);
523
+ for (const r of missing) {
524
+ console.log(` export ${r.envVar}="your-key-here"`);
321
525
  }
322
526
  }
323
527
  console.log();
324
528
  console.log(" Run 'longeragent' to start.");
325
529
  console.log();
326
- return { configPath, templatesPath, skillsPath };
530
+ return { homeDir };
327
531
  }
328
532
  //# sourceMappingURL=init-wizard.js.map