ccpoke 1.4.2 → 1.5.0

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 (89) hide show
  1. package/README.en.md +26 -11
  2. package/README.md +26 -11
  3. package/dist/agent/agent-handler.d.ts +12 -0
  4. package/dist/agent/agent-handler.js +49 -0
  5. package/dist/agent/agent-handler.js.map +1 -0
  6. package/dist/agent/agent-registry.d.ts +9 -0
  7. package/dist/agent/agent-registry.js +24 -0
  8. package/dist/agent/agent-registry.js.map +1 -0
  9. package/dist/{hook/hook-installer.d.ts → agent/claude-code/claude-code-installer.d.ts} +2 -2
  10. package/dist/agent/claude-code/claude-code-installer.js +82 -0
  11. package/dist/agent/claude-code/claude-code-installer.js.map +1 -0
  12. package/dist/{monitor/transcript-parser.d.ts → agent/claude-code/claude-code-parser.d.ts} +6 -0
  13. package/dist/{monitor/transcript-parser.js → agent/claude-code/claude-code-parser.js} +10 -2
  14. package/dist/agent/claude-code/claude-code-parser.js.map +1 -0
  15. package/dist/agent/claude-code/claude-code-provider.d.ts +12 -0
  16. package/dist/agent/claude-code/claude-code-provider.js +72 -0
  17. package/dist/agent/claude-code/claude-code-provider.js.map +1 -0
  18. package/dist/agent/cursor/cursor-installer.d.ts +9 -0
  19. package/dist/agent/cursor/cursor-installer.js +89 -0
  20. package/dist/agent/cursor/cursor-installer.js.map +1 -0
  21. package/dist/agent/cursor/cursor-parser.d.ts +19 -0
  22. package/dist/agent/cursor/cursor-parser.js +61 -0
  23. package/dist/agent/cursor/cursor-parser.js.map +1 -0
  24. package/dist/agent/cursor/cursor-provider.d.ts +12 -0
  25. package/dist/agent/cursor/cursor-provider.js +72 -0
  26. package/dist/agent/cursor/cursor-provider.js.map +1 -0
  27. package/dist/agent/types.d.ts +27 -0
  28. package/dist/agent/types.js +11 -0
  29. package/dist/agent/types.js.map +1 -0
  30. package/dist/channel/telegram/telegram-channel.js +7 -90
  31. package/dist/channel/telegram/telegram-channel.js.map +1 -1
  32. package/dist/channel/types.d.ts +2 -0
  33. package/dist/commands/setup.js +83 -19
  34. package/dist/commands/setup.js.map +1 -1
  35. package/dist/commands/uninstall.js +12 -9
  36. package/dist/commands/uninstall.js.map +1 -1
  37. package/dist/config-manager.d.ts +2 -1
  38. package/dist/config-manager.js +21 -17
  39. package/dist/config-manager.js.map +1 -1
  40. package/dist/i18n/index.d.ts +7 -2
  41. package/dist/i18n/index.js +10 -5
  42. package/dist/i18n/index.js.map +1 -1
  43. package/dist/i18n/locales/en.js +11 -0
  44. package/dist/i18n/locales/en.js.map +1 -1
  45. package/dist/i18n/locales/vi.js +11 -0
  46. package/dist/i18n/locales/vi.js.map +1 -1
  47. package/dist/i18n/locales/zh.js +11 -0
  48. package/dist/i18n/locales/zh.js.map +1 -1
  49. package/dist/i18n/types.d.ts +11 -0
  50. package/dist/index.js +28 -8
  51. package/dist/index.js.map +1 -1
  52. package/dist/{hook/hook-server.d.ts → server/api-server.d.ts} +3 -3
  53. package/dist/{hook/hook-server.js → server/api-server.js} +6 -4
  54. package/dist/server/api-server.js.map +1 -0
  55. package/dist/utils/git-collector.js.map +1 -0
  56. package/dist/utils/markdown.d.ts +4 -0
  57. package/dist/utils/markdown.js +47 -0
  58. package/dist/utils/markdown.js.map +1 -0
  59. package/dist/utils/paths.d.ts +4 -1
  60. package/dist/utils/paths.js +5 -1
  61. package/dist/utils/paths.js.map +1 -1
  62. package/dist/utils/response-store.d.ts +2 -0
  63. package/dist/utils/response-store.js.map +1 -1
  64. package/dist/utils/stats-format.d.ts +3 -0
  65. package/dist/utils/stats-format.js +44 -0
  66. package/dist/utils/stats-format.js.map +1 -0
  67. package/package.json +4 -1
  68. package/dist/hook/hook-handler.d.ts +0 -10
  69. package/dist/hook/hook-handler.js +0 -76
  70. package/dist/hook/hook-handler.js.map +0 -1
  71. package/dist/hook/hook-installer.js +0 -110
  72. package/dist/hook/hook-installer.js.map +0 -1
  73. package/dist/hook/hook-server.js.map +0 -1
  74. package/dist/hook/response-store.d.ts +0 -8
  75. package/dist/hook/response-store.js +0 -18
  76. package/dist/monitor/git-collector.js.map +0 -1
  77. package/dist/monitor/transcript-parser.js.map +0 -1
  78. package/dist/setup.d.ts +0 -1
  79. package/dist/setup.js +0 -130
  80. package/dist/telegram/bot.d.ts +0 -20
  81. package/dist/telegram/bot.js +0 -130
  82. package/dist/telegram/message-formatter.d.ts +0 -18
  83. package/dist/telegram/message-formatter.js +0 -56
  84. package/dist/telegram/message-sender.d.ts +0 -2
  85. package/dist/telegram/message-sender.js +0 -76
  86. package/dist/utils/error-utils.d.ts +0 -1
  87. package/dist/utils/error-utils.js +0 -3
  88. /package/dist/{monitor → utils}/git-collector.d.ts +0 -0
  89. /package/dist/{monitor → utils}/git-collector.js +0 -0
