@astrofoundry/pi-astro 0.19.3 → 0.19.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astrofoundry/pi-astro",
3
- "version": "0.19.3",
3
+ "version": "0.19.4",
4
4
  "description": "Personal pi customizations (extensions, subagents, skills, prompts, themes) for the pi coding agent.",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -15,7 +15,7 @@ The `security` tool is read-only. Every call is a fixed command on the guest; `[
15
15
  | `attention` | JSON: `bouncer` (count, oldest pull age), `wazuh_agents` (active/total, list), `recent_high_alerts` (24 h, level ≥ 10), `health_issues` |
16
16
  | `agents` | `agent_control -l` |
17
17
  | `alerts <days> [ip]` | summary: `count`, `uniqueIps`, `repeatIps`, `daily`, `topIps` (10), `scenarios`, `latest` (20 compact alerts: `at`, `ip`, `scenario`, `events`, `decisions`, `country`, `as`); with `ip`, `latest` holds every alert of that address |
18
- | `decisions` | `count` and compact active decisions (`value`, `scope`, `type`, `duration`, `scenario`, `origin`) |
18
+ | `decisions` | `count` and one row per active decision (`value`, `scope`, `type`, `duration`, `scenario`, `origin`, `alertId`, `country`) |
19
19
  | `bouncers` | bouncers JSON (`name`, `last_pull`, `revoked`) |
20
20
  | `metrics` | `cscli metrics -o json` |
21
21
  | `wazuh-alerts <days> <minLevel>` | summary: count, groups by rule and agent, latest 20 (log line truncated to 200 chars) |
@@ -92,7 +92,7 @@ interface CrowdsecAlert {
92
92
  scenario?: string;
93
93
  events_count?: number;
94
94
  source?: { ip?: string; range?: string; as_number?: string; as_name?: string; cn?: string };
95
- decisions?: { type?: string; duration?: string; value?: string }[];
95
+ decisions?: CrowdsecDecision[];
96
96
  }
97
97
 
98
98
  interface CompactAlert {
@@ -188,17 +188,32 @@ interface CrowdsecDecision {
188
188
  origin?: string;
189
189
  }
190
190
 
191
- /** Keeps the fields an operator needs from `cscli decisions list -o json`. */
192
- export function compactDecisions(text: string): { count: number; decisions: CrowdsecDecision[] } {
193
- const decisions = parseJsonArray<CrowdsecDecision>(text, "decisions").map(({ id, value, scope, type, duration, scenario, origin }) => ({
194
- id,
195
- value,
196
- scope,
197
- type,
198
- duration,
199
- scenario,
200
- origin,
201
- }));
191
+ interface CompactDecision extends CrowdsecDecision {
192
+ alertId?: number;
193
+ country?: string;
194
+ }
195
+
196
+ /**
197
+ * `cscli decisions list -o json` returns alerts, each with its active decisions
198
+ * (the table's ALERT ID, COUNTRY, and AS columns are alert fields). Flattens to one row per decision.
199
+ */
200
+ export function compactDecisions(text: string): { count: number; decisions: CompactDecision[] } {
201
+ const decisions: CompactDecision[] = [];
202
+ for (const alert of parseJsonArray<CrowdsecAlert & { decisions?: CrowdsecDecision[] }>(text, "decisions")) {
203
+ for (const d of alert.decisions ?? []) {
204
+ decisions.push({
205
+ id: d.id,
206
+ value: d.value,
207
+ scope: d.scope,
208
+ type: d.type,
209
+ duration: d.duration,
210
+ scenario: d.scenario ?? alert.scenario,
211
+ origin: d.origin,
212
+ alertId: alert.id,
213
+ country: alert.source?.cn,
214
+ });
215
+ }
216
+ }
202
217
  return { count: decisions.length, decisions };
203
218
  }
204
219
 
@@ -244,9 +244,18 @@ describe("security wrapper", () => {
244
244
  expect(summary.latest[0]).toMatchObject({ id: 3, ip: "203.0.113.9", decisions: ["ban 4h"], country: "US" });
245
245
  expect(summariseAlerts(text, 7, "45.79.207.181").latest.map((a) => a.id)).toEqual([2, 1]);
246
246
  expect(summariseAlerts("null", 7)).toMatchObject({ count: 0, uniqueIps: 0 });
247
- expect(compactDecisions(JSON.stringify([{ id: 9, value: "203.0.113.9", scope: "Ip", type: "ban", duration: "3h59m", scenario: "x", origin: "crowdsec", extra: 1 }]))).toEqual({
247
+ const decisionsText = JSON.stringify([
248
+ {
249
+ id: 364,
250
+ scenario: "37pla/frontdoor-sni-scanning",
251
+ source: { ip: "203.0.113.9", cn: "US" },
252
+ decisions: [{ id: 9, value: "203.0.113.9", scope: "Ip", type: "ban", duration: "3h59m", origin: "crowdsec", simulated: false }],
253
+ },
254
+ { id: 365, scenario: "x", source: {}, decisions: [] },
255
+ ]);
256
+ expect(compactDecisions(decisionsText)).toEqual({
248
257
  count: 1,
249
- decisions: [{ id: 9, value: "203.0.113.9", scope: "Ip", type: "ban", duration: "3h59m", scenario: "x", origin: "crowdsec" }],
258
+ decisions: [{ id: 9, value: "203.0.113.9", scope: "Ip", type: "ban", duration: "3h59m", scenario: "37pla/frontdoor-sni-scanning", origin: "crowdsec", alertId: 364, country: "US" }],
250
259
  });
251
260
  expect(() => summariseAlerts("not json", 7)).toThrow(/no JSON/);
252
261
  });