cngkit 1.1.18 → 1.1.20

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 (80) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +94 -19
  3. package/dist/chunk-CIZBVLN5.js +35 -0
  4. package/dist/chunk-CIZBVLN5.js.map +1 -0
  5. package/dist/{chunk-IB5B3BLY.js → chunk-E2GLGGKO.js} +16 -4
  6. package/dist/chunk-E2GLGGKO.js.map +1 -0
  7. package/dist/chunk-MRXGD6TC.js +42 -0
  8. package/dist/chunk-MRXGD6TC.js.map +1 -0
  9. package/dist/{chunk-ZA4YOWPB.js → chunk-NGEWD4BW.js} +2 -1
  10. package/dist/chunk-NODJM6SH.js +658 -0
  11. package/dist/chunk-NODJM6SH.js.map +1 -0
  12. package/dist/chunk-SKK2XLRZ.js +1590 -0
  13. package/dist/chunk-SKK2XLRZ.js.map +1 -0
  14. package/dist/chunk-SMTQ3W3F.js +271 -0
  15. package/dist/chunk-SMTQ3W3F.js.map +1 -0
  16. package/dist/{chunk-FJ34NVQ4.js → chunk-WDI43VPW.js} +578 -88
  17. package/dist/chunk-WDI43VPW.js.map +1 -0
  18. package/dist/cli.js +107 -27
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/coderoom/index.js +6 -6
  21. package/dist/commands/coderoom/index.js.map +1 -1
  22. package/dist/commands/coderoom/join.js +5 -5
  23. package/dist/commands/coderoom/share.js +5 -5
  24. package/dist/commands/hookify/index.js +6 -6
  25. package/dist/commands/hookify/index.js.map +1 -1
  26. package/dist/commands/hookify/ingest.js +52 -13
  27. package/dist/commands/hookify/ingest.js.map +1 -1
  28. package/dist/commands/hooks/index.js +25 -0
  29. package/dist/commands/hooks/index.js.map +1 -0
  30. package/dist/commands/hooks/install.js +40 -0
  31. package/dist/commands/hooks/install.js.map +1 -0
  32. package/dist/commands/hooks/uninstall.js +40 -0
  33. package/dist/commands/hooks/uninstall.js.map +1 -0
  34. package/dist/commands/index.js +5 -5
  35. package/dist/commands/index.js.map +1 -1
  36. package/dist/commands/knowledges/audiences.js +6 -6
  37. package/dist/commands/knowledges/cat.js +31 -0
  38. package/dist/commands/knowledges/cat.js.map +1 -0
  39. package/dist/commands/knowledges/files.js +6 -6
  40. package/dist/commands/knowledges/find.js +66 -0
  41. package/dist/commands/knowledges/find.js.map +1 -0
  42. package/dist/commands/knowledges/glob.js +6 -6
  43. package/dist/commands/knowledges/grep.js +6 -6
  44. package/dist/commands/knowledges/head.js +41 -0
  45. package/dist/commands/knowledges/head.js.map +1 -0
  46. package/dist/commands/knowledges/index.js +6 -6
  47. package/dist/commands/knowledges/index.js.map +1 -1
  48. package/dist/commands/knowledges/list.js +7 -7
  49. package/dist/commands/knowledges/list.js.map +1 -1
  50. package/dist/commands/knowledges/ls.js +16 -7
  51. package/dist/commands/knowledges/ls.js.map +1 -1
  52. package/dist/commands/knowledges/read.js +6 -6
  53. package/dist/commands/knowledges/realpath.js +31 -0
  54. package/dist/commands/knowledges/realpath.js.map +1 -0
  55. package/dist/commands/knowledges/search.js +6 -6
  56. package/dist/commands/knowledges/stat.js +31 -0
  57. package/dist/commands/knowledges/stat.js.map +1 -0
  58. package/dist/commands/knowledges/status.js +6 -6
  59. package/dist/commands/knowledges/tail.js +41 -0
  60. package/dist/commands/knowledges/tail.js.map +1 -0
  61. package/dist/commands/knowledges/tree.js +46 -0
  62. package/dist/commands/knowledges/tree.js.map +1 -0
  63. package/dist/commands/login.js +4 -4
  64. package/dist/commands/login.js.map +1 -1
  65. package/dist/commands/scrub.js +38 -15
  66. package/dist/commands/scrub.js.map +1 -1
  67. package/dist/commands/transcripts.js +44 -24
  68. package/dist/commands/transcripts.js.map +1 -1
  69. package/package.json +3 -4
  70. package/dist/chunk-C7HFDK4S.js +0 -393
  71. package/dist/chunk-C7HFDK4S.js.map +0 -1
  72. package/dist/chunk-CBIVTEZP.js +0 -222
  73. package/dist/chunk-CBIVTEZP.js.map +0 -1
  74. package/dist/chunk-FJ34NVQ4.js.map +0 -1
  75. package/dist/chunk-IB5B3BLY.js.map +0 -1
  76. package/dist/chunk-KSW6QT5Q.js +0 -628
  77. package/dist/chunk-KSW6QT5Q.js.map +0 -1
  78. package/dist/chunk-TWQDLZ6F.js +0 -26
  79. package/dist/chunk-TWQDLZ6F.js.map +0 -1
  80. /package/dist/{chunk-ZA4YOWPB.js.map → chunk-NGEWD4BW.js.map} +0 -0
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  Beerware License
2
2
 
3
- This software is provided as personal, opinionated Curly.ng tooling.
3
+ This software is provided as personal, opinionated CNG tooling.
4
4
 
5
5
  You can use it, modify it, and share it. If it helps you, support the workshop.
