@craftspace/cli 0.4.3 → 0.6.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",
@@ -18778,6 +18788,18 @@ var AuditActor;
18778
18788
  AuditActor2["System"] = "system";
18779
18789
  AuditActor2["Anonymous"] = "anonymous";
18780
18790
  })(AuditActor || (AuditActor = {}));
18791
+ var AuditResource;
18792
+ (function(AuditResource2) {
18793
+ AuditResource2["Members"] = "members";
18794
+ AuditResource2["Connections"] = "connections";
18795
+ AuditResource2["Pages"] = "pages";
18796
+ AuditResource2["Spaces"] = "spaces";
18797
+ AuditResource2["Tables"] = "tables";
18798
+ AuditResource2["Agents"] = "agents";
18799
+ AuditResource2["Automation"] = "automation";
18800
+ AuditResource2["Organization"] = "organization";
18801
+ AuditResource2["Tools"] = "tools";
18802
+ })(AuditResource || (AuditResource = {}));
18781
18803
  var AuditTier;
18782
18804
  (function(AuditTier2) {
18783
18805
  AuditTier2["Critical"] = "critical";
@@ -18890,7 +18912,8 @@ var AUDIT_RETENTION_DAYS = {
18890
18912
  };
18891
18913
  var AUDIT_GROUPS = [
18892
18914
  {
18893
- label: "Identity and access",
18915
+ resource: AuditResource.Members,
18916
+ label: "Members and tokens",
18894
18917
  actions: [
18895
18918
  AuditAction.AccessDenied,
18896
18919
  AuditAction.MemberSignedIn,
@@ -18904,7 +18927,8 @@ var AUDIT_GROUPS = [
18904
18927
  ]
18905
18928
  },
18906
18929
  {
18907
- label: "Connections",
18930
+ resource: AuditResource.Connections,
18931
+ label: "Connected apps",
18908
18932
  actions: [
18909
18933
  AuditAction.ConnectionCreated,
18910
18934
  AuditAction.ConnectionReconnected,
@@ -18916,23 +18940,34 @@ var AUDIT_GROUPS = [
18916
18940
  ]
18917
18941
  },
18918
18942
  {
18919
- label: "Knowledge",
18943
+ resource: AuditResource.Pages,
18944
+ label: "Pages",
18920
18945
  actions: [
18921
18946
  AuditAction.PageCreated,
18922
18947
  AuditAction.PageUpdated,
18923
18948
  AuditAction.PageDeleted,
18924
18949
  AuditAction.PageMoved,
18925
18950
  AuditAction.PageShared,
18926
- AuditAction.PageUnshared,
18951
+ AuditAction.PageUnshared
18952
+ ]
18953
+ },
18954
+ {
18955
+ resource: AuditResource.Spaces,
18956
+ label: "Spaces",
18957
+ actions: [
18927
18958
  AuditAction.SpaceCreated,
18928
18959
  AuditAction.SpaceRenamed,
18929
18960
  AuditAction.SpaceDeleted,
18930
- AuditAction.SpaceAccessChanged,
18931
- AuditAction.TableCreated,
18932
- AuditAction.TableDeleted
18961
+ AuditAction.SpaceAccessChanged
18933
18962
  ]
18934
18963
  },
18935
18964
  {
18965
+ resource: AuditResource.Tables,
18966
+ label: "Tables",
18967
+ actions: [AuditAction.TableCreated, AuditAction.TableDeleted]
18968
+ },
18969
+ {
18970
+ resource: AuditResource.Agents,
18936
18971
  label: "Agents and channels",
18937
18972
  actions: [
18938
18973
  AuditAction.AgentCreated,
@@ -18943,7 +18978,8 @@ var AUDIT_GROUPS = [
18943
18978
  ]
18944
18979
  },
18945
18980
  {
18946
- label: "Automation",
18981
+ resource: AuditResource.Automation,
18982
+ label: "Workflows and kits",
18947
18983
  actions: [
18948
18984
  AuditAction.KitInstalled,
18949
18985
  AuditAction.KitRemoved,
@@ -18954,10 +18990,7 @@ var AUDIT_GROUPS = [
18954
18990
  ]
18955
18991
  },
18956
18992
  {
18957
- label: "MCP calls",
18958
- actions: [AuditAction.McpToolCall, AuditAction.McpRequest]
18959
- },
18960
- {
18993
+ resource: AuditResource.Organization,
18961
18994
  label: "Organization",
18962
18995
  actions: [
18963
18996
  AuditAction.OrganizationRenamed,
@@ -18965,10 +18998,17 @@ var AUDIT_GROUPS = [
18965
18998
  AuditAction.OrganizationModelChanged,
18966
18999
  AuditAction.OrganizationDeleted
18967
19000
  ]
19001
+ },
19002
+ {
19003
+ resource: AuditResource.Tools,
19004
+ label: "Tool and MCP calls",
19005
+ actions: [AuditAction.McpToolCall, AuditAction.McpRequest]
18968
19006
  }
18969
19007
  ];
19008
+ var AUDIT_RESOURCES = Object.fromEntries(AUDIT_GROUPS.flatMap((group) => group.actions.map((action) => [action, group.resource])));
18970
19009
  var AUDIT_LISTED_ACTIONS = AUDIT_GROUPS.flatMap((group) => group.actions);
18971
- var AUDIT_DEFAULT_ACTIONS = AUDIT_LISTED_ACTIONS.filter((action) => AUDIT_TIERS[action] !== AuditTier.Volume);
19010
+ var AUDIT_QUIET_ACTIONS = [AuditAction.McpRequest, AuditAction.PageRead, AuditAction.PageWrite];
19011
+ var AUDIT_DEFAULT_ACTIONS = AUDIT_LISTED_ACTIONS.filter((action) => !AUDIT_QUIET_ACTIONS.includes(action));
18972
19012
  var AUDIT_LABELS = {
18973
19013
  [AuditAction.AccessDenied]: "Denied request",
18974
19014
  [AuditAction.MemberSignedIn]: "Signed in",
@@ -19018,6 +19058,11 @@ var AUDIT_LABELS = {
19018
19058
  [AuditAction.PageRead]: "Read page",
19019
19059
  [AuditAction.PageWrite]: "Wrote page"
19020
19060
  };
19061
+ var AuditAppSchema = external_exports.object({
19062
+ kind: external_exports.string(),
19063
+ id: external_exports.string().optional(),
19064
+ name: external_exports.string().optional()
19065
+ });
19021
19066
  var AuditTargetSchema = external_exports.object({
19022
19067
  type: external_exports.string(),
19023
19068
  id: external_exports.string().optional(),
@@ -19035,6 +19080,8 @@ var AuditEntrySchema = external_exports.object({
19035
19080
  image: external_exports.string().optional()
19036
19081
  }),
19037
19082
  target: AuditTargetSchema.optional(),
19083
+ app: AuditAppSchema.optional(),
19084
+ outcome: external_exports.enum(["ok", "error"]).optional(),
19038
19085
  data: external_exports.record(external_exports.string(), external_exports.unknown()),
19039
19086
  ip: external_exports.string().optional(),
19040
19087
  userAgent: external_exports.string().optional(),
@@ -19049,15 +19096,38 @@ var AuditSummarySchema = external_exports.object({
19049
19096
  var AuditQuerySchema = external_exports.object({
19050
19097
  days: external_exports.coerce.number().int().refine((value) => AUDIT_RANGES.includes(value)).default(AUDIT_DEFAULT_DAYS),
19051
19098
  page: external_exports.coerce.number().int().min(1).default(1),
19052
- action: external_exports.union([external_exports.enum(AuditAction), external_exports.array(external_exports.enum(AuditAction))]).optional().transform((value) => value === void 0 ? void 0 : Array.isArray(value) ? value : [value]),
19099
+ action: manyOf(external_exports.enum(AuditAction)),
19053
19100
  actor: external_exports.enum(AuditActor).optional(),
19054
- accountId: external_exports.string().optional()
19101
+ account: manyOf(external_exports.string()),
19102
+ app: manyOf(external_exports.string())
19103
+ });
19104
+ var AuditFacetsSchema = external_exports.object({
19105
+ actions: external_exports.record(external_exports.string(), external_exports.number()),
19106
+ accounts: external_exports.array(external_exports.object({
19107
+ accountId: external_exports.string(),
19108
+ name: external_exports.string(),
19109
+ email: external_exports.string().optional(),
19110
+ image: external_exports.string().optional(),
19111
+ count: external_exports.number()
19112
+ })),
19113
+ apps: external_exports.array(external_exports.object({ kind: external_exports.string(), name: external_exports.string().optional(), count: external_exports.number() }))
19055
19114
  });
19056
19115
  var AuditResponseSchema = external_exports.object({
19057
19116
  summary: AuditSummarySchema,
19058
19117
  entries: external_exports.array(AuditEntrySchema),
19118
+ facets: AuditFacetsSchema,
19059
19119
  total: external_exports.number()
19060
19120
  });
19121
+ var VERB_OVERRIDES = {
19122
+ [AuditAction.McpToolCall]: "called",
19123
+ [AuditAction.McpRequest]: "called",
19124
+ [AuditAction.PageRead]: "read",
19125
+ [AuditAction.PageWrite]: "written",
19126
+ [AuditAction.AdminEnteredOrg]: "opened as support"
19127
+ };
19128
+ function manyOf(schema) {
19129
+ return external_exports.union([schema, external_exports.array(schema)]).optional().transform((value) => value === void 0 ? void 0 : Array.isArray(value) ? value : [value]);
19130
+ }
19061
19131
 
19062
19132
  // ../../shared/dist/organization/install-admin.js
19063
19133
  var InstallCredentialNameSchema = external_exports.enum([
@@ -19310,7 +19380,8 @@ var OAuthCallbackQuerySchema = external_exports.object({
19310
19380
  });
19311
19381
  var ListConnectionsResponseSchema = external_exports.object({
19312
19382
  connections: external_exports.array(ConnectionSchema),
19313
- canManage: external_exports.boolean()
19383
+ canManage: external_exports.boolean(),
19384
+ egressIp: external_exports.string()
19314
19385
  });
19315
19386
  var CONNECTION_CATEGORIES = [
19316
19387
  "Sales & support",
@@ -19751,6 +19822,10 @@ var GUS_MAX_TURN_MS = 45 * 60 * 1e3;
19751
19822
  // ../../shared/dist/gus/workstation.js
19752
19823
  var WORKSTATION_CORES = 2;
19753
19824
  var WORKSTATION_RECIPE_MAX_CHARS = 8e3;
19825
+ var WORKSTATION_MAX_STEPS = 20;
19826
+ var WORKSTATION_STEP_NAME_MAX_CHARS = 60;
19827
+ var WORKSTATION_STEP_DETAIL_MAX_CHARS = 600;
19828
+ var WORKSTATION_MAX_REPOS = 20;
19754
19829
  var WORKSTATION_ALLOWLIST_MAX_HOSTS = 50;
19755
19830
  var WORKSTATION_HOST_MAX_CHARS = 200;
19756
19831
  var SANDBOX_BROWSERS_PATH = "/vercel/sandbox/.cache/ms-playwright";
@@ -19762,14 +19837,53 @@ var WORKSTATION_SPECS = {
19762
19837
  };
19763
19838
  var PLAYWRIGHT_VERSION = "1.49.1";
19764
19839
  var WORKSTATION_BASE_RECIPE = [
19765
- "# Craftspace base v2: a headless browser, ready to use.",
19840
+ "# Craftspace base v3: a headless browser, ready to use.",
19841
+ `export PLAYWRIGHT_BROWSERS_PATH=${SANDBOX_BROWSERS_PATH}`,
19766
19842
  "sudo dnf install -y nss nspr atk at-spi2-atk at-spi2-core cups-libs libdrm libxkbcommon \\",
19767
19843
  " 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`
19844
+ `npx --yes playwright@${PLAYWRIGHT_VERSION} install chromium`
19769
19845
  ].join("\n");
19770
19846
  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.");
19847
+ var WorkstationStepSchema = external_exports.object({
19848
+ id: external_exports.string().min(1).max(64),
19849
+ name: external_exports.string().trim().min(1).max(WORKSTATION_STEP_NAME_MAX_CHARS),
19850
+ script: external_exports.string().max(WORKSTATION_RECIPE_MAX_CHARS)
19851
+ });
19852
+ var WorkstationRepoSchema = external_exports.object({
19853
+ name: external_exports.string().trim().min(3).max(200).regex(/^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/, "A repo is owner/name."),
19854
+ ref: external_exports.string().trim().min(1).max(200)
19855
+ });
19856
+ var WORKSTATION_SEED_STEPS = [
19857
+ { id: "step_claude_code", name: "Claude Code", script: "npm install -g @anthropic-ai/claude-code" },
19858
+ { id: "step_codex", name: "Codex", script: "npm install -g @openai/codex" },
19859
+ {
19860
+ id: "step_github_cli",
19861
+ name: "GitHub CLI",
19862
+ script: [
19863
+ "if command -v gh >/dev/null; then exit 0; fi",
19864
+ "if command -v brew >/dev/null; then brew install gh",
19865
+ "elif command -v dnf >/dev/null; then sudo -n dnf install -y gh",
19866
+ "elif command -v apt-get >/dev/null; then sudo -n apt-get install -y gh",
19867
+ 'else echo "no package manager here, install the GitHub CLI by hand" >&2; exit 1',
19868
+ "fi"
19869
+ ].join("\n")
19870
+ },
19871
+ {
19872
+ id: "step_chrome",
19873
+ name: "Chrome for Playwright",
19874
+ script: `npx --yes playwright@${PLAYWRIGHT_VERSION} install chromium`
19875
+ }
19876
+ ];
19877
+ var WorkstationStepResultSchema = external_exports.object({
19878
+ id: external_exports.string().min(1).max(64),
19879
+ name: external_exports.string().max(WORKSTATION_STEP_NAME_MAX_CHARS),
19880
+ ok: external_exports.boolean(),
19881
+ detail: external_exports.string().max(WORKSTATION_STEP_DETAIL_MAX_CHARS),
19882
+ ms: external_exports.number().int().nonnegative()
19883
+ });
19771
19884
  var WorkstationSchema = external_exports.object({
19772
- recipe: external_exports.string(),
19885
+ steps: external_exports.array(WorkstationStepSchema),
19886
+ repos: external_exports.array(WorkstationRepoSchema),
19773
19887
  allowlist: external_exports.array(external_exports.string()),
19774
19888
  templateId: external_exports.string().nullable(),
19775
19889
  publicKey: external_exports.string(),
@@ -19777,13 +19891,14 @@ var WorkstationSchema = external_exports.object({
19777
19891
  updatedByName: external_exports.string().nullable()
19778
19892
  });
19779
19893
  var SaveWorkstationBodySchema = external_exports.object({
19780
- recipe: external_exports.string().max(WORKSTATION_RECIPE_MAX_CHARS),
19894
+ steps: external_exports.array(WorkstationStepSchema).max(WORKSTATION_MAX_STEPS),
19895
+ repos: external_exports.array(WorkstationRepoSchema).max(WORKSTATION_MAX_REPOS),
19781
19896
  allowlist: external_exports.array(HostSchema).max(WORKSTATION_ALLOWLIST_MAX_HOSTS)
19782
19897
  });
19783
19898
  var WorkstationResponseSchema = external_exports.object({ workstation: WorkstationSchema });
19784
19899
 
19785
19900
  // ../../shared/dist/machines/machine.js
19786
- var CLI_VERSION = "0.4.3";
19901
+ var CLI_VERSION = "0.6.0";
19787
19902
  var CLI_PACKAGE = "@craftspace/cli";
19788
19903
  var CLI_INSTALL = `npm i -g ${CLI_PACKAGE}`;
19789
19904
  var MACHINE_BEAT_INTERVAL_MS = 15e3;
@@ -19804,13 +19919,20 @@ var MachineAuthorizedKeySchema = external_exports.object({
19804
19919
  addedByName: external_exports.string(),
19805
19920
  addedAt: external_exports.coerce.date()
19806
19921
  });
19807
- var MACHINE_TOOLS = ["brain", "claude", "codex", "git", "chrome", "ssh"];
19922
+ var MACHINE_TOOLS = ["brain", "ssh"];
19808
19923
  var MachineToolIdSchema = external_exports.enum(MACHINE_TOOLS);
19809
19924
  var MachineToolSchema = external_exports.object({
19810
19925
  id: MachineToolIdSchema,
19811
19926
  ok: external_exports.boolean(),
19812
19927
  detail: external_exports.string().max(200)
19813
19928
  });
19929
+ var MachineRepoSchema = external_exports.object({
19930
+ name: external_exports.string().max(200),
19931
+ ref: external_exports.string().max(200),
19932
+ path: external_exports.string().max(400),
19933
+ ok: external_exports.boolean(),
19934
+ detail: external_exports.string().max(300)
19935
+ });
19814
19936
  var MachineSchema = external_exports.object({
19815
19937
  id: external_exports.string(),
19816
19938
  tokenId: external_exports.string(),
@@ -19832,6 +19954,10 @@ var MachineSchema = external_exports.object({
19832
19954
  state: MachineStateSchema,
19833
19955
  sessions: external_exports.array(MachineSessionSchema),
19834
19956
  tools: external_exports.array(MachineToolSchema),
19957
+ environment: external_exports.array(WorkstationStepResultSchema),
19958
+ templateId: external_exports.string().nullable(),
19959
+ workDir: external_exports.string().nullable(),
19960
+ repos: external_exports.array(MachineRepoSchema),
19835
19961
  authorizedKeys: external_exports.array(MachineAuthorizedKeySchema),
19836
19962
  lastBeatAt: external_exports.coerce.date().nullable(),
19837
19963
  lastAskedAt: external_exports.coerce.date().nullable(),
@@ -19894,12 +20020,22 @@ var MachineBeatBodySchema = external_exports.object({
19894
20020
  sessions: external_exports.array(MachineSessionSchema).max(50),
19895
20021
  runs: external_exports.array(MachineRunReportSchema).max(20).default([]),
19896
20022
  tools: external_exports.array(MachineToolSchema).max(10).default([]),
20023
+ environment: external_exports.array(WorkstationStepResultSchema).max(WORKSTATION_MAX_STEPS).default([]),
20024
+ templateId: external_exports.string().max(64).nullable().default(null),
20025
+ workDir: external_exports.string().max(400).optional(),
20026
+ repos: external_exports.array(MachineRepoSchema).max(WORKSTATION_MAX_REPOS).default([]),
19897
20027
  sshUser: external_exports.string().min(1).max(64).optional()
19898
20028
  });
20029
+ var MachineEnvironmentSchema = external_exports.object({
20030
+ templateId: external_exports.string().nullable(),
20031
+ steps: external_exports.array(WorkstationStepSchema),
20032
+ repos: external_exports.array(WorkstationRepoSchema).default([])
20033
+ });
19899
20034
  var MachineBeatResponseSchema = external_exports.object({
19900
20035
  cli: external_exports.object({ version: external_exports.string(), url: external_exports.string(), sha256: external_exports.string() }).nullable(),
19901
20036
  work: external_exports.array(MachineWorkSchema),
19902
- authorizedKeys: external_exports.array(external_exports.string())
20037
+ authorizedKeys: external_exports.array(external_exports.string()),
20038
+ environment: MachineEnvironmentSchema
19903
20039
  });
19904
20040
  var AddMachineKeyBodySchema = external_exports.object({
19905
20041
  key: external_exports.string().min(60).max(4e3),
@@ -20251,11 +20387,11 @@ var MemberActivityListResponse = external_exports.object({
20251
20387
  });
20252
20388
 
20253
20389
  // dist/machine.js
20254
- import { execFile as execFile4, spawn as spawn2 } from "node:child_process";
20255
- import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
20390
+ import { execFile as execFile3, spawn as spawn2 } from "node:child_process";
20391
+ import { mkdir as mkdir2, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
20256
20392
  import os5 from "node:os";
20257
20393
  import path4 from "node:path";
20258
- import { promisify as promisify4 } from "node:util";
20394
+ import { promisify as promisify2 } from "node:util";
20259
20395
 
20260
20396
  // ../../../node_modules/@inquirer/core/dist/lib/key.js
20261
20397
  var keybindings = ["emacs", "vim"];
@@ -21225,11 +21361,11 @@ function readlineWidth() {
21225
21361
 
21226
21362
  // ../../../node_modules/@inquirer/core/dist/lib/pagination/use-pagination.js
21227
21363
  function usePointerPosition({ active, renderedItems, pageSize, loop }) {
21228
- const state = useRef({
21364
+ const state2 = useRef({
21229
21365
  lastPointer: active,
21230
21366
  lastActive: void 0
21231
21367
  });
21232
- const { lastPointer, lastActive } = state.current;
21368
+ const { lastPointer, lastActive } = state2.current;
21233
21369
  const middle = Math.floor(pageSize / 2);
21234
21370
  const renderedLength = renderedItems.reduce((acc, item) => acc + item.length, 0);
21235
21371
  const defaultPointerPosition = renderedItems.slice(0, active).reduce((acc, item) => acc + item.length, 0);
@@ -21269,8 +21405,8 @@ function usePointerPosition({ active, renderedItems, pageSize, loop }) {
21269
21405
  );
21270
21406
  }
21271
21407
  }
21272
- state.current.lastPointer = pointer;
21273
- state.current.lastActive = active;
21408
+ state2.current.lastPointer = pointer;
21409
+ state2.current.lastActive = active;
21274
21410
  return pointer;
21275
21411
  }
21276
21412
  function usePagination({ items, active, renderItem, pageSize, loop = true }) {
@@ -22121,9 +22257,50 @@ var dist_default6 = createPrompt((config2, done) => {
22121
22257
  return `${lines}${cursorHide}`;
22122
22258
  });
22123
22259
 
22260
+ // dist/probe.js
22261
+ import net from "node:net";
22262
+ import { readFile } from "node:fs/promises";
22263
+ import os from "node:os";
22264
+ import path2 from "node:path";
22265
+ var probe = {
22266
+ async tools({ mcpPath = path2.join(os.homedir(), ".mcp.json") } = {}) {
22267
+ return Promise.all([brainWired(mcpPath), sshListening()]);
22268
+ }
22269
+ };
22270
+ async function brainWired(mcpPath) {
22271
+ const raw = await readFile(mcpPath, "utf8").catch(() => null);
22272
+ if (raw === null)
22273
+ return { id: "brain", ok: false, detail: `no ${short(mcpPath)} on this machine` };
22274
+ const parsed = JSON.parse(raw);
22275
+ const servers = parsed?.mcpServers;
22276
+ const wired = servers !== void 0 && servers !== null && "craftspace" in servers;
22277
+ return {
22278
+ id: "brain",
22279
+ ok: wired,
22280
+ detail: wired ? short(mcpPath) : `${short(mcpPath)} has no craftspace entry`
22281
+ };
22282
+ }
22283
+ function sshListening() {
22284
+ return new Promise((resolve) => {
22285
+ const socket = net.connect({ host: "127.0.0.1", port: 22 });
22286
+ const answer = (ok, detail) => {
22287
+ socket.destroy();
22288
+ resolve({ id: "ssh", ok, detail });
22289
+ };
22290
+ socket.setTimeout(SSH_PROBE_TIMEOUT_MS);
22291
+ socket.on("connect", () => answer(true, "sshd is listening on port 22"));
22292
+ socket.on("timeout", () => answer(false, "nothing answered on port 22"));
22293
+ socket.on("error", () => answer(false, "sshd is not running, so nobody can open a terminal here"));
22294
+ });
22295
+ }
22296
+ function short(target) {
22297
+ return target.startsWith(os.homedir()) ? `~${target.slice(os.homedir().length)}` : target;
22298
+ }
22299
+ var SSH_PROBE_TIMEOUT_MS = 1500;
22300
+
22124
22301
  // dist/run.js
22125
22302
  import { spawn } from "node:child_process";
22126
- import os from "node:os";
22303
+ import os2 from "node:os";
22127
22304
  var runner = {
22128
22305
  attended(argv) {
22129
22306
  return new Promise((resolve) => {
@@ -22132,7 +22309,7 @@ var runner = {
22132
22309
  child.on("close", (code) => resolve(code ?? 0));
22133
22310
  });
22134
22311
  },
22135
- async unattended({ work, onProgress }) {
22312
+ async unattended({ work, cwd = os2.homedir(), onProgress }) {
22136
22313
  const argv = headlessArgv(work);
22137
22314
  const raw = argv[0] === "script";
22138
22315
  let output = "";
@@ -22162,7 +22339,7 @@ var runner = {
22162
22339
  const code = await new Promise((resolve) => {
22163
22340
  const child = spawn(argv[0], argv.slice(1), {
22164
22341
  stdio: ["ignore", "pipe", "pipe"],
22165
- cwd: os.homedir(),
22342
+ cwd,
22166
22343
  env: {
22167
22344
  ...process.env,
22168
22345
  TERM: process.env.TERM ?? "xterm-256color",
@@ -22259,112 +22436,10 @@ function parseJson(line) {
22259
22436
  }
22260
22437
 
22261
22438
  // dist/setup.js
22262
- import { execFile as execFile2 } from "node:child_process";
22439
+ import { execFile } from "node:child_process";
22440
+ import { mkdir } from "node:fs/promises";
22263
22441
  import os4 from "node:os";
22264
22442
  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
22443
 
22369
22444
  // node_modules/chalk/source/vendor/ansi-styles/index.js
22370
22445
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -22650,10 +22725,10 @@ function _supportsColor(haveStream, { streamIsTTY, sniffFlags = true } = {}) {
22650
22725
  return 3;
22651
22726
  }
22652
22727
  if ("TERM_PROGRAM" in env) {
22653
- const version3 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
22728
+ const version2 = Number.parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
22654
22729
  switch (env.TERM_PROGRAM) {
22655
22730
  case "iTerm.app": {
22656
- return version3 >= 3 ? 3 : 2;
22731
+ return version2 >= 3 ? 3 : 2;
22657
22732
  }
22658
22733
  case "Apple_Terminal": {
22659
22734
  return 2;
@@ -22929,7 +23004,6 @@ var LABEL_WIDTH = 13;
22929
23004
  var BAR_CELLS = 10;
22930
23005
 
22931
23006
  // dist/setup.js
22932
- var run2 = promisify2(execFile2);
22933
23007
  var setup = {
22934
23008
  widenPath() {
22935
23009
  const current = (process.env.PATH ?? "").split(path3.delimiter).filter((entry) => entry !== "");
@@ -22937,44 +23011,89 @@ var setup = {
22937
23011
  if (missing.length > 0)
22938
23012
  process.env.PATH = [...current, ...missing].join(path3.delimiter);
22939
23013
  },
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)}`);
23014
+ workDir() {
23015
+ return path3.join(os4.homedir(), WORK_DIR_NAME);
23016
+ },
23017
+ async cloneRepos({ repos, say }) {
23018
+ if (repos.length === 0)
23019
+ return [];
23020
+ await mkdir(setup.workDir(), { recursive: true });
23021
+ const cloned = [];
23022
+ for (const [index, repo] of repos.entries()) {
23023
+ say?.(ui.progress({ name: repo.name, done: index, total: repos.length }));
23024
+ const at = path3.join(setup.workDir(), repo.name.split("/").pop() ?? repo.name);
23025
+ const outcome = await checkout(repo, at);
23026
+ cloned.push({ name: repo.name, ref: repo.ref, path: at, ...outcome });
23027
+ say?.(`${ui.clear()}${outcome.ok ? ui.ok(`${repo.name} \xB7 ${outcome.detail}`) : ui.bad(repo.name, outcome.detail)}`);
23028
+ }
23029
+ say?.(ui.tally({ done: cloned.filter((repo) => repo.ok).length, total: cloned.length, noun: "cloned" }));
23030
+ return cloned;
23031
+ },
23032
+ async runSteps({ steps, say }) {
23033
+ const results = [];
23034
+ for (const [index, step] of steps.entries()) {
23035
+ say?.(ui.progress({ name: step.name, done: index, total: steps.length }));
23036
+ const startedAt = Date.now();
23037
+ const outcome = await runScript(step.script);
23038
+ results.push({ ...outcome, id: step.id, name: step.name, ms: Date.now() - startedAt });
23039
+ say?.(`${ui.clear()}${outcome.ok ? ui.ok(step.name) : ui.bad(step.name, outcome.detail)}`);
23040
+ }
23041
+ if (results.length > 0) {
23042
+ say?.(ui.tally({ done: results.filter((result) => result.ok).length, total: results.length, noun: "ready" }));
22963
23043
  }
22964
- say?.(ui.tally({ done: ready, total: broken.length, noun: "ready" }));
22965
- return withReasons(await probe.tools());
23044
+ return results;
22966
23045
  }
22967
23046
  };
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) };
23047
+ async function checkout(repo, at) {
23048
+ const opened = await git(["-C", at, "rev-parse", "--abbrev-ref", "HEAD"]);
23049
+ if (!opened.ok) {
23050
+ const url2 = `https://github.com/${repo.name}.git`;
23051
+ const made = await git(["clone", "--depth", String(CLONE_DEPTH), "--branch", repo.ref, url2, at]);
23052
+ if (!made.ok)
23053
+ return { ok: false, detail: made.detail };
23054
+ return { ok: true, detail: await state({ repo, at, branch: repo.ref, dirty: false }) };
23055
+ }
23056
+ const branch = opened.detail;
23057
+ const dirty = (await git(["-C", at, "status", "--porcelain"])).detail !== "";
23058
+ const pulled = branch === repo.ref && !dirty ? await git(["-C", at, "pull", "--ff-only"]) : null;
23059
+ return { ok: true, detail: await state({ repo, at, branch, dirty, pulled }) };
23060
+ }
23061
+ async function state({ repo, at, branch, dirty, pulled }) {
23062
+ const head = await git(["-C", at, "rev-parse", "--short", "HEAD"]);
23063
+ return [
23064
+ `${branch} @ ${head.detail}`,
23065
+ branch === repo.ref ? "" : `your team asks for ${repo.ref}`,
23066
+ dirty ? "uncommitted work here, so it was left alone" : "",
23067
+ pulled?.ok === false ? `could not pull: ${pulled.detail}` : ""
23068
+ ].filter((part) => part !== "").join(" \xB7 ");
23069
+ }
23070
+ function git(argv) {
23071
+ return new Promise((resolve) => {
23072
+ execFile("git", argv, {
23073
+ timeout: CLONE_TIMEOUT_MS,
23074
+ maxBuffer: SCRIPT_BUFFER_BYTES,
23075
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "", GIT_LFS_SKIP_SMUDGE: "1" }
23076
+ }, (error51, stdout, stderr) => {
23077
+ if (error51 === null)
23078
+ return resolve({ ok: true, detail: stdout.trim() });
23079
+ resolve({ ok: false, detail: lastLines(`${stderr}
23080
+ ${stdout}`) || error51.message.split("\n")[0] || "git failed" });
23081
+ });
23082
+ });
23083
+ }
23084
+ function runScript(script) {
23085
+ return new Promise((resolve) => {
23086
+ execFile("/bin/sh", ["-e", "-c", script], { timeout: STEP_TIMEOUT_MS, maxBuffer: SCRIPT_BUFFER_BYTES }, (error51, stdout, stderr) => {
23087
+ if (error51 === null)
23088
+ return resolve({ ok: true, detail: lastLines(stdout) });
23089
+ resolve({ ok: false, detail: lastLines(`${stderr}
23090
+ ${stdout}`) || error51.message.split("\n")[0] || "it failed" });
23091
+ });
22976
23092
  });
22977
23093
  }
23094
+ function lastLines(output) {
23095
+ return output.split("\n").map((line) => line.trim()).filter((line) => line !== "").slice(-KEPT_LINES).join(" \xB7 ").slice(0, WORKSTATION_STEP_DETAIL_MAX_CHARS);
23096
+ }
22978
23097
  function toolDirs() {
22979
23098
  const prefix = process.env.npm_config_prefix;
22980
23099
  return [
@@ -22986,56 +23105,17 @@ function toolDirs() {
22986
23105
  "/usr/local/bin"
22987
23106
  ];
22988
23107
  }
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;
23108
+ var WORK_DIR_NAME = "craftspace";
23109
+ var CLONE_DEPTH = 20;
23110
+ var CLONE_TIMEOUT_MS = 9e5;
23111
+ var STEP_TIMEOUT_MS = 6e5;
23112
+ var SCRIPT_BUFFER_BYTES = 4 * 1024 * 1024;
23113
+ var KEPT_LINES = 3;
23034
23114
 
23035
23115
  // 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);
23116
+ import { execFile as execFile2 } from "node:child_process";
23117
+ import { promisify } from "node:util";
23118
+ var run = promisify(execFile2);
23039
23119
  var update = {
23040
23120
  async latest() {
23041
23121
  const answer = await fetch(`${REGISTRY}/${CLI_PACKAGE}/latest`, {
@@ -23045,17 +23125,17 @@ var update = {
23045
23125
  if (answer === null || !answer.ok)
23046
23126
  return null;
23047
23127
  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;
23128
+ const version2 = isRecord(body) ? body.version : void 0;
23129
+ return typeof version2 === "string" && isNewer(version2) ? version2 : null;
23050
23130
  },
23051
23131
  async toLatest() {
23052
- const version3 = await update.latest();
23053
- if (version3 === null)
23132
+ const version2 = await update.latest();
23133
+ if (version2 === null)
23054
23134
  return null;
23055
- const installed = await run3("npm", ["install", "--global", "--no-fund", `${CLI_PACKAGE}@${version3}`], {
23056
- timeout: INSTALL_TIMEOUT_MS2
23135
+ const installed = await run("npm", ["install", "--global", "--no-fund", `${CLI_PACKAGE}@${version2}`], {
23136
+ timeout: INSTALL_TIMEOUT_MS
23057
23137
  }).then(() => true, () => false);
23058
- return installed ? version3 : null;
23138
+ return installed ? version2 : null;
23059
23139
  }
23060
23140
  };
23061
23141
  function isNewer(candidate, current = CLI_VERSION) {
@@ -23069,18 +23149,18 @@ function isNewer(candidate, current = CLI_VERSION) {
23069
23149
  }
23070
23150
  return false;
23071
23151
  }
23072
- function parts(version3) {
23073
- return (/^\d+\.\d+\.\d+/.exec(version3.trim())?.[0] ?? "").split(".").map(Number);
23152
+ function parts(version2) {
23153
+ return (/^\d+\.\d+\.\d+/.exec(version2.trim())?.[0] ?? "").split(".").map(Number);
23074
23154
  }
23075
23155
  function isRecord(value) {
23076
23156
  return typeof value === "object" && value !== null;
23077
23157
  }
23078
23158
  var REGISTRY = "https://registry.npmjs.org";
23079
23159
  var REQUEST_TIMEOUT_MS = 1e4;
23080
- var INSTALL_TIMEOUT_MS2 = 18e4;
23160
+ var INSTALL_TIMEOUT_MS = 18e4;
23081
23161
 
23082
23162
  // dist/machine.js
23083
- var run4 = promisify4(execFile4);
23163
+ var run2 = promisify2(execFile3);
23084
23164
  var machine = {
23085
23165
  home() {
23086
23166
  return process.env.CRAFTSPACE_CLI_HOME ?? path4.join(os5.homedir(), ".craftspace");
@@ -23102,15 +23182,13 @@ var machine = {
23102
23182
  },
23103
23183
  schema: MachineLoginResponseSchema
23104
23184
  });
23105
- await mkdir(machine.home(), { recursive: true, mode: 448 });
23185
+ await mkdir2(machine.home(), { recursive: true, mode: 448 });
23106
23186
  await writeFile(configPath(), JSON.stringify({ url: url2, machineId: answer.machine.id }, null, 2), { mode: 384 });
23107
23187
  await writeFile(tokenPath(), token, { mode: 384 });
23108
23188
  await writeAgentConfigs({ url: url2, token, writes: answer.write });
23109
23189
  await installService();
23110
- probed = {
23111
- at: Date.now(),
23112
- tools: await setup.run({ install, rewire, say: ui.say })
23113
- };
23190
+ if (install)
23191
+ await machine.beatOnce(ui.say);
23114
23192
  return answer.machine;
23115
23193
  },
23116
23194
  async controlPlane(override) {
@@ -23147,8 +23225,9 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
23147
23225
  const found = await withAuth(await signedInOrAsk(), (auth) => call({ ...auth, method: "GET", path: "/api/machines/me", schema: MachineSchema }));
23148
23226
  return { machine: found.value, url: found.auth.url };
23149
23227
  },
23150
- async beatOnce() {
23228
+ async beatOnce(say) {
23151
23229
  const { url: url2, token } = await requireSignedIn();
23230
+ const built = await readEnvironment();
23152
23231
  const answer = await call({
23153
23232
  url: url2,
23154
23233
  token,
@@ -23159,17 +23238,26 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
23159
23238
  ...load2(),
23160
23239
  sessions: await readSessions(),
23161
23240
  runs: drainReports(),
23162
- tools: await tools(),
23241
+ tools: await checks(),
23242
+ environment: built?.results ?? [],
23243
+ templateId: built?.templateId ?? null,
23244
+ workDir: setup.workDir(),
23245
+ repos: built?.repos ?? [],
23163
23246
  sshUser: os5.userInfo().username
23164
23247
  },
23165
23248
  schema: MachineBeatResponseSchema
23166
23249
  });
23167
23250
  await writeAuthorizedKeys(answer.authorizedKeys);
23251
+ const building2 = buildEnvironment(answer.environment, say);
23252
+ if (say === void 0)
23253
+ void building2.catch(() => void 0);
23254
+ else
23255
+ await building2;
23168
23256
  for (const work of answer.work) {
23169
23257
  if (running.has(work.id))
23170
23258
  continue;
23171
23259
  running.add(work.id);
23172
- void runner.unattended({ work, onProgress: report }).then(report).catch((error51) => report({ id: work.id, state: "failed", output: messageOf(error51) })).finally(() => running.delete(work.id));
23260
+ void runner.unattended({ work, cwd: startIn(built), onProgress: report }).then(report).catch((error51) => report({ id: work.id, state: "failed", output: messageOf(error51) })).finally(() => running.delete(work.id));
23173
23261
  }
23174
23262
  return running.size > 0 || answer.work.length > 0;
23175
23263
  },
@@ -23178,7 +23266,7 @@ ${ui.ok(`Signed in as ${signed.ownerName}. This machine is ${ui.name(signed.name
23178
23266
  let busy = false;
23179
23267
  let checkedAt = 0;
23180
23268
  for (; ; ) {
23181
- if (!busy && Date.now() - checkedAt > UPDATE_EVERY_MS) {
23269
+ if (!busy && (failures > 2 || Date.now() - checkedAt > UPDATE_EVERY_MS)) {
23182
23270
  checkedAt = Date.now();
23183
23271
  const moved = await update.toLatest();
23184
23272
  if (moved !== null) {
@@ -23240,7 +23328,7 @@ async function pick2(machines) {
23240
23328
  choices: machines.map((candidate) => ({
23241
23329
  name: `${candidate.name.padEnd(24)} ${candidate.state.padEnd(12)} ${specs(candidate)} ${beat(candidate)}`,
23242
23330
  value: candidate,
23243
- description: candidate.sshAddress === null ? "No address yet, so nothing to open" : void 0
23331
+ disabled: candidate.sshAddress === null ? "(no address yet, so nothing to open)" : false
23244
23332
  })),
23245
23333
  pageSize: 12
23246
23334
  });
@@ -23292,13 +23380,74 @@ ${ui.attention("No workstations here yet.")}
23292
23380
  ui.say("\n");
23293
23381
  return 1;
23294
23382
  }
23295
- async function tools() {
23383
+ async function checks() {
23296
23384
  if (probed !== null && Date.now() - probed.at < PROBE_EVERY_MS)
23297
23385
  return probed.tools;
23298
- const found = await setup.run({ install: process.env.CRAFTSPACE_NO_INSTALL !== "1", rewire });
23386
+ const first = await probe.tools();
23387
+ const unwired = first.some((tool) => tool.id === "brain" && !tool.ok);
23388
+ const found = unwired && installs() ? await rewire().then(() => probe.tools(), () => first) : first;
23299
23389
  probed = { at: Date.now(), tools: found };
23300
23390
  return found;
23301
23391
  }
23392
+ async function buildEnvironment(next, say) {
23393
+ if (building || !installs() || next.templateId === null)
23394
+ return;
23395
+ const built = await readEnvironment();
23396
+ const buildsSteps = buildIsDue(built, next.templateId);
23397
+ const clonesRepos = cloneIsDue(built, next.repos);
23398
+ if (!buildsSteps && !clonesRepos)
23399
+ return;
23400
+ building = true;
23401
+ try {
23402
+ let results = built?.results ?? [];
23403
+ let repos = built?.repos ?? [];
23404
+ if (buildsSteps) {
23405
+ say?.(ui.heading(`Building the ${next.steps.length} setup scripts your team runs on every box`));
23406
+ results = await setup.runSteps({ steps: next.steps, say });
23407
+ }
23408
+ if (clonesRepos) {
23409
+ say?.(ui.heading(`Cloning the ${next.repos.length} repos your team works in, into ${setup.workDir()}`));
23410
+ repos = await setup.cloneRepos({ repos: next.repos, say });
23411
+ await acceptClaudeGates(["craftspace"], repos.filter((repo) => repo.ok).map((repo) => repo.path));
23412
+ }
23413
+ await writeEnvironment({ templateId: next.templateId, results, repos, at: Date.now() });
23414
+ } finally {
23415
+ building = false;
23416
+ }
23417
+ }
23418
+ function cloneIsDue(built, wanted) {
23419
+ const cloned = built?.repos ?? [];
23420
+ if (wanted.length === 0 && cloned.length === 0)
23421
+ return false;
23422
+ if (wanted.length !== cloned.length)
23423
+ return true;
23424
+ const missing = wanted.some((repo) => !cloned.some((seen) => seen.name === repo.name && seen.ref === repo.ref && seen.ok));
23425
+ return missing || Date.now() - (built?.at ?? 0) > REBUILD_AFTER_MS;
23426
+ }
23427
+ function startIn(built) {
23428
+ return built?.repos.find((repo) => repo.ok)?.path ?? os5.homedir();
23429
+ }
23430
+ function buildIsDue(built, templateId) {
23431
+ if (built === null || built.templateId !== templateId)
23432
+ return true;
23433
+ if (built.results.every((result) => result.ok))
23434
+ return false;
23435
+ return Date.now() - built.at > REBUILD_AFTER_MS;
23436
+ }
23437
+ async function readEnvironment() {
23438
+ const raw = await readFile2(environmentPath(), "utf8").catch(() => null);
23439
+ if (raw === null)
23440
+ return null;
23441
+ const parsed = BuiltSchema.safeParse(JSON.parse(raw));
23442
+ return parsed.success ? parsed.data : null;
23443
+ }
23444
+ async function writeEnvironment(built) {
23445
+ await mkdir2(machine.home(), { recursive: true, mode: 448 });
23446
+ await writeFile(environmentPath(), JSON.stringify(built, null, 2), { mode: 384 });
23447
+ }
23448
+ function installs() {
23449
+ return process.env.CRAFTSPACE_NO_INSTALL !== "1";
23450
+ }
23302
23451
  async function rewire() {
23303
23452
  const { url: url2, token } = await requireSignedIn();
23304
23453
  await writeAgentConfigs({ url: url2, token, writes: [] });
@@ -23359,7 +23508,7 @@ ${ui.note("Picking up where you left off.")}
23359
23508
  }
23360
23509
  async function openInBrowser(url2) {
23361
23510
  const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
23362
- await run4(opener, [url2]).catch(() => void 0);
23511
+ await run2(opener, [url2]).catch(() => void 0);
23363
23512
  }
23364
23513
  function tokenIn(line) {
23365
23514
  return /cst_[A-Za-z0-9_-]+/.exec(line)?.[0];
@@ -23370,8 +23519,8 @@ async function ensureKey() {
23370
23519
  const existing = await readFile2(pub, "utf8").catch(() => null);
23371
23520
  if (existing !== null)
23372
23521
  return existing.trim();
23373
- await mkdir(machine.home(), { recursive: true, mode: 448 });
23374
- await run4("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os5.hostname()}`]);
23522
+ await mkdir2(machine.home(), { recursive: true, mode: 448 });
23523
+ await run2("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os5.hostname()}`]);
23375
23524
  return (await readFile2(pub, "utf8")).trim();
23376
23525
  }
23377
23526
  async function readConfig() {
@@ -23410,7 +23559,7 @@ ${KEYS_END}
23410
23559
  const next = `${trimEnd(before)}${trimEnd(before) === "" ? "" : "\n"}${block}${after.replace(/^\n/, "")}`;
23411
23560
  if (next === current)
23412
23561
  return;
23413
- await mkdir(path4.dirname(target), { recursive: true, mode: 448 });
23562
+ await mkdir2(path4.dirname(target), { recursive: true, mode: 448 });
23414
23563
  await writeFile(target, next, { mode: 384 });
23415
23564
  }
23416
23565
  function trimEnd(text) {
@@ -23421,7 +23570,7 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
23421
23570
  const owned = [];
23422
23571
  for (const write of planned) {
23423
23572
  const target = expand(write.path);
23424
- await mkdir(path4.dirname(target), { recursive: true });
23573
+ await mkdir2(path4.dirname(target), { recursive: true });
23425
23574
  const current = await readFile2(target, "utf8").catch(() => null);
23426
23575
  if (current !== null)
23427
23576
  await writeFile(`${target}.craftspace-backup`, current, { flag: "wx" }).catch(() => void 0);
@@ -23436,22 +23585,29 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
23436
23585
  function serversIn(writes) {
23437
23586
  return writes.flatMap((write) => isPlainObject3(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
23438
23587
  }
23439
- async function acceptClaudeGates(servers) {
23588
+ async function acceptClaudeGates(servers, dirs = [os5.homedir()]) {
23440
23589
  const target = path4.join(os5.homedir(), ".claude.json");
23441
23590
  const raw = await readFile2(target, "utf8").catch(() => null);
23442
23591
  if (raw === null)
23443
23592
  return;
23444
23593
  const parsed = JSON.parse(raw);
23445
23594
  const projects = isPlainObject3(parsed.projects) ? parsed.projects : {};
23446
- const found = projects[os5.homedir()];
23447
- const here2 = isPlainObject3(found) ? { ...found } : {};
23448
- const enabled = /* @__PURE__ */ new Set([...Array.isArray(here2.enabledMcpjsonServers) ? here2.enabledMcpjsonServers : [], ...servers]);
23449
- if (here2.hasTrustDialogAccepted === true && enabled.size === asArray(here2.enabledMcpjsonServers).length)
23595
+ const opened = { ...projects };
23596
+ let moved = false;
23597
+ for (const dir of dirs) {
23598
+ const found = opened[dir];
23599
+ const here2 = isPlainObject3(found) ? { ...found } : {};
23600
+ const enabled = /* @__PURE__ */ new Set([...asArray(here2.enabledMcpjsonServers), ...servers]);
23601
+ if (here2.hasTrustDialogAccepted === true && enabled.size === asArray(here2.enabledMcpjsonServers).length)
23602
+ continue;
23603
+ here2.hasTrustDialogAccepted = true;
23604
+ here2.enabledMcpjsonServers = [...enabled];
23605
+ opened[dir] = here2;
23606
+ moved = true;
23607
+ }
23608
+ if (!moved)
23450
23609
  return;
23451
- here2.hasTrustDialogAccepted = true;
23452
- here2.enabledMcpjsonServers = [...enabled];
23453
- const next = { ...parsed, projects: { ...projects, [os5.homedir()]: here2 } };
23454
- await writeFile(target, `${JSON.stringify(next, null, 2)}
23610
+ await writeFile(target, `${JSON.stringify({ ...parsed, projects: opened }, null, 2)}
23455
23611
  `, { mode: 384 });
23456
23612
  }
23457
23613
  function asArray(value) {
@@ -23502,30 +23658,31 @@ async function installService() {
23502
23658
  return;
23503
23659
  if (process.platform === "linux") {
23504
23660
  const dir = path4.join(os5.homedir(), ".config", "systemd", "user");
23505
- await mkdir(dir, { recursive: true });
23661
+ await mkdir2(dir, { recursive: true });
23506
23662
  await writeFile(path4.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
23507
- await run4("loginctl", ["enable-linger", os5.userInfo().username]).catch(() => void 0);
23508
- await run4("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
23509
- await run4("systemctl", ["--user", "enable", "--now", SERVICE_NAME]).catch(() => void 0);
23663
+ await run2("loginctl", ["enable-linger", os5.userInfo().username]).catch(() => void 0);
23664
+ await run2("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
23665
+ await run2("systemctl", ["--user", "enable", SERVICE_NAME]).catch(() => void 0);
23666
+ await run2("systemctl", ["--user", "restart", SERVICE_NAME]).catch(() => void 0);
23510
23667
  return;
23511
23668
  }
23512
23669
  if (process.platform === "darwin") {
23513
23670
  const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
23514
- await mkdir(path4.dirname(target), { recursive: true });
23671
+ await mkdir2(path4.dirname(target), { recursive: true });
23515
23672
  await writeFile(target, launchdPlist());
23516
- await run4("launchctl", ["unload", target]).catch(() => void 0);
23517
- await run4("launchctl", ["load", target]).catch(() => void 0);
23673
+ await run2("launchctl", ["unload", target]).catch(() => void 0);
23674
+ await run2("launchctl", ["load", target]).catch(() => void 0);
23518
23675
  }
23519
23676
  }
23520
23677
  async function uninstallService() {
23521
23678
  if (process.platform === "linux") {
23522
- await run4("systemctl", ["--user", "disable", "--now", SERVICE_NAME]).catch(() => void 0);
23679
+ await run2("systemctl", ["--user", "disable", "--now", SERVICE_NAME]).catch(() => void 0);
23523
23680
  await rm(path4.join(os5.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`), { force: true });
23524
23681
  return;
23525
23682
  }
23526
23683
  if (process.platform === "darwin") {
23527
23684
  const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
23528
- await run4("launchctl", ["unload", target]).catch(() => void 0);
23685
+ await run2("launchctl", ["unload", target]).catch(() => void 0);
23529
23686
  await rm(target, { force: true });
23530
23687
  }
23531
23688
  }
@@ -23562,10 +23719,10 @@ function here() {
23562
23719
  return `${process.execPath} ${entryPath()}`;
23563
23720
  }
23564
23721
  async function serviceRunning() {
23565
- return await run4("systemctl", ["--user", "is-active", SERVICE_NAME]).catch(() => null) !== null;
23722
+ return await run2("systemctl", ["--user", "is-active", SERVICE_NAME]).catch(() => null) !== null;
23566
23723
  }
23567
23724
  async function userLingers() {
23568
- const shown = await run4("loginctl", ["show-user", os5.userInfo().username, "-p", "Linger"]).catch(() => null);
23725
+ const shown = await run2("loginctl", ["show-user", os5.userInfo().username, "-p", "Linger"]).catch(() => null);
23569
23726
  return shown?.stdout.includes("Linger=yes") === true;
23570
23727
  }
23571
23728
  function entryPath() {
@@ -23583,6 +23740,9 @@ function tokenPath() {
23583
23740
  function ownedPath() {
23584
23741
  return path4.join(machine.home(), "owned.json");
23585
23742
  }
23743
+ function environmentPath() {
23744
+ return path4.join(machine.home(), "environment.json");
23745
+ }
23586
23746
  function load2() {
23587
23747
  const cores = Math.max(1, os5.cpus().length);
23588
23748
  const busy = Math.min(100, (os5.loadavg()[0] ?? 0) / cores * 100);
@@ -23601,6 +23761,12 @@ function messageOf(error51) {
23601
23761
  return error51 instanceof Error ? error51.message : String(error51);
23602
23762
  }
23603
23763
  var ConfigSchema = external_exports.object({ url: external_exports.string(), machineId: external_exports.string() });
23764
+ var BuiltSchema = external_exports.object({
23765
+ templateId: external_exports.string(),
23766
+ results: external_exports.array(WorkstationStepResultSchema),
23767
+ repos: external_exports.array(MachineRepoSchema).default([]),
23768
+ at: external_exports.number()
23769
+ });
23604
23770
  var OwnedSchema = external_exports.object({ paths: external_exports.array(external_exports.string()) });
23605
23771
  var SERVICE_NAME = "craftspace";
23606
23772
  var LAUNCH_LABEL = "app.craftspace.machine";
@@ -23629,7 +23795,9 @@ var PORTABLE_TERMS = /* @__PURE__ */ new Set([
23629
23795
  var LINGER_LINE = `sudo loginctl enable-linger ${os5.userInfo().username}`;
23630
23796
  var DEFAULT_URL = process.env.CRAFTSPACE_URL ?? "https://craftspace.app";
23631
23797
  var PROBE_EVERY_MS = 3e5;
23798
+ var REBUILD_AFTER_MS = 216e5;
23632
23799
  var probed = null;
23800
+ var building = false;
23633
23801
  var reports = /* @__PURE__ */ new Map();
23634
23802
  var running = /* @__PURE__ */ new Set();
23635
23803