@kingcrab/pi-imessage 0.0.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.
Files changed (75) hide show
  1. package/README.md +120 -0
  2. package/dist/agent.d.ts +22 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +341 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/cli.d.ts +15 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +179 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/imessage.d.ts +32 -0
  11. package/dist/imessage.d.ts.map +1 -0
  12. package/dist/imessage.js +65 -0
  13. package/dist/imessage.js.map +1 -0
  14. package/dist/logger.d.ts +33 -0
  15. package/dist/logger.d.ts.map +1 -0
  16. package/dist/logger.js +103 -0
  17. package/dist/logger.js.map +1 -0
  18. package/dist/main.d.ts +5 -0
  19. package/dist/main.d.ts.map +1 -0
  20. package/dist/main.js +66 -0
  21. package/dist/main.js.map +1 -0
  22. package/dist/pipeline.d.ts +26 -0
  23. package/dist/pipeline.d.ts.map +1 -0
  24. package/dist/pipeline.js +48 -0
  25. package/dist/pipeline.js.map +1 -0
  26. package/dist/queue.d.ts +16 -0
  27. package/dist/queue.d.ts.map +1 -0
  28. package/dist/queue.js +47 -0
  29. package/dist/queue.js.map +1 -0
  30. package/dist/self-echo.d.ts +19 -0
  31. package/dist/self-echo.d.ts.map +1 -0
  32. package/dist/self-echo.js +54 -0
  33. package/dist/self-echo.js.map +1 -0
  34. package/dist/send.d.ts +17 -0
  35. package/dist/send.d.ts.map +1 -0
  36. package/dist/send.js +107 -0
  37. package/dist/send.js.map +1 -0
  38. package/dist/settings.d.ts +39 -0
  39. package/dist/settings.d.ts.map +1 -0
  40. package/dist/settings.js +74 -0
  41. package/dist/settings.js.map +1 -0
  42. package/dist/store.d.ts +41 -0
  43. package/dist/store.d.ts.map +1 -0
  44. package/dist/store.js +72 -0
  45. package/dist/store.js.map +1 -0
  46. package/dist/tasks.d.ts +88 -0
  47. package/dist/tasks.d.ts.map +1 -0
  48. package/dist/tasks.js +260 -0
  49. package/dist/tasks.js.map +1 -0
  50. package/dist/types.d.ts +77 -0
  51. package/dist/types.d.ts.map +1 -0
  52. package/dist/types.js +24 -0
  53. package/dist/types.js.map +1 -0
  54. package/dist/watch.d.ts +21 -0
  55. package/dist/watch.d.ts.map +1 -0
  56. package/dist/watch.js +137 -0
  57. package/dist/watch.js.map +1 -0
  58. package/dist/web/data.d.ts +11 -0
  59. package/dist/web/data.d.ts.map +1 -0
  60. package/dist/web/data.js +56 -0
  61. package/dist/web/data.js.map +1 -0
  62. package/dist/web/html.d.ts +5 -0
  63. package/dist/web/html.d.ts.map +1 -0
  64. package/dist/web/html.js +16 -0
  65. package/dist/web/html.js.map +1 -0
  66. package/dist/web/index.d.ts +15 -0
  67. package/dist/web/index.d.ts.map +1 -0
  68. package/dist/web/index.js +116 -0
  69. package/dist/web/index.js.map +1 -0
  70. package/dist/web/render.d.ts +5 -0
  71. package/dist/web/render.d.ts.map +1 -0
  72. package/dist/web/render.js +50 -0
  73. package/dist/web/render.js.map +1 -0
  74. package/dist/web/templates/page.eta +85 -0
  75. package/package.json +42 -0
