@jsayubi/ccgram 1.0.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 (247) hide show
  1. package/.env.example +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +338 -0
  4. package/ccgram.service +24 -0
  5. package/config/channels.json +58 -0
  6. package/config/default.json +27 -0
  7. package/config/defaults/config.json +16 -0
  8. package/config/defaults/i18n.json +32 -0
  9. package/config/email-template.json +31 -0
  10. package/config/test-with-subagent.json +16 -0
  11. package/config/user.json +27 -0
  12. package/dist/claude-hook-notify.d.ts +7 -0
  13. package/dist/claude-hook-notify.d.ts.map +1 -0
  14. package/dist/claude-hook-notify.js +154 -0
  15. package/dist/claude-hook-notify.js.map +1 -0
  16. package/dist/claude-remote.d.ts +50 -0
  17. package/dist/claude-remote.d.ts.map +1 -0
  18. package/dist/claude-remote.js +927 -0
  19. package/dist/claude-remote.js.map +1 -0
  20. package/dist/cli.d.ts +3 -0
  21. package/dist/cli.d.ts.map +1 -0
  22. package/dist/cli.js +110 -0
  23. package/dist/cli.js.map +1 -0
  24. package/dist/enhanced-hook-notify.d.ts +16 -0
  25. package/dist/enhanced-hook-notify.d.ts.map +1 -0
  26. package/dist/enhanced-hook-notify.js +288 -0
  27. package/dist/enhanced-hook-notify.js.map +1 -0
  28. package/dist/permission-hook.d.ts +15 -0
  29. package/dist/permission-hook.d.ts.map +1 -0
  30. package/dist/permission-hook.js +357 -0
  31. package/dist/permission-hook.js.map +1 -0
  32. package/dist/prompt-bridge.d.ts +50 -0
  33. package/dist/prompt-bridge.d.ts.map +1 -0
  34. package/dist/prompt-bridge.js +173 -0
  35. package/dist/prompt-bridge.js.map +1 -0
  36. package/dist/question-notify.d.ts +16 -0
  37. package/dist/question-notify.d.ts.map +1 -0
  38. package/dist/question-notify.js +272 -0
  39. package/dist/question-notify.js.map +1 -0
  40. package/dist/setup.d.ts +10 -0
  41. package/dist/setup.d.ts.map +1 -0
  42. package/dist/setup.js +649 -0
  43. package/dist/setup.js.map +1 -0
  44. package/dist/smart-monitor.d.ts +7 -0
  45. package/dist/smart-monitor.d.ts.map +1 -0
  46. package/dist/smart-monitor.js +256 -0
  47. package/dist/smart-monitor.js.map +1 -0
  48. package/dist/src/automation/claude-automation.d.ts +45 -0
  49. package/dist/src/automation/claude-automation.d.ts.map +1 -0
  50. package/dist/src/automation/claude-automation.js +367 -0
  51. package/dist/src/automation/claude-automation.js.map +1 -0
  52. package/dist/src/automation/clipboard-automation.d.ts +35 -0
  53. package/dist/src/automation/clipboard-automation.d.ts.map +1 -0
  54. package/dist/src/automation/clipboard-automation.js +242 -0
  55. package/dist/src/automation/clipboard-automation.js.map +1 -0
  56. package/dist/src/automation/simple-automation.d.ts +56 -0
  57. package/dist/src/automation/simple-automation.d.ts.map +1 -0
  58. package/dist/src/automation/simple-automation.js +283 -0
  59. package/dist/src/automation/simple-automation.js.map +1 -0
  60. package/dist/src/channels/base/channel.d.ts +60 -0
  61. package/dist/src/channels/base/channel.d.ts.map +1 -0
  62. package/dist/src/channels/base/channel.js +96 -0
  63. package/dist/src/channels/base/channel.js.map +1 -0
  64. package/dist/src/channels/email/smtp.d.ts +74 -0
  65. package/dist/src/channels/email/smtp.d.ts.map +1 -0
  66. package/dist/src/channels/email/smtp.js +605 -0
  67. package/dist/src/channels/email/smtp.js.map +1 -0
  68. package/dist/src/channels/line/line.d.ts +36 -0
  69. package/dist/src/channels/line/line.d.ts.map +1 -0
  70. package/dist/src/channels/line/line.js +180 -0
  71. package/dist/src/channels/line/line.js.map +1 -0
  72. package/dist/src/channels/line/webhook.d.ts +55 -0
  73. package/dist/src/channels/line/webhook.d.ts.map +1 -0
  74. package/dist/src/channels/line/webhook.js +191 -0
  75. package/dist/src/channels/line/webhook.js.map +1 -0
  76. package/dist/src/channels/local/desktop.d.ts +30 -0
  77. package/dist/src/channels/local/desktop.d.ts.map +1 -0
  78. package/dist/src/channels/local/desktop.js +161 -0
  79. package/dist/src/channels/local/desktop.js.map +1 -0
  80. package/dist/src/channels/telegram/telegram.d.ts +43 -0
  81. package/dist/src/channels/telegram/telegram.d.ts.map +1 -0
  82. package/dist/src/channels/telegram/telegram.js +223 -0
  83. package/dist/src/channels/telegram/telegram.js.map +1 -0
  84. package/dist/src/channels/telegram/webhook.d.ts +75 -0
  85. package/dist/src/channels/telegram/webhook.d.ts.map +1 -0
  86. package/dist/src/channels/telegram/webhook.js +278 -0
  87. package/dist/src/channels/telegram/webhook.js.map +1 -0
  88. package/dist/src/config-manager.d.ts +16 -0
  89. package/dist/src/config-manager.d.ts.map +1 -0
  90. package/dist/src/config-manager.js +152 -0
  91. package/dist/src/config-manager.js.map +1 -0
  92. package/dist/src/core/config.d.ts +28 -0
  93. package/dist/src/core/config.d.ts.map +1 -0
  94. package/dist/src/core/config.js +248 -0
  95. package/dist/src/core/config.js.map +1 -0
  96. package/dist/src/core/logger.d.ts +19 -0
  97. package/dist/src/core/logger.d.ts.map +1 -0
  98. package/dist/src/core/logger.js +47 -0
  99. package/dist/src/core/logger.js.map +1 -0
  100. package/dist/src/core/notifier.d.ts +45 -0
  101. package/dist/src/core/notifier.d.ts.map +1 -0
  102. package/dist/src/core/notifier.js +189 -0
  103. package/dist/src/core/notifier.js.map +1 -0
  104. package/dist/src/daemon/taskping-daemon.d.ts +38 -0
  105. package/dist/src/daemon/taskping-daemon.d.ts.map +1 -0
  106. package/dist/src/daemon/taskping-daemon.js +306 -0
  107. package/dist/src/daemon/taskping-daemon.js.map +1 -0
  108. package/dist/src/relay/claude-command-bridge.d.ts +57 -0
  109. package/dist/src/relay/claude-command-bridge.d.ts.map +1 -0
  110. package/dist/src/relay/claude-command-bridge.js +188 -0
  111. package/dist/src/relay/claude-command-bridge.js.map +1 -0
  112. package/dist/src/relay/command-relay.d.ts +94 -0
  113. package/dist/src/relay/command-relay.d.ts.map +1 -0
  114. package/dist/src/relay/command-relay.js +463 -0
  115. package/dist/src/relay/command-relay.js.map +1 -0
  116. package/dist/src/relay/email-listener.d.ts +65 -0
  117. package/dist/src/relay/email-listener.d.ts.map +1 -0
  118. package/dist/src/relay/email-listener.js +460 -0
  119. package/dist/src/relay/email-listener.js.map +1 -0
  120. package/dist/src/relay/relay-pty.d.ts +21 -0
  121. package/dist/src/relay/relay-pty.d.ts.map +1 -0
  122. package/dist/src/relay/relay-pty.js +696 -0
  123. package/dist/src/relay/relay-pty.js.map +1 -0
  124. package/dist/src/relay/smart-injector.d.ts +30 -0
  125. package/dist/src/relay/smart-injector.d.ts.map +1 -0
  126. package/dist/src/relay/smart-injector.js +233 -0
  127. package/dist/src/relay/smart-injector.js.map +1 -0
  128. package/dist/src/relay/tmux-injector.d.ts +46 -0
  129. package/dist/src/relay/tmux-injector.d.ts.map +1 -0
  130. package/dist/src/relay/tmux-injector.js +413 -0
  131. package/dist/src/relay/tmux-injector.js.map +1 -0
  132. package/dist/src/tools/config-manager.d.ts +33 -0
  133. package/dist/src/tools/config-manager.d.ts.map +1 -0
  134. package/dist/src/tools/config-manager.js +448 -0
  135. package/dist/src/tools/config-manager.js.map +1 -0
  136. package/dist/src/tools/installer.d.ts +38 -0
  137. package/dist/src/tools/installer.d.ts.map +1 -0
  138. package/dist/src/tools/installer.js +222 -0
  139. package/dist/src/tools/installer.js.map +1 -0
  140. package/dist/src/types/callbacks.d.ts +29 -0
  141. package/dist/src/types/callbacks.d.ts.map +1 -0
  142. package/dist/src/types/callbacks.js +7 -0
  143. package/dist/src/types/callbacks.js.map +1 -0
  144. package/dist/src/types/config.d.ts +56 -0
  145. package/dist/src/types/config.d.ts.map +1 -0
  146. package/dist/src/types/config.js +6 -0
  147. package/dist/src/types/config.js.map +1 -0
  148. package/dist/src/types/hooks.d.ts +47 -0
  149. package/dist/src/types/hooks.d.ts.map +1 -0
  150. package/dist/src/types/hooks.js +6 -0
  151. package/dist/src/types/hooks.js.map +1 -0
  152. package/dist/src/types/index.d.ts +7 -0
  153. package/dist/src/types/index.d.ts.map +1 -0
  154. package/dist/src/types/index.js +23 -0
  155. package/dist/src/types/index.js.map +1 -0
  156. package/dist/src/types/ipc.d.ts +43 -0
  157. package/dist/src/types/ipc.d.ts.map +1 -0
  158. package/dist/src/types/ipc.js +7 -0
  159. package/dist/src/types/ipc.js.map +1 -0
  160. package/dist/src/types/session.d.ts +70 -0
  161. package/dist/src/types/session.d.ts.map +1 -0
  162. package/dist/src/types/session.js +9 -0
  163. package/dist/src/types/session.js.map +1 -0
  164. package/dist/src/types/telegram.d.ts +58 -0
  165. package/dist/src/types/telegram.d.ts.map +1 -0
  166. package/dist/src/types/telegram.js +6 -0
  167. package/dist/src/types/telegram.js.map +1 -0
  168. package/dist/src/utils/active-check.d.ts +19 -0
  169. package/dist/src/utils/active-check.d.ts.map +1 -0
  170. package/dist/src/utils/active-check.js +41 -0
  171. package/dist/src/utils/active-check.js.map +1 -0
  172. package/dist/src/utils/callback-parser.d.ts +21 -0
  173. package/dist/src/utils/callback-parser.d.ts.map +1 -0
  174. package/dist/src/utils/callback-parser.js +58 -0
  175. package/dist/src/utils/callback-parser.js.map +1 -0
  176. package/dist/src/utils/controller-injector.d.ts +21 -0
  177. package/dist/src/utils/controller-injector.d.ts.map +1 -0
  178. package/dist/src/utils/controller-injector.js +108 -0
  179. package/dist/src/utils/controller-injector.js.map +1 -0
  180. package/dist/src/utils/conversation-tracker.d.ts +32 -0
  181. package/dist/src/utils/conversation-tracker.d.ts.map +1 -0
  182. package/dist/src/utils/conversation-tracker.js +119 -0
  183. package/dist/src/utils/conversation-tracker.js.map +1 -0
  184. package/dist/src/utils/http-request.d.ts +25 -0
  185. package/dist/src/utils/http-request.d.ts.map +1 -0
  186. package/dist/src/utils/http-request.js +66 -0
  187. package/dist/src/utils/http-request.js.map +1 -0
  188. package/dist/src/utils/optional-require.d.ts +13 -0
  189. package/dist/src/utils/optional-require.d.ts.map +1 -0
  190. package/dist/src/utils/optional-require.js +37 -0
  191. package/dist/src/utils/optional-require.js.map +1 -0
  192. package/dist/src/utils/paths.d.ts +11 -0
  193. package/dist/src/utils/paths.d.ts.map +1 -0
  194. package/dist/src/utils/paths.js +28 -0
  195. package/dist/src/utils/paths.js.map +1 -0
  196. package/dist/src/utils/pty-session-manager.d.ts +42 -0
  197. package/dist/src/utils/pty-session-manager.d.ts.map +1 -0
  198. package/dist/src/utils/pty-session-manager.js +182 -0
  199. package/dist/src/utils/pty-session-manager.js.map +1 -0
  200. package/dist/src/utils/subagent-tracker.d.ts +64 -0
  201. package/dist/src/utils/subagent-tracker.d.ts.map +1 -0
  202. package/dist/src/utils/subagent-tracker.js +191 -0
  203. package/dist/src/utils/subagent-tracker.js.map +1 -0
  204. package/dist/src/utils/tmux-monitor.d.ts +102 -0
  205. package/dist/src/utils/tmux-monitor.d.ts.map +1 -0
  206. package/dist/src/utils/tmux-monitor.js +642 -0
  207. package/dist/src/utils/tmux-monitor.js.map +1 -0
  208. package/dist/src/utils/trace-capture.d.ts +42 -0
  209. package/dist/src/utils/trace-capture.d.ts.map +1 -0
  210. package/dist/src/utils/trace-capture.js +102 -0
  211. package/dist/src/utils/trace-capture.js.map +1 -0
  212. package/dist/start-all-webhooks.d.ts +7 -0
  213. package/dist/start-all-webhooks.d.ts.map +1 -0
  214. package/dist/start-all-webhooks.js +98 -0
  215. package/dist/start-all-webhooks.js.map +1 -0
  216. package/dist/start-line-webhook.d.ts +7 -0
  217. package/dist/start-line-webhook.d.ts.map +1 -0
  218. package/dist/start-line-webhook.js +59 -0
  219. package/dist/start-line-webhook.js.map +1 -0
  220. package/dist/start-relay-pty.d.ts +7 -0
  221. package/dist/start-relay-pty.d.ts.map +1 -0
  222. package/dist/start-relay-pty.js +173 -0
  223. package/dist/start-relay-pty.js.map +1 -0
  224. package/dist/start-telegram-webhook.d.ts +7 -0
  225. package/dist/start-telegram-webhook.d.ts.map +1 -0
  226. package/dist/start-telegram-webhook.js +80 -0
  227. package/dist/start-telegram-webhook.js.map +1 -0
  228. package/dist/user-prompt-hook.d.ts +13 -0
  229. package/dist/user-prompt-hook.d.ts.map +1 -0
  230. package/dist/user-prompt-hook.js +45 -0
  231. package/dist/user-prompt-hook.js.map +1 -0
  232. package/dist/workspace-router.d.ts +78 -0
  233. package/dist/workspace-router.d.ts.map +1 -0
  234. package/dist/workspace-router.js +408 -0
  235. package/dist/workspace-router.js.map +1 -0
  236. package/dist/workspace-telegram-bot.d.ts +3 -0
  237. package/dist/workspace-telegram-bot.d.ts.map +1 -0
  238. package/dist/workspace-telegram-bot.js +1172 -0
  239. package/dist/workspace-telegram-bot.js.map +1 -0
  240. package/package.json +80 -0
  241. package/src/types/callbacks.ts +39 -0
  242. package/src/types/config.ts +63 -0
  243. package/src/types/hooks.ts +50 -0
  244. package/src/types/index.ts +6 -0
  245. package/src/types/ipc.ts +55 -0
  246. package/src/types/session.ts +72 -0
  247. package/src/types/telegram.ts +66 -0
