pulseed 0.1.3 → 0.1.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.
Files changed (127) hide show
  1. package/dist/chat/chat-history.d.ts +80 -0
  2. package/dist/chat/chat-history.d.ts.map +1 -0
  3. package/dist/chat/chat-history.js +70 -0
  4. package/dist/chat/chat-history.js.map +1 -0
  5. package/dist/chat/chat-runner.d.ts +46 -0
  6. package/dist/chat/chat-runner.d.ts.map +1 -0
  7. package/dist/chat/chat-runner.js +141 -0
  8. package/dist/chat/chat-runner.js.map +1 -0
  9. package/dist/chat/escalation.d.ts +30 -0
  10. package/dist/chat/escalation.d.ts.map +1 -0
  11. package/dist/chat/escalation.js +53 -0
  12. package/dist/chat/escalation.js.map +1 -0
  13. package/dist/cli/commands/chat.d.ts +3 -0
  14. package/dist/cli/commands/chat.d.ts.map +1 -0
  15. package/dist/cli/commands/chat.js +148 -0
  16. package/dist/cli/commands/chat.js.map +1 -0
  17. package/dist/cli/commands/daemon.d.ts.map +1 -1
  18. package/dist/cli/commands/daemon.js +77 -14
  19. package/dist/cli/commands/daemon.js.map +1 -1
  20. package/dist/cli/commands/doctor.d.ts +19 -0
  21. package/dist/cli/commands/doctor.d.ts.map +1 -0
  22. package/dist/cli/commands/doctor.js +203 -0
  23. package/dist/cli/commands/doctor.js.map +1 -0
  24. package/dist/cli/commands/goal-utils.d.ts.map +1 -1
  25. package/dist/cli/commands/goal-utils.js +1 -0
  26. package/dist/cli/commands/goal-utils.js.map +1 -1
  27. package/dist/cli/commands/install.d.ts +16 -0
  28. package/dist/cli/commands/install.d.ts.map +1 -0
  29. package/dist/cli/commands/install.js +153 -0
  30. package/dist/cli/commands/install.js.map +1 -0
  31. package/dist/cli/commands/logs.d.ts +2 -0
  32. package/dist/cli/commands/logs.d.ts.map +1 -0
  33. package/dist/cli/commands/logs.js +201 -0
  34. package/dist/cli/commands/logs.js.map +1 -0
  35. package/dist/cli/commands/notify.d.ts +2 -0
  36. package/dist/cli/commands/notify.d.ts.map +1 -0
  37. package/dist/cli/commands/notify.js +277 -0
  38. package/dist/cli/commands/notify.js.map +1 -0
  39. package/dist/cli/setup.d.ts.map +1 -1
  40. package/dist/cli/setup.js +14 -4
  41. package/dist/cli/setup.js.map +1 -1
  42. package/dist/cli/utils.d.ts.map +1 -1
  43. package/dist/cli/utils.js +10 -0
  44. package/dist/cli/utils.js.map +1 -1
  45. package/dist/cli-runner.d.ts.map +1 -1
  46. package/dist/cli-runner.js +43 -2
  47. package/dist/cli-runner.js.map +1 -1
  48. package/dist/core-loop.d.ts +1 -1
  49. package/dist/core-loop.d.ts.map +1 -1
  50. package/dist/core-loop.js +7 -3
  51. package/dist/core-loop.js.map +1 -1
  52. package/dist/drive/drive-system.d.ts.map +1 -1
  53. package/dist/drive/drive-system.js +11 -2
  54. package/dist/drive/drive-system.js.map +1 -1
  55. package/dist/execution/issue-context-fetcher.d.ts +19 -0
  56. package/dist/execution/issue-context-fetcher.d.ts.map +1 -0
  57. package/dist/execution/issue-context-fetcher.js +73 -0
  58. package/dist/execution/issue-context-fetcher.js.map +1 -0
  59. package/dist/execution/session-manager.d.ts.map +1 -1
  60. package/dist/execution/session-manager.js +3 -0
  61. package/dist/execution/session-manager.js.map +1 -1
  62. package/dist/execution/task-generation.d.ts.map +1 -1
  63. package/dist/execution/task-generation.js +8 -0
  64. package/dist/execution/task-generation.js.map +1 -1
  65. package/dist/execution/task-lifecycle.d.ts.map +1 -1
  66. package/dist/execution/task-lifecycle.js +4 -1
  67. package/dist/execution/task-lifecycle.js.map +1 -1
  68. package/dist/execution/task-prompt-builder.d.ts.map +1 -1
  69. package/dist/execution/task-prompt-builder.js +30 -1
  70. package/dist/execution/task-prompt-builder.js.map +1 -1
  71. package/dist/execution/task-verifier.d.ts.map +1 -1
  72. package/dist/execution/task-verifier.js +16 -2
  73. package/dist/execution/task-verifier.js.map +1 -1
  74. package/dist/goal/goal-refiner.js +1 -1
  75. package/dist/goal/goal-refiner.js.map +1 -1
  76. package/dist/goal/goal-tree-manager.d.ts.map +1 -1
  77. package/dist/goal/goal-tree-manager.js +6 -0
  78. package/dist/goal/goal-tree-manager.js.map +1 -1
  79. package/dist/goal/refiner-prompts.d.ts.map +1 -1
  80. package/dist/goal/refiner-prompts.js +5 -1
  81. package/dist/goal/refiner-prompts.js.map +1 -1
  82. package/dist/goal/tree-loop-orchestrator.d.ts +3 -1
  83. package/dist/goal/tree-loop-orchestrator.d.ts.map +1 -1
  84. package/dist/goal/tree-loop-orchestrator.js +4 -4
  85. package/dist/goal/tree-loop-orchestrator.js.map +1 -1
  86. package/dist/loop/core-loop-phases.d.ts +1 -1
  87. package/dist/loop/core-loop-phases.d.ts.map +1 -1
  88. package/dist/loop/core-loop-phases.js +9 -3
  89. package/dist/loop/core-loop-phases.js.map +1 -1
  90. package/dist/loop/tree-loop-runner.d.ts.map +1 -1
  91. package/dist/loop/tree-loop-runner.js +10 -7
  92. package/dist/loop/tree-loop-runner.js.map +1 -1
  93. package/dist/observation/context-provider.d.ts +10 -0
  94. package/dist/observation/context-provider.d.ts.map +1 -1
  95. package/dist/observation/context-provider.js +35 -0
  96. package/dist/observation/context-provider.js.map +1 -1
  97. package/dist/observation/observation-engine.d.ts.map +1 -1
  98. package/dist/observation/observation-engine.js +1 -0
  99. package/dist/observation/observation-engine.js.map +1 -1
  100. package/dist/observation/observation-llm.d.ts.map +1 -1
  101. package/dist/observation/observation-llm.js +5 -1
  102. package/dist/observation/observation-llm.js.map +1 -1
  103. package/dist/observation/workspace-context.d.ts.map +1 -1
  104. package/dist/observation/workspace-context.js +59 -12
  105. package/dist/observation/workspace-context.js.map +1 -1
  106. package/dist/runtime/hook-manager.d.ts.map +1 -1
  107. package/dist/runtime/hook-manager.js +7 -0
  108. package/dist/runtime/hook-manager.js.map +1 -1
  109. package/dist/tui/entry.d.ts.map +1 -1
  110. package/dist/tui/entry.js +19 -1
  111. package/dist/tui/entry.js.map +1 -1
  112. package/dist/types/daemon.js +1 -1
  113. package/dist/types/daemon.js.map +1 -1
  114. package/dist/types/goal-refiner.d.ts +3 -0
  115. package/dist/types/goal-refiner.d.ts.map +1 -1
  116. package/dist/types/goal-refiner.js +1 -0
  117. package/dist/types/goal-refiner.js.map +1 -1
  118. package/dist/types/mcp.d.ts +6 -6
  119. package/dist/types/session.d.ts +4 -4
  120. package/dist/types/session.d.ts.map +1 -1
  121. package/dist/types/session.js +1 -0
  122. package/dist/types/session.js.map +1 -1
  123. package/dist/utils/execFileNoThrow.d.ts +20 -0
  124. package/dist/utils/execFileNoThrow.d.ts.map +1 -0
  125. package/dist/utils/execFileNoThrow.js +33 -0
  126. package/dist/utils/execFileNoThrow.js.map +1 -0
  127. package/package.json +1 -1