package/README.en.md CHANGED
@@ -1,19 +1,19 @@
1
- # 🤖 ccpoke — Claude Code ↔ Telegram Notification Bot
1
+ # 🤖 ccpoke — AI Agent ↔ Telegram Notification Bot
2
2
 
3
3
  [Tiếng Việt](./README.md)
4
4
 
5
- > Get Telegram notifications when Claude Code completes a response — with git diff, processing time, and result summary.
5
+ > Get Telegram notifications when your AI agent (Claude Code, Cursor, ...) completes a response — with git diff, processing time, and result summary.
6
6
 
7
7
  ---
8
8
 
9
9
  ## Problem
10
10
 
11
- You're using Claude Code on your computer. You step away with your phone but have no idea if Claude Code is done yet or what files it changed.
11
+ You're using Claude Code or Cursor on your computer. You step away with your phone but have no idea if the AI agent is done yet or what files it changed.
12
12
 
13
- **ccpoke** is a lightweight bridge between Claude Code and Telegram — when Claude Code finishes, you get a notification right on your phone.
13
+ **ccpoke** is a lightweight bridge between AI agents and Telegram — when any agent finishes, you get a notification right on your phone.
14
14
 
15
15
  ```
16
- Claude Code completes response
16
+ AI agent completes response
17
17
 
18
18
  Stop Hook triggers
19
19
 
@@ -22,12 +22,22 @@ Claude Code completes response
22
22
  Telegram notification 📱
23
23
  ```
24
24
 
25
+ ## Supported Agents
26
+
27
+ | Agent | Status |
28
+ |-------|--------|
29
+ | Claude Code | ✅ Supported |
30
+ | Cursor | ✅ Supported |
31
+
32
+ Adding new agents is easy via the plugin architecture — contributions welcome!
33
+
25
34
  ## Features
26
35
 
27
- - 🔔 **Auto notification** — Claude Code finishes Telegram notifies you instantly
36
+ - 🤖 **Multi-agent** — supports Claude Code, Cursor and more
37
+ - 🔔 **Auto notification** — AI agent finishes → Telegram notifies you instantly
28
38
  - 📂 **Git diff included** — see changed files without opening your computer
29
- - ⏱ **Processing time** — know how long Claude Code took
30
- - 📝 **Response summary** — quick glance at what Claude Code replied
39
+ - ⏱ **Processing time** — know how long the agent took
40
+ - 📝 **Response summary** — quick glance at what the agent replied
31
41
  - 🔐 **User whitelist** — only authorized users can use the bot
32
42
  - 📄 **Auto-split messages** — long responses are automatically paginated `[1/N]`
33
43
 
@@ -89,8 +99,12 @@ The setup wizard will guide you step by step:
89
99
 
90
100
  ◆ ✓ Connected! User ID: 123456789
91
101
 
102
+ ◇ Select AI agents (space to toggle)
103
+ │ Claude Code, Cursor
104
+
92
105
  ◆ Config saved
93
- ◆ Hook installed ~/.claude/settings.json
106
+ ◆ Hook installed for Claude Code
107
+ ◆ Hook installed for Cursor
94
108
  ◆ Chat ID registered
95
109
 
96
110
  └ 🎉 Setup complete!
@@ -128,7 +142,7 @@ ccpoke
128
142
  pnpm dev
