infinicode 1.0.0 → 2.0.0

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 (157) hide show
  1. package/.opencode/plugins/home-logo-animation.tsx +120 -0
  2. package/.opencode/plugins/routing-mode-display.tsx +135 -0
  3. package/.opencode/themes/infinibot-gold.json +222 -0
  4. package/.opencode/tui.json +8 -0
  5. package/README.md +527 -75
  6. package/dist/ascii-video-animation.d.ts +2 -0
  7. package/dist/ascii-video-animation.js +243 -0
  8. package/dist/cli.js +195 -50
  9. package/dist/commands/console.d.ts +6 -0
  10. package/dist/commands/console.js +111 -0
  11. package/dist/commands/kernel-setup.d.ts +3 -0
  12. package/dist/commands/kernel-setup.js +303 -0
  13. package/dist/commands/mcp.d.ts +12 -0
  14. package/dist/commands/mcp.js +96 -0
  15. package/dist/commands/mission.d.ts +16 -0
  16. package/dist/commands/mission.js +301 -0
  17. package/dist/commands/models.d.ts +3 -1
  18. package/dist/commands/models.js +111 -55
  19. package/dist/commands/providers.d.ts +6 -0
  20. package/dist/commands/providers.js +95 -0
  21. package/dist/commands/run.d.ts +2 -1
  22. package/dist/commands/run.js +349 -59
  23. package/dist/commands/serve.d.ts +18 -0
  24. package/dist/commands/serve.js +127 -0
  25. package/dist/commands/setup.d.ts +1 -1
  26. package/dist/commands/setup.js +77 -44
  27. package/dist/commands/status.d.ts +2 -1
  28. package/dist/commands/status.js +46 -30
  29. package/dist/commands/workers.d.ts +5 -0
  30. package/dist/commands/workers.js +103 -0
  31. package/dist/kernel/autonomy/goal-loop.d.ts +49 -0
  32. package/dist/kernel/autonomy/goal-loop.js +113 -0
  33. package/dist/kernel/autonomy/index.d.ts +8 -0
  34. package/dist/kernel/autonomy/index.js +7 -0
  35. package/dist/kernel/browser/browser-controller.d.ts +57 -0
  36. package/dist/kernel/browser/browser-controller.js +175 -0
  37. package/dist/kernel/checkpoint-engine.d.ts +17 -0
  38. package/dist/kernel/checkpoint-engine.js +128 -0
  39. package/dist/kernel/command-system.d.ts +37 -0
  40. package/dist/kernel/command-system.js +239 -0
  41. package/dist/kernel/config-schema.d.ts +53 -0
  42. package/dist/kernel/config-schema.js +36 -0
  43. package/dist/kernel/event-bus.d.ts +19 -0
  44. package/dist/kernel/event-bus.js +65 -0
  45. package/dist/kernel/federation/auto-update.d.ts +36 -0
  46. package/dist/kernel/federation/auto-update.js +57 -0
  47. package/dist/kernel/federation/compute-router.d.ts +27 -0
  48. package/dist/kernel/federation/compute-router.js +44 -0
  49. package/dist/kernel/federation/config-sync.d.ts +33 -0
  50. package/dist/kernel/federation/config-sync.js +37 -0
  51. package/dist/kernel/federation/discovery.d.ts +11 -0
  52. package/dist/kernel/federation/discovery.js +44 -0
  53. package/dist/kernel/federation/event-bridge.d.ts +30 -0
  54. package/dist/kernel/federation/event-bridge.js +61 -0
  55. package/dist/kernel/federation/federation-plugin.d.ts +24 -0
  56. package/dist/kernel/federation/federation-plugin.js +35 -0
  57. package/dist/kernel/federation/federation.d.ts +126 -0
  58. package/dist/kernel/federation/federation.js +335 -0
  59. package/dist/kernel/federation/index.d.ts +31 -0
  60. package/dist/kernel/federation/index.js +23 -0
  61. package/dist/kernel/federation/moltfed-adapter.d.ts +30 -0
  62. package/dist/kernel/federation/moltfed-adapter.js +52 -0
  63. package/dist/kernel/federation/moltfed-connector.d.ts +98 -0
  64. package/dist/kernel/federation/moltfed-connector.js +193 -0
  65. package/dist/kernel/federation/node-identity.d.ts +19 -0
  66. package/dist/kernel/federation/node-identity.js +86 -0
  67. package/dist/kernel/federation/peer-mesh.d.ts +46 -0
  68. package/dist/kernel/federation/peer-mesh.js +117 -0
  69. package/dist/kernel/federation/protocol.d.ts +20 -0
  70. package/dist/kernel/federation/protocol.js +61 -0
  71. package/dist/kernel/federation/role-context.d.ts +5 -0
  72. package/dist/kernel/federation/role-context.js +45 -0
  73. package/dist/kernel/federation/telemetry.d.ts +21 -0
  74. package/dist/kernel/federation/telemetry.js +138 -0
  75. package/dist/kernel/federation/transport-http.d.ts +44 -0
  76. package/dist/kernel/federation/transport-http.js +207 -0
  77. package/dist/kernel/federation/types.d.ts +212 -0
  78. package/dist/kernel/federation/types.js +3 -0
  79. package/dist/kernel/free-providers.d.ts +28 -0
  80. package/dist/kernel/free-providers.js +162 -0
  81. package/dist/kernel/frontend-scoring.d.ts +21 -0
  82. package/dist/kernel/frontend-scoring.js +125 -0
  83. package/dist/kernel/index.d.ts +63 -0
  84. package/dist/kernel/index.js +45 -0
  85. package/dist/kernel/kernel.d.ts +75 -0
  86. package/dist/kernel/kernel.js +223 -0
  87. package/dist/kernel/logger.d.ts +18 -0
  88. package/dist/kernel/logger.js +26 -0
  89. package/dist/kernel/mcp/index.d.ts +9 -0
  90. package/dist/kernel/mcp/index.js +8 -0
  91. package/dist/kernel/mcp/mcp-server.d.ts +27 -0
  92. package/dist/kernel/mcp/mcp-server.js +178 -0
  93. package/dist/kernel/mission-engine.d.ts +42 -0
  94. package/dist/kernel/mission-engine.js +160 -0
  95. package/dist/kernel/orchestrator.d.ts +51 -0
  96. package/dist/kernel/orchestrator.js +226 -0
  97. package/dist/kernel/plugin-manager.d.ts +28 -0
  98. package/dist/kernel/plugin-manager.js +76 -0
  99. package/dist/kernel/plugins/browser-plugin.d.ts +22 -0
  100. package/dist/kernel/plugins/browser-plugin.js +34 -0
  101. package/dist/kernel/plugins/dashboard-plugin.d.ts +17 -0
  102. package/dist/kernel/plugins/dashboard-plugin.js +72 -0
  103. package/dist/kernel/plugins/discord-plugin.d.ts +11 -0
  104. package/dist/kernel/plugins/discord-plugin.js +35 -0
  105. package/dist/kernel/plugins/metrics-plugin.d.ts +33 -0
  106. package/dist/kernel/plugins/metrics-plugin.js +86 -0
  107. package/dist/kernel/plugins/search-plugin.d.ts +17 -0
  108. package/dist/kernel/plugins/search-plugin.js +20 -0
  109. package/dist/kernel/plugins/slack-plugin.d.ts +11 -0
  110. package/dist/kernel/plugins/slack-plugin.js +35 -0
  111. package/dist/kernel/plugins/telegram-plugin.d.ts +20 -0
  112. package/dist/kernel/plugins/telegram-plugin.js +122 -0
  113. package/dist/kernel/policies.d.ts +33 -0
  114. package/dist/kernel/policies.js +105 -0
  115. package/dist/kernel/provider-discovery.d.ts +41 -0
  116. package/dist/kernel/provider-discovery.js +179 -0
  117. package/dist/kernel/provider-manager.d.ts +38 -0
  118. package/dist/kernel/provider-manager.js +206 -0
  119. package/dist/kernel/provider-url.d.ts +18 -0
  120. package/dist/kernel/provider-url.js +45 -0
  121. package/dist/kernel/providers/gemini-provider.d.ts +43 -0
  122. package/dist/kernel/providers/gemini-provider.js +203 -0
  123. package/dist/kernel/providers/ollama-provider.d.ts +44 -0
  124. package/dist/kernel/providers/ollama-provider.js +241 -0
  125. package/dist/kernel/providers/openai-compatible-provider.d.ts +43 -0
  126. package/dist/kernel/providers/openai-compatible-provider.js +233 -0
  127. package/dist/kernel/recovery-manager.d.ts +38 -0
  128. package/dist/kernel/recovery-manager.js +156 -0
  129. package/dist/kernel/router.d.ts +44 -0
  130. package/dist/kernel/router.js +222 -0
  131. package/dist/kernel/sample-missions.d.ts +11 -0
  132. package/dist/kernel/sample-missions.js +132 -0
  133. package/dist/kernel/scheduler.d.ts +30 -0
  134. package/dist/kernel/scheduler.js +147 -0
  135. package/dist/kernel/sdk/harness-sdk.d.ts +37 -0
  136. package/dist/kernel/sdk/harness-sdk.js +48 -0
  137. package/dist/kernel/sdk/plugin-sdk.d.ts +27 -0
  138. package/dist/kernel/sdk/plugin-sdk.js +40 -0
  139. package/dist/kernel/search/search-controller.d.ts +32 -0
  140. package/dist/kernel/search/search-controller.js +141 -0
  141. package/dist/kernel/setup.d.ts +13 -0
  142. package/dist/kernel/setup.js +59 -0
  143. package/dist/kernel/types.d.ts +479 -0
  144. package/dist/kernel/types.js +7 -0
  145. package/dist/kernel/verification-engine.d.ts +33 -0
  146. package/dist/kernel/verification-engine.js +287 -0
  147. package/dist/kernel/worker-runtime.d.ts +66 -0
  148. package/dist/kernel/worker-runtime.js +261 -0
  149. package/dist/kernel/workers/browser-worker.d.ts +6 -0
  150. package/dist/kernel/workers/browser-worker.js +92 -0
  151. package/dist/kernel/workers/builtin-workers.d.ts +20 -0
  152. package/dist/kernel/workers/builtin-workers.js +120 -0
  153. package/dist/kernel/workers/research-worker.d.ts +5 -0
  154. package/dist/kernel/workers/research-worker.js +90 -0
  155. package/dist/prompt-ascii-video-animation.d.ts +2 -0
  156. package/dist/prompt-ascii-video-animation.js +243 -0
  157. package/package.json +43 -9
