infinicode 2.2.1 → 2.2.3

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/dist/cli.js CHANGED
@@ -14,8 +14,9 @@ import { providersList, providersAdd, providersRemove, providersTest } from './c
14
14
  import { consoleRepl, consoleRun } from './commands/console.js';
15
15
  import { serve } from './commands/serve.js';
16
16
  import { mcpCommand } from './commands/mcp.js';
17
+ import { installTui } from './commands/install-tui.js';
17
18
  import { DEFAULT_CONFIG } from './kernel/config-schema.js';
18
- const VERSION = '2.2.1';
19
+ const VERSION = '2.2.3';
19
20
  const config = new Conf({
20
21
  projectName: 'infinicode',
21
22
  defaults: DEFAULT_CONFIG,
@@ -296,6 +297,16 @@ program
296
297
  .action(async (opts) => {
297
298
  await serve(config, opts);
298
299
  });
300
+ // ── infinicode: install the TUI binary into this install ───────────────────
301
+ program
302
+ .command('install-tui')
303
+ .description('Download/copy the TUI binary into this install so bare `infinicode` can launch it')
304
+ .option('--from <path>', 'copy the TUI binary from a local path')
305
+ .option('--url <url>', 'download the TUI binary from a URL (or set INFINICODE_TUI_URL)')
306
+ .option('--force', 'reinstall even if a TUI binary is already present')
307
+ .action(async (opts) => {
308
+ await installTui({ ...opts, url: opts.url ?? process.env.INFINICODE_TUI_URL });
309
+ });
299
310
  // ── OpenKernel: MCP control server (stdio) ─────────────────────────────────
300
311
  program
301
312
  .command('mcp')
@@ -0,0 +1,6 @@
1
+ export interface InstallTuiOptions {
2
+ from?: string;
3
+ url?: string;
4
+ force?: boolean;
5
+ }
6
+ export declare function installTui(opts: InstallTuiOptions): Promise<void>;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * infinicode install-tui — fetch the TUI binary into this install's bin/.
3
+ *
4
+ * The npm package ships the kernel + device-mesh CLI only (the TUI binary is
5
+ * large + platform-specific, so it is not published). This command puts the
6
+ * right TUI binary in place so bare `infinicode` can launch it:
7
+ *
8
+ * infinicode install-tui # auto-find a local copy (repo / OneDrive)
9
+ * infinicode install-tui --from C:\path\tui.exe
10
+ * infinicode install-tui --url https://host/infinicode-tui.exe
11
+ *
12
+ * It never fails the way the old launcher did (no loop) — it either installs the
13
+ * binary or prints exactly how to provide one.
14
+ */
15
+ import { existsSync, copyFileSync, mkdirSync, renameSync, chmodSync, statSync, createWriteStream } from 'node:fs';
16
+ import { dirname, join, resolve } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { homedir } from 'node:os';
19
+ import { Readable } from 'node:stream';
20
+ import { pipeline } from 'node:stream/promises';
21
+ import chalk from 'chalk';
22
+ const __dirname = dirname(fileURLToPath(import.meta.url));
23
+ /** Platform-specific TUI binary filename. */
24
+ function tuiName() {
25
+ return process.platform === 'win32' ? 'infinicode-tui.exe' : 'infinicode-tui';
26
+ }
27
+ /** This install's bin/ dir (<pkg>/dist/commands → <pkg>/bin). */
28
+ function targetBinDir() {
29
+ return resolve(__dirname, '..', '..', 'bin');
30
+ }
31
+ /** Common local locations to copy an existing TUI binary from. */
32
+ function localCandidates(name, explicit) {
33
+ const home = homedir();
34
+ return [
35
+ explicit,
36
+ join(process.cwd(), 'bin', name),
37
+ join(home, 'OneDrive', 'Desktop', 'infinicode-main', 'bin', name),
38
+ join(home, 'Desktop', 'infinicode-main', 'bin', name),
39
+ ].filter((p) => Boolean(p));
40
+ }
41
+ async function download(url, dest) {
42
+ console.log(chalk.dim(` downloading ${url} …`));
43
+ const res = await fetch(url, { redirect: 'follow' });
44
+ if (!res.ok || !res.body)
45
+ throw new Error(`download failed: HTTP ${res.status}`);
46
+ const tmp = `${dest}.download`;
47
+ await pipeline(Readable.fromWeb(res.body), createWriteStream(tmp));
48
+ renameSync(tmp, dest);
49
+ }
50
+ function finalize(dest) {
51
+ if (process.platform !== 'win32') {
52
+ try {
53
+ chmodSync(dest, 0o755);
54
+ }
55
+ catch {
56
+ /* best-effort */
57
+ }
58
+ }
59
+ const mb = (statSync(dest).size / 1024 / 1024).toFixed(1);
60
+ console.log(chalk.green(` ✓ TUI installed → ${dest} (${mb} MB)`));
61
+ console.log(chalk.dim(' run it with: ') + chalk.cyan('infinicode'));
62
+ }
63
+ export async function installTui(opts) {
64
+ const name = tuiName();
65
+ const binDir = targetBinDir();
66
+ const dest = join(binDir, name);
67
+ console.log(chalk.bold('\n infinicode · install TUI'));
68
+ console.log(chalk.dim(' ' + '-'.repeat(40)));
69
+ if (existsSync(dest) && !opts.force) {
70
+ console.log(chalk.green(` ✓ already installed → ${dest}`));
71
+ console.log(chalk.dim(' reinstall with --force'));
72
+ return;
73
+ }
74
+ mkdirSync(binDir, { recursive: true });
75
+ // 1. Explicit URL (hosted release / mirror).
76
+ if (opts.url) {
77
+ await download(opts.url, dest);
78
+ finalize(dest);
79
+ return;
80
+ }
81
+ // 2. Copy from an explicit --from path or a known local/OneDrive location.
82
+ const src = localCandidates(name, opts.from).find(p => existsSync(p));
83
+ if (src) {
84
+ copyFileSync(src, dest);
85
+ console.log(chalk.dim(` copied from ${src}`));
86
+ finalize(dest);
87
+ return;
88
+ }
89
+ // 3. Nothing found — tell the user exactly how to provide one.
90
+ console.log(chalk.yellow(` no ${name} found to install.`));
91
+ console.log(chalk.dim(' provide one of:'));
92
+ console.log(chalk.cyan(` infinicode install-tui --from "<path to ${name}>"`));
93
+ console.log(chalk.cyan(` infinicode install-tui --url "https://…/${name}"`));
94
+ console.log(chalk.dim(' (or set env INFINICODE_TUI_URL and re-run)'));
95
+ process.exit(1);
96
+ }
@@ -26,10 +26,21 @@ import { SilentLogger } from '../kernel/index.js';
26
26
  import { openAICompatBaseURL } from '../kernel/provider-url.js';
27
27
  const __dirname = dirname(fileURLToPath(import.meta.url));
28
28
  const PROMPT_ANIMATION_MAX_FRAMES = Math.min(ASCII_VIDEO_FRAMES.length, ASCII_VIDEO_FPS * 3);
29
- function resolveInfinicodeCommand() {
29
+ /**
30
+ * Locate the bundled TUI binary, or null if it isn't shipped in this install.
31
+ * IMPORTANT: never fall back to spawning `infinicode` itself — that re-enters
32
+ * this default command and creates an infinite launch loop (the npm package
33
+ * ships the CLI/kernel only, without the large platform-specific TUI binary).
34
+ */
35
+ function resolveTuiBinary() {
30
36
  const rootDir = resolve(__dirname, '..', '..');
31
- const bundled = join(rootDir, 'bin', 'infinicode-tui.exe');
32
- return existsSync(bundled) ? bundled : 'infinicode';
37
+ const names = process.platform === 'win32' ? ['infinicode-tui.exe'] : ['infinicode-tui'];
38
+ for (const name of names) {
39
+ const p = join(rootDir, 'bin', name);
40
+ if (existsSync(p))
41
+ return p;
42
+ }
43
+ return null;
33
44
  }
34
45
  function fitPromptFrameToTerminal(frame) {
35
46
  const columns = Math.max(1, (process.stdout.columns ?? 120) - 1);
@@ -345,27 +356,29 @@ export async function runAgent(_masterUrl, _model, _useTui, config) {
345
356
  }
346
357
  console.log(chalk.dim('-'.repeat(40)));
347
358
  await renderPromptAnimation();
359
+ const tuiBinary = resolveTuiBinary();
360
+ if (!tuiBinary) {
361
+ console.log(chalk.yellow('\ninfinicode TUI is not bundled in this install.'));
362
+ console.log(chalk.dim('The npm package ships the kernel + device-mesh CLI only — the TUI binary is'));
363
+ console.log(chalk.dim('large and platform-specific, so it is not published. Use these instead:'));
364
+ console.log(chalk.cyan(' infinicode serve --hub ') + chalk.dim('# run this device as a mesh node'));
365
+ console.log(chalk.cyan(' infinicode mcp ') + chalk.dim('# MCP control server for a harness'));
366
+ console.log(chalk.cyan(' infinicode mission run ... ') + chalk.dim('# headless mission execution'));
367
+ console.log(chalk.cyan(' infinicode console ') + chalk.dim('# slash-command console'));
368
+ console.log(chalk.dim('\nTo get the TUI on this machine, run:'));
369
+ console.log(chalk.cyan(' infinicode install-tui ') + chalk.dim('# auto-find a local copy (repo/OneDrive)'));
370
+ console.log(chalk.cyan(' infinicode install-tui --url <url>') + chalk.dim(' # or download from a hosted binary'));
371
+ process.exit(1);
372
+ }
348
373
  const { execa } = await import('execa');
349
- const infinicodeCommand = resolveInfinicodeCommand();
350
374
  try {
351
- if (infinicodeCommand === 'infinicode') {
352
- await execa('which', ['infinicode']).catch(() => execa('where', ['infinicode']));
353
- }
354
- await execa(infinicodeCommand, [], {
375
+ await execa(tuiBinary, [], {
355
376
  stdio: 'inherit',
356
377
  env: process.env,
357
378
  });
358
379
  }
359
380
  catch (e) {
360
381
  const err = e;
361
- if (err.code === 'ENOENT') {
362
- console.log(chalk.yellow('\ninfinicode TUI binary not found.'));
363
- console.log(chalk.dim('\nThe TUI requires the infinicode binary in bin/ or on PATH.'));
364
- console.log(chalk.dim('Build from source:'));
365
- console.log(chalk.cyan(' pnpm build'));
366
- console.log();
367
- process.exit(1);
368
- }
369
382
  if (err.exitCode) {
370
383
  process.exit(err.exitCode);
371
384
  }
@@ -36,6 +36,8 @@ export declare class MeshClient {
36
36
  private opts;
37
37
  constructor(opts?: MeshClientOptions);
38
38
  private headers;
39
+ /** Normalize a base URL so a trailing slash doesn't produce `//fed/...` (→ 404). */
40
+ private base;
39
41
  fetchManifest(baseUrl: string): Promise<NodeManifest>;
40
42
  rpc(baseUrl: string, env: FederationEnvelope): Promise<FederationEnvelope>;
41
43
  /**
@@ -152,8 +152,12 @@ export class MeshClient {
152
152
  h['authorization'] = `Bearer ${this.opts.token}`;
153
153
  return h;
154
154
  }
155
+ /** Normalize a base URL so a trailing slash doesn't produce `//fed/...` (→ 404). */
156
+ base(baseUrl) {
157
+ return baseUrl.replace(/\/+$/, '');
158
+ }
155
159
  async fetchManifest(baseUrl) {
156
- const res = await fetch(`${baseUrl}/fed/manifest`, {
160
+ const res = await fetch(`${this.base(baseUrl)}/fed/manifest`, {
157
161
  headers: this.headers(),
158
162
  signal: AbortSignal.timeout(this.opts.timeoutMs ?? 5000),
159
163
  });
@@ -162,7 +166,7 @@ export class MeshClient {
162
166
  return (await res.json());
163
167
  }
164
168
  async rpc(baseUrl, env) {
165
- const res = await fetch(`${baseUrl}/fed/rpc`, {
169
+ const res = await fetch(`${this.base(baseUrl)}/fed/rpc`, {
166
170
  method: 'POST',
167
171
  headers: this.headers(),
168
172
  body: JSON.stringify(env),
@@ -180,7 +184,7 @@ export class MeshClient {
180
184
  const ctrl = new AbortController();
181
185
  (async () => {
182
186
  try {
183
- const res = await fetch(`${baseUrl}/fed/stream`, {
187
+ const res = await fetch(`${this.base(baseUrl)}/fed/stream`, {
184
188
  headers: this.headers(),
185
189
  signal: ctrl.signal,
186
190
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "infinicode",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "description": "OpenKernel — provider-agnostic AI execution kernel. Native coding agent + mission-driven execution runtime.",
5
5
  "type": "module",
6
6
  "main": "./dist/kernel/index.js",