@@ -0,0 +1,16 @@
1
+ /** HTML utility functions. */
2
+ /** Format as "[YYYY-MM-DD HH:MM:SS]" in local time. */
3
+ export function formatTime(iso) {
4
+ const date = new Date(iso);
5
+ const year = date.getFullYear();
6
+ const month = String(date.getMonth() + 1).padStart(2, "0");
7
+ const day = String(date.getDate()).padStart(2, "0");
8
+ const hour = String(date.getHours()).padStart(2, "0");
9
+ const minute = String(date.getMinutes()).padStart(2, "0");
10
+ const second = String(date.getSeconds()).padStart(2, "0");
11
+ return `[${year}-${month}-${day} ${hour}:${minute}:${second}]`;
12
+ }
13
+ export function anchorId(guid) {
14
+ return guid.replace(/[^a-zA-Z0-9]/g, "_");
15
+ }
16
+ //# sourceMappingURL=html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"html.js","sourceRoot":"","sources":["../../src/web/html.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAE9B,uDAAuD;AACvD,MAAM,UAAU,UAAU,CAAC,GAAW;IACrC,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC3D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,OAAO,IAAI,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM,GAAG,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,15 @@
1
+ /** Web server: serves the chat log UI and SSE updates. */
2
+ import type { Settings } from "../settings.js";
3
+ export interface WebServerConfig {
4
+ workingDir: string;
5
+ host: string;
6
+ port: number;
7
+ getSettings: () => Settings;
8
+ setSettings: (settings: Settings) => void;
9
+ }
10
+ export interface WebServer {
11
+ start(): void;
12
+ stop(): Promise<void>;
13
+ }
14
+ export declare function createWebServer(config: WebServerConfig): WebServer;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAI1D,OAAO,KAAK,EAAiB,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAI9D,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,QAAQ,CAAC;IAC5B,WAAW,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACtB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CA6GlE"}
@@ -0,0 +1,116 @@
1
+ /** Web server: serves the chat log UI and SSE updates. */
2
+ import { existsSync, watch } from "node:fs";
3
+ import { createServer } from "node:http";
4
+ import { getChatBlocks } from "./data.js";
5
+ import { renderPage } from "./render.js";
6
+ export function createWebServer(config) {
7
+ const { workingDir, host, port, getSettings, setSettings } = config;
8
+ const sseClients = new Set();
9
+ let watcher = null;
10
+ let debounceTimer = null;
11
+ function broadcast() {
12
+ if (debounceTimer)
13
+ return;
14
+ debounceTimer = setTimeout(() => {
15
+ debounceTimer = null;
16
+ for (const client of sseClients) {
17
+ client.write("event: update\ndata: {}\n\n");
18
+ }
19
+ }, 300);
20
+ }
21
+ function startWatcher() {
22
+ if (!existsSync(workingDir))
23
+ return;
24
+ try {
25
+ watcher = watch(workingDir, { recursive: true }, (_event, filename) => {
26
+ if (filename?.endsWith("log.jsonl"))
27
+ broadcast();
28
+ });
29
+ watcher.on("error", () => { });
30
+ }
31
+ catch {
32
+ // workingDir may not exist yet
33
+ }
34
+ }
35
+ /** Toggle reply for a chatGuid by updating whitelist/blacklist. Does not touch "*" wildcards. */
36
+ function toggleChatReply(chatGuid, enabled) {
37
+ const { chatAllowlist, ...rest } = getSettings();
38
+ const whitelist = chatAllowlist.whitelist.filter((id) => id !== chatGuid);
39
+ const blacklist = chatAllowlist.blacklist.filter((id) => id !== chatGuid);
40
+ if (enabled) {
41
+ whitelist.push(chatGuid);
42
+ }
43
+ else {
44
+ blacklist.push(chatGuid);
45
+ }
46
+ setSettings({ ...rest, chatAllowlist: { whitelist, blacklist } });
47
+ }
48
+ function handleRequest(request, response) {
49
+ const url = new URL(request.url ?? "/", `http://localhost:${port}`);
50
+ if (url.pathname === "/events") {
51
+ response.writeHead(200, {
52
+ "Content-Type": "text/event-stream",
53
+ "Cache-Control": "no-cache",
54
+ Connection: "keep-alive",
55
+ });
56
+ response.write("retry: 5000\n\n");
57
+ sseClients.add(response);
58
+ request.on("close", () => sseClients.delete(response));
59
+ return;
60
+ }
61
+ if (request.method === "POST" && url.pathname === "/toggle") {
62
+ const chunks = [];
63
+ request.on("data", (chunk) => chunks.push(chunk));
64
+ request.on("end", () => {
65
+ try {
66
+ const body = JSON.parse(Buffer.concat(chunks).toString());
67
+ toggleChatReply(body.chatGuid, body.enabled);
68
+ console.log(`[web] toggled reply for ${body.chatGuid}: enabled=${body.enabled}`);
69
+ response.writeHead(200, { "Content-Type": "application/json" });
70
+ response.end(JSON.stringify({ ok: true }));
71
+ }
72
+ catch (error) {
73
+ console.error("[web] toggle error:", error);
74
+ response.writeHead(400);
75
+ response.end("bad request");
76
+ }
77
+ });
78
+ return;
79
+ }
80
+ const blocks = getChatBlocks(workingDir);
81
+ const settings = getSettings();
82
+ const html = renderPage(blocks, settings);
83
+ response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
84
+ response.end(html);
85
+ }
86
+ const server = createServer(handleRequest);
87
+ return {
88
+ start() {
89
+ startWatcher();
90
+ server.listen(port, host, () => {
91
+ console.log(`[web] UI available at http://${host}:${port}`);
92
+ });
93
+ },
94
+ stop() {
95
+ if (debounceTimer)
96
+ clearTimeout(debounceTimer);
97
+ watcher?.close();
98
+ for (const client of sseClients)
99
+ client.end();
100
+ sseClients.clear();
101
+ return new Promise((resolve, reject) => {
102
+ server.close((error) => {
103
+ if (error) {
104
+ console.error("[web] server close error:", error);
105
+ reject(error);
106
+ }
107
+ else {
108
+ console.log("[web] server closed");
109
+ resolve();
110
+ }
111
+ });
112
+ });
113
+ },
114
+ };
115
+ }
116
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /** Render the full HTML page from chat blocks using Eta templates. */
2
+ import type { Settings } from "../settings.js";
3
+ import type { ChatBlock } from "./data.js";
4
+ export declare function renderPage(blocks: ChatBlock[], settings: Settings): string;
5
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/web/render.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAMtE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AA6D3C,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAG1E"}
@@ -0,0 +1,50 @@
1
+ /** Render the full HTML page from chat blocks using Eta templates. */
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { Eta } from "eta";
5
+ import { isReplyEnabled } from "../settings.js";
6
+ import { firstLinePreview, senderLabel } from "../store.js";
7
+ import { anchorId, formatTime } from "./html.js";
8
+ const MAX_MESSAGES = 15;
9
+ const TEN_MINUTES_MS = 10 * 60 * 1000;
10
+ const templateDir = join(dirname(fileURLToPath(import.meta.url)), "templates");
11
+ const eta = new Eta({ views: templateDir, autoEscape: true });
12
+ /** Map MessageType to a display channel tag. */
13
+ function channelLabel(messageType) {
14
+ if (messageType === "group")
15
+ return "[GROUP]";
16
+ if (messageType === "sms")
17
+ return "[SMS]";
18
+ return "[DM]";
19
+ }
20
+ /** Prepare the row data for a single chat card. */
21
+ function prepareCard(block) {
22
+ const recent = block.messages.slice(-MAX_MESSAGES);
23
+ const rows = [];
24
+ for (let i = 0; i < recent.length; i++) {
25
+ const message = recent[i];
26
+ if (!message)
27
+ continue;
28
+ const prev = recent[i - 1];
29
+ if (prev) {
30
+ const gapMs = new Date(message.date).getTime() - new Date(prev.date).getTime();
31
+ if (gapMs > TEN_MINUTES_MS) {
32
+ rows.push({ gap: true, time: "", arrow: "", channel: "", sender: "", text: "" });
33
+ }
34
+ }
35
+ rows.push({
36
+ gap: false,
37
+ time: formatTime(message.date),
38
+ arrow: message.isBot ? "->" : "<-",
39
+ channel: channelLabel(message.messageType),
40
+ sender: senderLabel(message),
41
+ text: firstLinePreview(message.text),
42
+ });
43
+ }
44
+ return { rows };
45
+ }
46
+ export function renderPage(blocks, settings) {
47
+ const replyEnabledMap = (chatGuid) => isReplyEnabled(settings, chatGuid);
48
+ return eta.render("page", { blocks, prepareCard, anchorId, replyEnabledMap });
49
+ }
50
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/web/render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ <!DOCTYPE html>
2
+ <html><head><meta charset="UTF-8"><title>blue</title>
3
+ <style>
4
+ * { margin: 0; padding: 0 }
5
+ body { background: #111; color: #ccc; font: 13px/1.6 monospace; padding: 40px 40px 40px 240px; max-width: 900px }
6
+ nav { position: fixed; top: 0; left: 0; width: 210px; height: 100vh; overflow-y: auto; padding: 40px 24px 40px }
7
+ .toc-label { font-size: 11px; opacity: .35; margin-bottom: 16px }
8
+ nav a { display: block; color: inherit; text-decoration: none; line-height: 2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; opacity: .55 }
9
+ nav a:hover { opacity: 1 }
10
+ .card { background: #1a1a1a; margin-bottom: 24px; scroll-margin-top: 40px }
11
+ .card-body { max-height: 300px; overflow: hidden; display: flex; flex-direction: column; justify-content: flex-end }
12
+ .card-header { padding: 12px 16px; border-bottom: 1px solid #252525; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; backdrop-filter: blur(8px); background: rgba(26,26,26,0.75) }
13
+ .card-messages { padding: 14px 16px }
14
+ .meta { font-size: 11px; opacity: .4 }
15
+ .toggle { background: none; border: none; color: #ccc; font: 11px monospace; cursor: pointer; opacity: .4 }
16
+ .toggle:hover { opacity: .8 }
17
+ table { border-collapse: collapse }
18
+ td { vertical-align: top; padding: 1px 0 }
19
+ .c-fix { white-space: nowrap; padding-right: 8px }
20
+ .c-sender { text-align: right }
21
+ .c-msg { white-space: pre-wrap; word-break: break-word }
22
+ </style></head><body>
23
+
24
+ <nav>
25
+ <div class="toc-label">Recent Chats</div>
26
+ <% it.blocks.forEach(function(block) { %>
27
+ <a href="#<%= it.anchorId(block.guid) %>"><%= block.displayName %></a>
28
+ <% }) %>
29
+ </nav>
30
+
31
+ <% if (it.blocks.length === 0) { %>
32
+ <p style="opacity:.4">no chats in the last 7 days</p>
33
+ <% } else { %>
34
+ <% it.blocks.forEach(function(block) { %>
35
+ <% const card = it.prepareCard(block) %>
36
+ <div class="card" id="<%= it.anchorId(block.guid) %>">
37
+ <div class="card-body">
38
+ <div class="card-header">
39
+ <span><%= block.displayName %> <span class="meta"><%= block.messages.length %> msgs</span></span>
40
+ <% const enabled = it.replyEnabledMap(block.guid) %>
41
+ <button class="toggle"
42
+ onclick="toggleReply('<%= block.guid %>', <%= !enabled %>)">
43
+ <%= enabled ? '[on]' : '[off]' %>
44
+ </button>
45
+ </div>
46
+ <div class="card-messages">
47
+ <table>
48
+ <% card.rows.forEach(function(row) { %>
49
+ <% if (row.gap) { %>
50
+ <tr><td colspan="6">&nbsp;</td></tr>
51
+ <% } else { %>
52
+ <tr>
53
+ <td class="c-fix c-time"><%= row.time %></td>
54
+ <td class="c-fix c-source">[sid]</td>
55
+ <td class="c-fix c-arrow"><%= row.arrow %></td>
56
+ <td class="c-fix c-channel"><%= row.channel %></td>
57
+ <td class="c-fix c-sender"><%= row.sender %>:</td>
58
+ <td class="c-msg"><%= row.text %></td>
59
+ </tr>
60
+ <% } %>
61
+ <% }) %>
62
+ </table>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ <% }) %>
67
+ <% } %>
68
+
69
+ <script>
70
+ function toggleReply(chatGuid, enabled) {
71
+ const btn = event.target;
72
+ btn.textContent = enabled ? '[on]' : '[off]';
73
+ btn.setAttribute('onclick', "toggleReply('" + chatGuid + "', " + !enabled + ")");
74
+ fetch('/toggle', {
75
+ method: 'POST',
76
+ headers: { 'Content-Type': 'application/json' },
77
+ body: JSON.stringify({ chatGuid, enabled })
78
+ });
79
+ }
80
+ const es = new EventSource("/events");
81
+ es.addEventListener("update", () => location.reload());
82
+
83
+
84
+ </script>
85
+ </body></html>
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kingcrab/pi-imessage",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "bin": {
6
+ "pi-imessage": "./dist/cli.js"
7
+ },
8
+ "files": ["dist", "!dist/__tests__", "README.md"],
9
+ "engines": {
10
+ "node": ">=20.0.0"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc && mkdir -p dist/web/templates && cp src/web/templates/* dist/web/templates/",
14
+ "prepublishOnly": "npm run build",
15
+ "dev": "tsx watch src/main.ts",
16
+ "start": "tsx src/main.ts",
17
+ "test": "vitest run",
18
+ "typecheck": "tsc --noEmit",
19
+ "lint": "biome check .",
20
+ "format": "biome format --write .",
21
+ "check": "tsc --noEmit && biome check ."
22
+ },
23
+ "dependencies": {
24
+ "@mariozechner/pi-agent-core": "latest",
25
+ "@mariozechner/pi-ai": "latest",
26
+ "@mariozechner/pi-coding-agent": "latest",
27
+ "better-sqlite3": "^12.6.2",
28
+ "dotenv": "^17.3.1",
29
+ "eta": "^4.5.1",
30
+ "sharp": "^0.34.5"
31
+ },
32
+ "devDependencies": {
33
+ "@biomejs/biome": "^1.9.4",
34
+ "@types/better-sqlite3": "^7.6.13",
35
+ "@types/node": "^22.0.0",
36
+ "@types/sharp": "^0.31.1",
37
+ "@vitest/coverage-v8": "^3.2.4",
38
+ "tsx": "^4.0.0",
39
+ "typescript": "^5.7.3",
40
+ "vitest": "^3.0.0"
41
+ }
42
+ }