@kcisoul/remotecode 0.9.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 (86) hide show
  1. package/README.md +358 -0
  2. package/dist/__tests__/config.test.d.ts +2 -0
  3. package/dist/__tests__/config.test.d.ts.map +1 -0
  4. package/dist/__tests__/config.test.js +84 -0
  5. package/dist/__tests__/config.test.js.map +1 -0
  6. package/dist/__tests__/format.test.d.ts +2 -0
  7. package/dist/__tests__/format.test.d.ts.map +1 -0
  8. package/dist/__tests__/format.test.js +62 -0
  9. package/dist/__tests__/format.test.js.map +1 -0
  10. package/dist/__tests__/sessions.test.d.ts +2 -0
  11. package/dist/__tests__/sessions.test.d.ts.map +1 -0
  12. package/dist/__tests__/sessions.test.js +51 -0
  13. package/dist/__tests__/sessions.test.js.map +1 -0
  14. package/dist/banner.d.ts +4 -0
  15. package/dist/banner.d.ts.map +1 -0
  16. package/dist/banner.js +154 -0
  17. package/dist/banner.js.map +1 -0
  18. package/dist/callbacks.d.ts +10 -0
  19. package/dist/callbacks.d.ts.map +1 -0
  20. package/dist/callbacks.js +184 -0
  21. package/dist/callbacks.js.map +1 -0
  22. package/dist/claude.d.ts +9 -0
  23. package/dist/claude.d.ts.map +1 -0
  24. package/dist/claude.js +117 -0
  25. package/dist/claude.js.map +1 -0
  26. package/dist/cli.d.ts +14 -0
  27. package/dist/cli.d.ts.map +1 -0
  28. package/dist/cli.js +296 -0
  29. package/dist/cli.js.map +1 -0
  30. package/dist/commands.d.ts +3 -0
  31. package/dist/commands.d.ts.map +1 -0
  32. package/dist/commands.js +107 -0
  33. package/dist/commands.js.map +1 -0
  34. package/dist/config.d.ts +24 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +153 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/context.d.ts +12 -0
  39. package/dist/context.d.ts.map +1 -0
  40. package/dist/context.js +22 -0
  41. package/dist/context.js.map +1 -0
  42. package/dist/daemon.d.ts +11 -0
  43. package/dist/daemon.d.ts.map +1 -0
  44. package/dist/daemon.js +316 -0
  45. package/dist/daemon.js.map +1 -0
  46. package/dist/format.d.ts +8 -0
  47. package/dist/format.d.ts.map +1 -0
  48. package/dist/format.js +57 -0
  49. package/dist/format.js.map +1 -0
  50. package/dist/handler.d.ts +4 -0
  51. package/dist/handler.d.ts.map +1 -0
  52. package/dist/handler.js +292 -0
  53. package/dist/handler.js.map +1 -0
  54. package/dist/index.d.ts +3 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +142 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/logger.d.ts +10 -0
  59. package/dist/logger.d.ts.map +1 -0
  60. package/dist/logger.js +42 -0
  61. package/dist/logger.js.map +1 -0
  62. package/dist/session-ui.d.ts +17 -0
  63. package/dist/session-ui.d.ts.map +1 -0
  64. package/dist/session-ui.js +237 -0
  65. package/dist/session-ui.js.map +1 -0
  66. package/dist/sessions.d.ts +33 -0
  67. package/dist/sessions.d.ts.map +1 -0
  68. package/dist/sessions.js +341 -0
  69. package/dist/sessions.js.map +1 -0
  70. package/dist/setup.d.ts +3 -0
  71. package/dist/setup.d.ts.map +1 -0
  72. package/dist/setup.js +144 -0
  73. package/dist/setup.js.map +1 -0
  74. package/dist/stt.d.ts +11 -0
  75. package/dist/stt.d.ts.map +1 -0
  76. package/dist/stt.js +192 -0
  77. package/dist/stt.js.map +1 -0
  78. package/dist/telegram.d.ts +85 -0
  79. package/dist/telegram.d.ts.map +1 -0
  80. package/dist/telegram.js +97 -0
  81. package/dist/telegram.js.map +1 -0
  82. package/dist/watcher.d.ts +6 -0
  83. package/dist/watcher.d.ts.map +1 -0
  84. package/dist/watcher.js +230 -0
  85. package/dist/watcher.js.map +1 -0
  86. package/package.json +45 -0
