@clawops/cli 1.3.0 → 1.4.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.
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-3QJBNAHW.js";
5
5
  import {
6
6
  generatePlan
7
- } from "./chunk-ZDPBBYYV.js";
7
+ } from "./chunk-UAA6YOOP.js";
8
8
  import {
9
9
  applyPlan
10
- } from "./chunk-JCUZU5BH.js";
10
+ } from "./chunk-ZONXY3C6.js";
11
11
  import {
12
12
  validatePlan
13
13
  } from "./chunk-YTH4L2GN.js";
@@ -15,22 +15,24 @@ import "./chunk-6ZFIFDBJ.js";
15
15
  import "./chunk-BOPSG2LI.js";
16
16
  import {
17
17
  errText,
18
+ formatUptime,
19
+ gatherSnapshot,
18
20
  handleConfigGet,
19
21
  handleConfigSet,
20
22
  handleConfigUnset,
21
23
  handleConfigValidate,
22
24
  okText,
23
25
  resolveConn
24
- } from "./chunk-LVIIYY27.js";
26
+ } from "./chunk-JYQZJMD3.js";
25
27
  import {
26
28
  buildContext
27
- } from "./chunk-JDN6PLH2.js";
29
+ } from "./chunk-T5EX55GP.js";
28
30
  import {
29
31
  acquireSession,
30
32
  drainPool
31
33
  } from "./chunk-ZVOEQCNW.js";
32
34
  import "./chunk-4U3LTLWZ.js";
33
- import "./chunk-UDNZUSKA.js";
35
+ import "./chunk-ZFNPM2WG.js";
34
36
  import "./chunk-A2I76FTA.js";
