@outerlayer/cli 0.34.0-next.20261025180645 → 0.34.0-next.20261025210240

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.
@@ -1 +1 @@
1
- {"id":"2026-10-25T18:08:18.697Z+424fcf9","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
1
+ {"id":"2026-10-25T21:03:51.173Z+0cb29f4","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
@@ -1,13 +1,67 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { launcherPath, writeLauncher, LauncherWriteError, pathNotes } from './chunk-OS7DJOQJ.js';
4
- import { currentVersion, currentLink, isInLayout, packageRootIn, versionDir, switchCurrent, execStartLine } from './chunk-UMYYYF7B.js';
3
+ import { launcherPath, pathNotes, writeLauncher, LauncherWriteError } from './chunk-OS7DJOQJ.js';
4
+ import { currentVersion, currentLink, versionDir, execStartLine, isInLayout, packageRootIn, switchCurrent } from './chunk-UMYYYF7B.js';
5
5
  import { compareVersions } from './chunk-TQMI335S.js';
6
- import { RUNNER_DIR_MODE } from './chunk-PXGU3GKY.js';
7
- import { realpathSync, readFileSync, mkdirSync, rmSync, cpSync, existsSync } from 'fs';
6
+ import { runnerDir, RUNNER_DIR_MODE } from './chunk-PXGU3GKY.js';
7
+ import { lchownSync, lstatSync, realpathSync, readFileSync, mkdirSync, rmSync, cpSync, existsSync, readdirSync } from 'fs';
8
8
  import { dirname, sep, join } from 'path';
9
+ import { spawnSync } from 'child_process';
10
+ import { userInfo } from 'os';
9
11
 
10
12
  createRequire(import.meta.url);
13
+ function idOf(flag, account) {
14
+ const result = spawnSync("id", [flag, account], { encoding: "utf8" });
15
+ const id = Number(result.stdout);
16
+ return result.status === 0 && result.stdout.trim() !== "" && Number.isInteger(id) ? id : void 0;
17
+ }
18
+ function accountIdentity(account) {
19
+ const self = userInfo();
20
+ const uid = account === self.username ? self.uid : idOf("-u", account);
21
+ const gid = account === self.username ? self.gid : idOf("-g", account);
22
+ return uid === void 0 || gid === void 0 || uid < 0 || gid < 0 ? void 0 : { uid, gid };
23
+ }
24
+ function realOwnership() {
25
+ return {
26
+ uid: () => process.getuid?.(),
27
+ ownerOf: (path) => {
28
+ const info = lstatSync(path, { throwIfNoEntry: false });
29
+ return info === void 0 ? void 0 : { uid: info.uid, gid: info.gid };
30
+ },
31
+ identity: accountIdentity,
32
+ lchown: (path, owner) => lchownSync(path, owner.uid, owner.gid)
33
+ };
34
+ }
35
+ function forAnother(find, deps) {
36
+ if (deps.uid() !== 0) return void 0;
37
+ const owner = find();
38
+ return owner !== void 0 && owner.uid !== 0 ? owner : void 0;
39
+ }
40
+ function accountNamed(account, deps) {
41
+ return forAnother(() => deps.identity(account), deps);
42
+ }
43
+ function accountOwning(dir, deps) {
44
+ return forAnother(() => deps.ownerOf(dir), deps);
45
+ }
46
+ function handOver(owner, paths, deps) {
47
+ if (owner === void 0) return void 0;
48
+ const give = (path, tree) => {
49
+ const info = lstatSync(path, { throwIfNoEntry: false });
50
+ if (info === void 0) return;
51
+ deps.lchown(path, owner);
52
+ if (tree && info.isDirectory()) for (const name of readdirSync(path)) give(join(path, name), true);
53
+ };
54
+ for (const entry of paths) {
55
+ try {
56
+ give(entry.path, entry.tree === true);
57
+ } catch (err) {
58
+ return `${entry.path} could not be given to user id ${owner.uid}, the account the runner runs as (${err instanceof Error ? err.message : String(err)}): run \`chown -hR ${owner.uid}:${owner.gid} ${entry.path}\` as root`;
59
+ }
60
+ }
61
+ return void 0;
62
+ }
63
+
64
+ // src/runner/update/install-cmd.ts
11
65
  var RunnerInstallError = class extends Error {
12
66
  };
13
67
  var RunnerDowngradeError = class extends RunnerInstallError {
@@ -42,20 +96,17 @@ function runRunnerInstall(args) {
42
96
  `this CLI is ${version}, older than ${installed}, the version the runner runs from ${currentLink(configPath)}. Installing it would move the runner back to ${version}. Run \`${launcherPath(args.home)} ${command}\` instead, which runs ${installed}. To install ${version} anyway, run \`outerlayer ${command} --allow-downgrade\``
43
97
  );
44
98
  }
45
- if (!isInLayout(configPath, entry) && !sameBuildInstalled(packageRoot, packageRootIn(versionDir(configPath, version)))) {
46
- const target = packageRootIn(versionDir(configPath, version));
47
- mkdirSync(dirname(target), { recursive: true, mode: RUNNER_DIR_MODE });
48
- rmSync(target, { recursive: true, force: true });
49
- cpSync(packageRoot, target, { recursive: true });
50
- carryHoistedDevcontainerCli(packageRoot, target);
51
- }
52
- switchCurrent(configPath, version);
99
+ const ownership = args.ownership ?? realOwnership();
100
+ const account = args.account === void 0 ? accountOwning(dirname(configPath), ownership) : accountNamed(args.account, ownership);
101
+ const homeAccount = accountOwning(args.home, ownership);
53
102
  let written;
54
103
  try {
55
- written = writeLauncher({ home: args.home, configPath, nodePath, replace: args.replace });
56
- } catch (error) {
57
- if (error instanceof LauncherWriteError) throw new RunnerInstallError(error.message);
58
- throw error;
104
+ written = placeInLayout({ configPath, nodePath, home: args.home, replace: args.replace, entry, packageRoot, version });
105
+ } finally {
106
+ const launcher = launcherPath(args.home);
107
+ const problem = handOver(account, [{ path: runnerDir(configPath) }, { path: dirname(versionDir(configPath, version)) }, { path: versionDir(configPath, version), tree: true }, { path: currentLink(configPath) }], ownership) ?? // A launcher file the runner did not write stays with its owner.
108
+ handOver(homeAccount, [{ path: join(args.home, ".local") }, { path: dirname(launcher) }, ...written === void 0 || written.state === "foreign" ? [] : [{ path: launcher }]], ownership);
109
+ if (problem !== void 0 && written !== void 0) throw new RunnerInstallError(problem);
59
110
  }
60
111
  return {
61
112
  version,
@@ -69,6 +120,23 @@ function runRunnerInstall(args) {
69
120
  }
70
121
  };
71
122
  }
123
+ function placeInLayout(input) {
124
+ const { configPath, packageRoot, version } = input;
125
+ if (!isInLayout(configPath, input.entry) && !sameBuildInstalled(packageRoot, packageRootIn(versionDir(configPath, version)))) {
126
+ const target = packageRootIn(versionDir(configPath, version));
127
+ mkdirSync(dirname(target), { recursive: true, mode: RUNNER_DIR_MODE });
128
+ rmSync(target, { recursive: true, force: true });
129
+ cpSync(packageRoot, target, { recursive: true });
130
+ carryHoistedDevcontainerCli(packageRoot, target);
131
+ }
132
+ switchCurrent(configPath, version);
133
+ try {
134
+ return writeLauncher({ home: input.home, configPath, nodePath: input.nodePath, replace: input.replace });
135
+ } catch (error) {
136
+ if (error instanceof LauncherWriteError) throw new RunnerInstallError(error.message);
137
+ throw error;
138
+ }
139
+ }
72
140
  function sameBuildInstalled(source, target) {
73
141
  try {
74
142
  const wanted = readFileSync(join(source, "dist", "build-info.json"), "utf8");
@@ -95,4 +163,4 @@ function restartNote(result) {
95
163
  return `current moved from ${result.previous} to ${result.version}: a runner its service started restarts on ${result.version} at once when its running builds are container builds that carry on through the restart. Otherwise it takes no new work and waits for them to finish. A runner started by hand keeps running until you restart it`;
96
164
  }
97
165
 
98
- export { RunnerDowngradeError, RunnerInstallError, launcherSummary, restartNote, runRunnerInstall };
166
+ export { RunnerDowngradeError, RunnerInstallError, accountIdentity, accountNamed, handOver, launcherSummary, realOwnership, restartNote, runRunnerInstall };
@@ -5177,7 +5177,7 @@ function registerRunnerCommands(program2) {
5177
5177
  });
5178
5178
  runnerCmd.command("install").description("Put this CLI in the runner's install layout; --service adds its service").option("--config <path>", configOption).option("--service", "set up the service that keeps the runner running").option("--print", "with --service, print the unit and set up nothing").option("--user <account>", "with --service, the account the service runs as").option("--replace", "replace a unit or launcher file this command did not write").option("--allow-downgrade", "install this CLI even when it is older than the version the runner runs").option("--unit-name <name>", "with --service, the unit's name, to run a second runner beside the first").option("--build-memory-max <size>", "with --service, memory all builds may use together, such as 16g").option("--build-cpus-max <n>", "with --service, CPUs all builds may use together, such as 4").action(async (opts) => {
5179
5179
  const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-QASNKYM6.js');
5180
- const { runRunnerInstall, RunnerInstallError, launcherSummary, restartNote } = await import('./install-cmd-TWGRGEVL.js');
5180
+ const { runRunnerInstall, RunnerInstallError, launcherSummary, restartNote } = await import('./install-cmd-5H6M2KJ7.js');
5181
5181
  const configPath = opts.config ?? defaultRunnerConfigPath();
5182
5182
  if (opts.service !== true) {
5183
5183
  const names = { print: "print", user: "user", unitName: "unit-name", buildMemoryMax: "build-memory-max", buildCpusMax: "build-cpus-max" };
@@ -5189,13 +5189,13 @@ function registerRunnerCommands(program2) {
5189
5189
  }
5190
5190
  }
5191
5191
  const buildCpusMax = opts.buildCpusMax === void 0 ? void 0 : Number(opts.buildCpusMax);
5192
- if (buildCpusMax !== void 0 && !(await import('./command-4XOXVWFW.js')).cpusAreValid(buildCpusMax)) {
5192
+ if (buildCpusMax !== void 0 && !(await import('./command-KFYK3WEF.js')).cpusAreValid(buildCpusMax)) {
5193
5193
  fail(`--build-cpus-max ${opts.buildCpusMax} is not a number of CPUs from 0.01 to 1024.`);
5194
5194
  return;
5195
5195
  }
5196
5196
  try {
5197
5197
  if (opts.service === true) {
5198
- const { installServiceCommand } = await import('./command-4XOXVWFW.js');
5198
+ const { installServiceCommand } = await import('./command-KFYK3WEF.js');
5199
5199
  const result2 = await installServiceCommand({
5200
5200
  configPath,
5201
5201
  print: opts.print,
@@ -5253,7 +5253,7 @@ To keep it running, run ${C.yellow}${cliInvocation()} runner install --service${
5253
5253
  return;
5254
5254
  }
5255
5255
  const { defaultRunnerConfigPath } = await import('./config-QASNKYM6.js');
5256
- const { uninstallServiceCommand } = await import('./command-4XOXVWFW.js');
5256
+ const { uninstallServiceCommand } = await import('./command-KFYK3WEF.js');
5257
5257
  const result = await uninstallServiceCommand({ configPath: opts.config ?? defaultRunnerConfigPath(), unitName: opts.unitName, home: homeFromEnv() });
5258
5258
  if (result.ok) for (const line of result.lines) process.stdout.write(`${C.green}\u2713${C.reset} ${line}
5259
5259
  `);
@@ -5403,7 +5403,7 @@ ${C.yellow}${result.note}${C.reset}
5403
5403
  To isolate builds in containers instead, run ${C.yellow}${cliInvocation()} runner init --vm${C.reset}.
5404
5404
  `);
5405
5405
  }
5406
- const { runRunnerInstall, RunnerInstallError, RunnerDowngradeError, launcherSummary } = await import('./install-cmd-TWGRGEVL.js');
5406
+ const { runRunnerInstall, RunnerInstallError, RunnerDowngradeError, launcherSummary } = await import('./install-cmd-5H6M2KJ7.js');
5407
5407
  let installedLayout = false;
5408
5408
  let noService = "it starts the runner from the install layout, which needs a CLI installed with npm";
5409
5409
  try {
@@ -5435,7 +5435,7 @@ ${C.yellow}The runner will not update itself: ${installErr.message}${C.reset}
5435
5435
  let serviceUp = false;
5436
5436
  let restart;
5437
5437
  if (installedLayout) {
5438
- const { installServiceCommand } = await import('./command-4XOXVWFW.js');
5438
+ const { installServiceCommand } = await import('./command-KFYK3WEF.js');
5439
5439
  const service = await installServiceCommand({
5440
5440
  configPath,
5441
5441
  entryPath: process.argv[1] ?? "",
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { runRunnerInstall, launcherSummary, restartNote } from './chunk-LUHIN4UZ.js';
3
+ import { realOwnership, accountNamed, handOver, runRunnerInstall, launcherSummary, restartNote, accountIdentity } from './chunk-GC5PVMIJ.js';
4
4
  import './chunk-OS7DJOQJ.js';
5
5
  import { currentLink, execStartCommand } from './chunk-UMYYYF7B.js';
6
6
  import { detectMachine, helperSocketPath, ensureFirecrackerAndJailer } from './chunk-JQSZTQSK.js';
@@ -51,12 +51,12 @@ import './chunk-YQR6E6OV.js';
51
51
  import './chunk-FGXT5OXS.js';
52
52
  import './chunk-EOTMDIYC.js';
53
53
  import './chunk-VNVZDWO3.js';
54
- import { runnerDir } from './chunk-PXGU3GKY.js';
55
- import './chunk-G4W4B33M.js';
54
+ import { runnerDir, vmAssetsDir } from './chunk-PXGU3GKY.js';
55
+ import { CLAUDE_ENV_FILE } from './chunk-G4W4B33M.js';
56
56
  import './chunk-MESOOLF4.js';
57
57
  import './chunk-Z5GEFMRW.js';
58
58
  import { userInfo, totalmem, tmpdir } from 'os';
59
- import { join, dirname } from 'path';
59
+ import { dirname, join } from 'path';
60
60
  import { createHash } from 'crypto';
61
61
  import { rmSync, existsSync, readdirSync, mkdtempSync, writeFileSync, readFileSync, statSync, lstatSync } from 'fs';
62
62
  import { spawnSync } from 'child_process';
@@ -485,11 +485,6 @@ function readHelperRanges(dir = HELPER_CONFIG_DIR) {
485
485
  }
486
486
  return ranges;
487
487
  }
488
- function idOf(flag, account) {
489
- const result = spawnSync("id", [flag, account], { encoding: "utf8" });
490
- const id = Number(result.stdout);
491
- return result.status === 0 && Number.isInteger(id) ? id : void 0;
492
- }
493
488
  function realHelperDeps() {
494
489
  const arch = firecrackerArch(process.arch);
495
490
  const pinnedArch = MICROVM_ARCHES.includes(arch) ? arch : void 0;
@@ -518,11 +513,7 @@ function realHelperDeps() {
518
513
  return void 0;
519
514
  }
520
515
  },
521
- identity: (account) => {
522
- const uid = account === userInfo().username ? userInfo().uid : idOf("-u", account);
523
- const gid = account === userInfo().username ? userInfo().gid : idOf("-g", account);
524
- return uid === void 0 || gid === void 0 || uid < 0 || gid < 0 ? void 0 : { uid, gid };
525
- },
516
+ identity: accountIdentity,
526
517
  jailBase: findJailBase,
527
518
  uidRangeTaker: idRangeTaker,
528
519
  helperRanges: () => readHelperRanges(),
@@ -587,6 +578,28 @@ function sliceOf(input, usesVm, helperVersion) {
587
578
  return { limits: { memoryMaxBytes, cpus: input.buildCpusMax } };
588
579
  }
589
580
  async function installServiceCommand(input, deps = {}) {
581
+ if (input.print === true) return installServiceSteps(input, deps);
582
+ const ownership = deps.ownership ?? realOwnership();
583
+ const account = accountNamed(serviceAccount(input), ownership);
584
+ let result;
585
+ try {
586
+ result = await installServiceSteps(input, deps);
587
+ } finally {
588
+ const configDir = dirname(input.configPath);
589
+ const runner = runnerDir(input.configPath);
590
+ const problem = handOver(
591
+ account,
592
+ [{ path: join(configDir, CLAUDE_ENV_FILE) }, { path: join(configDir, "runner.env") }, { path: runner }, { path: dirname(vmAssetsDir(runner)) }, { path: vmAssetsDir(runner), tree: true }],
593
+ ownership
594
+ );
595
+ if (problem !== void 0 && result !== void 0) result = { ...result, ok: false, state: "refused", lines: [...result.lines, problem] };
596
+ }
597
+ return result;
598
+ }
599
+ function serviceAccount(input) {
600
+ return input.user ?? userInfo().username;
601
+ }
602
+ async function installServiceSteps(input, deps) {
590
603
  const nameProblem = input.unitName === void 0 ? void 0 : unitNameProblem(input.unitName);
591
604
  if (nameProblem !== void 0) return { ok: false, state: "refused", lines: [nameProblem] };
592
605
  const vm = runnerVmOf(input.configPath);
@@ -607,13 +620,15 @@ async function installServiceCommand(input, deps = {}) {
607
620
  PATH: process.env.PATH,
608
621
  replace: input.replace,
609
622
  allowDowngrade: input.allowDowngrade,
610
- command: "runner install --service"
623
+ command: "runner install --service",
624
+ account: serviceAccount(input),
625
+ ownership: deps.ownership
611
626
  });
612
627
  launcherLines = [launcherSummary(installed.launcher)].filter((line) => line !== void 0);
613
628
  launcherNotes = installed.launcher.notes;
614
629
  restartLine = restartNote(installed);
615
630
  }
616
- const account = input.user ?? userInfo().username;
631
+ const account = serviceAccount(input);
617
632
  const serviceDeps = (deps.service ?? realServiceDeps)();
618
633
  if (input.print !== true && account !== userInfo().username && serviceDeps.accountCan !== void 0) {
619
634
  const entry = join(currentLink(input.configPath), "node_modules", "@outerlayer", "cli", "dist", "index.js");
package/dist/index.js CHANGED
@@ -26,5 +26,5 @@ if (argv[0] === "claude") {
26
26
  process.exit(await runClaudeLauncher(argv.slice(1)));
27
27
  }
28
28
  (await import('./hook-fast-GPUL7T77.js')).recordLinkExitFailure(argv);
29
- var { runCli } = await import('./cli-G3P2B2XE.js');
29
+ var { runCli } = await import('./cli-VHQPX2B7.js');
30
30
  await runCli(process.argv);
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- export { RunnerDowngradeError, RunnerInstallError, launcherSummary, restartNote, runRunnerInstall } from './chunk-LUHIN4UZ.js';
3
+ export { RunnerDowngradeError, RunnerInstallError, launcherSummary, restartNote, runRunnerInstall } from './chunk-GC5PVMIJ.js';
4
4
  import './chunk-OS7DJOQJ.js';
5
5
  import './chunk-UMYYYF7B.js';
6
6
  import './chunk-TQMI335S.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outerlayer/cli",
3
- "version": "0.34.0-next.20261025180645",
3
+ "version": "0.34.0-next.20261025210240",
4
4
  "description": "OuterLayer CLI — commands: init, claude, connect, doctor, login, logout, sync, daemon (alias: watch), emit, findings, work, spec, runner, hooks, mcp, context. Capture and sync coding-agent sessions, and record proof of factory work.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Magu Studios, Inc.",