@outerlayer/cli 0.33.0-next.20261023120437 → 0.33.0-next.20261023122246

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -168,9 +168,19 @@ content:
168
168
  fetches the package's dependencies, and both npm runs use this user's npm
169
169
  configuration, including any registry token in `~/.npmrc`. Set
170
170
  `runner.autoUpdate` to `false` to stop them.
171
+ When `outerlayer runner start` fails a start-up check before its first
172
+ heartbeat, it sends one report with `POST /v1/hosts/startup-report`. The
173
+ report holds the host name, the CLI version, the name of the failed check
174
+ and the error text, with terminal control codes removed, scrubbed of the
175
+ keys the runner holds and cut to 200 characters. It sends none when another
176
+ runner already runs on the same config, or when the gateway refused the
177
+ runner key or its host key's signature. The call waits at most 5 seconds,
178
+ and a gateway with no such route ignores it.
171
179
  `outerlayer runner container-start`, the runner image's entrypoint, writes
172
- the config from its environment variables with no network call. It then
173
- runs `outerlayer runner start`, which makes the calls above. In the image
180
+ the config from its environment variables. When the container reaches no
181
+ Docker it can use, it sends that report, with the check `docker`, and
182
+ stops. Otherwise it runs `outerlayer runner start`, which makes the calls
183
+ above. In the image
174
184
  the runner never calls the npm registry. It logs the image tag to pull.
175
185
  `runner check` makes a list call and a host-key lookup to confirm the key
176
186
  works and sends nothing else; `runner status` and `runner logs` make no network call at all.
@@ -1 +1 @@
1
- {"id":"2026-10-23T12:05:53.102Z+82d4e55","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
1
+ {"id":"2026-10-23T12:24:08.386Z+0c94177","root":"/home/runner/_work/outerlayer/outerlayer","pkg":"outerlayer"}
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { probeCredentials } from './chunk-YSM4EEUK.js';
4
- import './chunk-FZUFUVLK.js';
3
+ import { probeCredentials } from './chunk-Y6KIGWFO.js';
5
4
  import './chunk-GH246BGV.js';
6
- import './chunk-ZVB57WI6.js';
7
5
  import './chunk-57L2YAJA.js';
8
6
  import './chunk-3JVYMD4G.js';
9
7
  import './chunk-E7R2GIAW.js';
10
- import './chunk-YGZLRX5U.js';
11
- import './chunk-MZYOUIY5.js';
8
+ import './chunk-ZUVERBFB.js';
9
+ import './chunk-RAMTJ2XP.js';
10
+ import './chunk-ZVB57WI6.js';
11
+ import './chunk-TRP3KPGK.js';
12
12
  import './chunk-HVRQQOMH.js';
13
13
  import './chunk-6LBSAW6I.js';
14
14
  import './chunk-I3LTIAOR.js';
@@ -22,7 +22,7 @@ import { scrubText } from './chunk-NIY2UBT6.js';
22
22
  import { readCloudConfig } from './chunk-PJDQPF5N.js';
23
23
  import { signerFor } from './chunk-SOI6K5DY.js';
24
24
  import { RUNNER_FILE_MODE, runnerDir, buildDir, vsockPath, jobConsolePath } from './chunk-PA6CK6WQ.js';
25
- import { JOB_MOUNT, claudeCredential, repositoriesOf, primaryOf, buildEnvironment, CHECKOUT_DIR, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, buildMavenSettingsXml, RELAY_PORT, repositoryPath, lifecycleWorkdir, CHECK_ITEMS_FILE, MAVEN_SETTINGS_FILE, CLAUDE_CREDENTIAL_VARIABLES, currentClaudeCredential, removedClaudeCredentialValues, checkEnvironment, EVAL_INPUT_DIR } from './chunk-BALIE3BA.js';
25
+ import { JOB_MOUNT, claudeCredential, repositoriesOf, primaryOf, buildEnvironment, CHECKOUT_DIR, AGENT_CREDENTIAL_MISSING, sharedUserEnvironment, headerVariables, authorEnvironment, withTunnelRecord, commitIdentityRecord, buildMavenSettingsXml, CLAUDE_CREDENTIAL_VARIABLES, currentClaudeCredential, removedClaudeCredentialValues, RELAY_PORT, repositoryPath, lifecycleWorkdir, CHECK_ITEMS_FILE, MAVEN_SETTINGS_FILE, checkEnvironment, EVAL_INPUT_DIR } from './chunk-BALIE3BA.js';
26
26
  import { resolvePackRoot } from './chunk-44V5SD4G.js';
27
27
  import { MAINTAINED_SKILLS } from './chunk-CDDED64E.js';
28
28
  import { resolveVersion } from './chunk-RA3O54FC.js';
@@ -3547,4 +3547,4 @@ async function stopSender(input) {
3547
3547
  }
3548
3548
  }
3549
3549
 
