@astrofoundry/pi-astro 0.29.0 → 0.29.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.
|
@@ -13,8 +13,15 @@ describe("approvals", () => {
|
|
|
13
13
|
expect(isRisky("identity", ["zitadel", "DELETE", "/v2/users/1"])).toBe(true);
|
|
14
14
|
expect(isRisky("identity", ["dmz", "deploy-config"])).toBe(true);
|
|
15
15
|
expect(isRisky("arcane", ["projects", "list"])).toBe(false);
|
|
16
|
-
expect(isRisky("arcane", ["projects", "redeploy", "mealie"])).toBe(
|
|
17
|
-
expect(isRisky("arcane", ["
|
|
16
|
+
expect(isRisky("arcane", ["projects", "redeploy", "mealie"])).toBe(false);
|
|
17
|
+
expect(isRisky("arcane", ["projects", "upgrade", "media", "sonarr", "--yes"])).toBe(false);
|
|
18
|
+
expect(isRisky("arcane", ["gitops", "sync", "arcane", "--yes"])).toBe(false);
|
|
19
|
+
expect(isRisky("arcane", ["projects", "upgrade", "--help"])).toBe(false);
|
|
20
|
+
expect(isRisky("arcane", ["projects", "down", "media"])).toBe(true);
|
|
21
|
+
expect(isRisky("arcane", ["--json", "projects", "delete", "media"])).toBe(true);
|
|
22
|
+
expect(isRisky("arcane", ["volumes", "remove", "mealie_mealie-pgdata"])).toBe(true);
|
|
23
|
+
expect(isRisky("arcane", ["system", "upgrade"])).toBe(true);
|
|
24
|
+
expect(isRisky("edge", ["vps", "reboot", "--confirm", "--help"])).toBe(false);
|
|
18
25
|
expect(isRisky("proxmox", ["guests"])).toBe(false);
|
|
19
26
|
expect(isRisky("proxmox", ["set", "107", "memory=768"])).toBe(true);
|
|
20
27
|
expect(isRisky("proxmox", ["shutdown", "107", "--confirm"])).toBe(true);
|
|
@@ -20,12 +20,13 @@ export const APPROVAL_TOKEN_ENV = "ASTRO_APPROVAL_TOKEN";
|
|
|
20
20
|
* the writes that do not need `--confirm` in a terminal session.
|
|
21
21
|
*/
|
|
22
22
|
export const RISKY: Readonly<Record<string, readonly RegExp[]>> = {
|
|
23
|
+
// Destructive or hard to undo only: upgrades, redeploys, restarts, and syncs are routine and run on the caller's request.
|
|
23
24
|
arcane: [
|
|
24
|
-
/^(projects|stacks)\s+(
|
|
25
|
-
/^containers\s+(
|
|
25
|
+
/^(projects|stacks)\s+(down|delete|remove|rm|destroy)\b/,
|
|
26
|
+
/^containers\s+(remove|rm|delete)\b/,
|
|
26
27
|
/^(images|volumes|networks)\s+(prune|remove|rm|delete)\b/,
|
|
27
28
|
/^system\s+(prune|upgrade)\b/,
|
|
28
|
-
/^gitops\s+
|
|
29
|
+
/^gitops\s+delete\b/,
|
|
29
30
|
],
|
|
30
31
|
identity: [/^zitadel\s+(DELETE|POST|PUT|PATCH)\b/, /^dmz\s+(deploy-config|pomerium-restart|render|lego-renew)\b/],
|
|
31
32
|
network: [/^unifi\s+policy-(create|update|delete)\b/],
|
|
@@ -37,8 +38,11 @@ export const RISKY: Readonly<Record<string, readonly RegExp[]>> = {
|
|
|
37
38
|
};
|
|
38
39
|
|
|
39
40
|
export function isRisky(service: string, args: readonly string[]): boolean {
|
|
41
|
+
// A help lookup prints usage and changes nothing, whatever command it names.
|
|
42
|
+
if (args.includes("--help") || args.includes("-h")) return false;
|
|
40
43
|
if (args.includes("--confirm")) return true;
|
|
41
|
-
|
|
44
|
+
// Flags placed before the command (`--json projects delete x`) must not hide it from the patterns.
|
|
45
|
+
const joined = args.filter((a) => !a.startsWith("-")).join(" ");
|
|
42
46
|
return (RISKY[service] ?? []).some((pattern) => pattern.test(joined));
|
|
43
47
|
}
|
|
44
48
|
|
package/package.json
CHANGED
package/specialists/AGENTS.md
CHANGED
|
@@ -73,7 +73,7 @@ Wrappers ship as TypeScript and run under Node 24 type stripping: erasable synta
|
|
|
73
73
|
|
|
74
74
|
## Chat bridges
|
|
75
75
|
|
|
76
|
-
`extensions/astro-discord/` runs specialists for Discord messages through `runAgent` and passes `ASTRO_APPROVAL_URL`/`ASTRO_APPROVAL_TOKEN` to the child. The gate consults `RISKY` in `extensions/specialist-gate/index.ts` and blocks on a ticket until an owner decides; keep `RISKY` in step with the wrappers when a new interrupting or destructive command appears (every `--confirm` command is covered by construction).
|
|
76
|
+
`extensions/astro-discord/` runs specialists for Discord messages through `runAgent` and passes `ASTRO_APPROVAL_URL`/`ASTRO_APPROVAL_TOKEN` to the child. The gate consults `RISKY` in `extensions/specialist-gate/index.ts` and blocks on a ticket until an owner decides (`--help`/`-h` never counts; flags are ignored when matching, so they cannot hide a command; Arcane lists only destructive actions since 2026-09-26, because a routine upgrade the caller asked for made them approve the same thing twice); keep `RISKY` in step with the wrappers when a new interrupting or destructive command appears (every `--confirm` command is covered by construction).
|
|
77
77
|
|
|
78
78
|
## Next
|
|
79
79
|
|
package/specialists/README.md
CHANGED
|
@@ -107,7 +107,7 @@ The `astro-discord` extension gives Discord users access to the specialists from
|
|
|
107
107
|
|
|
108
108
|
- Channels are configured one by one (`channels.<id>`): `trigger` is `always` (every message that names a specialist counts) or `mention` (only messages that start with `@Cortex` or reply to the bot); `specialists` is `*` or a list. A channel with one specialist needs no prefix: every message there is a task for it. In a channel with several, `dns list the zones` names the specialist; an addressed message without a name gets a reply asking for one. Threads inherit their parent channel's entry; unlisted channels are ignored. The bot replies at once with "<specialist> is working on your task" and shows the typing indicator until the answer replaces that reply. Reactions on your message: ⏳ running, 🔒 waiting for an owner's approval, ✅ done, ❌ failed. `help` shows the specialists you may use in that channel and the commands (`status` lists running tasks; owners in the admin channel also see the `config` commands). Each channel and specialist has one conversation: a reply to the bot's answer, or another task for that specialist within 30 minutes, continues it with everything the specialist saw before; otherwise a new one starts, and a task beginning with `new` forces that. Replied-to and forwarded messages (text, embeds, attachment names) travel with the task, so "check this" on a forwarded alert works. Image attachments on the message or on what it quotes (up to 4, 8 MiB each) are downloaded and passed to the specialist, which sees them; other file types are named only. Sessions live under `~/.config/astro-discord/sessions/<specialist>/` and are deleted after 7 days. Long answers arrive as a `.md` attachment. When the bridge restarts, open approval requests are marked cancelled and running tasks interrupted in Discord; ask again.
|
|
109
109
|
- Access: `owners` may use every specialist and decide approvals; `access.<specialist>` lists extra users for that specialist; everyone else gets no reply. Owners change the configuration from the admin channel (`adminChannelId`) without a shell: `config show`, `config channel <#channel> <always|mention> <*|dns,edge>`, `config channel <#channel> remove`, `config access <specialist> add|remove <@user>`; the bot writes `~/.config/astro-discord/config.json` on Cortex, which is also editable by hand and re-read on every message. Deny the bot's role View Channel on channels it must never read; that is the boundary the map cannot provide.
|
|
110
|
-
- Approvals: a risky call (anything with `--confirm`, plus writes such as record or zone deletes, Zitadel changes, Pomerium deploys, Arcane
|
|
110
|
+
- Approvals: a risky call (anything with `--confirm`, plus writes such as record or zone deletes, Zitadel changes, Pomerium deploys, service restarts, and on Arcane only destructive actions: `down`, deletes, prunes, image, volume, or network removal, `system upgrade`, `gitops delete`; help lookups never ask) pauses the specialist and posts Approve/Deny buttons; only owners' clicks count; no decision within `approvalTimeoutMinutes` denies it. The specialist then reports the denial. Ambiguous tasks get a question back instead of an action.
|
|
111
111
|
- Setup, as `cortex`: `d=$(mktemp -d) && cp ~/.pi/agent/npm/node_modules/@astrofoundry/pi-astro/extensions/astro-discord/*.ts "$d" && node --disable-warning=ExperimentalWarning "$d/setup.ts"` (Node does not strip types inside `node_modules`, so the files are copied first) prompts for the bot token and the ids, checks each against Discord, and writes `~/.config/astro-discord/{token,config.json,run.sh}` and the LaunchAgent; it prints the `launchctl bootstrap` line. After a pi-astro update restart the bridge with `launchctl kickstart -k gui/$(id -u)/com.astrofoundry.astro-discord`. Log: `~/Library/Logs/astro-discord.log`.
|
|
112
112
|
- Discord side: application `Cortex` with a bot, Message Content Intent enabled, invited with scopes `bot` and `applications.commands` and permissions View Channel, Send Messages, Attach Files, Read Message History, Add Reactions.
|
|
113
113
|
|