6
6
 
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![npm version](https://img.shields.io/npm/v/cngkit.svg)](https://www.npmjs.com/package/cngkit)
4
4
  [![Node.js >= 20](https://img.shields.io/badge/node-%3E%3D20-339933)](https://nodejs.org/)
5
5
 
6
- Curly.ng's operator CLI for shared code rooms, safe local cleanup, local agent transcript
6
+ CNG operator CLI for shared code rooms, safe local cleanup, local agent transcript
7
7
  inspection, and terminal access to the hosted Harness knowledge catalog.
8
8
 
9
9
  `cngkit` is built for developers and AI agents who need practical commands that work
@@ -42,22 +42,24 @@ cngkit coderoom join <room-code>
42
42
  cngkit scrub [path]
43
43
  cngkit transcripts list --limit 12
44
44
  cngkit knowledges search Cloudflare --limit 3
45
- cngkit knowledges read /libraries/lib-cloudflare/SUBSKILL.md --limit 80
45
+ cngkit knowledges cat /libraries/lib-cloudflare/TOPIC.md
46
+ cngkit hooks install
46
47
  printf '{"hook":"PreToolUse"}' | cngkit hookify ingest --event PreToolUse
47
48
  ```
48
49
 
49
- The CLI currently has five main jobs:
50
+ The CLI currently has six main jobs:
50
51
 
51
52
  - **Coderoom**: start or join a live shared working-tree room.
52
53
  - **Scrub**: scan local files for secrets and optionally mask them inline.
53
54
  - **Transcripts**: inspect local Claude and Codex transcript files from the terminal.
54
- - **Knowledges**: search and read the hosted Harness knowledge catalog.
55
- - **Hookify**: forward local hook events to Curly.ng for server-side hook processing.
55
+ - **Knowledges**: traverse, search, and read the hosted Harness knowledge catalog.
56
+ - **Hooks**: install Hookify forwarding hooks into supported AI-assisted coding tools.
57
+ - **Hookify**: forward local hook events for server-side hook processing.
56
58
 
57
59
  ## Coderoom
58
60
 
59
61
  Coderoom is a live room for quickly sharing a working tree with another developer or agent.
60
- One machine starts a room, another joins with the room code, and the Curly backend relays
62
+ One machine starts a room, another joins with the room code, and the backend relays
61
63
  file changes.
62
64
 
63
65
  Start a room:
@@ -84,7 +86,7 @@ received change wins.
84
86
 
85
87
  ## Harness Knowledges
86
88
 
87
- The `knowledges` commands read the Cloudflare-backed Harness catalog from Curly.ng. They are
89
+ The `knowledges` commands read the Cloudflare-backed Harness catalog. They are
88
90
  read-only and designed for AI-friendly terminal use.
89
91
 
90
92
  Check catalog health:
@@ -93,6 +95,17 @@ Check catalog health:
93
95
  cngkit knowledges status
94
96
  ```
95
97
 
98
+ Browse the catalog like a remote filesystem:
99
+
100
+ ```bash
101
+ cngkit knowledges ls /
102
+ cngkit knowledges ls /libraries/lib-cloudflare -l
103
+ cngkit knowledges tree /libraries --depth 2
104
+ cngkit knowledges ls /libraries/lib-cloudflare
105
+ cngkit knowledges stat /libraries/lib-cloudflare/TOPIC.md
106
+ cngkit knowledges realpath /libraries/lib-cloudflare
107
+ ```
108
+
96
109
  Find relevant knowledge:
97
110
 
98
111
  ```bash
@@ -102,7 +115,16 @@ cngkit knowledges search Cloudflare --limit 3
102
115
  Read a catalog file:
103
116
 
104
117
  ```bash
105
- cngkit knowledges read /libraries/lib-cloudflare/SUBSKILL.md --limit 80
118
+ cngkit knowledges cat /libraries/lib-cloudflare/TOPIC.md
119
+ cngkit knowledges head /libraries/lib-cloudflare/TOPIC.md -n 20
120
+ cngkit knowledges tail /libraries/lib-cloudflare/TOPIC.md -n 20
121
+ cngkit knowledges read /libraries/lib-cloudflare/TOPIC.md --limit 80
122
+ ```
123
+
124
+ Find paths by shell-style filters:
125
+
126
+ ```bash
127
+ cngkit knowledges find /libraries -name "*cloudflare*" -type f
106
128
  ```
107
129
 
108
130
  List matching files:
@@ -121,21 +143,37 @@ Return JSON for another tool:
121
143
 
122
144
  ```bash
123
145
  cngkit knowledges status --json
146
+ cngkit --format json knowledges ls /
147
+ cngkit --format json knowledges tree /libraries --depth 2
148
+ cngkit --format json knowledges find /libraries -name "*cloudflare*" -type f
149
+ cngkit --format json knowledges stat /libraries/lib-cloudflare/TOPIC.md
124
150
  cngkit --format json knowledges search "vector search" --limit 5
125
151
  ```
126
152
 
127
- Catalog path shortcuts are supported. For example:
153
+ Catalog paths are rooted at the Harness topics folder. For example:
128
154
 
129
155
  ```text
130
- /libraries/lib-cloudflare/SUBSKILL.md
156
+ /libraries/lib-cloudflare/TOPIC.md
131
157
  ```
132
158
 
133
- maps to:
159
+ means:
134
160
 
135
161
  ```text
136
- skills/knowledges/subskills/libraries/lib-cloudflare/SUBSKILL.md
162
+ ~/.agents/topics/libraries/lib-cloudflare/TOPIC.md
137
163
  ```
138
164
 
165
+ The `knowledges` command set is stateless. It does not keep a working directory and does
166
+ not provide interactive traversal commands; pass the path explicitly on each command.
167
+
168
+ Text output is shell-friendly:
169
+
170
+ - `ls` prints one entry name per line; directories end with `/`. Use `--long` or `-l`
171
+ for the older metadata-heavy tab-separated output.
172
+ - `tree` prints a deterministic plain-text tree; directories end with `/`.
173
+ - `cat`, `head`, and `tail` print raw file content only.
174
+ - `find`, `glob`, and `grep --output-mode files_with_matches` print one path per line.
175
+ - `read`, `glob`, and `realpath` remain useful agent/API-oriented helper commands.
176
+
139
177
  ## Secret Scrubbing
140
178
 
141
179
  `scrub` scans a file or directory with TruffleHog and prints a redacted report.
@@ -172,17 +210,38 @@ Safety notes:
172
210
 
173
211
  ## Hookify
174
212
 
175
- `hookify` is the Curly.ng hook processing surface. The first command is intentionally
176
- small: it reads stdin and forwards the raw payload to the backend.
213
+ Use `hooks install` to configure supported local AI-assisted coding tools:
214
+
215
+ ```bash
216
+ cngkit hooks install --dry-run
217
+ cngkit hooks install
218
+ cngkit hooks install --tool codex
219
+ cngkit hooks uninstall --dry-run
220
+ ```
221
+
222
+ The installer currently writes user-level hook configuration for Claude Code and Codex.
223
+ It preserves existing non-cngkit hook handlers, replaces older cngkit Hookify handlers,
224
+ and installs `cngkit hookify ingest --event <EventName>` for every supported hook event
225
+ in each tool. Hook commands use the already-installed `cngkit` binary so hook execution
226
+ does not resolve `cngkit@latest` through npm on every event.
227
+ `hooks uninstall` removes current direct cngkit Hookify handlers and older npm/npx
228
+ `cngkit@latest` handlers, then reports the exact files, handler counts, and event names
229
+ it changed.
230
+
231
+ `hookify` is the hosted hook processing surface. The first command is intentionally
232
+ small: it reads stdin and forwards the raw payload to the backend. The backend can return
233
+ the hook result immediately or return a request id for workflow-backed processing; the CLI
234
+ polls until the final stdout, stderr, and exit code are ready.
177
235
 
178
236
  ```bash
179
237
  cngkit hookify ingest --event PreToolUse < hook-payload.json
180
238
  cngkit hookify ingest --event Stop --async < hook-payload.json
181
239
  ```
182
240
 
183
- The backend response controls stdout and the process exit code. If the backend request
184
- fails, the command falls back to exit code `0` so local hooks do not block work because
185
- the remote service is unavailable.
241
+ The backend response controls stdout and the process exit code. `--async` means the server
242
+ may defer the result behind a request id; it does not mean the local hook returns before the
243
+ final result is known. If the backend request or polling fails, the command falls back to
244
+ exit code `0` so local hooks do not block work because the remote service is unavailable.
186
245
 
187
246
  ## Local Agent Transcripts
188
247
 
@@ -211,7 +270,7 @@ Search recent transcript entries:
211
270
  cngkit transcripts grep "deploy failed" --source all --file-limit 60 --limit 20
212
271
  ```
213
272
 
214
- Transcript commands are local-only. They do not upload transcript content to Curly.ng.
273
+ Transcript commands are local-only. They do not upload transcript content to the backend.
215
274
  By default, they print user and assistant text and skip internal prompt, hook, and tool noise.
216
275
  Use `--include-internal` when debugging transcript plumbing.
217
276
 
@@ -232,7 +291,7 @@ source text:
232
291
  ```bash
233
292
  cngkit --format text --help
234
293
  cngkit --format json knowledges status
235
- cngkit --format markdown knowledges read /libraries/lib-cloudflare/SUBSKILL.md
294
+ cngkit --format markdown knowledges read /libraries/lib-cloudflare/TOPIC.md
236
295
  cngkit --no-color knowledges status
237
296
  ```
238
297
 
@@ -251,6 +310,9 @@ cngkit help knowledges
251
310
  cngkit coderoom --help
252
311
  cngkit knowledges read --help
253
312
  cngkit transcripts --help
313
+ cngkit hooks --help
314
+ cngkit hooks install --help
315
+ cngkit hooks uninstall --help
254
316
  cngkit hookify --help
255
317
  cngkit hookify ingest --help
256
318
  ```
@@ -280,9 +342,22 @@ src/commands/
280
342
  share.tsx cngkit coderoom share
281
343
  join.tsx cngkit coderoom join
282
344
  knowledges/
345
+ ls.tsx cngkit knowledges ls
346
+ tree.tsx cngkit knowledges tree
347
+ cat.tsx cngkit knowledges cat
348
+ head.tsx cngkit knowledges head
349
+ tail.tsx cngkit knowledges tail
350
+ find.tsx cngkit knowledges find
351
+ stat.tsx cngkit knowledges stat
352
+ realpath.tsx cngkit knowledges realpath
283
353
  read.tsx cngkit knowledges read
284
354
  grep.tsx cngkit knowledges grep
285
355
  glob.tsx cngkit knowledges glob
356
+ hooks/
357
+ install.tsx cngkit hooks install
358
+ uninstall.tsx cngkit hooks uninstall
359
+ hookify/
360
+ ingest.tsx cngkit hookify ingest
286
361
  ```
287
362
 
288
363
  The CLI does not hand-roll ANSI escape codes. Status styles go through Ink's built-in
@@ -0,0 +1,35 @@
1
+ import {
2
+ CngApiClient,
3
+ formatError,
4
+ resolveApiBaseUrl
5
+ } from "./chunk-WDI43VPW.js";
6
+
7
+ // src/shared/api-client.ts
8
+ function createCngApiClient(options) {
9
+ return new CngApiClient({
10
+ baseUrl: resolveApiBaseUrl(options),
11
+ timeoutInSeconds: 15,
12
+ maxRetries: 1
13
+ });
14
+ }
15
+ async function readBackendHealth(options) {
16
+ try {
17
+ const client = createCngApiClient(options);
18
+ const health = await client.system.getHealth();
19
+ return {
20
+ ok: true,
21
+ service: health.service
22
+ };
23
+ } catch (error) {
24
+ return {
25
+ ok: false,
26
+ message: formatError(error)
27
+ };
28
+ }
29
+ }
30
+
31
+ export {
32
+ createCngApiClient,
33
+ readBackendHealth
34
+ };
35
+ //# sourceMappingURL=chunk-CIZBVLN5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/shared/api-client.ts"],"sourcesContent":["import { CngApiClient } from \"@cng/client\";\n\nimport { resolveApiBaseUrl, type GlobalCommandOptions } from \"./config.js\";\nimport { formatError } from \"./output.js\";\n\nexport type BackendHealthStatus =\n | {\n ok: true;\n service: string;\n }\n | {\n ok: false;\n message: string;\n };\n\nexport function createCngApiClient(options: GlobalCommandOptions): CngApiClient {\n return new CngApiClient({\n baseUrl: resolveApiBaseUrl(options),\n timeoutInSeconds: 15,\n maxRetries: 1,\n });\n}\n\nexport async function readBackendHealth(\n options: GlobalCommandOptions\n): Promise<BackendHealthStatus> {\n try {\n const client = createCngApiClient(options);\n const health = await client.system.getHealth();\n return {\n ok: true,\n service: health.service,\n };\n } catch (error) {\n return {\n ok: false,\n message: formatError(error),\n };\n }\n}\n"],"mappings":";;;;;;;AAeO,SAAS,mBAAmB,SAA6C;AAC9E,SAAO,IAAI,aAAa;AAAA,IACtB,SAAS,kBAAkB,OAAO;AAAA,IAClC,kBAAkB;AAAA,IAClB,YAAY;AAAA,EACd,CAAC;AACH;AAEA,eAAsB,kBACpB,SAC8B;AAC9B,MAAI;AACF,UAAM,SAAS,mBAAmB,OAAO;AACzC,UAAM,SAAS,MAAM,OAAO,OAAO,UAAU;AAC7C,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,SAAS,OAAO;AAAA,IAClB;AAAA,EACF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,SAAS,YAAY,KAAK;AAAA,IAC5B;AAAA,EACF;AACF;","names":[]}
@@ -1,17 +1,18 @@
1
1
  import {
2
2
  readBackendHealth
3
- } from "./chunk-FJ34NVQ4.js";
3
+ } from "./chunk-CIZBVLN5.js";
4
4
  import {
5
5
  createPeerId,
6
6
  createRoomCode,
7
7
  resolveApiBaseUrl
8
- } from "./chunk-CBIVTEZP.js";
8
+ } from "./chunk-WDI43VPW.js";
9
9
 
10
10
  // src/features/coderoom/run-coderoom-command.ts
11
11
  import process2 from "process";
12
12
 
13
13
  // src/features/coderoom/sync/client.ts
14
14
  import process from "process";
15
+ import fs2 from "fs";
15
16
  import chokidar from "chokidar";
16
17
  import WebSocket from "ws";
17
18
 
@@ -263,7 +264,15 @@ function createRepoWatcher(context) {
263
264
  ignoreInitial: true,
264
265
  ignored: (candidatePath) => {
265
266
  const relativePath = toRepoRelativePath(context.repoContext.rootDir, candidatePath);
266
- return relativePath?.split("/").includes(".git") ?? false;
267
+ if (relativePath?.split("/").includes(".git") ?? false) {
268
+ return true;
269
+ }
270
+ try {
271
+ const stat = fs2.statSync(candidatePath);
272
+ return !stat.isFile() && !stat.isDirectory();
273
+ } catch {
274
+ return false;
275
+ }
267
276
  }
268
277
  });