3550
- export { ALREADY_RELEASED, AttemptLogBook, EXIT_CODE_FILE, GUEST_USAGE_INTERVAL_MS, MICROVM_COMMAND_DIR, RENEW_INTERVAL_SECONDS, appendCommandLog, commandLogOffset, deliverCommitHooks, describeHookEnd, finishJob, foldSample, hookLimitsFor, inTeardown, isContainerTracked, isGroupAlive, isIdle, isMicroVmTracked, isReadable, isTimedOut, isTracked, isUnfinished, jobEnv, judgeProbe, listAdoptableJobs, listTrackedJobs, listUnreadableJobs, microVmCommandLine, microVmOomKills, needsRenew, outcomeFromExitCode, probeCommand, probeMicroVmCommand, provisionMicroVm, provisionProcess, readCpuUsec, readProvisionOut, recoverLostJobs, releaseLease, renewLease, restartSender, retryFailedReleases, runCommand, runHook, sampleGuest, serveLease, setStage, startMicroVmCommand, startSender, stopOrphanedMicroVms, unfinishedJobs, vmAlive, workdirProblem, writeGuestGhConfig };
3550
+ export { ALREADY_RELEASED, AttemptLogBook, EXIT_CODE_FILE, GUEST_USAGE_INTERVAL_MS, MICROVM_COMMAND_DIR, RENEW_INTERVAL_SECONDS, appendCommandLog, commandLogOffset, deliverCommitHooks, describeHookEnd, finishJob, foldSample, hookLimitsFor, inTeardown, isContainerTracked, isGroupAlive, isIdle, isMicroVmTracked, isReadable, isTimedOut, isTracked, isUnfinished, jobEnv, judgeProbe, listAdoptableJobs, listTrackedJobs, listUnreadableJobs, microVmCommandLine, microVmOomKills, needsRenew, outcomeFromExitCode, probeCommand, probeMicroVmCommand, provisionMicroVm, provisionProcess, readCpuUsec, readProvisionOut, recoverLostJobs, releaseLease, renewLease, restartSender, retryFailedReleases, runCommand, runHook, sampleGuest, scrubReason, serveLease, setStage, startMicroVmCommand, startSender, stopOrphanedMicroVms, stripTerminalControls, unfinishedJobs, vmAlive, workdirProblem, writeGuestGhConfig };
@@ -4,6 +4,6 @@ import { createRequire } from 'node:module';
4
4
  createRequire(import.meta.url);
5
5
 
6
6
  // src/runner/protocol.ts
7
- var RUNNER_PROTOCOL = 28;
7
+ var RUNNER_PROTOCOL = 29;
8
8
 
9
9
  export { RUNNER_PROTOCOL };
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { releaseLease, isUnfinished, setStage, jobEnv, runHook, describeHookEnd, provisionMicroVm, deliverCommitHooks, provisionProcess, readProvisionOut, workdirProblem, MICROVM_COMMAND_DIR, commandLogOffset, startMicroVmCommand, microVmCommandLine, runCommand, startSender, finishJob, sampleGuest, foldSample, isContainerTracked, isMicroVmTracked, probeMicroVmCommand, probeCommand, appendCommandLog, judgeProbe, EXIT_CODE_FILE, isTracked, isGroupAlive, outcomeFromExitCode, isTimedOut, readCpuUsec, isIdle, stopOrphanedMicroVms, unfinishedJobs, listUnreadableJobs, listAdoptableJobs, retryFailedReleases, recoverLostJobs, GUEST_USAGE_INTERVAL_MS, hookLimitsFor, AttemptLogBook, microVmOomKills, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, listTrackedJobs, restartSender, serveLease, vmAlive, writeGuestGhConfig, ALREADY_RELEASED, inTeardown } from './chunk-FZUFUVLK.js';
4
3
  import { writePendingUpdate } from './chunk-GH246BGV.js';
5
- import { JOB_STATE_FORMAT, readsJobState, jobStateFormatOf } from './chunk-ZVB57WI6.js';
6
4
  import { readHousekeepingRecord, writeHousekeepingRecord } from './chunk-57L2YAJA.js';
7
5
  import { decideInstalledSwitch } from './chunk-3JVYMD4G.js';
8
6
  import { fetchVersionMeta, npmVersion, npmSupportsProvenance, fetchTarballDigest, fetchProvenance, installVersion, auditSignatures } from './chunk-E7R2GIAW.js';
