@dench.com/cli 0.2.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.
package/README.md CHANGED
@@ -7,13 +7,13 @@ Agent-facing CLI for the Dench agent workspace.
7
7
  Run without installing:
8
8
 
9
9
  ```bash
10
- npx -y dench-cli login --name "AI Agent - Billing Repo"
10
+ npx -y @dench.com/cli login --name "AI Agent - Billing Repo"
11
11
  ```
12
12
 
13
13
  Or install globally:
14
14
 
15
15
  ```bash
16
- npm install -g dench-cli
16
+ npm install -g @dench.com/cli
17
17
  dench login --name "AI Agent - Billing Repo"
18
18
  ```
19
19
 
package/dench.ts CHANGED
@@ -1199,7 +1199,7 @@ async function login() {
1199
1199
  sessionTokenHash,
1200
1200
  agentName,
1201
1201
  agentKind,
1202
- userAgent: "dench-cli",
1202
+ userAgent: "@dench.com/cli",
1203
1203
  },
1204
1204
  );
1205
1205
  const approvalUrl = `${host}/agent-login/${encodeURIComponent(code)}`;
package/fs-daemon.ts CHANGED
@@ -27,19 +27,21 @@
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";
36
- import { mkdir, readFile, stat, unlink, writeFile } from "node:fs/promises";
38
+ import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
37
39
  import { dirname, relative } from "node:path";
38
40
  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
  };
@@ -67,11 +69,10 @@ function parseArgs(argv: string[]): Args {
67
69
  break;
68
70
  }
69
71
  }
70
- if (!out.orgId || !out.workspace || !out.hotCache) {
71
- throw new Error(
72
- "dench-fs-daemon: --org-id, --workspace, and --hot-cache are required",
73
- );
74
- }
72
+ out.orgId ??= process.env.DENCH_ORG_ID?.trim() || undefined;
73
+ out.workspace ??= process.env.DENCH_VOLUME_PATH?.trim() || "/workspace";
74
+ out.hotCache ??=
75
+ process.env.DENCH_HOT_CACHE_PATH?.trim() || "/tmp/dench-cache";
75
76
  return out as Args;
76
77
  }
77
78
 
@@ -144,7 +145,9 @@ class HashTracker {
144
145
  setTimeout(() => {
145
146
  this.paused.delete(absPath);
146
147
  this.recentSyncs.delete(absPath);
147
- console.warn(`[dench-fs-daemon] circuit breaker released for ${absPath}`);
148
+ console.warn(
149
+ `[dench-fs-daemon] circuit breaker released for ${absPath}`,
150
+ );
148
151
  }, 60_000);
149
152
  return true;
150
153
  }