35
37
  import {
36
38
  getConfig,
@@ -140,7 +142,7 @@ var clawops_statusAnnotations = {
140
142
  };
141
143
  var clawops_logs_tailSchema = z.object({
142
144
  stackName: z.string().optional(),
143
- tailLines: z.number().int().default(100),
145
+ tailLines: z.number().int().optional().default(100),
144
146
  sinceMin: z.number().int().optional()
145
147
  });
146
148
  var clawops_logs_tailAnnotations = {
@@ -151,6 +153,18 @@ var clawops_logs_tailAnnotations = {
151
153
  openWorldHint: true,
152
154
  toolsets: ["cli", "read"]
153
155
  };
156
+ var clawops_monitorSchema = z.object({
157
+ stackName: z.string().optional(),
158
+ tailLines: z.number().int().optional().default(5)
159
+ });
160
+ var clawops_monitorAnnotations = {
161
+ title: "Monitor Stack Health",
162
+ readOnlyHint: true,
163
+ destructiveHint: false,
164
+ idempotentHint: true,
165
+ openWorldHint: true,
166
+ toolsets: ["cli", "read"]
167
+ };
154
168
  var clawops_stacks_listSchema = z.object({});
155
169
  var clawops_stacks_listAnnotations = {
156
170
  title: "List Stacks",
@@ -187,9 +201,9 @@ var clawops_upSchema = z.object({
187
201
  stackName: z.string().optional(),
188
202
  provider: z.enum(["aws", "gcp", "azure", "local"]).optional(),
189
203
  region: z.string().optional(),
190
- instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).default("small"),
204
+ instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).optional().default("small"),
191
205
  openclawVersion: z.string().optional(),
192
- dryRun: z.boolean().default(false)
206
+ dryRun: z.boolean().optional().default(false)
193
207
  });
194
208
  var clawops_upAnnotations = {
195
209
  title: "Provision and Deploy Stack",
@@ -201,7 +215,7 @@ var clawops_upAnnotations = {
201
215
  };
202
216
  var clawops_destroySchema = z.object({
203
217
  stackName: z.string(),
204
- yes: z.boolean().default(false)
218
+ yes: z.boolean().optional().default(false)
205
219
  });
206
220
  var clawops_destroyAnnotations = {
207
221
  title: "Destroy Stack (DESTRUCTIVE)",
@@ -213,7 +227,7 @@ var clawops_destroyAnnotations = {
213
227
  };
214
228
  var clawops_applySchema = z.object({
215
229
  planPath: z.string(),
216
- yes: z.boolean().default(false)
230
+ yes: z.boolean().optional().default(false)
217
231
  });
218
232
  var clawops_applyAnnotations = {
219
233
  title: "Apply Maker Plan",
@@ -242,7 +256,7 @@ var clawops_config_setSchema = z.object({
242
256
  stackName: z.string().optional(),
243
257
  key: z.string(),
244
258
  value: z.string(),
245
- restart: z.boolean().default(false)
259
+ restart: z.boolean().optional().default(false)
246
260
  });
247
261
  var clawops_config_setAnnotations = {
248
262
  title: "Set OpenClaw Config Value",
@@ -255,7 +269,7 @@ var clawops_config_setAnnotations = {
255
269
  var clawops_config_unsetSchema = z.object({
256
270
  stackName: z.string().optional(),
257
271
  key: z.string(),
258
- restart: z.boolean().default(false)
272
+ restart: z.boolean().optional().default(false)
259
273
  });
260
274
  var clawops_config_unsetAnnotations = {
261
275
  title: "Unset OpenClaw Config Key",
@@ -302,8 +316,8 @@ var clawops_gateway_restartAnnotations = {
302
316
  var clawops_workflow_deploy_appSchema = z.object({
303
317
  provider: z.enum(["aws", "gcp", "azure", "local"]),
304
318
  region: z.string().optional(),
305
- stackName: z.string().default("default"),
306
- instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).default("small")
319
+ stackName: z.string().optional().default("default"),
320
+ instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).optional().default("small")
307
321
  });
308
322
  var clawops_workflow_deploy_appAnnotations = {
309
323
  title: "Deploy OpenClaw (End-to-End Workflow)",
@@ -339,6 +353,7 @@ var TOOLSETS = {
339
353
  cli: [
340
354
  "clawops_status",
341
355
  "clawops_logs_tail",
356
+ "clawops_monitor",
342
357
  "clawops_config_get",
343
358
  "clawops_agents_list",
344
359
  "clawops_up",
@@ -359,6 +374,7 @@ var TOOLSETS = {
359
374
  read: [
360
375
  "clawops_status",
361
376
  "clawops_logs_tail",
377
+ "clawops_monitor",
362
378
  "clawops_stacks_list",
363
379
  "clawops_config_get",
364
380
  "clawops_agents_list",
@@ -835,6 +851,62 @@ async function handleTaskStatus(input, _server) {
835
851
  return { content: [{ type: "text", text: JSON.stringify(record, null, 2) }] };
836
852
  }
837
853
 
854
+ // src/mcp/tools/cli/monitor.ts
855
+ async function handleMonitor(input, _server) {
856
+ const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-FBFHATDG.js");
857
+ const ctx = buildContext({ stack: input.stackName });
858
+ const tailLines = input.tailLines ?? 5;
859
+ let conn;
860
+ if (ctx.adapter.name === "local") {
861
+ if (!ctx.localState) {
862
+ return text2(JSON.stringify({ error: "Stack is not bootstrapped. Run `clawops up` first." }));
863
+ }
864
+ const ls = ctx.localState;
865
+ conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
866
+ } else {
867
+ const stack = await ctx.getStack();
868
+ const outputMap = await stack.outputs();
869
+ const outputs = Object.fromEntries(
870
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
871
+ );
872
+ if (!outputs["publicIp"]) {
873
+ return text2(JSON.stringify({ error: "Stack has no outputs. Run `clawops up` first." }));
874
+ }
875
+ const base = extractBaseOutputs(outputs);
876
+ conn = ctx.adapter.getConnectionInfo({
877
+ ...base,
878
+ privateKeyPath: ctx.config.ssh.keyPath,
879
+ knownHostsPath: ctx.config.ssh.knownHostsPath
880
+ });
881
+ }
882
+ const ac = new AbortController();
883
+ const { session, release } = await acquireSession2({ ...conn, signal: ac.signal });
884
+ try {
885
+ const snap = await gatherSnapshot(session, ac.signal, tailLines);
886
+ return text2(JSON.stringify({
887
+ gateway: snap.gateway,
888
+ container: {
889
+ status: snap.container.status,
890
+ image: snap.container.image,
891
+ restartCount: snap.container.restartCount,
892
+ memUsage: snap.container.memUsage,
893
+ cpuPct: snap.container.cpuPct,
894
+ uptime: formatUptime(snap.container.startedAt)
895
+ },
896
+ disk: snap.disk,
897
+ logLines: snap.logLines,
898
+ capturedAt: snap.capturedAt.toISOString()
899
+ }, null, 2));
900
+ } finally {
901
+ release();
902
+ drainPool2();
903
+ ac.abort();
904
+ }
905
+ }
906
+ function text2(t) {
907
+ return { content: [{ type: "text", text: t }] };
908
+ }
909
+
838
910
  // src/mcp/tools/workflow/deploy_app.ts
839
911
  async function handleWorkflowDeployApp(input, server) {
840
912
  const parts = [`## Deployment Workflow \u2014 ${input.provider} / ${input.stackName}
@@ -947,6 +1019,7 @@ function makeEntry(schema, annotations, handler) {
947
1019
  var TOOL_REGISTRY = {
948
1020
  clawops_status: makeEntry(clawops_statusSchema, clawops_statusAnnotations, handleStatus),
949
1021
  clawops_logs_tail: makeEntry(clawops_logs_tailSchema, clawops_logs_tailAnnotations, handleLogsTail),
1022
+ clawops_monitor: makeEntry(clawops_monitorSchema, clawops_monitorAnnotations, handleMonitor),
950
1023
  clawops_stacks_list: makeEntry(clawops_stacks_listSchema, clawops_stacks_listAnnotations, handleStacksList),
951
1024
  clawops_config_get: makeEntry(clawops_config_getSchema, clawops_config_getAnnotations, handleConfigGet),
952
1025
  clawops_agents_list: makeEntry(clawops_agents_listSchema, clawops_agents_listAnnotations, handleAgentsList),
@@ -1059,19 +1132,19 @@ function registerResources(server) {
1059
1132
  async (uri, { name }) => {
1060
1133
  const stackName = Array.isArray(name) ? name[0] : name;
1061
1134
  const filePath = path3.join(getConfigDir(), "state", `${stackName}.last-run.json`);
1062
- let text2;
1135
+ let text3;
1063
1136
  try {
1064
1137
  const raw = readFileSync2(filePath, "utf-8");
1065
1138
  const parsed = JSON.parse(raw);
1066
- text2 = parsed.output ?? "(no output recorded)";
1139
+ text3 = parsed.output ?? "(no output recorded)";
1067
1140
  } catch {
1068
- text2 = `No last-run output found for stack "${stackName}".`;
1141
+ text3 = `No last-run output found for stack "${stackName}".`;
1069
1142
  }
1070
1143
  return {
1071
1144
  contents: [{
1072
1145
  uri: uri.href,
1073
1146
  mimeType: "text/plain",
1074
- text: text2
1147
+ text: text3
1075
1148
  }]
1076
1149
  };
1077
1150
  }
@@ -1232,7 +1305,7 @@ If none of the above resolves the issue:
1232
1305
 
1233
1306
  // src/mcp/server.ts
1234
1307
  async function serveMcp(opts) {
1235
- const { version } = await import("./package-AFUTH62F.js");
1308
+ const { version } = await import("./package-WAJRGBBM.js");
1236
1309
  const server = new McpServer({ name: "clawops", version });
1237
1310
  registerTools(server, opts);
1238
1311
  registerResources(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawops/cli",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Deploy and manage self-hosted OpenClaw instances across clouds",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,6 +32,7 @@
32
32
  "@pulumi/docker": "^4.0.0",
33
33
  "@pulumi/gcp": "^7.0.0",
34
34
  "@pulumi/pulumi": "^3.0.0",
35
+ "@pulumi/random": "^4.20.0",
35
36
  "ajv": "^8.0.0",
36
37
  "ajv-formats": "^3.0.0",
37
38
  "chalk": "^5.0.0",
@@ -1,266 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- buildContext
4
- } from "./chunk-JDN6PLH2.js";
5
- import {
6
- acquireSession,
7
- drainPool
8
- } from "./chunk-ZVOEQCNW.js";
9
- import {
10
- OPENCLAW_CONFIG,
11
- atomicWriteConfig,
12
- restartGateway
13
- } from "./chunk-UDNZUSKA.js";
14
- import {
15
- StateError
16
- } from "./chunk-KGXPLI7W.js";
17
-
18
- // src/mcp/tools/_conn.ts
19
- async function resolveConn(ctx) {
20
- if (ctx.adapter.name === "local") {
21
- const state = ctx.localState;
22
- if (!state) throw new StateError("Stack has no local state \u2014 run `clawops up` first.");
23
- return {
24
- host: state.sshHost,
25
- port: state.sshPort,
26
- user: state.sshUser,
27
- privateKeyPath: state.privateKeyPath,
28
- knownHostsPath: state.knownHostsPath
29
- };
30
- }
31
- const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
32
- const stack = await ctx.getStack();
33
- const outputMap = await stack.outputs();
34
- const outputs = Object.fromEntries(
35
- Object.entries(outputMap).map(([k, v]) => [k, v.value])
36
- );
37
- if (!outputs["publicIp"]) {
38
- throw new StateError("Stack has no outputs \u2014 run `clawops up` first.");
39
- }
40
- const base = extractBaseOutputs(outputs);
41
- return ctx.adapter.getConnectionInfo({
42
- ...base,
43
- privateKeyPath: ctx.config.ssh.keyPath,
44
- knownHostsPath: ctx.config.ssh.knownHostsPath
45
- });
46
- }
47
- function errText(message) {
48
- return { content: [{ type: "text", text: message }], isError: true };
49
- }
50
- function okText(t) {
51
- return { content: [{ type: "text", text: t }] };
52
- }
53
-
54
- // src/mcp/tools/cli/config.ts
55
- var VALID_AUTH_MODES = /* @__PURE__ */ new Set(["none", "token", "password", "trusted-proxy"]);
56
- async function handleConfigGet(input, _server) {
57
- const ac = new AbortController();
58
- const ctx = buildContext({ stack: input.stackName });
59
- const conn = await resolveConn(ctx);
60
- const { session, release } = await acquireSession(conn);
61
- try {
62
- const result = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
63
- let cfg;
64
- try {
65
- cfg = JSON.parse(result.stdout);
66
- } catch {
67
- return errText(`Cannot parse ${OPENCLAW_CONFIG}: ${result.stderr || result.stdout}`);
68
- }
69
- const value = input.key ? getPath(cfg, input.key) : cfg;
70
- return okText(JSON.stringify(value, null, 2));
71
- } finally {
72
- release();
73
- drainPool();
74
- }
75
- }
76
- async function handleConfigSet(input, server) {
77
- const elicit = await server.server.elicitInput({
78
- message: `Set ${input.key} = ${input.value} on stack "${input.stackName ?? "default"}"?`,
79
- requestedSchema: {
80
- type: "object",
81
- properties: { confirmed: { type: "boolean", title: "Confirm config change" } },
82
- required: ["confirmed"]
83
- }
84
- });
85
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
86
- return okText("Config change cancelled.");
87
- }
88
- const ac = new AbortController();
89
- const ctx = buildContext({ stack: input.stackName });
90
- const conn = await resolveConn(ctx);
91
- const { session, release } = await acquireSession(conn);
92
- try {
93
- const readResult = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
94
- let cfg;
95
- try {
96
- cfg = JSON.parse(readResult.stdout);
97
- } catch {
98
- return errText(`Cannot parse ${OPENCLAW_CONFIG}: ${readResult.stderr}`);
99
- }
100
- let parsedValue = input.value;
101
- try {
102
- parsedValue = JSON.parse(input.value);
103
- } catch {
104
- }
105
- setPath(cfg, input.key, parsedValue);
106
- try {
107
- await atomicWriteConfig(session, cfg, ac.signal);
108
- } catch (err) {
109
- return errText(`Failed to write config: ${err.message}`);
110
- }
111
- let note = "";
112
- if (input.restart) {
113
- try {
114
- await restartGateway(session, ac.signal);
115
- note = " (gateway restarted)";
116
- } catch (err) {
117
- return errText(`Gateway restart failed: ${err.message}`);
118
- }
119
- }
120
- return okText(`Config set: ${input.key}${note}`);
121
- } finally {
122
- release();
123
- drainPool();
124
- }
125
- }
126
- async function handleConfigUnset(input, server) {
127
- const elicit = await server.server.elicitInput({
128
- message: `Remove config key "${input.key}" on stack "${input.stackName ?? "default"}"?`,
129
- requestedSchema: {
130
- type: "object",
131
- properties: { confirmed: { type: "boolean", title: "Confirm key removal" } },
132
- required: ["confirmed"]
133
- }
134
- });
135
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
136
- return okText("Config unset cancelled.");
137
- }
138
- const ac = new AbortController();
139
- const ctx = buildContext({ stack: input.stackName });
140
- const conn = await resolveConn(ctx);
141
- const { session, release } = await acquireSession(conn);
142
- try {
143
- const readResult = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
144
- let cfg;
145
- try {
146
- cfg = JSON.parse(readResult.stdout);
147
- } catch {
148
- return errText(`Cannot parse ${OPENCLAW_CONFIG}: ${readResult.stderr}`);
149
- }
150
- deletePath(cfg, input.key);
151
- try {
152
- await atomicWriteConfig(session, cfg, ac.signal);
153
- } catch (err) {
154
- return errText(`Failed to write config: ${err.message}`);
155
- }
156
- let note = "";
157
- if (input.restart) {
158
- try {
159
- await restartGateway(session, ac.signal);
160
- note = " (gateway restarted)";
161
- } catch (err) {
162
- return errText(`Gateway restart failed: ${err.message}`);
163
- }
164
- }
165
- return okText(`Config key removed: ${input.key}${note}`);
166
- } finally {
167
- release();
168
- drainPool();
169
- }
170
- }
171
- async function handleConfigValidate(input, _server) {
172
- const ac = new AbortController();
173
- const ctx = buildContext({ stack: input.stackName });
174
- const conn = await resolveConn(ctx);
175
- const { session, release } = await acquireSession(conn);
176
- try {
177
- const result = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
178
- let cfg;
179
- try {
180
- cfg = JSON.parse(result.stdout);
181
- } catch {
182
- return okText(JSON.stringify({ valid: false, issues: [`Invalid JSON: ${result.stderr || result.stdout}`] }));
183
- }
184
- const issues = validateOpenclawConfig(cfg);
185
- return okText(JSON.stringify({ valid: issues.length === 0, issues }));
186
- } finally {
187
- release();
188
- drainPool();
189
- }
190
- }
191
- function getPath(obj, dotKey) {
192
- return dotKey.split(".").reduce((cur, k) => {
193
- if (cur !== null && typeof cur === "object") return cur[k];
194
- return void 0;
195
- }, obj);
196
- }
197
- function setPath(obj, dotKey, value) {
198
- const keys = dotKey.split(".");
199
- let cur = obj;
200
- for (let i = 0; i < keys.length - 1; i++) {
201
- const k = keys[i];
202
- if (typeof cur[k] !== "object" || cur[k] === null) cur[k] = {};
203
- cur = cur[k];
204
- }
205
- cur[keys[keys.length - 1]] = value;
206
- }
207
- function deletePath(obj, dotKey) {
208
- const keys = dotKey.split(".");
209
- let cur = obj;
210
- for (let i = 0; i < keys.length - 1; i++) {
211
- const k = keys[i];
212
- if (typeof cur[k] !== "object" || cur[k] === null) return;
213
- cur = cur[k];
214
- }
215
- delete cur[keys[keys.length - 1]];
216
- }
217
- function validateOpenclawConfig(cfg) {
218
- const issues = [];
219
- if ("version" in cfg) {
220
- issues.push(
221
- "Top-level 'version' is not a valid OpenClaw config key. Use 'meta.lastTouchedVersion' (string) instead."
222
- );
223
- }
224
- if ("channels" in cfg && Array.isArray(cfg["channels"])) {
225
- issues.push(
226
- `'channels' must be an object keyed by provider name (e.g. {"discord":{...}}), not an array.`
227
- );
228
- }
229
- const meta = cfg["meta"];
230
- if (meta !== void 0 && (typeof meta !== "object" || Array.isArray(meta) || meta === null)) {
231
- issues.push("'meta' must be an object.");
232
- } else if (meta && typeof meta === "object") {
233
- const ltv = meta["lastTouchedVersion"];
234
- if (ltv !== void 0 && typeof ltv !== "string") {
235
- issues.push("'meta.lastTouchedVersion' must be a string.");
236
- }
237
- }
238
- const gateway = cfg["gateway"];
239
- if (gateway !== void 0 && typeof gateway === "object" && !Array.isArray(gateway) && gateway !== null) {
240
- const gw = gateway;
241
- if ("port" in gw && typeof gw["port"] !== "number") {
242
- issues.push("'gateway.port' must be a number.");
243
- }
244
- const auth = gw["auth"];
245
- if (auth !== void 0 && typeof auth === "object" && !Array.isArray(auth) && auth !== null) {
246
- const mode = auth["mode"];
247
- if (mode !== void 0 && !VALID_AUTH_MODES.has(mode)) {
248
- issues.push(
249
- `'gateway.auth.mode' must be one of: ${[...VALID_AUTH_MODES].join(", ")}. Got: "${mode}".`
250
- );
251
- }
252
- }
253
- }
254
- return issues;
255
- }
256
-
257
- export {
258
- resolveConn,
259
- errText,
260
- okText,
261
- handleConfigGet,
262
- handleConfigSet,
263
- handleConfigUnset,
264
- handleConfigValidate,
265
- validateOpenclawConfig
266
- };