@clawops/cli 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +98 -12
- package/dist/apply-KYFPWI5U.js +27 -0
- package/dist/automation-RMAIBNVC.js +44 -0
- package/dist/bedrock-XVASJX2K.js +61 -0
- package/dist/{bootstrap-DMRPWJGN.js → bootstrap-HNGKY6WW.js} +11 -8
- package/dist/{chunk-N44K3LHE.js → chunk-2I7KCMXZ.js} +6 -4
- package/dist/{chunk-3WYA2QDM.js → chunk-2Z7J3ERQ.js} +1 -1
- package/dist/chunk-3OLUJPCB.js +36 -0
- package/dist/chunk-5XT5IAJK.js +69 -0
- package/dist/chunk-AEEMRFZV.js +24 -0
- package/dist/{chunk-YR3O2I3S.js → chunk-BSLIYB5Q.js} +6 -1
- package/dist/{chunk-AHAAEXWC.js → chunk-E4NJSJ54.js} +16 -93
- package/dist/chunk-FEK7VJAY.js +16 -0
- package/dist/{chunk-6TIYVHPX.js → chunk-GIEVAGEK.js} +7 -33
- package/dist/chunk-HSPHXG6W.js +67 -0
- package/dist/{chunk-GYV4KNW7.js → chunk-IVWP4QUJ.js} +133 -40
- package/dist/{chunk-AX74VXTV.js → chunk-KC3GC3PE.js} +58 -49
- package/dist/{chunk-UFRO4H5W.js → chunk-LBE6COC4.js} +56 -40
- package/dist/chunk-LEVIW2PV.js +890 -0
- package/dist/chunk-MC5ATCXS.js +108 -0
- package/dist/chunk-N4WZLWJ4.js +16 -0
- package/dist/{chunk-SBLQH2TJ.js → chunk-NUL5F6YV.js} +1 -1
- package/dist/chunk-RHOWYIWT.js +52 -0
- package/dist/{chunk-WHJMKDV7.js → chunk-STLRHLLV.js} +65 -12
- package/dist/chunk-V25QRZRB.js +45 -0
- package/dist/chunk-VOEJYRIK.js +33 -0
- package/dist/{providers-2OABPW2E.js → chunk-VUFMZEIM.js} +3 -2
- package/dist/chunk-W3OLNNJW.js +51 -0
- package/dist/cli-WLD3EBEM.js +11 -0
- package/dist/cli-auth-GD5SK7JU.js +13 -0
- package/dist/cli.js +518 -206
- package/dist/context-7EVC6W45.js +20 -0
- package/dist/docker-3PDO4TS7.js +12 -0
- package/dist/{firewall-T3NTPZJD.js → firewall-5VKZ4WBH.js} +22 -1
- package/dist/generate-I6YQ5V2T.js +29 -0
- package/dist/{harden-PCDK3UQ5.js → harden-72JMRT4E.js} +8 -5
- package/dist/index.d.ts +56 -0
- package/dist/known-hosts-file-I3Q3NBYQ.js +22 -0
- package/dist/{logs-P7YCKVF4.js → logs-5A7YYA53.js} +1 -1
- package/dist/{mcp-wire-BFEY2Y6H.js → mcp-wire-3SWBM7D6.js} +25 -15
- package/dist/models-AGTQWYDL.js +32 -0
- package/dist/network-args-PYJRJBYO.js +64 -0
- package/dist/{package-3HUW57XH.js → package-5BCMEBTE.js} +10 -3
- package/dist/passphrase-MJBE45N7.js +15 -0
- package/dist/{pool-EX4ZTA5P.js → pool-PIIKJ4L2.js} +3 -2
- package/dist/preflight-EF52S3WW.js +162 -0
- package/dist/preflight-MWWM5JT3.js +181 -0
- package/dist/preflight-TFD4N5ZU.js +141 -0
- package/dist/providers-FW6H6OPF.js +11 -0
- package/dist/ready-KDNRBNSU.js +95 -0
- package/dist/{remote-config-Z6IIZR3D.js → remote-config-3HZLUWEA.js} +3 -2
- package/dist/{runtime-LZ7NA53V.js → runtime-X3KW5TCP.js} +4 -2
- package/dist/server-GBA7BJGK.js +46 -0
- package/dist/ssh-VAI5DU5M.js +11 -0
- package/dist/ssh-key-DUO6OKOI.js +7 -0
- package/dist/stack-config-R4SPSUQZ.js +12 -0
- package/dist/{validate-BOQRWX4F.js → validate-YVI3BJ55.js} +1 -1
- package/dist/wait-WO72TRRQ.js +79 -0
- package/package.json +10 -3
- package/spec/mcp-tools.yaml +21 -2
- package/spec/models.yaml +14 -3
- package/spec/providers.schema.json +163 -28
- package/dist/apply-AM5HERE7.js +0 -19
- package/dist/automation-RULUSJBW.js +0 -30
- package/dist/aws-AMON7DA7.js +0 -265
- package/dist/azure-T4G6OBJQ.js +0 -273
- package/dist/chunk-CKSPROD5.js +0 -129
- package/dist/chunk-LU2V3LXA.js +0 -114
- package/dist/context-KHKWJJMC.js +0 -10
- package/dist/gcp-2JBTD7ZM.js +0 -204
- package/dist/generate-2XV2GSP2.js +0 -15
- package/dist/local-DXBEVZ5C.js +0 -47
- package/dist/server-A5AB67GQ.js +0 -36
- package/dist/ssh-B3NZAU4I.js +0 -8
- package/dist/{version-guard-GPSTCPT7.js → version-guard-XVYW5WFJ.js} +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/transport/known-hosts.ts
|
|
4
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
5
|
+
function hostEntryFor(host, port) {
|
|
6
|
+
return port === 22 ? host : `[${host}]:${port}`;
|
|
7
|
+
}
|
|
8
|
+
function parseKnownHosts(content) {
|
|
9
|
+
const entries = [];
|
|
10
|
+
for (const raw of content.split("\n")) {
|
|
11
|
+
const line = raw.trim();
|
|
12
|
+
if (!line || line.startsWith("#")) continue;
|
|
13
|
+
let parts = line.split(/\s+/);
|
|
14
|
+
let marker;
|
|
15
|
+
if (parts[0]?.startsWith("@")) {
|
|
16
|
+
marker = parts[0];
|
|
17
|
+
parts = parts.slice(1);
|
|
18
|
+
}
|
|
19
|
+
const hostField = parts[0];
|
|
20
|
+
if (!hostField) continue;
|
|
21
|
+
const hosts = hostField.split(",");
|
|
22
|
+
if (parts.length >= 3) {
|
|
23
|
+
entries.push({ hosts, keyType: parts[1], base64Key: parts[2], ...marker ? { marker } : {} });
|
|
24
|
+
} else if (parts.length === 2 && /^[0-9a-f]+$/i.test(parts[1] ?? "")) {
|
|
25
|
+
entries.push({ hosts, hexKey: parts[1], ...marker ? { marker } : {} });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return entries;
|
|
29
|
+
}
|
|
30
|
+
function hashedHostMatches(token, hostEntry) {
|
|
31
|
+
const parts = token.split("|");
|
|
32
|
+
if (parts.length !== 4 || parts[1] !== "1") return false;
|
|
33
|
+
const salt = parts[2];
|
|
34
|
+
const expected = parts[3];
|
|
35
|
+
if (!salt || !expected) return false;
|
|
36
|
+
try {
|
|
37
|
+
const actual = createHmac("sha1", Buffer.from(salt, "base64")).update(hostEntry).digest("base64");
|
|
38
|
+
const a = Buffer.from(actual);
|
|
39
|
+
const b = Buffer.from(expected);
|
|
40
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
41
|
+
} catch {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function patternToRegExp(pattern) {
|
|
46
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
47
|
+
return new RegExp(`^${escaped}$`, "i");
|
|
48
|
+
}
|
|
49
|
+
var isPattern = (host) => host.includes("*") || host.includes("?");
|
|
50
|
+
function entryMatchesHost(entry, hostEntry) {
|
|
51
|
+
let matched = false;
|
|
52
|
+
for (const host of entry.hosts) {
|
|
53
|
+
if (host.startsWith("|1|")) {
|
|
54
|
+
if (hashedHostMatches(host, hostEntry)) matched = true;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const negated = host.startsWith("!");
|
|
58
|
+
const pattern = negated ? host.slice(1) : host;
|
|
59
|
+
const hit = isPattern(pattern) ? patternToRegExp(pattern).test(hostEntry) : pattern === hostEntry;
|
|
60
|
+
if (hit) {
|
|
61
|
+
if (negated) return false;
|
|
62
|
+
matched = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return matched;
|
|
66
|
+
}
|
|
67
|
+
function verifyAgainstKnownHosts(content, host, port, key) {
|
|
68
|
+
const hostEntry = hostEntryFor(host, port);
|
|
69
|
+
const base64Key = key.toString("base64");
|
|
70
|
+
const hexKey = key.toString("hex");
|
|
71
|
+
let sawHost = false;
|
|
72
|
+
for (const entry of parseKnownHosts(content)) {
|
|
73
|
+
if (!entryMatchesHost(entry, hostEntry)) continue;
|
|
74
|
+
if (entry.marker === "@cert-authority") continue;
|
|
75
|
+
sawHost = true;
|
|
76
|
+
const matches = entry.base64Key ? entry.base64Key === base64Key : entry.hexKey?.toLowerCase() === hexKey.toLowerCase();
|
|
77
|
+
if (matches) return entry.marker === "@revoked" ? "mismatch" : "match";
|
|
78
|
+
}
|
|
79
|
+
return sawHost ? "mismatch" : "unknown";
|
|
80
|
+
}
|
|
81
|
+
function withoutHost(content, host, port) {
|
|
82
|
+
const hostEntry = hostEntryFor(host, port);
|
|
83
|
+
const kept = [];
|
|
84
|
+
for (const raw of content.split("\n")) {
|
|
85
|
+
const [entry] = parseKnownHosts(raw);
|
|
86
|
+
if (entry && entryMatchesHost(entry, hostEntry)) continue;
|
|
87
|
+
kept.push(raw);
|
|
88
|
+
}
|
|
89
|
+
return kept.join("\n");
|
|
90
|
+
}
|
|
91
|
+
function formatKnownHostsLine(host, port, keyType, key) {
|
|
92
|
+
return `${hostEntryFor(host, port)} ${keyType} ${key.toString("base64")}
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
function keyTypeFromBlob(key) {
|
|
96
|
+
if (key.length < 4) return void 0;
|
|
97
|
+
const len = key.readUInt32BE(0);
|
|
98
|
+
if (len === 0 || len > 64 || key.length < 4 + len) return void 0;
|
|
99
|
+
const type = key.subarray(4, 4 + len).toString("utf-8");
|
|
100
|
+
return /^[\x20-\x7e]+$/.test(type) ? type : void 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
verifyAgainstKnownHosts,
|
|
105
|
+
withoutHost,
|
|
106
|
+
formatKnownHostsLine,
|
|
107
|
+
keyTypeFromBlob
|
|
108
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/providers/aws/sizes.ts
|
|
4
|
+
var INSTANCE_TYPE_MAP = {
|
|
5
|
+
micro: "t3.micro",
|
|
6
|
+
small: "t3.small",
|
|
7
|
+
medium: "t3.medium",
|
|
8
|
+
large: "t3.large",
|
|
9
|
+
gpu: "g4dn.xlarge"
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_ALIAS = "small";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
INSTANCE_TYPE_MAP,
|
|
15
|
+
DEFAULT_ALIAS
|
|
16
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/openclaw/logs.ts
|
|
4
|
-
var GATEWAY_LOGS_PROBE = "docker exec openclaw openclaw logs --limit 1 >/dev/null
|
|
4
|
+
var GATEWAY_LOGS_PROBE = "docker exec openclaw openclaw logs --limit 1 >/dev/null";
|
|
5
5
|
function gatewayLogsCommand(opts) {
|
|
6
6
|
return [
|
|
7
7
|
"docker exec openclaw openclaw logs",
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/providers/azure/cli-auth.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
import process from "process";
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
function azureConfigDir() {
|
|
8
|
+
const explicit = process.env["AZURE_CONFIG_DIR"];
|
|
9
|
+
if (explicit) return explicit;
|
|
10
|
+
const home = process.env["HOME"] ?? process.env["USERPROFILE"];
|
|
11
|
+
return home ? path.join(home, ".azure") : void 0;
|
|
12
|
+
}
|
|
13
|
+
function accountFromProfile(body) {
|
|
14
|
+
let parsed;
|
|
15
|
+
try {
|
|
16
|
+
parsed = JSON.parse(body.replace(/^\uFEFF/, ""));
|
|
17
|
+
} catch {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
const subscriptions = parsed?.subscriptions;
|
|
21
|
+
if (!Array.isArray(subscriptions)) return void 0;
|
|
22
|
+
const entries = subscriptions;
|
|
23
|
+
const chosen = entries.find((s) => s["isDefault"] === true) ?? entries[0];
|
|
24
|
+
if (!chosen) return void 0;
|
|
25
|
+
const id = typeof chosen["id"] === "string" ? chosen["id"] : void 0;
|
|
26
|
+
if (!id) return void 0;
|
|
27
|
+
const user = chosen["user"];
|
|
28
|
+
return {
|
|
29
|
+
subscriptionId: id,
|
|
30
|
+
name: typeof chosen["name"] === "string" ? chosen["name"] : id,
|
|
31
|
+
...typeof user?.name === "string" ? { user: user.name } : {}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function azureCliAccount() {
|
|
35
|
+
const dir = azureConfigDir();
|
|
36
|
+
if (!dir) return void 0;
|
|
37
|
+
try {
|
|
38
|
+
return accountFromProfile(readFileSync(path.join(dir, "azureProfile.json"), "utf-8"));
|
|
39
|
+
} catch {
|
|
40
|
+
return void 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function resolveSubscriptionId() {
|
|
44
|
+
return process.env["ARM_SUBSCRIPTION_ID"] ?? process.env["AZURE_SUBSCRIPTION_ID"] ?? azureCliAccount()?.subscriptionId;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
azureConfigDir,
|
|
49
|
+
accountFromProfile,
|
|
50
|
+
azureCliAccount,
|
|
51
|
+
resolveSubscriptionId
|
|
52
|
+
};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
validatePlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-BSLIYB5Q.js";
|
|
5
5
|
import {
|
|
6
|
-
buildContext
|
|
7
|
-
|
|
6
|
+
buildContext,
|
|
7
|
+
loadAdapterModule
|
|
8
|
+
} from "./chunk-5XT5IAJK.js";
|
|
8
9
|
import {
|
|
9
10
|
getConfig
|
|
10
11
|
} from "./chunk-CX5SL5HP.js";
|
|
11
12
|
import {
|
|
13
|
+
StateError,
|
|
12
14
|
UsageError
|
|
13
15
|
} from "./chunk-KGXPLI7W.js";
|
|
14
16
|
|
|
@@ -19,12 +21,16 @@ function parseDiff(lines) {
|
|
|
19
21
|
const create = [];
|
|
20
22
|
const update = [];
|
|
21
23
|
const del = [];
|
|
24
|
+
const seen = /* @__PURE__ */ new Set();
|
|
22
25
|
for (const line of lines) {
|
|
23
26
|
const m = PREVIEW_LINE_RE.exec(line.trimStart());
|
|
24
27
|
if (!m) continue;
|
|
25
28
|
const [, op, resourceType, name] = m;
|
|
29
|
+
const urn = `urn:pulumi:::clawops::${resourceType}::${name ?? ""}`;
|
|
30
|
+
if (seen.has(`${op}${urn}`)) continue;
|
|
31
|
+
seen.add(`${op}${urn}`);
|
|
26
32
|
const ref = {
|
|
27
|
-
urn
|
|
33
|
+
urn,
|
|
28
34
|
type: resourceType,
|
|
29
35
|
...name ? { name } : {}
|
|
30
36
|
};
|
|
@@ -39,16 +45,33 @@ function parseDiff(lines) {
|
|
|
39
45
|
totalChanges: create.length + update.length + del.length
|
|
40
46
|
};
|
|
41
47
|
}
|
|
48
|
+
var INSTANCE_ALIASES = ["micro", "small", "medium", "large", "gpu"];
|
|
49
|
+
function isInstanceAlias(value) {
|
|
50
|
+
return INSTANCE_ALIASES.includes(value);
|
|
51
|
+
}
|
|
52
|
+
async function resolveInstanceType(intent) {
|
|
53
|
+
const requested = intent.instanceType ?? "small";
|
|
54
|
+
if (!isInstanceAlias(requested)) {
|
|
55
|
+
process.stderr.write(
|
|
56
|
+
`[clawops] note: "${requested}" is not a clawops size (${INSTANCE_ALIASES.join(", ")}), so it is passed to ${intent.provider} as written.
|
|
57
|
+
`
|
|
58
|
+
);
|
|
59
|
+
return requested;
|
|
60
|
+
}
|
|
61
|
+
const adapter = await loadAdapterModule(intent.provider);
|
|
62
|
+
return adapter.normalizeInstanceType(requested);
|
|
63
|
+
}
|
|
64
|
+
var parseDiffForTest = parseDiff;
|
|
42
65
|
async function generatePlan(intent, _opts) {
|
|
43
66
|
if (intent.provider === "local") {
|
|
44
67
|
throw new UsageError(
|
|
45
68
|
"plan/apply is not supported for the local provider. Use `clawops up` directly."
|
|
46
69
|
);
|
|
47
70
|
}
|
|
48
|
-
const { version } = await import("./package-
|
|
71
|
+
const { version } = await import("./package-5BCMEBTE.js");
|
|
49
72
|
const config = getConfig();
|
|
50
|
-
const instanceType = intent
|
|
51
|
-
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-
|
|
73
|
+
const instanceType = await resolveInstanceType(intent);
|
|
74
|
+
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
|
|
52
75
|
const openclawVersion = await guardOpenclawVersion(
|
|
53
76
|
intent.openclawVersion ?? await defaultOpenclawVersion()
|
|
54
77
|
);
|
|
@@ -56,6 +79,13 @@ async function generatePlan(intent, _opts) {
|
|
|
56
79
|
allowedSshCidrs: [],
|
|
57
80
|
allowedGatewayCidrs: []
|
|
58
81
|
};
|
|
82
|
+
const { resolvePublicKey } = await import("./ssh-key-DUO6OKOI.js");
|
|
83
|
+
const publicKey = config ? resolvePublicKey(expandHome(config.ssh.keyPath)) : void 0;
|
|
84
|
+
if (!publicKey) {
|
|
85
|
+
process.stderr.write(
|
|
86
|
+
"[clawops] warning: no SSH public key could be resolved" + (config ? ` from ${config.ssh.keyPath}` : " \u2014 no config file") + ". `clawops apply` will refuse this plan: the instance would have no way to admit anyone. Run `clawops doctor` to see why the key is unusable.\n"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
59
89
|
const stackName = intent.stackName;
|
|
60
90
|
const region = intent.region ?? config?.stacks[stackName]?.region;
|
|
61
91
|
const plan = {
|
|
@@ -74,15 +104,24 @@ async function generatePlan(intent, _opts) {
|
|
|
74
104
|
instanceType,
|
|
75
105
|
openclaw: { version: openclawVersion },
|
|
76
106
|
network,
|
|
107
|
+
...publicKey ? { ssh: { publicKey } } : {},
|
|
77
108
|
...intent.tags ? { tags: intent.tags } : {}
|
|
78
109
|
}
|
|
79
110
|
};
|
|
111
|
+
let stack;
|
|
80
112
|
try {
|
|
81
113
|
const ctx = buildContext({ stack: stackName, provider: intent.provider });
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
114
|
+
stack = await ctx.getStack();
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof UsageError) throw err;
|
|
117
|
+
throw new StateError(
|
|
118
|
+
`Cannot open the state backend for stack "${stackName}": ${messageOf(err)}
|
|
119
|
+
A plan is only as good as the state it was computed against, so this is not a plan clawops will write. Check the stateUrl in ~/.clawops/config.json, and that the bucket or container exists and your credentials can read it.`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const { writeStackConfig } = await import("./stack-config-R4SPSUQZ.js");
|
|
124
|
+
await writeStackConfig(stack, plan);
|
|
86
125
|
const outputLines = [];
|
|
87
126
|
const preview = await stack.preview({
|
|
88
127
|
onOutput: (line) => outputLines.push(line)
|
|
@@ -98,6 +137,7 @@ async function generatePlan(intent, _opts) {
|
|
|
98
137
|
plan.metadata.stackVersion = info.version;
|
|
99
138
|
}
|
|
100
139
|
} catch (err) {
|
|
140
|
+
if (err instanceof UsageError) throw err;
|
|
101
141
|
process.stderr.write(
|
|
102
142
|
`[clawops] Warning: preview failed, diff section omitted: ${err instanceof Error ? err.message : String(err)}
|
|
103
143
|
`
|
|
@@ -108,7 +148,7 @@ async function generatePlan(intent, _opts) {
|
|
|
108
148
|
throw new Error(`Generated plan failed schema validation:
|
|
109
149
|
${validation.errors.join("\n")}`);
|
|
110
150
|
}
|
|
111
|
-
const { validatePlanConfig, validatePlanNetwork } = await import("./validate-
|
|
151
|
+
const { validatePlanConfig, validatePlanNetwork } = await import("./validate-YVI3BJ55.js");
|
|
112
152
|
const cfgCheck = await validatePlanConfig(plan);
|
|
113
153
|
for (const w of cfgCheck.warnings) process.stderr.write(`[clawops] warning: ${w}
|
|
114
154
|
`);
|
|
@@ -133,8 +173,21 @@ Fix spec.openclaw.config before applying \u2014 the gateway reads it verbatim.`
|
|
|
133
173
|
function planId() {
|
|
134
174
|
return randomUUID().slice(0, 8);
|
|
135
175
|
}
|
|
176
|
+
function expandHome(p) {
|
|
177
|
+
return p.replace(/^~/, process.env["HOME"] ?? "~");
|
|
178
|
+
}
|
|
179
|
+
function messageOf(err) {
|
|
180
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
181
|
+
const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l !== "");
|
|
182
|
+
const explained = lines.find((l) => /(^|\s)error:/i.test(l));
|
|
183
|
+
if (explained) return explained.replace(/^.*?error:\s*/i, "");
|
|
184
|
+
return lines[0] ?? raw;
|
|
185
|
+
}
|
|
136
186
|
|
|
137
187
|
export {
|
|
188
|
+
INSTANCE_ALIASES,
|
|
189
|
+
isInstanceAlias,
|
|
190
|
+
parseDiffForTest,
|
|
138
191
|
generatePlan,
|
|
139
192
|
planId
|
|
140
193
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/pulumi/passphrase.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
import process from "process";
|
|
6
|
+
import { randomBytes } from "crypto";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
|
|
8
|
+
var PASSPHRASE_FILE = "pulumi-passphrase";
|
|
9
|
+
function passphrasePath(configDir) {
|
|
10
|
+
return path.join(configDir, "secrets", PASSPHRASE_FILE);
|
|
11
|
+
}
|
|
12
|
+
function passphraseInEnvironment() {
|
|
13
|
+
return Boolean(
|
|
14
|
+
process.env["PULUMI_CONFIG_PASSPHRASE"] ?? process.env["PULUMI_CONFIG_PASSPHRASE_FILE"]
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
function passphraseStatus(configDir) {
|
|
18
|
+
if (passphraseInEnvironment()) return "environment";
|
|
19
|
+
return existsSync(passphrasePath(configDir)) ? "stored" : "absent";
|
|
20
|
+
}
|
|
21
|
+
function ensurePassphrase(configDir) {
|
|
22
|
+
if (passphraseInEnvironment()) return void 0;
|
|
23
|
+
const file = passphrasePath(configDir);
|
|
24
|
+
if (existsSync(file)) {
|
|
25
|
+
const stored = readFileSync(file, "utf-8").trim();
|
|
26
|
+
if (stored !== "") return stored;
|
|
27
|
+
}
|
|
28
|
+
const dir = path.dirname(file);
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
try {
|
|
31
|
+
chmodSync(dir, 448);
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
34
|
+
const generated = randomBytes(32).toString("base64url");
|
|
35
|
+
writeFileSync(file, generated + "\n", { encoding: "utf-8", mode: 384 });
|
|
36
|
+
return generated;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
PASSPHRASE_FILE,
|
|
41
|
+
passphrasePath,
|
|
42
|
+
passphraseInEnvironment,
|
|
43
|
+
passphraseStatus,
|
|
44
|
+
ensurePassphrase
|
|
45
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
err,
|
|
4
|
+
ok
|
|
5
|
+
} from "./chunk-EI7CQQ7D.js";
|
|
6
|
+
|
|
7
|
+
// src/openclaw/run-flags.ts
|
|
8
|
+
var CONFIG_MOUNT_PATH = "/app/config.json";
|
|
9
|
+
var GATEWAY_PORT = 18789;
|
|
10
|
+
var CONFIG_PATH_ENV = `-e OPENCLAW_CONFIG_PATH=${CONFIG_MOUNT_PATH}`;
|
|
11
|
+
var ADD_HOST_FLAG = "--add-host=host.docker.internal:host-gateway";
|
|
12
|
+
var PORT_PIN = `--port ${GATEWAY_PORT}`;
|
|
13
|
+
var COMMON_RUN_FLAGS = `${CONFIG_PATH_ENV} ${ADD_HOST_FLAG}`;
|
|
14
|
+
var IMAGE_INSPECT_CMD = `docker inspect openclaw --format '{{.Config.Image}}'`;
|
|
15
|
+
function imageForRestart(inspectStdout) {
|
|
16
|
+
const image = inspectStdout.trim();
|
|
17
|
+
if (!image || image.includes("Error") || !image.includes(":")) {
|
|
18
|
+
return err(
|
|
19
|
+
"No running OpenClaw container found, so there is no image version to reuse. Deploy one with `clawops up --openclaw-version <version>`. clawops does not fall back to `latest` or `stable` here: both now point at OpenClaw 2.0, which this release line does not support."
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return ok(image);
|
|
23
|
+
}
|
|
24
|
+
function versionOf(image) {
|
|
25
|
+
return image.split(":")[1] ?? "unknown";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
GATEWAY_PORT,
|
|
30
|
+
IMAGE_INSPECT_CMD,
|
|
31
|
+
imageForRestart,
|
|
32
|
+
versionOf
|
|
33
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/pulumi/cli.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
import process from "process";
|
|
6
|
+
import { PulumiCommand } from "@pulumi/pulumi/automation";
|
|
7
|
+
function pulumiCliRoot(configDir) {
|
|
8
|
+
return path.join(configDir, ".pulumi-cli");
|
|
9
|
+
}
|
|
10
|
+
function versionOf(cmd) {
|
|
11
|
+
return cmd.version ? `v${cmd.version.toString()}` : "unknown version";
|
|
12
|
+
}
|
|
13
|
+
async function pulumiCliStatus(configDir) {
|
|
14
|
+
const root = pulumiCliRoot(configDir);
|
|
15
|
+
try {
|
|
16
|
+
return { kind: "managed", version: versionOf(await PulumiCommand.get({ root })), root };
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
return { kind: "path", version: versionOf(await PulumiCommand.get()) };
|
|
21
|
+
} catch {
|
|
22
|
+
return { kind: "missing" };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function ensurePulumiCli(opts) {
|
|
26
|
+
const root = pulumiCliRoot(opts.configDir);
|
|
27
|
+
const status = await pulumiCliStatus(opts.configDir);
|
|
28
|
+
if (status.kind === "managed") return await PulumiCommand.get({ root });
|
|
29
|
+
if (status.kind === "path") return await PulumiCommand.get();
|
|
30
|
+
const announce = opts.onInstall ?? defaultAnnounce;
|
|
31
|
+
announce({ root });
|
|
32
|
+
try {
|
|
33
|
+
return await PulumiCommand.install({ root });
|
|
34
|
+
} catch (err) {
|
|
35
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
36
|
+
throw new Error(
|
|
37
|
+
`could not install the Pulumi CLI into ${root}: ${reason}
|
|
38
|
+
Install it yourself and clawops will use it: https://www.pulumi.com/docs/install/`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function defaultAnnounce({ root }) {
|
|
43
|
+
process.stderr.write(`Pulumi CLI not found \u2014 installing it into ${root} (one time).
|
|
44
|
+
`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
pulumiCliRoot,
|
|
49
|
+
pulumiCliStatus,
|
|
50
|
+
ensurePulumiCli
|
|
51
|
+
};
|