129
143
  ```
130
144
 
131
- Once running, use Claude Code as usual → notifications will arrive on Telegram.
145
+ Once running, use Claude Code / Cursor as usual → notifications will arrive on Telegram.
132
146
 
133
147
  ### Telegram Commands
134
148
 
@@ -163,7 +177,8 @@ ccpoke uninstall
163
177
  ```
164
178
  ┌ 🗑️ Uninstalling ccpoke
165
179
 
166
- ◆ Hook removed from ~/.claude/settings.json
180
+ ◆ Hook removed from Claude Code
181
+ ◆ Hook removed from Cursor
167
182
  ◆ Removed ~/.ccpoke/ (config, state, hooks)
168
183
 
169
184
  └ ccpoke uninstalled
package/README.md CHANGED
@@ -1,19 +1,19 @@
1
- # 🤖 ccpoke — Claude Code ↔ Telegram Notification Bot
1
+ # 🤖 ccpoke — AI Agent ↔ Telegram Notification Bot
2
2
 
3
3
  [English](./README.en.md)
4
4
 
5
- > Nhận thông báo Telegram khi Claude Code hoàn thành response — kèm git diff, thời gian xử lý, và tóm tắt kết quả.
5
+ > Nhận thông báo Telegram khi AI agent (Claude Code, Cursor, ...) hoàn thành response — kèm git diff, thời gian xử lý, và tóm tắt kết quả.
6
6
 
7
7
  ---
8
8
 
9
9
  ## Vấn đề giải quyết
10
10
 
11
- Bạn đang dùng Claude Code trên máy tính. Ra ngoài cầm điện thoại nhưng không biết Claude Code đã xong chưa, thay đổi file nào.
11
+ Bạn đang dùng Claude Code hoặc Cursor trên máy tính. Ra ngoài cầm điện thoại nhưng không biết AI agent đã xong chưa, thay đổi file nào.
12
12
 
13
- **ccpoke** là cầu nối nhẹ giữa Claude Code và Telegram — khi Claude Code xong việc, bạn nhận notification ngay trên điện thoại.
13
+ **ccpoke** là cầu nối nhẹ giữa AI agents và Telegram — khi agent xong việc, bạn nhận notification ngay trên điện thoại.
14
14
 
15
15
  ```
16
- Claude Code xong response
16
+ AI agent xong response
17
17
 
18
18
  Stop Hook trigger
19
19
 
@@ -22,12 +22,22 @@ Claude Code xong response
22
22
  Telegram notification 📱
23
23
  ```
24
24
 
25
+ ## Supported Agents
26
+
27
+ | Agent | Trạng thái |
28
+ |-------|-----------|
29
+ | Claude Code | ✅ Hỗ trợ |
30
+ | Cursor | ✅ Hỗ trợ |
31
+
32
+ Thêm agent mới qua kiến trúc plugin — contributions welcome!
33
+
25
34
  ## Tính năng
26
35
 
27
- - 🔔 **Notification tự động** — Claude Code xong Telegram nhận tin ngay
36
+ - 🤖 **Multi-agent** — hỗ trợ Claude Code, Cursor mở rộng thêm
37
+ - 🔔 **Notification tự động** — AI agent xong → Telegram nhận tin ngay
28
38
  - 📂 **Git diff kèm theo** — biết file nào thay đổi mà không cần mở máy tính
29
- - ⏱ **Thời gian xử lý** — biết Claude Code chạy bao lâu
30
- - 📝 **Tóm tắt response** — xem nhanh Claude Code trả lời gì
39
+ - ⏱ **Thời gian xử lý** — biết agent chạy bao lâu
40
+ - 📝 **Tóm tắt response** — xem nhanh agent trả lời gì
31
41
  - 🔐 **Whitelist user** — chỉ user được phép mới dùng được bot
32
42
  - 📄 **Auto-split message** — response dài tự động chia page `[1/N]`
33
43
 
@@ -89,8 +99,12 @@ Setup wizard sẽ hướng dẫn từng bước:
89
99
 
90
100
  ◆ ✓ Connected! User ID: 123456789
91
101
 
102
+ ◇ Chọn AI agents (ấn cách để chọn)
103
+ │ Claude Code, Cursor
104
+
92
105
  ◆ Config saved
93
- ◆ Hook installed ~/.claude/settings.json
106
+ ◆ Hook installed for Claude Code
107
+ ◆ Hook installed for Cursor
94
108
  ◆ Chat ID registered
95
109
 
96
110
  └ 🎉 Setup complete!
@@ -128,7 +142,7 @@ ccpoke
128
142
  pnpm dev