@@ -177,10 +180,11 @@ const api = {
177
180
  commitFileUpload: makeFunctionReference<"mutation">(
178
181
  "functions/files:commitFileUpload",
179
182
  ),
180
- deleteFile: makeFunctionReference<"mutation">(
181
- "functions/files:deleteFile",
182
- ),
183
+ deleteFile: makeFunctionReference<"mutation">("functions/files:deleteFile"),
183
184
  listTree: makeFunctionReference<"query">("functions/files:listTree"),
185
+ resolveApiKeyOrganization: makeFunctionReference<"query">(
186
+ "functions/files:resolveApiKeyOrganization",
187
+ ),
184
188
  appendLiveBuffer: makeFunctionReference<"mutation">(
185
189
  "functions/files:appendLiveBuffer",
186
190
  ),
@@ -197,17 +201,24 @@ class DenchFileClient {
197
201
  private http: ConvexHttpClient;
198
202
  private realtime: ConvexClient | null = null;
199
203
 
200
- constructor(convexUrl: string, apiKey: string) {
204
+ constructor(
205
+ convexUrl: string,
206
+ private readonly apiKey: string,
207
+ ) {
201
208
  this.http = new ConvexHttpClient(convexUrl);
202
- // Convex's HTTP client supports a Bearer token for auth via setAuth.
203
- // The unified Dench API key is accepted by the validator we wired in
204
- // convex/lib/denchApiKey.ts.
205
- 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;
206
216
  }
207
217
 
208
218
  async generateUploadUrl(path: string): Promise<string> {
209
219
  return (await this.http.mutation(api.files.generateUploadUrl, {
210
220
  path,
221
+ apiKey: this.apiKey,
211
222
  })) as string;
212
223
  }
213
224
 
@@ -238,7 +249,10 @@ class DenchFileClient {
238
249
  lastModifiedBy: string;
239
250
  }): Promise<{ versionConflict: boolean; currentHash?: string }> {
240
251
  try {
241
- await this.http.mutation(api.files.commitFileUpload, args);
252
+ await this.http.mutation(api.files.commitFileUpload, {
253
+ ...args,
254
+ apiKey: this.apiKey,
255
+ });
242
256
  return { versionConflict: false };
243
257
  } catch (error) {
244
258
  const message = error instanceof Error ? error.message : String(error);
@@ -248,7 +262,7 @@ class DenchFileClient {
248
262
  ) {
249
263
  // Convex serializes ConvexError data into the message; pull
250
264
  // currentHash if present.
251
- const m = /currentHash[\"']?:[\"']?([a-f0-9]{64})/.exec(message);
265
+ const m = /currentHash["']?:["']?([a-f0-9]{64})/.exec(message);
252
266
  return { versionConflict: true, currentHash: m?.[1] };
253
267
  }
254
268
  throw error;
@@ -256,14 +270,17 @@ class DenchFileClient {
256
270
  }
257
271
 
258
272
  async deleteFile(path: string): Promise<void> {
259
- await this.http.mutation(api.files.deleteFile, { path });
273
+ await this.http.mutation(api.files.deleteFile, {
274
+ path,
275
+ apiKey: this.apiKey,
276
+ });
260
277
  }
261
278
 
262
279
  async getSignedDownloadUrl(path: string): Promise<string | null> {
263
- const url = (await this.http.action(
264
- api.files.getFileSignedDownloadUrl,
265
- { path },
266
- )) as string | null;
280
+ const url = (await this.http.action(api.files.getFileSignedDownloadUrl, {
281
+ path,
282
+ apiKey: this.apiKey,
283
+ })) as string | null;
267
284
  return url ?? null;
268
285
  }
269
286
 
@@ -272,11 +289,17 @@ class DenchFileClient {
272
289
  runId: string;
273
290
  chunk: string;
274
291
  }): Promise<void> {
275
- 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);
276
296
  }
277
297
 
278
298
  async finalizeLiveBuffer(path: string): Promise<void> {
279
- await this.http.mutation(api.files.finalizeLiveBuffer, { path });
299
+ await this.http.mutation(api.files.finalizeLiveBuffer, {
300
+ path,
301
+ apiKey: this.apiKey,
302
+ });
280
303
  }
281
304
 
282
305
  /**
@@ -295,7 +318,7 @@ class DenchFileClient {
295
318
  }
296
319
  const unsubscribe = this.realtime.onUpdate(
297
320
  api.files.listTree,
298
- { prefix: "/" },
321
+ { prefix: "/", apiKey: this.apiKey },
299
322
  (rows: unknown) => {
300
323
  const list = Array.isArray(rows)
301
324
  ? (rows as Array<{
@@ -347,14 +370,16 @@ async function runDaemon(daemonArgs: Args): Promise<void> {
347
370
  return;
348
371
  }
349
372
 
373
+ const client = new DenchFileClient(convexUrl, apiKey);
374
+ const resolvedOrgId =
375
+ daemonArgs.orgId ?? (await client.resolveOrganizationId());
350
376
  console.log(
351
- `[dench-fs-daemon] starting org=${daemonArgs.orgId} workspace=${daemonArgs.workspace}`,
377
+ `[dench-fs-daemon] starting org=${resolvedOrgId} workspace=${daemonArgs.workspace}`,
352
378
  );
353
379
  mkdirSync(daemonArgs.workspace, { recursive: true });
354
380
  mkdirSync(daemonArgs.hotCache, { recursive: true });
355
381
 
356
382
  const tracker = new HashTracker();
357
- const client = new DenchFileClient(convexUrl, apiKey);
358
383
 
359
384
  let chokidar: typeof import("chokidar");
360
385
  try {
@@ -518,8 +543,7 @@ async function runStreamOpen(args: string[]): Promise<void> {
518
543
  if (!path) {
519
544
  throw new Error("dench-fs-daemon stream-open requires <path>");
520
545
  }
521
- const runId =
522
- process.env.DENCH_RUN_ID?.trim() ?? `manual-${Date.now()}`;
546
+ const runId = process.env.DENCH_RUN_ID?.trim() ?? `manual-${Date.now()}`;
523
547
  const token = newToken();
524
548
  const store = await readTokenStore();
525
549
  store[token] = { path, runId };
@@ -531,9 +555,7 @@ async function runStreamAppend(args: string[]): Promise<void> {
531
555
  const token = args[0];
532
556
  const chunk = args.slice(1).join(" ");
533
557
  if (!token || chunk === undefined) {
534
- throw new Error(
535
- "dench-fs-daemon stream-append requires <token> <chunk>",
536
- );
558
+ throw new Error("dench-fs-daemon stream-append requires <token> <chunk>");
537
559
  }
538
560
  const store = await readTokenStore();
539
561
  const entry = store[token];
@@ -606,21 +628,31 @@ 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();
617
647
  const apiKey = process.env.DENCH_API_KEY?.trim();
618
648
  if (!convexUrl || !apiKey) {
619
- throw new Error(
620
- "dench fs sync requires CONVEX_URL + DENCH_API_KEY in env",
621
- );
649
+ throw new Error("dench fs sync requires CONVEX_URL + DENCH_API_KEY in env");
622
650
  }
623
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
+ );
624
656
  const fs = await import("node:fs/promises");
625
657
  const path = await import("node:path");
626
658
 
@@ -670,7 +702,7 @@ async function runInitialSync(argv: string[]): Promise<void> {
670
702
  main().catch((error) => {
671
703
  console.error(
672
704
  `[dench-fs-daemon] fatal: ${
673
- error instanceof Error ? error.stack ?? error.message : String(error)
705
+ error instanceof Error ? (error.stack ?? error.message) : String(error)
674
706
  }`,
675
707
  );
676
708
  // Long-running daemon: don't exit on transient errors. One-shot
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {