cngkit 1.1.21 → 1.1.23

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 (87) hide show
  1. package/LICENSE +198 -4
  2. package/README.md +22 -12
  3. package/dist/{chunk-NGEWD4BW.js → chunk-25Q463MH.js} +1 -2
  4. package/dist/{chunk-SMTQ3W3F.js → chunk-3BATDTKU.js} +131 -112
  5. package/dist/chunk-3BATDTKU.js.map +1 -0
  6. package/dist/{chunk-DIJEVOVN.js → chunk-67FUBMNB.js} +71 -240
  7. package/dist/chunk-67FUBMNB.js.map +1 -0
  8. package/dist/chunk-AS7FIJWP.js +88 -0
  9. package/dist/chunk-AS7FIJWP.js.map +1 -0
  10. package/dist/chunk-BRFWVQI4.js +18 -0
  11. package/dist/chunk-BRFWVQI4.js.map +1 -0
  12. package/dist/{chunk-CVF2ODLP.js → chunk-BXS4IKUA.js} +300 -143
  13. package/dist/chunk-BXS4IKUA.js.map +1 -0
  14. package/dist/{chunk-YJXAH7D5.js → chunk-GYRLVNJX.js} +2 -2
  15. package/dist/chunk-GYRLVNJX.js.map +1 -0
  16. package/dist/{chunk-SKK2XLRZ.js → chunk-JNHW72SU.js} +21 -15
  17. package/dist/chunk-JNHW72SU.js.map +1 -0
  18. package/dist/chunk-U725ZHCI.js +67 -0
  19. package/dist/chunk-U725ZHCI.js.map +1 -0
  20. package/dist/chunk-VMTXY4KQ.js +111 -0
  21. package/dist/chunk-VMTXY4KQ.js.map +1 -0
  22. package/dist/chunk-WIEYHKQA.js +268 -0
  23. package/dist/chunk-WIEYHKQA.js.map +1 -0
  24. package/dist/chunk-YALWTRIP.js +102 -0
  25. package/dist/chunk-YALWTRIP.js.map +1 -0
  26. package/dist/cli.js +2 -2
  27. package/dist/cli.js.map +1 -1
  28. package/dist/commands/coderoom/index.js +4 -4
  29. package/dist/commands/coderoom/join.js +5 -5
  30. package/dist/commands/coderoom/share.js +8 -11
  31. package/dist/commands/coderoom/share.js.map +1 -1
  32. package/dist/commands/hookify/index.js +4 -4
  33. package/dist/commands/hookify/ingest.js +4 -4
  34. package/dist/commands/hooks/index.js +4 -4
  35. package/dist/commands/hooks/install.js +4 -4
  36. package/dist/commands/hooks/uninstall.js +4 -4
  37. package/dist/commands/index.js +3 -3
  38. package/dist/commands/knowledges/audiences.js +26 -8
  39. package/dist/commands/knowledges/audiences.js.map +1 -1
  40. package/dist/commands/knowledges/cat.js +26 -7
  41. package/dist/commands/knowledges/cat.js.map +1 -1
  42. package/dist/commands/knowledges/files.js +39 -8
  43. package/dist/commands/knowledges/files.js.map +1 -1
  44. package/dist/commands/knowledges/find.js +48 -8
  45. package/dist/commands/knowledges/find.js.map +1 -1
  46. package/dist/commands/knowledges/glob.js +41 -8
  47. package/dist/commands/knowledges/glob.js.map +1 -1
  48. package/dist/commands/knowledges/grep.js +73 -8
  49. package/dist/commands/knowledges/grep.js.map +1 -1
  50. package/dist/commands/knowledges/head.js +31 -8
  51. package/dist/commands/knowledges/head.js.map +1 -1
  52. package/dist/commands/knowledges/index.js +4 -4
  53. package/dist/commands/knowledges/list.js +39 -8
  54. package/dist/commands/knowledges/list.js.map +1 -1
  55. package/dist/commands/knowledges/ls.js +47 -8
  56. package/dist/commands/knowledges/ls.js.map +1 -1
  57. package/dist/commands/knowledges/read.js +46 -8
  58. package/dist/commands/knowledges/read.js.map +1 -1
  59. package/dist/commands/knowledges/realpath.js +23 -8
  60. package/dist/commands/knowledges/realpath.js.map +1 -1
  61. package/dist/commands/knowledges/search.js +34 -8
  62. package/dist/commands/knowledges/search.js.map +1 -1
  63. package/dist/commands/knowledges/stat.js +32 -8
  64. package/dist/commands/knowledges/stat.js.map +1 -1
  65. package/dist/commands/knowledges/status.js +26 -8
  66. package/dist/commands/knowledges/status.js.map +1 -1
  67. package/dist/commands/knowledges/tail.js +30 -8
  68. package/dist/commands/knowledges/tail.js.map +1 -1
  69. package/dist/commands/knowledges/tree.js +38 -8
  70. package/dist/commands/knowledges/tree.js.map +1 -1
  71. package/dist/commands/login.js +3 -3
  72. package/dist/commands/login.js.map +1 -1
  73. package/dist/commands/scrub.js +3 -3
  74. package/dist/commands/scrub.js.map +1 -1
  75. package/dist/commands/transcripts.js +137 -119
  76. package/dist/commands/transcripts.js.map +1 -1
  77. package/package.json +2 -2
  78. package/dist/chunk-52PGDSFU.js +0 -42
  79. package/dist/chunk-52PGDSFU.js.map +0 -1
  80. package/dist/chunk-5WTRGYAO.js +0 -658
  81. package/dist/chunk-5WTRGYAO.js.map +0 -1
  82. package/dist/chunk-CVF2ODLP.js.map +0 -1
  83. package/dist/chunk-DIJEVOVN.js.map +0 -1
  84. package/dist/chunk-SKK2XLRZ.js.map +0 -1
  85. package/dist/chunk-SMTQ3W3F.js.map +0 -1
  86. package/dist/chunk-YJXAH7D5.js.map +0 -1
  87. /package/dist/{chunk-NGEWD4BW.js.map → chunk-25Q463MH.js.map} +0 -0
