podwatch 1.0.2

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.
Files changed (51) hide show
  1. package/LICENSE +28 -0
  2. package/README.md +92 -0
  3. package/bin/podwatch.js +10 -0
  4. package/dist/classifier.d.ts +22 -0
  5. package/dist/classifier.d.ts.map +1 -0
  6. package/dist/classifier.js +157 -0
  7. package/dist/classifier.js.map +1 -0
  8. package/dist/hooks/cost.d.ts +26 -0
  9. package/dist/hooks/cost.d.ts.map +1 -0
  10. package/dist/hooks/cost.js +107 -0
  11. package/dist/hooks/cost.js.map +1 -0
  12. package/dist/hooks/lifecycle.d.ts +16 -0
  13. package/dist/hooks/lifecycle.d.ts.map +1 -0
  14. package/dist/hooks/lifecycle.js +273 -0
  15. package/dist/hooks/lifecycle.js.map +1 -0
  16. package/dist/hooks/security.d.ts +19 -0
  17. package/dist/hooks/security.d.ts.map +1 -0
  18. package/dist/hooks/security.js +128 -0
  19. package/dist/hooks/security.js.map +1 -0
  20. package/dist/hooks/sessions.d.ts +10 -0
  21. package/dist/hooks/sessions.d.ts.map +1 -0
  22. package/dist/hooks/sessions.js +53 -0
  23. package/dist/hooks/sessions.js.map +1 -0
  24. package/dist/index.d.ts +32 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +120 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/redact.d.ts +35 -0
  29. package/dist/redact.d.ts.map +1 -0
  30. package/dist/redact.js +372 -0
  31. package/dist/redact.js.map +1 -0
  32. package/dist/scanner.d.ts +27 -0
  33. package/dist/scanner.d.ts.map +1 -0
  34. package/dist/scanner.js +117 -0
  35. package/dist/scanner.js.map +1 -0
  36. package/dist/transmitter.d.ts +58 -0
  37. package/dist/transmitter.d.ts.map +1 -0
  38. package/dist/transmitter.js +654 -0
  39. package/dist/transmitter.js.map +1 -0
  40. package/dist/types.d.ts +116 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +9 -0
  43. package/dist/types.js.map +1 -0
  44. package/dist/updater.d.ts +168 -0
  45. package/dist/updater.d.ts.map +1 -0
  46. package/dist/updater.js +579 -0
  47. package/dist/updater.js.map +1 -0
  48. package/lib/installer.js +599 -0
  49. package/openclaw.plugin.json +59 -0
  50. package/package.json +56 -0
  51. package/skills/podwatch/SKILL.md +112 -0
