@dench.com/cli 0.3.0 → 0.3.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 (2) hide show
  1. package/fs-daemon.ts +57 -23
  2. package/package.json +1 -1
package/fs-daemon.ts CHANGED
@@ -27,9 +27,11 @@
27
27
  *
28
28
  * Usage (daemon mode):
29
29
  * dench-fs-daemon \
30
- * --org-id <orgId> \
31
30
  * --workspace /workspace \
32
31
  * --hot-cache /tmp/dench-cache
32
+ *
33
+ * `--org-id` is optional: when omitted, the daemon resolves the organization
34
+ * from DENCH_API_KEY via Convex before it starts watching files.
33
35
  */
34
36
  import { createHash } from "node:crypto";
35
37
  import { mkdirSync } from "node:fs";
@@ -39,7 +41,7 @@ import { ConvexClient, ConvexHttpClient } from "convex/browser";
39
41
  import { makeFunctionReference } from "convex/server";
40
42
 
41
43
  type Args = {
42
- orgId: string;
44
+ orgId?: string;
43
45
  workspace: string;
44
46
  hotCache: string;
45
47
  };
@@ -71,11 +73,6 @@ function parseArgs(argv: string[]): Args {
71
73
  out.workspace ??= process.env.DENCH_VOLUME_PATH?.trim() || "/workspace";
72
74
  out.hotCache ??=
73
75
  process.env.DENCH_HOT_CACHE_PATH?.trim() || "/tmp/dench-cache";
74
- if (!out.orgId) {
75
- throw new Error(
76
- "dench-fs-daemon: org id is required. Set DENCH_ORG_ID env var or pass --org-id.",
77
- );
78
- }
79
76
  return out as Args;
80
77
  }
81
78
 
@@ -185,6 +182,9 @@ const api = {
185
182
  ),
186
183
  deleteFile: makeFunctionReference<"mutation">("functions/files:deleteFile"),
187
184
  listTree: makeFunctionReference<"query">("functions/files:listTree"),
185
+ resolveApiKeyOrganization: makeFunctionReference<"query">(
186
+ "functions/files:resolveApiKeyOrganization",
187
+ ),
188
188
  appendLiveBuffer: makeFunctionReference<"mutation">(
189
189
  "functions/files:appendLiveBuffer",
190
190
  ),
