@dench.com/cli 0.4.9 → 2.0.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/tools.ts CHANGED
@@ -3,13 +3,9 @@
3
3
  * Composio's get-apps / get-tools / execute-tool surface.
4
4
  *
5
5
  * Mirrors `denchclaw`'s pattern: this CLI is a thin wrapper over five
6
- * gateway endpoints, authenticated with the unified `DENCH_API_KEY`
7
- * baked into every Dench sandbox at create time. There is no Convex
8
- * session involved; that means these subcommands work in any
9
- * environment that has the API key (sandboxes, CI, local dev with the
10
- * key exported manually). The previous implementation forced
11
- * `requireSessionRuntime` and routed through a Convex action, which
12
- * broke `apiKey` runtime mode entirely.
6
+ * gateway endpoints, authenticated with either `DENCH_API_KEY` or a
7
+ * paid-workspace gateway key resolved from the active agent session by
8
+ * the top-level dispatcher.
13
9
  *
14
10
  * Subcommands:
15
11
  *
@@ -20,7 +16,7 @@
20
16
  * dench tool connect <toolkit> [--callback-url <url>] [--json] — print OAuth redirect URL
21
17
  * dench tool disconnect <connectionId> [--json]
22
18
  *
23
- * Endpoints used (all `Authorization: Bearer DENCH_API_KEY`):
19
+ * Endpoints used (all `Authorization: Bearer <gateway credential>`):
24
20
  *
25
21
  * GET /v1/composio/connections
26
22
  * GET /v1/composio/toolkits?search=<slug>
@@ -36,10 +32,7 @@
36
32
  * working.
37
33
  */
38
34
 
