@clawops/cli 1.6.0 → 1.7.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 CHANGED
@@ -12,6 +12,68 @@ and Cursor drive them through typed MCP tools with explicit safety controls.
12
12
 
13
13
  ---
14
14
 
15
+ ## What's new in v1.7.0
16
+
17
+ **`clawops harden`** — run an idempotent set of server-hardening modules against a deployed
18
+ stack over SSH. Each module has a read-only `check()` and an `apply()` step; `check()` runs first
19
+ and `apply()` is skipped when the box is already compliant. Sentinel files under
20
+ `/etc/clawops/hardening/` record what has been applied.
21
+
22
+ ```bash
23
+ clawops harden --list # show available modules
24
+ clawops harden --stack prod --dry-run # preview changes, apply nothing
25
+ clawops harden --stack prod # apply the default module set
26
+ clawops harden --stack prod --options ssh,ufw # apply a specific subset
27
+ ```
28
+
29
+ **Common modules (all providers), ON by default:** `ssh` (hardens `sshd_config`, guards against
30
+ lockout by verifying `authorized_keys` is non-empty before restarting sshd), `ufw` (deny-all
31
+ incoming, allow SSH + gateway 18789), `fail2ban` (SSH jail: 5 failures → 10-minute ban),
32
+ `unattended-upgrades` (security-only auto updates), and `docker-socket` (verify
33
+ `/var/run/docker.sock` is `root:docker 660`). **Opt-in:** `auditd`, `lynis` (CIS Level 1 scan →
34
+ `~/.clawops/reports/`), `sysctl`.
35
+
36
+ **AWS modules (check-only, ON by default):** `aws-sg-audit` (warns on `0.0.0.0/0` ingress on
37
+ unexpected ports) and `aws-ssm-check` (verifies `AmazonSSMManagedInstanceCore` for emergency SSM
38
+ shell access). **Opt-in:** `aws-flow-logs`, `aws-guardduty`.
39
+
40
+ `clawops setup` now presents a multi-select hardening step after deploy (skippable with
41
+ `--no-harden`), and `clawops doctor --stack <name>` gains a Hardening section showing which
42
+ modules are applied, missing, or drifted.
43
+
44
+ ---
45
+
46
+ ## What's new in v1.6.0
47
+
48
+ **`clawops bug`** — open a pre-filled GitHub issue with system context (version, Node, OS,
49
+ provider, stack count, SSH key presence) populated automatically. `--json` emits the URL without
50
+ prompting or opening a browser, suitable for scripting. `clawops doctor` now prints a `clawops bug`
51
+ hint in its footer when it exits with an error.
52
+
53
+ ```bash
54
+ clawops bug # open a pre-filled issue in the browser
55
+ clawops bug --json # print the issue URL for scripting
56
+ ```
57
+
58
+ **Cloud deploy bug fixes (AWS, GCP, Azure).** This release fixes 10 bugs found in a deploy audit,
59
+ several deploy-blocking:
60
+
61
+ - **Azure:** updated deprecated image reference `UbuntuServer/22.04-LTS` →
62
+ `0001-com-ubuntu-server-jammy/22_04-lts-gen2` — every Azure deployment was broken; and fixed a
63
+ malformed Key Vault `roleDefinitionId` that broke Key Vault RBAC entirely.
64
+ - **AWS:** Bedrock startup script now uses the IMDSv2 PUT→GET token flow (a plain `curl` to IMDS
65
+ returned 401, so the region always fell back to `us-east-1`); replaced
66
+ `AmazonBedrockFullAccess` with a least-privilege inline policy.
67
+ - **AWS (⚠️ migration impact):** migrated from inline Security Group ingress/egress arrays to
68
+ individual `SecurityGroupIngressRule`/`SecurityGroupEgressRule` resources so CIDR changes no
69
+ longer replace the whole Security Group. **Existing AWS stacks have their Security Group
70
+ replaced on the first `clawops up` after this upgrade** — see
71
+ `docs/decisions/0009-aws-sg-rule-resources.md` for the import-based mitigation.
72
+ - **All providers:** `accessMode=auto` egress-IP detection now returns a `Result` and throws a
73
+ clear error on failure instead of silently producing a VM with zero ingress rules.
74
+
75
+ ---
76
+
15
77
  ## What's new in v1.5.0
16
78
 
17
79
  **`clawops mcp wire`** — wire the OpenClaw gateway's own AI as an MCP client of clawops.
@@ -277,6 +339,7 @@ clawops down --yes # Destroy local-provider stack
277
339
  | `mcp install` | Interactively wire clawops into AI editors |
278
340
  | `mcp wire` | Wire the gateway's AI as an MCP client of clawops |