package/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2026, Podwatch
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # Podwatch
2
+
3
+ Agent security monitoring, cost tracking, and budget enforcement for [OpenClaw](https://openclaw.ai).
4
+
5
+ ## Quick Install
6
+
7
+ ```bash
8
+ npx podwatch pw_your_api_key_here
9
+ ```
10
+
11
+ That's it. The installer will validate your key, install the plugin to `~/.openclaw/extensions/podwatch/`, configure it, and restart the gateway.
12
+
13
+ ## What You Get
14
+
15
+ - **Cost tracking** — per-call LLM cost and token monitoring
16
+ - **Budget enforcement** — block tool calls when spend exceeds limits
17
+ - **Security alerts** — detect risky tool usage patterns
18
+ - **Agent pulse** — online status and heartbeat monitoring
19
+ - **Dashboard** — [podwatch.app](https://podwatch.app)
20
+
21
+ ## Prerequisites
22
+
23
+ - **Node.js** ≥ 16
24
+ - **OpenClaw** ≥ v2026.2.0
25
+ - **Linux or macOS** (Windows: use WSL)
26
+ - A **Podwatch API key** — get one at [podwatch.app/dashboard](https://podwatch.app/dashboard)
27
+
28
+ ## Usage Modes
29
+
30
+ ### As an installer (npx)
31
+
32
+ ```bash
33
+ # Install with your API key
34
+ npx podwatch pw_abc123def456
35
+
36
+ # Show help
37
+ npx podwatch --help
38
+ ```
39
+
40
+ The installer:
41
+ 1. Validates your API key
42
+ 2. Checks OpenClaw is installed and compatible
43
+ 3. Copies the plugin to `~/.openclaw/extensions/podwatch/`
44
+ 4. Configures the plugin in `~/.openclaw/openclaw.json`
45
+ 5. Restarts the gateway to activate
46
+
47
+ ### As an OpenClaw plugin
48
+
49
+ The plugin is what gets installed to `~/.openclaw/extensions/podwatch/`. It hooks into OpenClaw's event system to capture diagnostics, enforce budgets, and send security alerts to the Podwatch dashboard.
50
+
51
+ Plugin source is in `src/` (TypeScript), compiled to `dist/`.
52
+
53
+ ## No `npx`? No problem.
54
+
55
+ ```bash
56
+ curl -sL https://podwatch.app/install.js | node - pw_your_api_key_here
57
+ ```
58
+
59
+ ## Development
60
+
61
+ ```bash
62
+ npm run build # Compile TypeScript
63
+ npm run dev # Watch mode
64
+ npm run clean # Remove dist/
65
+ ```
66
+
67
+ ## Configuration
68
+
69
+ The installer adds this to `~/.openclaw/openclaw.json`:
70
+
71
+ ```json
72
+ {
73
+ "diagnostics": { "enabled": true },
74
+ "plugins": {
75
+ "entries": {
76
+ "podwatch": {
77
+ "enabled": true,
78
+ "config": {
79
+ "apiKey": "pw_your_key",
80
+ "endpoint": "https://podwatch.app/api",
81
+ "enableBudgetEnforcement": true,
82
+ "enableSecurityAlerts": true
83
+ }
84
+ }
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ ## License
91
+
92
+ BSD-3-Clause
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const { run } = require('../lib/installer');
6
+
7
+ run().catch((err) => {
8
+ console.error(`\n❌ Unexpected error: ${err.message}`);
9
+ process.exit(1);
10
+ });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Tool classifier — lightweight client-side classification for real-time
3
+ * security decisions (exfiltration detection, persistence attempts).
4
+ *
5
+ * NOTE: Full risk-level classification remains server-side
6
+ * (podwatch-app/src/lib/risk-classifier.ts). This classifier only provides
7
+ * boolean flags needed for the before_tool_call hook to make blocking/alerting
8
+ * decisions in real time.
9
+ */
10
+ export interface ToolClassification {
11
+ /** Tool reads credential-like files (.env, .key, .pem, ssh keys). */
12
+ accessesCredentials: boolean;
13
+ /** Tool makes outbound network calls (web_fetch, curl, wget, http_request). */
14
+ makesNetworkCall: boolean;
15
+ /** Tool attempts to set up persistence (crontab, systemd, autostart). */
16
+ persistenceAttempt: boolean;
17
+ }
18
+ /**
19
+ * Classify a tool call for real-time security decisions.
20
+ */
21
+ export declare function classifyTool(toolName: string, params: Record<string, unknown>): ToolClassification;
22
+ //# sourceMappingURL=classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.d.ts","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,mBAAmB,EAAE,OAAO,CAAC;IAC7B,+EAA+E;IAC/E,gBAAgB,EAAE,OAAO,CAAC;IAC1B,yEAAyE;IACzE,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAmED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,kBAAkB,CAQpB"}
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * Tool classifier — lightweight client-side classification for real-time
4
+ * security decisions (exfiltration detection, persistence attempts).
5
+ *
6
+ * NOTE: Full risk-level classification remains server-side
7
+ * (podwatch-app/src/lib/risk-classifier.ts). This classifier only provides
8
+ * boolean flags needed for the before_tool_call hook to make blocking/alerting
9
+ * decisions in real time.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.classifyTool = classifyTool;
13
+ // ---------------------------------------------------------------------------
14
+ // Credential file patterns
15
+ // ---------------------------------------------------------------------------
16
+ const CREDENTIAL_FILE_PATTERNS = [
17
+ /\.env$/i,
18
+ /\.env\.[a-z]+$/i, // .env.local, .env.production, etc.
19
+ /\.key$/i,
20
+ /\.pem$/i,
21
+ /\.p12$/i,
22
+ /\.pfx$/i,
23
+ /\.jks$/i,
24
+ /\.keystore$/i,
25
+ /\.ssh\//i,
26
+ /id_rsa/i,
27
+ /id_ed25519/i,
28
+ /id_ecdsa/i,
29
+ /id_dsa/i,
30
+ /authorized_keys/i,
31
+ /known_hosts/i,
32
+ /\.gnupg\//i,
33
+ /\.aws\/credentials/i,
34
+ /\.netrc/i,
35
+ ];
36
+ // ---------------------------------------------------------------------------
37
+ // Network tools
38
+ // ---------------------------------------------------------------------------
39
+ const NETWORK_TOOLS = new Set([
40
+ "web_fetch",
41
+ "curl",
42
+ "wget",
43
+ "http_request",
44
+ "fetch",
45
+ "httpie",
46
+ ]);
47
+ // ---------------------------------------------------------------------------
48
+ // Persistence keywords (for bash/exec/spawn commands)
49
+ // ---------------------------------------------------------------------------
50
+ const PERSISTENCE_KEYWORDS = [
51
+ "crontab",
52
+ "systemd",
53
+ "systemctl",
54
+ "autostart",
55
+ "launchctl",
56
+ "launchd",
57
+ "at ", // `at` scheduler
58
+ "rc.local",
59
+ ".bashrc",
60
+ ".bash_profile",
61
+ ".profile",
62
+ ".zshrc",
63
+ "init.d",
64
+ "cron.d",
65
+ ];
66
+ const EXEC_TOOLS = new Set(["bash", "exec", "spawn", "shell", "run"]);
67
+ // ---------------------------------------------------------------------------
68
+ // Public API
69
+ // ---------------------------------------------------------------------------
70
+ /**
71
+ * Classify a tool call for real-time security decisions.
72
+ */
73
+ function classifyTool(toolName, params) {
74
+ const name = toolName.toLowerCase();
75
+ return {
76
+ accessesCredentials: checkAccessesCredentials(name, params),
77
+ makesNetworkCall: checkMakesNetworkCall(name, params),
78
+ persistenceAttempt: checkPersistenceAttempt(name, params),
79
+ };
80
+ }
81
+ // ---------------------------------------------------------------------------
82
+ // Helpers
83
+ // ---------------------------------------------------------------------------
84
+ /** Extract command string from exec-like tool params. */
85
+ function extractCommand(params) {
86
+ return ((typeof params.command === "string" && params.command) ||
87
+ (typeof params.cmd === "string" && params.cmd) ||
88
+ (typeof params.script === "string" && params.script) ||
89
+ "");
90
+ }
91
+ // ---------------------------------------------------------------------------
92
+ // Internal checks
93
+ // ---------------------------------------------------------------------------
94
+ function checkAccessesCredentials(toolName, params) {
95
+ // Direct file-reading tools: check path params
96
+ if (toolName === "read" || toolName === "read_file" || toolName === "cat") {
97
+ const filePath = (typeof params.path === "string" && params.path) ||
98
+ (typeof params.file_path === "string" && params.file_path) ||
99
+ (typeof params.file === "string" && params.file) ||
100
+ "";
101
+ if (!filePath)
102
+ return false;
103
+ return CREDENTIAL_FILE_PATTERNS.some((pattern) => pattern.test(filePath));
104
+ }
105
+ // Exec-like tools: parse command for credential file paths
106
+ // Check each token/word individually since patterns use anchors (e.g. \.env$)
107
+ if (EXEC_TOOLS.has(toolName)) {
108
+ const command = extractCommand(params);
109
+ if (!command)
110
+ return false;
111
+ // Split on whitespace; also strip common prefixes like @ (curl's file ref)
112
+ const tokens = command.split(/\s+/).map((t) => t.replace(/^[@<>]+/, ""));
113
+ return tokens.some((token) => CREDENTIAL_FILE_PATTERNS.some((pattern) => pattern.test(token)));
114
+ }
115
+ return false;
116
+ }
117
+ // Network tool names that may appear as commands inside exec
118
+ const EXEC_NETWORK_COMMANDS = [
119
+ "curl",
120
+ "wget",
121
+ "nc",
122
+ "ncat",
123
+ "ssh",
124
+ "scp",
125
+ "rsync",
126
+ "fetch",
127
+ "http", // httpie
128
+ "httpie",
129
+ ];
130
+ function checkMakesNetworkCall(toolName, params) {
131
+ // Direct network tools
132
+ if (NETWORK_TOOLS.has(toolName))
133
+ return true;
134
+ // Exec-like tools: parse command for network tool invocations
135
+ if (EXEC_TOOLS.has(toolName)) {
136
+ const command = extractCommand(params);
137
+ if (!command)
138
+ return false;
139
+ const lower = command.toLowerCase();
140
+ return EXEC_NETWORK_COMMANDS.some((cmd) => {
141
+ // Match as a standalone word (start of command, after pipe, after &&, etc.)
142
+ const re = new RegExp(`(?:^|[|;&\\s])${cmd}(?:\\s|$)`);
143
+ return re.test(lower);
144
+ });
145
+ }
146
+ return false;
147
+ }
148
+ function checkPersistenceAttempt(toolName, params) {
149
+ if (!EXEC_TOOLS.has(toolName))
150
+ return false;
151
+ const command = extractCommand(params);
152
+ if (!command)
153
+ return false;
154
+ const lower = command.toLowerCase();
155
+ return PERSISTENCE_KEYWORDS.some((kw) => lower.includes(kw));
156
+ }
157
+ //# sourceMappingURL=classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classifier.js","sourceRoot":"","sources":["../src/classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Cost handler — extracts token/cost data from before_agent_start messages.
3
+ *
4
+ * The plugin-sdk's onDiagnosticEvent is broken (separate module instances).
5
+ * Instead, we use before_agent_start which provides the full session history
6
+ * with usage data on every assistant message:
7
+ * { provider, model, usage: { input, output, cacheRead, cacheWrite, totalTokens, cost: { total } } }
8
+ *
9
+ * Dedup strategy: lastSeenIndex tracks how far we've read into event.messages.
10
+ * Each invocation only processes messages from lastSeenIndex onwards, then
11
+ * advances the pointer. Zero memory growth, O(1) bookkeeping.
12
+ */
13
+ import type { PodwatchConfig } from "../index.js";
14
+ /**
15
+ * Reset all dedup state (exported for testing).
16
+ */
17
+ export declare function _resetCostState(): void;
18
+ /**
19
+ * Clear a single session's index (call on session_end to prevent memory leaks).
20
+ */
21
+ export declare function _clearSessionIndex(sessionKey: string): void;
22
+ /**
23
+ * Register the cost handler via before_agent_start hook.
24
+ */
25
+ export declare function registerCostHandler(api: any, config: PodwatchConfig, diagnosticsEnabled: boolean): void;
26
+ //# sourceMappingURL=cost.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.d.ts","sourceRoot":"","sources":["../../src/hooks/cost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAMlD;;GAEG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,GAAG,EACR,MAAM,EAAE,cAAc,EACtB,kBAAkB,EAAE,OAAO,GAC1B,IAAI,CA4EN"}
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ /**
3
+ * Cost handler — extracts token/cost data from before_agent_start messages.
4
+ *
5
+ * The plugin-sdk's onDiagnosticEvent is broken (separate module instances).
6
+ * Instead, we use before_agent_start which provides the full session history
7
+ * with usage data on every assistant message:
8
+ * { provider, model, usage: { input, output, cacheRead, cacheWrite, totalTokens, cost: { total } } }
9
+ *
10
+ * Dedup strategy: lastSeenIndex tracks how far we've read into event.messages.
11
+ * Each invocation only processes messages from lastSeenIndex onwards, then
12
+ * advances the pointer. Zero memory growth, O(1) bookkeeping.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports._resetCostState = _resetCostState;
16
+ exports._clearSessionIndex = _clearSessionIndex;
17
+ exports.registerCostHandler = registerCostHandler;
18
+ const transmitter_js_1 = require("../transmitter.js");
19
+ // Track how far into event.messages we've already processed — per session
20
+ const lastSeenIndexMap = new Map();
21
+ /**
22
+ * Reset all dedup state (exported for testing).
23
+ */
24
+ function _resetCostState() {
25
+ lastSeenIndexMap.clear();
26
+ }
27
+ /**
28
+ * Clear a single session's index (call on session_end to prevent memory leaks).
29
+ */
30
+ function _clearSessionIndex(sessionKey) {
31
+ lastSeenIndexMap.delete(sessionKey);
32
+ }
33
+ /**
34
+ * Register the cost handler via before_agent_start hook.
35
+ */
36
+ function registerCostHandler(api, config, diagnosticsEnabled) {
37
+ // before_agent_start carries the full message history with usage on each assistant turn
38
+ api.on("before_agent_start", async (event, ctx) => {
39
+ if (!event?.messages || !Array.isArray(event.messages))
40
+ return;
41
+ const sessionKey = ctx?.sessionKey ?? "__default__";
42
+ // Get per-session index (defaults to 0 for new sessions)
43
+ let lastSeenIndex = lastSeenIndexMap.get(sessionKey) ?? 0;
44
+ // Bounds check: if messages were compacted, reset to 0
45
+ if (lastSeenIndex > event.messages.length) {
46
+ lastSeenIndex = 0;
47
+ }
48
+ // Only process new messages since last invocation
49
+ const newMessages = event.messages.slice(lastSeenIndex);
50
+ lastSeenIndexMap.set(sessionKey, event.messages.length);
51
+ if (newMessages.length === 0)
52
+ return;
53
+ // Detect heartbeat-triggered turns by scanning the last user message
54
+ // OpenClaw heartbeat prompts always contain "HEARTBEAT" (e.g. "Read HEARTBEAT.md")
55
+ let isHeartbeat = false;
56
+ for (let i = event.messages.length - 1; i >= 0; i--) {
57
+ const m = event.messages[i];
58
+ if (m?.role === "user") {
59
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content ?? "");
60
+ if (/HEARTBEAT/i.test(text)) {
61
+ isHeartbeat = true;
62
+ }
63
+ break; // only check the last user message
64
+ }
65
+ }
66
+ for (const msg of newMessages) {
67
+ // Only assistant messages have usage data
68
+ if (msg.role !== "assistant")
69
+ continue;
70
+ if (!msg.usage)
71
+ continue;
72
+ // Skip zero-cost internal messages (delivery-mirror, etc.)
73
+ if (msg.provider === "openclaw" || msg.model === "delivery-mirror")
74
+ continue;
75
+ if (msg.usage.totalTokens === 0 && !msg.usage.input && !msg.usage.output)
76
+ continue;
77
+ const costTotal = msg.usage.cost?.total ?? undefined;
78
+ transmitter_js_1.transmitter.enqueue({
79
+ type: "cost",
80
+ ts: msg.timestamp ?? Date.now(),
81
+ sessionKey: ctx?.sessionKey,
82
+ agentId: ctx?.agentId,
83
+ provider: msg.provider,
84
+ model: msg.model,
85
+ inputTokens: msg.usage.input ?? 0,
86
+ outputTokens: msg.usage.output ?? 0,
87
+ cacheReadTokens: msg.usage.cacheRead ?? 0,
88
+ cacheWriteTokens: msg.usage.cacheWrite ?? 0,
89
+ totalTokens: msg.usage.totalTokens ?? ((msg.usage.input ?? 0) + (msg.usage.output ?? 0)),
90
+ costUsd: costTotal,
91
+ costBreakdown: msg.usage.cost, // full {input, output, cacheRead, cacheWrite, total} object
92
+ durationMs: undefined,
93
+ // Tag heartbeat-triggered cost events so the dashboard can distinguish them
94
+ ...(isHeartbeat ? { sessionType: "heartbeat" } : {}),
95
+ });
96
+ }
97
+ }, { name: "podwatch-cost" });
98
+ // Clean up session index on session end to prevent memory leaks
99
+ api.on("session_end", async (_event, ctx) => {
100
+ const sessionKey = ctx?.sessionKey;
101
+ if (sessionKey) {
102
+ lastSeenIndexMap.delete(sessionKey);
103
+ }
104
+ }, { name: "podwatch-cost-cleanup" });
105
+ api.logger.info("[podwatch/cost] Cost tracking via before_agent_start message history");
106
+ }
107
+ //# sourceMappingURL=cost.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost.js","sourceRoot":"","sources":["../../src/hooks/cost.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Lifecycle handlers — gateway_start, gateway_stop, before_compaction.
3
+ *
4
+ * register(): starts pulse interval + initial skill/plugin scan
5
+ * gateway_stop: flushes pending events, stops intervals
6
+ * before_compaction: tracks context window pressure
7
+ *
8
+ * Pulse = Podwatch plugin's lightweight alive-ping (direct HTTP, no LLM cost).
9
+ * This is NOT OpenClaw's agent heartbeat (which triggers LLM turns).
10
+ */
11
+ import type { PodwatchConfig } from "../index.js";
12
+ /**
13
+ * Register lifecycle hook handlers.
14
+ */
15
+ export declare function registerLifecycleHandlers(api: any, config: PodwatchConfig): void;
16
+ //# sourceMappingURL=lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/hooks/lifecycle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAwElD;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,cAAc,GAAG,IAAI,CAwHhF"}