@craftspace/cli 0.2.0 → 0.2.2
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 +127 -33
- package/dist/machine.d.ts +1 -0
- package/dist/machine.js +50 -6
- package/dist/run.js +22 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18187,6 +18187,22 @@ var TREE_KINDS = [
|
|
|
18187
18187
|
PageKind.Artifact,
|
|
18188
18188
|
PageKind.Table
|
|
18189
18189
|
];
|
|
18190
|
+
var PageLayer;
|
|
18191
|
+
(function(PageLayer2) {
|
|
18192
|
+
PageLayer2["Record"] = "record";
|
|
18193
|
+
PageLayer2["Knowledge"] = "knowledge";
|
|
18194
|
+
PageLayer2["Skill"] = "skill";
|
|
18195
|
+
})(PageLayer || (PageLayer = {}));
|
|
18196
|
+
var KIND_LAYER = {
|
|
18197
|
+
[PageKind.Page]: PageLayer.Knowledge,
|
|
18198
|
+
[PageKind.Decision]: PageLayer.Knowledge,
|
|
18199
|
+
[PageKind.Skill]: PageLayer.Skill,
|
|
18200
|
+
[PageKind.Meeting]: PageLayer.Record,
|
|
18201
|
+
[PageKind.Thread]: PageLayer.Record,
|
|
18202
|
+
[PageKind.Customer]: PageLayer.Record,
|
|
18203
|
+
[PageKind.Artifact]: PageLayer.Record,
|
|
18204
|
+
[PageKind.Table]: PageLayer.Record
|
|
18205
|
+
};
|
|
18190
18206
|
var AUTHORABLE_KINDS = [PageKind.Page, PageKind.Decision, PageKind.Customer, PageKind.Skill];
|
|
18191
18207
|
var PAGE_COLORS = [
|
|
18192
18208
|
"slate",
|
|
@@ -18937,6 +18953,10 @@ var AUDIT_GROUPS = [
|
|
|
18937
18953
|
AuditAction.WorkflowSecretRotated
|
|
18938
18954
|
]
|
|
18939
18955
|
},
|
|
18956
|
+
{
|
|
18957
|
+
label: "MCP calls",
|
|
18958
|
+
actions: [AuditAction.McpToolCall, AuditAction.McpRequest]
|
|
18959
|
+
},
|
|
18940
18960
|
{
|
|
18941
18961
|
label: "Organization",
|
|
18942
18962
|
actions: [
|
|
@@ -18948,6 +18968,7 @@ var AUDIT_GROUPS = [
|
|
|
18948
18968
|
}
|
|
18949
18969
|
];
|
|
18950
18970
|
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);
|
|
18951
18972
|
var AUDIT_VERBS = {
|
|
18952
18973
|
[AuditAction.AccessDenied]: "Was refused",
|
|
18953
18974
|
[AuditAction.MemberSignedIn]: "Signed in",
|
|
@@ -19016,6 +19037,7 @@ var AuditEntrySchema = external_exports.object({
|
|
|
19016
19037
|
target: AuditTargetSchema.optional(),
|
|
19017
19038
|
data: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
19018
19039
|
ip: external_exports.string().optional(),
|
|
19040
|
+
userAgent: external_exports.string().optional(),
|
|
19019
19041
|
createdAt: external_exports.string()
|
|
19020
19042
|
});
|
|
19021
19043
|
var AuditSummarySchema = external_exports.object({
|
|
@@ -19258,6 +19280,12 @@ var CreateConnectionBodySchema = external_exports.object({
|
|
|
19258
19280
|
// sent it, and can start a connect for that one kit and nothing else.
|
|
19259
19281
|
inviteToken: external_exports.string().optional()
|
|
19260
19282
|
});
|
|
19283
|
+
var ReplaceConnectionSecretBodySchema = external_exports.object({
|
|
19284
|
+
mcpUrl: external_exports.url().optional(),
|
|
19285
|
+
authToken: external_exports.string().min(1).optional(),
|
|
19286
|
+
dsn: external_exports.string().min(1).optional(),
|
|
19287
|
+
apiKey: external_exports.string().min(1).optional()
|
|
19288
|
+
});
|
|
19261
19289
|
var ConnectionIdParamsSchema = external_exports.object({ id: external_exports.string() });
|
|
19262
19290
|
var PendingOAuthSchema = external_exports.object({
|
|
19263
19291
|
id: external_exports.string(),
|
|
@@ -19754,7 +19782,7 @@ var SaveWorkstationBodySchema = external_exports.object({
|
|
|
19754
19782
|
var WorkstationResponseSchema = external_exports.object({ workstation: WorkstationSchema });
|
|
19755
19783
|
|
|
19756
19784
|
// ../../shared/dist/machines/machine.js
|
|
19757
|
-
var CLI_VERSION = "0.2.
|
|
19785
|
+
var CLI_VERSION = "0.2.2";
|
|
19758
19786
|
var MACHINE_BEAT_INTERVAL_MS = 15e3;
|
|
19759
19787
|
var MACHINE_BEAT_BUSY_INTERVAL_MS = 3e3;
|
|
19760
19788
|
var MACHINE_RUN_OUTPUT_MAX = 32e3;
|
|
@@ -19874,6 +19902,7 @@ var AddMachineKeyBodySchema = external_exports.object({
|
|
|
19874
19902
|
key: external_exports.string().min(60).max(4e3),
|
|
19875
19903
|
label: external_exports.string().max(80).optional()
|
|
19876
19904
|
});
|
|
19905
|
+
var RenameMachineBodySchema = external_exports.object({ name: external_exports.string().min(1).max(64) });
|
|
19877
19906
|
var StartRunBodySchema = external_exports.object({ argv: external_exports.array(external_exports.string().max(4e3)).min(1).max(64) });
|
|
19878
19907
|
var AnswerRunBodySchema = external_exports.object({ text: external_exports.string().min(1).max(4e3) });
|
|
19879
19908
|
var MachineRunsResponseSchema = external_exports.object({ runs: external_exports.array(MachineRunSchema) });
|
|
@@ -20221,7 +20250,7 @@ var MemberActivityListResponse = external_exports.object({
|
|
|
20221
20250
|
// dist/machine.js
|
|
20222
20251
|
import { execFile as execFile3 } from "node:child_process";
|
|
20223
20252
|
import { mkdir, readFile as readFile2, readdir, rm, writeFile } from "node:fs/promises";
|
|
20224
|
-
import
|
|
20253
|
+
import os4 from "node:os";
|
|
20225
20254
|
import path4 from "node:path";
|
|
20226
20255
|
import { promisify as promisify3 } from "node:util";
|
|
20227
20256
|
|
|
@@ -22091,6 +22120,7 @@ var dist_default6 = createPrompt((config2, done) => {
|
|
|
22091
22120
|
|
|
22092
22121
|
// dist/run.js
|
|
22093
22122
|
import { spawn } from "node:child_process";
|
|
22123
|
+
import os from "node:os";
|
|
22094
22124
|
var runner = {
|
|
22095
22125
|
attended(argv) {
|
|
22096
22126
|
return new Promise((resolve) => {
|
|
@@ -22101,6 +22131,7 @@ var runner = {
|
|
|
22101
22131
|
},
|
|
22102
22132
|
async unattended({ work, onProgress }) {
|
|
22103
22133
|
const argv = headlessArgv(work);
|
|
22134
|
+
const raw = argv[0] === "script";
|
|
22104
22135
|
let output = "";
|
|
22105
22136
|
let agentSessionId = work.agentSessionId ?? void 0;
|
|
22106
22137
|
let lastText = "";
|
|
@@ -22126,10 +22157,24 @@ var runner = {
|
|
|
22126
22157
|
}
|
|
22127
22158
|
};
|
|
22128
22159
|
const code = await new Promise((resolve) => {
|
|
22129
|
-
const child = spawn(argv[0], argv.slice(1), {
|
|
22160
|
+
const child = spawn(argv[0], argv.slice(1), {
|
|
22161
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
22162
|
+
cwd: os.homedir(),
|
|
22163
|
+
env: {
|
|
22164
|
+
...process.env,
|
|
22165
|
+
TERM: process.env.TERM ?? "xterm-256color",
|
|
22166
|
+
COLUMNS: process.env.COLUMNS ?? "120",
|
|
22167
|
+
LINES: process.env.LINES ?? "40"
|
|
22168
|
+
}
|
|
22169
|
+
});
|
|
22170
|
+
append("");
|
|
22130
22171
|
child.stdout.setEncoding("utf8");
|
|
22131
22172
|
child.stderr.setEncoding("utf8");
|
|
22132
22173
|
child.stdout.on("data", (chunk) => {
|
|
22174
|
+
if (raw) {
|
|
22175
|
+
append(chunk);
|
|
22176
|
+
return;
|
|
22177
|
+
}
|
|
22133
22178
|
pending += chunk;
|
|
22134
22179
|
const lines = pending.split("\n");
|
|
22135
22180
|
pending = lines.pop() ?? "";
|
|
@@ -22179,8 +22224,11 @@ function headlessArgv(work) {
|
|
|
22179
22224
|
return [argv[0], "-p", argv.slice(1).join(" "), ...streaming];
|
|
22180
22225
|
}
|
|
22181
22226
|
function underTty(argv) {
|
|
22182
|
-
|
|
22227
|
+
const line = `stty rows ${PTY_ROWS} cols ${PTY_COLS} 2>/dev/null; exec ${argv.join(" ")}`;
|
|
22228
|
+
return process.platform === "linux" ? ["script", "-qec", line, "/dev/null"] : ["script", "-q", "/dev/null", "sh", "-c", line];
|
|
22183
22229
|
}
|
|
22230
|
+
var PTY_ROWS = 40;
|
|
22231
|
+
var PTY_COLS = 120;
|
|
22184
22232
|
function isAgent(command) {
|
|
22185
22233
|
return AGENTS.has((command ?? "").split("/").pop() ?? "");
|
|
22186
22234
|
}
|
|
@@ -22209,7 +22257,7 @@ function parseJson(line) {
|
|
|
22209
22257
|
|
|
22210
22258
|
// dist/setup.js
|
|
22211
22259
|
import { execFile as execFile2 } from "node:child_process";
|
|
22212
|
-
import
|
|
22260
|
+
import os3 from "node:os";
|
|
22213
22261
|
import path3 from "node:path";
|
|
22214
22262
|
import { promisify as promisify2 } from "node:util";
|
|
22215
22263
|
|
|
@@ -22217,12 +22265,12 @@ import { promisify as promisify2 } from "node:util";
|
|
|
22217
22265
|
import { execFile } from "node:child_process";
|
|
22218
22266
|
import net from "node:net";
|
|
22219
22267
|
import { access, readFile } from "node:fs/promises";
|
|
22220
|
-
import
|
|
22268
|
+
import os2 from "node:os";
|
|
22221
22269
|
import path2 from "node:path";
|
|
22222
22270
|
import { promisify } from "node:util";
|
|
22223
22271
|
var run = promisify(execFile);
|
|
22224
22272
|
var probe = {
|
|
22225
|
-
async tools({ mcpPath = path2.join(
|
|
22273
|
+
async tools({ mcpPath = path2.join(os2.homedir(), ".mcp.json") } = {}) {
|
|
22226
22274
|
const [brain, claude, codex, git, chrome, ssh] = await Promise.all([
|
|
22227
22275
|
brainWired(mcpPath),
|
|
22228
22276
|
version2({ id: "claude", command: "claude" }),
|
|
@@ -22268,7 +22316,7 @@ async function gitAndGithub() {
|
|
|
22268
22316
|
};
|
|
22269
22317
|
}
|
|
22270
22318
|
async function githubAccount() {
|
|
22271
|
-
const raw = await readFile(path2.join(
|
|
22319
|
+
const raw = await readFile(path2.join(os2.homedir(), ".config", "gh", "hosts.yml"), "utf8").catch(() => null);
|
|
22272
22320
|
return raw === null ? null : githubUser(raw);
|
|
22273
22321
|
}
|
|
22274
22322
|
function githubUser(hostsYml) {
|
|
@@ -22308,7 +22356,7 @@ async function firstLine(command, args) {
|
|
|
22308
22356
|
return line === "" ? { ran: false, why: "broken" } : { ran: true, line: line.slice(0, 200) };
|
|
22309
22357
|
}
|
|
22310
22358
|
function short(target) {
|
|
22311
|
-
return target.startsWith(
|
|
22359
|
+
return target.startsWith(os2.homedir()) ? `~${target.slice(os2.homedir().length)}` : target;
|
|
22312
22360
|
}
|
|
22313
22361
|
var MAC_CHROME = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"];
|
|
22314
22362
|
var LINUX_CHROME = ["google-chrome", "google-chrome-stable", "chromium"];
|
|
@@ -22363,8 +22411,8 @@ function toolDirs() {
|
|
|
22363
22411
|
return [
|
|
22364
22412
|
path3.dirname(process.execPath),
|
|
22365
22413
|
...prefix === void 0 ? [] : [path3.join(prefix, "bin")],
|
|
22366
|
-
path3.join(
|
|
22367
|
-
path3.join(
|
|
22414
|
+
path3.join(os3.homedir(), ".local", "bin"),
|
|
22415
|
+
path3.join(os3.homedir(), ".npm-global", "bin"),
|
|
22368
22416
|
"/opt/homebrew/bin",
|
|
22369
22417
|
"/usr/local/bin"
|
|
22370
22418
|
];
|
|
@@ -22419,7 +22467,7 @@ var RETRY_AFTER_MS = 216e5;
|
|
|
22419
22467
|
var run3 = promisify3(execFile3);
|
|
22420
22468
|
var machine = {
|
|
22421
22469
|
home() {
|
|
22422
|
-
return process.env.CRAFTSPACE_CLI_HOME ?? path4.join(
|
|
22470
|
+
return process.env.CRAFTSPACE_CLI_HOME ?? path4.join(os4.homedir(), ".craftspace");
|
|
22423
22471
|
},
|
|
22424
22472
|
async login({ token, url: url2, install }) {
|
|
22425
22473
|
const answer = await call({
|
|
@@ -22428,11 +22476,11 @@ var machine = {
|
|
|
22428
22476
|
method: "POST",
|
|
22429
22477
|
path: "/api/machines/login",
|
|
22430
22478
|
body: {
|
|
22431
|
-
name:
|
|
22479
|
+
name: os4.hostname().split(".")[0] ?? "machine",
|
|
22432
22480
|
platform: `${process.platform}-${process.arch}`,
|
|
22433
|
-
sshUser:
|
|
22434
|
-
cores:
|
|
22435
|
-
memoryGb: Math.max(1, Math.round(
|
|
22481
|
+
sshUser: os4.userInfo().username,
|
|
22482
|
+
cores: os4.cpus().length,
|
|
22483
|
+
memoryGb: Math.max(1, Math.round(os4.totalmem() / 1024 ** 3)),
|
|
22436
22484
|
cli: CLI_VERSION,
|
|
22437
22485
|
publicKey: await ensureKey()
|
|
22438
22486
|
},
|
|
@@ -22445,8 +22493,7 @@ var machine = {
|
|
|
22445
22493
|
await installService();
|
|
22446
22494
|
probed = {
|
|
22447
22495
|
at: Date.now(),
|
|
22448
|
-
tools: await setup.run({ install, rewire, say: (
|
|
22449
|
-
`) })
|
|
22496
|
+
tools: await setup.run({ install, rewire, say: (text) => process.stdout.write(text) })
|
|
22450
22497
|
};
|
|
22451
22498
|
return answer.machine;
|
|
22452
22499
|
},
|
|
@@ -22495,7 +22542,7 @@ Signed in as ${signed.ownerName}. This machine is ${signed.name}.
|
|
|
22495
22542
|
sessions: await readSessions(),
|
|
22496
22543
|
runs: drainReports(),
|
|
22497
22544
|
tools: await tools(),
|
|
22498
|
-
sshUser:
|
|
22545
|
+
sshUser: os4.userInfo().username
|
|
22499
22546
|
},
|
|
22500
22547
|
schema: MachineBeatResponseSchema
|
|
22501
22548
|
});
|
|
@@ -22528,6 +22575,17 @@ Signed in as ${signed.ownerName}. This machine is ${signed.name}.
|
|
|
22528
22575
|
return runHere(argv);
|
|
22529
22576
|
return runThere({ argv, on });
|
|
22530
22577
|
},
|
|
22578
|
+
async daemonLine() {
|
|
22579
|
+
if (process.env.CRAFTSPACE_NO_SERVICE === "1")
|
|
22580
|
+
return `not installed. Start it with: ${here()} daemon`;
|
|
22581
|
+
if (process.platform !== "linux")
|
|
22582
|
+
return BEATING;
|
|
22583
|
+
if (!await serviceRunning())
|
|
22584
|
+
return `not running. Start it with: ${here()} daemon`;
|
|
22585
|
+
if (!await userLingers())
|
|
22586
|
+
return `beating, but it stops when you log out. Run: ${LINGER_LINE}`;
|
|
22587
|
+
return BEATING;
|
|
22588
|
+
},
|
|
22531
22589
|
async logout() {
|
|
22532
22590
|
const config2 = await readConfig();
|
|
22533
22591
|
const token = await readToken();
|
|
@@ -22736,7 +22794,7 @@ async function ensureKey() {
|
|
|
22736
22794
|
if (existing !== null)
|
|
22737
22795
|
return existing.trim();
|
|
22738
22796
|
await mkdir(machine.home(), { recursive: true, mode: 448 });
|
|
22739
|
-
await run3("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${
|
|
22797
|
+
await run3("ssh-keygen", ["-t", "ed25519", "-N", "", "-q", "-f", priv, "-C", `craftspace-${os4.hostname()}`]);
|
|
22740
22798
|
return (await readFile2(pub, "utf8")).trim();
|
|
22741
22799
|
}
|
|
22742
22800
|
async function readConfig() {
|
|
@@ -22764,7 +22822,7 @@ async function readSessions() {
|
|
|
22764
22822
|
return sessions.slice(0, MAX_REPORTED_SESSIONS);
|
|
22765
22823
|
}
|
|
22766
22824
|
async function writeAuthorizedKeys(keys) {
|
|
22767
|
-
const target = path4.join(
|
|
22825
|
+
const target = path4.join(os4.homedir(), ".ssh", "authorized_keys");
|
|
22768
22826
|
const current = await readFile2(target, "utf8").catch(() => "");
|
|
22769
22827
|
const before = current.split(KEYS_BEGIN)[0] ?? "";
|
|
22770
22828
|
const after = current.includes(KEYS_END) ? current.split(KEYS_END)[1] ?? "" : "";
|
|
@@ -22804,6 +22862,31 @@ async function writeAgentConfigs({ url: url2, token, writes }) {
|
|
|
22804
22862
|
owned.push(target);
|
|
22805
22863
|
}
|
|
22806
22864
|
await writeFile(ownedPath(), JSON.stringify({ paths: owned }, null, 2));
|
|
22865
|
+
await acceptClaudeGates(serversIn(planned));
|
|
22866
|
+
}
|
|
22867
|
+
function serversIn(writes) {
|
|
22868
|
+
return writes.flatMap((write) => isPlainObject3(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
22869
|
+
}
|
|
22870
|
+
async function acceptClaudeGates(servers) {
|
|
22871
|
+
const target = path4.join(os4.homedir(), ".claude.json");
|
|
22872
|
+
const raw = await readFile2(target, "utf8").catch(() => null);
|
|
22873
|
+
if (raw === null)
|
|
22874
|
+
return;
|
|
22875
|
+
const parsed = JSON.parse(raw);
|
|
22876
|
+
const projects = isPlainObject3(parsed.projects) ? parsed.projects : {};
|
|
22877
|
+
const found = projects[os4.homedir()];
|
|
22878
|
+
const here2 = isPlainObject3(found) ? { ...found } : {};
|
|
22879
|
+
const enabled = /* @__PURE__ */ new Set([...Array.isArray(here2.enabledMcpjsonServers) ? here2.enabledMcpjsonServers : [], ...servers]);
|
|
22880
|
+
if (here2.hasTrustDialogAccepted === true && enabled.size === asArray(here2.enabledMcpjsonServers).length)
|
|
22881
|
+
return;
|
|
22882
|
+
here2.hasTrustDialogAccepted = true;
|
|
22883
|
+
here2.enabledMcpjsonServers = [...enabled];
|
|
22884
|
+
const next = { ...parsed, projects: { ...projects, [os4.homedir()]: here2 } };
|
|
22885
|
+
await writeFile(target, `${JSON.stringify(next, null, 2)}
|
|
22886
|
+
`, { mode: 384 });
|
|
22887
|
+
}
|
|
22888
|
+
function asArray(value) {
|
|
22889
|
+
return Array.isArray(value) ? value : [];
|
|
22807
22890
|
}
|
|
22808
22891
|
function mergeInto(current, incoming) {
|
|
22809
22892
|
const merged = { ...current };
|
|
@@ -22846,20 +22929,19 @@ async function weCreated(target) {
|
|
|
22846
22929
|
return backup === null;
|
|
22847
22930
|
}
|
|
22848
22931
|
async function installService() {
|
|
22849
|
-
if (process.env.CRAFTSPACE_NO_SERVICE === "1")
|
|
22850
|
-
process.stdout.write("Skipping the service. Run the beat yourself with: craftspace daemon\n");
|
|
22932
|
+
if (process.env.CRAFTSPACE_NO_SERVICE === "1")
|
|
22851
22933
|
return;
|
|
22852
|
-
}
|
|
22853
22934
|
if (process.platform === "linux") {
|
|
22854
|
-
const dir = path4.join(
|
|
22935
|
+
const dir = path4.join(os4.homedir(), ".config", "systemd", "user");
|
|
22855
22936
|
await mkdir(dir, { recursive: true });
|
|
22856
22937
|
await writeFile(path4.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
22938
|
+
await run3("loginctl", ["enable-linger", os4.userInfo().username]).catch(() => void 0);
|
|
22857
22939
|
await run3("systemctl", ["--user", "daemon-reload"]).catch(() => void 0);
|
|
22858
22940
|
await run3("systemctl", ["--user", "enable", "--now", SERVICE_NAME]).catch(() => void 0);
|
|
22859
22941
|
return;
|
|
22860
22942
|
}
|
|
22861
22943
|
if (process.platform === "darwin") {
|
|
22862
|
-
const target = path4.join(
|
|
22944
|
+
const target = path4.join(os4.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
22863
22945
|
await mkdir(path4.dirname(target), { recursive: true });
|
|
22864
22946
|
await writeFile(target, launchdPlist());
|
|
22865
22947
|
await run3("launchctl", ["unload", target]).catch(() => void 0);
|
|
@@ -22869,11 +22951,11 @@ async function installService() {
|
|
|
22869
22951
|
async function uninstallService() {
|
|
22870
22952
|
if (process.platform === "linux") {
|
|
22871
22953
|
await run3("systemctl", ["--user", "disable", "--now", SERVICE_NAME]).catch(() => void 0);
|
|
22872
|
-
await rm(path4.join(
|
|
22954
|
+
await rm(path4.join(os4.homedir(), ".config", "systemd", "user", `${SERVICE_NAME}.service`), { force: true });
|
|
22873
22955
|
return;
|
|
22874
22956
|
}
|
|
22875
22957
|
if (process.platform === "darwin") {
|
|
22876
|
-
const target = path4.join(
|
|
22958
|
+
const target = path4.join(os4.homedir(), "Library", "LaunchAgents", `${LAUNCH_LABEL}.plist`);
|
|
22877
22959
|
await run3("launchctl", ["unload", target]).catch(() => void 0);
|
|
22878
22960
|
await rm(target, { force: true });
|
|
22879
22961
|
}
|
|
@@ -22907,11 +22989,21 @@ function launchdPlist() {
|
|
|
22907
22989
|
</plist>
|
|
22908
22990
|
`;
|
|
22909
22991
|
}
|
|
22992
|
+
function here() {
|
|
22993
|
+
return `${process.execPath} ${entryPath()}`;
|
|
22994
|
+
}
|
|
22995
|
+
async function serviceRunning() {
|
|
22996
|
+
return await run3("systemctl", ["--user", "is-active", SERVICE_NAME]).catch(() => null) !== null;
|
|
22997
|
+
}
|
|
22998
|
+
async function userLingers() {
|
|
22999
|
+
const shown = await run3("loginctl", ["show-user", os4.userInfo().username, "-p", "Linger"]).catch(() => null);
|
|
23000
|
+
return shown?.stdout.includes("Linger=yes") === true;
|
|
23001
|
+
}
|
|
22910
23002
|
function entryPath() {
|
|
22911
23003
|
return path4.join(path4.dirname(new URL(import.meta.url).pathname), "index.js");
|
|
22912
23004
|
}
|
|
22913
23005
|
function expand(target) {
|
|
22914
|
-
return target.startsWith("~/") ? path4.join(
|
|
23006
|
+
return target.startsWith("~/") ? path4.join(os4.homedir(), target.slice(2)) : target;
|
|
22915
23007
|
}
|
|
22916
23008
|
function configPath() {
|
|
22917
23009
|
return path4.join(machine.home(), "config.json");
|
|
@@ -22923,9 +23015,9 @@ function ownedPath() {
|
|
|
22923
23015
|
return path4.join(machine.home(), "owned.json");
|
|
22924
23016
|
}
|
|
22925
23017
|
function load2() {
|
|
22926
|
-
const cores = Math.max(1,
|
|
22927
|
-
const busy = Math.min(100, (
|
|
22928
|
-
const used = (
|
|
23018
|
+
const cores = Math.max(1, os4.cpus().length);
|
|
23019
|
+
const busy = Math.min(100, (os4.loadavg()[0] ?? 0) / cores * 100);
|
|
23020
|
+
const used = (os4.totalmem() - os4.freemem()) / os4.totalmem() * 100;
|
|
22929
23021
|
return { cpuPercent: Math.round(busy), memoryPercent: Math.round(used) };
|
|
22930
23022
|
}
|
|
22931
23023
|
function nextDelayMs(failures, busy = false) {
|
|
@@ -22949,6 +23041,8 @@ var MAX_BACKOFF_MS = 12e4;
|
|
|
22949
23041
|
var KEYS_BEGIN = "# craftspace begin";
|
|
22950
23042
|
var KEYS_END = "# craftspace end";
|
|
22951
23043
|
var POLL_INTERVAL_MS = 1e3;
|
|
23044
|
+
var BEATING = "beating every 15s, outbound only, and keeps this set up";
|
|
23045
|
+
var LINGER_LINE = `sudo loginctl enable-linger ${os4.userInfo().username}`;
|
|
22952
23046
|
var DEFAULT_URL = process.env.CRAFTSPACE_URL ?? "https://craftspace.app";
|
|
22953
23047
|
var PROBE_EVERY_MS = 3e5;
|
|
22954
23048
|
var probed = null;
|
|
@@ -22966,7 +23060,7 @@ program2.command("login").description("sign this machine in").option("--token <t
|
|
|
22966
23060
|
`Signed in as ${signed.ownerName}`,
|
|
22967
23061
|
`Machine ${signed.name} \xB7 ${signed.platform} \xB7 ${signed.cores ?? "?"} vCPU`,
|
|
22968
23062
|
"MCP wired ~/.mcp.json",
|
|
22969
|
-
|
|
23063
|
+
`Daemon ${await machine.daemonLine()}`,
|
|
22970
23064
|
"",
|
|
22971
23065
|
`Listed at ${url2.replace(/\/$/, "")}/workstations`,
|
|
22972
23066
|
""
|
package/dist/machine.d.ts
CHANGED
package/dist/machine.js
CHANGED
|
@@ -37,7 +37,7 @@ export const machine = {
|
|
|
37
37
|
await installService();
|
|
38
38
|
probed = {
|
|
39
39
|
at: Date.now(),
|
|
40
|
-
tools: await setup.run({ install, rewire, say: (
|
|
40
|
+
tools: await setup.run({ install, rewire, say: (text) => process.stdout.write(text) }),
|
|
41
41
|
};
|
|
42
42
|
return answer.machine;
|
|
43
43
|
},
|
|
@@ -118,6 +118,17 @@ export const machine = {
|
|
|
118
118
|
return runHere(argv);
|
|
119
119
|
return runThere({ argv, on });
|
|
120
120
|
},
|
|
121
|
+
async daemonLine() {
|
|
122
|
+
if (process.env.CRAFTSPACE_NO_SERVICE === '1')
|
|
123
|
+
return `not installed. Start it with: ${here()} daemon`;
|
|
124
|
+
if (process.platform !== 'linux')
|
|
125
|
+
return BEATING;
|
|
126
|
+
if (!(await serviceRunning()))
|
|
127
|
+
return `not running. Start it with: ${here()} daemon`;
|
|
128
|
+
if (!(await userLingers()))
|
|
129
|
+
return `beating, but it stops when you log out. Run: ${LINGER_LINE}`;
|
|
130
|
+
return BEATING;
|
|
131
|
+
},
|
|
121
132
|
async logout() {
|
|
122
133
|
const config = await readConfig();
|
|
123
134
|
const token = await readToken();
|
|
@@ -348,8 +359,6 @@ async function readSessions() {
|
|
|
348
359
|
}
|
|
349
360
|
return sessions.slice(0, MAX_REPORTED_SESSIONS);
|
|
350
361
|
}
|
|
351
|
-
// Only the block between our markers is ours. Everything a person put in this file by hand is left exactly
|
|
352
|
-
// where it was, because the fastest way to lock a team out of its own box is to rewrite this file wholesale.
|
|
353
362
|
async function writeAuthorizedKeys(keys) {
|
|
354
363
|
const target = path.join(os.homedir(), '.ssh', 'authorized_keys');
|
|
355
364
|
const current = await readFile(target, 'utf8').catch(() => '');
|
|
@@ -387,6 +396,30 @@ async function writeAgentConfigs({ url, token, writes, }) {
|
|
|
387
396
|
owned.push(target);
|
|
388
397
|
}
|
|
389
398
|
await writeFile(ownedPath(), JSON.stringify({ paths: owned }, null, 2));
|
|
399
|
+
await acceptClaudeGates(serversIn(planned));
|
|
400
|
+
}
|
|
401
|
+
function serversIn(writes) {
|
|
402
|
+
return writes.flatMap((write) => isPlainObject(write.contents.mcpServers) ? Object.keys(write.contents.mcpServers) : []);
|
|
403
|
+
}
|
|
404
|
+
async function acceptClaudeGates(servers) {
|
|
405
|
+
const target = path.join(os.homedir(), '.claude.json');
|
|
406
|
+
const raw = await readFile(target, 'utf8').catch(() => null);
|
|
407
|
+
if (raw === null)
|
|
408
|
+
return;
|
|
409
|
+
const parsed = JSON.parse(raw);
|
|
410
|
+
const projects = isPlainObject(parsed.projects) ? parsed.projects : {};
|
|
411
|
+
const found = projects[os.homedir()];
|
|
412
|
+
const here = isPlainObject(found) ? { ...found } : {};
|
|
413
|
+
const enabled = new Set([...(Array.isArray(here.enabledMcpjsonServers) ? here.enabledMcpjsonServers : []), ...servers]);
|
|
414
|
+
if (here.hasTrustDialogAccepted === true && enabled.size === asArray(here.enabledMcpjsonServers).length)
|
|
415
|
+
return;
|
|
416
|
+
here.hasTrustDialogAccepted = true;
|
|
417
|
+
here.enabledMcpjsonServers = [...enabled];
|
|
418
|
+
const next = { ...parsed, projects: { ...projects, [os.homedir()]: here } };
|
|
419
|
+
await writeFile(target, `${JSON.stringify(next, null, 2)}\n`, { mode: 0o600 });
|
|
420
|
+
}
|
|
421
|
+
function asArray(value) {
|
|
422
|
+
return Array.isArray(value) ? value : [];
|
|
390
423
|
}
|
|
391
424
|
function mergeInto(current, incoming) {
|
|
392
425
|
const merged = { ...current };
|
|
@@ -429,14 +462,13 @@ async function weCreated(target) {
|
|
|
429
462
|
return backup === null;
|
|
430
463
|
}
|
|
431
464
|
async function installService() {
|
|
432
|
-
if (process.env.CRAFTSPACE_NO_SERVICE === '1')
|
|
433
|
-
process.stdout.write('Skipping the service. Run the beat yourself with: craftspace daemon\n');
|
|
465
|
+
if (process.env.CRAFTSPACE_NO_SERVICE === '1')
|
|
434
466
|
return;
|
|
435
|
-
}
|
|
436
467
|
if (process.platform === 'linux') {
|
|
437
468
|
const dir = path.join(os.homedir(), '.config', 'systemd', 'user');
|
|
438
469
|
await mkdir(dir, { recursive: true });
|
|
439
470
|
await writeFile(path.join(dir, `${SERVICE_NAME}.service`), systemdUnit());
|
|
471
|
+
await run('loginctl', ['enable-linger', os.userInfo().username]).catch(() => undefined);
|
|
440
472
|
await run('systemctl', ['--user', 'daemon-reload']).catch(() => undefined);
|
|
441
473
|
await run('systemctl', ['--user', 'enable', '--now', SERVICE_NAME]).catch(() => undefined);
|
|
442
474
|
return;
|
|
@@ -490,6 +522,16 @@ function launchdPlist() {
|
|
|
490
522
|
</plist>
|
|
491
523
|
`;
|
|
492
524
|
}
|
|
525
|
+
function here() {
|
|
526
|
+
return `${process.execPath} ${entryPath()}`;
|
|
527
|
+
}
|
|
528
|
+
async function serviceRunning() {
|
|
529
|
+
return (await run('systemctl', ['--user', 'is-active', SERVICE_NAME]).catch(() => null)) !== null;
|
|
530
|
+
}
|
|
531
|
+
async function userLingers() {
|
|
532
|
+
const shown = await run('loginctl', ['show-user', os.userInfo().username, '-p', 'Linger']).catch(() => null);
|
|
533
|
+
return shown?.stdout.includes('Linger=yes') === true;
|
|
534
|
+
}
|
|
493
535
|
function entryPath() {
|
|
494
536
|
return path.join(path.dirname(new URL(import.meta.url).pathname), 'index.js');
|
|
495
537
|
}
|
|
@@ -532,6 +574,8 @@ const MAX_BACKOFF_MS = 120_000;
|
|
|
532
574
|
const KEYS_BEGIN = '# craftspace begin';
|
|
533
575
|
const KEYS_END = '# craftspace end';
|
|
534
576
|
const POLL_INTERVAL_MS = 1_000;
|
|
577
|
+
const BEATING = 'beating every 15s, outbound only, and keeps this set up';
|
|
578
|
+
const LINGER_LINE = `sudo loginctl enable-linger ${os.userInfo().username}`;
|
|
535
579
|
const DEFAULT_URL = process.env.CRAFTSPACE_URL ?? 'https://craftspace.app';
|
|
536
580
|
const PROBE_EVERY_MS = 300_000;
|
|
537
581
|
let probed = null;
|
package/dist/run.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import { MACHINE_RUN_OUTPUT_MAX, spreadIfDefined } from '@craftspace/shared';
|
|
3
4
|
export const runner = {
|
|
4
5
|
attended(argv) {
|
|
@@ -10,6 +11,7 @@ export const runner = {
|
|
|
10
11
|
},
|
|
11
12
|
async unattended({ work, onProgress, }) {
|
|
12
13
|
const argv = headlessArgv(work);
|
|
14
|
+
const raw = argv[0] === 'script';
|
|
13
15
|
let output = '';
|
|
14
16
|
let agentSessionId = work.agentSessionId ?? undefined;
|
|
15
17
|
let lastText = '';
|
|
@@ -33,10 +35,24 @@ export const runner = {
|
|
|
33
35
|
}
|
|
34
36
|
};
|
|
35
37
|
const code = await new Promise((resolve) => {
|
|
36
|
-
const child = spawn(argv[0], argv.slice(1), {
|
|
38
|
+
const child = spawn(argv[0], argv.slice(1), {
|
|
39
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
40
|
+
cwd: os.homedir(),
|
|
41
|
+
env: {
|
|
42
|
+
...process.env,
|
|
43
|
+
TERM: process.env.TERM ?? 'xterm-256color',
|
|
44
|
+
COLUMNS: process.env.COLUMNS ?? '120',
|
|
45
|
+
LINES: process.env.LINES ?? '40',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
append('');
|
|
37
49
|
child.stdout.setEncoding('utf8');
|
|
38
50
|
child.stderr.setEncoding('utf8');
|
|
39
51
|
child.stdout.on('data', (chunk) => {
|
|
52
|
+
if (raw) {
|
|
53
|
+
append(chunk);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
40
56
|
pending += chunk;
|
|
41
57
|
const lines = pending.split('\n');
|
|
42
58
|
pending = lines.pop() ?? '';
|
|
@@ -85,10 +101,13 @@ export function headlessArgv(work) {
|
|
|
85
101
|
return [argv[0], '-p', argv.slice(1).join(' '), ...streaming];
|
|
86
102
|
}
|
|
87
103
|
function underTty(argv) {
|
|
104
|
+
const line = `stty rows ${PTY_ROWS} cols ${PTY_COLS} 2>/dev/null; exec ${argv.join(' ')}`;
|
|
88
105
|
return process.platform === 'linux'
|
|
89
|
-
? ['script', '-qec',
|
|
90
|
-
: ['script', '-q', '/dev/null',
|
|
106
|
+
? ['script', '-qec', line, '/dev/null']
|
|
107
|
+
: ['script', '-q', '/dev/null', 'sh', '-c', line];
|
|
91
108
|
}
|
|
109
|
+
const PTY_ROWS = 40;
|
|
110
|
+
const PTY_COLS = 120;
|
|
92
111
|
function isAgent(command) {
|
|
93
112
|
return AGENTS.has((command ?? '').split('/').pop() ?? '');
|
|
94
113
|
}
|