package/dist/setup.js ADDED
@@ -0,0 +1,649 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * CCGram interactive setup
5
+ * - Guides user through Telegram bot configuration
6
+ * - Installs to ~/.ccgram/ for persistent hook paths
7
+ * - Merges required hooks into ~/.claude/settings.json
8
+ * - Generates launchd/systemd service file
9
+ */
10
+ var __importDefault = (this && this.__importDefault) || function (mod) {
11
+ return (mod && mod.__esModule) ? mod : { "default": mod };
12
+ };
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ const fs_1 = __importDefault(require("fs"));
15
+ const os_1 = __importDefault(require("os"));
16
+ const path_1 = __importDefault(require("path"));
17
+ const readline_1 = __importDefault(require("readline"));
18
+ const https_1 = __importDefault(require("https"));
19
+ const child_process_1 = require("child_process");
20
+ const dotenv_1 = __importDefault(require("dotenv"));
21
+ const paths_1 = require("./src/utils/paths");
22
+ let projectRoot = paths_1.PROJECT_ROOT;
23
+ let envPath = path_1.default.join(projectRoot, '.env');
24
+ let defaultSessionMap = path_1.default.join(paths_1.PROJECT_ROOT, 'src', 'data', 'session-map.json');
25
+ // Hook definitions for Claude Code integration
26
+ const HOOK_DEFINITIONS = [
27
+ { event: 'PermissionRequest', script: 'permission-hook.js', timeout: 120 },
28
+ { event: 'PreToolUse', script: 'question-notify.js', timeout: 5, matcher: 'AskUserQuestion' },
29
+ { event: 'Stop', script: 'enhanced-hook-notify.js', args: 'completed', timeout: 5 },
30
+ { event: 'Notification', script: 'enhanced-hook-notify.js', args: 'waiting', timeout: 5, matcher: 'permission_prompt' },
31
+ { event: 'UserPromptSubmit', script: 'user-prompt-hook.js', timeout: 2 },
32
+ { event: 'SessionStart', script: 'enhanced-hook-notify.js', args: 'session-start', timeout: 5 },
33
+ { event: 'SessionEnd', script: 'enhanced-hook-notify.js', args: 'session-end', timeout: 5 },
34
+ { event: 'SubagentStop', script: 'enhanced-hook-notify.js', args: 'subagent-done', timeout: 5 },
35
+ ];
36
+ // ANSI color codes
37
+ const colors = {
38
+ reset: '\x1b[0m',
39
+ bright: '\x1b[1m',
40
+ dim: '\x1b[2m',
41
+ underscore: '\x1b[4m',
42
+ blink: '\x1b[5m',
43
+ reverse: '\x1b[7m',
44
+ hidden: '\x1b[8m',
45
+ // Foreground colors
46
+ black: '\x1b[30m',
47
+ red: '\x1b[31m',
48
+ green: '\x1b[32m',
49
+ yellow: '\x1b[33m',
50
+ blue: '\x1b[34m',
51
+ magenta: '\x1b[35m',
52
+ cyan: '\x1b[36m',
53
+ white: '\x1b[37m',
54
+ gray: '\x1b[90m',
55
+ // Background colors
56
+ bgRed: '\x1b[41m',
57
+ bgGreen: '\x1b[42m',
58
+ bgYellow: '\x1b[43m',
59
+ bgBlue: '\x1b[44m',
60
+ bgMagenta: '\x1b[45m',
61
+ bgCyan: '\x1b[46m',
62
+ bgWhite: '\x1b[47m'
63
+ };
64
+ // Icons
65
+ const icons = {
66
+ check: '\u2713',
67
+ cross: '\u2717',
68
+ info: '\u2139',
69
+ warning: '\u26A0',
70
+ arrow: '\u2192',
71
+ bullet: '\u2022',
72
+ };
73
+ // Helper functions for colored output
74
+ const color = (text, colorName) => `${colors[colorName]}${text}${colors.reset}`;
75
+ const bold = (text) => `${colors.bright}${text}${colors.reset}`;
76
+ const dim = (text) => `${colors.dim}${text}${colors.reset}`;
77
+ const success = (text) => color(`${icons.check} ${text}`, 'green');
78
+ const error = (text) => color(`${icons.cross} ${text}`, 'red');
79
+ const warning = (text) => color(`${icons.warning} ${text}`, 'yellow');
80
+ const info = (text) => color(`${icons.info} ${text}`, 'blue');
81
+ const rl = readline_1.default.createInterface({
82
+ input: process.stdin,
83
+ output: process.stdout
84
+ });
85
+ // ─── Box-drawing helpers ──────────────────────────────────────
86
+ function centerText(text, visibleLen, width) {
87
+ const pad = Math.max(0, Math.floor((width - visibleLen) / 2));
88
+ return ' '.repeat(pad) + text;
89
+ }
90
+ function printHeader() {
91
+ const W = 54; // inner width (between the vertical bars)
92
+ const top = ` \u250C${'\u2500'.repeat(W)}\u2510`;
93
+ const bottom = ` \u2514${'\u2500'.repeat(W)}\u2518`;
94
+ const blank = ` \u2502${' '.repeat(W)}\u2502`;
95
+ const title = bold(color('CCGram Setup', 'cyan'));
96
+ const titleLen = 12; // "CCGram Setup"
97
+ const sub = dim('Control Claude Code from Telegram');
98
+ const subLen = 32; // "Control Claude Code from Telegram"
99
+ console.log();
100
+ console.log(top);
101
+ console.log(blank);
102
+ console.log(` \u2502${centerText(title, titleLen, W)}`
103
+ + ' '.repeat(Math.max(0, W - Math.floor((W - titleLen) / 2) - titleLen)) + '\u2502');
104
+ console.log(` \u2502${centerText(sub, subLen, W)}`
105
+ + ' '.repeat(Math.max(0, W - Math.floor((W - subLen) / 2) - subLen)) + '\u2502');
106
+ console.log(blank);
107
+ console.log(bottom);
108
+ console.log();
109
+ }
110
+ function printSection(title) {
111
+ const line = '\u2500'.repeat(42 - title.length);
112
+ console.log('\n ' + bold(color(`\u2500\u2500\u2500 ${title} `, 'cyan')) + color(line, 'gray'));
113
+ console.log();
114
+ }
115
+ function ask(question, defaultValue = '') {
116
+ const suffix = defaultValue ? dim(` (${defaultValue})`) : '';
117
+ return new Promise(resolve => {
118
+ rl.question(` ${color(icons.arrow, 'green')} ${question}${suffix}: `, (answer) => {
119
+ resolve(answer.trim() || defaultValue);
120
+ });
121
+ });
122
+ }
123
+ function askSelect(question, options, defaultIndex = 0) {
124
+ return new Promise(resolve => {
125
+ console.log(`\n${bold(question)}`);
126
+ options.forEach((opt, idx) => {
127
+ const num = dim(`[${idx + 1}]`);
128
+ const isDefault = idx === defaultIndex;
129
+ const label = isDefault ? bold(opt.label) : opt.label;
130
+ console.log(` ${num} ${label}`);
131
+ });
132
+ rl.question(`\n${color(icons.arrow, 'green')} Select (1-${options.length}) ${dim(`[${defaultIndex + 1}]`)}: `, (answer) => {
133
+ const num = parseInt(answer.trim() || String(defaultIndex + 1));
134
+ if (num >= 1 && num <= options.length) {
135
+ resolve(options[num - 1]);
136
+ }
137
+ else {
138
+ resolve(options[defaultIndex]);
139
+ }
140
+ });
141
+ });
142
+ }
143
+ function askYesNo(question, defaultValue = false) {
144
+ const suffix = defaultValue ? color(' [Y/n]', 'green') : color(' [y/N]', 'red');
145
+ return new Promise(resolve => {
146
+ rl.question(` ${color(icons.arrow, 'green')} ${question}${suffix} `, (answer) => {
147
+ const normalized = answer.trim().toLowerCase();
148
+ if (!normalized)
149
+ return resolve(defaultValue);
150
+ resolve(normalized === 'y' || normalized === 'yes');
151
+ });
152
+ });
153
+ }
154
+ function loadExistingEnv() {
155
+ if (!fs_1.default.existsSync(envPath))
156
+ return {};
157
+ try {
158
+ const content = fs_1.default.readFileSync(envPath, 'utf8');
159
+ return dotenv_1.default.parse(content);
160
+ }
161
+ catch (err) {
162
+ const message = err instanceof Error ? err.message : String(err);
163
+ console.warn(warning('Failed to parse existing .env, starting fresh:') + ' ' + message);
164
+ return {};
165
+ }
166
+ }
167
+ function checkTmux() {
168
+ try {
169
+ const version = (0, child_process_1.execSync)('tmux -V', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
170
+ console.log(' ' + success(`tmux found: ${version}`));
171
+ return true;
172
+ }
173
+ catch {
174
+ const isMac = process.platform === 'darwin';
175
+ console.log(' ' + warning('tmux not found.'));
176
+ console.log(dim(' Without tmux, /new starts headless PTY sessions (Telegram-only control).'));
177
+ console.log(dim(` For full terminal+Telegram experience: ${isMac ? 'brew install tmux' : 'sudo apt install tmux'}`));
178
+ return false;
179
+ }
180
+ }
181
+ function validateBotToken(token) {
182
+ return new Promise(resolve => {
183
+ const url = `https://api.telegram.org/bot${token}/getMe`;
184
+ const req = https_1.default.get(url, { timeout: 10000 }, (res) => {
185
+ let data = '';
186
+ res.on('data', (chunk) => { data += chunk; });
187
+ res.on('end', () => {
188
+ try {
189
+ const json = JSON.parse(data);
190
+ if (json.ok && json.result) {
191
+ const result = json.result;
192
+ resolve({ ok: true, username: String(result.username) });
193
+ }
194
+ else {
195
+ console.log(' ' + warning(`Bot token validation failed: ${json.description || 'unknown error'}`));
196
+ resolve({ ok: false });
197
+ }
198
+ }
199
+ catch {
200
+ console.log(' ' + warning('Bot token validation failed: invalid API response'));
201
+ resolve({ ok: false });
202
+ }
203
+ });
204
+ });
205
+ req.on('error', (err) => {
206
+ console.log(' ' + warning(`Bot token validation failed: ${err.message}`));
207
+ resolve({ ok: false });
208
+ });
209
+ req.on('timeout', () => {
210
+ req.destroy();
211
+ console.log(' ' + warning('Bot token validation timed out'));
212
+ resolve({ ok: false });
213
+ });
214
+ });
215
+ }
216
+ function serializeEnvValue(value) {
217
+ if (value === undefined || value === null)
218
+ return '';
219
+ const stringValue = String(value);
220
+ if (stringValue === '')
221
+ return '';
222
+ if (/[^\w@%/:.\-]/.test(stringValue)) {
223
+ return `"${stringValue.replace(/"/g, '\\"')}"`;
224
+ }
225
+ return stringValue;
226
+ }
227
+ function writeEnvFile(values, existingEnv) {
228
+ const orderedKeys = [
229
+ 'TELEGRAM_ENABLED', 'TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID', 'TELEGRAM_GROUP_ID',
230
+ 'TELEGRAM_WHITELIST', 'TELEGRAM_WEBHOOK_URL', 'TELEGRAM_WEBHOOK_PORT',
231
+ 'TELEGRAM_FORCE_IPV4',
232
+ 'PROJECT_DIRS',
233
+ 'SESSION_MAP_PATH', 'INJECTION_MODE', 'CLAUDE_CLI_PATH', 'LOG_LEVEL',
234
+ 'ACTIVE_THRESHOLD_SECONDS'
235
+ ];
236
+ // Merge: new values override existing, keep any extra keys user already had
237
+ const merged = { ...existingEnv, ...values };
238
+ const lines = [];
239
+ lines.push('# CCGram configuration');
240
+ lines.push(`# Generated by ccgram init on ${new Date().toISOString()}`);
241
+ lines.push('');
242
+ orderedKeys.forEach((key) => {
243
+ if (merged[key] === undefined)
244
+ return;
245
+ lines.push(`${key}=${serializeEnvValue(merged[key])}`);
246
+ });
247
+ const extras = Object.keys(merged).filter(k => !orderedKeys.includes(k));
248
+ if (extras.length > 0) {
249
+ lines.push('');
250
+ lines.push('# User-defined / preserved keys');
251
+ extras.forEach((key) => {
252
+ lines.push(`${key}=${serializeEnvValue(merged[key])}`);
253
+ });
254
+ }
255
+ fs_1.default.writeFileSync(envPath, lines.join('\n') + '\n');
256
+ return envPath;
257
+ }
258
+ function makeHookCommand(def) {
259
+ const scriptPath = path_1.default.join(projectRoot, 'dist', def.script);
260
+ const quoted = scriptPath.includes(' ') ? `"${scriptPath}"` : scriptPath;
261
+ return def.args ? `node ${quoted} ${def.args}` : `node ${quoted}`;
262
+ }
263
+ function buildHooksJSON() {
264
+ const hooks = {};
265
+ for (const def of HOOK_DEFINITIONS) {
266
+ const entry = {};
267
+ if (def.matcher)
268
+ entry.matcher = def.matcher;
269
+ entry.hooks = [{ type: 'command', command: makeHookCommand(def), timeout: def.timeout }];
270
+ if (!hooks[def.event])
271
+ hooks[def.event] = [];
272
+ hooks[def.event].push(entry);
273
+ }
274
+ return hooks;
275
+ }
276
+ function ensureHooksFile() {
277
+ const settingsDir = path_1.default.join(os_1.default.homedir(), '.claude');
278
+ const settingsPath = path_1.default.join(settingsDir, 'settings.json');
279
+ let settings = {};
280
+ let existing = false;
281
+ let backupPath = null;
282
+ if (!fs_1.default.existsSync(settingsDir)) {
283
+ fs_1.default.mkdirSync(settingsDir, { recursive: true });
284
+ }
285
+ if (fs_1.default.existsSync(settingsPath)) {
286
+ existing = true;
287
+ try {
288
+ settings = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf8'));
289
+ }
290
+ catch {
291
+ backupPath = `${settingsPath}.bak-${Date.now()}`;
292
+ fs_1.default.copyFileSync(settingsPath, backupPath);
293
+ console.warn(warning(`Existing ~/.claude/settings.json is invalid JSON, backed up to ${backupPath}`));
294
+ settings = {};
295
+ }
296
+ }
297
+ if (!settings.hooks)
298
+ settings.hooks = {};
299
+ const hooksObj = settings.hooks;
300
+ for (const def of HOOK_DEFINITIONS) {
301
+ const command = makeHookCommand(def);
302
+ const eventHooks = Array.isArray(hooksObj[def.event]) ? hooksObj[def.event] : [];
303
+ const exists = eventHooks.some((entry) => Array.isArray(entry.hooks) && entry.hooks.some(h => h.command === command));
304
+ if (!exists) {
305
+ const entry = {};
306
+ if (def.matcher)
307
+ entry.matcher = def.matcher;
308
+ entry.hooks = [{ type: 'command', command, timeout: def.timeout }];
309
+ eventHooks.push(entry);
310
+ }
311
+ hooksObj[def.event] = eventHooks;
312
+ }
313
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
314
+ return { settingsPath, existing, backupPath };
315
+ }
316
+ function printServiceInstructions() {
317
+ const isMac = process.platform === 'darwin';
318
+ const isLinux = process.platform === 'linux';
319
+ printSection('Background Service');
320
+ if (isLinux) {
321
+ // Generate a filled-in systemd unit file
322
+ const user = os_1.default.userInfo().username;
323
+ const nodePath = process.execPath;
324
+ const logsDir = path_1.default.join(projectRoot, 'logs');
325
+ if (!fs_1.default.existsSync(logsDir))
326
+ fs_1.default.mkdirSync(logsDir, { recursive: true });
327
+ const unit = [
328
+ '[Unit]',
329
+ 'Description=CCGram - Claude Code Telegram Bot',
330
+ 'After=network.target',
331
+ '',
332
+ '[Service]',
333
+ 'Type=simple',
334
+ `User=${user}`,
335
+ `WorkingDirectory=${projectRoot}`,
336
+ `ExecStart=${nodePath} dist/workspace-telegram-bot.js`,
337
+ 'Restart=always',
338
+ 'RestartSec=5',
339
+ 'Environment=NODE_ENV=production',
340
+ '',
341
+ `StandardOutput=append:${logsDir}/bot-stdout.log`,
342
+ `StandardError=append:${logsDir}/bot-stderr.log`,
343
+ '',
344
+ '[Install]',
345
+ 'WantedBy=multi-user.target',
346
+ ].join('\n');
347
+ const servicePath = path_1.default.join(projectRoot, 'ccgram.service');
348
+ fs_1.default.writeFileSync(servicePath, unit + '\n');
349
+ // Try to auto-install the systemd service
350
+ try {
351
+ (0, child_process_1.execSync)(`sudo cp ${servicePath} /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable ccgram && sudo systemctl start ccgram`, { stdio: 'pipe' });
352
+ console.log(' ' + success('Bot service installed and started'));
353
+ }
354
+ catch {
355
+ console.log(' ' + success(`Generated ${servicePath}`));
356
+ console.log();
357
+ console.log(dim(' Install as systemd service (requires sudo):'));
358
+ console.log(dim(` sudo cp ${servicePath} /etc/systemd/system/`));
359
+ console.log(dim(' sudo systemctl daemon-reload'));
360
+ console.log(dim(' sudo systemctl enable ccgram'));
361
+ console.log(dim(' sudo systemctl start ccgram'));
362
+ }
363
+ console.log();
364
+ console.log(dim(' Manage:'));
365
+ console.log(dim(' sudo systemctl restart ccgram'));
366
+ console.log(dim(' journalctl -u ccgram -f'));
367
+ }
368
+ else if (isMac) {
369
+ // Generate a filled-in launchd plist
370
+ const nodePath = process.execPath;
371
+ const nodeDir = path_1.default.dirname(nodePath);
372
+ const logsDir = path_1.default.join(projectRoot, 'logs');
373
+ if (!fs_1.default.existsSync(logsDir))
374
+ fs_1.default.mkdirSync(logsDir, { recursive: true });
375
+ const plist = [
376
+ '<?xml version="1.0" encoding="UTF-8"?>',
377
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
378
+ '<plist version="1.0">',
379
+ '<dict>',
380
+ ' <key>Label</key>',
381
+ ' <string>com.ccgram</string>',
382
+ '',
383
+ ' <key>ProgramArguments</key>',
384
+ ' <array>',
385
+ ` <string>${nodePath}</string>`,
386
+ ` <string>${path_1.default.join(projectRoot, 'dist', 'workspace-telegram-bot.js')}</string>`,
387
+ ' </array>',
388
+ '',
389
+ ' <key>WorkingDirectory</key>',
390
+ ` <string>${projectRoot}</string>`,
391
+ '',
392
+ ' <key>EnvironmentVariables</key>',
393
+ ' <dict>',
394
+ ' <key>PATH</key>',
395
+ ` <string>${nodeDir}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>`,
396
+ ' <key>HOME</key>',
397
+ ` <string>${os_1.default.homedir()}</string>`,
398
+ ' </dict>',
399
+ '',
400
+ ' <key>RunAtLoad</key>',
401
+ ' <true/>',
402
+ '',
403
+ ' <key>KeepAlive</key>',
404
+ ' <true/>',
405
+ '',
406
+ ' <key>StandardOutPath</key>',
407
+ ` <string>${logsDir}/bot-stdout.log</string>`,
408
+ '',
409
+ ' <key>StandardErrorPath</key>',
410
+ ` <string>${logsDir}/bot-stderr.log</string>`,
411
+ '',
412
+ ' <key>ThrottleInterval</key>',
413
+ ' <integer>10</integer>',
414
+ '</dict>',
415
+ '</plist>',
416
+ ].join('\n');
417
+ const plistDir = path_1.default.join(os_1.default.homedir(), 'Library', 'LaunchAgents');
418
+ if (!fs_1.default.existsSync(plistDir))
419
+ fs_1.default.mkdirSync(plistDir, { recursive: true });
420
+ const plistPath = path_1.default.join(plistDir, 'com.ccgram.plist');
421
+ fs_1.default.writeFileSync(plistPath, plist + '\n');
422
+ // Auto-load the service
423
+ try {
424
+ (0, child_process_1.execSync)('launchctl bootout gui/$(id -u)/com.ccgram 2>/dev/null', { stdio: 'pipe' });
425
+ }
426
+ catch { }
427
+ try {
428
+ (0, child_process_1.execSync)(`launchctl bootstrap gui/$(id -u) ${plistPath}`, { stdio: 'pipe' });
429
+ console.log(' ' + success('Bot service started'));
430
+ }
431
+ catch {
432
+ console.log(' ' + warning('Could not auto-start service. Load manually:'));
433
+ console.log(dim(` launchctl bootstrap gui/$(id -u) ${plistPath}`));
434
+ }
435
+ console.log();
436
+ console.log(dim(' Manage:'));
437
+ console.log(dim(' launchctl kickstart -k gui/$(id -u)/com.ccgram # restart'));
438
+ console.log(dim(` tail -f ${logsDir}/bot-stdout.log # logs`));
439
+ }
440
+ else {
441
+ console.log(dim(' Run the bot with: npm start'));
442
+ console.log(dim(' Or use a process manager like pm2:'));
443
+ console.log(dim(' npx pm2 start workspace-telegram-bot.js --name ccgram'));
444
+ }
445
+ }
446
+ function installToHome(sourceRoot) {
447
+ if (path_1.default.resolve(sourceRoot) === path_1.default.resolve(paths_1.CCGRAM_HOME)) {
448
+ console.log(dim(' Already running from ~/.ccgram, skipping install copy'));
449
+ return paths_1.CCGRAM_HOME;
450
+ }
451
+ console.log(info(`Installing to ${paths_1.CCGRAM_HOME}...`));
452
+ // Create directory structure
453
+ for (const sub of ['dist', 'src/data', 'logs', 'config']) {
454
+ fs_1.default.mkdirSync(path_1.default.join(paths_1.CCGRAM_HOME, sub), { recursive: true });
455
+ }
456
+ // Preserve existing .env
457
+ let preservedEnv = null;
458
+ const homeEnvPath = path_1.default.join(paths_1.CCGRAM_HOME, '.env');
459
+ if (fs_1.default.existsSync(homeEnvPath)) {
460
+ preservedEnv = fs_1.default.readFileSync(homeEnvPath, 'utf8');
461
+ }
462
+ // Copy required files
463
+ const filesToCopy = [
464
+ { src: 'package.json', dest: 'package.json' },
465
+ { src: 'dist', dest: 'dist', dir: true },
466
+ { src: 'config', dest: 'config', dir: true },
467
+ { src: '.env.example', dest: '.env.example' },
468
+ ];
469
+ for (const item of filesToCopy) {
470
+ const srcPath = path_1.default.join(sourceRoot, item.src);
471
+ const destPath = path_1.default.join(paths_1.CCGRAM_HOME, item.dest);
472
+ if (!fs_1.default.existsSync(srcPath))
473
+ continue;
474
+ if (item.dir) {
475
+ fs_1.default.cpSync(srcPath, destPath, { recursive: true, force: true });
476
+ }
477
+ else {
478
+ fs_1.default.copyFileSync(srcPath, destPath);
479
+ }
480
+ }
481
+ // Copy dotenv module (only required runtime dependency)
482
+ const dotenvSrc = path_1.default.join(sourceRoot, 'node_modules', 'dotenv');
483
+ const dotenvDest = path_1.default.join(paths_1.CCGRAM_HOME, 'node_modules', 'dotenv');
484
+ if (fs_1.default.existsSync(dotenvSrc)) {
485
+ fs_1.default.mkdirSync(path_1.default.join(paths_1.CCGRAM_HOME, 'node_modules'), { recursive: true });
486
+ fs_1.default.cpSync(dotenvSrc, dotenvDest, { recursive: true, force: true });
487
+ }
488
+ else if (!fs_1.default.existsSync(path_1.default.join(dotenvDest, 'package.json'))) {
489
+ // dotenv wasn't in source and isn't already installed — npm install it
490
+ console.log(dim(' Installing dotenv dependency...'));
491
+ try {
492
+ (0, child_process_1.execSync)('npm install --production --no-optional', { cwd: paths_1.CCGRAM_HOME, stdio: 'pipe' });
493
+ }
494
+ catch (err) {
495
+ const message = err instanceof Error ? err.message : String(err);
496
+ console.log(warning(`Failed to install dependencies: ${message}`));
497
+ }
498
+ }
499
+ // Restore existing ~/.ccgram/.env, or migrate source .env on first install
500
+ if (preservedEnv !== null) {
501
+ fs_1.default.writeFileSync(homeEnvPath, preservedEnv);
502
+ }
503
+ else {
504
+ const sourceEnvPath = path_1.default.join(sourceRoot, '.env');
505
+ if (fs_1.default.existsSync(sourceEnvPath)) {
506
+ fs_1.default.copyFileSync(sourceEnvPath, homeEnvPath);
507
+ }
508
+ }
509
+ // Verify key files
510
+ const requiredFiles = [
511
+ 'dist/permission-hook.js',
512
+ 'dist/workspace-telegram-bot.js',
513
+ 'node_modules/dotenv/package.json',
514
+ ];
515
+ const missing = requiredFiles.filter(f => !fs_1.default.existsSync(path_1.default.join(paths_1.CCGRAM_HOME, f)));
516
+ if (missing.length > 0) {
517
+ console.log(warning(`Missing files in ~/.ccgram/: ${missing.join(', ')}`));
518
+ }
519
+ else {
520
+ console.log(' ' + success(`Installed to ${paths_1.CCGRAM_HOME}`));
521
+ }
522
+ return paths_1.CCGRAM_HOME;
523
+ }
524
+ // ─── Main ─────────────────────────────────────────────────────
525
+ async function main() {
526
+ printHeader();
527
+ // Install to ~/.ccgram/ for persistent hook paths
528
+ projectRoot = installToHome(paths_1.PROJECT_ROOT);
529
+ envPath = path_1.default.join(projectRoot, '.env');
530
+ defaultSessionMap = path_1.default.join(projectRoot, 'src', 'data', 'session-map.json');
531
+ // Check tmux availability + show paths
532
+ checkTmux();
533
+ console.log(dim(` Install path: ${projectRoot}`));
534
+ console.log(dim(` Config: ${envPath}`));
535
+ const existingEnv = loadExistingEnv();
536
+ // ─── Telegram ──────────────────────────────────────────
537
+ printSection('Telegram');
538
+ console.log(dim(' Create a bot with @BotFather and get your chat ID from @userinfobot'));
539
+ console.log();
540
+ const botToken = await ask('Bot token (from @BotFather)', existingEnv.TELEGRAM_BOT_TOKEN || '');
541
+ const chatId = await ask('Chat ID (from @userinfobot)', existingEnv.TELEGRAM_CHAT_ID || '');
542
+ const defaultProjectDirs = existingEnv.PROJECT_DIRS || `${os_1.default.homedir()}/projects,${os_1.default.homedir()}/tools`;
543
+ const projectDirs = await ask('Project directories, comma-separated', defaultProjectDirs);
544
+ // ─── Advanced settings (single gate) ───────────────────
545
+ let groupId = existingEnv.TELEGRAM_GROUP_ID || '';
546
+ let whitelist = existingEnv.TELEGRAM_WHITELIST || '';
547
+ let webhookUrl = existingEnv.TELEGRAM_WEBHOOK_URL || '';
548
+ let webhookPort = existingEnv.TELEGRAM_WEBHOOK_PORT || '3001';
549
+ let forceIPv4 = existingEnv.TELEGRAM_FORCE_IPV4 === 'true';
550
+ let injectionMode = existingEnv.INJECTION_MODE || 'tmux';
551
+ let logLevel = existingEnv.LOG_LEVEL || 'info';
552
+ let sessionMapPath = existingEnv.SESSION_MAP_PATH || defaultSessionMap;
553
+ let activeThreshold = existingEnv.ACTIVE_THRESHOLD_SECONDS || '300';
554
+ let advancedConfigured = false;
555
+ console.log();
556
+ const showAdvanced = await askYesNo('Configure advanced settings?', false);
557
+ if (showAdvanced) {
558
+ advancedConfigured = true;
559
+ printSection('Advanced');
560
+ groupId = await ask('Group chat ID (optional)', groupId);
561
+ whitelist = await ask('Allowed user IDs, comma-separated (optional)', whitelist);
562
+ webhookUrl = await ask('Webhook URL (leave empty for long-polling)', webhookUrl);
563
+ if (webhookUrl) {
564
+ webhookPort = await ask('Webhook port', webhookPort);
565
+ }
566
+ forceIPv4 = await askYesNo('Force IPv4 for Telegram API?', forceIPv4);
567
+ injectionMode = (await ask('Injection mode (tmux or pty)', injectionMode)).toLowerCase();
568
+ if (!['tmux', 'pty'].includes(injectionMode)) {
569
+ console.log(' ' + warning('Invalid injection mode, defaulting to tmux'));
570
+ injectionMode = 'tmux';
571
+ }
572
+ logLevel = await ask('Log level (debug, info, warn, error)', logLevel);
573
+ sessionMapPath = await ask('Session map path', sessionMapPath);
574
+ activeThreshold = await ask('Active threshold in seconds (suppress notifications when working at terminal)', activeThreshold);
575
+ }
576
+ // ─── Build env values ──────────────────────────────────
577
+ const envValues = {
578
+ TELEGRAM_ENABLED: 'true',
579
+ TELEGRAM_BOT_TOKEN: botToken,
580
+ TELEGRAM_CHAT_ID: chatId,
581
+ PROJECT_DIRS: projectDirs,
582
+ };
583
+ // Only write advanced keys if user configured them or they existed before
584
+ if (groupId)
585
+ envValues.TELEGRAM_GROUP_ID = groupId;
586
+ if (whitelist)
587
+ envValues.TELEGRAM_WHITELIST = whitelist;
588
+ if (webhookUrl)
589
+ envValues.TELEGRAM_WEBHOOK_URL = webhookUrl;
590
+ if (webhookUrl && webhookPort)
591
+ envValues.TELEGRAM_WEBHOOK_PORT = webhookPort;
592
+ if (forceIPv4)
593
+ envValues.TELEGRAM_FORCE_IPV4 = 'true';
594
+ if (advancedConfigured || existingEnv.INJECTION_MODE)
595
+ envValues.INJECTION_MODE = injectionMode;
596
+ if (advancedConfigured || existingEnv.SESSION_MAP_PATH)
597
+ envValues.SESSION_MAP_PATH = sessionMapPath;
598
+ if (logLevel !== 'info' || existingEnv.LOG_LEVEL)
599
+ envValues.LOG_LEVEL = logLevel;
600
+ if (activeThreshold !== '300' || existingEnv.ACTIVE_THRESHOLD_SECONDS)
601
+ envValues.ACTIVE_THRESHOLD_SECONDS = activeThreshold;
602
+ // ─── Write .env ────────────────────────────────────────
603
+ const savedEnvPath = writeEnvFile(envValues, existingEnv);
604
+ // ─── Validate bot token ────────────────────────────────
605
+ let botUsername;
606
+ if (botToken) {
607
+ const result = await validateBotToken(botToken);
608
+ botUsername = result.username;
609
+ }
610
+ // ─── Install hooks (automatic, no prompt) ──────────────
611
+ const { settingsPath, existing, backupPath } = ensureHooksFile();
612
+ if (backupPath) {
613
+ console.log(' ' + warning(`Invalid settings.json backed up to ${backupPath}`));
614
+ }
615
+ // ─── Generate service file ─────────────────────────────
616
+ printServiceInstructions();
617
+ // ─── Print hooks JSON ──────────────────────────────────
618
+ const hooksJSON = buildHooksJSON();
619
+ console.log('\n ' + bold('Hooks for ~/.claude/settings.json:'));
620
+ console.log(color(' ' + '\u2500'.repeat(55), 'gray'));
621
+ const jsonLines = JSON.stringify({ hooks: hooksJSON }, null, 2).split('\n');
622
+ for (const line of jsonLines) {
623
+ console.log(dim(' ' + line));
624
+ }
625
+ console.log(color(' ' + '\u2500'.repeat(55), 'gray'));
626
+ // Close readline before summary output
627
+ rl.close();
628
+ // ─── Summary ───────────────────────────────────────────
629
+ printSection('Complete');
630
+ console.log(' ' + success(`Installed to ${projectRoot}`));
631
+ console.log(' ' + success(`Saved ${savedEnvPath}`));
632
+ if (botUsername) {
633
+ console.log(' ' + success(`Bot verified: @${botUsername}`));
634
+ }
635
+ console.log(' ' + success(`Hooks ${existing ? 'updated' : 'created'}: ${settingsPath}`));
636
+ console.log(' ' + success('Service generated'));
637
+ console.log();
638
+ console.log(' ' + bold('Next steps:'));
639
+ console.log(' 1. Open Telegram and message your bot');
640
+ console.log(' 2. Start Claude Code in a tmux session');
641
+ console.log();
642
+ }
643
+ main().catch((err) => {
644
+ const message = err instanceof Error ? err.message : String(err);
645
+ console.error(error(`Setup failed: ${message}`));
646
+ rl.close();
647
+ process.exit(1);
648
+ });
649
+ //# sourceMappingURL=setup.js.map