9
- import { fitImageBuildMemory } from './chunk-YGZLRX5U.js';
10
- import { RUNNER_PROTOCOL } from './chunk-MZYOUIY5.js';
7
+ import { reportFailedStart, fitImageBuildMemory } from './chunk-ZUVERBFB.js';
8
+ import { releaseLease, isUnfinished, setStage, jobEnv, runHook, describeHookEnd, provisionMicroVm, deliverCommitHooks, provisionProcess, readProvisionOut, workdirProblem, MICROVM_COMMAND_DIR, commandLogOffset, startMicroVmCommand, microVmCommandLine, runCommand, startSender, finishJob, sampleGuest, foldSample, isContainerTracked, isMicroVmTracked, probeMicroVmCommand, probeCommand, appendCommandLog, judgeProbe, EXIT_CODE_FILE, isTracked, isGroupAlive, outcomeFromExitCode, isTimedOut, readCpuUsec, isIdle, stopOrphanedMicroVms, unfinishedJobs, listUnreadableJobs, listAdoptableJobs, retryFailedReleases, recoverLostJobs, GUEST_USAGE_INTERVAL_MS, hookLimitsFor, AttemptLogBook, microVmOomKills, needsRenew, renewLease, RENEW_INTERVAL_SECONDS, listTrackedJobs, restartSender, serveLease, vmAlive, writeGuestGhConfig, ALREADY_RELEASED, inTeardown } from './chunk-RAMTJ2XP.js';
9
+ import { JOB_STATE_FORMAT, readsJobState, jobStateFormatOf } from './chunk-ZVB57WI6.js';
10
+ import { RUNNER_PROTOCOL } from './chunk-TRP3KPGK.js';
11
11
  import { realBlockedProbes, checkBlockedWithSlots, keepsHostBuilder } from './chunk-SQQD5MST.js';
12
12
  import { writeMarker, readMarker, currentVersion, removeMarker, entryIn, versionDir, startedThroughCurrent, switchCurrent, isInLayout, cleanVersions, stagingDir } from './chunk-NBZ7XBT6.js';
13
13
  import { RepoFilter } from './chunk-TBT347UY.js';
@@ -2813,21 +2813,26 @@ async function probeCredentials(config, fetchImpl = fetch) {
2813
2813
  return { ok: true };
2814
2814
  } catch (err) {
2815
2815
  const status = err instanceof WorkCommandError ? err.status : void 0;
2816
+ const code = err instanceof WorkCommandError ? err.code : void 0;
2816
2817
  if (err instanceof WorkCommandError && isSignatureRefusal(err.code)) {
2817
2818
  return {
2818
2819
  ok: false,
2819
2820
  fatal: err.code === "runner_key_signature_invalid",
2820
- reason: `${config.configPath}: ${err.message}`
2821
+ reason: `${config.configPath}: ${err.message}`,
2822
+ status,
2823
+ code
2821
2824
  };
2822
2825
  }
2823
2826
  if (status === 401 || status === 403) {
2824
2827
  return {
2825
2828
  ok: false,
2826
2829
  fatal: true,
2827
- reason: `${config.configPath}: the API key was refused (${status}). A runner key needs git.read and work.read to list and read work items, and work.claim to claim, renew and release.`
2830
+ reason: `${config.configPath}: the API key was refused (${status}). A runner key needs git.read and work.read to list and read work items, and work.claim to claim, renew and release.`,
2831
+ status,
2832
+ code
2828
2833
  };
2829
2834
  }
2830
- return { ok: false, fatal: false, reason: describeCallFailure(err) };
2835
+ return { ok: false, fatal: false, reason: describeCallFailure(err), status, code };
2831
2836
  }
2832
2837
  }
2833
2838
  async function rejoinContainerBuild(state, adopted) {
@@ -3071,6 +3076,15 @@ function hooksOf(configPath) {
3071
3076
  }
3072
3077
  }