@@ -0,0 +1,301 @@
1
+ /**
2
+ * infinicode mission command — drives the OpenKernel runtime.
3
+ *
4
+ * Usage:
5
+ * infinicode mission run --goal "..." [--task "..." --cap coding ...]
6
+ * infinicode mission status <id>
7
+ * infinicode mission list
8
+ * infinicode mission resume <id>
9
+ * infinicode mission cancel <id>
10
+ */
11
+ import chalk from 'chalk';
12
+ import ora from 'ora';
13
+ import { buildKernelFromConfig } from '../kernel/setup.js';
14
+ import { SAMPLE_MISSIONS } from '../kernel/sample-missions.js';
15
+ function parseCapabilities(raw) {
16
+ if (!raw)
17
+ return ['reasoning'];
18
+ return raw.split(',').map(s => s.trim()).filter(Boolean);
19
+ }
20
+ export async function missionRun(config, opts) {
21
+ const kernel = buildKernelFromConfig(config);
22
+ let input;
23
+ if (opts.sample) {
24
+ const sample = SAMPLE_MISSIONS[opts.sample];
25
+ if (!sample) {
26
+ console.log(chalk.red(`Unknown sample: ${opts.sample}`));
27
+ console.log(chalk.dim(`Available: ${Object.keys(SAMPLE_MISSIONS).join(', ')}`));
28
+ process.exit(1);
29
+ }
30
+ input = sample;
31
+ console.log(chalk.dim(`Using sample mission: ${chalk.cyan(opts.sample)}`));
32
+ }
33
+ else {
34
+ const tasks = (opts.task ?? []).map((desc, i) => ({
35
+ description: desc,
36
+ capabilities: parseCapabilities(opts.cap?.[i] ?? opts.cap?.[0] ?? 'coding'),
37
+ input: { prompt: desc },
38
+ }));
39
+ if (tasks.length === 0) {
40
+ console.log(chalk.yellow('No --task provided; running a single goal-driven task.'));
41
+ tasks.push({
42
+ description: opts.goal,
43
+ capabilities: ['reasoning', 'coding'],
44
+ input: { prompt: opts.goal },
45
+ });
46
+ }
47
+ input = {
48
+ name: opts.name,
49
+ description: opts.description ?? opts.goal,
50
+ goal: opts.goal,
51
+ policy: opts.policy,
52
+ tasks,
53
+ };
54
+ }
55
+ const spinner = ora('Starting mission...').start();
56
+ // Renders are serialized through a queue so the routing animation for one
57
+ // task never interleaves with another task's spinner (missions run tasks in
58
+ // parallel, but their visualization plays back in event order).
59
+ let chain = Promise.resolve();
60
+ const enqueue = (fn) => {
61
+ chain = chain.then(fn).catch(() => { });
62
+ };
63
+ const line = (s) => {
64
+ if (spinner.isSpinning)
65
+ spinner.stop();
66
+ console.log(s);
67
+ };
68
+ const unsub = kernel.subscribeAll((event) => {
69
+ switch (event.type) {
70
+ case 'MISSION_STARTED':
71
+ enqueue(() => line(`${chalk.green('✔')} Mission started: ${chalk.cyan(String(event.missionId))}`));
72
+ break;
73
+ case 'ROUTING_DECISION':
74
+ enqueue(() => animateRouting(spinner, event.data));
75
+ break;
76
+ case 'TASK_STARTED': {
77
+ const model = event.data && typeof event.data === 'object' && 'modelId' in event.data
78
+ ? event.data.modelId : '?';
79
+ enqueue(() => {
80
+ spinner.start(`${chalk.dim('task ' + (event.taskId ?? '?'))} executing on ${chalk.cyan(String(model))}`);
81
+ });
82
+ break;
83
+ }
84
+ case 'TASK_COMPLETED':
85
+ enqueue(() => line(`${chalk.green('✔')} ${chalk.dim('task ' + (event.taskId ?? '?'))} completed`));
86
+ break;
87
+ case 'TASK_FAILED':
88
+ enqueue(() => line(`${chalk.red('✖')} ${chalk.dim('task ' + (event.taskId ?? '?'))} failed: ${String(event.data && typeof event.data === 'object' && 'error' in event.data ? event.data.error : '')}`));
89
+ break;
90
+ case 'MISSION_COMPLETED':
91
+ enqueue(() => line(chalk.green('✔ Mission completed')));
92
+ break;
93
+ case 'MISSION_FAILED':
94
+ enqueue(() => line(chalk.red('✖ Mission failed')));
95
+ break;
96
+ case 'PROVIDER_HEALTH':
97
+ // silent
98
+ break;
99
+ default:
100
+ if (process.env.OPENKERNEL_DEBUG === '1') {
101
+ enqueue(() => line(chalk.dim(`event ${event.type}`)));
102
+ }
103
+ }
104
+ });
105
+ try {
106
+ const mission = await kernel.execute(input);
107
+ await chain;
108
+ if (spinner.isSpinning)
109
+ spinner.stop();
110
+ unsub();
111
+ console.log(chalk.bold('\nMission Result'));
112
+ console.log(chalk.dim('-'.repeat(50)));
113
+ console.log(` ID: ${chalk.cyan(mission.id)}`);
114
+ console.log(` Status: ${formatMissionStatus(mission.status)}`);
115
+ console.log(` Tasks: ${mission.tasks.length}`);
116
+ const completed = mission.tasks.filter(t => t.status === 'COMPLETED').length;
117
+ console.log(` Completed: ${completed}/${mission.tasks.length}`);
118
+ console.log(` Checkpoints: ${mission.checkpoints.length}`);
119
+ for (const task of mission.tasks) {
120
+ console.log(chalk.dim(`\n ── Task ${task.id} ──`));
121
+ console.log(` description: ${task.description}`);
122
+ console.log(` status: ${formatTaskStatus(task.status)}`);
123
+ if (task.providerId && task.modelId) {
124
+ console.log(` model: ${chalk.cyan(task.providerId + '/' + task.modelId)}`);
125
+ }
126
+ if (task.output?.content) {
127
+ const preview = task.output.content.slice(0, 400);
128
+ console.log(chalk.dim(' output:'));
129
+ console.log(chalk.dim(' ─────────────────────────────────────'));
130
+ for (const line of preview.split('\n')) {
131
+ console.log(' ' + line);
132
+ }
133
+ if (task.output.content.length > 400) {
134
+ console.log(chalk.dim(` … (${task.output.content.length - 400} more chars)`));
135
+ }
136
+ console.log(chalk.dim(' ─────────────────────────────────────'));
137
+ }
138
+ if (task.error) {
139
+ console.log(` error: ${chalk.red(task.error)}`);
140
+ }
141
+ }
142
+ console.log();
143
+ }
144
+ catch (err) {
145
+ await chain.catch(() => { });
146
+ unsub();
147
+ if (spinner.isSpinning)
148
+ spinner.stop();
149
+ console.log(chalk.red('✖ Mission execution failed'));
150
+ console.error(err instanceof Error ? err.message : String(err));
151
+ process.exit(1);
152
+ }
153
+ finally {
154
+ kernel.destroy();
155
+ }
156
+ }
157
+ const GOLD = '#FFD86B';
158
+ function sleep(ms) {
159
+ return new Promise(r => setTimeout(r, ms));
160
+ }
161
+ function candidateLabel(c) {
162
+ return `${c.providerId}/${c.modelId}`;
163
+ }
164
+ /**
165
+ * Animate the orchestrator's routing choice: a short "slot machine" over the
166
+ * ranked candidates, landing on and highlighting the chosen model in gold.
167
+ */
168
+ async function animateRouting(spinner, data) {
169
+ if (!data || !data.chosen)
170
+ return;
171
+ if (spinner.isSpinning)
172
+ spinner.stop();
173
+ if (data.locked) {
174
+ console.log(' ' + chalk.hex(GOLD)('🔒 ') +
175
+ chalk.dim(`${data.workerType} · ${data.mode} locked → `) +
176
+ chalk.hex(GOLD).bold(candidateLabel(data.chosen)));
177
+ return;
178
+ }
179
+ const cands = data.candidates.length ? data.candidates : [data.chosen];
180
+ const interactive = !!process.stdout.isTTY && !process.env.CI;
181
+ if (interactive && cands.length > 1) {
182
+ const ticks = Math.min(16, 6 + cands.length * 2);
183
+ for (let i = 0; i < ticks; i++) {
184
+ const c = cands[i % cands.length];
185
+ process.stdout.write(`\r ${chalk.hex(GOLD)('⚡')} routing ${chalk.dim(data.workerType)} … ${chalk.cyan(candidateLabel(c))}\x1b[K`);
186
+ await sleep(50 + i * 8);
187
+ }
188
+ process.stdout.write('\r\x1b[K');
189
+ }
190
+ console.log(` ${chalk.hex(GOLD)('⚡')} ${chalk.bold('routing')} ${chalk.dim(data.workerType)} ${chalk.dim('· mode=' + data.mode)}`);
191
+ cands.forEach((c, idx) => {
192
+ const stats = `score=${c.score.toFixed(2)} bench=${c.benchmarkScore} ${c.providerType}`;
193
+ if (idx === 0) {
194
+ console.log(' ' + chalk.hex(GOLD).bold(`◆ ${candidateLabel(c)}`) + ' ' + chalk.hex(GOLD)(stats) + chalk.hex(GOLD)(' ◄ chosen'));
195
+ }
196
+ else {
197
+ console.log(' ' + chalk.dim(`${candidateLabel(c)} ${stats}`));
198
+ }
199
+ });
200
+ }
201
+ export async function missionStatus(config, missionId) {
202
+ // Status reads from checkpoint storage
203
+ const kernel = buildKernelFromConfig(config);
204
+ const mission = kernel.getMission(missionId);
205
+ if (!mission) {
206
+ console.log(chalk.yellow(`Mission ${missionId} not found in this session.`));
207
+ console.log(chalk.dim(' (Missions live in-memory for Phase 1; checkpoints persist to .openkernel/checkpoints)'));
208
+ return;
209
+ }
210
+ console.log(chalk.bold('\nMission Status'));
211
+ console.log(chalk.dim('-'.repeat(40)));
212
+ console.log(` ID: ${chalk.cyan(mission.id)}`);
213
+ console.log(` Name: ${mission.name}`);
214
+ console.log(` Status: ${formatMissionStatus(mission.status)}`);
215
+ console.log(` Goal: ${mission.goal}`);
216
+ console.log(` Tasks: ${mission.tasks.length}`);
217
+ console.log();
218
+ }
219
+ export async function missionList(config) {
220
+ const kernel = buildKernelFromConfig(config);
221
+ const missions = kernel.listMissions();
222
+ if (missions.length === 0) {
223
+ console.log(chalk.yellow('No missions in this session.'));
224
+ return;
225
+ }
226
+ console.log(chalk.bold('\nMissions'));
227
+ console.log(chalk.dim('-'.repeat(60)));
228
+ for (const m of missions) {
229
+ console.log(` ${chalk.cyan(m.id)} ${formatMissionStatus(m.status)} ${m.name}`);
230
+ }
231
+ console.log();
232
+ }
233
+ export async function missionResume(config, missionId) {
234
+ const kernel = buildKernelFromConfig(config);
235
+ const mission = kernel.getMission(missionId);
236
+ if (!mission) {
237
+ console.log(chalk.yellow(`Mission ${missionId} not found in this session.`));
238
+ console.log(chalk.dim(' (Missions are in-memory; only checkpoints persist across restarts.)'));
239
+ return;
240
+ }
241
+ if (mission.status !== 'PAUSED' && mission.status !== 'WAITING') {
242
+ console.log(chalk.yellow(`Mission ${missionId} is ${mission.status}; only PAUSED or WAITING missions can be resumed.`));
243
+ return;
244
+ }
245
+ console.log(chalk.dim(`Resuming mission ${chalk.cyan(missionId)}...`));
246
+ try {
247
+ const resumed = await kernel.resume(missionId);
248
+ console.log(chalk.green(`✓ Mission resumed — status: ${formatMissionStatus(resumed.status)}`));
249
+ for (const task of resumed.tasks) {
250
+ console.log(chalk.dim(` ── Task ${task.id} ── ${formatTaskStatus(task.status)}`));
251
+ if (task.output?.content) {
252
+ const preview = task.output.content.slice(0, 300);
253
+ console.log(chalk.dim(` ${preview}${task.output.content.length > 300 ? '…' : ''}`));
254
+ }
255
+ }
256
+ }
257
+ catch (err) {
258
+ console.log(chalk.red(`Resume failed: ${err instanceof Error ? err.message : String(err)}`));
259
+ }
260
+ finally {
261
+ kernel.destroy();
262
+ }
263
+ }
264
+ export async function missionCancel(config, missionId) {
265
+ const kernel = buildKernelFromConfig(config);
266
+ const mission = kernel.getMission(missionId);
267
+ if (!mission) {
268
+ console.log(chalk.yellow(`Mission ${missionId} not found in this session.`));
269
+ return;
270
+ }
271
+ await kernel.cancel(missionId);
272
+ console.log(chalk.green(`✓ Mission ${missionId} cancelled — status: ${formatMissionStatus(kernel.getMission(missionId)?.status ?? 'CANCELLED')}`));
273
+ kernel.destroy();
274
+ }
275
+ function formatMissionStatus(s) {
276
+ const map = {
277
+ NEW: chalk.dim('NEW'),
278
+ PLANNING: chalk.yellow('PLANNING'),
279
+ READY: chalk.blue('READY'),
280
+ RUNNING: chalk.cyan('RUNNING'),
281
+ VERIFYING: chalk.magenta('VERIFYING'),
282
+ WAITING: chalk.yellow('WAITING'),
283
+ PAUSED: chalk.yellow('PAUSED'),
284
+ FAILED: chalk.red('FAILED'),
285
+ COMPLETED: chalk.green('COMPLETED'),
286
+ CANCELLED: chalk.red('CANCELLED'),
287
+ };
288
+ return map[s] ?? s;
289
+ }
290
+ function formatTaskStatus(s) {
291
+ const map = {
292
+ PENDING: chalk.dim('PENDING'),
293
+ QUEUED: chalk.blue('QUEUED'),
294
+ RUNNING: chalk.cyan('RUNNING'),
295
+ VERIFYING: chalk.magenta('VERIFYING'),
296
+ COMPLETED: chalk.green('COMPLETED'),
297
+ FAILED: chalk.red('FAILED'),
298
+ SKIPPED: chalk.yellow('SKIPPED'),
299
+ };
300
+ return map[s] ?? s;
301
+ }
@@ -1,2 +1,4 @@
1
1
  import type Conf from 'conf';