279
341
  | `help` | List all commands and global flags |
342
+ | `harden` | Apply security hardening to a deployed stack (SSH, UFW, fail2ban, unattended-upgrades, Docker socket; AWS: SG audit, SSM check, Flow Logs, GuardDuty) |
280
343
  | `bug` | Open a pre-filled GitHub issue with system context from `doctor` |
281
344
 
282
345
  Full flag reference: `clawops <command> --help`
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-QHGFENYR.js";
4
+ } from "./chunk-ISFHLA4G.js";
5
5
  import "./chunk-YTH4L2GN.js";
6
6
  import "./chunk-6ZFIFDBJ.js";
7
7
  import "./chunk-BOPSG2LI.js";
8
- import "./chunk-QI75OVJK.js";
9
8
  import "./chunk-ZFNPM2WG.js";
9
+ import "./chunk-3MFZ7E74.js";
10
10
  import "./chunk-A2I76FTA.js";
11
11
  import "./chunk-CX5SL5HP.js";
12
12
  import "./chunk-KGXPLI7W.js";
@@ -1,50 +1,27 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ OPENCLAW_CONFIG,
4
+ atomicWriteConfig,
5
+ restartGateway
6
+ } from "./chunk-ZFNPM2WG.js";
2
7
  import {
3
8
  buildContext
4
- } from "./chunk-QI75OVJK.js";
9
+ } from "./chunk-3MFZ7E74.js";
5
10
  import {
6
11
  acquireSession,
7
12
  drainPool
8
13
  } from "./chunk-LCKD7L7X.js";
9
14
  import {
10
- OPENCLAW_CONFIG,
11
- atomicWriteConfig,
12
- restartGateway
13
- } from "./chunk-ZFNPM2WG.js";
15
+ chalk,
16
+ failure
17
+ } from "./chunk-Q7NQY5HV.js";
14
18
  import {
15
19
  StateError
16
20
  } from "./chunk-KGXPLI7W.js";
17
21
 
18
22
  // src/cli/commands/monitor.ts
19
23
  import { defineCommand } from "citty";
20
- import process2 from "process";
21
-
22
- // src/output/human.ts
23
- import chalk from "chalk";
24
- import ora from "ora";
25
- function success(msg) {
26
- console.log(chalk.green("\u2713") + " " + msg);
27
- }
28
- function failure(msg) {
29
- console.error(chalk.red("\u2717") + " " + msg);
30
- }
31
- function warn(msg) {
32
- console.warn(chalk.yellow("\u26A0") + " " + msg);
33
- }
34
- function info(msg) {
35
- console.log(chalk.blue("\u2139") + " " + msg);
36
- }
37
- function spinner(text) {
38
- return ora(text).start();
39
- }
40
- var REPO_URL = "https://github.com/dfridkin/clawops";
41
- function printCta() {
42
- process.stdout.write(
43
- "\n" + chalk.dim(" Thank you for using clawops! If it has been useful, star the project:") + "\n" + chalk.dim(" " + REPO_URL) + "\n\n" + chalk.dim(" Found a bug? Open an issue:") + "\n" + chalk.dim(" " + REPO_URL + "/issues") + "\n\n"
44
- );
45
- }
46
-
47
- // src/cli/commands/monitor.ts
24
+ import process from "process";
48
25
  var GATEWAY_PORT = 18789;
49
26
  function formatUptime(startedAt) {
50
27
  if (!startedAt) return "\u2014";
@@ -154,7 +131,7 @@ function renderSnapshot(snap, opts) {
154
131
  return lines.join("\n");
155
132
  }
156
133
  async function probeEntries() {
157
- const { buildContext: buildContext2 } = await import("./context-KPKBOWOP.js");
134
+ const { buildContext: buildContext2 } = await import("./context-ALSJMTHE.js");
158
135
  const { getConfig } = await import("./store-SDUR52Z5.js");
159
136
  const config = getConfig();
160
137
  if (!config) return [];
@@ -234,7 +211,7 @@ async function deleteFromRegistry(name) {
234
211
  setConfig(updated);
235
212
  }
236
213
  async function runStackMenu(ac, setKeyHandler, noColor) {
237
- process2.stdout.write("\x1B[2J\x1B[H\n Checking stacks...\n");
214
+ process.stdout.write("\x1B[2J\x1B[H\n Checking stacks...\n");
238
215
  const entries = await probeEntries();
239
216
  if (ac.signal.aborted) return null;
240
217
  let selectedIdx = 0;
@@ -244,7 +221,7 @@ async function runStackMenu(ac, setKeyHandler, noColor) {
244
221
  return showAll ? entries : entries.filter((e) => e.deployed);
245
222
  }
246
223
  function redraw() {
247
- process2.stdout.write("\x1B[2J\x1B[H" + renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete));
224
+ process.stdout.write("\x1B[2J\x1B[H" + renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete));
248
225
  }
249
226
  redraw();
250
227
  return new Promise((resolve) => {
@@ -323,7 +300,7 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
323
300
  noColor: opts.noColor,
324
301
  menuMode
325
302
  });
326
- process2.stdout.write("\x1B[2J\x1B[H" + out + "\n");
303
+ process.stdout.write("\x1B[2J\x1B[H" + out + "\n");
327
304
  }
