@clawops/cli 0.2.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.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ buildContext
4
+ } from "./chunk-PERSDQMT.js";
5
+ import "./chunk-PRYLTCS4.js";
6
+ import "./chunk-ALSUDYA7.js";
7
+ import "./chunk-ZSE4QRKE.js";
8
+ export {
9
+ buildContext
10
+ };
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/providers/firewall.ts
4
+ function resolveIngressCidrs(accessMode, allowedCidrs, portOverride, detectedIp) {
5
+ if (portOverride.trim()) {
6
+ return portOverride.split(",").map((s) => s.trim()).filter(Boolean);
7
+ }
8
+ switch (accessMode) {
9
+ case "restricted": {
10
+ if (!allowedCidrs.trim()) return [];
11
+ return allowedCidrs.split(",").map((s) => s.trim()).filter(Boolean);
12
+ }
13
+ case "auto": {
14
+ if (!detectedIp.trim()) return [];
15
+ const ip = detectedIp.trim();
16
+ return [ip.includes("/") ? ip : `${ip}/32`];
17
+ }
18
+ case "open":
19
+ return ["0.0.0.0/0"];
20
+ default:
21
+ return [];
22
+ }
23
+ }
24
+ async function detectEgressIp(checkUrl) {
25
+ try {
26
+ const res = await fetch(checkUrl, { signal: AbortSignal.timeout(5e3) });
27
+ if (!res.ok) return "";
28
+ return (await res.text()).trim();
29
+ } catch {
30
+ return "";
31
+ }
32
+ }
33
+ export {
34
+ detectEgressIp,
35
+ resolveIngressCidrs
36
+ };
@@ -0,0 +1,198 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/providers/gcp/index.ts
4
+ import process from "process";
5
+
6
+ // src/providers/gcp/program.ts
7
+ var GATEWAY_PORT = 18789;
8
+ var SSH_PORT = 22;
9
+ var gcpProgram = async () => {
10
+ const [pulumi, gcp] = await Promise.all([
11
+ import("@pulumi/pulumi"),
12
+ import("@pulumi/gcp")
13
+ ]);
14
+ const cfg = new pulumi.Config();
15
+ const instanceType = cfg.get("instanceType") ?? "e2-standard-2";
16
+ const region = cfg.get("region") ?? "us-central1";
17
+ const openclawVersion = cfg.get("openclawVersion") ?? "stable";
18
+ const zone = cfg.get("zone") ?? `${region}-a`;
19
+ const network = new gcp.compute.Network("clawops-network", {
20
+ autoCreateSubnetworks: false,
21
+ description: "clawops managed network"
22
+ });
23
+ const subnet = new gcp.compute.Subnetwork("clawops-subnet", {
24
+ ipCidrRange: "10.0.0.0/24",
25
+ region,
26
+ network: network.id
27
+ });
28
+ new gcp.compute.Firewall("clawops-firewall", {
29
+ network: network.selfLink,
30
+ allows: [
31
+ {
32
+ protocol: "tcp",
33
+ ports: [String(SSH_PORT), String(GATEWAY_PORT)]
34
+ }
35
+ ],
36
+ sourceRanges: ["0.0.0.0/0"],
37
+ targetTags: ["clawops"]
38
+ });
39
+ const address = new gcp.compute.Address("clawops-address", { region });
40
+ const instance = new gcp.compute.Instance("clawops-instance", {
41
+ machineType: instanceType,
42
+ zone,
43
+ tags: ["clawops"],
44
+ bootDisk: {
45
+ initializeParams: {
46
+ image: "debian-cloud/debian-12",
47
+ size: 20
48
+ }
49
+ },
50
+ networkInterfaces: [
51
+ {
52
+ network: network.id,
53
+ subnetwork: subnet.id,
54
+ accessConfigs: [
55
+ {
56
+ natIp: address.address,
57
+ networkTier: "PREMIUM"
58
+ }
59
+ ]
60
+ }
61
+ ],
62
+ metadata: {
63
+ "startup-script": makeStartupScript(openclawVersion)
64
+ },
65
+ serviceAccount: {
66
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"]
67
+ }
68
+ });
69
+ return {
70
+ instanceId: instance.id,
71
+ publicIp: address.address,
72
+ gatewayUrl: pulumi.interpolate`https://${address.address}:${GATEWAY_PORT}`,
73
+ sshHost: address.address,
74
+ sshPort: SSH_PORT,
75
+ sshUser: "clawops",
76
+ region,
77
+ provisionedAt: (/* @__PURE__ */ new Date()).toISOString()
78
+ };
79
+ };
80
+ function makeStartupScript(openclawVersion) {
81
+ return `#!/bin/bash
82
+ set -euo pipefail
83
+
84
+ # Create clawops user with SSH access
85
+ id -u clawops &>/dev/null || useradd -m -s /bin/bash clawops
86
+ mkdir -p /home/clawops/.ssh
87
+ chmod 700 /home/clawops/.ssh
88
+
89
+ # Install Docker if not present
90
+ if ! command -v docker &>/dev/null; then
91
+ apt-get update -q
92
+ apt-get install -y -q ca-certificates curl gnupg lsb-release
93
+ install -m 0755 -d /etc/apt/keyrings
94
+ curl -fsSL https://download.docker.com/linux/debian/gpg \\
95
+ | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
96
+ chmod a+r /etc/apt/keyrings/docker.gpg
97
+ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \\
98
+ https://download.docker.com/linux/debian $(lsb_release -cs) stable" \\
99
+ > /etc/apt/sources.list.d/docker.list
100
+ apt-get update -q
101
+ apt-get install -y -q docker-ce docker-ce-cli containerd.io
102
+ systemctl enable --now docker
103
+ fi
104
+
105
+ usermod -aG docker clawops
106
+
107
+ # Pull OpenClaw image
108
+ OPENCLAW_VERSION="${openclawVersion}"
109
+ docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
110
+
111
+ # Create default openclaw.json if not present
112
+ OPENCLAW_CONFIG=/home/clawops/openclaw.json
113
+ if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
114
+ cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
115
+ {"version":"2026.4","gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":[]}
116
+ OPENCLAWJSON
117
+ chown clawops:clawops "\${OPENCLAW_CONFIG}"
118
+ fi
119
+
120
+ # Start OpenClaw container
121
+ docker stop openclaw 2>/dev/null || true
122
+ docker rm openclaw 2>/dev/null || true
123
+ docker run -d \\
124
+ --name openclaw \\
125
+ --restart unless-stopped \\
126
+ -p ${GATEWAY_PORT}:${GATEWAY_PORT} \\
127
+ -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
128
+ ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
129
+ `;
130
+ }
131
+
132
+ // src/providers/gcp/index.ts
133
+ var INSTANCE_TYPE_MAP = {
134
+ micro: "e2-micro",
135
+ small: "e2-standard-2",
136
+ medium: "e2-standard-4",
137
+ large: "e2-standard-8",
138
+ gpu: "n1-standard-4"
139
+ // TODO M3: add accelerator config for GPU instances
140
+ };
141
+ var gcpAdapter = {
142
+ name: "gcp",
143
+ get program() {
144
+ return gcpProgram;
145
+ },
146
+ getConnectionInfo(outputs) {
147
+ return {
148
+ host: String(outputs["sshHost"]),
149
+ port: Number(outputs["sshPort"]),
150
+ user: String(outputs["sshUser"]),
151
+ privateKeyPath: String(outputs["privateKeyPath"] ?? ""),
152
+ knownHostsPath: String(outputs["knownHostsPath"] ?? "")
153
+ };
154
+ },
155
+ normalizeInstanceType(alias) {
156
+ const mapped = INSTANCE_TYPE_MAP[alias];
157
+ if (!mapped) throw new Error(`Unknown instance alias: ${alias}`);
158
+ return mapped;
159
+ },
160
+ defaultRegion() {
161
+ return "us-central1";
162
+ },
163
+ stateBackendUrl(bucket) {
164
+ return `gs://${bucket}`;
165
+ },
166
+ async validateConfig() {
167
+ const errors = [];
168
+ const hasKeyFile = Boolean(process.env["GOOGLE_APPLICATION_CREDENTIALS"]);
169
+ const hasUserCreds = Boolean(process.env["CLOUDSDK_AUTH_ACCESS_TOKEN"]);
170
+ if (!hasKeyFile && !hasUserCreds) {
171
+ const onGcp = await checkInstanceMetadata();
172
+ if (!onGcp) {
173
+ errors.push(
174
+ "No GCP credentials found. Set GOOGLE_APPLICATION_CREDENTIALS to a service account key file, or run `gcloud auth application-default login`."
175
+ );
176
+ }
177
+ }
178
+ return { ok: errors.length === 0, errors };
179
+ }
180
+ };
181
+ async function checkInstanceMetadata() {
182
+ try {
183
+ const res = await fetch(
184
+ "http://metadata.google.internal/computeMetadata/v1/instance/id",
185
+ {
186
+ headers: { "Metadata-Flavor": "Google" },
187
+ signal: AbortSignal.timeout(1e3)
188
+ }
189
+ );
190
+ return res.ok;
191
+ } catch {
192
+ return false;
193
+ }
194
+ }
195
+ var gcp_default = gcpAdapter;
196
+ export {
197
+ gcp_default as default
198
+ };
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ generatePlan,
4
+ planId
5
+ } from "./chunk-ENQY5OW2.js";
6
+ import "./chunk-PERSDQMT.js";
7
+ import "./chunk-PRYLTCS4.js";
8
+ import "./chunk-YTH4L2GN.js";
9
+ import "./chunk-ALSUDYA7.js";
10
+ import "./chunk-ZSE4QRKE.js";
11
+ export {
12
+ generatePlan,
13
+ planId
14
+ };
@@ -0,0 +1,52 @@
1
+ /** Inline Pulumi program — a closure that creates resources when called. */
2
+ type PulumiFn = () => Promise<Record<string, unknown> | void>;
3
+ /** Lowercase provider id matching the schema enum. */
4
+ type ProviderName = 'aws' | 'gcp' | 'azure' | 'local';
5
+ /** Normalised clawops instance-size alias. */
6
+ type InstanceAlias = 'micro' | 'small' | 'medium' | 'large' | 'gpu';
7
+ /** How this provider's credentials are sourced (R6: never in tool args). */
8
+ type CredentialSource = 'env' | 'cli-profile' | 'file' | 'instance-metadata';
9
+ /** Pulumi state backend URL scheme. */
10
+ type StateBackendScheme = 's3' | 'gs' | 'azblob' | 'file';
11
+ interface ConnectionInfo {
12
+ host: string;
13
+ port: number;
14
+ user: string;
15
+ privateKeyPath: string;
16
+ knownHostsPath: string;
17
+ }
18
+ interface BaseStackOutputs {
19
+ instanceId: string;
20
+ publicIp: string;
21
+ gatewayUrl: string;
22
+ sshHost: string;
23
+ sshPort: number;
24
+ sshUser: string;
25
+ region: string;
26
+ /** ISO-8601 timestamp */
27
+ provisionedAt: string;
28
+ }
29
+ type StackOutputs = BaseStackOutputs & Record<string, unknown>;
30
+ interface ValidationResult {
31
+ ok: boolean;
32
+ errors: string[];
33
+ }
34
+ /** Contract every cloud provider adapter must satisfy. Per R-meta-1. */
35
+ interface ProviderAdapter {
36
+ /** Lowercase provider id matching the schema enum. */
37
+ readonly name: ProviderName;
38
+ /** Inline Pulumi program for this provider's stack. */
39
+ readonly program: PulumiFn;
40
+ /** Extract connection details from a deployed stack's outputs. */
41
+ getConnectionInfo(outputs: StackOutputs): ConnectionInfo;
42
+ /** Map an alias like "small" to the provider's native instance type. */
43
+ normalizeInstanceType(alias: InstanceAlias): string;
44
+ /** Provider-default region if user didn't specify. */
45
+ defaultRegion(): string;
46
+ /** State backend URL prefix for this provider. */
47
+ stateBackendUrl(bucket: string): string;
48
+ /** Validate provider-specific config (env vars, profiles) at startup. */
49
+ validateConfig(): Promise<ValidationResult>;
50
+ }
51
+
52
+ export type { BaseStackOutputs, ConnectionInfo, CredentialSource, InstanceAlias, ProviderAdapter, ProviderName, PulumiFn, StackOutputs, StateBackendScheme, ValidationResult };
package/dist/index.js ADDED
File without changes
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/providers/local/index.ts
4
+ import { accessSync } from "fs";
5
+ var localAdapter = {
6
+ name: "local",
7
+ /** Never called — local stacks bypass Pulumi entirely. */
8
+ get program() {
9
+ return async () => ({});
10
+ },
11
+ getConnectionInfo(outputs) {
12
+ return {
13
+ host: String(outputs["sshHost"] ?? ""),
14
+ port: Number(outputs["sshPort"] ?? 22),
15
+ user: String(outputs["sshUser"] ?? "root"),
16
+ privateKeyPath: String(outputs["privateKeyPath"] ?? ""),
17
+ knownHostsPath: String(outputs["knownHostsPath"] ?? "")
18
+ };
19
+ },
20
+ normalizeInstanceType(_alias) {
21
+ return "local";
22
+ },
23
+ defaultRegion() {
24
+ return "local";
25
+ },
26
+ stateBackendUrl(_bucket) {
27
+ return "file://~/.clawops/state";
28
+ },
29
+ async validateConfig() {
30
+ const keyPath = process.env["CLAWOPS_SSH_KEY_PATH"];
31
+ if (keyPath) {
32
+ try {
33
+ accessSync(keyPath);
34
+ } catch {
35
+ return {
36
+ ok: false,
37
+ errors: [`SSH key not readable: ${keyPath}`]
38
+ };
39
+ }
40
+ }
41
+ return { ok: true, errors: [] };
42
+ }
43
+ };
44
+ var local_default = localAdapter;
45
+ export {
46
+ local_default as default
47
+ };
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ extractBaseOutputs
4
+ } from "./chunk-LT24GUUO.js";
5
+ import "./chunk-ZSE4QRKE.js";
6
+ export {
7
+ extractBaseOutputs
8
+ };
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env node
2
+
3
+ // package.json
4
+ var name = "@clawops/cli";
5
+ var version = "0.2.0";
6
+ var description = "Deploy and manage self-hosted OpenClaw instances across clouds";
7
+ var type = "module";
8
+ var bin = {
9
+ clawops: "./dist/cli.js"
10
+ };
11
+ var exports = {
12
+ ".": "./dist/index.js"
13
+ };
14
+ var files = [
15
+ "dist"
16
+ ];
17
+ var engines = {
18
+ node: ">=22"
19
+ };
20
+ var packageManager = "pnpm@10.12.1";
21
+ var scripts = {
22
+ dev: "tsx src/cli/index.ts",
23
+ build: "tsup",
24
+ test: "vitest run",
25
+ "test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
26
+ "test:changed": "vitest --changed",
27
+ typecheck: "tsc --noEmit",
28
+ lint: "eslint --max-warnings=0 src tests scripts",
29
+ "gen:schemas": "tsx scripts/gen-schemas.ts",
30
+ changeset: "changeset",
31
+ release: "pnpm build && changeset publish"
32
+ };
33
+ var dependencies = {
34
+ "@modelcontextprotocol/sdk": "^1.0.0",
35
+ "@pulumi/aws": "^6.0.0",
36
+ "@pulumi/azure-native": "^2.0.0",
37
+ "@pulumi/command": "^1.0.0",
38
+ "@pulumi/docker": "^4.0.0",
39
+ "@pulumi/gcp": "^7.0.0",
40
+ "@pulumi/pulumi": "^3.0.0",
41
+ ajv: "^8.0.0",
42
+ "ajv-formats": "^3.0.0",
43
+ chalk: "^5.0.0",
44
+ citty: "^0.1.0",
45
+ conf: "^12.0.0",
46
+ inquirer: "^9.0.0",
47
+ ora: "^8.0.0",
48
+ ssh2: "^1.0.0",
49
+ zod: "^3.0.0"
50
+ };
51
+ var devDependencies = {
52
+ "aws-sdk-client-mock": "^4.0.0",
53
+ "@changesets/cli": "^2.0.0",
54
+ "@types/js-yaml": "^4.0.0",
55
+ "@types/node": "^22.0.0",
56
+ "@types/ssh2": "^1.0.0",
57
+ "@vitest/coverage-v8": "^2.0.0",
58
+ eslint: "^9.0.0",
59
+ "js-yaml": "^4.0.0",
60
+ nock: "^14.0.0",
61
+ tsup: "^8.0.0",
62
+ tsx: "^4.0.0",
63
+ typescript: "^5.0.0",
64
+ "typescript-eslint": "^8.0.0",
65
+ vitest: "^2.0.0"
66
+ };
67
+ var pnpm = {
68
+ overrides: {
69
+ "@pulumi/pulumi": "^3.0.0"
70
+ },
71
+ onlyBuiltDependencies: [
72
+ "@pulumi/command",
73
+ "cpu-features",
74
+ "esbuild",
75
+ "protobufjs",
76
+ "ssh2"
77
+ ]
78
+ };
79
+ var package_default = {
80
+ name,
81
+ version,
82
+ description,
83
+ type,
84
+ bin,
85
+ exports,
86
+ files,
87
+ engines,
88
+ packageManager,
89
+ scripts,
90
+ dependencies,
91
+ devDependencies,
92
+ pnpm
93
+ };
94
+ export {
95
+ bin,
96
+ package_default as default,
97
+ dependencies,
98
+ description,
99
+ devDependencies,
100
+ engines,
101
+ exports,
102
+ files,
103
+ name,
104
+ packageManager,
105
+ pnpm,
106
+ scripts,
107
+ type,
108
+ version
109
+ };
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ acquireSession,
4
+ drainPool
5
+ } from "./chunk-5XEZAU7V.js";
6
+ import "./chunk-ZSE4QRKE.js";
7
+ export {
8
+ acquireSession,
9
+ drainPool
10
+ };
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/providers/index.ts
4
+ var registry = /* @__PURE__ */ new Map();
5
+ function registerProvider(adapter) {
6
+ registry.set(adapter.name, adapter);
7
+ }
8
+ function getProvider(name) {
9
+ const adapter = registry.get(name);
10
+ if (!adapter) {
11
+ throw new Error(
12
+ `No provider adapter registered for '${name}'. Run \`clawops init\` to configure a provider.`
13
+ );
14
+ }
15
+ return adapter;
16
+ }
17
+ function listProviders() {
18
+ return Array.from(registry.keys());
19
+ }
20
+ export {
21
+ getProvider,
22
+ listProviders,
23
+ registerProvider
24
+ };