@@ -201,17 +201,24 @@ class DenchFileClient {
201
201
  private http: ConvexHttpClient;
202
202
  private realtime: ConvexClient | null = null;
203
203
 
204
- constructor(convexUrl: string, apiKey: string) {
204
+ constructor(
205
+ convexUrl: string,
206
+ private readonly apiKey: string,
207
+ ) {
205
208
  this.http = new ConvexHttpClient(convexUrl);
206
- // Convex's HTTP client supports a Bearer token for auth via setAuth.
207
- // The unified Dench API key is accepted by the validator we wired in
208
- // convex/lib/denchApiKey.ts.
209
- this.http.setAuth(apiKey);
209
+ }
210
+
211
+ async resolveOrganizationId(): Promise<string> {
212
+ const result = (await this.http.query(api.files.resolveApiKeyOrganization, {
213
+ apiKey: this.apiKey,
214
+ })) as { organizationId: string };
215
+ return result.organizationId;
210
216
  }
211
217
 
212
218
  async generateUploadUrl(path: string): Promise<string> {
213
219
  return (await this.http.mutation(api.files.generateUploadUrl, {
214
220
  path,
221
+ apiKey: this.apiKey,
215
222
  })) as string;
216
223
  }
217
224
 
@@ -242,7 +249,10 @@ class DenchFileClient {
242
249
  lastModifiedBy: string;
243
250
  }): Promise<{ versionConflict: boolean; currentHash?: string }> {
244
251
  try {
245
- await this.http.mutation(api.files.commitFileUpload, args);
252
+ await this.http.mutation(api.files.commitFileUpload, {
253
+ ...args,
254
+ apiKey: this.apiKey,
255
+ });
246
256
  return { versionConflict: false };
247
257
  } catch (error) {
248
258
  const message = error instanceof Error ? error.message : String(error);
@@ -260,12 +270,16 @@ class DenchFileClient {
260
270
  }
261
271
 
262
272
  async deleteFile(path: string): Promise<void> {
263
- await this.http.mutation(api.files.deleteFile, { path });
273
+ await this.http.mutation(api.files.deleteFile, {
274
+ path,
275
+ apiKey: this.apiKey,
276
+ });
264
277
  }
265
278
 
266
279
  async getSignedDownloadUrl(path: string): Promise<string | null> {
267
280
  const url = (await this.http.action(api.files.getFileSignedDownloadUrl, {
268
281
  path,
282
+ apiKey: this.apiKey,
269
283
  })) as string | null;
270
284
  return url ?? null;
271
285
  }
@@ -275,11 +289,17 @@ class DenchFileClient {
275
289
  runId: string;
276
290
  chunk: string;
277
291
  }): Promise<void> {
278
- await this.http.mutation(api.files.appendLiveBuffer, args as never);
292
+ await this.http.mutation(api.files.appendLiveBuffer, {
293
+ ...args,
294
+ apiKey: this.apiKey,
295
+ } as never);
279
296
  }
280
297
 
281
298
  async finalizeLiveBuffer(path: string): Promise<void> {
282
- await this.http.mutation(api.files.finalizeLiveBuffer, { path });
299
+ await this.http.mutation(api.files.finalizeLiveBuffer, {
300
+ path,
301
+ apiKey: this.apiKey,
302
+ });
283
303
  }
284
304
 
285
305
  /**
@@ -298,7 +318,7 @@ class DenchFileClient {
298
318
  }
299
319
  const unsubscribe = this.realtime.onUpdate(
300
320
  api.files.listTree,
301
- { prefix: "/" },
321
+ { prefix: "/", apiKey: this.apiKey },
302
322
  (rows: unknown) => {
303
323
  const list = Array.isArray(rows)
304
324
  ? (rows as Array<{
@@ -350,14 +370,16 @@ async function runDaemon(daemonArgs: Args): Promise<void> {
350
370
  return;
351
371
  }
352
372
 
373
+ const client = new DenchFileClient(convexUrl, apiKey);
374
+ const resolvedOrgId =
375
+ daemonArgs.orgId ?? (await client.resolveOrganizationId());
353
376
  console.log(
354
- `[dench-fs-daemon] starting org=${daemonArgs.orgId} workspace=${daemonArgs.workspace}`,
377
+ `[dench-fs-daemon] starting org=${resolvedOrgId} workspace=${daemonArgs.workspace}`,
355
378
  );
356
379
  mkdirSync(daemonArgs.workspace, { recursive: true });
357
380
  mkdirSync(daemonArgs.hotCache, { recursive: true });
358
381
 
359
382
  const tracker = new HashTracker();
360
- const client = new DenchFileClient(convexUrl, apiKey);
361
383
 
362
384
  let chokidar: typeof import("chokidar");
363
385
  try {
@@ -606,11 +628,19 @@ async function main(): Promise<void> {
606
628
  */
607
629
  async function runInitialSync(argv: string[]): Promise<void> {
608
630
  const initial = argv.includes("--initial");
609
- const orgId = argv[argv.indexOf("--org-id") + 1];
610
- const workspace = argv[argv.indexOf("--workspace") + 1] ?? "/workspace";
611
- if (!initial || !orgId) {
631
+ const orgIdIndex = argv.indexOf("--org-id");
632
+ const workspaceIndex = argv.indexOf("--workspace");
633
+ const orgId =
634
+ orgIdIndex === -1
635
+ ? (process.env.DENCH_ORG_ID?.trim() ?? undefined)
636
+ : argv[orgIdIndex + 1];
637
+ const workspace =
638
+ workspaceIndex === -1
639
+ ? "/workspace"
640
+ : (argv[workspaceIndex + 1] ?? "/workspace");
641
+ if (!initial) {
612
642
  throw new Error(
613
- "dench fs sync --initial --org-id <orgId> [--workspace /workspace]",
643
+ "dench fs sync --initial [--org-id <orgId>] [--workspace /workspace]",
614
644
  );
615
645
  }
616
646
  const convexUrl = process.env.CONVEX_URL?.trim();
@@ -619,6 +649,10 @@ async function runInitialSync(argv: string[]): Promise<void> {
619
649
  throw new Error("dench fs sync requires CONVEX_URL + DENCH_API_KEY in env");
620
650
  }
621
651
  const client = new DenchFileClient(convexUrl, apiKey);
652
+ const resolvedOrgId = orgId ?? (await client.resolveOrganizationId());
653
+ process.stdout.write(
654
+ `[dench-fs-daemon] initial sync org=${resolvedOrgId} workspace=${workspace}\n`,
655
+ );
622
656
  const fs = await import("node:fs/promises");
623
657
  const path = await import("node:path");
624
658
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {