@craftspace/cli 0.4.3 → 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 +256 -253
- package/dist/machine.d.ts +1 -1
- package/dist/machine.js +66 -10
- package/dist/probe.d.ts +0 -1
- package/dist/probe.js +3 -66
- package/dist/setup.d.ts +4 -5
- package/dist/setup.js +32 -81
- package/package.json +1 -1
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
|
|
4636
|
-
const hasChecks =
|
|
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
|
|
4661
|
-
const hasChecks =
|
|
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
|
|
4688
|
-
const hasChecks =
|
|
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
|
|
4739
|
-
const hasChecks =
|
|
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 = (
|
|
5242
|
-
if (!
|
|
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}-${
|
|
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
|
|
5926
|
+
const checks2 = [...inst._zod.def.checks ?? []];
|
|
5927
5927
|
if (inst._zod.traits.has("$ZodCheck")) {
|
|
5928
|
-
|
|
5928
|
+
checks2.unshift(inst);
|
|
5929
5929
|
}
|
|
5930
|
-
for (const ch of
|
|
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 (
|
|
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,
|
|
5941
|
+
const runChecks = (payload, checks3, ctx) => {
|
|
5942
5942
|
let isAborted = aborted(payload);
|
|
5943
5943
|
let asyncResult;
|
|
5944
|
-
for (const ch of
|
|
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,
|
|
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,
|
|
6013
|
+
return result.then((result2) => runChecks(result2, checks2, ctx));
|
|
6014
6014
|
}
|
|
6015
|
-
return runChecks(result,
|
|
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
|
|
18120
|
+
const version2 = detectVersion(normalized, params?.defaultTarget);
|
|
18121
18121
|
const defs = normalized.$defs || normalized.definitions || {};
|
|
18122
18122
|
const ctx = {
|
|
18123
|
-
version:
|
|
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
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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", "
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
22941
|
-
const
|
|
22942
|
-
|
|
22943
|
-
|
|
22944
|
-
|
|
22945
|
-
|
|
22946
|
-
|
|
22947
|
-
|
|
22948
|
-
|
|
22949
|
-
|
|
22950
|
-
|
|
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)}`);
|
|
22949
|
+
}
|
|
22950
|
+
if (results.length > 0) {
|
|
22951
|
+
say?.(ui.tally({ done: results.filter((result) => result.ok).length, total: results.length, noun: "ready" }));
|
|
22963
22952
|
}
|
|
22964
|
-
|
|
22965
|
-
return withReasons(await probe.tools());
|
|
22953
|
+
return results;
|
|
22966
22954
|
}
|
|
22967
22955
|
};
|
|
22968
|
-
function
|
|
22969
|
-
|
|
22970
|
-
|
|
22971
|
-
|
|
22972
|
-
|
|
22973
|
-
|
|
22974
|
-
|
|
22975
|
-
|
|
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
|
|
22990
|
-
|
|
22991
|
-
|
|
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
|
|
23037
|
-
import { promisify
|
|
23038
|
-
var
|
|
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
|
|
23049
|
-
return typeof
|
|
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
|
|
23053
|
-
if (
|
|
23001
|
+
const version2 = await update.latest();
|
|
23002
|
+
if (version2 === null)
|
|
23054
23003
|
return null;
|
|
23055
|
-
const installed = await
|
|
23056
|
-
timeout:
|
|
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 ?
|
|
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(
|
|
23073
|
-
return (/^\d+\.\d+\.\d+/.exec(
|
|
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
|
|
23029
|
+
var INSTALL_TIMEOUT_MS = 18e4;
|
|
23081
23030
|
|
|
23082
23031
|
// dist/machine.js
|
|
23083
|
-
var
|
|
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
|
-
|
|
23111
|
-
|
|
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
|
|
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
|
-
|
|
23198
|
+
disabled: candidate.sshAddress === null ? "(no address yet, so nothing to open)" : false
|
|
23244
23199
|
})),
|
|
23245
23200
|
pageSize: 12
|
|
23246
23201
|
});
|
|
@@ -23292,13 +23247,51 @@ ${ui.attention("No workstations here yet.")}
|
|
|
23292
23247
|
ui.say("\n");
|
|
23293
23248
|
return 1;
|
|
23294
23249
|
}
|
|
23295
|
-
async function
|
|
23250
|
+
async function checks() {
|
|
23296
23251
|
if (probed !== null && Date.now() - probed.at < PROBE_EVERY_MS)
|
|
23297
23252
|
return probed.tools;
|
|
23298
|
-
const
|
|
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;
|
|
23299
23256
|
probed = { at: Date.now(), tools: found };
|
|
23300
23257
|
return found;
|
|
23301
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
|
+
}
|
|
23302
23295
|
async function rewire() {
|
|
23303
23296
|
const { url: url2, token } = await requireSignedIn();
|
|
23304
23297
|
await writeAgentConfigs({ url: url2, token, writes: [] });
|
|
@@ -23359,7 +23352,7 @@ ${ui.note("Picking up where you left off.")}
|
|
|
23359
23352
|
}
|
|
23360
23353
|
async function openInBrowser(url2) {
|
|
23361
23354
|
const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
23362
|
-
await
|
|
23355
|
+
await run2(opener, [url2]).catch(() => void 0);
|
|
23363
23356
|
}
|
|
23364
23357
|
function tokenIn(line) {
|
|
23365
23358
|
return /cst_[A-Za-z0-9_-]+/.exec(line)?.[0];
|
|
@@ -23371,7 +23364,7 @@ async function ensureKey() {
|
|
|
23371
23364
|
if (existing !== null)
|
|
23372
23365
|
return existing.trim();
|
|
23373
23366
|
await mkdir(machine.home(), { recursive: true, mode: 448 });
|
|
23374
|
-
await
|
|
23367
|
+
await run2("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os5.hostname()}`]);
|
|
23375
23368
|
return (await readFile2(pub, "utf8")).trim();
|
|
23376
23369
|
}
|
|
23377
23370
|
async function readConfig() {
|
|
@@ -23504,28 +23497,28 @@ async function installService() {
|
|
|
23504
23497
|
const dir = path4.join(os5.homedir(), ".config", "systemd", "user");
|
|
23505
23498
|
await mkdir(dir, { recursive: true });
|
|
23506
23499
|
await writeFile(path4.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
23507
|
-
await
|
|
23508
|
-
await
|
|
23509
|
-
await
|
|
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);
|
|
23510
23503
|
return;
|
|
23511
23504
|
}
|
|
23512
23505
|
if (process.platform === "darwin") {
|
|
23513
23506
|
const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
23514
23507
|
await mkdir(path4.dirname(target), { recursive: true });
|
|
23515
23508
|
await writeFile(target, launchdPlist());
|
|
23516
|
-
await
|
|
23517
|
-
await
|
|
23509
|
+
await run2("launchctl", ["unload", target]).catch(() => void 0);
|
|
23510
|
+
await run2("launchctl", ["load", target]).catch(() => void 0);
|
|
23518
23511
|
}
|
|
23519
23512
|
}
|
|
23520
23513
|
async function uninstallService() {
|
|
23521
23514
|
if (process.platform === "linux") {
|
|
23522
|
-
await
|
|
23515
|
+
await run2("systemctl", ["--user", "disable", "--now", SERVICE_NAME]).catch(() => void 0);
|
|
23523
23516
|
await rm(path4.join(os5.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`), { force: true });
|
|
23524
23517
|
return;
|
|
23525
23518
|
}
|
|
23526
23519
|
if (process.platform === "darwin") {
|
|
23527
23520
|
const target = path4.join(os5.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
23528
|
-
await
|
|
23521
|
+
await run2("launchctl", ["unload", target]).catch(() => void 0);
|
|
23529
23522
|
await rm(target, { force: true });
|
|
23530
23523
|
}
|
|
23531
23524
|
}
|
|
@@ -23562,10 +23555,10 @@ function here() {
|
|
|
23562
23555
|
return `${process.execPath} ${entryPath()}`;
|
|
23563
23556
|
}
|
|
23564
23557
|
async function serviceRunning() {
|
|
23565
|
-
return await
|
|
23558
|
+
return await run2("systemctl", ["--user", "is-active", SERVICE_NAME]).catch(() => null) !== null;
|
|
23566
23559
|
}
|
|
23567
23560
|
async function userLingers() {
|
|
23568
|
-
const shown = await
|
|
23561
|
+
const shown = await run2("loginctl", ["show-user", os5.userInfo().username, "-p", "Linger"]).catch(() => null);
|
|
23569
23562
|
return shown?.stdout.includes("Linger=yes") === true;
|
|
23570
23563
|
}
|
|
23571
23564
|
function entryPath() {
|
|
@@ -23583,6 +23576,9 @@ function tokenPath() {
|
|
|
23583
23576
|
function ownedPath() {
|
|
23584
23577
|
return path4.join(machine.home(), "owned.json");
|
|
23585
23578
|
}
|
|
23579
|
+
function environmentPath() {
|
|
23580
|
+
return path4.join(machine.home(), "environment.json");
|
|
23581
|
+
}
|
|
23586
23582
|
function load2() {
|
|
23587
23583
|
const cores = Math.max(1, os5.cpus().length);
|
|
23588
23584
|
const busy = Math.min(100, (os5.loadavg()[0] ?? 0) / cores * 100);
|
|
@@ -23601,6 +23597,11 @@ function messageOf(error51) {
|
|
|
23601
23597
|
return error51 instanceof Error ? error51.message : String(error51);
|
|
23602
23598
|
}
|
|
23603
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
|
+
});
|
|
23604
23605
|
var OwnedSchema = external_exports.object({ paths: external_exports.array(external_exports.string()) });
|
|
23605
23606
|
var SERVICE_NAME = "craftspace";
|
|
23606
23607
|
var LAUNCH_LABEL = "app.craftspace.machine";
|
|
@@ -23629,7 +23630,9 @@ var PORTABLE_TERMS = /* @__PURE__ */ new Set([
|
|
|
23629
23630
|
var LINGER_LINE = `sudo loginctl enable-linger ${os5.userInfo().username}`;
|
|
23630
23631
|
var DEFAULT_URL = process.env.CRAFTSPACE_URL ?? "https://craftspace.app";
|
|
23631
23632
|
var PROBE_EVERY_MS = 3e5;
|
|
23633
|
+
var REBUILD_AFTER_MS = 216e5;
|
|
23632
23634
|
var probed = null;
|
|
23635
|
+
var building = false;
|
|
23633
23636
|
var reports = /* @__PURE__ */ new Map();
|
|
23634
23637
|
var running = /* @__PURE__ */ new Set();
|
|
23635
23638
|
|
package/dist/machine.d.ts
CHANGED
package/dist/machine.js
CHANGED
|
@@ -3,9 +3,10 @@ import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
|
-
import { CLI_VERSION, MACHINE_BEAT_BUSY_INTERVAL_MS, MACHINE_BEAT_INTERVAL_MS, MachineBeatResponseSchema, MachineLoginResponseSchema, MachineSchema, MachineSessionSchema, MachinesResponseSchema, } from '@craftspace/shared';
|
|
6
|
+
import { CLI_VERSION, MACHINE_BEAT_BUSY_INTERVAL_MS, MACHINE_BEAT_INTERVAL_MS, MachineBeatResponseSchema, MachineLoginResponseSchema, MachineSchema, MachineSessionSchema, MachinesResponseSchema, WorkstationStepResultSchema, } from '@craftspace/shared';
|
|
7
7
|
import { confirm, input, select } from '@inquirer/prompts';
|
|
8
8
|
import { z } from 'zod';
|
|
9
|
+
import { probe } from './probe.js';
|
|
9
10
|
import { runner } from './run.js';
|
|
10
11
|
import { setup } from './setup.js';
|
|
11
12
|
import { ui } from './ui.js';
|
|
@@ -37,10 +38,8 @@ export const machine = {
|
|
|
37
38
|
await writeFile(tokenPath(), token, { mode: 0o600 });
|
|
38
39
|
await writeAgentConfigs({ url, token, writes: answer.write });
|
|
39
40
|
await installService();
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
tools: await setup.run({ install, rewire, say: ui.say }),
|
|
43
|
-
};
|
|
41
|
+
if (install)
|
|
42
|
+
await machine.beatOnce(ui.say);
|
|
44
43
|
return answer.machine;
|
|
45
44
|
},
|
|
46
45
|
async controlPlane(override) {
|
|
@@ -74,8 +73,9 @@ export const machine = {
|
|
|
74
73
|
const found = await withAuth(await signedInOrAsk(), (auth) => call({ ...auth, method: 'GET', path: '/api/machines/me', schema: MachineSchema }));
|
|
75
74
|
return { machine: found.value, url: found.auth.url };
|
|
76
75
|
},
|
|
77
|
-
async beatOnce() {
|
|
76
|
+
async beatOnce(say) {
|
|
78
77
|
const { url, token } = await requireSignedIn();
|
|
78
|
+
const built = await readEnvironment();
|
|
79
79
|
const answer = await call({
|
|
80
80
|
url,
|
|
81
81
|
token,
|
|
@@ -86,12 +86,19 @@ export const machine = {
|
|
|
86
86
|
...load(),
|
|
87
87
|
sessions: await readSessions(),
|
|
88
88
|
runs: drainReports(),
|
|
89
|
-
tools: await
|
|
89
|
+
tools: await checks(),
|
|
90
|
+
environment: built?.results ?? [],
|
|
91
|
+
templateId: built?.templateId ?? null,
|
|
90
92
|
sshUser: os.userInfo().username,
|
|
91
93
|
},
|
|
92
94
|
schema: MachineBeatResponseSchema,
|
|
93
95
|
});
|
|
94
96
|
await writeAuthorizedKeys(answer.authorizedKeys);
|
|
97
|
+
const building = buildEnvironment(answer.environment, say);
|
|
98
|
+
if (say === undefined)
|
|
99
|
+
void building.catch(() => undefined);
|
|
100
|
+
else
|
|
101
|
+
await building;
|
|
95
102
|
for (const work of answer.work) {
|
|
96
103
|
if (running.has(work.id))
|
|
97
104
|
continue;
|
|
@@ -170,7 +177,7 @@ async function pick(machines) {
|
|
|
170
177
|
choices: machines.map((candidate) => ({
|
|
171
178
|
name: `${candidate.name.padEnd(24)} ${candidate.state.padEnd(12)} ${specs(candidate)} ${beat(candidate)}`,
|
|
172
179
|
value: candidate,
|
|
173
|
-
|
|
180
|
+
disabled: candidate.sshAddress === null ? '(no address yet, so nothing to open)' : false,
|
|
174
181
|
})),
|
|
175
182
|
pageSize: 12,
|
|
176
183
|
});
|
|
@@ -218,13 +225,52 @@ function nowhereToGo({ url }) {
|
|
|
218
225
|
ui.say('\n');
|
|
219
226
|
return 1;
|
|
220
227
|
}
|
|
221
|
-
async function
|
|
228
|
+
async function checks() {
|
|
222
229
|
if (probed !== null && Date.now() - probed.at < PROBE_EVERY_MS)
|
|
223
230
|
return probed.tools;
|
|
224
|
-
const
|
|
231
|
+
const first = await probe.tools();
|
|
232
|
+
const unwired = first.some((tool) => tool.id === 'brain' && !tool.ok);
|
|
233
|
+
const found = unwired && installs() ? await rewire().then(() => probe.tools(), () => first) : first;
|
|
225
234
|
probed = { at: Date.now(), tools: found };
|
|
226
235
|
return found;
|
|
227
236
|
}
|
|
237
|
+
async function buildEnvironment(next, say) {
|
|
238
|
+
if (building || !installs() || next.templateId === null)
|
|
239
|
+
return;
|
|
240
|
+
const built = await readEnvironment();
|
|
241
|
+
if (!buildIsDue(built, next.templateId))
|
|
242
|
+
return;
|
|
243
|
+
building = true;
|
|
244
|
+
try {
|
|
245
|
+
say?.(ui.heading(`Building the ${next.steps.length} setup scripts your team runs on every box`));
|
|
246
|
+
const results = await setup.runSteps({ steps: next.steps, say });
|
|
247
|
+
await writeEnvironment({ templateId: next.templateId, results, at: Date.now() });
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
building = false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function buildIsDue(built, templateId) {
|
|
254
|
+
if (built === null || built.templateId !== templateId)
|
|
255
|
+
return true;
|
|
256
|
+
if (built.results.every((result) => result.ok))
|
|
257
|
+
return false;
|
|
258
|
+
return Date.now() - built.at > REBUILD_AFTER_MS;
|
|
259
|
+
}
|
|
260
|
+
async function readEnvironment() {
|
|
261
|
+
const raw = await readFile(environmentPath(), 'utf8').catch(() => null);
|
|
262
|
+
if (raw === null)
|
|
263
|
+
return null;
|
|
264
|
+
const parsed = BuiltSchema.safeParse(JSON.parse(raw));
|
|
265
|
+
return parsed.success ? parsed.data : null;
|
|
266
|
+
}
|
|
267
|
+
async function writeEnvironment(built) {
|
|
268
|
+
await mkdir(machine.home(), { recursive: true, mode: 0o700 });
|
|
269
|
+
await writeFile(environmentPath(), JSON.stringify(built, null, 2), { mode: 0o600 });
|
|
270
|
+
}
|
|
271
|
+
function installs() {
|
|
272
|
+
return process.env.CRAFTSPACE_NO_INSTALL !== '1';
|
|
273
|
+
}
|
|
228
274
|
async function rewire() {
|
|
229
275
|
const { url, token } = await requireSignedIn();
|
|
230
276
|
await writeAgentConfigs({ url, token, writes: [] });
|
|
@@ -503,6 +549,9 @@ function tokenPath() {
|
|
|
503
549
|
function ownedPath() {
|
|
504
550
|
return path.join(machine.home(), 'owned.json');
|
|
505
551
|
}
|
|
552
|
+
function environmentPath() {
|
|
553
|
+
return path.join(machine.home(), 'environment.json');
|
|
554
|
+
}
|
|
506
555
|
function load() {
|
|
507
556
|
const cores = Math.max(1, os.cpus().length);
|
|
508
557
|
const busy = Math.min(100, ((os.loadavg()[0] ?? 0) / cores) * 100);
|
|
@@ -521,6 +570,11 @@ function messageOf(error) {
|
|
|
521
570
|
return error instanceof Error ? error.message : String(error);
|
|
522
571
|
}
|
|
523
572
|
const ConfigSchema = z.object({ url: z.string(), machineId: z.string() });
|
|
573
|
+
const BuiltSchema = z.object({
|
|
574
|
+
templateId: z.string(),
|
|
575
|
+
results: z.array(WorkstationStepResultSchema),
|
|
576
|
+
at: z.number(),
|
|
577
|
+
});
|
|
524
578
|
const OwnedSchema = z.object({ paths: z.array(z.string()) });
|
|
525
579
|
const SERVICE_NAME = 'craftspace';
|
|
526
580
|
const LAUNCH_LABEL = 'app.craftspace.machine';
|
|
@@ -549,6 +603,8 @@ const PORTABLE_TERMS = new Set([
|
|
|
549
603
|
const LINGER_LINE = `sudo loginctl enable-linger ${os.userInfo().username}`;
|
|
550
604
|
const DEFAULT_URL = process.env.CRAFTSPACE_URL ?? 'https://craftspace.app';
|
|
551
605
|
const PROBE_EVERY_MS = 300_000;
|
|
606
|
+
const REBUILD_AFTER_MS = 21_600_000;
|
|
552
607
|
let probed = null;
|
|
608
|
+
let building = false;
|
|
553
609
|
const reports = new Map();
|
|
554
610
|
const running = new Set();
|
package/dist/probe.d.ts
CHANGED
package/dist/probe.js
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
import { execFile } from 'node:child_process';
|
|
2
1
|
import net from 'node:net';
|
|
3
|
-
import {
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
4
3
|
import os from 'node:os';
|
|
5
4
|
import path from 'node:path';
|
|
6
|
-
import { promisify } from 'node:util';
|
|
7
|
-
const run = promisify(execFile);
|
|
8
5
|
export const probe = {
|
|
9
6
|
async tools({ mcpPath = path.join(os.homedir(), '.mcp.json') } = {}) {
|
|
10
|
-
|
|
11
|
-
brainWired(mcpPath),
|
|
12
|
-
version({ id: 'claude', command: 'claude' }),
|
|
13
|
-
version({ id: 'codex', command: 'codex' }),
|
|
14
|
-
gitAndGithub(),
|
|
15
|
-
chromeInstalled(),
|
|
16
|
-
sshListening(),
|
|
17
|
-
]);
|
|
18
|
-
return [brain, claude, codex, git, chrome, ssh];
|
|
7
|
+
return Promise.all([brainWired(mcpPath), sshListening()]);
|
|
19
8
|
},
|
|
20
9
|
};
|
|
21
10
|
async function brainWired(mcpPath) {
|
|
@@ -31,43 +20,6 @@ async function brainWired(mcpPath) {
|
|
|
31
20
|
detail: wired ? short(mcpPath) : `${short(mcpPath)} has no craftspace entry`,
|
|
32
21
|
};
|
|
33
22
|
}
|
|
34
|
-
async function version({ id, command }) {
|
|
35
|
-
const found = await firstLine(command, ['--version']);
|
|
36
|
-
if (found.ran)
|
|
37
|
-
return { id, ok: true, detail: found.line };
|
|
38
|
-
return { id, ok: false, detail: found.why === 'absent' ? 'not installed' : 'installed, but it will not report a version' };
|
|
39
|
-
}
|
|
40
|
-
async function gitAndGithub() {
|
|
41
|
-
const git = await firstLine('git', ['--version']);
|
|
42
|
-
if (!git.ran)
|
|
43
|
-
return { id: 'git', ok: false, detail: 'git is not installed' };
|
|
44
|
-
const gh = await firstLine('gh', ['--version']);
|
|
45
|
-
if (!gh.ran)
|
|
46
|
-
return { id: 'git', ok: false, detail: `${git.line} · the GitHub CLI is not installed yet` };
|
|
47
|
-
const account = await githubAccount();
|
|
48
|
-
return {
|
|
49
|
-
id: 'git',
|
|
50
|
-
ok: account !== null,
|
|
51
|
-
detail: account === null
|
|
52
|
-
? `${git.line} · ${gh.line}, run gh auth login on this machine`
|
|
53
|
-
: `${git.line} · ${gh.line}, signed in as ${account}`,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
async function githubAccount() {
|
|
57
|
-
const raw = await readFile(path.join(os.homedir(), '.config', 'gh', 'hosts.yml'), 'utf8').catch(() => null);
|
|
58
|
-
return raw === null ? null : githubUser(raw);
|
|
59
|
-
}
|
|
60
|
-
export function githubUser(hostsYml) {
|
|
61
|
-
return /^ {4}user: *(\S+) *$/m.exec(hostsYml)?.[1] ?? null;
|
|
62
|
-
}
|
|
63
|
-
async function chromeInstalled() {
|
|
64
|
-
for (const command of [...MAC_CHROME, ...LINUX_CHROME]) {
|
|
65
|
-
const found = await firstLine(command, ['--version']);
|
|
66
|
-
if (found.ran)
|
|
67
|
-
return { id: 'chrome', ok: true, detail: found.line };
|
|
68
|
-
}
|
|
69
|
-
return { id: 'chrome', ok: false, detail: 'not installed yet, so browser runs will fail' };
|
|
70
|
-
}
|
|
71
23
|
function sshListening() {
|
|
72
24
|
return new Promise((resolve) => {
|
|
73
25
|
const socket = net.connect({ host: '127.0.0.1', port: 22 });
|
|
@@ -78,25 +30,10 @@ function sshListening() {
|
|
|
78
30
|
socket.setTimeout(SSH_PROBE_TIMEOUT_MS);
|
|
79
31
|
socket.on('connect', () => answer(true, 'sshd is listening on port 22'));
|
|
80
32
|
socket.on('timeout', () => answer(false, 'nothing answered on port 22'));
|
|
81
|
-
socket.on('error', () => answer(false, 'sshd is not running, so nobody can
|
|
33
|
+
socket.on('error', () => answer(false, 'sshd is not running, so nobody can open a terminal here'));
|
|
82
34
|
});
|
|
83
35
|
}
|
|
84
|
-
async function firstLine(command, args) {
|
|
85
|
-
if (command.includes('/')) {
|
|
86
|
-
const reachable = await access(command).then(() => true, () => false);
|
|
87
|
-
if (!reachable)
|
|
88
|
-
return { ran: false, why: 'absent' };
|
|
89
|
-
}
|
|
90
|
-
const answer = await run(command, args, { timeout: PROBE_TIMEOUT_MS }).catch((thrown) => thrown);
|
|
91
|
-
if (answer instanceof Error)
|
|
92
|
-
return { ran: false, why: answer.code === 'ENOENT' ? 'absent' : 'broken' };
|
|
93
|
-
const line = answer.stdout.split('\n')[0]?.trim() ?? '';
|
|
94
|
-
return line === '' ? { ran: false, why: 'broken' } : { ran: true, line: line.slice(0, 200) };
|
|
95
|
-
}
|
|
96
36
|
function short(target) {
|
|
97
37
|
return target.startsWith(os.homedir()) ? `~${target.slice(os.homedir().length)}` : target;
|
|
98
38
|
}
|
|
99
|
-
const MAC_CHROME = ['/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'];
|
|
100
|
-
const LINUX_CHROME = ['google-chrome', 'google-chrome-stable', 'chromium'];
|
|
101
|
-
const PROBE_TIMEOUT_MS = 5_000;
|
|
102
39
|
const SSH_PROBE_TIMEOUT_MS = 1_500;
|
package/dist/setup.d.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type WorkstationStep, type WorkstationStepResult } from '@craftspace/shared';
|
|
2
2
|
export declare const setup: {
|
|
3
3
|
widenPath(): void;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
rewire?: () => Promise<void>;
|
|
4
|
+
runSteps({ steps, say, }: {
|
|
5
|
+
steps: WorkstationStep[];
|
|
7
6
|
say?: (line: string) => void;
|
|
8
|
-
}): Promise<
|
|
7
|
+
}): Promise<WorkstationStepResult[]>;
|
|
9
8
|
};
|
package/dist/setup.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import { probe } from './probe.js';
|
|
4
|
+
import { WORKSTATION_STEP_DETAIL_MAX_CHARS, } from '@craftspace/shared';
|
|
6
5
|
import { ui } from './ui.js';
|
|
7
|
-
const run = promisify(execFile);
|
|
8
6
|
export const setup = {
|
|
9
7
|
widenPath() {
|
|
10
8
|
const current = (process.env.PATH ?? '').split(path.delimiter).filter((entry) => entry !== '');
|
|
@@ -12,44 +10,39 @@ export const setup = {
|
|
|
12
10
|
if (missing.length > 0)
|
|
13
11
|
process.env.PATH = [...current, ...missing].join(path.delimiter);
|
|
14
12
|
},
|
|
15
|
-
async
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
let ready = 0;
|
|
24
|
-
for (const [index, tool] of broken.entries()) {
|
|
25
|
-
const repair = repairs[tool.id];
|
|
26
|
-
if (repair === undefined)
|
|
27
|
-
continue;
|
|
28
|
-
say?.(ui.progress({ name: repair.name, done: index, total: broken.length }));
|
|
29
|
-
tried.set(tool.id, Date.now());
|
|
30
|
-
const failed = await repair.fix().then(() => null, (error) => firstLine(error.message));
|
|
31
|
-
if (failed === null)
|
|
32
|
-
reasons.delete(tool.id);
|
|
33
|
-
else
|
|
34
|
-
reasons.set(tool.id, failed);
|
|
35
|
-
if (failed === null)
|
|
36
|
-
ready += 1;
|
|
37
|
-
say?.(`${ui.clear()}${failed === null ? ui.ok(repair.name) : ui.bad(repair.name, failed)}`);
|
|
13
|
+
async runSteps({ steps, say, }) {
|
|
14
|
+
const results = [];
|
|
15
|
+
for (const [index, step] of steps.entries()) {
|
|
16
|
+
say?.(ui.progress({ name: step.name, done: index, total: steps.length }));
|
|
17
|
+
const startedAt = Date.now();
|
|
18
|
+
const outcome = await runScript(step.script);
|
|
19
|
+
results.push({ ...outcome, id: step.id, name: step.name, ms: Date.now() - startedAt });
|
|
20
|
+
say?.(`${ui.clear()}${outcome.ok ? ui.ok(step.name) : ui.bad(step.name, outcome.detail)}`);
|
|
38
21
|
}
|
|
39
|
-
|
|
40
|
-
|
|
22
|
+
if (results.length > 0) {
|
|
23
|
+
say?.(ui.tally({ done: results.filter((result) => result.ok).length, total: results.length, noun: 'ready' }));
|
|
24
|
+
}
|
|
25
|
+
return results;
|
|
41
26
|
},
|
|
42
27
|
};
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return reason === undefined ? tool : { ...tool, detail: `${tool.detail} · ${reason}`.slice(0, 200) };
|
|
28
|
+
function runScript(script) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
execFile('/bin/sh', ['-e', '-c', script], { timeout: STEP_TIMEOUT_MS, maxBuffer: SCRIPT_BUFFER_BYTES }, (error, stdout, stderr) => {
|
|
31
|
+
if (error === null)
|
|
32
|
+
return resolve({ ok: true, detail: lastLines(stdout) });
|
|
33
|
+
resolve({ ok: false, detail: lastLines(`${stderr}\n${stdout}`) || error.message.split('\n')[0] || 'it failed' });
|
|
34
|
+
});
|
|
51
35
|
});
|
|
52
36
|
}
|
|
37
|
+
function lastLines(output) {
|
|
38
|
+
return output
|
|
39
|
+
.split('\n')
|
|
40
|
+
.map((line) => line.trim())
|
|
41
|
+
.filter((line) => line !== '')
|
|
42
|
+
.slice(-KEPT_LINES)
|
|
43
|
+
.join(' · ')
|
|
44
|
+
.slice(0, WORKSTATION_STEP_DETAIL_MAX_CHARS);
|
|
45
|
+
}
|
|
53
46
|
function toolDirs() {
|
|
54
47
|
const prefix = process.env.npm_config_prefix;
|
|
55
48
|
return [
|
|
@@ -61,48 +54,6 @@ function toolDirs() {
|
|
|
61
54
|
'/usr/local/bin',
|
|
62
55
|
];
|
|
63
56
|
}
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
git: {
|
|
68
|
-
name: 'the GitHub CLI',
|
|
69
|
-
fix: async () => {
|
|
70
|
-
if ((await firstAvailable(['gh'])) !== null)
|
|
71
|
-
return;
|
|
72
|
-
await installPackage({ brew: ['gh'], apt: ['gh'], dnf: ['gh'] });
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
chrome: {
|
|
76
|
-
name: 'Google Chrome',
|
|
77
|
-
fix: () => installPackage({ brew: ['--cask', 'google-chrome'], apt: ['chromium'], dnf: ['chromium'] }),
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
async function npmGlobal(name) {
|
|
81
|
-
await run('npm', ['install', '-g', name], { timeout: INSTALL_TIMEOUT_MS });
|
|
82
|
-
}
|
|
83
|
-
async function installPackage({ brew, apt, dnf }) {
|
|
84
|
-
if (process.platform === 'darwin') {
|
|
85
|
-
await run('brew', ['install', ...brew], { timeout: INSTALL_TIMEOUT_MS });
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
const manager = await firstAvailable(['apt-get', 'dnf']);
|
|
89
|
-
if (manager === null)
|
|
90
|
-
throw new Error('no package manager this CLI knows how to drive');
|
|
91
|
-
const packages = manager === 'apt-get' ? apt : dnf;
|
|
92
|
-
await run('sudo', ['-n', manager, 'install', '-y', ...packages], { timeout: INSTALL_TIMEOUT_MS });
|
|
93
|
-
}
|
|
94
|
-
async function firstAvailable(commands) {
|
|
95
|
-
for (const command of commands) {
|
|
96
|
-
const there = await run('/bin/sh', ['-c', `command -v ${command}`]).then(() => true, () => false);
|
|
97
|
-
if (there)
|
|
98
|
-
return command;
|
|
99
|
-
}
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
function firstLine(message) {
|
|
103
|
-
return message.split('\n')[0] ?? 'it failed';
|
|
104
|
-
}
|
|
105
|
-
const tried = new Map();
|
|
106
|
-
const reasons = new Map();
|
|
107
|
-
const INSTALL_TIMEOUT_MS = 180_000;
|
|
108
|
-
const RETRY_AFTER_MS = 21_600_000;
|
|
57
|
+
const STEP_TIMEOUT_MS = 600_000;
|
|
58
|
+
const SCRIPT_BUFFER_BYTES = 4 * 1024 * 1024;
|
|
59
|
+
const KEPT_LINES = 3;
|