@mutagent/cli 0.1.345 → 0.1.346

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 (3) hide show
  1. package/README.md +30 -22
  2. package/dist/bin/cli.js +445 -419
  3. package/package.json +1 -1
package/dist/bin/cli.js CHANGED
@@ -16189,7 +16189,7 @@ function runAgentProcess(options) {
16189
16189
  child.stderr.on("data", collect(stderr));
16190
16190
  child.once("error", (error3) => {
16191
16191
  cleanup();
16192
- reject(new MutagentError("AGENT_LOCAL_START_FAILED", error3.message, "Install Helix or set MUTAGENT_HELIX_BIN to the compatible executable."));
16192
+ reject(new MutagentError("AGENT_LOCAL_START_FAILED", error3.message, "Install Helix with mutagent install helix, or set MUTAGENT_HELIX_BIN to the compatible executable."));
16193
16193
  });
16194
16194
  child.once("close", (code) => {
16195
16195
  cleanup();
@@ -17539,7 +17539,8 @@ agent in one Environment; --env names it, and no --env is the default slot.
17539
17539
  Run a managed agent with: mutagent helix agent @slug[:vN|:latest] [--env X] -p "<task>"
17540
17540
 
17541
17541
  The compiler needs Bun 1.3.14 (on PATH, or MUTAGENT_BUN_BIN). agent run needs a
17542
- local Helix binary (MUTAGENT_HELIX_BIN selects one) and the LLM provider key for
17542
+ local Helix binary (mutagent install helix puts one at ~/.mutagent/bin/helix;
17543
+ MUTAGENT_HELIX_BIN selects another) and the LLM provider key for
17543
17544
  model on this machine.
17544
17545
 
17545
17546
  JSON: --json prints one result object on stdout. Revisions are numbers in JSON
@@ -18113,7 +18114,7 @@ function handleAgentParseError(error3, json) {
18113
18114
 
18114
18115
  // src/bin/cli.ts
18115
18116
  import chalk37 from "chalk";
18116
- import { readFileSync as readFileSync15 } from "fs";
18117
+ import { readFileSync as readFileSync14 } from "fs";
18117
18118
  import { join as join21, dirname as dirname7 } from "path";
18118
18119
  import { fileURLToPath as fileURLToPath4 } from "url";
18119
18120
 
@@ -18166,22 +18167,225 @@ function scaffoldRcConfig(cwd = process.cwd(), deps = {}) {
18166
18167
 
18167
18168
  // src/lib/installer.ts
18168
18169
  init_errors();
18169
- init_config();
18170
18170
 
18171
18171
  // src/lib/installer-helix.ts
18172
18172
  init_errors();
18173
18173
  import { spawn as spawn3 } from "child_process";
18174
18174
  import { createHash as createHash2 } from "crypto";
18175
+ import { existsSync as existsSync5, lstatSync, mkdirSync as mkdirSync2, renameSync, rmSync, symlinkSync } from "fs";
18176
+ import { chmod, open } from "fs/promises";
18175
18177
  import { homedir as homedir3 } from "os";
18176
- import { join as join10, posix as posixPath, win32 as win32Path, resolve as resolvePath } from "path";
18177
- import {
18178
- existsSync as existsSync5,
18179
- mkdirSync as mkdirSync2,
18180
- readFileSync as readFileSync4,
18181
- renameSync,
18182
- rmSync,
18183
- writeFileSync as writeFileSync3
18184
- } from "fs";
18178
+ import { delimiter as delimiter3, join as join10, resolve as resolve5 } from "path";
18179
+ var HELIX_CHANNELS = ["latest", "candidate"];
18180
+ var DEFAULT_INSTALL_HOST = "https://install.mutagent.io";
18181
+ var HELIX_BINARY_NAME = "helix";
18182
+ var HELIX_ALIAS_NAME = "mutagent-helix";
18183
+ var SUPPORTED_OS = { darwin: "darwin", linux: "linux" };
18184
+ var SUPPORTED_ARCH = { x64: "x64", arm64: "arm64" };
18185
+ var VERSION_TIMEOUT_MS = 60000;
18186
+ function resolveHelixAsset(platform, arch) {
18187
+ const os = SUPPORTED_OS[platform];
18188
+ const cpu = SUPPORTED_ARCH[arch];
18189
+ if (os === undefined || cpu === undefined) {
18190
+ throw new MutagentError("INSTALL_UNSUPPORTED_PLATFORM", `Helix has no build for ${platform}/${arch}.`, "Helix builds exist for macOS and Linux on x64 and arm64. On Windows, run mutagent install helix inside WSL.");
18191
+ }
18192
+ return `${HELIX_BINARY_NAME}-${os}-${cpu}`;
18193
+ }
18194
+ function resolveHelixChannel(option, envValue) {
18195
+ const requested = option ?? (envValue !== undefined && envValue !== "" ? envValue : "latest");
18196
+ if (!HELIX_CHANNELS.includes(requested)) {
18197
+ const source2 = option !== undefined ? "--channel" : "MUTAGENT_HELIX_CHANNEL";
18198
+ throw new MutagentError("INVALID_ARGUMENTS", `Unknown Helix channel "${requested}" (from ${source2}). Valid: ${HELIX_CHANNELS.join(", ")}`, "Use --channel latest or --channel candidate.");
18199
+ }
18200
+ return requested;
18201
+ }
18202
+ function findChecksum(checksums, asset) {
18203
+ for (const line of checksums.split(`
18204
+ `)) {
18205
+ const match = /^([0-9a-f]{64})\s+\*?(\S+)\s*$/i.exec(line.trim());
18206
+ if (match?.[2] === asset)
18207
+ return match[1]?.toLowerCase();
18208
+ }
18209
+ return;
18210
+ }
18211
+ function assertSecureHost(host) {
18212
+ let url;
18213
+ try {
18214
+ url = new URL(host);
18215
+ } catch {
18216
+ throw new MutagentError("INVALID_ARGUMENTS", `MUTAGENT_INSTALL_HOST is not a URL: ${host}`, `Set MUTAGENT_INSTALL_HOST to an https:// URL, or unset it to use ${DEFAULT_INSTALL_HOST}.`);
18217
+ }
18218
+ const loopback = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
18219
+ if (url.protocol === "https:" || url.protocol === "http:" && loopback)
18220
+ return;
18221
+ throw new MutagentError("INVALID_ARGUMENTS", `Refusing to install from ${host}: plaintext HTTP to a non-loopback host.`, "checksums.txt and the binary come from the same origin, so an attacker who can rewrite one can rewrite the other. Use an https:// MUTAGENT_INSTALL_HOST.");
18222
+ }
18223
+ async function fetchOrFail(fetchImpl, url, what) {
18224
+ let response;
18225
+ try {
18226
+ response = await fetchImpl(url);
18227
+ } catch (error3) {
18228
+ const reason = error3 instanceof Error ? error3.message : String(error3);
18229
+ throw new MutagentError("INSTALL_DOWNLOAD_FAILED", `Could not fetch ${what} from ${url}: ${reason}`, "Check your network connection, then run mutagent install helix again.");
18230
+ }
18231
+ if (!response.ok) {
18232
+ throw new MutagentError("INSTALL_DOWNLOAD_FAILED", `Could not fetch ${what} from ${url}: HTTP ${String(response.status)}`, "Check your network and the channel (--channel latest or candidate), then run mutagent install helix again.");
18233
+ }
18234
+ return response;
18235
+ }
18236
+ function writeFailed(path, error3) {
18237
+ const reason = error3 instanceof Error ? error3.message : String(error3);
18238
+ return new MutagentError("INSTALL_WRITE_FAILED", `Could not write ${path}: ${reason}`, "Set MUTAGENT_INSTALL_DIR to a writable directory, then run mutagent install helix again.");
18239
+ }
18240
+ async function streamToFile(response, partial3, what) {
18241
+ const hash3 = createHash2("sha256");
18242
+ let handle;
18243
+ try {
18244
+ handle = await open(partial3, "w", 384);
18245
+ } catch (error3) {
18246
+ throw writeFailed(partial3, error3);
18247
+ }
18248
+ try {
18249
+ const reader = response.body?.getReader();
18250
+ for (;; ) {
18251
+ let read;
18252
+ try {
18253
+ read = await reader?.read();
18254
+ } catch (error3) {
18255
+ const reason = error3 instanceof Error ? error3.message : String(error3);
18256
+ throw new MutagentError("INSTALL_DOWNLOAD_FAILED", `The download of ${what} broke off: ${reason}`, "Nothing was installed. Check your network connection, then run mutagent install helix again.");
18257
+ }
18258
+ if (read === undefined || read.done)
18259
+ break;
18260
+ hash3.update(read.value);
18261
+ try {
18262
+ await handle.write(read.value);
18263
+ } catch (error3) {
18264
+ throw writeFailed(partial3, error3);
18265
+ }
18266
+ }
18267
+ } finally {
18268
+ await handle.close();
18269
+ }
18270
+ return hash3.digest("hex");
18271
+ }
18272
+ function linkAlias(installDir, binaryPath) {
18273
+ const aliasPath = join10(installDir, HELIX_ALIAS_NAME);
18274
+ let existing;
18275
+ try {
18276
+ existing = lstatSync(aliasPath);
18277
+ } catch {
18278
+ existing = undefined;
18279
+ }
18280
+ if (existing !== undefined && !existing.isSymbolicLink())
18281
+ return;
18282
+ const staging = `${aliasPath}.${String(process.pid)}.link`;
18283
+ try {
18284
+ rmSync(staging, { force: true });
18285
+ symlinkSync(binaryPath, staging);
18286
+ renameSync(staging, aliasPath);
18287
+ } catch (error3) {
18288
+ const reason = error3 instanceof Error ? error3.message : String(error3);
18289
+ throw new MutagentError("INSTALL_ALIAS_FAILED", `Installed ${binaryPath}, but could not point ${aliasPath} at it: ${reason}`, `helix itself is installed and runs as ${binaryPath}. Remove whatever is at ${staging} or ${aliasPath}, then run mutagent install helix again.`);
18290
+ }
18291
+ return aliasPath;
18292
+ }
18293
+ function runVersion(binaryPath) {
18294
+ return new Promise((done) => {
18295
+ const child = spawn3(binaryPath, ["--version"], { stdio: ["ignore", "pipe", "pipe"] });
18296
+ let stdout = "";
18297
+ let stderr = "";
18298
+ const timer = setTimeout(() => child.kill("SIGKILL"), VERSION_TIMEOUT_MS);
18299
+ child.stdout.on("data", (chunk) => {
18300
+ stdout += chunk.toString();
18301
+ });
18302
+ child.stderr.on("data", (chunk) => {
18303
+ stderr += chunk.toString();
18304
+ });
18305
+ child.once("error", (error3) => {
18306
+ clearTimeout(timer);
18307
+ done({ code: null, stdout, stderr, error: error3 });
18308
+ });
18309
+ child.once("close", (code) => {
18310
+ clearTimeout(timer);
18311
+ done({ code, stdout, stderr });
18312
+ });
18313
+ });
18314
+ }
18315
+ function parseHelixVersion(stdout) {
18316
+ const first = stdout.split(`
18317
+ `).find((line) => line.trim() !== "")?.trim() ?? "";
18318
+ const match = /^helix\s+(\S+)/.exec(first);
18319
+ return match?.[1] ?? first;
18320
+ }
18321
+ async function verifyVersion(binaryPath) {
18322
+ const run = await runVersion(binaryPath);
18323
+ const version = run.code === 0 ? parseHelixVersion(run.stdout) : "";
18324
+ if (version === "") {
18325
+ const detail = run.error?.message ?? (run.stderr.trim().slice(-400) || `exit code ${String(run.code)}`);
18326
+ throw new MutagentError("INSTALL_VERIFY_FAILED", `Installed ${binaryPath}, but \`${binaryPath} --version\` failed: ${detail}`, `Run ${binaryPath} doctor to see what is wrong, then run mutagent install helix again.`);
18327
+ }
18328
+ return version;
18329
+ }
18330
+ function isOnPath(installDir, pathValue) {
18331
+ const target = resolve5(installDir);
18332
+ return (pathValue ?? "").split(delimiter3).filter((entry) => entry !== "").some((entry) => resolve5(entry) === target);
18333
+ }
18334
+ async function installHelix(options, deps = {}) {
18335
+ const env = deps.env ?? process.env;
18336
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
18337
+ const channel = resolveHelixChannel(options.channel, env.MUTAGENT_HELIX_CHANNEL);
18338
+ const asset = resolveHelixAsset(deps.platform ?? process.platform, deps.arch ?? process.arch);
18339
+ const rawHost = env.MUTAGENT_INSTALL_HOST !== undefined && env.MUTAGENT_INSTALL_HOST !== "" ? env.MUTAGENT_INSTALL_HOST : DEFAULT_INSTALL_HOST;
18340
+ assertSecureHost(rawHost);
18341
+ const host = rawHost.replace(/\/+$/, "");
18342
+ const installDir = env.MUTAGENT_INSTALL_DIR !== undefined && env.MUTAGENT_INSTALL_DIR !== "" ? env.MUTAGENT_INSTALL_DIR : join10(deps.homeDir ?? homedir3(), ".mutagent", "bin");
18343
+ const base = `${host}/${channel}`;
18344
+ const checksumsUrl = `${base}/checksums.txt`;
18345
+ const checksums = await (await fetchOrFail(fetchImpl, checksumsUrl, "checksums.txt")).text();
18346
+ const expected = findChecksum(checksums, asset);
18347
+ if (expected === undefined) {
18348
+ throw new MutagentError("INSTALL_ASSET_MISSING", `${asset} is not listed in ${checksumsUrl}.`, `The ${channel} channel has no build for this machine. Try --channel ${channel === "latest" ? "candidate" : "latest"}.`);
18349
+ }
18350
+ const response = await fetchOrFail(fetchImpl, `${base}/${asset}`, asset);
18351
+ try {
18352
+ mkdirSync2(installDir, { recursive: true });
18353
+ } catch (error3) {
18354
+ throw writeFailed(installDir, error3);
18355
+ }
18356
+ const binaryPath = join10(installDir, HELIX_BINARY_NAME);
18357
+ const partial3 = join10(installDir, `.${HELIX_BINARY_NAME}.${String(process.pid)}.partial`);
18358
+ try {
18359
+ const actual = await streamToFile(response, partial3, asset);
18360
+ if (actual !== expected) {
18361
+ throw new MutagentError("INTEGRITY_ERROR", `${asset} failed checksum verification: expected ${expected}, got ${actual}.`, "The download is corrupt or was altered in transit. Nothing was installed; run mutagent install helix again.");
18362
+ }
18363
+ try {
18364
+ await chmod(partial3, 493);
18365
+ renameSync(partial3, binaryPath);
18366
+ } catch (error3) {
18367
+ throw writeFailed(binaryPath, error3);
18368
+ }
18369
+ } finally {
18370
+ if (existsSync5(partial3))
18371
+ rmSync(partial3, { force: true });
18372
+ }
18373
+ const aliasPath = linkAlias(installDir, binaryPath);
18374
+ const version = await verifyVersion(binaryPath);
18375
+ const onPath = isOnPath(installDir, env.PATH);
18376
+ return {
18377
+ channel,
18378
+ platform: asset.slice(`${HELIX_BINARY_NAME}-`.length),
18379
+ version,
18380
+ binaryPath,
18381
+ ...aliasPath !== undefined ? { aliasPath } : {},
18382
+ sha256: expected,
18383
+ onPath,
18384
+ ...onPath ? {} : { pathLine: `export PATH="${installDir}:$PATH"` },
18385
+ installerUrl: `${host}/${HELIX_BINARY_NAME}`,
18386
+ checksumsUrl
18387
+ };
18388
+ }
18185
18389
 
18186
18390
  // src/lib/install-telemetry.ts
18187
18391
  init_config();
@@ -18197,208 +18401,25 @@ function resolveApiAuth() {
18197
18401
  headers["x-organization-id"] = config.defaultOrganization;
18198
18402
  return { apiBase, apiKey, headers };
18199
18403
  }
18200
- function readCliVersion() {
18201
- return process.env.CLI_VERSION ?? "unknown";
18202
- }
18203
18404
  async function reportInstall(event, deps = {}) {
18204
18405
  try {
18205
18406
  const auth = (deps.resolveAuth ?? resolveApiAuth)();
18206
18407
  if (!auth.apiKey)
18207
- return;
18208
- const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
18408
+ return "skipped";
18209
18409
  const body = {
18210
- pkg: event.pkg,
18211
- version: event.version,
18212
- harness: event.harness,
18213
- cliVersion: deps.cliVersion ?? readCliVersion()
18410
+ ...event,
18411
+ cliVersion: deps.cliVersion ?? process.env.CLI_VERSION ?? "unknown"
18214
18412
  };
18215
- await fetchImpl(`${auth.apiBase}/api/helix/installs`, {
18413
+ const response = await (deps.fetchImpl ?? globalThis.fetch)(`${auth.apiBase}/api/helix/installs`, {
18216
18414
  method: "POST",
18217
- headers: {
18218
- "Content-Type": "application/json",
18219
- "x-api-key": auth.apiKey,
18220
- ...auth.headers
18221
- },
18415
+ headers: { "Content-Type": "application/json", "x-api-key": auth.apiKey, ...auth.headers },
18222
18416
  body: JSON.stringify(body),
18223
18417
  signal: AbortSignal.timeout(deps.timeoutMs ?? TELEMETRY_TIMEOUT_MS)
18224
18418
  });
18225
- } catch {}
18226
- }
18227
-
18228
- // src/lib/installer-helix.ts
18229
- async function installHelix(opts, deps = {}) {
18230
- const auth = (deps.resolveAuth ?? resolveApiAuth)();
18231
- const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
18232
- const descriptor = await fetchDescriptor(fetchImpl, auth, opts.version);
18233
- const baseDir = deps.homeDir ?? join10(homedir3(), ".mutagent", "helix");
18234
- const versionDir = join10(baseDir, descriptor.version);
18235
- mkdirSync2(versionDir, { recursive: true });
18236
- const tgzPath = join10(versionDir, `helix-plugin-${descriptor.version}.tgz`);
18237
- const tmpPath = `${tgzPath}.${String(process.pid)}.${String(Date.now())}.part`;
18238
- const download = deps.download ?? defaultDownload;
18239
- await download(descriptor.url, tmpPath);
18240
- const sha2562 = deps.sha256 ?? defaultSha256;
18241
- const actual = (await sha2562(tmpPath)).toLowerCase();
18242
- const expected = descriptor.sha256.toLowerCase();
18243
- if (actual !== expected) {
18244
- safeRm(tmpPath);
18245
- throw new MutagentError("INTEGRITY_ERROR", `Downloaded helix plugin failed sha256 verification (expected ${expected.slice(0, 12)}…, got ${actual.slice(0, 12)}…).`, "The download may be corrupt or tampered with. Retry: mutagent install helix");
18246
- }
18247
- renameSync(tmpPath, tgzPath);
18248
- const extract3 = deps.extract ?? defaultExtract;
18249
- await extract3(tgzPath, versionDir);
18250
- const locateInitBin = deps.locateInitBin ?? defaultLocateInitBin;
18251
- const binPath = await locateInitBin(versionDir);
18252
- const initArgs = ["init", "--yes"];
18253
- const runInit = deps.runInit ?? defaultRunInit;
18254
- const code = await runInit(binPath, initArgs, process.cwd());
18255
- if (code !== 0) {
18256
- throw new MutagentError("INSTALL_FAILED", `helix plugin init exited with code ${String(code)}.`, "Re-run: mutagent install helix — or run the plugin init manually and report the error.");
18257
- }
18258
- return { version: descriptor.version };
18259
- }
18260
- async function fetchDescriptor(fetchImpl, auth, version) {
18261
- if (!auth.apiKey) {
18262
- throw new MutagentError("AUTH_REQUIRED", "Authentication required to install helix.", "Run: mutagent login");
18263
- }
18264
- const url = `${auth.apiBase}/api/helix/plugin/download?version=${encodeURIComponent(version)}`;
18265
- let res;
18266
- try {
18267
- res = await fetchImpl(url, {
18268
- method: "GET",
18269
- headers: { "x-api-key": auth.apiKey, ...auth.headers }
18270
- });
18419
+ return response.ok ? "sent" : "failed";
18271
18420
  } catch {
18272
- throw new MutagentError("SERVER_UNAVAILABLE", "Could not reach the Mutagent plugin broker.", "Check your network connection or verify the endpoint with: mutagent config show");
18421
+ return "failed";
18273
18422
  }
18274
- if (res.status === 401) {
18275
- throw new MutagentError("AUTH_REQUIRED", "The plugin broker rejected your credentials.", "Re-authenticate: mutagent login");
18276
- }
18277
- if (!res.ok) {
18278
- const detail = await readErrorMessage(res);
18279
- throw new MutagentError("INSTALL_FAILED", `Plugin broker returned ${String(res.status)}.${detail ? ` ${detail}` : ""}`, "Verify the requested version exists, then retry: mutagent install helix");
18280
- }
18281
- const raw = await res.json();
18282
- if (!isBrokerResponse(raw)) {
18283
- throw new MutagentError("INSTALL_FAILED", "Plugin broker returned a malformed response (missing version/sha256/url).", "Retry: mutagent install helix — if it persists, report it.");
18284
- }
18285
- return raw;
18286
- }
18287
- async function defaultDownload(url, destPath) {
18288
- let res;
18289
- try {
18290
- res = await globalThis.fetch(url);
18291
- } catch {
18292
- throw new MutagentError("SERVER_UNAVAILABLE", "Failed to download the helix plugin from storage.", "Check your network connection and retry: mutagent install helix");
18293
- }
18294
- if (!res.ok) {
18295
- throw new MutagentError("INSTALL_FAILED", `Plugin download failed with status ${String(res.status)}.`, "The signed URL may have expired — retry: mutagent install helix");
18296
- }
18297
- const bytes = Buffer.from(await res.arrayBuffer());
18298
- writeFileSync3(destPath, bytes);
18299
- }
18300
- async function defaultSha256(filePath) {
18301
- return Promise.resolve(createHash2("sha256").update(readFileSync4(filePath)).digest("hex"));
18302
- }
18303
- function buildTarExtractInvocation(tgzPath, destDir, platform = process.platform) {
18304
- const p = platform === "win32" ? win32Path : posixPath;
18305
- const cwd = p.dirname(tgzPath) || ".";
18306
- const archive = p.basename(tgzPath);
18307
- const dest = p.isAbsolute(destDir) ? destDir : resolvePath(destDir);
18308
- return { args: ["-xzf", archive, "-C", dest], cwd };
18309
- }
18310
- function defaultExtract(tgzPath, destDir) {
18311
- const invocation = buildTarExtractInvocation(tgzPath, destDir);
18312
- return new Promise((resolve5, reject) => {
18313
- const child = spawn3("tar", invocation.args, {
18314
- cwd: invocation.cwd,
18315
- stdio: ["ignore", "ignore", "pipe"]
18316
- });
18317
- let stderr = "";
18318
- child.stderr.on("data", (chunk) => {
18319
- stderr += chunk.toString("utf-8");
18320
- });
18321
- child.on("error", (err) => {
18322
- reject(new MutagentError("INSTALL_FAILED", `Failed to extract the helix plugin: ${err.message}`, 'Ensure "tar" is installed and available on your PATH.'));
18323
- });
18324
- child.on("close", (code) => {
18325
- if (code === 0) {
18326
- resolve5();
18327
- } else {
18328
- reject(new MutagentError("INSTALL_FAILED", `Extracting the helix plugin failed (tar exit ${String(code ?? 1)}).${stderr ? ` ${stderr.trim().slice(0, 200)}` : ""}`, "The downloaded archive may be corrupt — retry: mutagent install helix"));
18329
- }
18330
- });
18331
- });
18332
- }
18333
- function defaultLocateInitBin(extractedDir) {
18334
- const pkgDir = existsSync5(join10(extractedDir, "package", "package.json")) ? join10(extractedDir, "package") : extractedDir;
18335
- const pkgJsonPath = join10(pkgDir, "package.json");
18336
- if (!existsSync5(pkgJsonPath)) {
18337
- return Promise.reject(new MutagentError("INSTALL_FAILED", `Could not find package.json under ${pkgDir}.`, "The package layout is unexpected — retry the install, or report it."));
18338
- }
18339
- const raw = JSON.parse(readFileSync4(pkgJsonPath, "utf-8"));
18340
- const binRel = resolveBinField(raw);
18341
- if (!binRel) {
18342
- return Promise.reject(new MutagentError("INSTALL_FAILED", "The helix plugin package.json declares no runnable bin.", "The plugin package is malformed — report: mutagent install helix"));
18343
- }
18344
- const binPath = join10(pkgDir, binRel);
18345
- if (!existsSync5(binPath)) {
18346
- return Promise.reject(new MutagentError("INSTALL_FAILED", `The helix plugin bin was not found at ${binRel}.`, "The plugin package is incomplete — report: mutagent install helix"));
18347
- }
18348
- return Promise.resolve(binPath);
18349
- }
18350
- function defaultRunInit(binPath, args, cwd) {
18351
- return new Promise((resolve5, reject) => {
18352
- const child = spawn3("node", [binPath, ...args], { cwd, stdio: ["ignore", 2, 2] });
18353
- child.on("error", (err) => {
18354
- reject(new MutagentError("INSTALL_FAILED", `Failed to run the package init: ${err.message}`, 'Ensure "node" is installed and available on your PATH.'));
18355
- });
18356
- child.on("close", (code) => {
18357
- resolve5(code ?? 1);
18358
- });
18359
- });
18360
- }
18361
- function resolveBinField(pkg) {
18362
- if (!pkg || typeof pkg !== "object")
18363
- return;
18364
- const bin = pkg.bin;
18365
- if (typeof bin === "string")
18366
- return bin;
18367
- if (bin && typeof bin === "object") {
18368
- const entries = bin;
18369
- const preferred = entries["mutagent-helix"];
18370
- if (typeof preferred === "string")
18371
- return preferred;
18372
- for (const value2 of Object.values(entries)) {
18373
- if (typeof value2 === "string")
18374
- return value2;
18375
- }
18376
- }
18377
- return;
18378
- }
18379
- function isBrokerResponse(value2) {
18380
- if (!value2 || typeof value2 !== "object")
18381
- return false;
18382
- const v = value2;
18383
- return typeof v.version === "string" && typeof v.sha256 === "string" && typeof v.url === "string";
18384
- }
18385
- async function readErrorMessage(res) {
18386
- try {
18387
- const body = await res.json();
18388
- if (body && typeof body === "object") {
18389
- const b = body;
18390
- if (typeof b.message === "string")
18391
- return b.message;
18392
- if (typeof b.error === "string")
18393
- return b.error;
18394
- }
18395
- } catch {}
18396
- return;
18397
- }
18398
- function safeRm(path) {
18399
- try {
18400
- rmSync(path, { force: true });
18401
- } catch {}
18402
18423
  }
18403
18424
 
18404
18425
  // src/lib/installer.ts
@@ -18407,64 +18428,21 @@ var RETIRED_PACKAGES = {
18407
18428
  diagnostics: "@mutagent/diagnostics",
18408
18429
  evaluator: "@mutagent/evaluator"
18409
18430
  };
18410
- var VALID_HARNESSES = ["claude-code", "codex", "omp"];
18411
- var VERSION_MATRIX = {
18412
- helix: "latest"
18413
- };
18414
- function isValidPackage(pkg) {
18415
- return VALID_PACKAGES.includes(pkg);
18416
- }
18417
- function isValidHarness(harness) {
18418
- return VALID_HARNESSES.includes(harness);
18419
- }
18431
+ var HELIX_NEXT_STEP = "run helix in a project";
18420
18432
  async function installPackage(pkg, opts, deps = {}) {
18433
+ assertInstallTarget(pkg);
18434
+ const helix = await installHelix({ channel: opts.channel }, deps.helix ?? {});
18435
+ const telemetry = await reportInstall({ pkg: "helix", version: helix.version, channel: helix.channel, platform: helix.platform }, deps.telemetry ?? {});
18436
+ return { package: "helix", ...helix, nextStep: HELIX_NEXT_STEP, telemetry };
18437
+ }
18438
+ function assertInstallTarget(pkg) {
18421
18439
  const retiredNpmName = RETIRED_PACKAGES[pkg];
18422
18440
  if (retiredNpmName !== undefined) {
18423
18441
  throw new MutagentError("INVALID_ARGUMENTS", `"${pkg}" is no longer an install target — ${retiredNpmName} is no longer published.`, `The ${pkg} skill now ships inside helix. Run: mutagent install helix`);
18424
18442
  }
18425
- const isAuthed = deps.isAuthed ?? hasCredentials;
18426
- if (!isAuthed()) {
18427
- throw new MutagentError("AUTH_REQUIRED", `Authentication required to install ${pkg}.`, "Run: mutagent login");
18428
- }
18429
- const projectPath = process.cwd();
18430
- if (opts.global === true) {
18431
- throw new MutagentError("INVALID_ARGUMENTS", "--global is no longer supported: install scope is no longer selectable.", [
18432
- "Every install is project-scoped now:",
18433
- ` the package resolves into ~/.mutagent/<pkg>/<version>, and its skill`,
18434
- ` is installed into the current project (${projectPath}).`,
18435
- "Nothing is installed globally. Re-run without --global."
18436
- ].join(`
18437
- `));
18438
- }
18439
- if (!isValidPackage(pkg)) {
18443
+ if (!VALID_PACKAGES.includes(pkg)) {
18440
18444
  throw new MutagentError("INVALID_ARGUMENTS", `Unknown package "${pkg}". Valid: ${VALID_PACKAGES.join(", ")}`, "Run: mutagent install --help");
18441
18445
  }
18442
- if (!isValidHarness(opts.harness)) {
18443
- throw new MutagentError("INVALID_ARGUMENTS", `Unknown harness "${opts.harness}". Valid: ${VALID_HARNESSES.join(", ")}`, "Run: mutagent install --help");
18444
- }
18445
- const version = opts.version ?? VERSION_MATRIX[pkg];
18446
- switch (pkg) {
18447
- case "helix": {
18448
- const { version: resolved } = await installHelix({ harness: opts.harness, version }, deps.helix ?? {});
18449
- return finish({
18450
- package: pkg,
18451
- version: resolved,
18452
- harness: opts.harness,
18453
- scope: "project",
18454
- projectPath,
18455
- skillInstalled: true
18456
- }, deps);
18457
- }
18458
- default:
18459
- return assertNoInstallPath(pkg);
18460
- }
18461
- }
18462
- function assertNoInstallPath(pkg) {
18463
- throw new MutagentError("INVALID_ARGUMENTS", `No install path is implemented for "${String(pkg)}".`, "Run: mutagent install --help");
18464
- }
18465
- async function finish(result, deps) {
18466
- await reportInstall({ pkg: result.package, version: result.version, harness: result.harness }, deps.telemetry ?? {});
18467
- return result;
18468
18446
  }
18469
18447
 
18470
18448
  // src/commands/onboarding.ts
@@ -18500,12 +18478,16 @@ async function runPostOnboarding(deps = {}) {
18500
18478
  console.log("");
18501
18479
  if (action === "helix") {
18502
18480
  console.log(chalk2.bold(" Installing Helix…"));
18503
- console.log(chalk2.dim(" Downloading + verifying the plugin, then writing .claude/ + .codex/"));
18481
+ console.log(chalk2.dim(" Downloading the helix binary and verifying its checksum"));
18504
18482
  console.log("");
18505
18483
  try {
18506
- await install("helix", { harness: "claude-code", global: false });
18507
- console.log("");
18508
- console.log(` ${chalk2.green("✓")} Helix ready — run ${chalk2.cyan("/help")} inside your agent`);
18484
+ const result = await install("helix", {});
18485
+ console.log(` ${chalk2.green("✓")} Helix ${result.version} installed at ${result.binaryPath}`);
18486
+ if (result.pathLine !== undefined) {
18487
+ console.log(` ${chalk2.yellow("⚠")} Not on your PATH yet. Add this line to your shell profile:`);
18488
+ console.log(` ${chalk2.cyan(result.pathLine)}`);
18489
+ }
18490
+ console.log(` ${chalk2.blue("→")} Next: ${result.nextStep}`);
18509
18491
  console.log("");
18510
18492
  } catch (error3) {
18511
18493
  console.log("");
@@ -18572,14 +18554,14 @@ async function openBrowser(url) {
18572
18554
  return;
18573
18555
  }
18574
18556
  try {
18575
- const { default: open } = await import("open");
18576
- await open(url);
18557
+ const { default: open2 } = await import("open");
18558
+ await open2(url);
18577
18559
  } catch {
18578
18560
  throw new BrowserAuthError("BROWSER_OPEN_FAILED", `Could not open browser automatically. Please visit: ${url}`);
18579
18561
  }
18580
18562
  }
18581
18563
  function sleep(ms) {
18582
- return new Promise((resolve5) => setTimeout(resolve5, ms));
18564
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
18583
18565
  }
18584
18566
  async function performBrowserAuth(options, onStatusUpdate) {
18585
18567
  const {
@@ -20045,9 +20027,9 @@ init_known_secrets();
20045
20027
  init_secret_scrub();
20046
20028
 
20047
20029
  // src/lib/helix-local-store.ts
20048
- import { existsSync as existsSync7, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
20030
+ import { existsSync as existsSync7, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
20049
20031
  import { homedir as homedir4 } from "os";
20050
- import { join as join12, resolve as resolve5 } from "path";
20032
+ import { join as join12, resolve as resolve6 } from "path";
20051
20033
 
20052
20034
  // src/lib/harness-model-config.ts
20053
20035
  import { z as z2 } from "zod";
@@ -20213,13 +20195,13 @@ function harnessProviderOf(metadata) {
20213
20195
 
20214
20196
  // src/lib/helix-local-store.ts
20215
20197
  init_secret_scrub();
20216
- var HELIX_CHANNELS = ["stable", "rc"];
20198
+ var HELIX_CHANNELS2 = ["stable", "rc"];
20217
20199
  function resolveAgentDir(channel, env = process.env, home = homedir4()) {
20218
20200
  const override2 = env.HELIX_CODING_AGENT_DIR;
20219
20201
  if (!override2)
20220
20202
  return join12(home, ".mutagent", channel === "rc" ? "agent-rc" : "agent");
20221
20203
  const expanded = override2 === "~" ? home : override2.startsWith("~/") ? join12(home, override2.slice(2)) : override2;
20222
- return resolve5(expanded);
20204
+ return resolve6(expanded);
20223
20205
  }
20224
20206
  var credentialInventory = new WeakMap;
20225
20207
  function localCredentialSecrets(entries) {
@@ -20278,7 +20260,7 @@ function readJsonObject(path, notes, label) {
20278
20260
  notes.push(`${label} exceeds the 1 MiB mirror limit; ignored`);
20279
20261
  return null;
20280
20262
  }
20281
- const parsed = JSON.parse(readFileSync5(path, "utf-8"));
20263
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
20282
20264
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
20283
20265
  notes.push(`${label} is not a JSON object; ignored`);
20284
20266
  return null;
@@ -20686,7 +20668,7 @@ function statusGlyph(result) {
20686
20668
  }
20687
20669
  }
20688
20670
  function registerMirrorCommand(parent) {
20689
- parent.command("mirror").description("Copy the API keys from your local Helix login store into the workspace as LLM providers").option("-y, --yes", "Consent without prompting (required when non-interactive)").option("--no-overwrite", "Leave LLM providers the workspace already has untouched").option("--workspace <id>", "Target workspace for this run (default: the configured one)").option("--channel <channel>", `Helix channel whose store is read: ${HELIX_CHANNELS.join(" | ")}`, "stable").option("--provider <id>", "Only this local LLM provider id (repeatable)", (value2, prev = []) => [...prev, value2]).addHelpText("after", `
20671
+ parent.command("mirror").description("Copy the API keys from your local Helix login store into the workspace as LLM providers").option("-y, --yes", "Consent without prompting (required when non-interactive)").option("--no-overwrite", "Leave LLM providers the workspace already has untouched").option("--workspace <id>", "Target workspace for this run (default: the configured one)").option("--channel <channel>", `Helix channel whose store is read: ${HELIX_CHANNELS2.join(" | ")}`, "stable").option("--provider <id>", "Only this local LLM provider id (repeatable)", (value2, prev = []) => [...prev, value2]).addHelpText("after", `
20690
20672
  Examples:
20691
20673
  ${chalk11.dim("$")} mutagent providers mirror
20692
20674
  ${chalk11.dim("$")} mutagent providers mirror --json ${chalk11.dim("Show the plan without sending anything")}
@@ -20741,8 +20723,8 @@ AI Agent Directive:
20741
20723
  if (options.workspace) {
20742
20724
  process.env.MUTAGENT_WORKSPACE_ID = options.workspace;
20743
20725
  }
20744
- if (!HELIX_CHANNELS.includes(options.channel)) {
20745
- throw new MutagentError("INVALID_OPTION", `Unknown channel '${options.channel}'.`, `Use --channel ${HELIX_CHANNELS.join(" or ")}`);
20726
+ if (!HELIX_CHANNELS2.includes(options.channel)) {
20727
+ throw new MutagentError("INVALID_OPTION", `Unknown channel '${options.channel}'.`, `Use --channel ${HELIX_CHANNELS2.join(" or ")}`);
20746
20728
  }
20747
20729
  const channel = options.channel;
20748
20730
  const agentDir = resolveAgentDir(channel);
@@ -21023,14 +21005,14 @@ Available Models:`));
21023
21005
  init_config();
21024
21006
  import { Command as Command8 } from "commander";
21025
21007
  import chalk14 from "chalk";
21026
- import { readFileSync as readFileSync6 } from "fs";
21008
+ import { readFileSync as readFileSync5 } from "fs";
21027
21009
  import { join as join14 } from "path";
21028
21010
  init_errors();
21029
21011
 
21030
21012
  // src/commands/skills.ts
21031
21013
  import { Command as Command7 } from "commander";
21032
21014
  import chalk13 from "chalk";
21033
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
21015
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
21034
21016
  import { dirname as dirname4, join as join13 } from "path";
21035
21017
  import { execSync } from "child_process";
21036
21018
 
@@ -21041,7 +21023,7 @@ name: mutagent-cli
21041
21023
  description: |
21042
21024
  Mutagent CLI - the command-line client for the Mutagent platform.
21043
21025
  Guides coding agents through setup (login, init, providers, workspaces),
21044
- installing Mutagent packages (helix), and sending
21026
+ installing the standalone Helix binary, and sending
21045
21027
  product feedback, and deploying and running managed agents (agent.md folders).
21046
21028
  Triggers: "mutagent", "mutagent cli", "mutagent login", "set up mutagent",
21047
21029
  "install diagnostics", "install evaluator", "install helix", "mutagent install",
@@ -21148,7 +21130,7 @@ Parse \`_compat.cliVersion\` from the \`--version --json\` output and compare ag
21148
21130
 
21149
21131
  ## Login (RUN SECOND — most commands are login-gated)
21150
21132
 
21151
- Install, feedback, usage, workspaces, and providers commands all require
21133
+ Feedback, usage, workspaces, and providers commands all require
21152
21134
  an authenticated session. Establish it early:
21153
21135
 
21154
21136
  - **CI / automated**: \`export MUTAGENT_API_KEY=mt_... && mutagent login --json\` -- no browser, no prompts.
@@ -21175,7 +21157,7 @@ skill never inlines flags. The active surface:
21175
21157
  | \`usage\` | Show usage + quota | [workflows/setup.md](./workflows/setup.md) |
21176
21158
  | \`skills install\` | Install this skill into a project | [workflows/setup.md](./workflows/setup.md) |
21177
21159
  | \`hooks install\` | Install Claude Code telemetry hooks | [workflows/setup.md](./workflows/setup.md) |
21178
- | \`install <pkg>\` | Install helix (diagnostics / evaluator are retired) | [workflows/install.md](./workflows/install.md) |
21160
+ | \`install helix\` | Install the standalone Helix binary (diagnostics / evaluator are retired) | [workflows/install.md](./workflows/install.md) |
21179
21161
  | \`agent\` | Check, pack, run locally, deploy, activate, list, inspect and retire managed agents | [workflows/agent-deployment.md](./workflows/agent-deployment.md) |
21180
21162
  | \`feedback send\` | Send product feedback | [workflows/feedback.md](./workflows/feedback.md) |
21181
21163
 
@@ -21185,9 +21167,9 @@ skill never inlines flags. The active surface:
21185
21167
 
21186
21168
  1. **\`--json\` on EVERY command.** No exceptions. Agents use JSON mode exclusively.
21187
21169
  2. **\`<command> --help\` BEFORE first use of any command.** The CLI is the source of truth for flags -- this skill never inlines them.
21188
- 3. **Login before login-gated commands.** install, feedback, usage, workspaces, and providers require an authenticated session. Run \`mutagent login\` (or set \`MUTAGENT_API_KEY\`) first.
21170
+ 3. **Login before login-gated commands.** feedback, usage, workspaces, and providers require an authenticated session. Run \`mutagent login\` (or set \`MUTAGENT_API_KEY\`) first.
21189
21171
  4. **Show command output to the user.** Command output appears in bash blocks the user may not see -- always present the key results in your chat reply.
21190
- 5. **Confirm before writing to the user's machine or spending.** \`install\`, \`skills install\`, and \`hooks install\` write files or install packages -- state what will happen and get confirmation first.
21172
+ 5. **Confirm before writing to the user's machine or spending.** \`install\`, \`skills install\`, and \`hooks install\` write files or install a binary -- state what will happen and get confirmation first.
21191
21173
 
21192
21174
  ---
21193
21175
 
@@ -21198,7 +21180,7 @@ Match the user's first request. Load ONLY the matching subfile per the table. Do
21198
21180
  | User said / signal detected | Load subfile | Why |
21199
21181
  |---|---|---|
21200
21182
  | "log in", "authenticate", "set up mutagent", "init", "which workspace", "what models", "usage", "quota", "install the skill", "install hooks" | [workflows/setup.md](./workflows/setup.md) | Auth + project setup + discovery commands |
21201
- | "install helix", "install diagnostics", "install evaluator", "add mutagent package" | [workflows/install.md](./workflows/install.md) | Login-gated meta-installer (diagnostics/evaluator are retired → helix) |
21183
+ | "install helix", "install diagnostics", "install evaluator", "add mutagent package" | [workflows/install.md](./workflows/install.md) | Standalone Helix binary installer, no login (diagnostics/evaluator are retired → helix) |
21202
21184
  | "agent.md", "deploy agent", "managed agent", "mutagent agent", "helix agent @slug" | [workflows/agent-deployment.md](./workflows/agent-deployment.md) | agent.md folder → check → deploy to a slot → run with \`helix agent @slug\` |
21203
21185
  | "send feedback", "report a bug", "file feedback", "the CLI crashed" | [workflows/feedback.md](./workflows/feedback.md) | Product feedback (+ optional transcript) |
21204
21186
  | Unclear / first time | run \`mutagent --help --json\` then reroute | Discover the surface before acting |
@@ -21210,7 +21192,7 @@ Match the user's first request. Load ONLY the matching subfile per the table. Do
21210
21192
  | File | WHEN to load | WHY |
21211
21193
  |---|---|---|
21212
21194
  | [workflows/setup.md](./workflows/setup.md) | User is onboarding: login, init, config, workspaces, providers, usage, skill/hooks install | Auth + configuration + discovery |
21213
- | [workflows/install.md](./workflows/install.md) | User wants to install a Mutagent package | \`install helix\` meta-command |
21195
+ | [workflows/install.md](./workflows/install.md) | User wants to install Helix | \`install helix\`: the standalone binary into ~/.mutagent/bin |
21214
21196
  | [workflows/agent-deployment.md](./workflows/agent-deployment.md) | User wants to deploy or run a managed agent, or run an agent.md locally | Slots (\`--env\`), revisions (\`vN\`), \`@slug\` runs, rollback and retire |
21215
21197
  | [workflows/feedback.md](./workflows/feedback.md) | User wants to send feedback or report a bug | \`feedback send\` + \`--attach-transcript\` |
21216
21198
 
@@ -21434,9 +21416,9 @@ how they learn something sensitive was in the file at all.
21434
21416
  "workflows/install.md": `---
21435
21417
  name: mutagent-cli-workflows-install
21436
21418
  description: |
21437
- Meta-installer workflow. Installs helix into the user's environment via
21438
- \`mutagent install helix\`. Login-gated. helix is fetched from a private
21439
- registry via a login-brokered signed URL (no static secret ships in the CLI).
21419
+ Install workflow. \`mutagent install helix\` installs the standalone Helix
21420
+ binary into ~/.mutagent/bin, the same result as
21421
+ \`curl -fsSL https://install.mutagent.io/helix | bash\`. No login needed.
21440
21422
  \`diagnostics\` and \`evaluator\` are RETIRED as install targets — both skills
21441
21423
  now ship inside helix.
21442
21424
  triggers:
@@ -21449,23 +21431,24 @@ triggers:
21449
21431
  - "set up evaluator"
21450
21432
  ---
21451
21433
 
21452
- # Workflow — Install (Meta-Installer)
21434
+ # Workflow — Install Helix
21453
21435
 
21454
- > **\`helix\` is the only install target.** It installs into the CURRENT project
21455
- > directory (\`scope: "project"\`).
21436
+ > **\`helix\` is the only install target.** \`mutagent install helix\` installs the
21437
+ > standalone Helix binary at \`~/.mutagent/bin/helix\`, the same result as
21438
+ > \`curl -fsSL https://install.mutagent.io/helix | bash\`.
21456
21439
  >
21457
21440
  > \`diagnostics\` and \`evaluator\` are **retired** — their npm packages are no
21458
- > longer published. Both skills ship *inside* the helix payload, so a user
21459
- > asking for either one needs \`mutagent install helix\`. The CLI says so itself:
21441
+ > longer published. Both skills ship *inside* helix, so a user asking for either
21442
+ > one needs \`mutagent install helix\`. The CLI says so itself:
21460
21443
  > \`mutagent install diagnostics\` fails with \`INVALID_ARGUMENTS\` and names helix
21461
21444
  > as the route. Do not work around that error — follow it.
21462
21445
  >
21463
- > This WRITES to the user's machine — confirm before running (Core Rule 5).
21446
+ > This WRITES a binary to the user's machine — confirm before running (Core Rule 5).
21464
21447
 
21465
21448
  Read the **Core Rules** in [SKILL.md](../SKILL.md) first. Key reminders:
21466
21449
  - \`--json\` on every command
21467
21450
  - \`<command> --help\` before first use
21468
- - **Login-gated** — run \`mutagent login\` first
21451
+ - No login is needed for this command (when signed in, the CLI also records the install activation)
21469
21452
  - Confirm before installing (writes to the user's machine)
21470
21453
 
21471
21454
  ---
@@ -21475,53 +21458,61 @@ Read the **Core Rules** in [SKILL.md](../SKILL.md) first. Key reminders:
21475
21458
  \`\`\`bash
21476
21459
  mutagent install --help # authoritative flags — read before first use
21477
21460
 
21478
- mutagent install helix [--harness <claude-code|codex|omp>] \\
21479
- [--version <v>] [--json]
21461
+ mutagent install helix [--channel <latest|candidate>] [--json]
21480
21462
  \`\`\`
21481
21463
 
21482
- Where \`<package>\` is:
21464
+ What it does, in order:
21483
21465
 
21484
- | Package | Source | Notes |
21485
- |---|---|---|
21486
- | \`helix\` | private registry via login-brokered signed URL | Ready to install — login-gated download, sha256-verified, then initialized into your project. Carries the diagnostics + evaluator skills and the \`helix-cli\` binary. |
21466
+ 1. Maps the machine to its build, \`helix-<darwin|linux>-<x64|arm64>\`. Other
21467
+ platforms (Windows included) fail with \`INSTALL_UNSUPPORTED_PLATFORM\`; on
21468
+ Windows, run it inside WSL.
21469
+ 2. Downloads \`checksums.txt\` and the build from \`https://install.mutagent.io/<channel>/\`.
21470
+ 3. Verifies the build's sha256 against \`checksums.txt\`.
21471
+ 4. Writes \`~/.mutagent/bin/helix\` (mode 755) and points the \`mutagent-helix\`
21472
+ alias symlink at it.
21473
+ 5. Runs \`helix --version\` and reports the version it prints.
21474
+
21475
+ It never edits shell rc files. When \`~/.mutagent/bin\` is not on PATH, the
21476
+ response carries the line to add.
21477
+
21478
+ After a successful install, and only when the user is signed in, the CLI posts
21479
+ the install activation (package, version, channel, platform) to the Platform.
21480
+ Signed out, nothing is sent. The post is best-effort: its outcome is reported in
21481
+ \`telemetry\` and never changes the install result.
21487
21482
 
21488
- Retired — these are rejected by name, they do not fall through to a generic error:
21483
+ **Flags and environment** (verify against \`--help\`):
21484
+ - \`--channel <latest|candidate>\` — release channel (default \`latest\`).
21485
+ - \`--json\` — structured output (Rule 1).
21486
+ - \`MUTAGENT_HELIX_CHANNEL\` — channel when \`--channel\` is not passed.
21487
+ - \`MUTAGENT_INSTALL_HOST\` — install host (default \`https://install.mutagent.io\`; plain \`http://\` is refused except for loopback).
21488
+ - \`MUTAGENT_INSTALL_DIR\` — install directory (default \`~/.mutagent/bin\`).
21489
+
21490
+ Retired — rejected by name, they do not fall through to a generic error:
21489
21491
 
21490
21492
  | Retired target | Was | Use instead |
21491
21493
  |---|---|---|
21492
21494
  | \`diagnostics\` | public npm \`@mutagent/diagnostics\` | \`mutagent install helix\` |
21493
21495
  | \`evaluator\` | public npm \`@mutagent/evaluator\` | \`mutagent install helix\` |
21494
21496
 
21495
- **Flags** (verify against \`--help\`):
21496
- - \`--harness <claude-code|codex|omp>\` -- target coding-agent harness (default \`claude-code\`). helix installs every harness itself, so this does not change what lands.
21497
- - \`--version <v>\` -- pin a specific version (default: latest).
21498
- - \`--json\` -- structured output (Rule 1).
21499
- - Install scope is NOT selectable: helix installs into the current directory. \`--global\` is retired — passing it fails and explains what happens instead.
21500
-
21501
21497
  ---
21502
21498
 
21503
21499
  ## Steps
21504
21500
 
21505
21501
  \`\`\`
21506
- 1. mutagent auth status --json
21507
- → confirm the user is logged in (install is login-gated)
21508
- → if not authenticated, run the login workflow first
21502
+ 1. mutagent install --help
21503
+ → read the current flags (Rule 2)
21509
21504
 
21510
- 2. mutagent install --help
21511
- → read the current packages + flags (Rule 2)
21505
+ 2. Confirm with the user WHAT will be installed and WHERE
21506
+ → "I'll install the Helix binary at ~/.mutagent/bin/helix. Proceed?"
21512
21507
 
21513
- 3. Confirm with the user WHAT will be installed and WHERE
21514
- → "I'll install helix into <current directory>. Proceed?"
21508
+ 3. mutagent install helix --json
21509
+ → add --channel candidate only if the user asked for the candidate build
21515
21510
 
21516
- 4. mutagent install helix [--harness <h>] [--version <v>] --json
21517
- → run the install
21518
- → show the output: package, version, harness, scope, and projectPath
21511
+ 4. Report from the JSON: version, channel, binaryPath
21512
+ → onPath === false → show the user pathLine and tell them to add it to their
21513
+ shell profile. Do NOT edit their rc files yourself.
21519
21514
 
21520
- 5. CHECK \`skillInstalled\` in the JSON response — do NOT stop at \`success: true\`
21521
- → skillInstalled === true → done; the skill is installed and usable
21522
- → skillInstalled === false → NOT done. Say so plainly and surface \`nextStep\`
21523
- as the command to run next. Confirm before running it — it writes to
21524
- their project (Core Rule 5).
21515
+ 5. Tell the user the next step: nextStep ("run helix in a project")
21525
21516
  \`\`\`
21526
21517
 
21527
21518
  ---
@@ -21530,8 +21521,7 @@ Retired — these are rejected by name, they do not fall through to a generic er
21530
21521
 
21531
21522
  \`\`\`bash
21532
21523
  mutagent install helix --json
21533
- mutagent install helix --harness codex --json
21534
- mutagent install helix --version 1.2.3 --json
21524
+ mutagent install helix --channel candidate --json
21535
21525
  \`\`\`
21536
21526
 
21537
21527
  ---
@@ -21544,35 +21534,46 @@ Success shape:
21544
21534
  {
21545
21535
  "success": true,
21546
21536
  "package": "helix",
21547
- "version": "0.1.0-alpha.25",
21548
- "harness": "claude-code",
21549
- "scope": "project",
21550
- "projectPath": "/path/to/the/users/project",
21551
- "skillInstalled": true,
21552
- "_links": { "install": "...", "login": "..." }
21537
+ "channel": "latest",
21538
+ "version": "0.0.32-m0",
21539
+ "binaryPath": "/Users/me/.mutagent/bin/helix",
21540
+ "aliasPath": "/Users/me/.mutagent/bin/mutagent-helix",
21541
+ "sha256": "95e0b562…",
21542
+ "onPath": false,
21543
+ "pathLine": "export PATH=\\"/Users/me/.mutagent/bin:$PATH\\"",
21544
+ "nextStep": "run helix in a project",
21545
+ "telemetry": "sent",
21546
+ "_links": {
21547
+ "installer": "https://install.mutagent.io/helix",
21548
+ "checksums": "https://install.mutagent.io/latest/checksums.txt"
21549
+ }
21553
21550
  }
21554
21551
  \`\`\`
21555
21552
 
21556
- - **\`skillInstalled\` is the field to branch on — NOT \`success\`.** \`success: true\`
21557
- reports that the install step did its job, not that the user is ready.
21558
- \`nextStep\` is present only when work remains; on a completed helix install it
21559
- is absent.
21560
- - \`skillInstalled: true\`: the CLI resolved a signed download URL from the login
21561
- broker, downloaded + sha256-verified the plugin, and ran its init into the
21562
- project. Nothing further is needed. An \`INTEGRITY_ERROR\` means the download
21563
- failed checksum verification — retry.
21564
- - **\`scope\` tells you WHERE it went. Read it; do not assume.** It is \`"project"\`,
21565
- and \`projectPath\` is the directory. Surface it so the user can confirm the
21566
- location.
21567
- - On an auth error (including a broker \`AUTH_REQUIRED\`): route to the login
21568
- workflow, then retry.
21569
- - On \`INVALID_ARGUMENTS\` naming a retired target (\`diagnostics\`, \`evaluator\`):
21570
- the package is gone from npm and the skill ships inside helix. Tell the user
21571
- that, then offer \`mutagent install helix\`. Do NOT suggest \`npm install
21572
- @mutagent/diagnostics\` as a fallback — it will 404.
21573
- - On \`INVALID_ARGUMENTS\` naming \`--global\`: the flag is retired. Re-run without
21574
- it, or \`cd\` to the intended project first — the error names the path that
21575
- would have been used.
21553
+ - \`version\` is what the installed binary printed for \`helix --version\`.
21554
+ - \`pathLine\` is present only when \`onPath\` is false.
21555
+ - \`telemetry\` is \`sent\`, \`skipped\` (not signed in) or \`failed\`; telemetry never
21556
+ changes the install result, so do not retry the install over it.
21557
+ - \`aliasPath\` is absent when a regular file named \`mutagent-helix\` already
21558
+ exists in the directory; the installer leaves that file alone.
21559
+
21560
+ Errors (all carry \`suggestedAction\`; surface it):
21561
+
21562
+ | Code | Meaning | What to do |
21563
+ |---|---|---|
21564
+ | \`INSTALL_UNSUPPORTED_PLATFORM\` | No build for this OS/arch | macOS or Linux on x64/arm64; on Windows use WSL |
21565
+ | \`INSTALL_ASSET_MISSING\` | The channel lists no build for this machine | Try the other channel |
21566
+ | \`INSTALL_DOWNLOAD_FAILED\` | \`checksums.txt\` or the build could not be fetched, or the download broke off; nothing was installed | Check the network, retry |
21567
+ | \`INTEGRITY_ERROR\` | sha256 mismatch; nothing was installed and an existing helix is left as it was | Retry |
21568
+ | \`INSTALL_WRITE_FAILED\` | The install directory is not writable, or the disk is full | Set \`MUTAGENT_INSTALL_DIR\` to a writable directory |
21569
+ | \`INSTALL_ALIAS_FAILED\` | helix is installed, but the \`mutagent-helix\` alias could not be written | Remove what the message names, retry; \`helix\` itself already works |
21570
+ | \`INSTALL_VERIFY_FAILED\` | The binary was written but \`--version\` failed | Run the \`helix doctor\` command it names |
21571
+ | \`INVALID_ARGUMENTS\` | Unknown channel, insecure host, unknown or retired package | Fix the input; for a retired target, offer \`mutagent install helix\` |
21572
+
21573
+ On \`INVALID_ARGUMENTS\` naming a retired target (\`diagnostics\`, \`evaluator\`): the
21574
+ package is gone from npm and the skill ships inside helix. Tell the user that,
21575
+ then offer \`mutagent install helix\`. Do NOT suggest \`npm install
21576
+ @mutagent/diagnostics\` as a fallback — it will 404.
21576
21577
 
21577
21578
  ---
21578
21579
 
@@ -21581,21 +21582,17 @@ Success shape:
21581
21582
  - **Telling the user to \`npm install @mutagent/diagnostics\` or
21582
21583
  \`@mutagent/evaluator\`.** Both are unpublished; the install 404s. The skills
21583
21584
  are inside helix.
21584
- - **Treating a retired-target error as a typo.** \`mutagent install diagnostics\`
21585
- is a command that used to work. The user is not confused about spelling —
21586
- they want diagnostics. Route them to helix, which has it.
21587
- - **Treating \`success: true\` as "the user is ready".** Check \`skillInstalled\`.
21588
- - Running before login → auth error (install is login-gated).
21589
- - Assuming \`helix\` installs from public npm — it is fetched from a private registry via a login-brokered signed URL (the CLI holds no static secret).
21585
+ - **Treating a retired-target error as a typo.** The user wants diagnostics;
21586
+ route them to helix, which has it.
21587
+ - **Editing the user's shell rc files.** Show \`pathLine\` and let the user add it.
21590
21588
  - Installing without confirming with the user first (Core Rule 5).
21591
- - Passing \`--global\` — retired; scope is not selectable.
21592
21589
 
21593
21590
  ---
21594
21591
 
21595
21592
  ## Cross-references
21596
21593
 
21597
21594
  - [SKILL.md](../SKILL.md) → Core Rules + Task Router
21598
- - [workflows/setup.md](./setup.md) → login (prerequisite for install)
21595
+ - [workflows/setup.md](./setup.md) → login and project setup
21599
21596
  `,
21600
21597
  "workflows/setup.md": `---
21601
21598
  name: mutagent-cli-workflows-setup
@@ -21743,7 +21740,7 @@ confirm before running (Core Rule 5).
21743
21740
  ## Cross-references
21744
21741
 
21745
21742
  - [SKILL.md](../SKILL.md) → Core Rules + Task Router
21746
- - [workflows/install.md](./install.md) → install helix / diagnostics / evaluator packages
21743
+ - [workflows/install.md](./install.md) → install the standalone Helix binary (diagnostics / evaluator are retired)
21747
21744
  `
21748
21745
  };
21749
21746
  var SKILL_CONTENT = SKILL_FILES["SKILL.md"] ?? "";
@@ -21799,7 +21796,7 @@ function installCliSkill(targetRoot) {
21799
21796
  const finalContent = raw.endsWith(`
21800
21797
  `) ? raw : `${raw}
21801
21798
  `;
21802
- writeFileSync4(destPath, finalContent, "utf-8");
21799
+ writeFileSync3(destPath, finalContent, "utf-8");
21803
21800
  writtenFiles.push({ relPath, destPath, bytes: finalContent.length });
21804
21801
  totalBytes += finalContent.length;
21805
21802
  }
@@ -21885,7 +21882,7 @@ It never prompts. Without a sign-in it prints what to run and exits.
21885
21882
  let rcConfig;
21886
21883
  if (rcExists) {
21887
21884
  try {
21888
- rcConfig = JSON.parse(readFileSync6(join14(cwd, ".mutagentrc.json"), "utf-8"));
21885
+ rcConfig = JSON.parse(readFileSync5(join14(cwd, ".mutagentrc.json"), "utf-8"));
21889
21886
  } catch {
21890
21887
  rcConfig = {};
21891
21888
  }
@@ -21956,7 +21953,7 @@ It never prompts. Without a sign-in it prints what to run and exits.
21956
21953
  } else {
21957
21954
  console.log("");
21958
21955
  console.log(chalk14.bold(` What's next:`));
21959
- console.log(` ${chalk14.cyan("mutagent install helix".padEnd(30))}${chalk14.dim("# the ADL lifecycle conductor")}`);
21956
+ console.log(` ${chalk14.cyan("mutagent install helix".padEnd(30))}${chalk14.dim("# the standalone Helix binary")}`);
21960
21957
  console.log("");
21961
21958
  console.log(chalk14.dim(" Your coding agent now knows these workflows — restart it to pick up"));
21962
21959
  console.log(chalk14.dim(' the skill, then just say "set up Mutagent on this project".'));
@@ -22031,7 +22028,7 @@ Examples:
22031
22028
  }
22032
22029
 
22033
22030
  // src/commands/hooks/index.ts
22034
- import { readFileSync as readFileSync9 } from "node:fs";
22031
+ import { readFileSync as readFileSync8 } from "node:fs";
22035
22032
  import { basename as basename4 } from "node:path";
22036
22033
  import { Command as Command10 } from "commander";
22037
22034
  import chalk16 from "chalk";
@@ -22180,7 +22177,7 @@ async function sendBatchTrace(traces) {
22180
22177
  import { randomUUID as randomUUID2 } from "crypto";
22181
22178
 
22182
22179
  // src/commands/hooks/state.ts
22183
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, renameSync as renameSync2, unlinkSync, existsSync as existsSync9 } from "fs";
22180
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, renameSync as renameSync2, unlinkSync, existsSync as existsSync9 } from "fs";
22184
22181
  import { join as join15 } from "path";
22185
22182
  import { tmpdir as tmpdir3 } from "os";
22186
22183
  function stateFilePath(sessionId) {
@@ -22191,7 +22188,7 @@ function readState(sessionId) {
22191
22188
  if (!existsSync9(path))
22192
22189
  return null;
22193
22190
  try {
22194
- const raw = JSON.parse(readFileSync7(path, "utf-8"));
22191
+ const raw = JSON.parse(readFileSync6(path, "utf-8"));
22195
22192
  if (!Array.isArray(raw.parentStack)) {
22196
22193
  raw.parentStack = [];
22197
22194
  }
@@ -22206,7 +22203,7 @@ function readState(sessionId) {
22206
22203
  function writeState(sessionId, state) {
22207
22204
  const path = stateFilePath(sessionId);
22208
22205
  const tmpPath = `${path}.${process.pid.toString()}.tmp`;
22209
- writeFileSync5(tmpPath, JSON.stringify(state), "utf-8");
22206
+ writeFileSync4(tmpPath, JSON.stringify(state), "utf-8");
22210
22207
  renameSync2(tmpPath, path);
22211
22208
  }
22212
22209
  function deleteState(sessionId) {
@@ -22819,7 +22816,7 @@ async function handlePostToolUseFailure() {
22819
22816
  }
22820
22817
 
22821
22818
  // src/commands/hooks/install.ts
22822
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
22819
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync5, existsSync as existsSync10, mkdirSync as mkdirSync4 } from "fs";
22823
22820
  import { join as join16 } from "path";
22824
22821
 
22825
22822
  class SettingsParseError extends Error {
@@ -22887,12 +22884,12 @@ function installHooks(cwd) {
22887
22884
  const existed = existsSync10(settingsPath);
22888
22885
  let settings = {};
22889
22886
  if (existed) {
22890
- const raw = readFileSync8(settingsPath, "utf-8");
22887
+ const raw = readFileSync7(settingsPath, "utf-8");
22891
22888
  try {
22892
22889
  settings = JSON.parse(raw);
22893
22890
  } catch (err) {
22894
22891
  const backupPath = `${settingsPath}.bak.${new Date().toISOString().replace(/:/g, "-")}`;
22895
- writeFileSync6(backupPath, raw, "utf-8");
22892
+ writeFileSync5(backupPath, raw, "utf-8");
22896
22893
  throw new SettingsParseError(settingsPath, backupPath, err);
22897
22894
  }
22898
22895
  }
@@ -22925,7 +22922,7 @@ function installHooks(cwd) {
22925
22922
  if (!existsSync10(claudeDir)) {
22926
22923
  mkdirSync4(claudeDir, { recursive: true });
22927
22924
  }
22928
- writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + `
22925
+ writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + `
22929
22926
  `, "utf-8");
22930
22927
  }
22931
22928
  if (added.length > 0) {
@@ -22991,7 +22988,7 @@ unparseable lines without sending anything.
22991
22988
  for (const file of files) {
22992
22989
  let raw;
22993
22990
  try {
22994
- raw = readFileSync9(file, "utf8");
22991
+ raw = readFileSync8(file, "utf8");
22995
22992
  } catch (err) {
22996
22993
  const why = err instanceof Error ? err.message : String(err);
22997
22994
  report.push({ file: basename4(file), skipped: why });
@@ -23161,7 +23158,7 @@ Safe to run again.
23161
23158
  import { Command as Command11 } from "commander";
23162
23159
  import chalk19 from "chalk";
23163
23160
  import { type as osType, release as osRelease } from "os";
23164
- import { readFileSync as readFileSync11 } from "fs";
23161
+ import { readFileSync as readFileSync10 } from "fs";
23165
23162
  import { join as join18, dirname as dirname5 } from "path";
23166
23163
  import { fileURLToPath as fileURLToPath3 } from "url";
23167
23164
  init_errors();
@@ -23174,7 +23171,7 @@ import { join as join17 } from "path";
23174
23171
  import {
23175
23172
  existsSync as fsExistsSync,
23176
23173
  statSync as fsStatSync,
23177
- readFileSync as readFileSync10,
23174
+ readFileSync as readFileSync9,
23178
23175
  readdirSync,
23179
23176
  openSync,
23180
23177
  readSync,
@@ -23185,7 +23182,7 @@ var TAIL_BYTES = 200000;
23185
23182
  function defaultReadTail(path, tailBytes) {
23186
23183
  const { size } = fsStatSync(path);
23187
23184
  if (size <= tailBytes) {
23188
- return { content: readFileSync10(path, "utf-8"), truncated: false };
23185
+ return { content: readFileSync9(path, "utf-8"), truncated: false };
23189
23186
  }
23190
23187
  const fd = openSync(path, "r");
23191
23188
  try {
@@ -23418,7 +23415,7 @@ var BY_DESIGN_ENTRIES = [
23418
23415
  id: "mutagent-helix-not-on-npm",
23419
23416
  title: "@mutagent/helix is not published to npm — on purpose",
23420
23417
  looksLike: pairPatterns(HELIX_PKG, NOT_FOUND),
23421
- reality: "Helix is not distributed through the public npm registry, so a 404 there is expected. It is delivered by the Mutagent CLI itself, which authenticates you, fetches a signed download from private storage, verifies its checksum, and initializes it into your project. Install it with: mutagent install helix"
23418
+ reality: "Helix is not distributed through the public npm registry, so a 404 there is expected. It ships as a standalone binary from install.mutagent.io, verified against the checksums published beside it. Install it with: mutagent install helix"
23422
23419
  },
23423
23420
  {
23424
23421
  id: "trace-lives-in-a-different-binary",
@@ -23427,7 +23424,7 @@ var BY_DESIGN_ENTRIES = [
23427
23424
  ...pairPatterns(String.raw`mutagent\s+trace`, NOT_A_COMMAND),
23428
23425
  /\btrace\b[^\n]{0,40}?(?:not\s+a\s+(?:valid\s+)?(?:sub)?command|unknown\s+command)/i
23429
23426
  ],
23430
- reality: "trace is not a subcommand of mutagent. It lives in helix-cli — a different binary — which ships inside helix. Install it with: mutagent install helix, then run: helix-cli trace <args>"
23427
+ reality: "trace is not a subcommand of mutagent. It lives in helix-cli — a different binary — which ships inside helix and is on the PATH of every helix session. Install helix with: mutagent install helix, then in a helix session run: helix-cli trace <args>"
23431
23428
  },
23432
23429
  {
23433
23430
  id: "two-separate-binaries",
@@ -23437,7 +23434,7 @@ var BY_DESIGN_ENTRIES = [
23437
23434
  /(?:helix-cli|mutagent-cli)\s+(?:binary|executable|command)[^\n]{0,60}?(?:missing|not\s+found|doesn'?t\s+exist|does\s+not\s+exist|not\s+installed)/i,
23438
23435
  new RegExp(String.raw`no\s+(?:helix-cli|mutagent-cli)[^\n]{0,40}?in\s+@mutagent\/cli`, "i")
23439
23436
  ],
23440
- reality: "mutagent and helix-cli are separate binaries that come from separate packages. @mutagent/cli provides the mutagent binary only — account, configuration, and installation commands. The helix-cli binary ships inside helix and carries the trace tooling. Having both installed is normal and expected."
23437
+ reality: "mutagent and helix-cli are separate binaries that come from separate packages. @mutagent/cli provides the mutagent binary only — account, configuration, and installation commands. The helix-cli binary ships inside helix, is on the PATH of every helix session, and carries the trace tooling. Having both installed is normal and expected."
23441
23438
  }
23442
23439
  ];
23443
23440
  function matchByDesign(draft) {
@@ -23548,7 +23545,7 @@ function getCliVersion2() {
23548
23545
  try {
23549
23546
  const __dirname2 = dirname5(fileURLToPath3(import.meta.url));
23550
23547
  const pkgPath = join18(__dirname2, "..", "..", "package.json");
23551
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
23548
+ const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
23552
23549
  return pkg.version ?? "0.1.1";
23553
23550
  } catch {
23554
23551
  return "0.1.1";
@@ -23747,45 +23744,73 @@ import { Command as Command12 } from "commander";
23747
23744
  import chalk20 from "chalk";
23748
23745
  init_errors();
23749
23746
  function createInstallCommand(deps) {
23750
- const install = new Command12("install").description("Install a Mutagent package on this machine (helix)").argument("<package>", `Package to install: ${VALID_PACKAGES.join(", ")}`).option("--harness <harness>", `Harness named in the output and the install report: ${VALID_HARNESSES.join(", ")}`, "claude-code").option("--global", "Refused: helix always installs into the current directory").option("--version <version>", "Package version to install (default: latest)").addHelpText("after", `
23747
+ const install = new Command12("install").description("Install the standalone Helix binary on this machine").argument("<package>", `Package to install: ${VALID_PACKAGES.join(", ")}`).option("--channel <channel>", `Release channel: ${HELIX_CHANNELS.join(" or ")} (default: latest)`).addHelpText("after", `
23751
23748
  ${chalk20.bold("Examples:")}
23752
23749
  ${chalk20.dim("$")} mutagent install helix
23753
- ${chalk20.dim("$")} mutagent install helix --version <version>
23750
+ ${chalk20.dim("$")} mutagent install helix --channel candidate
23754
23751
  ${chalk20.dim("$")} mutagent install helix --json
23755
23752
 
23756
- ${chalk20.bold("helix")}: the Helix agent-development toolkit, set up in the current directory for
23757
- Claude Code and Codex (includes the diagnostics and evaluator skills and helix-cli).
23758
- Requires \`mutagent login\`. This installs Helix locally; \`mutagent helix\` runs it in the cloud.
23759
- --harness does not change what is installed. --global exits with an error.
23760
- `).action(async (pkg, options) => {
23753
+ ${chalk20.bold("helix")}: the standalone Helix binary, the same one
23754
+ \`curl -fsSL https://install.mutagent.io/helix | bash\` installs. It downloads
23755
+ helix-<os>-<arch> for macOS or Linux (x64, arm64), verifies it against
23756
+ checksums.txt from the same origin, writes ~/.mutagent/bin/helix and points the
23757
+ mutagent-helix alias at it, then runs helix --version. No login is needed; the
23758
+ CLI records the install activation when you are signed in, and never fails the
23759
+ install over it.
23760
+ Shell rc files are never changed: when the directory is not on PATH, the line to
23761
+ add is printed. This installs Helix on this machine; mutagent helix runs it in
23762
+ the cloud.
23763
+
23764
+ ${chalk20.bold("Environment:")}
23765
+ MUTAGENT_HELIX_CHANNEL latest or candidate; --channel wins
23766
+ MUTAGENT_INSTALL_HOST install host (default: https://install.mutagent.io)
23767
+ MUTAGENT_INSTALL_DIR install directory (default: ~/.mutagent/bin)
23768
+
23769
+ AI Agent Directive:
23770
+ This writes a binary to the user's machine: confirm with the user first.
23771
+ Read binaryPath, version and onPath from --json. When onPath is false, show
23772
+ the user pathLine; do not edit their shell rc files yourself.
23773
+ telemetry in --json is sent, skipped (not signed in) or failed; it never
23774
+ changes the install result.
23775
+ Then tell the user to run helix in a project.
23776
+ `).action(async (pkg, options) => {
23761
23777
  const isJson = getJsonFlag(install);
23762
23778
  const output = new OutputFormatter(isJson ? "json" : "table");
23763
23779
  try {
23764
- const result = await installPackage(pkg, { harness: options.harness, global: options.global, version: options.version }, deps);
23780
+ assertInstallTarget(pkg);
23781
+ if (!isJson)
23782
+ console.log(chalk20.dim(`Downloading and verifying ${pkg}…`));
23783
+ const result = await installPackage(pkg, { channel: options.channel }, deps);
23765
23784
  if (isJson) {
23766
23785
  output.output({
23767
23786
  success: true,
23768
23787
  package: result.package,
23788
+ channel: result.channel,
23769
23789
  version: result.version,
23770
- harness: result.harness,
23771
- scope: result.scope,
23772
- ...result.projectPath ? { projectPath: result.projectPath } : {},
23773
- skillInstalled: result.skillInstalled,
23774
- ...result.nextStep ? { nextStep: result.nextStep } : {},
23790
+ binaryPath: result.binaryPath,
23791
+ ...result.aliasPath !== undefined ? { aliasPath: result.aliasPath } : {},
23792
+ sha256: result.sha256,
23793
+ onPath: result.onPath,
23794
+ ...result.pathLine !== undefined ? { pathLine: result.pathLine } : {},
23795
+ nextStep: result.nextStep,
23796
+ telemetry: result.telemetry,
23775
23797
  _links: {
23776
- install: "mutagent install <package> [--harness <h>] [--version <v>]",
23777
- login: "mutagent login"
23798
+ installer: result.installerUrl,
23799
+ checksums: result.checksumsUrl
23778
23800
  }
23779
23801
  });
23780
- } else if (result.skillInstalled) {
23781
- output.success(`Installed ${result.package}@${result.version} for ${result.harness} in ${result.projectPath ?? "this project"}`);
23782
- } else {
23783
- output.success(`Installed ${result.package}@${result.version}, but the skill is not in place yet`);
23784
- console.log(`${chalk20.yellow("⚠")} No skill was installed yet — a setup step still has to run.`);
23785
- if (result.nextStep) {
23786
- console.log(`${chalk20.blue("→")} Run next: ${chalk20.cyan(result.nextStep)}`);
23787
- }
23802
+ return;
23803
+ }
23804
+ output.success(`Installed helix ${result.version} (${result.channel}) → ${result.binaryPath}`);
23805
+ console.log(chalk20.dim(` sha256 ${result.sha256}`));
23806
+ if (result.aliasPath !== undefined) {
23807
+ console.log(chalk20.dim(` alias ${result.aliasPath}`));
23788
23808
  }
23809
+ if (result.pathLine !== undefined) {
23810
+ console.log(`${chalk20.yellow("⚠")} The install directory is not on your PATH. Add this line to your shell profile:`);
23811
+ console.log(` ${chalk20.cyan(result.pathLine)}`);
23812
+ }
23813
+ console.log(`${chalk20.blue("→")} Next: ${result.nextStep}`);
23789
23814
  } catch (error3) {
23790
23815
  handleError(error3, isJson);
23791
23816
  }
@@ -23796,11 +23821,12 @@ Requires \`mutagent login\`. This installs Helix locally; \`mutagent helix\` run
23796
23821
  // src/commands/trace.ts
23797
23822
  import { Command as Command13 } from "commander";
23798
23823
  init_errors();
23799
- var TRACE_RELOCATION_HINT = "trace lives in the `helix-cli` binary, which ships inside helix (install it with: mutagent install helix)";
23824
+ var TRACE_RELOCATION_HINT = "trace lives in the `helix-cli` binary, which ships inside helix and is on the PATH of every helix session (install helix with: mutagent install helix)";
23800
23825
  function createTraceCommand() {
23801
23826
  const trace = new Command13("trace").description("Not in this binary — trace lives in helix-cli").allowUnknownOption(true).allowExcessArguments(true).argument("[args...]", "Arguments intended for `helix-cli trace`").addHelpText("after", `
23802
- mutagent does not run trace. trace is a command of helix-cli, which
23803
- mutagent install helix installs. Run:
23827
+ mutagent does not run trace. trace is a command of helix-cli, which ships
23828
+ inside helix (mutagent install helix installs it) and is on the PATH of every
23829
+ helix session. In a helix session, run:
23804
23830
 
23805
23831
  helix-cli trace <args>
23806
23832
 
@@ -23808,8 +23834,8 @@ This command only prints where trace is and exits with an error.
23808
23834
  `).action((args) => {
23809
23835
  const isJson = getJsonFlag(trace);
23810
23836
  const forwarded = args.length > 0 ? ` ${args.join(" ")}` : " <args>";
23811
- handleError(new MutagentError("INVALID_ARGUMENTS", `\`mutagent trace\` is not a command — ${TRACE_RELOCATION_HINT}.`, `Run it with the other binary instead: helix-cli trace${forwarded}
23812
- If helix-cli is not installed: mutagent install helix`), isJson);
23837
+ handleError(new MutagentError("INVALID_ARGUMENTS", `\`mutagent trace\` is not a command — ${TRACE_RELOCATION_HINT}.`, `Run it with the other binary, in a helix session: helix-cli trace${forwarded}
23838
+ If helix is not installed: mutagent install helix`), isJson);
23813
23839
  });
23814
23840
  return trace;
23815
23841
  }
@@ -23824,8 +23850,8 @@ init_errors();
23824
23850
  // src/commands/helix/agent-argv.ts
23825
23851
  init_errors();
23826
23852
  import { homedir as homedir5 } from "os";
23827
- import { basename as basename6, join as join19, isAbsolute as isAbsolute6, resolve as resolve7 } from "path";
23828
- import { accessSync as accessSync3, constants as constants3, existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "fs";
23853
+ import { basename as basename6, join as join19, isAbsolute as isAbsolute6, resolve as resolve8 } from "path";
23854
+ import { accessSync as accessSync3, constants as constants3, existsSync as existsSync11, readFileSync as readFileSync11, statSync as statSync4 } from "fs";
23829
23855
 
23830
23856
  // src/commands/helix/argv-transport.ts
23831
23857
  var HOST_PATH = "it names a path on THIS machine, and the sandbox is a different machine";
@@ -24137,7 +24163,7 @@ function redactCommanderError(text) {
24137
24163
 
24138
24164
  // src/commands/helix/planned-flags.ts
24139
24165
  init_errors();
24140
- import { basename as basename5, isAbsolute as isAbsolute5, resolve as resolve6 } from "path";
24166
+ import { basename as basename5, isAbsolute as isAbsolute5, resolve as resolve7 } from "path";
24141
24167
  var OWN_FLAGS = {
24142
24168
  "--sandbox": true,
24143
24169
  "--preset": true,
@@ -24184,7 +24210,7 @@ function unreadableNotice(flag, name) {
24184
24210
  return `${flag}: ${name} could not be read, so the value is sent as literal text, as helix does locally.`;
24185
24211
  }
24186
24212
  function inlinePromptFile(flag, value2, deps) {
24187
- const path = isAbsolute5(value2) ? value2 : resolve6(deps.cwd, value2);
24213
+ const path = isAbsolute5(value2) ? value2 : resolve7(deps.cwd, value2);
24188
24214
  const name = basename5(path);
24189
24215
  if (deps.fileExists(path)) {
24190
24216
  let text;
@@ -24228,7 +24254,7 @@ function defaultPlanDeps() {
24228
24254
  return {
24229
24255
  cwd: process.cwd(),
24230
24256
  env: process.env,
24231
- readTextFile: (path) => readFileSync12(path, "utf8"),
24257
+ readTextFile: (path) => readFileSync11(path, "utf8"),
24232
24258
  fileExists: (path) => isRegularFile(path) && canRead(path),
24233
24259
  unreadable: (path) => isRegularFile(path) && !canRead(path)
24234
24260
  };
@@ -24393,7 +24419,7 @@ function resolveDefinition(source2, deps) {
24393
24419
  return { prompt, origin: "--prompt (inline)" };
24394
24420
  }
24395
24421
  if (source2.kind === "file") {
24396
- const path2 = isAbsolute6(source2.value) ? source2.value : resolve7(deps.cwd, source2.value);
24422
+ const path2 = isAbsolute6(source2.value) ? source2.value : resolve8(deps.cwd, source2.value);
24397
24423
  if (deps.unreadable?.(path2) === true) {
24398
24424
  throw new MutagentError("INVALID_ARGUMENTS", `agent file is not readable: ${basename6(path2)}`, "Check its permissions. The file is read on THIS machine, and its contents travel.", 2);
24399
24425
  }
@@ -24518,7 +24544,7 @@ init_errors();
24518
24544
  init_secure_file();
24519
24545
  init_errors();
24520
24546
  init_known_secrets();
24521
- import { existsSync as existsSync12, readFileSync as readFileSync13, mkdirSync as mkdirSync5, rmSync as rmSync2 } from "fs";
24547
+ import { existsSync as existsSync12, readFileSync as readFileSync12, mkdirSync as mkdirSync5, rmSync as rmSync2 } from "fs";
24522
24548
  import { homedir as homedir6 } from "os";
24523
24549
  import { join as join20, dirname as dirname6 } from "path";
24524
24550
  function sandboxTokenFile() {
@@ -24538,7 +24564,7 @@ function createFileTokenStore(path) {
24538
24564
  if (!existsSync12(path))
24539
24565
  return null;
24540
24566
  try {
24541
- const parsed = JSON.parse(readFileSync13(path, "utf-8"));
24567
+ const parsed = JSON.parse(readFileSync12(path, "utf-8"));
24542
24568
  return isCacheShape(parsed) ? parsed : null;
24543
24569
  } catch {
24544
24570
  return null;
@@ -24665,7 +24691,7 @@ async function stoppingRetryDelayMs(response) {
24665
24691
  const seconds = Number.isFinite(header) && header >= 0 ? header : 5;
24666
24692
  return Math.min(seconds * 1000, MAX_STOPPING_RETRY_MS);
24667
24693
  }
24668
- async function withStoppingRetry(send, sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))) {
24694
+ async function withStoppingRetry(send, sleep2 = (ms) => new Promise((resolve9) => setTimeout(resolve9, ms))) {
24669
24695
  const first = await send();
24670
24696
  const delayMs = await stoppingRetryDelayMs(first);
24671
24697
  if (delayMs === null)
@@ -25390,18 +25416,18 @@ function isGapError(payload) {
25390
25416
  return payload.code === "gap";
25391
25417
  }
25392
25418
  function delay(ms, signal) {
25393
- return new Promise((resolve8) => {
25419
+ return new Promise((resolve9) => {
25394
25420
  if (signal.aborted) {
25395
- resolve8();
25421
+ resolve9();
25396
25422
  return;
25397
25423
  }
25398
25424
  const timer = setTimeout(() => {
25399
25425
  signal.removeEventListener("abort", onAbort);
25400
- resolve8();
25426
+ resolve9();
25401
25427
  }, ms);
25402
25428
  function onAbort() {
25403
25429
  clearTimeout(timer);
25404
- resolve8();
25430
+ resolve9();
25405
25431
  }
25406
25432
  signal.addEventListener("abort", onAbort, { once: true });
25407
25433
  });
@@ -27410,7 +27436,7 @@ function createHelixCommand() {
27410
27436
  " Choose -p, --mode json or --mode rpc explicitly.",
27411
27437
  " Without --prime the full Helix orchestrator runs; --prime runs the lean Prime agent.",
27412
27438
  " Keep the session reference (hs1_...) printed on stderr; attach, send, signal and checkpoint take it.",
27413
- " mutagent install helix installs Helix on this machine; this command runs it in the cloud."
27439
+ " mutagent install helix installs the standalone Helix binary on this machine; this command runs it in the cloud."
27414
27440
  ].join(`
27415
27441
  `)).action(async (argv, _options, command) => {
27416
27442
  try {
@@ -28168,7 +28194,7 @@ Requires a workspace: mutagent config set workspace <workspace-id>
28168
28194
  // src/commands/env/index.ts
28169
28195
  import { Command as Command17 } from "commander";
28170
28196
  import chalk36 from "chalk";
28171
- import { readFileSync as readFileSync14 } from "fs";
28197
+ import { readFileSync as readFileSync13 } from "fs";
28172
28198
  init_errors();
28173
28199
 
28174
28200
  // src/lib/environment-api.ts
@@ -28482,7 +28508,7 @@ function readOptionalFile(path) {
28482
28508
  if (path === undefined)
28483
28509
  return;
28484
28510
  try {
28485
- return readFileSync14(path, "utf8");
28511
+ return readFileSync13(path, "utf8");
28486
28512
  } catch {
28487
28513
  throw new MutagentError("FILE_NOT_FOUND", `Could not read env file: ${path}`, "Check the path. The file is read on THIS machine and its entries are sent; it is never uploaded as-is.", 2);
28488
28514
  }
@@ -29072,7 +29098,7 @@ if (process.env.CLI_VERSION) {
29072
29098
  try {
29073
29099
  const __dirname2 = dirname7(fileURLToPath4(import.meta.url));
29074
29100
  const pkgPath = join21(__dirname2, "..", "..", "package.json");
29075
- const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
29101
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
29076
29102
  cliVersion = pkg.version ?? cliVersion;
29077
29103
  } catch {}
29078
29104
  }
@@ -29091,7 +29117,7 @@ ${chalk37.yellow("Get started:")}
29091
29117
  mutagent login ${chalk37.dim("Sign in (browser or API key)")}
29092
29118
  mutagent providers mirror ${chalk37.dim("Copy local Helix LLM provider keys into the workspace")}
29093
29119
  mutagent helix -p "<task>" ${chalk37.dim("Run Helix in the cloud")}
29094
- mutagent install helix ${chalk37.dim("Install Helix on this machine")}
29120
+ mutagent install helix ${chalk37.dim("Install the standalone Helix binary on this machine")}
29095
29121
 
29096
29122
  ${chalk37.yellow("Managed agents:")}
29097
29123
  mutagent agent deploy <agent.md> ${chalk37.dim("Check, upload and activate an agent.md folder (--env names the slot)")}
@@ -29215,4 +29241,4 @@ if (isAgentInvocation(rawArgs)) {
29215
29241
  program.parse(argvForCommander, { from: "user" });
29216
29242
  }
29217
29243
 
29218
- //# debugId=9E0A0E4D9358B89864756E2164756E21
29244
+ //# debugId=093DD78045AEB58864756E2164756E21