@@ -0,0 +1,153 @@
1
+ // ─── pulseed install / uninstall (macOS launchd integration) ───
2
+ import { parseArgs } from "node:util";
3
+ import * as fs from "node:fs";
4
+ import * as path from "node:path";
5
+ import * as os from "node:os";
6
+ import { fileURLToPath } from "node:url";
7
+ import { execFileNoThrow } from "../../utils/execFileNoThrow.js";
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const PLIST_LABEL = "com.pulseed.daemon";
11
+ const PLIST_PATH = path.join(os.homedir(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
12
+ /** Build the plist XML string from the given parameters. */
13
+ export function buildPlist(opts) {
14
+ const programArgs = [opts.nodePath, opts.cliRunnerPath, "start"];
15
+ for (const id of opts.goalIds) {
16
+ programArgs.push("--goal", id);
17
+ }
18
+ if (opts.configPath) {
19
+ programArgs.push("--config", opts.configPath);
20
+ }
21
+ if (opts.intervalMs !== undefined) {
22
+ programArgs.push("--check-interval-ms", String(opts.intervalMs));
23
+ }
24
+ const argEntries = programArgs
25
+ .map((a) => `\t\t<string>${escapeXml(a)}</string>`)
26
+ .join("\n");
27
+ const envEntries = [];
28
+ if (opts.envPath) {
29
+ envEntries.push(`\t\t<key>PATH</key>\n\t\t<string>${escapeXml(opts.envPath)}</string>`);
30
+ }
31
+ if (opts.pulseedHome) {
32
+ envEntries.push(`\t\t<key>PULSEED_HOME</key>\n\t\t<string>${escapeXml(opts.pulseedHome)}</string>`);
33
+ }
34
+ const envBlock = envEntries.length > 0
35
+ ? `\t<key>EnvironmentVariables</key>\n\t<dict>\n${envEntries.join("\n")}\n\t</dict>\n`
36
+ : "";
37
+ return `<?xml version="1.0" encoding="UTF-8"?>
38
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39
+ <plist version="1.0">
40
+ <dict>
41
+ \t<key>Label</key>
42
+ \t<string>${PLIST_LABEL}</string>
43
+ \t<key>ProgramArguments</key>
44
+ \t<array>
45
+ ${argEntries}
46
+ \t</array>
47
+ \t<key>RunAtLoad</key>
48
+ \t<true/>
49
+ \t<key>KeepAlive</key>
50
+ \t<true/>
51
+ \t<key>StandardOutPath</key>
52
+ \t<string>${escapeXml(opts.stdoutLog)}</string>
53
+ \t<key>StandardErrorPath</key>
54
+ \t<string>${escapeXml(opts.stderrLog)}</string>
55
+ \t<key>WorkingDirectory</key>
56
+ \t<string>${escapeXml(opts.workingDir)}</string>
57
+ ${envBlock}</dict>
58
+ </plist>
59
+ `;
60
+ }
61
+ function escapeXml(s) {
62
+ return s
63
+ .replace(/&/g, "&amp;")
64
+ .replace(/</g, "&lt;")
65
+ .replace(/>/g, "&gt;")
66
+ .replace(/"/g, "&quot;")
67
+ .replace(/'/g, "&apos;");
68
+ }
69
+ export async function cmdInstall(args) {
70
+ if (process.platform !== "darwin") {
71
+ console.error("launchd is only supported on macOS");
72
+ return 1;
73
+ }
74
+ let values;
75
+ try {
76
+ ({ values } = parseArgs({
77
+ args,
78
+ options: {
79
+ goal: { type: "string", multiple: true },
80
+ config: { type: "string" },
81
+ interval: { type: "string" },
82
+ },
83
+ strict: false,
84
+ }));
85
+ }
86
+ catch {
87
+ console.error("Failed to parse arguments");
88
+ return 1;
89
+ }
90
+ const goalIds = values.goal ?? [];
91
+ if (goalIds.length === 0) {
92
+ console.error("Error: at least one --goal is required");
93
+ return 1;
94
+ }
95
+ const intervalMs = values.interval !== undefined ? parseInt(values.interval, 10) : undefined;
96
+ if (intervalMs !== undefined && (isNaN(intervalMs) || intervalMs <= 0)) {
97
+ console.error("--interval must be a positive integer (milliseconds)");
98
+ return 1;
99
+ }
100
+ if (fs.existsSync(PLIST_PATH)) {
101
+ console.warn(`Warning: plist already exists at ${PLIST_PATH}, overwriting`);
102
+ }
103
+ const nodePath = process.execPath;
104
+ // This file is compiled to dist/cli/commands/install.js — go up two levels to dist/
105
+ const cliRunnerPath = path.resolve(__dirname, "../../cli-runner.js");
106
+ const home = os.homedir();
107
+ const logsDir = path.join(home, ".pulseed", "logs");
108
+ const stdoutLog = path.join(logsDir, "launchd-stdout.log");
109
+ const stderrLog = path.join(logsDir, "launchd-stderr.log");
110
+ const plistContent = buildPlist({
111
+ nodePath,
112
+ cliRunnerPath,
113
+ goalIds,
114
+ configPath: values.config,
115
+ intervalMs,
116
+ stdoutLog,
117
+ stderrLog,
118
+ workingDir: home,
119
+ envPath: process.env["PATH"],
120
+ pulseedHome: process.env["PULSEED_HOME"],
121
+ });
122
+ // Ensure LaunchAgents directory exists
123
+ fs.mkdirSync(path.dirname(PLIST_PATH), { recursive: true });
124
+ fs.writeFileSync(PLIST_PATH, plistContent, "utf8");
125
+ const result = await execFileNoThrow("launchctl", ["load", PLIST_PATH]);
126
+ if (result.exitCode !== 0) {
127
+ console.error(`Failed to load plist with launchctl: ${result.stderr.trim()}`);
128
+ return 1;
129
+ }
130
+ console.log(`PulSeed daemon installed at: ${PLIST_PATH}`);
131
+ console.log(`To check status: launchctl list ${PLIST_LABEL}`);
132
+ console.log(`Logs: ${stdoutLog} / ${stderrLog}`);
133
+ return 0;
134
+ }
135
+ export async function cmdUninstall(_args) {
136
+ if (process.platform !== "darwin") {
137
+ console.error("launchd is only supported on macOS");
138
+ return 1;
139
+ }
140
+ if (!fs.existsSync(PLIST_PATH)) {
141
+ console.log("Not installed");
142
+ return 1;
143
+ }
144
+ const result = await execFileNoThrow("launchctl", ["unload", PLIST_PATH]);
145
+ if (result.exitCode !== 0) {
146
+ // Warn but still proceed to remove the plist file
147
+ console.warn(`Warning: launchctl unload returned an error: ${result.stderr.trim()}`);
148
+ }
149
+ fs.unlinkSync(PLIST_PATH);
150
+ console.log(`PulSeed daemon uninstalled (removed ${PLIST_PATH})`);
151
+ return 0;
152
+ }
153
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../../../src/cli/commands/install.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdLogs(args: string[]): Promise<number>;
2
+ //# sourceMappingURL=logs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/logs.ts"],"names":[],"mappings":"AAkJA,wBAAsB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAuE7D"}
@@ -0,0 +1,201 @@
1
+ // ─── pulseed logs command ───
2
+ //
3
+ // Shows daemon log lines from ~/.pulseed/logs/pulseed.log
4
+ // Supports: --lines N, --level <level>, --follow/-f
5
+ import { parseArgs } from "node:util";
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+ import { getLogsDir } from "../../utils/paths.js";
9
+ const LEVEL_ORDER = {
10
+ DEBUG: 0,
11
+ INFO: 1,
12
+ WARN: 2,
13
+ ERROR: 3,
14
+ };
15
+ const READ_CHUNK_SIZE = 64 * 1024; // 64KB
16
+ function parseLevel(raw) {
17
+ const upper = raw.toUpperCase();
18
+ return upper in LEVEL_ORDER ? upper : null;
19
+ }
20
+ function lineMatchesLevel(line, minLevel) {
21
+ // Log format: [<ISO timestamp>] [<LEVEL padded 5>] ...
22
+ // e.g. [2026-04-01T00:00:00.000Z] [ERROR] message
23
+ const match = line.match(/\[([A-Z ]{4,5})\]/);
24
+ if (!match || !match[1])
25
+ return false;
26
+ const found = match[1].trim();
27
+ if (!(found in LEVEL_ORDER))
28
+ return false;
29
+ return LEVEL_ORDER[found] >= LEVEL_ORDER[minLevel];
30
+ }
31
+ /**
32
+ * Read the last N lines from a file efficiently by reading a chunk from the end.
33
+ */
34
+ function readLastLines(filePath, n) {
35
+ let stat;
36
+ try {
37
+ stat = fs.statSync(filePath);
38
+ }
39
+ catch {
40
+ return [];
41
+ }
42
+ if (stat.size === 0)
43
+ return [];
44
+ const readSize = Math.min(READ_CHUNK_SIZE, stat.size);
45
+ const buffer = Buffer.alloc(readSize);
46
+ const fd = fs.openSync(filePath, "r");
47
+ try {
48
+ fs.readSync(fd, buffer, 0, readSize, stat.size - readSize);
49
+ }
50
+ finally {
51
+ fs.closeSync(fd);
52
+ }
53
+ const content = buffer.toString("utf8");
54
+ const lines = content.split("\n");
55
+ // Remove trailing empty entry from trailing newline
56
+ if (lines[lines.length - 1] === "")
57
+ lines.pop();
58
+ return lines.slice(-n);
59
+ }
60
+ /**
61
+ * Follow a log file, printing new lines as they are appended.
62
+ * Handles file rotation by re-opening when the file is replaced.
63
+ */
64
+ async function followLog(filePath, minLevel) {
65
+ let fileSize = 0;
66
+ try {
67
+ fileSize = fs.statSync(filePath).size;
68
+ }
69
+ catch {
70
+ // File may not exist yet; start at 0
71
+ }
72
+ let watcher = null;
73
+ let stopped = false;
74
+ const checkForNewLines = () => {
75
+ try {
76
+ const stat = fs.statSync(filePath);
77
+ if (stat.size < fileSize) {
78
+ // File was rotated/truncated — reset position
79
+ fileSize = 0;
80
+ }
81
+ if (stat.size === fileSize)
82
+ return;
83
+ const toRead = stat.size - fileSize;
84
+ const buf = Buffer.alloc(toRead);
85
+ const fd = fs.openSync(filePath, "r");
86
+ try {
87
+ fs.readSync(fd, buf, 0, toRead, fileSize);
88
+ }
89
+ finally {
90
+ fs.closeSync(fd);
91
+ }
92
+ fileSize = stat.size;
93
+ const chunk = buf.toString("utf8");
94
+ const lines = chunk.split("\n");
95
+ // The last element may be an incomplete line if the write was partial;
96
+ // for simplicity, print complete lines only (those followed by \n)
97
+ for (let i = 0; i < lines.length - 1; i++) {
98
+ const line = lines[i];
99
+ if (minLevel === null || lineMatchesLevel(line, minLevel)) {
100
+ process.stdout.write(line + "\n");
101
+ }
102
+ }
103
+ }
104
+ catch {
105
+ // File disappeared (rotation in progress) — will retry on next event
106
+ }
107
+ };
108
+ // Initial drain in case file already has content
109
+ checkForNewLines();
110
+ const setupWatcher = () => {
111
+ try {
112
+ watcher = fs.watch(filePath, () => {
113
+ checkForNewLines();
114
+ });
115
+ watcher.on("error", () => {
116
+ // Watch target gone; re-try after a short delay
117
+ watcher = null;
118
+ if (!stopped)
119
+ setTimeout(setupWatcher, 500);
120
+ });
121
+ }
122
+ catch {
123
+ // File may not exist yet; retry
124
+ if (!stopped)
125
+ setTimeout(setupWatcher, 500);
126
+ }
127
+ };
128
+ setupWatcher();
129
+ await new Promise((resolve) => {
130
+ process.once("SIGINT", () => {
131
+ stopped = true;
132
+ watcher?.close();
133
+ resolve();
134
+ });
135
+ });
136
+ }
137
+ export async function cmdLogs(args) {
138
+ let values;
139
+ try {
140
+ ({ values } = parseArgs({
141
+ args,
142
+ options: {
143
+ follow: { type: "boolean", short: "f" },
144
+ lines: { type: "string", short: "n", default: "50" },
145
+ level: { type: "string" },
146
+ },
147
+ strict: false,
148
+ }));
149
+ }
150
+ catch {
151
+ values = { lines: "50" };
152
+ }
153
+ const logPath = path.join(getLogsDir(), "pulseed.log");
154
+ const lineCount = parseInt(values.lines ?? "50", 10);
155
+ const n = isNaN(lineCount) || lineCount <= 0 ? 50 : lineCount;
156
+ let minLevel = null;
157
+ if (values.level) {
158
+ minLevel = parseLevel(values.level);
159
+ if (minLevel === null) {
160
+ process.stderr.write(`Unknown log level: "${values.level}". Valid levels: DEBUG, INFO, WARN, ERROR\n`);
161
+ return 1;
162
+ }
163
+ }
164
+ // Check file existence for non-follow mode
165
+ if (!values.follow) {
166
+ try {
167
+ fs.accessSync(logPath, fs.constants.R_OK);
168
+ }
169
+ catch {
170
+ process.stdout.write(`No log file found at ${logPath}\n`);
171
+ return 1;
172
+ }
173
+ }
174
+ if (values.follow) {
175
+ // For follow mode, print existing tail first, then watch
176
+ let existingLines = [];
177
+ try {
178
+ fs.accessSync(logPath, fs.constants.R_OK);
179
+ existingLines = readLastLines(logPath, n);
180
+ }
181
+ catch {
182
+ // File not yet present; that's fine for follow mode
183
+ }
184
+ for (const line of existingLines) {
185
+ if (minLevel === null || lineMatchesLevel(line, minLevel)) {
186
+ process.stdout.write(line + "\n");
187
+ }
188
+ }
189
+ await followLog(logPath, minLevel);
190
+ return 0;
191
+ }
192
+ // Non-follow: read last N lines and print
193
+ const lines = readLastLines(logPath, n);
194
+ for (const line of lines) {
195
+ if (minLevel === null || lineMatchesLevel(line, minLevel)) {
196
+ process.stdout.write(line + "\n");
197
+ }
198
+ }
199
+ return 0;
200
+ }
201
+ //# sourceMappingURL=logs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logs.js","sourceRoot":"","sources":["../../../src/cli/commands/logs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function cmdNotify(args: string[]): Promise<number>;
2
+ //# sourceMappingURL=notify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/notify.ts"],"names":[],"mappings":"AAgSA,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B/D"}
@@ -0,0 +1,277 @@
1
+ // ─── pulseed notify commands (add, list, remove, test) ───
2
+ import { parseArgs } from "node:util";
3
+ import * as path from "node:path";
4
+ import { readJsonFileOrNull, writeJsonFileAtomic } from "../../utils/json-io.js";
5
+ import { NotificationConfigSchema } from "../../types/notification.js";
6
+ import { getPulseedDirPath } from "../../utils/paths.js";
7
+ function getNotificationConfigPath(baseDir) {
8
+ return path.join(baseDir ?? getPulseedDirPath(), "notification.json");
9
+ }
10
+ async function loadConfig(configPath) {
11
+ const raw = await readJsonFileOrNull(configPath);
12
+ if (raw === null) {
13
+ return NotificationConfigSchema.parse({});
14
+ }
15
+ const result = NotificationConfigSchema.safeParse(raw);
16
+ if (!result.success) {
17
+ console.error(`Warning: notification config has invalid format, resetting. (${result.error.message})`);
18
+ return NotificationConfigSchema.parse({});
19
+ }
20
+ return result.data;
21
+ }
22
+ async function saveConfig(configPath, config) {
23
+ await writeJsonFileAtomic(configPath, config);
24
+ }
25
+ function formatChannel(ch, index) {
26
+ let detail = "";
27
+ if (ch.type === "slack") {
28
+ detail = `webhook: ${ch.webhook_url}`;
29
+ }
30
+ else if (ch.type === "webhook") {
31
+ detail = `url: ${ch.url}`;
32
+ }
33
+ else if (ch.type === "email") {
34
+ detail = `address: ${ch.address}, smtp: ${ch.smtp.host}:${ch.smtp.port}`;
35
+ }
36
+ const reports = ch.report_types.length > 0 ? ` (reports: ${ch.report_types.join(", ")})` : "";
37
+ return `[${index}] ${ch.type.padEnd(7)} — ${detail}${reports}`;
38
+ }
39
+ async function cmdNotifyAdd(args, configPath) {
40
+ const positionals = args.filter((a) => !a.startsWith("-"));
41
+ const channelType = positionals[0];
42
+ if (!channelType) {
43
+ console.error("Usage: pulseed notify add <slack|webhook|email> [options]");
44
+ return 1;
45
+ }
46
+ if (channelType === "slack") {
47
+ let values;
48
+ try {
49
+ ({ values } = parseArgs({
50
+ args,
51
+ options: {
52
+ "webhook-url": { type: "string" },
53
+ },
54
+ strict: false,
55
+ }));
56
+ }
57
+ catch {
58
+ values = {};
59
+ }
60
+ if (!values["webhook-url"]) {
61
+ console.error("Error: --webhook-url is required for slack channel");
62
+ return 1;
63
+ }
64
+ const config = await loadConfig(configPath);
65
+ const channel = {
66
+ type: "slack",
67
+ webhook_url: values["webhook-url"],
68
+ report_types: [],
69
+ format: "compact",
70
+ };
71
+ config.channels.push(channel);
72
+ await saveConfig(configPath, config);
73
+ console.log(`Added slack channel (index ${config.channels.length - 1})`);
74
+ return 0;
75
+ }
76
+ if (channelType === "webhook") {
77
+ let values;
78
+ try {
79
+ ({ values } = parseArgs({
80
+ args,
81
+ options: {
82
+ url: { type: "string" },
83
+ header: { type: "string", multiple: true },
84
+ },
85
+ strict: false,
86
+ }));
87
+ }
88
+ catch {
89
+ values = {};
90
+ }
91
+ if (!values.url) {
92
+ console.error("Error: --url is required for webhook channel");
93
+ return 1;
94
+ }
95
+ const headers = {};
96
+ for (const h of values.header ?? []) {
97
+ const colonIdx = h.indexOf(":");
98
+ if (colonIdx === -1) {
99
+ console.error(`Error: invalid header format "${h}", expected "Key: Value"`);
100
+ return 1;
101
+ }
102
+ const key = h.slice(0, colonIdx).trim();
103
+ const val = h.slice(colonIdx + 1).trim();
104
+ headers[key] = val;
105
+ }
106
+ const config = await loadConfig(configPath);
107
+ const channel = {
108
+ type: "webhook",
109
+ url: values.url,
110
+ report_types: [],
111
+ format: "json",
112
+ ...(Object.keys(headers).length > 0 ? { headers } : {}),
113
+ };
114
+ config.channels.push(channel);
115
+ await saveConfig(configPath, config);
116
+ console.log(`Added webhook channel (index ${config.channels.length - 1})`);
117
+ return 0;
118
+ }
119
+ if (channelType === "email") {
120
+ let values;
121
+ try {
122
+ ({ values } = parseArgs({
123
+ args,
124
+ options: {
125
+ address: { type: "string" },
126
+ "smtp-host": { type: "string" },
127
+ "smtp-port": { type: "string" },
128
+ },
129
+ strict: false,
130
+ }));
131
+ }
132
+ catch {
133
+ values = {};
134
+ }
135
+ if (!values.address) {
136
+ console.error("Error: --address is required for email channel");
137
+ return 1;
138
+ }
139
+ if (!values["smtp-host"]) {
140
+ console.error("Error: --smtp-host is required for email channel");
141
+ return 1;
142
+ }
143
+ const smtpPort = values["smtp-port"] ? parseInt(values["smtp-port"], 10) : 587;
144
+ if (isNaN(smtpPort) || smtpPort <= 0) {
145
+ console.error("Error: --smtp-port must be a positive integer");
146
+ return 1;
147
+ }
148
+ const config = await loadConfig(configPath);
149
+ const channel = {
150
+ type: "email",
151
+ address: values.address,
152
+ smtp: {
153
+ host: values["smtp-host"],
154
+ port: smtpPort,
155
+ secure: true,
156
+ auth: { user: "", pass: "" },
157
+ },
158
+ report_types: [],
159
+ format: "full",
160
+ };
161
+ config.channels.push(channel);
162
+ await saveConfig(configPath, config);
163
+ console.log(`Added email channel (index ${config.channels.length - 1})`);
164
+ return 0;
165
+ }
166
+ console.error(`Error: unknown channel type "${channelType}". Use: slack, webhook, email`);
167
+ return 1;
168
+ }
169
+ async function cmdNotifyList(configPath) {
170
+ const config = await loadConfig(configPath);
171
+ if (config.channels.length === 0) {
172
+ console.log("No channels configured");
173
+ return 0;
174
+ }
175
+ console.log("Notification Channels:");
176
+ for (let i = 0; i < config.channels.length; i++) {
177
+ console.log(formatChannel(config.channels[i], i));
178
+ }
179
+ return 0;
180
+ }
181
+ async function cmdNotifyRemove(args, configPath) {
182
+ const indexStr = args[0];
183
+ if (!indexStr) {
184
+ console.error("Usage: pulseed notify remove <index>");
185
+ return 1;
186
+ }
187
+ const index = parseInt(indexStr, 10);
188
+ if (isNaN(index) || index < 0) {
189
+ console.error("Error: index must be a non-negative integer");
190
+ return 1;
191
+ }
192
+ const config = await loadConfig(configPath);
193
+ if (index >= config.channels.length) {
194
+ console.error(`Error: index ${index} out of bounds (${config.channels.length} channel(s) configured)`);
195
+ return 1;
196
+ }
197
+ const removed = config.channels.splice(index, 1)[0];
198
+ await saveConfig(configPath, config);
199
+ console.log(`Removed ${removed.type} channel at index ${index}`);
200
+ return 0;
201
+ }
202
+ async function cmdNotifyTest(args, configPath) {
203
+ const config = await loadConfig(configPath);
204
+ if (config.channels.length === 0) {
205
+ console.log("No channels configured");
206
+ return 0;
207
+ }
208
+ const indexStr = args[0];
209
+ let targets;
210
+ if (indexStr !== undefined) {
211
+ const index = parseInt(indexStr, 10);
212
+ if (isNaN(index) || index < 0) {
213
+ console.error("Error: index must be a non-negative integer");
214
+ return 1;
215
+ }
216
+ if (index >= config.channels.length) {
217
+ console.error(`Error: index ${index} out of bounds (${config.channels.length} channel(s) configured)`);
218
+ return 1;
219
+ }
220
+ targets = [{ index, channel: config.channels[index] }];
221
+ }
222
+ else {
223
+ targets = config.channels.map((ch, i) => ({ index: i, channel: ch }));
224
+ }
225
+ const testPayload = {
226
+ type: "test",
227
+ message: "PulSeed notification test",
228
+ timestamp: new Date().toISOString(),
229
+ };
230
+ console.log("Test notification payload (dry-run — daemon must be running for actual delivery):");
231
+ console.log(JSON.stringify(testPayload, null, 2));
232
+ console.log("");
233
+ for (const { index, channel } of targets) {
234
+ console.log(`[${index}] ${channel.type} — would send to:`);
235
+ if (channel.type === "slack") {
236
+ console.log(` webhook_url: ${channel.webhook_url}`);
237
+ }
238
+ else if (channel.type === "webhook") {
239
+ console.log(` url: ${channel.url}`);
240
+ if (channel.headers && Object.keys(channel.headers).length > 0) {
241
+ for (const [k, v] of Object.entries(channel.headers)) {
242
+ console.log(` header: ${k}: ${v}`);
243
+ }
244
+ }
245
+ }
246
+ else if (channel.type === "email") {
247
+ console.log(` address: ${channel.address}`);
248
+ console.log(` smtp: ${channel.smtp.host}:${channel.smtp.port}`);
249
+ }
250
+ }
251
+ return 0;
252
+ }
253
+ export async function cmdNotify(args) {
254
+ const subcommand = args[0];
255
+ const rest = args.slice(1);
256
+ const configPath = getNotificationConfigPath();
257
+ switch (subcommand) {
258
+ case "add":
259
+ return cmdNotifyAdd(rest, configPath);
260
+ case "list":
261
+ return cmdNotifyList(configPath);
262
+ case "remove":
263
+ return cmdNotifyRemove(rest, configPath);
264
+ case "test":
265
+ return cmdNotifyTest(rest, configPath);
266
+ default:
267
+ console.error("Usage: pulseed notify <add|list|remove|test>\n" +
268
+ " add slack --webhook-url <url>\n" +
269
+ " add webhook --url <url> [--header 'Key: Value']\n" +
270
+ " add email --address <email> --smtp-host <host> [--smtp-port <port>]\n" +
271
+ " list\n" +
272
+ " remove <index>\n" +
273
+ " test [index]");
274
+ return 1;
275
+ }
276
+ }
277
+ //# sourceMappingURL=notify.js.map