269
278
  watcher.on("add", (absolutePath) => {
@@ -275,6 +284,9 @@ function createRepoWatcher(context) {
275
284
  watcher.on("unlink", (absolutePath) => {
276
285
  void sendLocalDelete(context, absolutePath);
277
286
  });
287
+ watcher.on("error", (watcherError) => {
288
+ context.output.warning(`watcher error: ${watcherError instanceof Error ? watcherError.message : String(watcherError)}`);
289
+ });
278
290
  return watcher;
279
291
  }
280
292
  async function sendInitialSnapshot(socket, repoContext, peerId, output) {
@@ -412,4 +424,4 @@ export {
412
424
  runShareCommand,
413
425
  runJoinCommand
414
426
  };
415
- //# sourceMappingURL=chunk-IB5B3BLY.js.map
427
+ //# sourceMappingURL=chunk-E2GLGGKO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/features/coderoom/run-coderoom-command.ts","../src/features/coderoom/sync/client.ts","../src/features/coderoom/sync/files.ts","../src/features/coderoom/sync/paths.ts","../src/features/coderoom/sync/protocol.ts"],"sourcesContent":["import process from \"node:process\";\n\nimport { readBackendHealth } from \"../../shared/api-client.js\";\nimport { createRoomCode, resolveApiBaseUrl, type GlobalCommandOptions } from \"../../shared/config.js\";\nimport type { CommandOutput } from \"../../shared/output.js\";\nimport { startSyncSession, type SyncSessionRole } from \"./sync/client.js\";\n\nexport type CoderoomCommandOptions = GlobalCommandOptions;\n\nexport type ShareCommandOptions = GlobalCommandOptions;\n\nexport async function runCoderoomCommand(\n args: string[] | undefined,\n options: CoderoomCommandOptions,\n output: CommandOutput\n): Promise<void> {\n const [subcommand, ...subcommandArgs] = args ?? [];\n\n switch (subcommand) {\n case \"share\":\n return runShareCommand(subcommandArgs[0], options, output);\n case \"join\":\n return runJoinCommand(subcommandArgs[0], options, output);\n default:\n throw new Error(\"Missing coderoom command. Usage: cngkit coderoom <share|join>\");\n }\n}\n\nexport async function runShareCommand(\n roomCode: string | undefined,\n options: ShareCommandOptions,\n output: CommandOutput\n): Promise<void> {\n const syncRoomCode = roomCode ?? createRoomCode();\n\n output.success(`Share code: ${syncRoomCode}`);\n await printBackendStatus(options, output);\n await runSyncSession(\"share\", syncRoomCode, options, output);\n}\n\nexport async function runJoinCommand(\n roomCode: string | undefined,\n options: GlobalCommandOptions,\n output: CommandOutput\n): Promise<void> {\n if (!roomCode) {\n throw new Error(\"Missing room code. Usage: cngkit coderoom join <room-code>\");\n }\n\n await printBackendStatus(options, output);\n await runSyncSession(\"join\", roomCode, options, output);\n}\n\nasync function printBackendStatus(\n options: GlobalCommandOptions,\n output: CommandOutput\n): Promise<void> {\n const health = await readBackendHealth(options);\n if (health.ok) {\n output.success(`API: ${health.service} ready`);\n return;\n }\n\n output.warning(`API: unavailable (${health.message})`);\n}\n\nasync function runSyncSession(\n role: SyncSessionRole,\n roomCode: string,\n options: GlobalCommandOptions,\n output: CommandOutput\n): Promise<void> {\n await startSyncSession({\n apiBaseUrl: resolveApiBaseUrl(options),\n roomCode,\n role,\n cwd: process.cwd(),\n output,\n });\n}\n","import process from \"node:process\";\nimport fs from \"node:fs\";\n\nimport chokidar, { type FSWatcher } from \"chokidar\";\nimport WebSocket, { type RawData } from \"ws\";\n\nimport { createPeerId } from \"../../../shared/config.js\";\nimport type { CommandOutput } from \"../../../shared/output.js\";\nimport {\n applyRemoteMessage,\n buildFileMessage,\n collectSnapshotMessages,\n createSuppressionTracker,\n type SuppressionTracker,\n} from \"./files.js\";\nimport {\n resolveRepoContext,\n shouldSyncRelativePath,\n toRepoRelativePath,\n type RepoContext,\n} from \"./paths.js\";\nimport { decodeSyncMessage, encodeSyncMessage, type SyncMessage } from \"./protocol.js\";\n\nexport type SyncSessionRole = \"share\" | \"join\";\n\nexport type StartSyncSessionOptions = {\n apiBaseUrl: string;\n roomCode: string;\n role: SyncSessionRole;\n cwd: string;\n output: CommandOutput;\n};\n\ntype WatcherContext = {\n repoContext: RepoContext;\n socket: WebSocket;\n peerId: string;\n suppressionTracker: SuppressionTracker;\n output: CommandOutput;\n};\n\nfunction createSyncWebSocketUrl(apiBaseUrl: string, roomCode: string): string {\n const url = new URL(apiBaseUrl);\n url.protocol = url.protocol === \"http:\" ? \"ws:\" : \"wss:\";\n url.pathname = `/api/cng/sync/${encodeURIComponent(roomCode)}`;\n url.search = \"\";\n url.hash = \"\";\n return url.toString();\n}\n\nfunction sendMessage(socket: WebSocket, message: SyncMessage): void {\n if (socket.readyState === WebSocket.OPEN) {\n socket.send(encodeSyncMessage(message));\n }\n}\n\nexport async function startSyncSession(options: StartSyncSessionOptions): Promise<void> {\n const repoContext = await resolveRepoContext(options.cwd);\n const peerId = createPeerId();\n const suppressionTracker = createSuppressionTracker();\n const webSocketUrl = createSyncWebSocketUrl(options.apiBaseUrl, options.roomCode);\n const socket = new WebSocket(webSocketUrl);\n\n options.output.success(`Room: ${options.roomCode}`);\n options.output.info(`Repo: ${repoContext.rootDir}`);\n options.output.muted(`Peer: ${peerId}`);\n\n const watcherContext: WatcherContext = {\n repoContext,\n socket,\n peerId,\n suppressionTracker,\n output: options.output,\n };\n const watcher = createRepoWatcher(watcherContext);\n\n socket.on(\"open\", () => {\n sendMessage(socket, {\n type: \"hello\",\n peerId,\n role: options.role,\n sentAt: Date.now(),\n });\n void sendInitialSnapshot(socket, repoContext, peerId, options.output);\n });\n\n socket.on(\"message\", (data) => {\n void handleRemoteMessage({\n data,\n repoContext,\n peerId,\n suppressionTracker,\n output: options.output,\n });\n });\n\n await waitForSessionClose(socket, watcher);\n}\n\nfunction createRepoWatcher(context: WatcherContext): FSWatcher {\n const watcher = chokidar.watch(context.repoContext.rootDir, {\n ignoreInitial: true,\n ignored: (candidatePath) => {\n const relativePath = toRepoRelativePath(context.repoContext.rootDir, candidatePath);\n if (relativePath?.split(\"/\").includes(\".git\") ?? false) {\n return true;\n }\n try {\n const stat = fs.statSync(candidatePath);\n return !stat.isFile() && !stat.isDirectory();\n } catch {\n return false;\n }\n },\n });\n\n watcher.on(\"add\", (absolutePath) => {\n void sendLocalFileChange(context, absolutePath);\n });\n watcher.on(\"change\", (absolutePath) => {\n void sendLocalFileChange(context, absolutePath);\n });\n watcher.on(\"unlink\", (absolutePath) => {\n void sendLocalDelete(context, absolutePath);\n });\n watcher.on(\"error\", (watcherError) => {\n context.output.warning(`watcher error: ${watcherError instanceof Error ? watcherError.message : String(watcherError)}`);\n });\n\n return watcher;\n}\n\nasync function sendInitialSnapshot(\n socket: WebSocket,\n repoContext: RepoContext,\n peerId: string,\n output: CommandOutput\n): Promise<void> {\n let fileCount = 0;\n\n for await (const message of collectSnapshotMessages(repoContext, peerId)) {\n sendMessage(socket, message);\n fileCount += 1;\n }\n\n sendMessage(socket, {\n type: \"snapshot-complete\",\n peerId,\n sentAt: Date.now(),\n fileCount,\n });\n output.success(`sent snapshot ${fileCount} files`);\n}\n\nasync function sendLocalFileChange(context: WatcherContext, absolutePath: string): Promise<void> {\n const relativePath = toRepoRelativePath(context.repoContext.rootDir, absolutePath);\n if (!relativePath || context.suppressionTracker.isSuppressed(relativePath)) {\n return;\n }\n\n const message = await buildFileMessage(context.repoContext, context.peerId, relativePath);\n if (!message) {\n return;\n }\n\n sendMessage(context.socket, message);\n context.output.muted(`sent file ${relativePath}`);\n}\n\nasync function sendLocalDelete(context: WatcherContext, absolutePath: string): Promise<void> {\n const relativePath = toRepoRelativePath(context.repoContext.rootDir, absolutePath);\n if (!relativePath || context.suppressionTracker.isSuppressed(relativePath)) {\n return;\n }\n\n if (!(await shouldSyncRelativePath(context.repoContext, relativePath))) {\n return;\n }\n\n sendMessage(context.socket, {\n type: \"delete\",\n peerId: context.peerId,\n path: relativePath,\n mtimeMs: Date.now(),\n sentAt: Date.now(),\n });\n context.output.warning(`sent delete ${relativePath}`);\n}\n\nasync function handleRemoteMessage(options: {\n data: RawData;\n repoContext: RepoContext;\n peerId: string;\n suppressionTracker: SuppressionTracker;\n output: CommandOutput;\n}): Promise<void> {\n const decodedMessage = decodeSyncMessage(options.data.toString());\n if (!decodedMessage || decodedMessage.peerId === options.peerId) {\n return;\n }\n\n if (decodedMessage.type === \"hello\") {\n options.output.info(`peer joined ${decodedMessage.peerId}`);\n return;\n }\n\n if (decodedMessage.type === \"snapshot-complete\") {\n options.output.success(`peer snapshot complete ${decodedMessage.fileCount} files`);\n return;\n }\n\n await applyRemoteMessage(options.repoContext, decodedMessage, options.suppressionTracker);\n options.output.success(`applied ${decodedMessage.type} ${decodedMessage.path}`);\n}\n\nasync function waitForSessionClose(socket: WebSocket, watcher: FSWatcher): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n let settled = false;\n\n const closeSession = async (): Promise<void> => {\n await watcher.close();\n if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {\n socket.close();\n }\n };\n\n const finish = async (): Promise<void> => {\n if (settled) {\n return;\n }\n settled = true;\n process.off(\"SIGINT\", onSignal);\n process.off(\"SIGTERM\", onSignal);\n await closeSession();\n resolve();\n };\n\n const onSignal = (): void => {\n void finish();\n };\n\n socket.on(\"error\", async (error) => {\n if (settled) {\n return;\n }\n settled = true;\n process.off(\"SIGINT\", onSignal);\n process.off(\"SIGTERM\", onSignal);\n await closeSession();\n reject(error);\n });\n\n socket.on(\"close\", () => {\n void finish();\n });\n\n process.on(\"SIGINT\", onSignal);\n process.on(\"SIGTERM\", onSignal);\n });\n}\n","import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { SyncFileMutationMessage, SyncMessage } from \"./protocol.js\";\nimport {\n resolveRepoPath,\n shouldSyncRelativePath,\n toRepoRelativePath,\n type RepoContext,\n} from \"./paths.js\";\n\nexport type SuppressionTracker = {\n suppress(relativePath: string): void;\n isSuppressed(relativePath: string): boolean;\n};\n\nexport function createSuppressionTracker(windowMs = 1500): SuppressionTracker {\n const suppressedUntilByPath = new Map<string, number>();\n\n return {\n suppress(relativePath: string) {\n suppressedUntilByPath.set(relativePath, Date.now() + windowMs);\n },\n isSuppressed(relativePath: string) {\n const suppressedUntil = suppressedUntilByPath.get(relativePath);\n if (!suppressedUntil) {\n return false;\n }\n if (suppressedUntil < Date.now()) {\n suppressedUntilByPath.delete(relativePath);\n return false;\n }\n return true;\n },\n };\n}\n\nexport async function* collectSnapshotMessages(\n context: RepoContext,\n peerId: string\n): AsyncGenerator<SyncMessage> {\n yield* collectDirectorySnapshot(context, context.rootDir, peerId);\n}\n\nasync function* collectDirectorySnapshot(\n context: RepoContext,\n directoryPath: string,\n peerId: string\n): AsyncGenerator<SyncMessage> {\n const entries = await fs.opendir(directoryPath);\n\n for await (const entry of entries) {\n const absolutePath = path.join(directoryPath, entry.name);\n const relativePath = toRepoRelativePath(context.rootDir, absolutePath);\n\n if (!relativePath || !(await shouldSyncRelativePath(context, relativePath))) {\n continue;\n }\n\n if (entry.isDirectory()) {\n yield* collectDirectorySnapshot(context, absolutePath, peerId);\n continue;\n }\n\n if (!entry.isFile()) {\n continue;\n }\n\n const [content, stat] = await Promise.all([fs.readFile(absolutePath), fs.stat(absolutePath)]);\n yield {\n type: \"file\",\n peerId,\n path: relativePath,\n contentBase64: content.toString(\"base64\"),\n mtimeMs: stat.mtimeMs,\n sentAt: Date.now(),\n };\n }\n}\n\nexport async function buildFileMessage(\n context: RepoContext,\n peerId: string,\n relativePath: string\n): Promise<SyncMessage | undefined> {\n if (!(await shouldSyncRelativePath(context, relativePath))) {\n return undefined;\n }\n\n const absolutePath = resolveRepoPath(context.rootDir, relativePath);\n if (!absolutePath) {\n return undefined;\n }\n\n const stat = await fs.stat(absolutePath);\n if (!stat.isFile()) {\n return undefined;\n }\n\n const content = await fs.readFile(absolutePath);\n return {\n type: \"file\",\n peerId,\n path: relativePath,\n contentBase64: content.toString(\"base64\"),\n mtimeMs: stat.mtimeMs,\n sentAt: Date.now(),\n };\n}\n\nexport async function applyRemoteMessage(\n context: RepoContext,\n message: SyncFileMutationMessage,\n suppressionTracker: SuppressionTracker\n): Promise<void> {\n const absolutePath = resolveRepoPath(context.rootDir, message.path);\n if (!absolutePath || !(await shouldSyncRelativePath(context, message.path))) {\n return;\n }\n\n suppressionTracker.suppress(message.path);\n\n if (message.type === \"delete\") {\n await fs.rm(absolutePath, { force: true });\n return;\n }\n\n await fs.mkdir(path.dirname(absolutePath), { recursive: true });\n await fs.writeFile(absolutePath, Buffer.from(message.contentBase64, \"base64\"));\n}\n","import { execFile } from \"node:child_process\";\nimport path from \"node:path\";\nimport { promisify } from \"node:util\";\n\nconst execFileAsync = promisify(execFile);\n\nexport type RepoContext = {\n rootDir: string;\n};\n\nexport async function resolveRepoContext(cwd: string): Promise<RepoContext> {\n try {\n const { stdout } = await execFileAsync(\"git\", [\"rev-parse\", \"--show-toplevel\"], {\n cwd,\n });\n return { rootDir: path.resolve(stdout.trim()) };\n } catch {\n return { rootDir: path.resolve(cwd) };\n }\n}\n\nexport function toRepoRelativePath(rootDir: string, absolutePath: string): string | undefined {\n const relativePath = path.relative(rootDir, absolutePath);\n\n if (!relativePath || relativePath.startsWith(\"..\") || path.isAbsolute(relativePath)) {\n return undefined;\n }\n\n return relativePath.split(path.sep).join(\"/\");\n}\n\nexport function resolveRepoPath(rootDir: string, relativePath: string): string | undefined {\n if (!isSafeRelativePath(relativePath)) {\n return undefined;\n }\n\n const absolutePath = path.resolve(rootDir, relativePath);\n const normalizedRoot = `${path.resolve(rootDir)}${path.sep}`;\n\n if (absolutePath !== path.resolve(rootDir) && !absolutePath.startsWith(normalizedRoot)) {\n return undefined;\n }\n\n return absolutePath;\n}\n\nexport function isSafeRelativePath(relativePath: string): boolean {\n if (!relativePath || relativePath.startsWith(\"/\") || relativePath.includes(\"\\0\")) {\n return false;\n }\n\n const normalizedParts = relativePath.split(/[\\\\/]+/).filter(Boolean);\n if (normalizedParts.includes(\"..\")) {\n return false;\n }\n\n return normalizedParts[0] !== \".git\";\n}\n\nexport async function shouldSyncRelativePath(\n context: RepoContext,\n relativePath: string\n): Promise<boolean> {\n if (!isSafeRelativePath(relativePath)) {\n return false;\n }\n\n try {\n await execFileAsync(\"git\", [\"check-ignore\", \"--quiet\", \"--\", relativePath], {\n cwd: context.rootDir,\n });\n return false;\n } catch (error) {\n const exitCode =\n typeof error === \"object\" && error !== null && \"code\" in error ? error.code : undefined;\n return exitCode === 1;\n }\n}\n","import { z } from \"zod\";\n\nconst SyncBaseMessageSchema = z.object({\n peerId: z.string().min(1),\n sentAt: z.number().finite(),\n});\n\nexport const SyncHelloMessageSchema = SyncBaseMessageSchema.extend({\n type: z.literal(\"hello\"),\n role: z.union([z.literal(\"share\"), z.literal(\"join\")]),\n});\n\nexport const SyncFileMessageSchema = SyncBaseMessageSchema.extend({\n type: z.literal(\"file\"),\n path: z.string().min(1),\n contentBase64: z.string(),\n mtimeMs: z.number().finite(),\n});\n\nexport const SyncDeleteMessageSchema = SyncBaseMessageSchema.extend({\n type: z.literal(\"delete\"),\n path: z.string().min(1),\n mtimeMs: z.number().finite(),\n});\n\nexport const SyncSnapshotCompleteMessageSchema = SyncBaseMessageSchema.extend({\n type: z.literal(\"snapshot-complete\"),\n fileCount: z.number().int().nonnegative(),\n});\n\nexport const SyncMessageSchema = z.discriminatedUnion(\"type\", [\n SyncHelloMessageSchema,\n SyncFileMessageSchema,\n SyncDeleteMessageSchema,\n SyncSnapshotCompleteMessageSchema,\n]);\n\nexport type SyncMessage = z.infer<typeof SyncMessageSchema>;\nexport type SyncFileMutationMessage = Extract<SyncMessage, { type: \"file\" | \"delete\" }>;\n\nexport function encodeSyncMessage(message: SyncMessage): string {\n return JSON.stringify(SyncMessageSchema.parse(message));\n}\n\nexport function decodeSyncMessage(value: unknown): SyncMessage | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n try {\n return SyncMessageSchema.parse(JSON.parse(value));\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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","names":["process","fs","path","path","fs","process"]}
@@ -0,0 +1,42 @@
1
+ import {
2
+ CommandOutputMessages,
3
+ createCommandOutput,
4
+ formatError
5
+ } from "./chunk-WDI43VPW.js";
6
+
7
+ // src/cli/command-runner.tsx
8
+ import { useCallback, useEffect, useRef, useState } from "react";
9
+ import { useApp } from "ink";
10
+ import { jsx } from "react/jsx-runtime";
11
+ function CommandRunner({ run }) {
12
+ const { exit } = useApp();
13
+ const nextMessageIdRef = useRef(1);
14
+ const [messages, setMessages] = useState([]);
15
+ const appendMessage = useCallback((message) => {
16
+ const nextMessage = {
17
+ ...message,
18
+ id: nextMessageIdRef.current
19
+ };
20
+ nextMessageIdRef.current += 1;
21
+ setMessages((currentMessages) => [...currentMessages, nextMessage]);
22
+ }, []);
23
+ useEffect(() => {
24
+ const output = createCommandOutput({
25
+ appendMessage,
26
+ stdout: process.stdout,
27
+ stderr: process.stderr
28
+ });
29
+ void run(output).catch((error) => {
30
+ process.exitCode = 1;
31
+ output.error(formatError(error));
32
+ }).finally(() => {
33
+ setTimeout(() => exit(), 0);
34
+ });
35
+ }, [appendMessage, exit, run]);
36
+ return /* @__PURE__ */ jsx(CommandOutputMessages, { messages });
37
+ }
38
+
39
+ export {
40
+ CommandRunner
41
+ };
42
+ //# sourceMappingURL=chunk-MRXGD6TC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/command-runner.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useApp } from \"ink\";\n\nimport {\n CommandOutputMessages,\n createCommandOutput,\n formatError,\n type CommandOutput,\n type CommandOutputMessage,\n type CommandOutputMessagePayload,\n} from \"../shared/output.js\";\n\ntype CommandRunnerProps = {\n readonly run: (output: CommandOutput) => Promise<void>;\n};\n\nexport function CommandRunner({ run }: CommandRunnerProps) {\n const { exit } = useApp();\n const nextMessageIdRef = useRef(1);\n const [messages, setMessages] = useState<CommandOutputMessage[]>([]);\n const appendMessage = useCallback((message: CommandOutputMessagePayload) => {\n const nextMessage: CommandOutputMessage = {\n ...message,\n id: nextMessageIdRef.current,\n };\n nextMessageIdRef.current += 1;\n setMessages((currentMessages) => [...currentMessages, nextMessage]);\n }, []);\n\n useEffect(() => {\n const output = createCommandOutput({\n appendMessage,\n stdout: process.stdout,\n stderr: process.stderr,\n });\n\n void run(output)\n .catch((error: unknown) => {\n process.exitCode = 1;\n output.error(formatError(error));\n })\n .finally(() => {\n setTimeout(() => exit(), 0);\n });\n }, [appendMessage, exit, run]);\n\n return <CommandOutputMessages messages={messages} />;\n}\n"],"mappings":";;;;;;;AAAA,SAAS,aAAa,WAAW,QAAQ,gBAAgB;AACzD,SAAS,cAAc;AA6Cd;AA9BF,SAAS,cAAc,EAAE,IAAI,GAAuB;AACzD,QAAM,EAAE,KAAK,IAAI,OAAO;AACxB,QAAM,mBAAmB,OAAO,CAAC;AACjC,QAAM,CAAC,UAAU,WAAW,IAAI,SAAiC,CAAC,CAAC;AACnE,QAAM,gBAAgB,YAAY,CAAC,YAAyC;AAC1E,UAAM,cAAoC;AAAA,MACxC,GAAG;AAAA,MACH,IAAI,iBAAiB;AAAA,IACvB;AACA,qBAAiB,WAAW;AAC5B,gBAAY,CAAC,oBAAoB,CAAC,GAAG,iBAAiB,WAAW,CAAC;AAAA,EACpE,GAAG,CAAC,CAAC;AAEL,YAAU,MAAM;AACd,UAAM,SAAS,oBAAoB;AAAA,MACjC;AAAA,MACA,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAED,SAAK,IAAI,MAAM,EACZ,MAAM,CAAC,UAAmB;AACzB,cAAQ,WAAW;AACnB,aAAO,MAAM,YAAY,KAAK,CAAC;AAAA,IACjC,CAAC,EACA,QAAQ,MAAM;AACb,iBAAW,MAAM,KAAK,GAAG,CAAC;AAAA,IAC5B,CAAC;AAAA,EACL,GAAG,CAAC,eAAe,MAAM,GAAG,CAAC;AAE7B,SAAO,oBAAC,yBAAsB,UAAoB;AACpD;","names":[]}
@@ -107,9 +107,10 @@ export {
107
107
  OptionalQueryArgsSchema,
108
108
  RequiredQueryArgsSchema,
109
109
  RequiredFilePathArgsSchema,
110
+ RequiredCatalogPathArgsSchema,
110
111
  RequiredPatternArgsSchema,
111
112
  OptionalGlobPatternArgsSchema,
112
113
  LimitOptionsSchema,
113
114
  TranscriptArgsSchema
114
115
  };
115
- //# sourceMappingURL=chunk-ZA4YOWPB.js.map
116
+ //# sourceMappingURL=chunk-NGEWD4BW.js.map