3073
3078
  async function start(configPath, deps = { fetchImpl: fetch }) {
3079
+ const startup = {};
3080
+ try {
3081
+ return await startRunner(configPath, deps, startup);
3082
+ } catch (err) {
3083
+ await startup.report?.(err);
3084
+ throw err;
3085
+ }
3086
+ }
3087
+ async function startRunner(configPath, deps, startup) {
3074
3088
  const log = deps.log ??= createRunnerLog(deps.now ?? (() => /* @__PURE__ */ new Date()));
3075
3089
  takeClaudeCredential(process.env, hooksOf(configPath));
3076
3090
  const update = deps.update;
@@ -3081,7 +3095,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
3081
3095
  check: update.check ? () => update.check(configPath) : async () => {
3082
3096
  const held = heldClaudeCredential();
3083
3097
  const env = held === void 0 ? process.env : { ...process.env, [held.variable]: held.value };
3084
- return (await import('./check-4QNLMDP7.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl, env });
3098
+ return (await import('./check-YCFZ3H35.js')).runRunnerCheck(configPath, { fetchImpl: deps.fetchImpl, env });
3085
3099
  },
3086
3100
  now: () => (deps.now ?? (() => /* @__PURE__ */ new Date()))().getTime(),
3087
3101
  log
@@ -3092,22 +3106,37 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
3092
3106
  }
3093
3107
  }
3094
3108
  const loaded = loadRunnerConfig(configPath);
3109
+ let check = "hooks";
3110
+ let probeRefusal;
3111
+ startup.report = (err) => reportFailedStart(
3112
+ { config: loaded, cliVersion: deps.cliVersion ?? resolveVersion(), check, error: err, refusal: probeRefusal },
3113
+ deps.fetchImpl
3114
+ );
3095
3115
  const hookProblem = checkHooks(loaded);
3096
3116
  if (hookProblem) throw new RunnerStartError(hookProblem);
3117
+ check = "image";
3097
3118
  const { config, note: fitNote } = fitForImage(deps, loaded);
3119
+ check = "host-key";
3098
3120
  await ensureHostKey(configPath);
3121
+ check = "identity";
3099
3122
  if (builtinKind(config.hooks.provision) === "container" || builtinKind(config.hooks.provision) === "vm") {
3100
3123
  await ensureRunnerIdentity(configPath, {
3101
3124
  docker: deps.runnerIdentity ?? (deps.container?.host === void 0 ? void 0 : { holdsObjects: async () => false }),
3102
3125
  log: (line) => log.error(line)
3103
3126
  }).catch((err) => log.error(`the runner's Docker identity could not be stored: ${describeCallFailure(err)} \xB7 using the names its folder's path gives`));
3104
3127
  }
3128
+ check = "credentials";
3105
3129
  const probe = await probeCredentials(config, deps.fetchImpl);
3106
- if (!probe.ok && probe.fatal) throw new RunnerStartError(probe.reason);
3130
+ if (!probe.ok && probe.fatal) {
3131
+ probeRefusal = { status: probe.status, code: probe.code };
3132
+ throw new RunnerStartError(probe.reason);
3133
+ }
3107
3134
  if (!probe.ok) log.error(`the gateway did not answer at start: ${probe.reason} \xB7 starting anyway`);
3135
+ check = "pid-file";
3108
3136
  mkdirSync(jobsDir(configPath), { recursive: true, mode: RUNNER_DIR_MODE });
3109
3137
  const self = { pid: process.pid, start: startTimeOf(process.pid) ?? "" };
3110
3138
  claimPidFile(pidFilePath(configPath), self);
3139
+ check = "cgroup";
3111
3140
  const { delegated, refusal } = setUpRunnerCgroup();
3112
3141
  if (refusal !== void 0) log.error(`the kernel refused a cgroup step for the runner's service: ${refusal}`);
3113
3142
  if (isBuiltinVm(config.hooks.provision) && !delegated) {
@@ -3122,6 +3151,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
3122
3151
  const state = { config, jobs: /* @__PURE__ */ new Map(), deps, configMtimeMs };
3123
3152
  log.line(`runner ${config.host} started \xB7 config ${configPath} \xB7 ${describeConfig(config)}`);
3124
3153
  if (fitNote !== void 0) log.line(fitNote);
3154
+ check = "recovery";
3125
3155
  const leavesBuilds = await canLeaveBuilds(state);
3126
3156
  let sigusr2Target;
3127
3157
  let sigusr2Early = false;
@@ -3189,6 +3219,7 @@ async function start(configPath, deps = { fetchImpl: fetch }) {
3189
3219
  `recovered job ${recovered.queue} #${recovered.item} (claim ${shortClaim(recovered.claimId)}) as ${recovered.outcome ?? "interrupted"}: ${why}`
3190
3220
  );
3191
3221
  }
3222
+ startup.report = void 0;
3192
3223
  let intakeClosed = false;
3193
3224
  let closed = false;
3194
3225
  let stopping = false;
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from 'node:module';
3
+ import { scrubReason, stripTerminalControls } from './chunk-RAMTJ2XP.js';
4
+ import { RunnerAlreadyRunningError, readPidFile } from './chunk-F5UL23I2.js';
5
+ import { isSameProcess } from './chunk-JBJTC3UU.js';
6
+ import { checkFit, RUNNER_RESERVE_BYTES } from './chunk-EHOFFTVX.js';
7
+ import { isSignatureRefusal, postJson } from './chunk-YXMGHGHR.js';
8
+ import { signerFor } from './chunk-SOI6K5DY.js';
9
+ import { pidFilePath } from './chunk-PA6CK6WQ.js';
10
+ import { readFileSync } from 'fs';
11
+
12
+ createRequire(import.meta.url);
13
+ var GIB = 1024 ** 3;
14
+ var DEFAULT_GIB = 8;
15
+ var MINIMUM_GIB = 4;
16
+ var BUILD_MEMORY_VARIABLE = "OUTERLAYER_BUILD_MEMORY";
17
+ function memoryIsSet(configPath) {
18
+ try {
19
+ const raw = JSON.parse(readFileSync(configPath, "utf8"));
20
+ return raw.runner.build.memory !== void 0;
21
+ } catch {
22
+ return false;
23
+ }
24
+ }
25
+ function gib(bytes) {
26
+ return `${Number((bytes / GIB).toFixed(1))} GiB`;
27
+ }
28
+ function fitImageBuildMemory(config, totalMemoryBytes) {
29
+ if (memoryIsSet(config.configPath)) return { config };
30
+ const current = config.build?.memory ?? `${DEFAULT_GIB}g`;
31
+ if (checkFit({ concurrency: config.concurrency, buildMemory: current, usableBytes: totalMemoryBytes }).fits) return { config };
32
+ const available = Math.max(0, totalMemoryBytes - RUNNER_RESERVE_BYTES);
33
+ const perBuild = Math.floor(available / config.concurrency / GIB);
34
+ if (perBuild < MINIMUM_GIB) return { config };
35
+ const memory = `${perBuild}g`;
36
+ return {
37
+ config: { ...config, build: { ...config.build, memory } },
38
+ note: `builds get ${memory} of memory to fit this machine's ${gib(totalMemoryBytes)}; set ${BUILD_MEMORY_VARIABLE} to choose another value`
39
+ };
40
+ }
41
+
42
+ // src/runner/startup-report.ts
43
+ var REPORT_TIMEOUT_MS = 5e3;
44
+ var EMPTY_MESSAGE = "the check failed with no message";
45
+ async function reportFailedStart(failed, fetchImpl, deps = {}) {
46
+ try {
47
+ if (failed.error instanceof RunnerAlreadyRunningError || refusesReport(failed.refusal)) return;
48
+ if (otherRunnerLive(failed.config.configPath, deps.isSameProcess ?? isSameProcess)) return;
49
+ await sendStartupReport(failed, fetchImpl, deps);
50
+ } catch {
51
+ }
52
+ }
53
+ function refusesReport(refusal) {
54
+ return refusal !== void 0 && (refusal.status === 401 || isSignatureRefusal(refusal.code));
55
+ }
56
+ function otherRunnerLive(configPath, isSame) {
57
+ const entry = readPidFile(pidFilePath(configPath));
58
+ return entry !== null && entry.pid !== process.pid && isSame(entry.pid, entry.start);
59
+ }
60
+ async function sendStartupReport(failed, fetchImpl, deps) {
61
+ const now = deps.now ?? (() => performance.now());
62
+ const startedAt = now();
63
+ const { config } = failed;
64
+ const text = failed.error instanceof Error ? failed.error.message : String(failed.error);
65
+ const message = (deps.scrub ?? scrubReason)(stripTerminalControls(text), config) ?? EMPTY_MESSAGE;
66
+ const signer = await signerFor(config.configPath).catch(() => void 0);
67
+ const left = REPORT_TIMEOUT_MS - (now() - startedAt);
68
+ if (left <= 0) return;
69
+ await postJson(
70
+ config.url,
71
+ "/v1/hosts/startup-report",
72
+ config.apiKey,
73
+ config.appId,
74
+ { host: config.host, cliVersion: failed.cliVersion, check: failed.check, message },
75
+ fetchImpl,
76
+ left,
77
+ signer
78
+ );
79
+ }
80
+
81
+ export { BUILD_MEMORY_VARIABLE, fitImageBuildMemory, reportFailedStart };
@@ -4963,7 +4963,7 @@ by a different key is refused; claiming again with the same key extends
4963
4963
  it. --host is a label only.
4964
4964
  `
4965
4965
  ).action(async (opts) => {
4966
- const { runWorkClaim, WorkCommandError } = await import('./work-claim-cmd-CIVWB7EJ.js');
4966
+ const { runWorkClaim, WorkCommandError } = await import('./work-claim-cmd-GBU2ACRF.js');
4967
4967
  try {
4968
4968
  await runWorkClaim(opts);
4969
4969
  } catch (err) {
@@ -4983,7 +4983,7 @@ Extends the live lease this API key holds. Refused with a 410 if the
4983
4983
  lease has expired, or a 403 if a different key holds it.
4984
4984
  `
4985
4985
  ).action(async (opts) => {
4986
- const { runWorkRenew, WorkCommandError } = await import('./work-claim-cmd-CIVWB7EJ.js');
4986
+ const { runWorkRenew, WorkCommandError } = await import('./work-claim-cmd-GBU2ACRF.js');
4987
4987
  try {
4988
4988
  await runWorkRenew(opts);
4989
4989
  } catch (err) {
@@ -5015,7 +5015,7 @@ applies to a host's lease only. A later tool call in the session
5015
5015
  takes the lease again.
5016
5016
  `
5017
5017
  ).action(async (opts) => {
5018
- const { runWorkRelease, WorkCommandError } = await import('./work-claim-cmd-CIVWB7EJ.js');
5018
+ const { runWorkRelease, WorkCommandError } = await import('./work-claim-cmd-GBU2ACRF.js');
5019
5019
  try {
5020
5020
  await runWorkRelease(opts);
5021
5021
  } catch (err) {
@@ -5085,7 +5085,7 @@ function registerRunnerCommands(program2) {
5085
5085
  const configOption = "the cloud config file to run against (default: ~/.outerlayer/config.json)";
5086
5086
  runnerCmd.command("start").description("Run the loop: claim work, run it, report, clean up, repeat").option("--config <path>", configOption).action(async (opts) => {
5087
5087
  const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-MVA7YTFW.js');
5088
- const { start, RunnerStartError } = await import('./loop-YJF27PUS.js');
5088
+ const { start, RunnerStartError } = await import('./loop-KRGNTFZ2.js');
5089
5089
  const { RunnerAlreadyRunningError } = await import('./pidfile-5UDLYPMC.js');
5090
5090
  const configPath = opts.config ?? defaultRunnerConfigPath();
5091
5091
  try {
@@ -5104,8 +5104,8 @@ function registerRunnerCommands(program2) {
5104
5104
  "\nReads OUTERLAYER_URL, OUTERLAYER_APP_ID, OUTERLAYER_API_KEY, OUTERLAYER_RUNNER_NAME and one Claude credential\n(CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY) from the environment. OUTERLAYER_AGENT\nis optional and defaults to claude. On a later start it refuses variables that disagree with the config in the volume.\nThe image sets OUTERLAYER_RUNNER_IMAGE; outside the image, use `runner start`.\n"
5105
5105
  ).action(async () => {
5106
5106
  const { defaultRunnerConfigPath, RunnerConfigError } = await import('./config-MVA7YTFW.js');
5107
- const { containerStart } = await import('./container-start-AQJ64LCF.js');
5108
- const { start, RunnerStartError } = await import('./loop-YJF27PUS.js');
5107
+ const { containerStart } = await import('./container-start-C4XCYPUA.js');
5108
+ const { start, RunnerStartError } = await import('./loop-KRGNTFZ2.js');
5109
5109
  const { RunnerAlreadyRunningError } = await import('./pidfile-5UDLYPMC.js');
5110
5110
  try {
5111
5111
  const result = await containerStart({
@@ -5125,7 +5125,7 @@ function registerRunnerCommands(program2) {
5125
5125
  });
5126
5126
  runnerCmd.command("check").description("Validate the config, the hooks and the key, and print the effective settings").option("--config <path>", configOption).action(async (opts) => {
5127
5127
  const { defaultRunnerConfigPath } = await import('./config-MVA7YTFW.js');
5128
- const { runRunnerCheck } = await import('./check-4QNLMDP7.js');
5128
+ const { runRunnerCheck } = await import('./check-YCFZ3H35.js');
5129
5129
  const configPath = opts.config ?? defaultRunnerConfigPath();
5130
5130
  const result = await runRunnerCheck(configPath);
5131
5131
  if (!result.ok) {
@@ -5525,7 +5525,7 @@ Next:
5525
5525
  });
5526
5526
  runnerCmd.command("stop").description("Take no more work; finish running jobs, then exit").option("--config <path>", "the cloud config file naming the runner to stop").option("--now", "end running jobs immediately, with outcome stopped").action(async (opts) => {
5527
5527
  const { defaultRunnerConfigPath } = await import('./config-MVA7YTFW.js');
5528
- const { requestStop } = await import('./loop-YJF27PUS.js');
5528
+ const { requestStop } = await import('./loop-KRGNTFZ2.js');
5529
5529
  const configPath = opts.config ?? defaultRunnerConfigPath();
5530
5530
  const { runnerImageVersion } = await import('./image-tag-ADSLWXBA.js');
5531
5531
  await requestStop(configPath, Boolean(opts.now), void 0, { image: runnerImageVersion() !== void 0 });
@@ -5533,8 +5533,8 @@ Next:
5533
5533
  runnerCmd.command("status").description("List running jobs; with --recent, finished ones too").option("--config <path>", "the cloud config file naming the runner to inspect").option("--recent", "also list finished jobs, newest first, capped at 20").option("--json", "machine-readable JSON output").action(async (opts) => {
5534
5534
  const { defaultRunnerConfigPath, loadRunnerConfig } = await import('./config-MVA7YTFW.js');
5535
5535
  const { jobsDir, runnerDir } = await import('./paths-7HVTDE6J.js');
5536
- const { listJobFiles, runnerProcessState } = await import('./loop-YJF27PUS.js');
5537
- const { renderStatus } = await import('./status-AYHBGIAP.js');
5536
+ const { listJobFiles, runnerProcessState } = await import('./loop-KRGNTFZ2.js');
5537
+ const { renderStatus } = await import('./status-YVJQWWDP.js');
5538
5538
  const { readHousekeepingRecord } = await import('./record-PPZEXAVA.js');
5539
5539
  const path2 = await import('path');
5540
5540
  const configPath = opts.config ?? defaultRunnerConfigPath();
@@ -5594,7 +5594,7 @@ Next:
5594
5594
  });
5595
5595
  runnerCmd.command("logs").argument("<item>", "the work item's number in the factory").description("Print the newest attempt's log for one item").option("--config <path>", "the cloud config file naming the runner to inspect").option("-f, --follow", "keep printing as the log grows").action(async (itemArg, opts) => {
5596
5596
  const { defaultRunnerConfigPath } = await import('./config-MVA7YTFW.js');
5597
- const { listJobFiles } = await import('./loop-YJF27PUS.js');
5597
+ const { listJobFiles } = await import('./loop-KRGNTFZ2.js');
5598
5598
  const { newestAttempt, logPathFor, tail, RunnerLogsError } = await import('./logs-HI46FO6Z.js');
5599
5599
  const configPath = opts.config ?? defaultRunnerConfigPath();
5600
5600
  const item = parseTrackerNumber(itemArg);
@@ -1,6 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { BUILD_MEMORY_VARIABLE } from './chunk-YGZLRX5U.js';
3
+ import { reportFailedStart, BUILD_MEMORY_VARIABLE } from './chunk-ZUVERBFB.js';
4
+ import './chunk-RAMTJ2XP.js';
5
+ import './chunk-ZVB57WI6.js';
6
+ import './chunk-HVRQQOMH.js';
7
+ import './chunk-6LBSAW6I.js';
8
+ import './chunk-I3LTIAOR.js';
9
+ import './chunk-7IFYI7XF.js';
10
+ import './chunk-D3EDZPJR.js';
11
+ import './chunk-7UPMYRPS.js';
12
+ import './chunk-ESVW4MJY.js';
13
+ import './chunk-TBT347UY.js';
4
14
  import { detectMachine, supportsContainerBuilds, MIN_ENGINE_MAJOR, describeMachineDocker, RunnerInitError, prepareRunnerInit, writeRunnerInit } from './chunk-IUPY54BZ.js';
5
15
  import './chunk-2PGI6RDE.js';
6
16
  import { isAgentName, AGENT_COMMANDS } from './chunk-EXZACZ2P.js';
@@ -36,17 +46,23 @@ import './chunk-PJDQPF5N.js';
36
46
  import './chunk-SOI6K5DY.js';
37
47
  import './chunk-PA6CK6WQ.js';
38
48
  import { readClaudeCredential, FORWARDED_CLAUDE_CREDENTIAL_VARIABLES } from './chunk-BALIE3BA.js';
49
+ import './chunk-UK4RGH46.js';
39
50
  import './chunk-ZJSNLVCE.js';
51
+ import './chunk-2O5KA67H.js';
52
+ import './chunk-BKO6JEZI.js';
40
53
  import './chunk-FGF52V4V.js';
41
54
  import './chunk-HCVLP7XA.js';
42
55
  import './chunk-BJ3KTMDH.js';
43
56
  import './chunk-44V5SD4G.js';
44
57
  import './chunk-5BHIBJDA.js';
45
58
  import './chunk-CDDED64E.js';
46
- import './chunk-RA3O54FC.js';
59
+ import { resolveVersion } from './chunk-RA3O54FC.js';
60
+ import './chunk-T2Q7ISPE.js';
47
61
  import './chunk-WZBHBH3J.js';
48
62
  import './chunk-CRB3POP4.js';
63
+ import './chunk-URIZRCRY.js';
49
64
  import './chunk-YQR6E6OV.js';
65
+ import './chunk-TUSYOL2O.js';
50
66
  import './chunk-6CXNWKWD.js';
51
67
  import './chunk-F2D5BSXS.js';
52
68
  import './chunk-VNVZDWO3.js';
@@ -170,22 +186,29 @@ async function containerStart(deps) {
170
186
  }
171
187
  const settings = settingsOf(env);
172
188
  if (typeof settings === "string") return refuse(settings);
189
+ const url = set(env, "OUTERLAYER_URL");
190
+ const appId = set(env, "OUTERLAYER_APP_ID");
191
+ const apiKey = set(env, "OUTERLAYER_API_KEY");
192
+ const host = named.name;
193
+ const refuseDocker = async (message) => {
194
+ await reportFailedStart(
195
+ { config: { configPath, url, apiKey, appId, host }, cliVersion: resolveVersion(), check: "docker", error: message },
196
+ deps.fetchImpl ?? fetch
197
+ );
198
+ return refuse(message);
199
+ };
173
200
  const socketPresent = deps.socketPresent ?? existsSync;
174
201
  if (!socketPresent(DOCKER_SOCKET)) {
175
- return refuse(
202
+ return refuseDocker(
176
203
  `${DOCKER_SOCKET} is not in the container. The runner starts each build as a container on the host's Docker, so start this container with -v ${DOCKER_SOCKET}:${DOCKER_SOCKET}.`
177
204
  );
178
205
  }
179
206
  const machine = { ...(deps.machine ?? (() => detectMachine()))(), inImage: true };
180
207
  if (!supportsContainerBuilds(machine)) {
181
- return refuse(
208
+ return refuseDocker(
182
209
  `The container reaches ${describeMachineDocker(machine)} through ${DOCKER_SOCKET}, and builds need Docker Engine in a version that isolates a build (${MIN_ENGINE_MAJOR} or later), on Docker Engine or Docker Desktop. Check that the socket is the host's Docker and that it is running.`
183
210
  );
184
211
  }
185
- const url = set(env, "OUTERLAYER_URL");
186
- const appId = set(env, "OUTERLAYER_APP_ID");
187
- const apiKey = set(env, "OUTERLAYER_API_KEY");
188
- const host = named.name;
189
212
  if (existsSync(configPath)) {
190
213
  const stored = readConfig(configPath);
191
214
  if (stored instanceof RunnerInitError) return refuse(stored.message);
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-3IYSWDL4.js')).recordLinkExitFailure(argv);
29
- var { runCli } = await import('./cli-BC7JWQUQ.js');
29
+ var { runCli } = await import('./cli-WFLOFK42.js');
30
30
  await runCli(process.argv);
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- export { RunnerStartError, leaseTimeLeftFor, listJobFiles, logUnusableHelper, probeBuild, probeCredentials, requestStop, runnerProcessState, runtimeOf, sampleUsage, start, tick, watchTick } from './chunk-YSM4EEUK.js';
4
- import './chunk-FZUFUVLK.js';
3
+ export { RunnerStartError, leaseTimeLeftFor, listJobFiles, logUnusableHelper, probeBuild, probeCredentials, requestStop, runnerProcessState, runtimeOf, sampleUsage, start, tick, watchTick } from './chunk-Y6KIGWFO.js';
5
4
  import './chunk-GH246BGV.js';
6
- import './chunk-ZVB57WI6.js';
7
5
  import './chunk-57L2YAJA.js';
8
6
  import './chunk-3JVYMD4G.js';
9
7
  import './chunk-E7R2GIAW.js';
10
- import './chunk-YGZLRX5U.js';
11
- import './chunk-MZYOUIY5.js';
8
+ import './chunk-ZUVERBFB.js';
9
+ import './chunk-RAMTJ2XP.js';
10
+ import './chunk-ZVB57WI6.js';
11
+ import './chunk-TRP3KPGK.js';
12
12
  import './chunk-HVRQQOMH.js';
13
13
  import './chunk-6LBSAW6I.js';
14
14
  import './chunk-I3LTIAOR.js';
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { isReadable, ALREADY_RELEASED, RENEW_INTERVAL_SECONDS } from './chunk-FZUFUVLK.js';
3
+ import { isReadable, ALREADY_RELEASED, RENEW_INTERVAL_SECONDS } from './chunk-RAMTJ2XP.js';
4
4
  import { jobStateFormatOf } from './chunk-ZVB57WI6.js';
5
5
  import './chunk-HVRQQOMH.js';
6
6
  import './chunk-6LBSAW6I.js';
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
- import { RUNNER_PROTOCOL } from './chunk-MZYOUIY5.js';
3
+ import { RUNNER_PROTOCOL } from './chunk-TRP3KPGK.js';
4
4
  import { defaultRunnerConfigPath } from './chunk-VWQCYOBS.js';
5
5
  import './chunk-X4JEBMYN.js';
6
6
  import './chunk-OGMXVPCS.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outerlayer/cli",
3
- "version": "0.33.0-next.20261023120437",
3
+ "version": "0.33.0-next.20261023122246",
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.",
@@ -1,36 +0,0 @@
1
- #!/usr/bin/env node
2
- import { createRequire } from 'node:module';
3
- import { checkFit, RUNNER_RESERVE_BYTES } from './chunk-EHOFFTVX.js';
4
- import { readFileSync } from 'fs';
5
-
6
- createRequire(import.meta.url);
7
- var GIB = 1024 ** 3;
8
- var DEFAULT_GIB = 8;
9
- var MINIMUM_GIB = 4;
10
- var BUILD_MEMORY_VARIABLE = "OUTERLAYER_BUILD_MEMORY";
11
- function memoryIsSet(configPath) {
12
- try {
13
- const raw = JSON.parse(readFileSync(configPath, "utf8"));
14
- return raw.runner.build.memory !== void 0;
15
- } catch {
16
- return false;
17
- }
18
- }
19
- function gib(bytes) {
20
- return `${Number((bytes / GIB).toFixed(1))} GiB`;
21
- }
22
- function fitImageBuildMemory(config, totalMemoryBytes) {
23
- if (memoryIsSet(config.configPath)) return { config };
24
- const current = config.build?.memory ?? `${DEFAULT_GIB}g`;
25
- if (checkFit({ concurrency: config.concurrency, buildMemory: current, usableBytes: totalMemoryBytes }).fits) return { config };
26
- const available = Math.max(0, totalMemoryBytes - RUNNER_RESERVE_BYTES);
27
- const perBuild = Math.floor(available / config.concurrency / GIB);
28
- if (perBuild < MINIMUM_GIB) return { config };
29
- const memory = `${perBuild}g`;
30
- return {
31
- config: { ...config, build: { ...config.build, memory } },
32
- note: `builds get ${memory} of memory to fit this machine's ${gib(totalMemoryBytes)}; set ${BUILD_MEMORY_VARIABLE} to choose another value`
33
- };
34
- }
35
-
36
- export { BUILD_MEMORY_VARIABLE, fitImageBuildMemory };