package/README.md ADDED
@@ -0,0 +1,358 @@
1
+ # RemoteCode
2
+
3
+ Control [Claude Code](https://docs.anthropic.com/en/docs/claude-code) remotely through Telegram. Built specifically for Claude Code.
4
+
5
+ RemoteCode works directly with your local Claude Code -- same sessions, same project context, same history. Pick up where you left off in the terminal, switch projects, or start a new session, all from Telegram.
6
+
7
+ ```
8
+ You (Anywhere) <--> RemoteCode (Host) <--> Claude Code (Host)
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Text chat** -- Send any message, get Claude Code responses with Markdown formatting
14
+ - **Image analysis** -- Send photos or image documents, optionally with a caption prompt
15
+ - **Voice messages** -- Transcribed locally via whisper-cli (offline, free), then sent to Claude Code
16
+ - **Session management** -- Multiple sessions, switch between them, browse by project
17
+ - **Auto-sync** -- Watch active session files and forward new messages in real-time
18
+ - **Background daemon** -- Runs as a detached process with log rotation
19
+ - **User access control** -- Restrict access by Telegram user ID or username
20
+
21
+ ## How It Works
22
+
23
+ ```mermaid
24
+ sequenceDiagram
25
+ participant U as You (Telegram)
26
+ participant T as Telegram API
27
+ participant D as RemoteCode Daemon
28
+ participant C as Claude Code CLI
29
+
30
+ U->>T: Send message
31
+ T->>D: Long polling (getUpdates)
32
+ D->>D: Auth check & route
33
+
34
+ alt Text message
35
+ D->>C: claude --resume <session> --print "prompt"
36
+ else Image
37
+ D->>T: Download file
38
+ D->>C: claude --resume <session> --print "prompt + image path"
39
+ else Voice
40
+ D->>T: Download audio
41
+ D->>D: ffmpeg + whisper-cli transcription
42
+ D->>C: claude --resume <session> --print "transcription"
43
+ end
44
+
45
+ C-->>D: stdout response
46
+ D->>D: Markdown → Telegram HTML
47
+ D->>T: sendMessage (HTML)
48
+ T-->>U: Formatted response
49
+ ```
50
+
51
+ ## Architecture
52
+
53
+ ```mermaid
54
+ graph TB
55
+ subgraph CLI["CLI (Terminal)"]
56
+ IDX[index.ts<br/>arg parser]
57
+ CL[cli.ts<br/>subcommands]
58
+ end
59
+
60
+ subgraph Daemon["Daemon Process"]
61
+ DAE[daemon.ts<br/>poll loop + PID]
62
+ HND[handler.ts<br/>message router]
63
+ CMD[commands.ts<br/>slash commands]
64
+ CB[callbacks.ts<br/>inline buttons]
65
+ WAT[watcher.ts<br/>auto-sync]
66
+ end
67
+
68
+ subgraph Core["Core"]
69
+ TEL[telegram.ts<br/>API client]
70
+ CLA[claude.ts<br/>CLI spawner]
71
+ SES[sessions.ts<br/>discovery + state]
72
+ SUI[session-ui.ts<br/>UI formatting]
73
+ end
74
+
75
+ subgraph Shared["Shared"]
76
+ CFG[config.ts<br/>paths + KV I/O]
77
+ LOG[logger.ts<br/>logging]
78
+ BAN[banner.ts<br/>terminal UI]
79
+ CTX[context.ts<br/>auth + locks]
80
+ FMT[format.ts<br/>MD → HTML]
81
+ end
82
+
83
+ IDX --> DAE
84
+ IDX --> CL
85
+ CL --> DAE
86
+ DAE --> HND
87
+ DAE --> CB
88
+ HND --> CMD
89
+ HND --> CLA
90
+ HND --> CTX
91
+ CB --> SES
92
+ CB --> SUI
93
+ CMD --> SUI
94
+ WAT --> SES
95
+ WAT --> CTX
96
+ CLA --> LOG
97
+ TEL --> LOG
98
+ SUI --> FMT
99
+ CL --> BAN
100
+ ```
101
+
102
+ ## Quick Start
103
+
104
+ ### Prerequisites
105
+
106
+ - **Node.js** >= 18
107
+ - **Claude Code CLI** installed and authenticated (`claude` command available)
108
+ - **Telegram Bot Token** -- create a bot via [@BotFather](https://t.me/BotFather) on Telegram (send `/newbot`, follow the prompts, copy the token). See [Telegram's official guide](https://core.telegram.org/bots/tutorial#obtain-your-bot-token) for details
109
+
110
+ ### Install
111
+
112
+ ```bash
113
+ npm install -g @kcisoul/remotecode
114
+ ```
115
+
116
+ Or from source:
117
+
118
+ ```bash
119
+ git clone https://github.com/kcisoul/remotecode.git
120
+ cd remotecode
121
+ npm install && npm run build
122
+ npm link
123
+ ```
124
+
125
+ ### First Run
126
+
127
+ ```bash
128
+ remotecode
129
+ ```
130
+
131
+ The interactive setup wizard will prompt for:
132
+
133
+ 1. **TELEGRAM_BOT_TOKEN** -- validated against Telegram API
134
+ 2. **REMOTECODE_ALLOWED_USERS** -- comma-separated user IDs or @usernames
135
+ 3. **REMOTECODE_YOLO** -- `Y` enables autonomous mode (Claude Code runs without permission prompts, required for full remote control). Set `N` if you prefer read-only / monitoring use, but note that any action requiring approval will block since there's no terminal to confirm
136
+ 4. **STT setup** -- optional offline voice transcription. Installs `whisper-cli` and `ffmpeg` via Homebrew, and downloads a local Whisper model (~466 MB). Runs entirely on your machine -- no API calls, completely free
137
+
138
+ Config is saved to `~/.remotecode/config`.
139
+
140
+ ## CLI Commands
141
+
142
+ | Command | Description |
143
+ |---|---|
144
+ | `remotecode` | Start daemon (or show status if already running) |
145
+ | `remotecode start` | Start the background daemon |
146
+ | `remotecode stop` | Stop the daemon |
147
+ | `remotecode restart` | Restart the daemon |
148
+ | `remotecode status` | Show daemon status, active session, uptime |
149
+ | `remotecode logs` | Follow logs in real-time (default) |
150
+ | `remotecode logs -n 50` | Show last 50 log lines (static) |
151
+ | `remotecode logs --level ERROR` | Filter by log level (DEBUG/INFO/WARN/ERROR) |
152
+ | `remotecode logs --tag claude` | Filter by component tag |
153
+ | `remotecode config` | Edit configuration (auto-restarts daemon) |
154
+ | `remotecode setup-stt` | Install whisper-cli, ffmpeg, and download model |
155
+
156
+ ### Flags
157
+
158
+ | Flag | Description |
159
+ |---|---|
160
+ | `-v`, `--verbose` | Enable verbose (DEBUG) logging |
161
+
162
+ ## Telegram Commands
163
+
164
+ Send these as messages in your Telegram chat with the bot:
165
+
166
+ | Command | Description |
167
+ |---|---|
168
+ | `/start`, `/help` | Welcome message with quick action buttons |
169
+ | `/sessions` | Browse and switch between recent sessions |
170
+ | `/projects` | Browse sessions grouped by project directory |
171
+ | `/new` | Start a new Claude Code session |
172
+ | `/history` | Show conversation history of current session |
173
+ | `/sync` | Toggle auto-sync notifications on/off |
174
+
175
+ ### Inline Buttons
176
+
177
+ After `/sessions` or `/projects`, interactive inline keyboards let you:
178
+
179
+ - **Switch** to any session with one tap
180
+ - **Create** new sessions (globally or per-project)
181
+ - **Delete** sessions
182
+ - **Navigate** between project views
183
+
184
+ ## Message Types
185
+
186
+ ### Text
187
+
188
+ Send any text message. If it's not a `/command`, it's forwarded to Claude Code as a prompt. Responses are rendered as Telegram HTML with code blocks, bold, italic, and more.
189
+
190
+ ### Images
191
+
192
+ Send a photo or image document (PNG, JPG, etc.). The bot downloads the image, saves it to a temp directory, and includes the file path in the Claude Code prompt. Add a caption to provide context.
193
+
194
+ ### Voice / Audio
195
+
196
+ Send a voice message or audio file. The bot:
197
+
198
+ 1. Downloads the audio file
199
+ 2. Converts to WAV via `ffmpeg`
200
+ 3. Transcribes via `whisper-cli` (local, offline)
201
+ 4. Sends the transcription as a prompt to Claude Code
202
+ 5. Returns both your transcription and Claude's response in a blockquote
203
+
204
+ > Requires STT setup: `remotecode setup-stt`
205
+
206
+ ## Session Management
207
+
208
+ ```mermaid
209
+ stateDiagram-v2
210
+ [*] --> NoSession: First message
211
+ NoSession --> NewSession: auto-create UUID
212
+ NewSession --> Active: claude --session-id
213
+ Active --> Active: claude --resume
214
+ Active --> Switched: /sessions or inline button
215
+ Switched --> Active: select different session
216
+ Active --> NewSession: /new
217
+ ```
218
+
219
+ RemoteCode discovers sessions from `~/.claude/projects/*/` by scanning `.jsonl` files. Each session maps to a Claude Code conversation.
220
+
221
+ - **Active session** is stored in `~/.remotecode/local`
222
+ - **Session CWD** determines which directory Claude Code runs in
223
+ - Sessions are auto-created on first message if none exists
224
+ - The `--resume` flag is used to continue existing sessions; falls back to `--session-id` for new ones
225
+
226
+ ### Auto-Sync
227
+
228
+ When enabled (`/sync`), RemoteCode watches the active session's `.jsonl` file and forwards new messages from Claude Code in real-time. This means if you use Claude Code on your host machine, you'll see the conversation in Telegram too.
229
+
230
+ The watcher polls for session changes every 3 seconds and uses `fs.watch` for file-level changes with 500ms debouncing.
231
+
232
+ ## Configuration
233
+
234
+ ### Config File
235
+
236
+ `~/.remotecode/config` -- simple key=value format:
237
+
238
+ ```ini
239
+ TELEGRAM_BOT_TOKEN=123456:ABC-DEF
240
+ REMOTECODE_ALLOWED_USERS=12345678,@username
241
+ REMOTECODE_YOLO=true
242
+ ```
243
+
244
+ ### Environment Variables
245
+
246
+ | Variable | Required | Description |
247
+ |---|---|---|
248
+ | `TELEGRAM_BOT_TOKEN` | Yes | Bot token from @BotFather |
249
+ | `REMOTECODE_ALLOWED_USERS` | Yes | Comma/space-separated user IDs or @usernames |
250
+ | `REMOTECODE_YOLO` | No | `true` for full remote control (skips Claude Code permission prompts). Set `false` for read-only / monitoring use |
251
+ | `REMOTECODE_VERBOSE` | No | `true` to enable DEBUG-level logging |
252
+
253
+ ## File Structure
254
+
255
+ ```
256
+ ~/.remotecode/
257
+ config # Global configuration (KV file)
258
+ local # Active session state (session ID, CWD, chat ID)
259
+ remotecode.pid # Daemon process ID
260
+ remotecode.log # Log file (5MB rotation, keeps .old)
261
+ remotecode.log.old # Previous rotated log
262
+ whisper/
263
+ ggml-small.bin # Whisper model (if STT enabled)
264
+ RemoteCodeSessions/ # Default CWD for new sessions
265
+ ```
266
+
267
+ ## Message Flow
268
+
269
+ ```mermaid
270
+ flowchart TD
271
+ A[Telegram Update] --> B{Message type?}
272
+ B -->|callback_query| C[callbacks.ts]
273
+ B -->|message| D{Content type?}
274
+
275
+ D -->|text starts with /| E[commands.ts]
276
+ D -->|text| F[handlePrompt]
277
+ D -->|photo / image doc| G[Download & save image]
278
+ D -->|voice / audio| H{STT ready?}
279
+
280
+ H -->|No| I[Send setup instructions]
281
+ H -->|Yes| J[Download → ffmpeg → whisper-cli]
282
+ J --> K{Blank audio?}
283
+ K -->|Yes| L[No speech detected]
284
+ K -->|No| F
285
+
286
+ G --> F
287
+
288
+ F --> M[withSessionLock]
289
+ M --> N[askClaude via CLI]
290
+ N --> O{Session exists?}
291
+ O -->|Yes| P[--resume session]
292
+ O -->|No| Q[--session-id new]
293
+ O -->|Busy| R[Retry up to 5x]
294
+
295
+ P --> S[Format response]
296
+ Q --> S
297
+ R --> S
298
+ S --> T[sendMessage HTML]
299
+
300
+ C --> U{Action}
301
+ U -->|sess:list| V[Show session grid]
302
+ U -->|sess:ID| W[Switch session]
303
+ U -->|sess:new| X[Create new session]
304
+ U -->|proj:list| Y[Show project list]
305
+ U -->|proj:DIR| Z[Show project sessions]
306
+ U -->|sessdel:ID| AA[Delete session]
307
+ ```
308
+
309
+ ## Speech-to-Text (STT)
310
+
311
+ RemoteCode uses [whisper.cpp](https://github.com/ggerganov/whisper.cpp) for local, offline speech-to-text.
312
+
313
+ ### Setup
314
+
315
+ ```bash
316
+ remotecode setup-stt
317
+ ```
318
+
319
+ This installs (via Homebrew on macOS):
320
+ - **whisper-cpp** -- C++ inference engine for Whisper
321
+ - **ffmpeg** -- audio format conversion
322
+ - **ggml-small.bin** -- Whisper small model (~466 MB, downloaded from HuggingFace)
323
+
324
+ ### How it works
325
+
326
+ 1. Audio downloaded from Telegram (`.oga` format)
327
+ 2. Converted to 16kHz mono WAV via `ffmpeg`
328
+ 3. Transcribed via `whisper-cli -m ggml-small.bin -l auto`
329
+ 4. Blank audio detection filters out silence/noise
330
+ 5. Transcription sent to Claude Code as a regular prompt
331
+
332
+ ## Security
333
+
334
+ - **User allowlist** -- Only configured user IDs and usernames can interact
335
+ - **Repeat block** -- Unauthorized users are warned once, then silently blocked
336
+ - **No webhook** -- Uses long polling, no public endpoints needed
337
+ - **Local STT** -- Voice transcription runs entirely offline via whisper.cpp
338
+ - **YOLO mode** -- Required for full remote control. Without it, any Claude Code action needing approval will hang since there's no terminal to confirm. If security is a concern, set YOLO to `false` and use RemoteCode for text conversations and monitoring only
339
+
340
+ ## Development
341
+
342
+ ```bash
343
+ # Run in development mode
344
+ npm run dev
345
+
346
+ # Build TypeScript
347
+ npm run build
348
+
349
+ # Run tests
350
+ npm test
351
+
352
+ # Watch tests
353
+ npm run test:watch
354
+ ```
355
+
356
+ ## License
357
+
358
+ [MIT](LICENSE)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const vitest_1 = require("vitest");
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ const config_1 = require("../config");
41
+ (0, vitest_1.describe)("readKvFile", () => {
42
+ const tmpFile = path.join(os.tmpdir(), `remotecode_test_kv_${Date.now()}.txt`);
43
+ (0, vitest_1.afterEach)(() => {
44
+ try {
45
+ fs.unlinkSync(tmpFile);
46
+ }
47
+ catch { /* ignore */ }
48
+ });
49
+ (0, vitest_1.it)("returns empty object for non-existent file", () => {
50
+ (0, vitest_1.expect)((0, config_1.readKvFile)("/tmp/does-not-exist-xyz.txt")).toEqual({});
51
+ });
52
+ (0, vitest_1.it)("parses key=value pairs", () => {
53
+ fs.writeFileSync(tmpFile, "FOO=bar\nBAZ=qux\n");
54
+ (0, vitest_1.expect)((0, config_1.readKvFile)(tmpFile)).toEqual({ FOO: "bar", BAZ: "qux" });
55
+ });
56
+ (0, vitest_1.it)("skips comments and empty lines", () => {
57
+ fs.writeFileSync(tmpFile, "# comment\n\nKEY=val\n");
58
+ (0, vitest_1.expect)((0, config_1.readKvFile)(tmpFile)).toEqual({ KEY: "val" });
59
+ });
60
+ (0, vitest_1.it)("handles values with = sign", () => {
61
+ fs.writeFileSync(tmpFile, "TOKEN=abc=def=ghi\n");
62
+ (0, vitest_1.expect)((0, config_1.readKvFile)(tmpFile)).toEqual({ TOKEN: "abc=def=ghi" });
63
+ });
64
+ });
65
+ (0, vitest_1.describe)("readEnvLines / writeEnvLines", () => {
66
+ const tmpFile = path.join(os.tmpdir(), `remotecode_test_env_${Date.now()}.txt`);
67
+ (0, vitest_1.afterEach)(() => {
68
+ try {
69
+ fs.unlinkSync(tmpFile);
70
+ }
71
+ catch { /* ignore */ }
72
+ });
73
+ (0, vitest_1.it)("returns empty array for non-existent file", () => {
74
+ (0, vitest_1.expect)((0, config_1.readEnvLines)("/tmp/does-not-exist-xyz.txt")).toEqual([]);
75
+ });
76
+ (0, vitest_1.it)("round-trips lines", () => {
77
+ const lines = ["FOO=bar", "BAZ=qux"];
78
+ (0, config_1.writeEnvLines)(tmpFile, lines);
79
+ const read = (0, config_1.readEnvLines)(tmpFile);
80
+ (0, vitest_1.expect)(read).toContain("FOO=bar");
81
+ (0, vitest_1.expect)(read).toContain("BAZ=qux");
82
+ });
83
+ });
84
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=format.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/format.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const format_1 = require("../format");
5
+ (0, vitest_1.describe)("escapeHtml", () => {
6
+ (0, vitest_1.it)("escapes &, <, >", () => {
7
+ (0, vitest_1.expect)((0, format_1.escapeHtml)("a & b < c > d")).toBe("a &amp; b &lt; c &gt; d");
8
+ });
9
+ (0, vitest_1.it)("returns empty string unchanged", () => {
10
+ (0, vitest_1.expect)((0, format_1.escapeHtml)("")).toBe("");
11
+ });
12
+ });
13
+ (0, vitest_1.describe)("mdToTelegramHtml", () => {
14
+ (0, vitest_1.it)("converts bold **text**", () => {
15
+ (0, vitest_1.expect)((0, format_1.mdToTelegramHtml)("**hello**")).toBe("<b>hello</b>");
16
+ });
17
+ (0, vitest_1.it)("converts bold __text__", () => {
18
+ (0, vitest_1.expect)((0, format_1.mdToTelegramHtml)("__hello__")).toBe("<b>hello</b>");
19
+ });
20
+ (0, vitest_1.it)("converts italic *text*", () => {
21
+ (0, vitest_1.expect)((0, format_1.mdToTelegramHtml)("*hello*")).toBe("<i>hello</i>");
22
+ });
23
+ (0, vitest_1.it)("converts inline code", () => {
24
+ (0, vitest_1.expect)((0, format_1.mdToTelegramHtml)("`code`")).toBe("<code>code</code>");
25
+ });
26
+ (0, vitest_1.it)("converts code blocks", () => {
27
+ const result = (0, format_1.mdToTelegramHtml)("```js\nconsole.log(1)\n```");
28
+ (0, vitest_1.expect)(result).toBe("<pre>console.log(1)</pre>");
29
+ });
30
+ (0, vitest_1.it)("converts headings to bold", () => {
31
+ (0, vitest_1.expect)((0, format_1.mdToTelegramHtml)("## Title")).toBe("<b>Title</b>");
32
+ });
33
+ (0, vitest_1.it)("converts strikethrough", () => {
34
+ (0, vitest_1.expect)((0, format_1.mdToTelegramHtml)("~~removed~~")).toBe("<s>removed</s>");
35
+ });
36
+ (0, vitest_1.it)("escapes HTML in plain text", () => {
37
+ (0, vitest_1.expect)((0, format_1.mdToTelegramHtml)("<script>alert(1)</script>")).toBe("&lt;script&gt;alert(1)&lt;/script&gt;");
38
+ });
39
+ (0, vitest_1.it)("preserves HTML inside code blocks", () => {
40
+ const result = (0, format_1.mdToTelegramHtml)("```\n<div>hi</div>\n```");
41
+ (0, vitest_1.expect)(result).toContain("&lt;div&gt;hi&lt;/div&gt;");
42
+ });
43
+ });
44
+ (0, vitest_1.describe)("tryMdToHtml", () => {
45
+ (0, vitest_1.it)("returns HTML with parseMode", () => {
46
+ const result = (0, format_1.tryMdToHtml)("**bold**");
47
+ (0, vitest_1.expect)(result.text).toBe("<b>bold</b>");
48
+ (0, vitest_1.expect)(result.parseMode).toBe("HTML");
49
+ });
50
+ });
51
+ (0, vitest_1.describe)("truncateMessage", () => {
52
+ (0, vitest_1.it)("returns short text unchanged", () => {
53
+ (0, vitest_1.expect)((0, format_1.truncateMessage)("hello", 10)).toBe("hello");
54
+ });
55
+ (0, vitest_1.it)("truncates long text", () => {
56
+ const long = "a".repeat(100);
57
+ const result = (0, format_1.truncateMessage)(long, 50);
58
+ (0, vitest_1.expect)(result.length).toBeLessThanOrEqual(70); // 50 + truncation marker
59
+ (0, vitest_1.expect)(result).toContain("[truncated]");
60
+ });
61
+ });
62
+ //# sourceMappingURL=format.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.test.js","sourceRoot":"","sources":["../../src/__tests__/format.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sessions.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/sessions.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const sessions_1 = require("../sessions");
5
+ function makeSession(overrides = {}) {
6
+ return {
7
+ sessionId: "00000000-0000-0000-0000-000000000000",
8
+ slug: null,
9
+ project: "/home/user/project",
10
+ projectName: "project",
11
+ lastModified: Date.now() / 1000,
12
+ firstMessage: null,
13
+ lastMessage: null,
14
+ ...overrides,
15
+ };
16
+ }
17
+ (0, vitest_1.describe)("formatTimeAgo", () => {
18
+ (0, vitest_1.it)("returns 'just now' for < 60s", () => {
19
+ (0, vitest_1.expect)((0, sessions_1.formatTimeAgo)(Date.now() / 1000 - 10)).toBe("just now");
20
+ });
21
+ (0, vitest_1.it)("returns minutes ago", () => {
22
+ (0, vitest_1.expect)((0, sessions_1.formatTimeAgo)(Date.now() / 1000 - 300)).toBe("5m ago");
23
+ });
24
+ (0, vitest_1.it)("returns hours ago", () => {
25
+ (0, vitest_1.expect)((0, sessions_1.formatTimeAgo)(Date.now() / 1000 - 7200)).toBe("2h ago");
26
+ });
27
+ (0, vitest_1.it)("returns days ago", () => {
28
+ (0, vitest_1.expect)((0, sessions_1.formatTimeAgo)(Date.now() / 1000 - 172800)).toBe("2d ago");
29
+ });
30
+ });
31
+ (0, vitest_1.describe)("formatSessionLabel", () => {
32
+ (0, vitest_1.it)("shows project + firstMessage", () => {
33
+ const s = makeSession({ firstMessage: "hello world" });
34
+ (0, vitest_1.expect)((0, sessions_1.formatSessionLabel)(s)).toBe("project - hello world");
35
+ });
36
+ (0, vitest_1.it)("truncates long firstMessage", () => {
37
+ const s = makeSession({ firstMessage: "a".repeat(50) });
38
+ const label = (0, sessions_1.formatSessionLabel)(s);
39
+ (0, vitest_1.expect)(label.length).toBeLessThanOrEqual(60);
40
+ (0, vitest_1.expect)(label).toContain("...");
41
+ });
42
+ (0, vitest_1.it)("falls back to slug", () => {
43
+ const s = makeSession({ slug: "my-slug" });
44
+ (0, vitest_1.expect)((0, sessions_1.formatSessionLabel)(s)).toBe("project (my-slug)");
45
+ });
46
+ (0, vitest_1.it)("falls back to project name only", () => {
47
+ const s = makeSession();
48
+ (0, vitest_1.expect)((0, sessions_1.formatSessionLabel)(s)).toBe("project");
49
+ });
50
+ });
51
+ //# sourceMappingURL=sessions.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.test.js","sourceRoot":"","sources":["../../src/__tests__/sessions.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ /** Stop listening for terminal resize (call before prompts / further output). */
2
+ export declare function stopBannerResize(): void;
3
+ export declare function printBanner(contentLines?: string[]): void;
4
+ //# sourceMappingURL=banner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"banner.d.ts","sourceRoot":"","sources":["../src/banner.ts"],"names":[],"mappings":"AAcA,iFAAiF;AACjF,wBAAgB,gBAAgB,IAAI,IAAI,CAKvC;AAED,wBAAgB,WAAW,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CA0GzD"}