From 71203829d8596db03cc857d5cbea43f828787fe6 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 24 Jan 2026 04:01:45 +0000 Subject: [PATCH] feat: add system cli --- CHANGELOG.md | 1 + docs/automation/cron-jobs.md | 6 +- docs/automation/cron-vs-heartbeat.md | 2 +- docs/cli/index.md | 27 ++++-- docs/cli/system.md | 55 ++++++++++++ docs/cli/wake.md | 35 -------- docs/docs.json | 2 +- docs/gateway/heartbeat.md | 2 +- src/cli/cron-cli/register.ts | 3 - src/cli/cron-cli/register.wake.ts | 37 -------- src/cli/program/register.subclis.ts | 8 ++ src/cli/system-cli.ts | 125 +++++++++++++++++++++++++++ 12 files changed, 217 insertions(+), 86 deletions(-) create mode 100644 docs/cli/system.md delete mode 100644 docs/cli/wake.md delete mode 100644 src/cli/cron-cli/register.wake.ts create mode 100644 src/cli/system-cli.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3f4d913..12b567798 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.clawd.bot - Plugins: add optional llm-task JSON-only tool for workflows. (#1498) Thanks @vignesh07. - CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it. - CLI: add live auth probes to `clawdbot models status` for per-profile verification. +- CLI: add `clawdbot system` for system events + heartbeat controls; remove standalone `wake`. - Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. - Docs: add cron vs heartbeat decision guide (with Lobster workflow notes). (#1533) Thanks @JustYannicc. - Markdown: add per-channel table conversion (bullets for Signal/WhatsApp, code blocks elsewhere). (#1495) Thanks @odysseus0. diff --git a/docs/automation/cron-jobs.md b/docs/automation/cron-jobs.md index 6a19bb2f0..b75fd352e 100644 --- a/docs/automation/cron-jobs.md +++ b/docs/automation/cron-jobs.md @@ -263,15 +263,15 @@ Run history: clawdbot cron runs --id --limit 50 ``` -Immediate wake without creating a job: +Immediate system event without creating a job: ```bash -clawdbot wake --mode now --text "Next heartbeat: check battery." +clawdbot system event --mode now --text "Next heartbeat: check battery." ``` ## Gateway API surface - `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove` - `cron.run` (force or due), `cron.runs` -- `wake` (enqueue system event + optional heartbeat) +For immediate system events without a job, use [`clawdbot system event`](/cli/system). ## Troubleshooting diff --git a/docs/automation/cron-vs-heartbeat.md b/docs/automation/cron-vs-heartbeat.md index b617b7b9a..333a45d0b 100644 --- a/docs/automation/cron-vs-heartbeat.md +++ b/docs/automation/cron-vs-heartbeat.md @@ -271,4 +271,4 @@ clawdbot cron add \ - [Heartbeat](/gateway/heartbeat) - full heartbeat configuration - [Cron jobs](/automation/cron-jobs) - full cron CLI and API reference -- [Wake](/cli/wake) - manual wake command \ No newline at end of file +- [System](/cli/system) - system events + heartbeat controls diff --git a/docs/cli/index.md b/docs/cli/index.md index fcc013fdc..ce1c619d5 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -29,6 +29,7 @@ This page describes the current CLI behavior. If commands change, update this do - [`sessions`](/cli/sessions) - [`gateway`](/cli/gateway) - [`logs`](/cli/logs) +- [`system`](/cli/system) - [`models`](/cli/models) - [`memory`](/cli/memory) - [`nodes`](/cli/nodes) @@ -38,7 +39,6 @@ This page describes the current CLI behavior. If commands change, update this do - [`sandbox`](/cli/sandbox) - [`tui`](/cli/tui) - [`browser`](/cli/browser) -- [`wake`](/cli/wake) - [`cron`](/cli/cron) - [`dns`](/cli/dns) - [`docs`](/cli/docs) @@ -145,6 +145,10 @@ clawdbot [--dev] [--profile ] restart run logs + system + event + heartbeat last|enable|disable + presence models list status @@ -160,7 +164,6 @@ clawdbot [--dev] [--profile ] list recreate explain - wake cron status list @@ -763,9 +766,9 @@ Options: - `set`: `--provider `, `--agent `, `` - `clear`: `--provider `, `--agent ` -## Cron + wake +## System -### `wake` +### `system event` Enqueue a system event and optionally trigger a heartbeat (Gateway RPC). Required: @@ -776,7 +779,21 @@ Options: - `--json` - `--url`, `--token`, `--timeout`, `--expect-final` -### `cron` +### `system heartbeat last|enable|disable` +Heartbeat controls (Gateway RPC). + +Options: +- `--json` +- `--url`, `--token`, `--timeout`, `--expect-final` + +### `system presence` +List system presence entries (Gateway RPC). + +Options: +- `--json` +- `--url`, `--token`, `--timeout`, `--expect-final` + +## Cron Manage scheduled jobs (Gateway RPC). See [/automation/cron-jobs](/automation/cron-jobs). Subcommands: diff --git a/docs/cli/system.md b/docs/cli/system.md new file mode 100644 index 000000000..16f7fda1c --- /dev/null +++ b/docs/cli/system.md @@ -0,0 +1,55 @@ +--- +summary: "CLI reference for `clawdbot system` (system events, heartbeat, presence)" +read_when: + - You want to enqueue a system event without creating a cron job + - You need to enable or disable heartbeats + - You want to inspect system presence entries +--- + +# `clawdbot system` + +System-level helpers for the Gateway: enqueue system events, control heartbeats, +and view presence. + +## Common commands + +```bash +clawdbot system event --text "Check for urgent follow-ups" --mode now +clawdbot system heartbeat enable +clawdbot system heartbeat last +clawdbot system presence +``` + +## `system event` + +Enqueue a system event on the **main** session. The next heartbeat will inject +it as a `System:` line in the prompt. Use `--mode now` to trigger the heartbeat +immediately; `next-heartbeat` waits for the next scheduled tick. + +Flags: +- `--text `: required system event text. +- `--mode `: `now` or `next-heartbeat` (default). +- `--json`: machine-readable output. + +## `system heartbeat last|enable|disable` + +Heartbeat controls: +- `last`: show the last heartbeat event. +- `enable`: turn heartbeats back on (use this if they were disabled). +- `disable`: pause heartbeats. + +Flags: +- `--json`: machine-readable output. + +## `system presence` + +List the current system presence entries the Gateway knows about (nodes, +instances, and similar status lines). + +Flags: +- `--json`: machine-readable output. + +## Notes + +- Requires a running Gateway reachable by your current config (local or remote). +- System events are ephemeral and not persisted across restarts. diff --git a/docs/cli/wake.md b/docs/cli/wake.md deleted file mode 100644 index 04804afeb..000000000 --- a/docs/cli/wake.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -summary: "CLI reference for `clawdbot wake` (enqueue a system event and optionally trigger an immediate heartbeat)" -read_when: - - You want to “poke” a running Gateway to process a system event - - You use `wake` with cron jobs or remote nodes ---- - -# `clawdbot wake` - -Enqueue a system event on the Gateway and optionally trigger an immediate heartbeat. - -This is a lightweight “poke” for automation flows where you don’t want to run a full command, but you do want the Gateway to react quickly. - -Related: -- Cron jobs: [Cron](/cli/cron) -- Gateway heartbeat: [Heartbeat](/gateway/heartbeat) - -## Common commands - -```bash -clawdbot wake --text "sync" -clawdbot wake --text "sync" --mode now -``` - -## Flags - -- `--text `: system event text. -- `--mode `: `now` or `next-heartbeat` (default). -- `--json`: machine-readable output. - -## Notes - -- Requires a running Gateway reachable by your current config (local or remote). -- If you’re using sandboxing, `wake` still targets the Gateway; sandboxing does not block the command itself. - diff --git a/docs/docs.json b/docs/docs.json index 63f12ccfc..ea097b430 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -850,12 +850,12 @@ "cli/memory", "cli/models", "cli/logs", + "cli/system", "cli/nodes", "cli/approvals", "cli/gateway", "cli/tui", "cli/voicecall", - "cli/wake", "cli/cron", "cli/dns", "cli/docs", diff --git a/docs/gateway/heartbeat.md b/docs/gateway/heartbeat.md index c046cf22d..d691d5fbf 100644 --- a/docs/gateway/heartbeat.md +++ b/docs/gateway/heartbeat.md @@ -195,7 +195,7 @@ Safety note: don’t put secrets (API keys, phone numbers, private tokens) into You can enqueue a system event and trigger an immediate heartbeat with: ```bash -clawdbot wake --text "Check for urgent follow-ups" --mode now +clawdbot system event --text "Check for urgent follow-ups" --mode now ``` If multiple agents have `heartbeat` configured, a manual wake runs each of those diff --git a/src/cli/cron-cli/register.ts b/src/cli/cron-cli/register.ts index 43350d20c..7989e08fb 100644 --- a/src/cli/cron-cli/register.ts +++ b/src/cli/cron-cli/register.ts @@ -8,11 +8,8 @@ import { } from "./register.cron-add.js"; import { registerCronEditCommand } from "./register.cron-edit.js"; import { registerCronSimpleCommands } from "./register.cron-simple.js"; -import { registerWakeCommand } from "./register.wake.js"; export function registerCronCli(program: Command) { - registerWakeCommand(program); - const cron = program .command("cron") .description("Manage cron jobs (via Gateway)") diff --git a/src/cli/cron-cli/register.wake.ts b/src/cli/cron-cli/register.wake.ts deleted file mode 100644 index db9ce0600..000000000 --- a/src/cli/cron-cli/register.wake.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Command } from "commander"; -import { danger } from "../../globals.js"; -import { defaultRuntime } from "../../runtime.js"; -import { formatDocsLink } from "../../terminal/links.js"; -import { theme } from "../../terminal/theme.js"; -import type { GatewayRpcOpts } from "../gateway-rpc.js"; -import { addGatewayClientOptions, callGatewayFromCli } from "../gateway-rpc.js"; - -export function registerWakeCommand(program: Command) { - addGatewayClientOptions( - program - .command("wake") - .description("Enqueue a system event and optionally trigger an immediate heartbeat") - .requiredOption("--text ", "System event text") - .option("--mode ", "Wake mode (now|next-heartbeat)", "next-heartbeat") - .option("--json", "Output JSON", false), - ) - .addHelpText( - "after", - () => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/wake", "docs.clawd.bot/cli/wake")}\n`, - ) - .action(async (opts: GatewayRpcOpts & { text?: string; mode?: string }) => { - try { - const result = await callGatewayFromCli( - "wake", - opts, - { mode: opts.mode, text: opts.text }, - { expectFinal: false }, - ); - if (opts.json) defaultRuntime.log(JSON.stringify(result, null, 2)); - else defaultRuntime.log("ok"); - } catch (err) { - defaultRuntime.error(danger(String(err))); - defaultRuntime.exit(1); - } - }); -} diff --git a/src/cli/program/register.subclis.ts b/src/cli/program/register.subclis.ts index 26beb81e2..b4a65c794 100644 --- a/src/cli/program/register.subclis.ts +++ b/src/cli/program/register.subclis.ts @@ -60,6 +60,14 @@ const entries: SubCliEntry[] = [ mod.registerLogsCli(program); }, }, + { + name: "system", + description: "System events, heartbeat, and presence", + register: async (program) => { + const mod = await import("../system-cli.js"); + mod.registerSystemCli(program); + }, + }, { name: "models", description: "Model configuration", diff --git a/src/cli/system-cli.ts b/src/cli/system-cli.ts new file mode 100644 index 000000000..4162c16de --- /dev/null +++ b/src/cli/system-cli.ts @@ -0,0 +1,125 @@ +import type { Command } from "commander"; + +import { danger } from "../globals.js"; +import { defaultRuntime } from "../runtime.js"; +import { formatDocsLink } from "../terminal/links.js"; +import { theme } from "../terminal/theme.js"; +import type { GatewayRpcOpts } from "./gateway-rpc.js"; +import { addGatewayClientOptions, callGatewayFromCli } from "./gateway-rpc.js"; + +type SystemEventOpts = GatewayRpcOpts & { text?: string; mode?: string; json?: boolean }; + +const normalizeWakeMode = (raw: unknown) => { + const mode = typeof raw === "string" ? raw.trim() : ""; + if (!mode) return "next-heartbeat" as const; + if (mode === "now" || mode === "next-heartbeat") return mode; + throw new Error("--mode must be now or next-heartbeat"); +}; + +export function registerSystemCli(program: Command) { + const system = program + .command("system") + .description("System tools (events, heartbeat, presence)") + .addHelpText( + "after", + () => + `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/system", "docs.clawd.bot/cli/system")}\n`, + ); + + addGatewayClientOptions( + system + .command("event") + .description("Enqueue a system event and optionally trigger a heartbeat") + .requiredOption("--text ", "System event text") + .option("--mode ", "Wake mode (now|next-heartbeat)", "next-heartbeat") + .option("--json", "Output JSON", false), + ).action(async (opts: SystemEventOpts) => { + try { + const text = typeof opts.text === "string" ? opts.text.trim() : ""; + if (!text) throw new Error("--text is required"); + const mode = normalizeWakeMode(opts.mode); + const result = await callGatewayFromCli("wake", opts, { mode, text }, { expectFinal: false }); + if (opts.json) defaultRuntime.log(JSON.stringify(result, null, 2)); + else defaultRuntime.log("ok"); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }); + + const heartbeat = system.command("heartbeat").description("Heartbeat controls"); + + addGatewayClientOptions( + heartbeat + .command("last") + .description("Show the last heartbeat event") + .option("--json", "Output JSON", false), + ).action(async (opts: GatewayRpcOpts & { json?: boolean }) => { + try { + const result = await callGatewayFromCli("last-heartbeat", opts, undefined, { + expectFinal: false, + }); + defaultRuntime.log(JSON.stringify(result, null, 2)); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }); + + addGatewayClientOptions( + heartbeat + .command("enable") + .description("Enable heartbeats") + .option("--json", "Output JSON", false), + ).action(async (opts: GatewayRpcOpts & { json?: boolean }) => { + try { + const result = await callGatewayFromCli( + "set-heartbeats", + opts, + { enabled: true }, + { expectFinal: false }, + ); + defaultRuntime.log(JSON.stringify(result, null, 2)); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }); + + addGatewayClientOptions( + heartbeat + .command("disable") + .description("Disable heartbeats") + .option("--json", "Output JSON", false), + ).action(async (opts: GatewayRpcOpts & { json?: boolean }) => { + try { + const result = await callGatewayFromCli( + "set-heartbeats", + opts, + { enabled: false }, + { expectFinal: false }, + ); + defaultRuntime.log(JSON.stringify(result, null, 2)); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }); + + addGatewayClientOptions( + system + .command("presence") + .description("List system presence entries") + .option("--json", "Output JSON", false), + ).action(async (opts: GatewayRpcOpts & { json?: boolean }) => { + try { + const result = await callGatewayFromCli("system-presence", opts, undefined, { + expectFinal: false, + }); + defaultRuntime.log(JSON.stringify(result, null, 2)); + } catch (err) { + defaultRuntime.error(danger(String(err))); + defaultRuntime.exit(1); + } + }); +}