129
143
  ```
130
144
 
131
- Bot chạy xong → dùng Claude Code bình thường → notification tự đến Telegram.
145
+ Bot chạy xong → dùng Claude Code / Cursor bình thường → notification tự đến Telegram.
132
146
 
133
147
  ### Telegram Commands
134
148
 
@@ -163,7 +177,8 @@ ccpoke uninstall
163
177
  ```
164
178
  ┌ 🗑️ Uninstalling ccpoke
165
179
 
166
- ◆ Hook removed from ~/.claude/settings.json
180
+ ◆ Hook removed from Claude Code
181
+ ◆ Hook removed from Cursor
167
182
  ◆ Removed ~/.ccpoke/ (config, state, hooks)
168
183
 
169
184
  └ ccpoke uninstalled
@@ -0,0 +1,12 @@
1
+ import type { AgentRegistry } from "./agent-registry.js";
2
+ import type { NotificationChannel } from "../channel/types.js";
3
+ import type { TunnelManager } from "../utils/tunnel.js";
4
+ export declare class AgentHandler {
5
+ private registry;
6
+ private channel;
7
+ private hookPort;
8
+ private tunnelManager;
9
+ constructor(registry: AgentRegistry, channel: NotificationChannel, hookPort: number, tunnelManager: TunnelManager);
10
+ handleStopEvent(agentName: string, rawEvent: unknown): Promise<void>;
11
+ private buildResponseUrl;
12
+ }
@@ -0,0 +1,49 @@
1
+ import { MINI_APP_BASE_URL } from "../utils/constants.js";
2
+ import { responseStore } from "../utils/response-store.js";
3
+ import { t } from "../i18n/index.js";
4
+ import { log, logError } from "../utils/log.js";
5
+ export class AgentHandler {
6
+ registry;
7
+ channel;
8
+ hookPort;
9
+ tunnelManager;
10
+ constructor(registry, channel, hookPort, tunnelManager) {
11
+ this.registry = registry;
12
+ this.channel = channel;
13
+ this.hookPort = hookPort;
14
+ this.tunnelManager = tunnelManager;
15
+ }
16
+ async handleStopEvent(agentName, rawEvent) {
17
+ const provider = this.registry.resolve(agentName);
18
+ if (!provider) {
19
+ log(t("agent.unknownAgent", { agent: agentName }));
20
+ return;
21
+ }
22
+ if (provider.settleDelayMs > 0) {
23
+ await new Promise((resolve) => setTimeout(resolve, provider.settleDelayMs));
24
+ }
25
+ const result = provider.parseEvent(rawEvent);
26
+ const data = {
27
+ agent: provider.name,
28
+ agentDisplayName: provider.displayName,
29
+ ...result,
30
+ };
31
+ const responseUrl = this.buildResponseUrl(data);
32
+ this.channel.sendNotification(data, responseUrl).catch((err) => {
33
+ logError(t("hook.notificationFailed"), err);
34
+ });
35
+ }
36
+ buildResponseUrl(data) {
37
+ const id = responseStore.save(data);
38
+ const apiBase = this.tunnelManager.getPublicUrl() || `http://localhost:${this.hookPort}`;
39
+ const params = new URLSearchParams({
40
+ id,
41
+ api: apiBase,
42
+ p: data.projectName,
43
+ d: String(data.durationMs),
44
+ a: data.agent,
45
+ });
46
+ return `${MINI_APP_BASE_URL}/response/?${params.toString()}`;
47
+ }
48
+ }
49
+ //# sourceMappingURL=agent-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-handler.js","sourceRoot":"","sources":["../../src/agent/agent-handler.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,CAAC,EAAE,MAAM,kBAAkB,CAAC;AACrC,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAEhD,MAAM,OAAO,YAAY;IAEb;IACA;IACA;IACA;IAJV,YACU,QAAuB,EACvB,OAA4B,EAC5B,QAAgB,EAChB,aAA4B;QAH5B,aAAQ,GAAR,QAAQ,CAAe;QACvB,YAAO,GAAP,OAAO,CAAqB;QAC5B,aAAQ,GAAR,QAAQ,CAAQ;QAChB,kBAAa,GAAb,aAAa,CAAe;IACnC,CAAC;IAEJ,KAAK,CAAC,eAAe,CAAC,SAAiB,EAAE,QAAiB;QACxD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,GAAG,CAAC,CAAC,CAAC,oBAAoB,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QAED,IAAI,QAAQ,CAAC,aAAa,GAAG,CAAC,EAAE,CAAC;YAC/B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,aAAa,CAAC,CAAC,CAAC;QAC9E,CAAC;QAED,MAAM,MAAM,GAAG,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAE7C,MAAM,IAAI,GAAqB;YAC7B,KAAK,EAAE,QAAQ,CAAC,IAAI;YACpB,gBAAgB,EAAE,QAAQ,CAAC,WAAW;YACtC,GAAG,MAAM;SACV,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC;QAChD,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YACtE,QAAQ,CAAC,CAAC,CAAC,yBAAyB,CAAC,EAAE,GAAG,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,gBAAgB,CAAC,IAAsB;QAC7C,MAAM,EAAE,GAAG,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAEpC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,YAAY,EAAE,IAAI,oBAAoB,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzF,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,EAAE;YACF,GAAG,EAAE,OAAO;YACZ,CAAC,EAAE,IAAI,CAAC,WAAW;YACnB,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;YAC1B,CAAC,EAAE,IAAI,CAAC,KAAK;SACd,CAAC,CAAC;QACH,OAAO,GAAG,iBAAiB,cAAc,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC/D,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import type { AgentProvider } from "./types.js";
2
+ export declare class AgentRegistry {
3
+ private providers;
4
+ register(provider: AgentProvider): void;
5
+ resolve(name: string): AgentProvider | undefined;
6
+ all(): AgentProvider[];
7
+ detectInstalled(): AgentProvider[];
8
+ }
9
+ export declare function createDefaultRegistry(): AgentRegistry;
@@ -0,0 +1,24 @@
1
+ import { ClaudeCodeProvider } from "./claude-code/claude-code-provider.js";
2
+ import { CursorProvider } from "./cursor/cursor-provider.js";
3
+ export class AgentRegistry {
4
+ providers = new Map();
5
+ register(provider) {
6
+ this.providers.set(provider.name, provider);
7
+ }
8
+ resolve(name) {
9
+ return this.providers.get(name);
10
+ }
11
+ all() {
12
+ return [...this.providers.values()];
13
+ }
14
+ detectInstalled() {
15
+ return this.all().filter((p) => p.detect());
16
+ }
17
+ }
18
+ export function createDefaultRegistry() {
19
+ const registry = new AgentRegistry();
20
+ registry.register(new ClaudeCodeProvider());
21
+ registry.register(new CursorProvider());
22
+ return registry;
23
+ }
24
+ //# sourceMappingURL=agent-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-registry.js","sourceRoot":"","sources":["../../src/agent/agent-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uCAAuC,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAE7D,MAAM,OAAO,aAAa;IAChB,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAC;IAErD,QAAQ,CAAC,QAAuB;QAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC9C,CAAC;IAED,OAAO,CAAC,IAAY;QAClB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,GAAG;QACD,OAAO,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACtC,CAAC;IAED,eAAe;QACb,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC9C,CAAC;CACF;AAED,MAAM,UAAU,qBAAqB;IACnC,MAAM,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;IACrC,QAAQ,CAAC,QAAQ,CAAC,IAAI,kBAAkB,EAAE,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC;IACxC,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -1,8 +1,8 @@
1
- export declare class HookInstaller {
1
+ export declare class ClaudeCodeInstaller {
2
2
  static isInstalled(): boolean;
3
3
  static install(hookPort: number, hookSecret: string): void;
4
4
  static uninstall(): void;
5
- private static installScript;
5
+ private static writeScript;
6
6
  private static removeScript;
7
7
  private static removeFromSettings;
8
8
  private static readSettings;
@@ -0,0 +1,82 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
2
+ import { paths } from "../../utils/paths.js";
3
+ import { ApiRoute } from "../../utils/constants.js";
4
+ import { AgentName } from "../types.js";
5
+ function hasCcpokeHook(entries) {
6
+ return entries.some((entry) => entry.hooks?.some((h) => typeof h.command === "string" && h.command.includes("ccpoke")));
7
+ }
8
+ export class ClaudeCodeInstaller {
9
+ static isInstalled() {
10
+ try {
11
+ const settings = ClaudeCodeInstaller.readSettings();
12
+ return hasCcpokeHook(settings.hooks?.Stop ?? []);
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ static install(hookPort, hookSecret) {
19
+ const settings = ClaudeCodeInstaller.readSettings();
20
+ if (!settings.hooks)
21
+ settings.hooks = {};
22
+ const stopEntries = settings.hooks.Stop ?? [];
23
+ if (hasCcpokeHook(stopEntries))
24
+ return;
25
+ stopEntries.push({
26
+ hooks: [{ type: "command", command: paths.claudeCodeHookScript, timeout: 10 }],
27
+ });
28
+ settings.hooks.Stop = stopEntries;
29
+ mkdirSync(paths.claudeDir, { recursive: true });
30
+ writeFileSync(paths.claudeSettings, JSON.stringify(settings, null, 2));
31
+ ClaudeCodeInstaller.writeScript(hookPort, hookSecret);
32
+ }
33
+ static uninstall() {
34
+ ClaudeCodeInstaller.removeFromSettings();
35
+ ClaudeCodeInstaller.removeScript();
36
+ }
37
+ static writeScript(hookPort, hookSecret) {
38
+ mkdirSync(paths.hooksDir, { recursive: true });
39
+ const agentParam = `?agent=${AgentName.ClaudeCode}`;
40
+ const isWindows = process.platform === "win32";
41
+ const script = isWindows
42
+ ? `@echo off\ncurl -s -X POST http://localhost:${hookPort}${ApiRoute.HookStop}${agentParam} -H "Content-Type: application/json" -H "X-CCPoke-Secret: ${hookSecret}" --data-binary @- > nul 2>&1\n`
43
+ : `#!/bin/bash\ncurl -s -X POST http://localhost:${hookPort}${ApiRoute.HookStop}${agentParam} \\\n -H "Content-Type: application/json" \\\n -H "X-CCPoke-Secret: ${hookSecret}" \\\n --data-binary @- > /dev/null 2>&1 || true\n`;
44
+ writeFileSync(paths.claudeCodeHookScript, script, { mode: isWindows ? 0o644 : 0o755 });
45
+ }
46
+ static removeScript() {
47
+ try {
48
+ unlinkSync(paths.claudeCodeHookScript);
49
+ }
50
+ catch {
51
+ // script file may not exist
52
+ }
53
+ }
54
+ static removeFromSettings() {
55
+ const settings = ClaudeCodeInstaller.readSettings();
56
+ if (!settings.hooks?.Stop)
57
+ return;
58
+ const filtered = settings.hooks.Stop.filter((entry) => !entry.hooks?.some((h) => typeof h.command === "string" && h.command.includes("ccpoke")));
59
+ if (filtered.length === 0) {
60
+ delete settings.hooks.Stop;
61
+ }
62
+ else {
63
+ settings.hooks.Stop = filtered;
64
+ }
65
+ if (Object.keys(settings.hooks).length === 0) {
66
+ delete settings.hooks;
67
+ }
68
+ writeFileSync(paths.claudeSettings, JSON.stringify(settings, null, 2));
69
+ }
70
+ static readSettings() {
71
+ try {
72
+ return JSON.parse(readFileSync(paths.claudeSettings, "utf-8"));
73
+ }
74
+ catch (err) {
75
+ const isFileNotFound = err instanceof Error && err.code === "ENOENT";
76
+ if (isFileNotFound)
77
+ return {};
78
+ throw err;
79
+ }
80
+ }
81
+ }
82
+ //# sourceMappingURL=claude-code-installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code-installer.js","sourceRoot":"","sources":["../../../src/agent/claude-code/claude-code-installer.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,8 @@
1
+ export interface StopEvent {
2
+ session_id: string;
3
+ transcript_path: string;
4
+ cwd: string;
5
+ }
1
6
  export interface TranscriptSummary {
2
7
  lastAssistantMessage: string;
3
8
  durationMs: number;
@@ -5,4 +10,5 @@ export interface TranscriptSummary {
5
10
  outputTokens: number;
6
11
  model: string;
7
12
  }
13
+ export declare function isValidStopEvent(data: unknown): data is StopEvent;
8
14
  export declare function parseTranscript(transcriptPath: string): TranscriptSummary;
@@ -1,5 +1,13 @@
1
1
  import { readFileSync } from "node:fs";
2
- import { expandHome } from "../utils/paths.js";
2
+ import { expandHome } from "../../utils/paths.js";
3
+ export function isValidStopEvent(data) {
4
+ if (typeof data !== "object" || data === null)
5
+ return false;
6
+ const obj = data;
7
+ return (typeof obj.session_id === "string" &&
8
+ typeof obj.transcript_path === "string" &&
9
+ typeof obj.cwd === "string");
10
+ }
3
11
  export function parseTranscript(transcriptPath) {
4
12
  const expandedPath = expandHome(transcriptPath);
5
13
  const raw = readFileSync(expandedPath, "utf-8");
@@ -70,4 +78,4 @@ function extractTextFromContent(parts) {
70
78
  .map((p) => p.text)
71
79
  .join("\n");
72
80
  }
73
- //# sourceMappingURL=transcript-parser.js.map
81
+ //# sourceMappingURL=claude-code-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code-parser.js","sourceRoot":"","sources":["../../../src/agent/claude-code/claude-code-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { AgentProvider, AgentEventResult } from "../types.js";
2
+ export declare class ClaudeCodeProvider implements AgentProvider {
3
+ readonly name: "claude-code";
4
+ readonly displayName: string;
5
+ readonly settleDelayMs = 500;
6
+ detect(): boolean;
7
+ isHookInstalled(): boolean;
8
+ installHook(port: number, secret: string): void;
9
+ uninstallHook(): void;
10
+ parseEvent(raw: unknown): AgentEventResult;
11
+ private createFallbackResult;
12
+ }
@@ -0,0 +1,72 @@
1
+ import { existsSync } from "node:fs";
2
+ import { AgentName, AGENT_DISPLAY_NAMES } from "../types.js";
3
+ import { ClaudeCodeInstaller } from "./claude-code-installer.js";
4
+ import { isValidStopEvent, parseTranscript } from "./claude-code-parser.js";
5
+ import { collectGitChanges } from "../../utils/git-collector.js";
6
+ import { extractProjectName, paths } from "../../utils/paths.js";
7
+ import { DEFAULT_FALLBACK_DURATION_MS, TRANSCRIPT_SETTLE_DELAY_MS } from "../../utils/constants.js";
8
+ import { logError } from "../../utils/log.js";
9
+ import { t } from "../../i18n/index.js";
10
+ export class ClaudeCodeProvider {
11
+ name = AgentName.ClaudeCode;
12
+ displayName = AGENT_DISPLAY_NAMES[AgentName.ClaudeCode];
13
+ settleDelayMs = TRANSCRIPT_SETTLE_DELAY_MS;
14
+ detect() {
15
+ return existsSync(paths.claudeDir);
16
+ }
17
+ isHookInstalled() {
18
+ return ClaudeCodeInstaller.isInstalled();
19
+ }
20
+ installHook(port, secret) {
21
+ ClaudeCodeInstaller.install(port, secret);
22
+ }
23
+ uninstallHook() {
24
+ ClaudeCodeInstaller.uninstall();
25
+ }
26
+ parseEvent(raw) {
27
+ if (!isValidStopEvent(raw)) {
28
+ return this.createFallbackResult(raw);
29
+ }
30
+ let summary = {
31
+ lastAssistantMessage: "",
32
+ durationMs: 0,
33
+ inputTokens: 0,
34
+ outputTokens: 0,
35
+ model: "",
36
+ };
37
+ try {
38
+ summary = parseTranscript(raw.transcript_path);
39
+ }
40
+ catch (err) {
41
+ logError(t("hook.transcriptFailed"), err);
42
+ }
43
+ const gitChanges = collectGitChanges(raw.cwd);
44
+ let durationMs = Math.max(0, summary.durationMs);
45
+ if (durationMs === 0 && summary.lastAssistantMessage) {
46
+ durationMs = DEFAULT_FALLBACK_DURATION_MS;
47
+ }
48
+ return {
49
+ projectName: extractProjectName(raw.cwd),
50
+ responseSummary: summary.lastAssistantMessage,
51
+ durationMs,
52
+ gitChanges,
53
+ inputTokens: summary.inputTokens,
54
+ outputTokens: summary.outputTokens,
55
+ model: summary.model,
56
+ };
57
+ }
58
+ createFallbackResult(raw) {
59
+ const obj = (typeof raw === "object" && raw !== null ? raw : {});
60
+ const cwd = typeof obj.cwd === "string" ? obj.cwd : process.cwd();
61
+ return {
62
+ projectName: extractProjectName(cwd),
63
+ responseSummary: "",
64
+ durationMs: 0,
65
+ gitChanges: collectGitChanges(cwd),
66
+ inputTokens: 0,
67
+ outputTokens: 0,
68
+ model: "",
69
+ };
70
+ }
71
+ }
72
+ //# sourceMappingURL=claude-code-provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude-code-provider.js","sourceRoot":"","sources":["../../../src/agent/claude-code/claude-code-provider.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare class CursorInstaller {
2
+ static isInstalled(): boolean;
3
+ static install(hookPort: number, hookSecret: string): void;
4
+ static uninstall(): void;
5
+ private static writeScript;
6
+ private static removeScript;
7
+ private static removeFromHooksJson;
8
+ private static readConfig;
9
+ }
@@ -0,0 +1,89 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, unlinkSync, existsSync } from "node:fs";
2
+ import { paths } from "../../utils/paths.js";
3
+ import { ApiRoute } from "../../utils/constants.js";
4
+ import { AgentName } from "../types.js";
5
+ function hasCcpokeHook(stopHooks) {
6
+ return stopHooks.some((entry) => typeof entry.command === "string" && entry.command.includes("ccpoke"));
7
+ }
8
+ export class CursorInstaller {
9
+ static isInstalled() {
10
+ try {
11
+ if (!existsSync(paths.cursorHooksJson))
12
+ return false;
13
+ const config = CursorInstaller.readConfig();
14
+ return hasCcpokeHook(config.hooks?.stop ?? []);
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ static install(hookPort, hookSecret) {
21
+ mkdirSync(paths.cursorDir, { recursive: true });
22
+ const config = CursorInstaller.readConfig();
23
+ if (!config.hooks)
24
+ config.hooks = {};
25
+ const stopHooks = config.hooks.stop ?? [];
26
+ if (hasCcpokeHook(stopHooks))
27
+ return;
28
+ stopHooks.push({
29
+ command: paths.cursorHookScript,
30
+ timeout: 10,
31
+ });
32
+ config.hooks.stop = stopHooks;
33
+ if (!config.version)
34
+ config.version = 1;
35
+ writeFileSync(paths.cursorHooksJson, JSON.stringify(config, null, 2));
36
+ CursorInstaller.writeScript(hookPort, hookSecret);
37
+ }
38
+ static uninstall() {
39
+ CursorInstaller.removeFromHooksJson();
40
+ CursorInstaller.removeScript();
41
+ }
42
+ static writeScript(hookPort, hookSecret) {
43
+ mkdirSync(paths.hooksDir, { recursive: true });
44
+ const agentParam = `?agent=${AgentName.Cursor}`;
45
+ const isWindows = process.platform === "win32";
46
+ const script = isWindows
47
+ ? `@echo off\ncurl -s -X POST http://localhost:${hookPort}${ApiRoute.HookStop}${agentParam} -H "Content-Type: application/json" -H "X-CCPoke-Secret: ${hookSecret}" --data-binary @- > nul 2>&1\n`
48
+ : `#!/bin/bash\ncurl -s -X POST http://localhost:${hookPort}${ApiRoute.HookStop}${agentParam} \\\n -H "Content-Type: application/json" \\\n -H "X-CCPoke-Secret: ${hookSecret}" \\\n --data-binary @- > /dev/null 2>&1 || true\n`;
49
+ writeFileSync(paths.cursorHookScript, script, { mode: isWindows ? 0o644 : 0o755 });
50
+ }
51
+ static removeScript() {
52
+ try {
53
+ unlinkSync(paths.cursorHookScript);
54
+ }
55
+ catch {
56
+ // script file may not exist
57
+ }
58
+ }
59
+ static removeFromHooksJson() {
60
+ if (!existsSync(paths.cursorHooksJson))
61
+ return;
62
+ const config = CursorInstaller.readConfig();
63
+ if (!config.hooks?.stop)
64
+ return;
65
+ const filtered = config.hooks.stop.filter((entry) => !(typeof entry.command === "string" && entry.command.includes("ccpoke")));
66
+ if (filtered.length === 0) {
67
+ delete config.hooks.stop;
68
+ }
69
+ else {
70
+ config.hooks.stop = filtered;
71
+ }
72
+ if (Object.keys(config.hooks).length === 0) {
73
+ delete config.hooks;
74
+ }
75
+ writeFileSync(paths.cursorHooksJson, JSON.stringify(config, null, 2));
76
+ }
77
+ static readConfig() {
78
+ try {
79
+ return JSON.parse(readFileSync(paths.cursorHooksJson, "utf-8"));
80
+ }
81
+ catch (err) {
82
+ const isFileNotFound = err instanceof Error && err.code === "ENOENT";
83
+ if (isFileNotFound)
84
+ return { version: 1, hooks: {} };
85
+ throw err;
86
+ }
87
+ }
88
+ }
89
+ //# sourceMappingURL=cursor-installer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-installer.js","sourceRoot":"","sources":["../../../src/agent/cursor/cursor-installer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface StopEvent {
2
+ conversationId: string;
3
+ model: string;
4
+ status: string;
5
+ transcriptPath: string;
6
+ cursorVersion: string;
7
+ cwd: string;
8
+ projectName: string;
9
+ }
10
+ export interface TranscriptSummary {
11
+ lastAssistantMessage: string;
12
+ durationMs: number;
13
+ inputTokens: number;
14
+ outputTokens: number;
15
+ model: string;
16
+ }
17
+ export declare function isValidStopEvent(data: unknown): data is Record<string, unknown>;
18
+ export declare function parseStopEvent(raw: Record<string, unknown>): StopEvent;
19
+ export declare function parseTranscript(transcriptPath: string): TranscriptSummary;