@craftspace/cli 0.4.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4632,8 +4632,8 @@ var BIGINT_FORMAT_RANGES = {
4632
4632
  };
4633
4633
  function pick(schema, mask) {
4634
4634
  const currDef = schema._zod.def;
4635
- const checks = currDef.checks;
4636
- const hasChecks = checks && checks.length > 0;
4635
+ const checks2 = currDef.checks;
4636
+ const hasChecks = checks2 && checks2.length > 0;
4637
4637
  if (hasChecks) {
4638
4638
  throw new Error(".pick() cannot be used on object schemas containing refinements");
4639
4639
  }
@@ -4657,8 +4657,8 @@ function pick(schema, mask) {
4657
4657
  }
4658
4658
  function omit(schema, mask) {
4659
4659
  const currDef = schema._zod.def;
4660
- const checks = currDef.checks;
4661
- const hasChecks = checks && checks.length > 0;
4660
+ const checks2 = currDef.checks;
4661
+ const hasChecks = checks2 && checks2.length > 0;
4662
4662
  if (hasChecks) {
4663
4663
  throw new Error(".omit() cannot be used on object schemas containing refinements");
4664
4664
  }
@@ -4684,8 +4684,8 @@ function extend(schema, shape) {
4684
4684
  if (!isPlainObject(shape)) {
4685
4685
  throw new Error("Invalid input to extend: expected a plain object");
4686
4686
  }
4687
- const checks = schema._zod.def.checks;
4688
- const hasChecks = checks && checks.length > 0;
4687
+ const checks2 = schema._zod.def.checks;
4688
+ const hasChecks = checks2 && checks2.length > 0;
4689
4689
  if (hasChecks) {
4690
4690
  const existingShape = schema._zod.def.shape;
4691
4691
  for (const key in shape) {
@@ -4735,8 +4735,8 @@ function merge(a, b) {
4735
4735
  }
4736
4736
  function partial(Class2, schema, mask) {
4737
4737
  const currDef = schema._zod.def;
4738
- const checks = currDef.checks;
4739
- const hasChecks = checks && checks.length > 0;
4738
+ const checks2 = currDef.checks;
4739
+ const hasChecks = checks2 && checks2.length > 0;
4740
4740
  if (hasChecks) {
4741
4741
  throw new Error(".partial() cannot be used on object schemas containing refinements");
4742
4742
  }
@@ -5238,10 +5238,10 @@ var nanoid = /^[a-zA-Z0-9_-]{21}$/;
5238
5238
  var duration = /^P(?:(\d+W)|(?!.*W)(?=\d|T\d)(\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+([.,]\d+)?S)?)?)$/;
5239
5239
  var extendedDuration = /^[-+]?P(?!$)(?:(?:[-+]?\d+Y)|(?:[-+]?\d+[.,]\d+Y$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:(?:[-+]?\d+W)|(?:[-+]?\d+[.,]\d+W$))?(?:(?:[-+]?\d+D)|(?:[-+]?\d+[.,]\d+D$))?(?:T(?=[\d+-])(?:(?:[-+]?\d+H)|(?:[-+]?\d+[.,]\d+H$))?(?:(?:[-+]?\d+M)|(?:[-+]?\d+[.,]\d+M$))?(?:[-+]?\d+(?:[.,]\d+)?S)?)??$/;
5240
5240
  var guid = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
5241
- var uuid = (version3) => {
5242
- if (!version3)
5241
+ var uuid = (version2) => {
5242
+ if (!version2)
5243
5243
  return /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;
5244
- return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version3}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
5244
+ return new RegExp(`^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-${version2}[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$`);
5245
5245
  };
5246
5246
  var uuid4 = /* @__PURE__ */ uuid(4);
5247
5247
  var uuid6 = /* @__PURE__ */ uuid(6);
@@ -5923,25 +5923,25 @@ var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
5923
5923
  inst._zod.def = def;
5924
5924
  inst._zod.bag = inst._zod.bag || {};
5925
5925
  inst._zod.version = version;
5926
- const checks = [...inst._zod.def.checks ?? []];
5926
+ const checks2 = [...inst._zod.def.checks ?? []];
5927
5927
  if (inst._zod.traits.has("$ZodCheck")) {
5928
- checks.unshift(inst);
5928
+ checks2.unshift(inst);
5929
5929
  }
5930
- for (const ch of checks) {
5930
+ for (const ch of checks2) {
5931
5931
  for (const fn of ch._zod.onattach) {
5932
5932
  fn(inst);
5933
5933
  }
5934
5934
  }
5935
- if (checks.length === 0) {
5935
+ if (checks2.length === 0) {
5936
5936
  (_a3 = inst._zod).deferred ?? (_a3.deferred = []);
5937
5937
  inst._zod.deferred?.push(() => {
5938
5938
  inst._zod.run = inst._zod.parse;
5939
5939
  });
5940
5940
  } else {
5941
- const runChecks = (payload, checks2, ctx) => {
5941
+ const runChecks = (payload, checks3, ctx) => {
5942
5942
  let isAborted = aborted(payload);
5943
5943
  let asyncResult;
5944
- for (const ch of checks2) {
5944
+ for (const ch of checks3) {
5945
5945
  if (ch._zod.def.when) {
5946
5946
  if (explicitlyAborted(payload))
5947
5947
  continue;
@@ -5985,7 +5985,7 @@ var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
5985
5985
  canary.aborted = true;
5986
5986
  return canary;
5987
5987
  }
5988
- const checkResult = runChecks(payload, checks, ctx);
5988
+ const checkResult = runChecks(payload, checks2, ctx);
5989
5989
  if (checkResult instanceof Promise) {
5990
5990
  if (ctx.async === false)
5991
5991
  throw new $ZodAsyncError();
@@ -6010,9 +6010,9 @@ var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
6010
6010
  if (result instanceof Promise) {
6011
6011
  if (ctx.async === false)
6012
6012
  throw new $ZodAsyncError();
6013
- return result.then((result2) => runChecks(result2, checks, ctx));
6013
+ return result.then((result2) => runChecks(result2, checks2, ctx));
6014
6014
  }
6015
- return runChecks(result, checks, ctx);
6015
+ return runChecks(result, checks2, ctx);
6016
6016
  };
6017
6017
  }
6018
6018
  defineLazy(inst, "~standard", () => ({
@@ -18117,10 +18117,10 @@ function fromJSONSchema(schema, params) {
18117
18117
  } catch {
18118
18118
  throw new Error("fromJSONSchema input is not valid JSON (possibly cyclic); use $defs/$ref for recursive schemas");
18119
18119
  }
18120
- const version3 = detectVersion(normalized, params?.defaultTarget);
18120
+ const version2 = detectVersion(normalized, params?.defaultTarget);
18121
18121
  const defs = normalized.$defs || normalized.definitions || {};
18122
18122
  const ctx = {
18123
- version: version3,
18123
+ version: version2,
18124
18124
  defs,
18125
18125
  refs: /* @__PURE__ */ new Map(),
18126
18126
  processing: /* @__PURE__ */ new Set(),
@@ -18710,6 +18710,16 @@ var GUS_MODELS = [
18710
18710
  reasoning: true,
18711
18711
  harness: GusHarness.OpenCode
18712
18712
  },
18713
+ {
18714
+ id: "deepseek/deepseek-v4.1-flash",
18715
+ label: "DeepSeek V4.1 Flash",
18716
+ blurb: "The V4 Flash refresh. Same pennies, steadier across a long tool run.",
18717
+ inputUsdPerMillion: 0.08,
18718
+ outputUsdPerMillion: 0.18,
18719
+ contextWindow: 1048576,
18720
+ reasoning: true,
18721
+ harness: GusHarness.OpenCode
18722
+ },
18713
18723
  {
18714
18724
  id: "deepseek/deepseek-v4-pro-0813",
18715
18725
  label: "DeepSeek V4 Pro",
@@ -19310,7 +19320,8 @@ var OAuthCallbackQuerySchema = external_exports.object({
19310
19320
  });
19311
19321
  var ListConnectionsResponseSchema = external_exports.object({
19312
19322
  connections: external_exports.array(ConnectionSchema),
19313
- canManage: external_exports.boolean()
19323
+ canManage: external_exports.boolean(),
19324
+ egressIp: external_exports.string()
19314
19325
  });
19315
19326
  var CONNECTION_CATEGORIES = [
19316
19327
  "Sales & support",
@@ -19751,6 +19762,10 @@ var GUS_MAX_TURN_MS = 45 * 60 * 1e3;
19751
19762
  // ../../shared/dist/gus/workstation.js
19752
19763
  var WORKSTATION_CORES = 2;
19753
19764
  var WORKSTATION_RECIPE_MAX_CHARS = 8e3;
19765
+ var WORKSTATION_MAX_STEPS = 20;
19766
+ var WORKSTATION_STEP_NAME_MAX_CHARS = 60;
19767
+ var WORKSTATION_STEP_DETAIL_MAX_CHARS = 600;
19768
+ var WORKSTATION_MAX_REPOS = 20;
19754
19769
  var WORKSTATION_ALLOWLIST_MAX_HOSTS = 50;
19755
19770
  var WORKSTATION_HOST_MAX_CHARS = 200;
19756
19771
  var SANDBOX_BROWSERS_PATH = "/vercel/sandbox/.cache/ms-playwright";
@@ -19762,14 +19777,53 @@ var WORKSTATION_SPECS = {
19762
19777
  };
19763
19778
  var PLAYWRIGHT_VERSION = "1.49.1";
19764
19779
  var WORKSTATION_BASE_RECIPE = [
19765
- "# Craftspace base v2: a headless browser, ready to use.",
19780
+ "# Craftspace base v3: a headless browser, ready to use.",
19781
+ `export PLAYWRIGHT_BROWSERS_PATH=${SANDBOX_BROWSERS_PATH}`,
19766
19782
  "sudo dnf install -y nss nspr atk at-spi2-atk at-spi2-core cups-libs libdrm libxkbcommon \\",
19767
19783
  " libXcomposite libXdamage libXrandr libXfixes libXrender mesa-libgbm pango alsa-lib >/dev/null",
19768
- `PLAYWRIGHT_BROWSERS_PATH=${SANDBOX_BROWSERS_PATH} npx --yes playwright@${PLAYWRIGHT_VERSION} install chromium`
19784
+ `npx --yes playwright@${PLAYWRIGHT_VERSION} install chromium`
19769
19785
  ].join("\n");
19770
19786
  var HostSchema = external_exports.string().trim().min(1).max(WORKSTATION_HOST_MAX_CHARS).regex(/^[A-Za-z0-9*._:-]+$/, "A host is a domain, optionally with a leading *. and a :port.");
19787
+ var WorkstationStepSchema = external_exports.object({
19788
+ id: external_exports.string().min(1).max(64),
19789
+ name: external_exports.string().trim().min(1).max(WORKSTATION_STEP_NAME_MAX_CHARS),
19790
+ script: external_exports.string().max(WORKSTATION_RECIPE_MAX_CHARS)
19791
+ });
19792
+ var WorkstationRepoSchema = external_exports.object({
19793
+ name: external_exports.string().trim().min(3).max(200).regex(/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/, "A repo is owner/name."),
19794
+ ref: external_exports.string().trim().min(1).max(200)
19795
+ });
19796
+ var WORKSTATION_SEED_STEPS = [
19797
+ { id: "step_claude_code", name: "Claude Code", script: "npm install -g @anthropic-ai/claude-code" },
19798
+ { id: "step_codex", name: "Codex", script: "npm install -g @openai/codex" },
19799
+ {
19800
+ id: "step_github_cli",
19801
+ name: "GitHub CLI",
19802
+ script: [
19803
+ "if command -v gh >/dev/null; then exit 0; fi",
19804
+ "if command -v brew >/dev/null; then brew install gh",
19805
+ "elif command -v dnf >/dev/null; then sudo -n dnf install -y gh",
19806
+ "elif command -v apt-get >/dev/null; then sudo -n apt-get install -y gh",
19807
+ 'else echo "no package manager here, install the GitHub CLI by hand" >&2; exit 1',
19808
+ "fi"
19809
+ ].join("\n")
19810
+ },
19811
+ {
19812
+ id: "step_chrome",
19813
+ name: "Chrome for Playwright",
19814
+ script: `npx --yes playwright@${PLAYWRIGHT_VERSION} install chromium`
19815
+ }
19816
+ ];
19817
+ var WorkstationStepResultSchema = external_exports.object({
19818
+ id: external_exports.string().min(1).max(64),
19819
+ name: external_exports.string().max(WORKSTATION_STEP_NAME_MAX_CHARS),
19820
+ ok: external_exports.boolean(),
19821
+ detail: external_exports.string().max(WORKSTATION_STEP_DETAIL_MAX_CHARS),
19822
+ ms: external_exports.number().int().nonnegative()
19823
+ });
19771
19824
  var WorkstationSchema = external_exports.object({
19772
- recipe: external_exports.string(),
19825
+ steps: external_exports.array(WorkstationStepSchema),
19826
+ repos: external_exports.array(WorkstationRepoSchema),
19773
19827
  allowlist: external_exports.array(external_exports.string()),
19774
19828
  templateId: external_exports.string().nullable(),
19775
19829
  publicKey: external_exports.string(),
@@ -19777,13 +19831,14 @@ var WorkstationSchema = external_exports.object({
19777
19831
  updatedByName: external_exports.string().nullable()
19778
19832
  });
19779
19833
  var SaveWorkstationBodySchema = external_exports.object({
19780
- recipe: external_exports.string().max(WORKSTATION_RECIPE_MAX_CHARS),
19834
+ steps: external_exports.array(WorkstationStepSchema).max(WORKSTATION_MAX_STEPS),
19835
+ repos: external_exports.array(WorkstationRepoSchema).max(WORKSTATION_MAX_REPOS),
19781
19836
  allowlist: external_exports.array(HostSchema).max(WORKSTATION_ALLOWLIST_MAX_HOSTS)
19782
19837
  });
19783
19838
  var WorkstationResponseSchema = external_exports.object({ workstation: WorkstationSchema });
19784
19839
 
19785
19840
  // ../../shared/dist/machines/machine.js
19786
- var CLI_VERSION = "0.4.2";
19841
+ var CLI_VERSION = "0.5.0";
19787
19842
  var CLI_PACKAGE = "@craftspace/cli";
19788
19843
  var CLI_INSTALL = `npm i -g ${CLI_PACKAGE}`;
19789
19844
  var MACHINE_BEAT_INTERVAL_MS = 15e3;
@@ -19804,7 +19859,7 @@ var MachineAuthorizedKeySchema = external_exports.object({
19804
19859
  addedByName: external_exports.string(),
19805
19860
  addedAt: external_exports.coerce.date()
19806
19861
  });
19807
- var MACHINE_TOOLS = ["brain", "claude", "codex", "git", "chrome", "ssh"];
19862
+ var MACHINE_TOOLS = ["brain", "ssh"];
19808
19863
  var MachineToolIdSchema = external_exports.enum(MACHINE_TOOLS);
19809
19864
  var MachineToolSchema = external_exports.object({
19810
19865
  id: MachineToolIdSchema,
@@ -19832,6 +19887,8 @@ var MachineSchema = external_exports.object({
19832
19887
  state: MachineStateSchema,
19833
19888
  sessions: external_exports.array(MachineSessionSchema),
19834
19889
  tools: external_exports.array(MachineToolSchema),
19890
+ environment: external_exports.array(WorkstationStepResultSchema),
19891
+ templateId: external_exports.string().nullable(),
19835
19892
  authorizedKeys: external_exports.array(MachineAuthorizedKeySchema),
19836
19893
  lastBeatAt: external_exports.coerce.date().nullable(),
19837
19894
  lastAskedAt: external_exports.coerce.date().nullable(),
@@ -19894,12 +19951,19 @@ var MachineBeatBodySchema = external_exports.object({
19894
19951
  sessions: external_exports.array(MachineSessionSchema).max(50),
19895
19952
  runs: external_exports.array(MachineRunReportSchema).max(20).default([]),
19896
19953
  tools: external_exports.array(MachineToolSchema).max(10).default([]),
19954
+ environment: external_exports.array(WorkstationStepResultSchema).max(WORKSTATION_MAX_STEPS).default([]),
19955
+ templateId: external_exports.string().max(64).nullable().default(null),
19897
19956
  sshUser: external_exports.string().min(1).max(64).optional()
19898
19957
  });
19958
+ var MachineEnvironmentSchema = external_exports.object({
19959
+ templateId: external_exports.string().nullable(),
19960
+ steps: external_exports.array(WorkstationStepSchema)
19961
+ });
19899
19962
  var MachineBeatResponseSchema = external_exports.object({
19900
19963
  cli: external_exports.object({ version: external_exports.string(), url: external_exports.string(), sha256: external_exports.string() }).nullable(),
19901
19964
  work: external_exports.array(MachineWorkSchema),
19902
- authorizedKeys: external_exports.array(external_exports.string())
19965
+ authorizedKeys: external_exports.array(external_exports.string()),
19966
+ environment: MachineEnvironmentSchema
19903
19967
  });
19904
19968
  var AddMachineKeyBodySchema = external_exports.object({
19905
19969
  key: external_exports.string().min(60).max(4e3),
@@ -20251,11 +20315,11 @@ var MemberActivityListResponse = external_exports.object({
20251
20315
  });
20252
20316
 
20253
20317
  // dist/machine.js
20254
- import { execFile as execFile4, spawn as spawn2 } from "node:child_process";
20318
+ import { execFile as execFile3, spawn as spawn2 } from "node:child_process";
20255
20319
  import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
20256
20320
  import os5 from "node:os";
20257
20321
  import path4 from "node:path";
20258
- import { promisify as promisify4 } from "node:util";
20322
+ import { promisify as promisify2 } from "node:util";
20259
20323
 
20260
20324
  // ../../../node_modules/@inquirer/core/dist/lib/key.js
20261
20325
  var keybindings = ["emacs", "vim"];
@@ -22121,9 +22185,50 @@ var dist_default6 = createPrompt((config2, done) => {
22121
22185
  return `${lines}${cursorHide}`;
22122
22186
  });
22123
22187
 
22188
+ // dist/probe.js
22189
+ import net from "node:net";
22190
+ import { readFile } from "node:fs/promises";
22191
+ import os from "node:os";
22192
+ import path2 from "node:path";
22193
+ var probe = {
22194
+ async tools({ mcpPath = path2.join(os.homedir(), ".mcp.json") } = {}) {
22195
+ return Promise.all([brainWired(mcpPath), sshListening()]);
22196
+ }
22197
+ };
22198
+ async function brainWired(mcpPath) {
22199
+ const raw = await readFile(mcpPath, "utf8").catch(() => null);
22200
+ if (raw === null)
22201
+ return { id: "brain", ok: false, detail: `no ${short(mcpPath)} on this machine` };
22202
+ const parsed = JSON.parse(raw);
22203
+ const servers = parsed?.mcpServers;
22204
+ const wired = servers !== void 0 && servers !== null && "craftspace" in servers;
22205
+ return {
22206
+ id: "brain",
22207
+ ok: wired,
22208
+ detail: wired ? short(mcpPath) : `${short(mcpPath)} has no craftspace entry`
22209
+ };
22210
+ }
22211
+ function sshListening() {
22212
+ return new Promise((resolve) => {
22213
+ const socket = net.connect({ host: "127.0.0.1", port: 22 });
22214
+ const answer = (ok, detail) => {
22215
+ socket.destroy();
22216
+ resolve({ id: "ssh", ok, detail });
22217
+ };
22218
+ socket.setTimeout(SSH_PROBE_TIMEOUT_MS);
22219
+ socket.on("connect", () => answer(true, "sshd is listening on port 22"));
22220
+ socket.on("timeout", () => answer(false, "nothing answered on port 22"));
22221
+ socket.on("error", () => answer(false, "sshd is not running, so nobody can open a terminal here"));
22222
+ });
22223
+ }
22224
+ function short(target) {
22225
+ return target.startsWith(os.homedir()) ? `~${target.slice(os.homedir().length)}` : target;
22226
+ }
22227
+ var SSH_PROBE_TIMEOUT_MS = 1500;
22228
+
22124
22229
  // dist/run.js
22125
22230
  import { spawn } from "node:child_process";
22126
- import os from "node:os";
22231
+ import os2 from "node:os";
22127
22232
  var runner = {
22128
22233
  attended(argv) {
22129
22234
  return new Promise((resolve) => {
@@ -22162,7 +22267,7 @@ var runner = {
22162
22267
  const code = await new Promise((resolve) => {
22163
22268
  const child = spawn(argv[0], argv.slice(1), {
22164
22269
  stdio: ["ignore", "pipe", "pipe"],
22165
- cwd: os.homedir(),
22270
+ cwd: os2.homedir(),
22166
22271
  env: {
22167
22272
  ...process.env,
22168
22273
  TERM: process.env.TERM ?? "xterm-256color",
@@ -22259,112 +22364,9 @@ function parseJson(line) {
22259
22364
  }
22260
22365
 
22261
22366
  // dist/setup.js
22262
- import { execFile as execFile2 } from "node:child_process";
22367
+ import { execFile } from "node:child_process";
22263
22368
  import os4 from "node:os";
22264
22369
  import path3 from "node:path";
22265
- import { promisify as promisify2 } from "node:util";
22266
-
22267
- // dist/probe.js
22268
- import { execFile } from "node:child_process";
22269
- import net from "node:net";
22270
- import { access, readFile } from "node:fs/promises";
22271
- import os2 from "node:os";
22272
- import path2 from "node:path";
22273
- import { promisify } from "node:util";
22274
- var run = promisify(execFile);
22275
- var probe = {
22276
- async tools({ mcpPath = path2.join(os2.homedir(), ".mcp.json") } = {}) {
22277
- const [brain, claude, codex, git, chrome, ssh] = await Promise.all([
22278
- brainWired(mcpPath),
22279
- version2({ id: "claude", command: "claude" }),
22280
- version2({ id: "codex", command: "codex" }),
22281
- gitAndGithub(),
22282
- chromeInstalled(),
22283
- sshListening()
22284
- ]);
22285
- return [brain, claude, codex, git, chrome, ssh];
22286
- }
22287
- };
22288
- async function brainWired(mcpPath) {
22289
- const raw = await readFile(mcpPath, "utf8").catch(() => null);
22290
- if (raw === null)
22291
- return { id: "brain", ok: false, detail: `no ${short(mcpPath)} on this machine` };
22292
- const parsed = JSON.parse(raw);
22293
- const servers = parsed?.mcpServers;
22294
- const wired = servers !== void 0 && servers !== null && "craftspace" in servers;
22295
- return {
22296
- id: "brain",
22297
- ok: wired,
22298
- detail: wired ? short(mcpPath) : `${short(mcpPath)} has no craftspace entry`
22299
- };
22300
- }
22301
- async function version2({ id, command }) {
22302
- const found = await firstLine(command, ["--version"]);
22303
- if (found.ran)
22304
- return { id, ok: true, detail: found.line };
22305
- return { id, ok: false, detail: found.why === "absent" ? "not installed" : "installed, but it will not report a version" };
22306
- }
22307
- async function gitAndGithub() {
22308
- const git = await firstLine("git", ["--version"]);
22309
- if (!git.ran)
22310
- return { id: "git", ok: false, detail: "git is not installed" };
22311
- const gh = await firstLine("gh", ["--version"]);
22312
- if (!gh.ran)
22313
- return { id: "git", ok: false, detail: `${git.line} \xB7 the GitHub CLI is not installed yet` };
22314
- const account = await githubAccount();
22315
- return {
22316
- id: "git",
22317
- ok: account !== null,
22318
- detail: account === null ? `${git.line} \xB7 ${gh.line}, run gh auth login on this machine` : `${git.line} \xB7 ${gh.line}, signed in as ${account}`
22319
- };
22320
- }
22321
- async function githubAccount() {
22322
- const raw = await readFile(path2.join(os2.homedir(), ".config", "gh", "hosts.yml"), "utf8").catch(() => null);
22323
- return raw === null ? null : githubUser(raw);
22324
- }
22325
- function githubUser(hostsYml) {
22326
- return /^ {4}user: *(\S+) *$/m.exec(hostsYml)?.[1] ?? null;
22327
- }
22328
- async function chromeInstalled() {
22329
- for (const command of [...MAC_CHROME, ...LINUX_CHROME]) {
22330
- const found = await firstLine(command, ["--version"]);
22331
- if (found.ran)
22332
- return { id: "chrome", ok: true, detail: found.line };
22333
- }
22334
- return { id: "chrome", ok: false, detail: "not installed yet, so browser runs will fail" };
22335
- }
22336
- function sshListening() {
22337
- return new Promise((resolve) => {
22338
- const socket = net.connect({ host: "127.0.0.1", port: 22 });
22339
- const answer = (ok, detail) => {
22340
- socket.destroy();
22341
- resolve({ id: "ssh", ok, detail });
22342
- };
22343
- socket.setTimeout(SSH_PROBE_TIMEOUT_MS);
22344
- socket.on("connect", () => answer(true, "sshd is listening on port 22"));
22345
- socket.on("timeout", () => answer(false, "nothing answered on port 22"));
22346
- socket.on("error", () => answer(false, "sshd is not running, so nobody can ssh in"));
22347
- });
22348
- }
22349
- async function firstLine(command, args) {
22350
- if (command.includes("/")) {
22351
- const reachable = await access(command).then(() => true, () => false);
22352
- if (!reachable)
22353
- return { ran: false, why: "absent" };
22354
- }
22355
- const answer = await run(command, args, { timeout: PROBE_TIMEOUT_MS }).catch((thrown) => thrown);
22356
- if (answer instanceof Error)
22357
- return { ran: false, why: answer.code === "ENOENT" ? "absent" : "broken" };
22358
- const line = answer.stdout.split("\n")[0]?.trim() ?? "";
22359
- return line === "" ? { ran: false, why: "broken" } : { ran: true, line: line.slice(0, 200) };
22360
- }
22361
- function short(target) {
22362
- return target.startsWith(os2.homedir()) ? `~${target.slice(os2.homedir().length)}` : target;
22363
- }
22364
- var MAC_CHROME = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
22365
- var LINUX_CHROME = ["google-chrome", "google-chrome-stable", "chromium"];
22366
- var PROBE_TIMEOUT_MS = 5e3;
22367
- var SSH_PROBE_TIMEOUT_MS = 1500;
22368
22370
 
22369
22371
  // node_modules/chalk/source/vendor/ansi-styles/index.js
22370
22372
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -22650,10 +22652,10 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
22650
22652
  return 3;
22651
22653
  }
22652
22654
  if ("TERM_PROGRAM" in env) {
22653
- const version3 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
22655
+ const version2 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
22654
22656
  switch (env.TERM_PROGRAM) {
22655
22657
  case "iTerm.app": {
22656
- return version3 >= 3 ? 3 : 2;
22658
+ return version2 >= 3 ? 3 : 2;
22657
22659
  }
22658
22660
  case "Apple_Terminal": {
22659
22661
  return 2;
@@ -22929,7 +22931,6 @@ var LABEL_WIDTH = 13;
22929
22931
  var BAR_CELLS = 10;
22930
22932
 
22931
22933
  // dist/setup.js
22932
- var run2 = promisify2(execFile2);
22933
22934
  var setup = {
22934
22935
  widenPath() {
22935
22936
  const current = (process.env.PATH ?? "").split(path3.delimiter).filter((entry) => entry !== "");
@@ -22937,44 +22938,34 @@ var setup = {
22937
22938
  if (missing.length > 0)
22938
22939
  process.env.PATH = [...current, ...missing].join(path3.delimiter);
22939
22940
  },
22940
- async run({ install, rewire: rewire2, say }) {
22941
- const found = await probe.tools();
22942
- if (!install)
22943
- return withReasons(found);
22944
- const repairs = { ...REPAIRS, ...rewire2 === void 0 ? {} : { brain: { name: "the brain", fix: rewire2 } } };
22945
- const broken = found.filter((tool) => !tool.ok && repairs[tool.id] !== void 0 && isDue(tool.id));
22946
- if (broken.length === 0)
22947
- return withReasons(found);
22948
- let ready = 0;
22949
- for (const [index, tool] of broken.entries()) {
22950
- const repair = repairs[tool.id];
22951
- if (repair === void 0)
22952
- continue;
22953
- say?.(ui.progress({ name: repair.name, done: index, total: broken.length }));
22954
- tried.set(tool.id, Date.now());
22955
- const failed = await repair.fix().then(() => null, (error51) => firstLine2(error51.message));
22956
- if (failed === null)
22957
- reasons.delete(tool.id);
22958
- else
22959
- reasons.set(tool.id, failed);
22960
- if (failed === null)
22961
- ready += 1;
22962
- say?.(`${ui.clear()}${failed === null ? ui.ok(repair.name) : ui.bad(repair.name, failed)}`);
22941
+ async runSteps({ steps, say }) {
22942
+ const results = [];
22943
+ for (const [index, step] of steps.entries()) {
22944
+ say?.(ui.progress({ name: step.name, done: index, total: steps.length }));
22945
+ const startedAt = Date.now();
22946
+ const outcome = await runScript(step.script);
22947
+ results.push({ ...outcome, id: step.id, name: step.name, ms: Date.now() - startedAt });
22948
+ say?.(`${ui.clear()}${outcome.ok ? ui.ok(step.name) : ui.bad(step.name, outcome.detail)}`);
22963
22949
  }
22964
- say?.(ui.tally({ done: ready, total: broken.length, noun: "ready" }));
22965
- return withReasons(await probe.tools());
22950
+ if (results.length > 0) {
22951
+ say?.(ui.tally({ done: results.filter((result) => result.ok).length, total: results.length, noun: "ready" }));
22952
+ }
22953
+ return results;
22966
22954
  }
22967
22955
  };
22968
- function isDue(id) {
22969
- const last = tried.get(id);
22970
- return last === void 0 || Date.now() - last > RETRY_AFTER_MS;
22971
- }
22972
- function withReasons(tools2) {
22973
- return tools2.map((tool) => {
22974
- const reason = tool.ok ? void 0 : reasons.get(tool.id);
22975
- return reason === void 0 ? tool : { ...tool, detail: `${tool.detail} \xB7 ${reason}`.slice(0, 200) };
22956
+ function runScript(script) {
22957
+ return new Promise((resolve) => {
22958
+ execFile("/bin/sh", ["-e", "-c", script], { timeout: STEP_TIMEOUT_MS, maxBuffer: SCRIPT_BUFFER_BYTES }, (error51, stdout, stderr) => {
22959
+ if (error51 === null)
22960
+ return resolve({ ok: true, detail: lastLines(stdout) });
22961
+ resolve({ ok: false, detail: lastLines(`${stderr}
22962
+ ${stdout}`) || error51.message.split("\n")[0] || "it failed" });
22963
+ });
22976
22964
  });
22977
22965
  }
22966
+ function lastLines(output) {
22967
+ return output.split("\n").map((line) => line.trim()).filter((line) => line !== "").slice(-KEPT_LINES).join(" \xB7 ").slice(0, WORKSTATION_STEP_DETAIL_MAX_CHARS);
22968
+ }
22978
22969
  function toolDirs() {
22979
22970
  const prefix = process.env.npm_config_prefix;
22980
22971
  return [
@@ -22986,56 +22977,14 @@ function toolDirs() {
22986
22977
  "/usr/local/bin"
22987
22978
  ];
22988
22979
  }
22989
- var REPAIRS = {
22990
- claude: { name: "Claude Code", fix: () => npmGlobal("@anthropic-ai/claude-code") },
22991
- codex: { name: "Codex", fix: () => npmGlobal("@openai/codex") },
22992
- git: {
22993
- name: "the GitHub CLI",
22994
- fix: async () => {
22995
- if (await firstAvailable(["gh"]) !== null)
22996
- return;
22997
- await installPackage({ brew: ["gh"], apt: ["gh"], dnf: ["gh"] });
22998
- }
22999
- },
23000
- chrome: {
23001
- name: "Google Chrome",
23002
- fix: () => installPackage({ brew: ["--cask", "google-chrome"], apt: ["chromium"], dnf: ["chromium"] })
23003
- }
23004
- };
23005
- async function npmGlobal(name) {
23006
- await run2("npm", ["install", "-g", name], { timeout: INSTALL_TIMEOUT_MS });
23007
- }
23008
- async function installPackage({ brew, apt, dnf }) {
23009
- if (process.platform === "darwin") {
23010
- await run2("brew", ["install", ...brew], { timeout: INSTALL_TIMEOUT_MS });
23011
- return;
23012
- }
23013
- const manager = await firstAvailable(["apt-get", "dnf"]);
23014
- if (manager === null)
23015
- throw new Error("no package manager this CLI knows how to drive");
23016
- const packages = manager === "apt-get" ? apt : dnf;
23017
- await run2("sudo", ["-n", manager, "install", "-y", ...packages], { timeout: INSTALL_TIMEOUT_MS });
23018
- }
23019
- async function firstAvailable(commands) {
23020
- for (const command of commands) {
23021
- const there = await run2("/bin/sh", ["-c", `command -v ${command}`]).then(() => true, () => false);
23022
- if (there)
23023
- return command;
23024
- }
23025
- return null;
23026
- }
23027
- function firstLine2(message) {
23028
- return message.split("\n")[0] ?? "it failed";
23029
- }
23030
- var tried = /* @__PURE__ */ new Map();
23031
- var reasons = /* @__PURE__ */ new Map();
23032
- var INSTALL_TIMEOUT_MS = 18e4;
23033
- var RETRY_AFTER_MS = 216e5;
22980
+ var STEP_TIMEOUT_MS = 6e5;
22981
+ var SCRIPT_BUFFER_BYTES = 4 * 1024 * 1024;
22982
+ var KEPT_LINES = 3;
23034
22983
 
23035
22984
  // dist/update.js
23036
- import { execFile as execFile3 } from "node:child_process";
23037
- import { promisify as promisify3 } from "node:util";
23038
- var run3 = promisify3(execFile3);
22985
+ import { execFile as execFile2 } from "node:child_process";
22986
+ import { promisify } from "node:util";
22987
+ var run = promisify(execFile2);
23039
22988
  var update = {
23040
22989
  async latest() {
23041
22990
  const answer = await fetch(`${REGISTRY}/${CLI_PACKAGE}/latest`, {
@@ -23045,17 +22994,17 @@ var update = {
23045
22994
  if (answer === null || !answer.ok)
23046
22995
  return null;
23047
22996
  const body = await answer.json().catch(() => null);
23048
- const version3 = isRecord(body) ? body.version : void 0;
23049
- return typeof version3 === "string" && isNewer(version3) ? version3 : null;
22997
+ const version2 = isRecord(body) ? body.version : void 0;
22998
+ return typeof version2 === "string" && isNewer(version2) ? version2 : null;
23050
22999
  },
23051
23000
  async toLatest() {
23052
- const version3 = await update.latest();
23053
- if (version3 === null)
23001
+ const version2 = await update.latest();
23002
+ if (version2 === null)
23054
23003
  return null;
23055
- const installed = await run3("npm", ["install", "--global", "--no-fund", `${CLI_PACKAGE}@${version3}`], {
23056
- timeout: INSTALL_TIMEOUT_MS2
23004
+ const installed = await run("npm", ["install", "--global", "--no-fund", `${CLI_PACKAGE}@${version2}`], {
23005
+ timeout: INSTALL_TIMEOUT_MS
23057
23006
  }).then(() => true, () => false);
23058
- return installed ? version3 : null;
23007
+ return installed ? version2 : null;
23059
23008
  }
23060
23009
  };
23061
23010
  function isNewer(candidate, current = CLI_VERSION) {
@@ -23069,18 +23018,18 @@ function isNewer(candidate, current = CLI_VERSION) {
23069
23018
  }
23070
23019
  return false;
23071
23020
  }
23072
- function parts(version3) {
23073
- return (/^\d+\.\d+\.\d+/.exec(version3.trim())?.[0] ?? "").split(".").map(Number);
23021
+ function parts(version2) {
23022
+ return (/^\d+\.\d+\.\d+/.exec(version2.trim())?.[0] ?? "").split(".").map(Number);
23074
23023
  }
23075
23024
  function isRecord(value) {
23076
23025
  return typeof value === "object" && value !== null;
23077
23026
  }
23078
23027
  var REGISTRY = "https://registry.npmjs.org";
23079
23028
  var REQUEST_TIMEOUT_MS = 1e4;
23080
- var INSTALL_TIMEOUT_MS2 = 18e4;
23029
+ var INSTALL_TIMEOUT_MS = 18e4;
23081
23030
 
23082
23031
  // dist/machine.js
23083
- var run4 = promisify4(execFile4);
23032
+ var run2 = promisify2(execFile3);
23084
23033
  var machine = {
23085
23034
  home() {
23086
23035
  return process.env.CRAFTSPACE_CLI_HOME ?? path4.join(os5.homedir(), ".craftspace");
@@ -23107,10 +23056,8 @@ var machine = {
23107
23056
  await writeFile(tokenPath(), token, { mode: 384 });
23108
23057
  await writeAgentConfigs({ url: url2, token, writes: answer.write });
23109
23058
  await installService();
23110
- probed = {
23111
- at: Date.now(),
23112
- tools: await setup.run({ install, rewire, say: ui.say })
23113
- };
23059
+ if (install)
23060
+ await machine.beatOnce(ui.say);
23114
23061
  return answer.machine;
23115
23062
  },
23116
23063
  async controlPlane(override) {
@@ -23147,8 +23094,9 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
23147
23094
  const found = await withAuth(await signedInOrAsk(), (auth) => call({ ...auth, method: "GET", path: "/api/machines/me", schema: MachineSchema }));
23148
23095
  return { machine: found.value, url: found.auth.url };
23149
23096
  },
23150
- async beatOnce() {
23097
+ async beatOnce(say) {
23151
23098
  const { url: url2, token } = await requireSignedIn();
23099
+ const built = await readEnvironment();
23152
23100
  const answer = await call({
23153
23101
  url: url2,
23154
23102
  token,
@@ -23159,12 +23107,19 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
23159
23107
  ...load2(),
23160
23108
  sessions: await readSessions(),
23161
23109
  runs: drainReports(),
23162
- tools: await tools(),
23110
+ tools: await checks(),
23111
+ environment: built?.results ?? [],
23112
+ templateId: built?.templateId ?? null,
23163
23113
  sshUser: os5.userInfo().username
23164
23114
  },
23165
23115
  schema: MachineBeatResponseSchema
23166
23116
  });
23167
23117
  await writeAuthorizedKeys(answer.authorizedKeys);
23118
+ const building2 = buildEnvironment(answer.environment, say);
23119
+ if (say === void 0)
23120
+ void building2.catch(() => void 0);
23121
+ else
23122
+ await building2;
23168
23123
  for (const work of answer.work) {
23169
23124
  if (running.has(work.id))
23170
23125
  continue;
@@ -23240,7 +23195,7 @@ async function pick2(machines) {
23240
23195
  choices: machines.map((candidate) => ({
23241
23196
  name: `${candidate.name.padEnd(24)} ${candidate.state.padEnd(12)} ${specs(candidate)} ${beat(candidate)}`,
23242
23197
  value: candidate,
23243
- description: candidate.sshAddress === null ? "No address yet, so nothing to open" : void 0
23198
+ disabled: candidate.sshAddress === null ? "(no address yet, so nothing to open)" : false
23244
23199
  })),
23245
23200
  pageSize: 12
23246
23201
  });
@@ -23268,7 +23223,10 @@ ${ui.dim("\u2192")} ${ui.name(target.name)} ${ui.dim(`\xB7 ${where}`)}
23268
23223
 
23269
23224
  `);
23270
23225
  return new Promise((resolve) => {
23271
- const session = spawn2("ssh", ["-t", where], { stdio: "inherit" });
23226
+ const session = spawn2("ssh", ["-t", where], {
23227
+ stdio: "inherit",
23228
+ env: { ...process.env, TERM: portableTerm(process.env.TERM) }
23229
+ });
23272
23230
  session.on("error", (error51) => {
23273
23231
  ui.say(ui.bad(`could not start ssh`, error51.message));
23274
23232
  resolve(1);
@@ -23276,6 +23234,9 @@ ${ui.dim("\u2192")} ${ui.name(target.name)} ${ui.dim(`\xB7 ${where}`)}
23276
23234
  session.on("exit", (code) => resolve(code ?? 0));
23277
23235
  });
23278
23236
  }
23237
+ function portableTerm(local) {
23238
+ return local !== void 0 && PORTABLE_TERMS.has(local) ? local : "xterm-256color";
23239
+ }
23279
23240
  function nowhereToGo({ url: url2 }) {
23280
23241
  ui.say(`
23281
23242
  ${ui.attention("No workstations here yet.")}
@@ -23286,13 +23247,51 @@ ${ui.attention("No workstations here yet.")}
23286
23247
  ui.say("\n");
23287
23248
  return 1;
23288
23249
  }
23289
- async function tools() {
23250
+ async function checks() {
23290
23251
  if (probed !== null && Date.now() - probed.at < PROBE_EVERY_MS)
23291
23252
  return probed.tools;
23292
- const found = await setup.run({ install: process.env.CRAFTSPACE_NO_INSTALL !== "1", rewire });
23253
+ const first = await probe.tools();
23254
+ const unwired = first.some((tool) => tool.id === "brain" && !tool.ok);
23255
+ const found = unwired && installs() ? await rewire().then(() => probe.tools(), () => first) : first;
23293
23256
  probed = { at: Date.now(), tools: found };
23294
23257
  return found;
23295
23258
  }
23259
+ async function buildEnvironment(next, say) {
23260
+ if (building || !installs() || next.templateId === null)
23261
+ return;
23262
+ const built = await readEnvironment();
23263
+ if (!buildIsDue(built, next.templateId))
23264
+ return;
23265
+ building = true;
23266
+ try {
23267
+ say?.(ui.heading(`Building the ${next.steps.length} setup scripts your team runs on every box`));
23268
+ const results = await setup.runSteps({ steps: next.steps, say });
23269
+ await writeEnvironment({ templateId: next.templateId, results, at: Date.now() });
23270
+ } finally {
23271
+ building = false;
23272
+ }
23273
+ }
23274
+ function buildIsDue(built, templateId) {
23275
+ if (built === null || built.templateId !== templateId)
23276
+ return true;
23277
+ if (built.results.every((result) => result.ok))
23278
+ return false;
23279
+ return Date.now() - built.at > REBUILD_AFTER_MS;
23280
+ }
23281
+ async function readEnvironment() {
23282
+ const raw = await readFile2(environmentPath(), "utf8").catch(() => null);
23283
+ if (raw === null)
23284
+ return null;
23285
+ const parsed = BuiltSchema.safeParse(JSON.parse(raw));
23286
+ return parsed.success ? parsed.data : null;
23287
+ }
23288
+ async function writeEnvironment(built) {
23289
+ await mkdir(machine.home(), { recursive: true, mode: 448 });
23290
+ await writeFile(environmentPath(), JSON.stringify(built, null, 2), { mode: 384 });
23291
+ }
23292
+ function installs() {
23293
+ return process.env.CRAFTSPACE_NO_INSTALL !== "1";
23294
+ }
23296
23295
  async function rewire() {
23297
23296
  const { url: url2, token } = await requireSignedIn();
23298
23297
  await writeAgentConfigs({ url: url2, token, writes: [] });
@@ -23353,7 +23352,7 @@ ${ui.note("Picking up where you left off.")}
23353
23352
  }
23354
23353
  async function openInBrowser(url2) {
23355
23354
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
23356
- await run4(opener, [url2]).catch(() => void 0);
23355
+ await run2(opener, [url2]).catch(() => void 0);
23357
23356
  }
23358
23357
  function tokenIn(line) {
23359
23358
  return /cst_[A-Za-z0-9_-]+/.exec(line)?.[0];
@@ -23365,7 +23364,7 @@ async function ensureKey() {
23365
23364
  if (existing !== null)
23366
23365
  return existing.trim();
23367
23366
  await mkdir(machine.home(), { recursive: true, mode: 448 });
23368
- await run4("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os5.hostname()}`]);
23367
+ await run2("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os5.hostname()}`]);
23369
23368
  return (await readFile2(pub, "utf8")).trim();
23370
23369
  }
23371
23370
  async function readConfig() {
@@ -23498,28 +23497,28 @@ async function installService() {
23498
23497
  const dir = path4.join(os5.homedir(), ".config", "systemd", "user");
23499
23498
  await mkdir(dir, { recursive: true });
23500
23499
  await writeFile(path4.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
23501
- await run4("loginctl", ["enable-linger", os5.userInfo().username]).catch(() => void 0);
23502
- await run4("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
23503
- await run4("systemctl", ["--user", "enable", "--now", SERVICE_NAME]).catch(() => void 0);
23500
+ await run2("loginctl", ["enable-linger", os5.userInfo().username]).catch(() => void 0);
23501
+ await run2("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
23502
+ await run2("systemctl", ["--user", "enable", "--now", SERVICE_NAME]).catch(() => void 0);
23504
23503
  return;
23505
23504
  }
23506
23505
  if (process.platform === "darwin") {
23507
23506
  const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
23508
23507
  await mkdir(path4.dirname(target), { recursive: true });
23509
23508
  await writeFile(target, launchdPlist());
23510
- await run4("launchctl", ["unload", target]).catch(() => void 0);
23511
- await run4("launchctl", ["load", target]).catch(() => void 0);
23509
+ await run2("launchctl", ["unload", target]).catch(() => void 0);
23510
+ await run2("launchctl", ["load", target]).catch(() => void 0);
23512
23511
  }
23513
23512
  }
23514
23513
  async function uninstallService() {
23515
23514
  if (process.platform === "linux") {
23516
- await run4("systemctl", ["--user", "disable", "--now", SERVICE_NAME]).catch(() => void 0);
23515
+ await run2("systemctl", ["--user", "disable", "--now", SERVICE_NAME]).catch(() => void 0);
23517
23516
  await rm(path4.join(os5.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`), { force: true });
23518
23517
  return;
23519
23518
  }
23520
23519
  if (process.platform === "darwin") {
23521
23520
  const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
23522
- await run4("launchctl", ["unload", target]).catch(() => void 0);
23521
+ await run2("launchctl", ["unload", target]).catch(() => void 0);
23523
23522
  await rm(target, { force: true });
23524
23523
  }
23525
23524
  }
@@ -23556,10 +23555,10 @@ function here() {
23556
23555
  return `${process.execPath} ${entryPath()}`;
23557
23556
  }
23558
23557
  async function serviceRunning() {
23559
- return await run4("systemctl", ["--user", "is-active", SERVICE_NAME]).catch(() => null) !== null;
23558
+ return await run2("systemctl", ["--user", "is-active", SERVICE_NAME]).catch(() => null) !== null;
23560
23559
  }
23561
23560
  async function userLingers() {
23562
- const shown = await run4("loginctl", ["show-user", os5.userInfo().username, "-p", "Linger"]).catch(() => null);
23561
+ const shown = await run2("loginctl", ["show-user", os5.userInfo().username, "-p", "Linger"]).catch(() => null);
23563
23562
  return shown?.stdout.includes("Linger=yes") === true;
23564
23563
  }
23565
23564
  function entryPath() {
@@ -23577,6 +23576,9 @@ function tokenPath() {
23577
23576
  function ownedPath() {
23578
23577
  return path4.join(machine.home(), "owned.json");
23579
23578
  }
23579
+ function environmentPath() {
23580
+ return path4.join(machine.home(), "environment.json");
23581
+ }
23580
23582
  function load2() {
23581
23583
  const cores = Math.max(1, os5.cpus().length);
23582
23584
  const busy = Math.min(100, (os5.loadavg()[0] ?? 0) / cores * 100);
@@ -23595,6 +23597,11 @@ function messageOf(error51) {
23595
23597
  return error51 instanceof Error ? error51.message : String(error51);
23596
23598
  }
23597
23599
  var ConfigSchema = external_exports.object({ url: external_exports.string(), machineId: external_exports.string() });
23600
+ var BuiltSchema = external_exports.object({
23601
+ templateId: external_exports.string(),
23602
+ results: external_exports.array(WorkstationStepResultSchema),
23603
+ at: external_exports.number()
23604
+ });
23598
23605
  var OwnedSchema = external_exports.object({ paths: external_exports.array(external_exports.string()) });
23599
23606
  var SERVICE_NAME = "craftspace";
23600
23607
  var LAUNCH_LABEL = "app.craftspace.machine";
@@ -23605,10 +23612,27 @@ var KEYS_BEGIN = "# craftspace begin";
23605
23612
  var KEYS_END = "# craftspace end";
23606
23613
  var UPDATE_EVERY_MS = 6 * 60 * 60 * 1e3;
23607
23614
  var BEATING = "beating every 15s, outbound only, and keeps this set up";
23615
+ var PORTABLE_TERMS = /* @__PURE__ */ new Set([
23616
+ "ansi",
23617
+ "dumb",
23618
+ "linux",
23619
+ "rxvt",
23620
+ "rxvt-unicode",
23621
+ "rxvt-unicode-256color",
23622
+ "screen",
23623
+ "screen-256color",
23624
+ "vt100",
23625
+ "vt220",
23626
+ "xterm",
23627
+ "xterm-color",
23628
+ "xterm-256color"
23629
+ ]);
23608
23630
  var LINGER_LINE = `sudo loginctl enable-linger ${os5.userInfo().username}`;
23609
23631
  var DEFAULT_URL = process.env.CRAFTSPACE_URL ?? "https://craftspace.app";
23610
23632
  var PROBE_EVERY_MS = 3e5;
23633
+ var REBUILD_AFTER_MS = 216e5;
23611
23634
  var probed = null;
23635
+ var building = false;
23612
23636
  var reports = /* @__PURE__ */ new Map();
23613
23637
  var running = /* @__PURE__ */ new Set();
23614
23638