328
305
  async function doRefresh() {
329
306
  cancelRefresh();
@@ -333,7 +310,7 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
333
310
  redraw();
334
311
  } catch (err) {
335
312
  if (!ac.signal.aborted) {
336
- process2.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err instanceof Error ? err.message : String(err)) + "\n");
313
+ process.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err instanceof Error ? err.message : String(err)) + "\n");
337
314
  }
338
315
  }
339
316
  if (!ac.signal.aborted) scheduleRefresh();
@@ -379,20 +356,20 @@ var monitor_default = defineCommand({
379
356
  async run({ args }) {
380
357
  const intervalSec = Math.max(2, parseInt(String(args.interval ?? "10"), 10) || 10);
381
358
  const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
382
- const isTTY = Boolean(process2.stdout.isTTY);
359
+ const isTTY = Boolean(process.stdout.isTTY);
383
360
  const noColor = Boolean(args["no-color"]) || !isTTY;
384
- const { buildContext: buildContext2 } = await import("./context-KPKBOWOP.js");
361
+ const { buildContext: buildContext2 } = await import("./context-ALSJMTHE.js");
385
362
  const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-JZGKPP6K.js");
386
363
  const ac = new AbortController();
387
- process2.on("SIGINT", () => ac.abort());
388
- process2.on("SIGTERM", () => ac.abort());
364
+ process.on("SIGINT", () => ac.abort());
365
+ process.on("SIGTERM", () => ac.abort());
389
366
  if (args.stack) {
390
367
  const ctx = buildContext2(args);
391
368
  let conn;
392
369
  if (ctx.adapter.name === "local") {
393
370
  if (!ctx.localState) {
394
371
  failure("Stack is not bootstrapped. Run `clawops up` first.");
395
- process2.exit(4);
372
+ process.exit(4);
396
373
  }
397
374
  const ls = ctx.localState;
398
375
  conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
@@ -405,7 +382,7 @@ var monitor_default = defineCommand({
405
382
  );
406
383
  if (!outputs["publicIp"]) {
407
384
  failure("Stack has no outputs. Run `clawops up` first.");
408
- process2.exit(4);
385
+ process.exit(4);
409
386
  }
410
387
  const base = extractBaseOutputs(outputs);
411
388
  conn = ctx.adapter.getConnectionInfo({
@@ -418,7 +395,7 @@ var monitor_default = defineCommand({
418
395
  const { session: session2, release: release2 } = await acquireSession2({ ...conn, signal: ac.signal });
419
396
  try {
420
397
  const snap = await gatherSnapshot(session2, ac.signal, tailLines);
421
- process2.stdout.write(
398
+ process.stdout.write(
422
399
  renderSnapshot(snap, { stackName: ctx.stackName, intervalSec, showLogs: true, noColor }) + "\n"
423
400
  );
424
401
  } finally {
@@ -428,21 +405,21 @@ var monitor_default = defineCommand({
428
405
  return;
429
406
  }
430
407
  const { session, release } = await acquireSession2({ ...conn, signal: ac.signal });
431
- process2.stdin.setRawMode(true);
432
- process2.stdin.resume();
433
- process2.stdin.setEncoding("utf8");
434
- process2.stdout.write("\x1B[?25l");
408
+ process.stdin.setRawMode(true);
409
+ process.stdin.resume();
410
+ process.stdin.setEncoding("utf8");
411
+ process.stdout.write("\x1B[?25l");
435
412
  let keyHandler2 = () => {
436
413
  };
437
- process2.stdin.on("data", (key) => keyHandler2(key));
414
+ process.stdin.on("data", (key) => keyHandler2(key));
438
415
  try {
439
416
  await runDashboard(session, ctx.stackName, { intervalSec, tailLines, noColor }, ac, (fn) => {
440
417
  keyHandler2 = fn;
441
418
  }, false);
442
419
  } finally {
443
- process2.stdin.setRawMode(false);
444
- process2.stdin.pause();
445
- process2.stdout.write("\x1B[?25h\n");
420
+ process.stdin.setRawMode(false);
421
+ process.stdin.pause();
422
+ process.stdout.write("\x1B[?25h\n");
446
423
  release();
447
424
  drainPool2();
448
425
  }
@@ -450,15 +427,15 @@ var monitor_default = defineCommand({
450
427
  }
451
428
  if (!isTTY) {
452
429
  failure("Pass --stack <name> or run in a TTY for interactive stack selection.");
453
- process2.exit(2);
430
+ process.exit(2);
454
431
  }
455
- process2.stdin.setRawMode(true);
456
- process2.stdin.resume();
457
- process2.stdin.setEncoding("utf8");
458
- process2.stdout.write("\x1B[?25l");
432
+ process.stdin.setRawMode(true);
433
+ process.stdin.resume();
434
+ process.stdin.setEncoding("utf8");
435
+ process.stdout.write("\x1B[?25l");
459
436
  let keyHandler = () => {
460
437
  };
461
- process2.stdin.on("data", (key) => keyHandler(key));
438
+ process.stdin.on("data", (key) => keyHandler(key));
462
439
  try {
463
440
  while (!ac.signal.aborted) {
464
441
  const stackName = await runStackMenu(ac, (fn) => {
@@ -503,9 +480,9 @@ var monitor_default = defineCommand({
503
480
  }
504
481
  }
505
482
  } finally {
506
- process2.stdin.setRawMode(false);
507
- process2.stdin.pause();
508
- process2.stdout.write("\x1B[?25h\n");
483
+ process.stdin.setRawMode(false);
484
+ process.stdin.pause();
485
+ process.stdout.write("\x1B[?25h\n");
509
486
  }
510
487
  }
511
488
  });
@@ -750,14 +727,6 @@ function validateOpenclawConfig(cfg) {
750
727
  }
751
728
 
752
729
  export {
753
- chalk,
754
- success,
755
- failure,
756
- warn,
757
- info,
758
- spinner,
759
- REPO_URL,
760
- printCta,
761
730
  resolveConn,
762
731
  errText,
763
732
  okText,
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YTH4L2GN.js";
5
5
  import {
6
6
  buildContext
7
- } from "./chunk-QI75OVJK.js";
7
+ } from "./chunk-3MFZ7E74.js";
8
8
  import {
9
9
  getConfig
10
10
  } from "./chunk-CX5SL5HP.js";
@@ -45,7 +45,7 @@ async function generatePlan(intent, _opts) {
45
45
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
46
46
  );
47
47
  }
48
- const { version } = await import("./package-EW3FS257.js");
48
+ const { version } = await import("./package-OMPI6RMV.js");
49
49
  const config = getConfig();
50
50
  const instanceType = intent.instanceType ?? "small";
51
51
  const openclawVersion = intent.openclawVersion ?? "latest";
@@ -8,15 +8,15 @@ import {
8
8
  import {
9
9
  resolveSecrets
10
10
  } from "./chunk-BOPSG2LI.js";
11
- import {
12
- buildContext
13
- } from "./chunk-QI75OVJK.js";
14
11
  import {
15
12
  atomicWriteConfig,
16
13
  deepMerge,
17
14
  readRemoteConfig,
18
15
  restartGateway
19
16
  } from "./chunk-ZFNPM2WG.js";
17
+ import {
18
+ buildContext
19
+ } from "./chunk-3MFZ7E74.js";
20
20
  import {
21
21
  UsageError
22
22
  } from "./chunk-KGXPLI7W.js";
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/output/human.ts
4
+ import chalk from "chalk";
5
+ import ora from "ora";
6
+ function success(msg) {
7
+ console.log(chalk.green("\u2713") + " " + msg);
8
+ }
9
+ function failure(msg) {
10
+ console.error(chalk.red("\u2717") + " " + msg);
11
+ }
12
+ function warn(msg) {
13
+ console.warn(chalk.yellow("\u26A0") + " " + msg);
14
+ }
15
+ function info(msg) {
16
+ console.log(chalk.blue("\u2139") + " " + msg);
17
+ }
18
+ function spinner(text) {
19
+ return ora(text).start();
20
+ }
21
+ var REPO_URL = "https://github.com/dfridkin/clawops";
22
+ function printCta() {
23
+ process.stdout.write(
24
+ "\n" + chalk.dim(" Thank you for using clawops! If it has been useful, star the project:") + "\n" + chalk.dim(" " + REPO_URL) + "\n\n" + chalk.dim(" Found a bug? Open an issue:") + "\n" + chalk.dim(" " + REPO_URL + "/issues") + "\n\n"
25
+ );
26
+ }
27
+
28
+ export {
29
+ chalk,
30
+ success,
31
+ failure,
32
+ warn,
33
+ info,
34
+ spinner,
35
+ REPO_URL,
36
+ printCta
37
+ };