@@ -1,20 +1,16 @@
1
1
  import {
2
+ createCngApiClient,
2
3
  readBackendHealth
3
- } from "./chunk-YJXAH7D5.js";
4
+ } from "./chunk-GYRLVNJX.js";
4
5
  import {
5
- createPeerId,
6
- createRoomCode,
7
6
  resolveApiBaseUrl
8
- } from "./chunk-DIJEVOVN.js";
7
+ } from "./chunk-67FUBMNB.js";
9
8
 
10
9
  // src/features/coderoom/run-coderoom-command.ts
11
- import process2 from "process";
10
+ import process3 from "process";
12
11
 
13
12
  // src/features/coderoom/sync/client.ts
14
- import process from "process";
15
- import fs2 from "fs";
16
- import chokidar from "chokidar";
17
- import WebSocket from "ws";
13
+ import WebSocket2 from "ws";
18
14
 
19
15
  // src/features/coderoom/sync/files.ts
20
16
  import fs from "fs/promises";
@@ -98,10 +94,10 @@ function createSuppressionTracker(windowMs = 1500) {
98
94
  }
99
95
  };
100
96
  }
101
- async function* collectSnapshotMessages(context, peerId) {
102
- yield* collectDirectorySnapshot(context, context.rootDir, peerId);
97
+ async function* collectSnapshotMessages(context) {
98
+ yield* collectDirectorySnapshot(context, context.rootDir);
103
99
  }
104
- async function* collectDirectorySnapshot(context, directoryPath, peerId) {
100
+ async function* collectDirectorySnapshot(context, directoryPath) {
105
101
  const entries = await fs.opendir(directoryPath);
106
102
  for await (const entry of entries) {
107
103
  const absolutePath = path2.join(directoryPath, entry.name);
@@ -110,7 +106,7 @@ async function* collectDirectorySnapshot(context, directoryPath, peerId) {
110
106
  continue;
111
107
  }
112
108
  if (entry.isDirectory()) {
113
- yield* collectDirectorySnapshot(context, absolutePath, peerId);
109
+ yield* collectDirectorySnapshot(context, absolutePath);
114
110
  continue;
115
111
  }
116
112
  if (!entry.isFile()) {
@@ -119,15 +115,13 @@ async function* collectDirectorySnapshot(context, directoryPath, peerId) {
119
115
  const [content, stat] = await Promise.all([fs.readFile(absolutePath), fs.stat(absolutePath)]);
120
116
  yield {
121
117
  type: "file",
122
- peerId,
123
118
  path: relativePath,
124
119
  contentBase64: content.toString("base64"),
125
- mtimeMs: stat.mtimeMs,
126
- sentAt: Date.now()
120
+ mtimeMs: stat.mtimeMs
127
121
  };
128
122
  }
129
123
  }
130
- async function buildFileMessage(context, peerId, relativePath) {
124
+ async function buildFileMessage(context, relativePath) {
131
125
  if (!await shouldSyncRelativePath(context, relativePath)) {
132
126
  return void 0;
133
127
  }
@@ -142,11 +136,9 @@ async function buildFileMessage(context, peerId, relativePath) {
142
136
  const content = await fs.readFile(absolutePath);
143
137
  return {
144
138
  type: "file",
145
- peerId,
146
139
  path: relativePath,
147
140
  contentBase64: content.toString("base64"),
148
- mtimeMs: stat.mtimeMs,
149
- sentAt: Date.now()
141
+ mtimeMs: stat.mtimeMs
150
142
  };
151
143
  }
152
144
  async function applyRemoteMessage(context, message, suppressionTracker) {
@@ -163,102 +155,195 @@ async function applyRemoteMessage(context, message, suppressionTracker) {
163
155
  await fs.writeFile(absolutePath, Buffer.from(message.contentBase64, "base64"));
164
156
  }
165
157
 
158
+ // src/features/coderoom/sync/local-events.ts
159
+ import fs2 from "fs";
160
+ import chokidar from "chokidar";
161
+
162
+ // src/features/coderoom/sync/session.ts
163
+ import os from "os";
164
+ import process from "process";
165
+ import WebSocket from "ws";
166
+
166
167
  // src/features/coderoom/sync/protocol.ts
167
168
  import { z } from "zod";
168
- var SyncBaseMessageSchema = z.object({
169
- peerId: z.string().min(1),
170
- sentAt: z.number().finite()
171
- });
172
- var SyncHelloMessageSchema = SyncBaseMessageSchema.extend({
173
- type: z.literal("hello"),
174
- role: z.union([z.literal("share"), z.literal("join")])
175
- });
176
- var SyncFileMessageSchema = SyncBaseMessageSchema.extend({
169
+ var ClientFileMessageSchema = z.object({
177
170
  type: z.literal("file"),
178
171
  path: z.string().min(1),
179
172
  contentBase64: z.string(),
180
173
  mtimeMs: z.number().finite()
181
174
  });
182
- var SyncDeleteMessageSchema = SyncBaseMessageSchema.extend({
175
+ var ClientDeleteMessageSchema = z.object({
183
176
  type: z.literal("delete"),
184
177
  path: z.string().min(1),
185
178
  mtimeMs: z.number().finite()
186
179
  });
187
- var SyncSnapshotCompleteMessageSchema = SyncBaseMessageSchema.extend({
188
- type: z.literal("snapshot-complete"),
180
+ var ClientJoinApprovalMessageSchema = z.object({
181
+ type: z.literal("join-approval"),
182
+ joinRequestId: z.string().min(1),
183
+ approved: z.boolean()
184
+ });
185
+ var ClientMessageSchema = z.discriminatedUnion("type", [
186
+ ClientFileMessageSchema,
187
+ ClientDeleteMessageSchema,
188
+ ClientJoinApprovalMessageSchema
189
+ ]);
190
+ var ServerConnectedMessageSchema = z.object({
191
+ type: z.literal("connected"),
192
+ roomCode: z.string(),
193
+ participantId: z.string(),
194
+ role: z.union([z.literal("host"), z.literal("guest")]),
195
+ status: z.union([z.literal("approved"), z.literal("pending"), z.literal("denied")])
196
+ });
197
+ var ServerJoinPendingMessageSchema = z.object({
198
+ type: z.literal("join-pending"),
199
+ participantId: z.string(),
200
+ joinRequestId: z.string(),
201
+ roomCode: z.string()
202
+ });
203
+ var ServerJoinRequestMessageSchema = z.object({
204
+ type: z.literal("join-request"),
205
+ joinRequestId: z.string(),
206
+ participantId: z.string(),
207
+ displayName: z.string(),
208
+ requestedAt: z.number().finite()
209
+ });
210
+ var ServerJoinApprovedMessageSchema = z.object({
211
+ type: z.literal("join-approved"),
212
+ participantId: z.string()
213
+ });
214
+ var ServerJoinDeniedMessageSchema = z.object({
215
+ type: z.literal("join-denied"),
216
+ participantId: z.string(),
217
+ reason: z.string()
218
+ });
219
+ var ServerFileMessageSchema = z.object({
220
+ type: z.literal("file"),
221
+ fileId: z.string(),
222
+ path: z.string().min(1),
223
+ sha256: z.string(),
224
+ size: z.number().int().nonnegative(),
225
+ contentBase64: z.string(),
226
+ mtimeMs: z.number().finite(),
227
+ updatedByParticipantId: z.string()
228
+ });
229
+ var ServerDeleteMessageSchema = z.object({
230
+ type: z.literal("delete"),
231
+ fileId: z.string().optional(),
232
+ path: z.string().min(1),
233
+ mtimeMs: z.number().finite(),
234
+ updatedByParticipantId: z.string()
235
+ });
236
+ var ServerFileAckMessageSchema = z.object({
237
+ type: z.literal("file-ack"),
238
+ fileId: z.string(),
239
+ path: z.string().min(1),
240
+ sha256: z.string(),
241
+ size: z.number().int().nonnegative(),
242
+ storage: z.union([z.literal("durable-object"), z.literal("r2")])
243
+ });
244
+ var ServerTreeStartMessageSchema = z.object({
245
+ type: z.literal("tree-start"),
189
246
  fileCount: z.number().int().nonnegative()
190
247
  });
191
- var SyncMessageSchema = z.discriminatedUnion("type", [
192
- SyncHelloMessageSchema,
193
- SyncFileMessageSchema,
194
- SyncDeleteMessageSchema,
195
- SyncSnapshotCompleteMessageSchema
248
+ var ServerTreeCompleteMessageSchema = z.object({
249
+ type: z.literal("tree-complete"),
250
+ fileCount: z.number().int().nonnegative()
251
+ });
252
+ var ServerErrorMessageSchema = z.object({
253
+ type: z.literal("error"),
254
+ error: z.string()
255
+ });
256
+ var ServerMessageSchema = z.discriminatedUnion("type", [
257
+ ServerConnectedMessageSchema,
258
+ ServerJoinPendingMessageSchema,
259
+ ServerJoinRequestMessageSchema,
260
+ ServerJoinApprovedMessageSchema,
261
+ ServerJoinDeniedMessageSchema,
262
+ ServerFileMessageSchema,
263
+ ServerDeleteMessageSchema,
264
+ ServerFileAckMessageSchema,
265
+ ServerTreeStartMessageSchema,
266
+ ServerTreeCompleteMessageSchema,
267
+ ServerErrorMessageSchema
196
268
  ]);
197
- function encodeSyncMessage(message) {
198
- return JSON.stringify(SyncMessageSchema.parse(message));
269
+ function encodeClientMessage(message) {
270
+ return JSON.stringify(ClientMessageSchema.parse(message));
199
271
  }
200
- function decodeSyncMessage(value) {
272
+ function decodeServerMessage(value) {
201
273
  if (typeof value !== "string") {
202
274
  return void 0;
203
275
  }
204
276
  try {
205
- return SyncMessageSchema.parse(JSON.parse(value));
277
+ return ServerMessageSchema.parse(JSON.parse(value));
206
278
  } catch {
207
279
  return void 0;
208
280
  }
209
281
  }
210
282
 
211
- // src/features/coderoom/sync/client.ts
212
- function createSyncWebSocketUrl(apiBaseUrl, roomCode) {
213
- const url = new URL(apiBaseUrl);
283
+ // src/features/coderoom/sync/session.ts
284
+ function createSyncWebSocketUrl(options) {
285
+ const url = new URL(options.apiBaseUrl);
214
286
  url.protocol = url.protocol === "http:" ? "ws:" : "wss:";
215
- url.pathname = `/api/cng/sync/${encodeURIComponent(roomCode)}`;
287
+ url.pathname = `/api/cng/coderoom/rooms/${encodeURIComponent(options.roomCode)}/ws`;
216
288
  url.search = "";
217
289
  url.hash = "";
290
+ url.searchParams.set("role", options.role);
291
+ url.searchParams.set("displayName", createDisplayName());
292
+ if (options.creatorToken) {
293
+ url.searchParams.set("creatorToken", options.creatorToken);
294
+ }
218
295
  return url.toString();
219
296
  }
220
297
  function sendMessage(socket, message) {
221
298
  if (socket.readyState === WebSocket.OPEN) {
222
- socket.send(encodeSyncMessage(message));
299
+ socket.send(encodeClientMessage(message));
223
300
  }
224
301
  }
225
- async function startSyncSession(options) {
226
- const repoContext = await resolveRepoContext(options.cwd);
227
- const peerId = createPeerId();
228
- const suppressionTracker = createSuppressionTracker();
229
- const webSocketUrl = createSyncWebSocketUrl(options.apiBaseUrl, options.roomCode);
230
- const socket = new WebSocket(webSocketUrl);
231
- options.output.success(`Room: ${options.roomCode}`);
232
- options.output.info(`Repo: ${repoContext.rootDir}`);
233
- options.output.muted(`Peer: ${peerId}`);
234
- const watcherContext = {
235
- repoContext,
236
- socket,
237
- peerId,
238
- suppressionTracker,
239
- output: options.output
240
- };
241
- const watcher = createRepoWatcher(watcherContext);
242
- socket.on("open", () => {
243
- sendMessage(socket, {
244
- type: "hello",
245
- peerId,
246
- role: options.role,
247
- sentAt: Date.now()
302
+ function createDisplayName() {
303
+ const username = os.userInfo().username || "user";
304
+ return `${username}@${os.hostname()}`;
305
+ }
306
+ async function waitForSessionClose(socket, watcher) {
307
+ await new Promise((resolve, reject) => {
308
+ let settled = false;
309
+ const closeSession = async () => {
310
+ await watcher.close();
311
+ if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
312
+ socket.close();
313
+ }
314
+ };
315
+ const finish = async () => {
316
+ if (settled) {
317
+ return;
318
+ }
319
+ settled = true;
320
+ process.off("SIGINT", onSignal);
321
+ process.off("SIGTERM", onSignal);
322
+ await closeSession();
323
+ resolve();
324
+ };
325
+ const onSignal = () => {
326
+ void finish();
327
+ };
328
+ socket.on("error", async (error) => {
329
+ if (settled) {
330
+ return;
331
+ }
332
+ settled = true;
333
+ process.off("SIGINT", onSignal);
334
+ process.off("SIGTERM", onSignal);
335
+ await closeSession();
336
+ reject(error);
248
337
  });
249
- void sendInitialSnapshot(socket, repoContext, peerId, options.output);
250
- });
251
- socket.on("message", (data) => {
252
- void handleRemoteMessage({
253
- data,
254
- repoContext,
255
- peerId,
256
- suppressionTracker,
257
- output: options.output
338
+ socket.on("close", () => {
339
+ void finish();
258
340
  });
341
+ process.on("SIGINT", onSignal);
342
+ process.on("SIGTERM", onSignal);
259
343
  });
260
- await waitForSessionClose(socket, watcher);
261
344
  }
345
+
346
+ // src/features/coderoom/sync/local-events.ts
262
347
  function createRepoWatcher(context) {
263
348
  const watcher = chokidar.watch(context.repoContext.rootDir, {
264
349
  ignoreInitial: true,
@@ -285,30 +370,29 @@ function createRepoWatcher(context) {
285
370
  void sendLocalDelete(context, absolutePath);
286
371
  });
287
372
  watcher.on("error", (watcherError) => {
288
- context.output.warning(`watcher error: ${watcherError instanceof Error ? watcherError.message : String(watcherError)}`);
373
+ context.output.warning(
374
+ `watcher error: ${watcherError instanceof Error ? watcherError.message : String(watcherError)}`
375
+ );
289
376
  });
290
377
  return watcher;
291
378
  }
292
- async function sendInitialSnapshot(socket, repoContext, peerId, output) {
379
+ async function sendInitialSnapshot(socket, repoContext, output) {
293
380
  let fileCount = 0;
294
- for await (const message of collectSnapshotMessages(repoContext, peerId)) {
381
+ for await (const message of collectSnapshotMessages(repoContext)) {
295
382
  sendMessage(socket, message);
296
383
  fileCount += 1;
297
384
  }
298
- sendMessage(socket, {
299
- type: "snapshot-complete",
300
- peerId,
301
- sentAt: Date.now(),
302
- fileCount
303
- });
304
385
  output.success(`sent snapshot ${fileCount} files`);
305
386
  }
306
387
  async function sendLocalFileChange(context, absolutePath) {
388
+ if (!context.state.approved) {
389
+ return;
390
+ }
307
391
  const relativePath = toRepoRelativePath(context.repoContext.rootDir, absolutePath);
308
392
  if (!relativePath || context.suppressionTracker.isSuppressed(relativePath)) {
309
393
  return;
310
394
  }
311
- const message = await buildFileMessage(context.repoContext, context.peerId, relativePath);
395
+ const message = await buildFileMessage(context.repoContext, relativePath);
312
396
  if (!message) {
313
397
  return;
314
398
  }
@@ -316,6 +400,9 @@ async function sendLocalFileChange(context, absolutePath) {
316
400
  context.output.muted(`sent file ${relativePath}`);
317
401
  }
318
402
  async function sendLocalDelete(context, absolutePath) {
403
+ if (!context.state.approved) {
404
+ return;
405
+ }
319
406
  const relativePath = toRepoRelativePath(context.repoContext.rootDir, absolutePath);
320
407
  if (!relativePath || context.suppressionTracker.isSuppressed(relativePath)) {
321
408
  return;
@@ -325,82 +412,147 @@ async function sendLocalDelete(context, absolutePath) {
325
412
  }
326
413
  sendMessage(context.socket, {
327
414
  type: "delete",
328
- peerId: context.peerId,
329
415
  path: relativePath,
330
- mtimeMs: Date.now(),
331
- sentAt: Date.now()
416
+ mtimeMs: Date.now()
332
417
  });
333
418
  context.output.warning(`sent delete ${relativePath}`);
334
419
  }
335
- async function handleRemoteMessage(options) {
336
- const decodedMessage = decodeSyncMessage(options.data.toString());
337
- if (!decodedMessage || decodedMessage.peerId === options.peerId) {
420
+
421
+ // src/features/coderoom/sync/remote-handler.ts
422
+ import process2 from "process";
423
+ import readline from "readline/promises";
424
+ async function handleServerMessage(options) {
425
+ const decodedMessage = decodeServerMessage(options.data.toString());
426
+ if (!decodedMessage) {
427
+ return;
428
+ }
429
+ if (decodedMessage.type === "connected") {
430
+ options.state.participantId = decodedMessage.participantId;
431
+ options.state.approved = decodedMessage.status === "approved";
432
+ options.output.muted(`Participant: ${decodedMessage.participantId}`);
338
433
  return;
339
434
  }
340
- if (decodedMessage.type === "hello") {
341
- options.output.info(`peer joined ${decodedMessage.peerId}`);
435
+ if (decodedMessage.type === "join-pending") {
436
+ options.output.info(`Join request sent: ${decodedMessage.joinRequestId}`);
342
437
  return;
343
438
  }
344
- if (decodedMessage.type === "snapshot-complete") {
345
- options.output.success(`peer snapshot complete ${decodedMessage.fileCount} files`);
439
+ if (decodedMessage.type === "join-request") {
440
+ await approveJoinRequest(options.socket, decodedMessage, options.output);
441
+ return;
442
+ }
443
+ if (decodedMessage.type === "join-approved") {
444
+ options.state.approved = true;
445
+ options.output.success("Join approved. Applying room file tree...");
446
+ return;
447
+ }
448
+ if (decodedMessage.type === "join-denied") {
449
+ options.output.warning(`Join denied: ${decodedMessage.reason}`);
450
+ options.socket.close();
451
+ return;
452
+ }
453
+ if (decodedMessage.type === "tree-start") {
454
+ options.output.info(`receiving room tree ${decodedMessage.fileCount} files`);
455
+ return;
456
+ }
457
+ if (decodedMessage.type === "tree-complete") {
458
+ options.output.success(`room tree complete ${decodedMessage.fileCount} files`);
459
+ return;
460
+ }
461
+ if (decodedMessage.type === "file-ack") {
462
+ options.output.muted(
463
+ `stored ${decodedMessage.storage} ${decodedMessage.path} ${decodedMessage.sha256}`
464
+ );
465
+ return;
466
+ }
467
+ if (decodedMessage.type === "error") {
468
+ options.output.warning(`server error: ${decodedMessage.error}`);
346
469
  return;
347
470
  }
348
471
  await applyRemoteMessage(options.repoContext, decodedMessage, options.suppressionTracker);
349
472
  options.output.success(`applied ${decodedMessage.type} ${decodedMessage.path}`);
350
473
  }
351
- async function waitForSessionClose(socket, watcher) {
352
- await new Promise((resolve, reject) => {
353
- let settled = false;
354
- const closeSession = async () => {
355
- await watcher.close();
356
- if (socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING) {
357
- socket.close();
358
- }
359
- };
360
- const finish = async () => {
361
- if (settled) {
362
- return;
363
- }
364
- settled = true;
365
- process.off("SIGINT", onSignal);
366
- process.off("SIGTERM", onSignal);
367
- await closeSession();
368
- resolve();
369
- };
370
- const onSignal = () => {
371
- void finish();
372
- };
373
- socket.on("error", async (error) => {
374
- if (settled) {
375
- return;
376
- }
377
- settled = true;
378
- process.off("SIGINT", onSignal);
379
- process.off("SIGTERM", onSignal);
380
- await closeSession();
381
- reject(error);
382
- });
383
- socket.on("close", () => {
384
- void finish();
474
+ async function approveJoinRequest(socket, message, output) {
475
+ const approved = await askForApproval(message, output);
476
+ sendMessage(socket, {
477
+ type: "join-approval",
478
+ joinRequestId: message.joinRequestId,
479
+ approved
480
+ });
481
+ output[approved ? "success" : "warning"](
482
+ `${approved ? "approved" : "denied"} ${message.displayName}`
483
+ );
484
+ }
485
+ async function askForApproval(message, output) {
486
+ output.info(`Join request: ${message.displayName} (${message.joinRequestId})`);
487
+ if (!process2.stdin.isTTY) {
488
+ output.warning("No interactive terminal available. Denying join request.");
489
+ return false;
490
+ }
491
+ const interfaceReader = readline.createInterface({
492
+ input: process2.stdin,
493
+ output: process2.stdout
494
+ });
495
+ try {
496
+ const answer = await interfaceReader.question("Approve join request? [y/N] ");
497
+ return answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes";
498
+ } finally {
499
+ interfaceReader.close();
500
+ }
501
+ }
502
+
503
+ // src/features/coderoom/sync/client.ts
504
+ async function startSyncSession(options) {
505
+ const repoContext = await resolveRepoContext(options.cwd);
506
+ const suppressionTracker = createSuppressionTracker();
507
+ const webSocketUrl = createSyncWebSocketUrl(options);
508
+ const socket = new WebSocket2(webSocketUrl);
509
+ const state = {
510
+ approved: options.role === "host",
511
+ participantId: void 0
512
+ };
513
+ options.output.success(`Room: ${options.roomCode}`);
514
+ options.output.info(`Repo: ${repoContext.rootDir}`);
515
+ const watcherContext = {
516
+ repoContext,
517
+ socket,
518
+ state,
519
+ suppressionTracker,
520
+ output: options.output
521
+ };
522
+ const watcher = createRepoWatcher(watcherContext);
523
+ socket.on("open", () => {
524
+ if (options.role === "host") {
525
+ void sendInitialSnapshot(socket, repoContext, options.output);
526
+ return;
527
+ }
528
+ options.output.info("Waiting for host approval...");
529
+ });
530
+ socket.on("message", (data) => {
531
+ void handleServerMessage({
532
+ data,
533
+ socket,
534
+ repoContext,
535
+ state,
536
+ suppressionTracker,
537
+ output: options.output
385
538
  });
386
- process.on("SIGINT", onSignal);
387
- process.on("SIGTERM", onSignal);
388
539
  });
540
+ await waitForSessionClose(socket, watcher);
389
541
  }
390
542
 
391
543
  // src/features/coderoom/run-coderoom-command.ts
392
- async function runShareCommand(roomCode, options, output) {
393
- const syncRoomCode = roomCode ?? createRoomCode();
394
- output.success(`Share code: ${syncRoomCode}`);
544
+ async function runShareCommand(options, output) {
545
+ const room = await createCoderoomRoom(options);
546
+ output.success(`Share code: ${room.roomCode}`);
395
547
  await printBackendStatus(options, output);
396
- await runSyncSession("share", syncRoomCode, options, output);
548
+ await runSyncSession("host", room.roomCode, room.creatorToken, options, output);
397
549
  }
398
550
  async function runJoinCommand(roomCode, options, output) {
399
551
  if (!roomCode) {
400
552
  throw new Error("Missing room code. Usage: cngkit coderoom join <room-code>");
401
553
  }
402
554
  await printBackendStatus(options, output);
403
- await runSyncSession("join", roomCode, options, output);
555
+ await runSyncSession("guest", roomCode, void 0, options, output);
404
556
  }
405
557
  async function printBackendStatus(options, output) {
406
558
  const health = await readBackendHealth(options);
@@ -410,18 +562,23 @@ async function printBackendStatus(options, output) {
410
562
  }
411
563
  output.warning(`API: unavailable (${health.message})`);
412
564
  }
413
- async function runSyncSession(role, roomCode, options, output) {
565
+ async function runSyncSession(role, roomCode, creatorToken, options, output) {
414
566
  await startSyncSession({
415
567
  apiBaseUrl: resolveApiBaseUrl(options),
416
568
  roomCode,
569
+ creatorToken,
417
570
  role,
418
- cwd: process2.cwd(),
571
+ cwd: process3.cwd(),
419
572
  output
420
573
  });
421
574
  }
575
+ async function createCoderoomRoom(options) {
576
+ const client = createCngApiClient(options);
577
+ return client.coderoom.createCoderoomRoom();
578
+ }
422
579
 
423
580
  export {
424
581
  runShareCommand,
425
582
  runJoinCommand
426
583
  };
427
- //# sourceMappingURL=chunk-CVF2ODLP.js.map
584
+ //# sourceMappingURL=chunk-BXS4IKUA.js.map