@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 +63 -0
- package/dist/{apply-SI3JKS4Z.js → apply-BTZFFGAD.js} +2 -2
- package/dist/{chunk-C5NDLOK2.js → chunk-CG3Y7Y2R.js} +39 -70
- package/dist/{chunk-72FF7IK2.js → chunk-FQ4JXUCR.js} +2 -2
- package/dist/{chunk-QHGFENYR.js → chunk-ISFHLA4G.js} +3 -3
- package/dist/chunk-Q7NQY5HV.js +37 -0
- package/dist/cli.js +273 -37
- package/dist/{context-KPKBOWOP.js → context-ALSJMTHE.js} +1 -1
- package/dist/{generate-YV7226Q7.js → generate-SPT7PJKR.js} +2 -2
- package/dist/harden-BDVYW567.js +748 -0
- package/dist/human-Z44CW7TR.js +21 -0
- package/dist/{package-EW3FS257.js → package-OMPI6RMV.js} +5 -1
- package/dist/{server-3DEZHUWB.js → server-RGLKKCCU.js} +7 -6
- package/package.json +5 -1
- /package/dist/{chunk-QI75OVJK.js → chunk-3MFZ7E74.js} +0 -0
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-
|
|
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-
|
|
9
|
+
} from "./chunk-3MFZ7E74.js";
|
|
5
10
|
import {
|
|
6
11
|
acquireSession,
|
|
7
12
|
drainPool
|
|
8
13
|
} from "./chunk-LCKD7L7X.js";
|
|
9
14
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
359
|
+
const isTTY = Boolean(process.stdout.isTTY);
|
|
383
360
|
const noColor = Boolean(args["no-color"]) || !isTTY;
|
|
384
|
-
const { buildContext: buildContext2 } = await import("./context-
|
|
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
|
-
|
|
388
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
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
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
-
|
|
430
|
+
process.exit(2);
|
|
454
431
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
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
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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-
|
|
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-
|
|
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
|
+
};
|