2
- export declare function listModels(config: Conf<any>): Promise<void>;
2
+ import type { InfinicodeConfig } from '../kernel/config-schema.js';
3
+ export declare function listModels(config: Conf<InfinicodeConfig>): Promise<void>;
4
+ export declare function pullAllModels(config: Conf<InfinicodeConfig>): Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
+ import { buildKernelFromConfig } from '../kernel/setup.js';
3
4
  function formatSize(bytes) {
4
5
  const gb = bytes / (1024 ** 3);
5
6
  if (gb >= 1)
@@ -7,70 +8,125 @@ function formatSize(bytes) {
7
8
  const mb = bytes / (1024 ** 2);
8
9
  return `${mb.toFixed(0)} MB`;
9
10
  }
11
+ function partitionModels(models) {
12
+ const codingModels = [];
13
+ const otherModels = [];
14
+ for (const model of models) {
15
+ const name = model.id.toLowerCase();
16
+ if (name.includes('code') || name.includes('coder') || name.includes('stral')) {
17
+ codingModels.push(model);
18
+ }
19
+ else {
20
+ otherModels.push(model);
21
+ }
22
+ }
23
+ return { codingModels, otherModels };
24
+ }
10
25
  export async function listModels(config) {
11
- const masterUrl = config.get('masterUrl');
12
26
  const defaultModel = config.get('defaultModel');
13
- console.log(chalk.bold('\n⚡ Available Models'));
14
- console.log(chalk.dim(` Master: ${masterUrl}`));
15
- console.log(chalk.dim('─'.repeat(50)));
16
- const spinner = ora('Fetching models...').start();
27
+ const kernel = buildKernelFromConfig(config);
28
+ const spinner = ora('Discovering models across all providers...').start();
29
+ // Give health checks a moment to populate
30
+ await new Promise(r => setTimeout(r, 1500));
31
+ let models = [];
17
32
  try {
18
- const response = await fetch(`${masterUrl}/api/tags`, {
19
- signal: AbortSignal.timeout(10000)
20
- });
21
- if (!response.ok) {
22
- throw new Error(`HTTP ${response.status}`);
23
- }
24
- const data = await response.json();
25
- spinner.stop();
26
- const models = data.models || [];
27
- if (models.length === 0) {
28
- console.log(chalk.yellow('\n No models installed.'));
29
- console.log(chalk.dim('\n Recommended models for coding:'));
30
- console.log(chalk.cyan(' ollama pull qwen2.5-coder:14b'));
31
- console.log(chalk.cyan(' ollama pull deepseek-coder-v2:16b'));
32
- console.log(chalk.cyan(' ollama pull codestral:22b'));
33
- console.log();
34
- return;
35
- }
36
- // Categorize models
37
- const codingModels = [];
38
- const otherModels = [];
39
- for (const m of models) {
40
- const name = m.name.toLowerCase();
41
- if (name.includes('code') || name.includes('coder') || name.includes('stral')) {
42
- codingModels.push(m);
43
- }
44
- else {
45
- otherModels.push(m);
46
- }
47
- }
48
- // Display coding models first
33
+ models = await kernel.providerManager.getAvailableModels();
34
+ }
35
+ catch {
36
+ // continue with empty
37
+ }
38
+ if (models.length === 0) {
39
+ spinner.fail('No models available no healthy providers.');
40
+ console.log(chalk.dim(' Check that Ollama is running or a cloud provider API key is set.'));
41
+ console.log(chalk.dim(' Run: infinicode providers test'));
42
+ kernel.destroy();
43
+ return;
44
+ }
45
+ spinner.succeed(`Found ${models.length} models across healthy providers`);
46
+ const byProvider = new Map();
47
+ for (const m of models) {
48
+ const list = byProvider.get(m.providerId) ?? [];
49
+ list.push(m);
50
+ byProvider.set(m.providerId, list);
51
+ }
52
+ console.log(chalk.bold('\nAvailable Models'));
53
+ console.log(chalk.dim('-'.repeat(60)));
54
+ for (const [providerId, providerModels] of byProvider) {
55
+ console.log(chalk.bold(`\n ${chalk.cyan(providerId)} (${providerModels.length})`));
56
+ const { codingModels, otherModels } = partitionModels(providerModels);
49
57
  if (codingModels.length > 0) {
50
- console.log(chalk.bold('\n 💻 Coding Models:'));
51
- for (const m of codingModels) {
52
- const isDefault = m.name === defaultModel || m.name.startsWith(defaultModel.split(':')[0]);
53
- const marker = isDefault ? chalk.green('') : ' ';
54
- const size = formatSize(m.size);
55
- console.log(` ${marker} ${chalk.cyan(m.name.padEnd(30))} ${chalk.dim(size)}`);
58
+ console.log(chalk.dim(' Coding:'));
59
+ for (const model of codingModels.slice(0, 20)) {
60
+ const isDefault = model.id === defaultModel || model.id.startsWith(defaultModel.split(':')[0]);
61
+ const marker = isDefault ? chalk.green('*') : ' ';
62
+ const ctx = model.contextLength >= 1000 ? `${Math.round(model.contextLength / 1000)}k` : `${model.contextLength}`;
63
+ const sizeMeta = model.metadata?.size;
64
+ const sizeStr = sizeMeta ? ` ${chalk.dim(formatSize(sizeMeta))}` : '';
65
+ console.log(` ${marker} ${model.id.padEnd(42)} ${chalk.dim(`ctx=${ctx}`)}${sizeStr}`);
56
66
  }
57
67
  }
58
68
  if (otherModels.length > 0) {
59
- console.log(chalk.bold('\n 🤖 Other Models:'));
60
- for (const m of otherModels) {
61
- const isDefault = m.name === defaultModel;
62
- const marker = isDefault ? chalk.green('') : ' ';
63
- const size = formatSize(m.size);
64
- console.log(` ${marker} ${chalk.cyan(m.name.padEnd(30))} ${chalk.dim(size)}`);
69
+ console.log(chalk.dim(' Other:'));
70
+ for (const model of otherModels.slice(0, 10)) {
71
+ const isDefault = model.id === defaultModel;
72
+ const marker = isDefault ? chalk.green('*') : ' ';
73
+ const ctx = model.contextLength >= 1000 ? `${Math.round(model.contextLength / 1000)}k` : `${model.contextLength}`;
74
+ const sizeMeta = model.metadata?.size;
75
+ const sizeStr = sizeMeta ? ` ${chalk.dim(formatSize(sizeMeta))}` : '';
76
+ console.log(` ${marker} ${model.id.padEnd(42)} ${chalk.dim(`ctx=${ctx}`)}${sizeStr}`);
77
+ }
78
+ if (otherModels.length > 10) {
79
+ console.log(chalk.dim(` ... and ${otherModels.length - 10} more`));
65
80
  }
66
81
  }
67
- console.log(chalk.dim(`\n Total: ${models.length} models`));
68
- console.log(chalk.dim(` ★ = current default\n`));
69
82
  }
70
- catch (error) {
71
- spinner.fail('Failed to fetch models');
72
- console.log(chalk.red(`\n ✗ Could not reach ${masterUrl}`));
73
- console.log(chalk.dim(' Is Ollama running?\n'));
74
- process.exit(1);
83
+ console.log(chalk.dim(`\n Total: ${models.length} models from ${byProvider.size} provider(s)`));
84
+ console.log(chalk.dim(' * = current default\n'));
85
+ kernel.destroy();
86
+ }
87
+ export async function pullAllModels(config) {
88
+ const masterUrl = config.get('masterUrl');
89
+ const tailscaleMasterUrl = config.get('tailscaleMasterUrl');
90
+ const candidates = [
91
+ masterUrl,
92
+ ...(tailscaleMasterUrl && tailscaleMasterUrl !== masterUrl ? [tailscaleMasterUrl] : []),
93
+ ];
94
+ let models = [];
95
+ for (const candidate of candidates) {
96
+ try {
97
+ const response = await fetch(`${candidate}/api/tags`, {
98
+ signal: AbortSignal.timeout(10000),
99
+ });
100
+ if (!response.ok)
101
+ throw new Error(`HTTP ${response.status}`);
102
+ const data = (await response.json());
103
+ models = data.models ?? [];
104
+ break;
105
+ }
106
+ catch {
107
+ // try next
108
+ }
109
+ }
110
+ if (models.length === 0) {
111
+ console.log(chalk.red('\nCould not reach any configured Ollama master.'));
112
+ return;
113
+ }
114
+ console.log(chalk.bold('\nPulling Models'));
115
+ console.log(chalk.dim('-'.repeat(50)));
116
+ console.log(chalk.dim(` Count: ${models.length}`));
117
+ console.log();
118
+ const { execa } = await import('execa');
119
+ const pulled = [];
120
+ for (const [index, model] of models.entries()) {
121
+ const spinner = ora(`[${index + 1}/${models.length}] Pulling ${model.name}...`).start();
122
+ try {
123
+ await execa('ollama', ['pull', model.name], { stdio: 'inherit' });
124
+ pulled.push(model.name);
125
+ spinner.succeed(`Pulled ${model.name}`);
126
+ }
127
+ catch {
128
+ spinner.fail(`Failed to pull ${model.name}`);
129
+ }
75
130
  }
131
+ console.log(chalk.green(`\nPulled ${pulled.length}/${models.length} models successfully.`));
76
132
  }
@@ -0,0 +1,6 @@
1
+ import type Conf from 'conf';
2
+ import type { InfinicodeConfig } from '../kernel/config-schema.js';
3
+ export declare function providersList(config: Conf<InfinicodeConfig>): Promise<void>;
4
+ export declare function providersAdd(config: Conf<InfinicodeConfig>): Promise<void>;
5
+ export declare function providersRemove(config: Conf<InfinicodeConfig>, id: string): Promise<void>;
6
+ export declare function providersTest(config: Conf<InfinicodeConfig>, id?: string): Promise<void>;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * infinicode providers — view + edit cloud provider credentials.
3
+ *
4
+ * infinicode providers list configured providers
5
+ * infinicode providers add add a new cloud provider (interactive)
6
+ * infinicode providers remove <id> remove a provider
7
+ * infinicode providers test [id] test connection to one or all providers
8
+ */
9
+ import chalk from 'chalk';
10
+ import inquirer from 'inquirer';
11
+ import { FREE_PROVIDERS, getProviderPreset, toCloudProviderConfig } from '../kernel/free-providers.js';
12
+ import { testOllamaProvider, testCloudProvider, testProviderInteractive } from '../kernel/provider-discovery.js';
13
+ export async function providersList(config) {
14
+ const cloud = config.get('cloudProviders') ?? [];
15
+ console.log(chalk.bold('\nConfigured Providers'));
16
+ console.log(chalk.dim('-'.repeat(60)));
17
+ // Ollama
18
+ const ollamaURL = config.get('masterUrl');
19
+ console.log(` ${chalk.cyan('ollama')} ${ollamaURL}`);
20
+ if (config.get('tailscaleMasterUrl')) {
21
+ console.log(chalk.dim(` tailscale fallback: ${config.get('tailscaleMasterUrl')}`));
22
+ }
23
+ if (cloud.length === 0) {
24
+ console.log(chalk.dim('\n No cloud providers configured.'));
25
+ }
26
+ else {
27
+ console.log();
28
+ for (const p of cloud) {
29
+ const status = p.enabled ? chalk.green('✓') : chalk.yellow('○');
30
+ console.log(` ${status} ${chalk.cyan(p.id.padEnd(12))} ${p.name}`);
31
+ console.log(chalk.dim(` ${p.baseURL}`));
32
+ }
33
+ }
34
+ console.log(chalk.dim(`\n Run ${chalk.cyan('infinicode providers add')} to add a cloud provider.`));
35
+ console.log(chalk.dim(` Run ${chalk.cyan('infinicode providers test')} to test connections.\n`));
36
+ }
37
+ export async function providersAdd(config) {
38
+ const existing = new Set((config.get('cloudProviders') ?? []).map(p => p.id));
39
+ const choices = FREE_PROVIDERS.map(p => ({
40
+ name: `${p.name} — ${p.notes ?? ''}${existing.has(p.id) ? chalk.dim(' (already configured)') : ''}`,
41
+ value: p.id,
42
+ short: p.name,
43
+ }));
44
+ const { presetId } = await inquirer.prompt([
45
+ {
46
+ type: 'list',
47
+ name: 'presetId',
48
+ message: 'Which cloud provider?',
49
+ choices,
50
+ pageSize: 10,
51
+ },
52
+ ]);
53
+ const preset = getProviderPreset(presetId);
54
+ if (!preset)
55
+ return;
56
+ console.log(chalk.dim(`\n Get a free API key at: ${chalk.cyan(preset.keyUrl)}`));
57
+ const { apiKey } = await inquirer.prompt([
58
+ {
59
+ type: 'password',
60
+ name: 'apiKey',
61
+ message: `API key for ${preset.name}:`,
62
+ mask: '*',
63
+ },
64
+ ]);
65
+ const cfg = toCloudProviderConfig(preset, apiKey);
66
+ const result = await testProviderInteractive(preset.name, () => testCloudProvider(cfg));
67
+ cfg.enabled = result.ok;
68
+ const cloud = config.get('cloudProviders') ?? [];
69
+ const filtered = cloud.filter(p => p.id !== presetId);
70
+ filtered.push(cfg);
71
+ config.set('cloudProviders', filtered);
72
+ console.log(chalk.green(`\n ✓ ${preset.name} ${result.ok ? 'added + enabled' : 'added (disabled — test failed, set API key later with `infinicode providers add`)'}.`));
73
+ }
74
+ export async function providersRemove(config, id) {
75
+ const cloud = config.get('cloudProviders') ?? [];
76
+ const next = cloud.filter(p => p.id !== id);
77
+ if (next.length === cloud.length) {
78
+ console.log(chalk.yellow(`No provider named "${id}" configured.`));
79
+ return;
80
+ }
81
+ config.set('cloudProviders', next);
82
+ console.log(chalk.green(`✓ Removed ${id}.`));
83
+ }
84
+ export async function providersTest(config, id) {
85
+ // Test Ollama
86
+ if (!id || id === 'ollama') {
87
+ await testProviderInteractive('Ollama', () => testOllamaProvider(config.get('masterUrl'), config.get('tailscaleMasterUrl')));
88
+ }
89
+ const cloud = config.get('cloudProviders') ?? [];
90
+ for (const p of cloud) {
91
+ if (id && id !== p.id)
92
+ continue;
93
+ await testProviderInteractive(p.name, () => testCloudProvider(p));
94
+ }
95
+ }
@@ -1,2 +1,3 @@
1
1
  import type Conf from 'conf';
2
- export declare function runAgent(masterUrl: string, model: string, useTui: boolean, config: Conf<any>): Promise<void>;
2
+ import type { InfinicodeConfig } from '../kernel/config-schema.js';
3
+ export declare function runAgent(_masterUrl: string, _model: string, _useTui: boolean, config: Conf<InfinicodeConfig>): Promise<void>;