39
- import {
40
- PRODUCTION_API_URL,
41
- PRODUCTION_GATEWAY_URL,
42
- } from "./host";
35
+ import { PRODUCTION_API_URL, PRODUCTION_GATEWAY_URL } from "./host";
43
36
  import {
44
37
  CliArgError,
45
38
  getFlag,
@@ -55,8 +48,15 @@ import {
55
48
  export type ToolCliContext = {
56
49
  args: string[];
57
50
  jsonOutput: boolean;
51
+ bearerToken?: string;
52
+ gatewayBaseUrl?: string;
58
53
  };
59
54
 
55
+ type GatewayAuthContext = Pick<
56
+ ToolCliContext,
57
+ "bearerToken" | "gatewayBaseUrl"
58
+ >;
59
+
60
60
  export async function runToolCommand(ctx: ToolCliContext): Promise<void> {
61
61
  const sub = ctx.args[0];
62
62
  if (!sub || sub === "help" || sub === "--help" || sub === "-h") {
@@ -91,11 +91,14 @@ export async function runToolCommand(ctx: ToolCliContext): Promise<void> {
91
91
 
92
92
  /**
93
93
  * Summarise the user's connected apps for `dench context` and friends.
94
- * Bypasses any Convex session and speaks straight to the gateway.
94
+ * Speaks straight to the gateway with either DENCH_API_KEY or a gateway
95
+ * key resolved from the active agent session.
95
96
  * Returns the same legacy shape the previous Convex-routed path did so
96
97
  * existing parsers don't need to change.
97
98
  */
98
- export async function fetchConnectedAppsSummary(): Promise<{
99
+ export async function fetchConnectedAppsSummary(
100
+ ctx: GatewayAuthContext = {},
101
+ ): Promise<{
99
102
  available: boolean;
100
103
  count: number;
101
104
  connections: Array<{
@@ -109,7 +112,7 @@ export async function fetchConnectedAppsSummary(): Promise<{
109
112
  }> {
110
113
  let payload: unknown;
111
114
  try {
112
- payload = await callGateway("GET", "/v1/composio/connections");
115
+ payload = await callGateway(ctx, "GET", "/v1/composio/connections");
113
116
  } catch (error) {
114
117
  if (error instanceof MissingApiKeyError) {
115
118
  return {
@@ -139,6 +142,7 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
139
142
  if (filterToolkit) ctx.args.shift();
140
143
 
141
144
  const connectionsPayload = await callGateway(
145
+ ctx,
142
146
  "GET",
143
147
  "/v1/composio/connections",
144
148
  );
@@ -146,6 +150,7 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
146
150
  if (filterToolkit) {
147
151
  const search = encodeURIComponent(filterToolkit);
148
152
  toolkitsPayload = await callGateway(
153
+ ctx,
149
154
  "GET",
150
155
  `/v1/composio/toolkits?search=${search}`,
151
156
  );
@@ -194,7 +199,12 @@ async function runSearchSubcommand(ctx: ToolCliContext): Promise<void> {
194
199
  if (toolkit) body.toolkit_slug = toolkit;
195
200
  if (limit) body.limit = limit;
196
201
 
197
- const payload = await callGateway("POST", "/v1/composio/tools/search", body);
202
+ const payload = await callGateway(
203
+ ctx,
204
+ "POST",
205
+ "/v1/composio/tools/search",
206
+ body,
207
+ );
198
208
  if (ctx.jsonOutput) {
199
209
  console.log(JSON.stringify(payload, null, 2));
200
210
  return;
@@ -231,7 +241,12 @@ async function runRunSubcommand(ctx: ToolCliContext): Promise<void> {
231
241
 
232
242
  let payload: unknown;
233
243
  try {
234
- payload = await callGateway("POST", "/v1/composio/tools/execute", body);
244
+ payload = await callGateway(
245
+ ctx,
246
+ "POST",
247
+ "/v1/composio/tools/execute",
248
+ body,
249
+ );
235
250
  } catch (error) {
236
251
  if (error instanceof GatewayHttpError && isLikelyNoConnection(error)) {
237
252
  const noConnPayload = noConnectionPayload(toolSlug, error);
@@ -266,7 +281,7 @@ async function runConnectSubcommand(ctx: ToolCliContext): Promise<void> {
266
281
  process.env.DENCH_OAUTH_CALLBACK_URL?.trim() ??
267
282
  `${resolveAppPublicOrigin()}/api/composio/callback`;
268
283
 
269
- const payload = await callGateway("POST", "/v1/composio/connect", {
284
+ const payload = await callGateway(ctx, "POST", "/v1/composio/connect", {
270
285
  toolkit,
271
286
  callback_url: callbackUrl,
272
287
  });
@@ -313,7 +328,7 @@ async function runDisconnectSubcommand(ctx: ToolCliContext): Promise<void> {
313
328
  const path = `/v1/composio/connections/${encodeURIComponent(connectionId)}`;
314
329
  let payload: unknown = { ok: true, deleted: true };
315
330
  try {
316
- payload = await callGateway("DELETE", path);
331
+ payload = await callGateway(ctx, "DELETE", path);
317
332
  } catch (error) {
318
333
  // Composio returns 404 for connections that are already gone — surface
319
334
  // that as a soft success so callers can keep their local state in sync.
@@ -350,9 +365,8 @@ class ToolCliError extends Error {
350
365
  class MissingApiKeyError extends ToolCliError {
351
366
  constructor() {
352
367
  super(
353
- "DENCH_API_KEY is not set. Inside a Dench sandbox it is baked into " +
354
- "the env automatically; outside, export it manually or run " +
355
- "`dench login` and copy the api-key from the output.",
368
+ "No Dench gateway credential found. Set DENCH_API_KEY or run " +
369
+ "`dench signin` for a paid workspace.",
356
370
  );
357
371
  this.name = "MissingApiKeyError";
358
372
  }
@@ -376,10 +390,19 @@ class GatewayHttpError extends ToolCliError {
376
390
  }
377
391
  }
378
392
 
379
- function resolveGatewayBaseUrl(): string {
393
+ function normalizeGatewayBaseUrl(value: string): string {
394
+ return value.replace(/\/+$/, "").replace(/\/v1$/, "");
395
+ }
396
+
397
+ function resolveGatewayBaseUrl(
398
+ ctx?: Pick<ToolCliContext, "gatewayBaseUrl">,
399
+ ): string {
400
+ if (ctx?.gatewayBaseUrl?.trim()) {
401
+ return normalizeGatewayBaseUrl(ctx.gatewayBaseUrl.trim());
402
+ }
380
403
  const explicit =
381
404
  process.env.DENCH_GATEWAY_URL?.trim() || process.env.GATEWAY_URL?.trim();
382
- if (explicit) return explicit.replace(/\/+$/, "");
405
+ if (explicit) return normalizeGatewayBaseUrl(explicit);
383
406
  const host = process.env.DENCH_HOST?.trim();
384
407
  if (host && /localhost|127\.0\.0\.1/.test(host)) {
385
408
  return "http://localhost:8787";
@@ -410,24 +433,26 @@ function resolveAppPublicOrigin(): string {
410
433
  return PRODUCTION_API_URL;
411
434
  }
412
435
 
413
- function requireApiKey(): string {
414
- const apiKey = process.env.DENCH_API_KEY?.trim();
415
- if (!apiKey) throw new MissingApiKeyError();
416
- return apiKey;
436
+ function requireBearerToken(ctx?: Pick<ToolCliContext, "bearerToken">): string {
437
+ const bearerToken =
438
+ ctx?.bearerToken?.trim() || process.env.DENCH_API_KEY?.trim();
439
+ if (!bearerToken) throw new MissingApiKeyError();
440
+ return bearerToken;
417
441
  }
418
442
 
419
443
  async function callGateway(
444
+ ctx: GatewayAuthContext,
420
445
  method: "GET" | "POST" | "DELETE",
421
446
  path: string,
422
447
  body?: Record<string, unknown>,
423
448
  ): Promise<unknown> {
424
- const apiKey = requireApiKey();
425
- const baseUrl = resolveGatewayBaseUrl();
449
+ const bearerToken = requireBearerToken(ctx);
450
+ const baseUrl = resolveGatewayBaseUrl(ctx);
426
451
  const init: RequestInit = {
427
452
  method,
428
453
  headers: {
429
454
  "content-type": "application/json",
430
- authorization: `Bearer ${apiKey}`,
455
+ authorization: `Bearer ${bearerToken}`,
431
456
  },
432
457
  };
433
458
  if (body !== undefined) init.body = JSON.stringify(body);
@@ -730,8 +755,8 @@ Usage:
730
755
  dench tool disconnect <connectionId> [--json]
731
756
 
732
757
  Notes:
733
- All commands authenticate with DENCH_API_KEY (Bearer) directly to the
734
- Dench Cloud Gateway. No \`dench login\` agent session required.
758
+ All commands authenticate with DENCH_API_KEY or the active \`dench signin\`
759
+ agent session. Agent sessions require a paid workspace.
735
760
  dench tool connect prints the OAuth redirect URL for the human to open.
736
761
  dench tool run output is redacted for display; pass --json for raw output.
737
762
  Override the gateway base with DENCH_GATEWAY_URL or GATEWAY_URL.
@@ -804,7 +829,7 @@ function parsePositiveInt(name: string, raw: string): number {
804
829
  // them as unused when callers tree-shake.
805
830
  export { ToolCliError, GatewayHttpError, MissingApiKeyError };
806
831
  // Kept reachable so a future caller can build their own dispatcher.
807
- export { resolveGatewayBaseUrl, requireApiKey, callGateway };
832
+ export { resolveGatewayBaseUrl, requireBearerToken, callGateway };
808
833
  // shiftRaw / hasFlag from cli-args are not used in this module yet but
809
834
  // keep the import group consistent with cli/search.ts conventions.
810
835
  void shiftRaw;