@dench.com/cli 2.0.2 → 2.0.4

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/agent-config.ts CHANGED
@@ -34,11 +34,51 @@ import { mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
34
34
  import { tmpdir } from "node:os";
35
35
  import { join } from "node:path";
36
36
  import type { ConvexHttpClient } from "convex/browser";
37
- import { api } from "@/../convex/_generated/api";
37
+ import { makeFunctionReference } from "convex/server";
38
38
  import { CliArgError, getFlag, hasFlag } from "./lib/cli-args";
39
39
 
40
40
  class AgentConfigCliError extends Error {}
41
41
 
42
+ const api = {
43
+ functions: {
44
+ agentConfig: {
45
+ getAgentConfig: makeFunctionReference<"query">(
46
+ "functions/agentConfig:getAgentConfig",
47
+ ),
48
+ updateIdentity: makeFunctionReference<"mutation">(
49
+ "functions/agentConfig:updateIdentity",
50
+ ),
51
+ updateOrganisation: makeFunctionReference<"mutation">(
52
+ "functions/agentConfig:updateOrganisation",
53
+ ),
54
+ updateMemory: makeFunctionReference<"mutation">(
55
+ "functions/agentConfig:updateMemory",
56
+ ),
57
+ updateToolsNotes: makeFunctionReference<"mutation">(
58
+ "functions/agentConfig:updateToolsNotes",
59
+ ),
60
+ updateUserProfile: makeFunctionReference<"mutation">(
61
+ "functions/agentConfig:updateUserProfile",
62
+ ),
63
+ updateHeartbeat: makeFunctionReference<"mutation">(
64
+ "functions/agentConfig:updateHeartbeat",
65
+ ),
66
+ updateBootstrapTemplate: makeFunctionReference<"mutation">(
67
+ "functions/agentConfig:updateBootstrapTemplate",
68
+ ),
69
+ completeBootstrap: makeFunctionReference<"mutation">(
70
+ "functions/agentConfig:completeBootstrap",
71
+ ),
72
+ setDefaultChatModelV2: makeFunctionReference<"mutation">(
73
+ "functions/agentConfig:setDefaultChatModelV2",
74
+ ),
75
+ setThreadModelOverride: makeFunctionReference<"mutation">(
76
+ "functions/agentConfig:setThreadModelOverride",
77
+ ),
78
+ },
79
+ },
80
+ };
81
+
42
82
  type CliCtx = {
43
83
  convex: ConvexHttpClient;
44
84
  args: string[];
@@ -86,7 +126,9 @@ async function readContentFromStdin(): Promise<string> {
86
126
  return new Promise((resolve, reject) => {
87
127
  const chunks: Buffer[] = [];
88
128
  process.stdin.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
89
- process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
129
+ process.stdin.on("end", () =>
130
+ resolve(Buffer.concat(chunks).toString("utf8")),
131
+ );
90
132
  process.stdin.on("error", reject);
91
133
  });
92
134
  }
@@ -150,7 +192,10 @@ async function loadAgentConfig(ctx: CliCtx): Promise<{
150
192
  userProfile: { content: string; updatedAt: number } | null;
151
193
  organizationId: string;
152
194
  }> {
153
- const result = (await runQuery(ctx, api.functions.agentConfig.getAgentConfig)) as {
195
+ const result = (await runQuery(
196
+ ctx,
197
+ api.functions.agentConfig.getAgentConfig,
198
+ )) as {
154
199
  agentConfig: {
155
200
  identity: { content: string; updatedAt: number } | null;
156
201
  organisation: { content: string; updatedAt: number } | null;
@@ -212,14 +257,14 @@ async function handleSimpleMagicFile(
212
257
  } else {
213
258
  const config = await loadAgentConfig(ctx);
214
259
  const initial = opts.perMember
215
- ? config.userProfile?.content ?? ""
260
+ ? (config.userProfile?.content ?? "")
216
261
  : opts.field === "memoryAggregate"
217
- ? config.agentConfig.memoryAggregate?.content ?? ""
262
+ ? (config.agentConfig.memoryAggregate?.content ?? "")
218
263
  : opts.field === "toolsNotes"
219
- ? config.agentConfig.toolsNotes?.content ?? ""
264
+ ? (config.agentConfig.toolsNotes?.content ?? "")
220
265
  : opts.field === "identity"
221
- ? config.agentConfig.identity?.content ?? ""
222
- : config.agentConfig.organisation?.content ?? "";
266
+ ? (config.agentConfig.identity?.content ?? "")
267
+ : (config.agentConfig.organisation?.content ?? "");
223
268
  content = openEditor(initial, opts.label);
224
269
  }
225
270
  if (!content.trim().length) {
@@ -519,8 +564,7 @@ export async function runMemoryAggregateCommand(ctx: CliCtx): Promise<void> {
519
564
  }
520
565
  if (sub === "append") {
521
566
  const text = ctx.args.slice(1).join(" ").trim();
522
- if (!text)
523
- throw new AgentConfigCliError("Usage: dench mem append <text>");
567
+ if (!text) throw new AgentConfigCliError("Usage: dench mem append <text>");
524
568
  await runMutation(ctx, api.functions.agentConfig.updateMemory, {
525
569
  content: text,
526
570
  append: true,
@@ -593,9 +637,7 @@ export async function runDailyCommand(ctx: CliCtx): Promise<void> {
593
637
  if (sub === "show") {
594
638
  const date = ctx.args[1];
595
639
  if (!date) {
596
- throw new AgentConfigCliError(
597
- "Usage: dench daily show <YYYY-MM-DD>",
598
- );
640
+ throw new AgentConfigCliError("Usage: dench daily show <YYYY-MM-DD>");
599
641
  }
600
642
  out(
601
643
  ctx,
package/chat-spawn.ts CHANGED
@@ -16,8 +16,6 @@
16
16
  * (`/api/chat/[runId]/stream`) and pretty-prints text deltas + tool
17
17
  * calls until the workflow finishes.
18
18
  */
19
-
20
- import { getCurrentRuntimeTimeZone } from "@/lib/timezone";
21
19
  import { hasFlag } from "./lib/cli-args";
22
20
 
23
21
  class ChatSpawnError extends Error {}
@@ -33,6 +31,18 @@ type ChatSpawnContext = {
33
31
  jsonOutput: boolean;
34
32
  };
35
33
 
34
+ function getCurrentRuntimeTimeZone(): string | null {
35
+ try {
36
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
37
+ if (!timeZone || timeZone.length > 100) return null;
38
+ // Validate that Intl accepts the resolved value before forwarding it.
39
+ Intl.DateTimeFormat("en-US", { timeZone }).format(new Date());
40
+ return timeZone;
41
+ } catch {
42
+ return null;
43
+ }
44
+ }
45
+
36
46
  function consumeFlagValue(args: string[], name: string): string | undefined {
37
47
  const idx = args.indexOf(name);
38
48
  if (idx === -1) return undefined;
package/dench.ts CHANGED
@@ -16,11 +16,11 @@ import { ConvexHttpClient } from "convex/browser";
16
16
  import { makeFunctionReference } from "convex/server";
17
17
  import { agentKindLabel, normalizeAgentKind } from "./agentKind";
18
18
  import {
19
+ apiHostForDenchHost,
19
20
  DEFAULT_HOST,
20
21
  isLocalHost,
21
22
  LOCAL_HOST,
22
23
  normalizeHost,
23
- PRODUCTION_API_URL,
24
24
  PRODUCTION_CONVEX_URL,
25
25
  PRODUCTION_HOST,
26
26
  resolveBackendAlias,
@@ -440,13 +440,13 @@ Workspace members (for CRM \`user\`-type fields):
440
440
  cells assign to real members instead of being stored as unlinked
441
441
  display-name strings. See \`dench members help\`.
442
442
 
443
- Live web search (Exa via the Dench Cloud Gateway, auths with the active \`dench signin\` agent session or DENCH_API_KEY):
443
+ Live web search (Exa via the Dench Gateway, auths with the active \`dench signin\` agent session or DENCH_API_KEY):
444
444
  dench search "<query>" [--num-results N] [--type auto|fast|deep|deep-reasoning|neural] [--category news|company|people|...] [--include-domains a.com,b.com] [--exclude-domains c.com] [--max-chars 800] [--json]
445
445
  dench search contents <url> [<url>...] [--max-chars 4000] [--summary "..."] [--json]
446
446
  dench search answer "<query>" [--json]
447
447
  Help: dench search help
448
448
 
449
- Image generation / editing (gpt-image-2 via the Dench Cloud Gateway):
449
+ Image generation / editing (via the Dench Gateway):
450
450
  dench image generate "<prompt>" [--size 1024x1024] [--quality auto|low|medium|high] [--format png|jpeg|webp] [--save-path /workspace/path/file.png] [--output <local file>] [--no-write] [--json]
451
451
  dench image edit "<prompt>" --input <path> [--input <path> ...] [--mask <path>] [--size ...] [--quality ...] [--format ...] [--save-path ...] [--output ...] [--json]
452
452
  Help: dench image help
@@ -472,7 +472,7 @@ Spawn a new chat-turn workflow (same as the web UI):
472
472
 
473
473
  Help: dench chat help
474
474
 
475
- File sync (Daytona-native rewrite):
475
+ File sync:
476
476
  dench fs status [--json] [--workspace /workspace] [--no-hash] [--drift-limit N]
477
477
  dench fs sync --initial --org-id <orgId> [--workspace /workspace]
478
478
  dench fs stream-open <path>
@@ -488,7 +488,7 @@ returns ENOSYS on the FUSE-backed volume mount):
488
488
  Persist scratch -> workspace (cp from /tmp into /workspace + Convex sync):
489
489
  dench stage <local-path> [<workspace-dest>] [--clean] [--json]
490
490
 
491
- Subagents (Daytona-native rewrite):
491
+ Subagents:
492
492
  dench agent spawn '<goal>' [--parent-run-id <id>] [--sandbox own|share_parent] [--time-budget-ms N]
493
493
  dench agent await --hook <token> | --children id1,id2,id3
494
494
  dench agent message <toRunId> '<text>' [--from-run-id <id>]
@@ -501,7 +501,7 @@ Subagents (Daytona-native rewrite):
501
501
 
502
502
  dench --version
503
503
 
504
- Self-updating agent harness (OpenClaw parity):
504
+ Self-updating agent harness:
505
505
  dench identity show | edit IDENTITY.md (org-wide)
506
506
  dench organisation show | edit ORGANISATION.md
507
507
  dench user show | edit USER.md (per-member)
@@ -1993,16 +1993,6 @@ async function resolveOrgChoiceForOtp(input: {
1993
1993
  return { newOrganizationName: newName.trim() };
1994
1994
  }
1995
1995
 
1996
- function normalizeApiBase(input: string) {
1997
- const trimmed = input.trim();
1998
- const withProtocol = /^https?:\/\//.test(trimmed)
1999
- ? trimmed
2000
- : /^(localhost|127\.0\.0\.1|\[::1\])(?::|$)/.test(trimmed)
2001
- ? `http://${trimmed}`
2002
- : `https://${trimmed}`;
2003
- return new URL(withProtocol).origin;
2004
- }
2005
-
2006
1996
  async function parseResponseBody(response: Response) {
2007
1997
  const text = await response.text();
2008
1998
  if (!text) return {};
@@ -2048,25 +2038,31 @@ function parseBillingTopupAmountUsd() {
2048
2038
  }
2049
2039
 
2050
2040
  async function billingStatus(runtime: Runtime) {
2051
- const sessionRuntime = requireSessionRuntime(runtime, "dench billing status");
2052
- const status = await sessionRuntime.client.query(
2041
+ const authedRuntime = requireAuthenticatedRuntime(
2042
+ runtime,
2043
+ "dench billing status",
2044
+ );
2045
+ const status = await authedRuntime.client.query(
2053
2046
  api.functions.agentWorkspace.getAgentBillingStatus,
2054
- { sessionToken: sessionRuntime.sessionToken },
2047
+ { sessionToken: authedRuntime.sessionToken },
2055
2048
  );
2056
2049
  print(json ? status : formatBillingStatus(status));
2057
2050
  }
2058
2051
 
2059
2052
  async function billingTopup(runtime: Runtime) {
2060
- const sessionRuntime = requireSessionRuntime(runtime, "dench billing topup");
2053
+ const authedRuntime = requireAuthenticatedRuntime(
2054
+ runtime,
2055
+ "dench billing topup",
2056
+ );
2061
2057
  const amountUsd = parseBillingTopupAmountUsd();
2062
2058
  const response = await fetch(
2063
- `${sessionRuntime.host.replace(/\/+$/, "")}/api/stripe/create-ai-topup`,
2059
+ `${authedRuntime.host.replace(/\/+$/, "")}/api/stripe/create-ai-topup`,
2064
2060
  {
2065
2061
  method: "POST",
2066
2062
  headers: {
2067
2063
  accept: "application/json",
2068
2064
  "content-type": "application/json",
2069
- authorization: `Bearer ${sessionRuntime.sessionToken}`,
2065
+ authorization: `Bearer ${authedRuntime.sessionToken}`,
2070
2066
  },
2071
2067
  body: JSON.stringify({
2072
2068
  amountUsd,
@@ -2133,7 +2129,7 @@ async function billingTopup(runtime: Runtime) {
2133
2129
  * needed in that case). We surface either outcome cleanly.
2134
2130
  */
2135
2131
  async function denchUpgrade(runtime: Runtime) {
2136
- const sessionRuntime = requireSessionRuntime(runtime, "dench upgrade");
2132
+ const authedRuntime = requireAuthenticatedRuntime(runtime, "dench upgrade");
2137
2133
  const tierFlag = option("--tier")?.trim().toLowerCase() || "pro";
2138
2134
  const cycleFlag =
2139
2135
  option("--cycle")?.trim().toLowerCase() ||
@@ -2156,13 +2152,13 @@ async function denchUpgrade(runtime: Runtime) {
2156
2152
  const internalTier = tierFlag === "pro" ? "desktop" : "cloud";
2157
2153
 
2158
2154
  const response = await fetch(
2159
- `${sessionRuntime.host.replace(/\/+$/, "")}/api/stripe/create-checkout`,
2155
+ `${authedRuntime.host.replace(/\/+$/, "")}/api/stripe/create-checkout`,
2160
2156
  {
2161
2157
  method: "POST",
2162
2158
  headers: {
2163
2159
  accept: "application/json",
2164
2160
  "content-type": "application/json",
2165
- authorization: `Bearer ${sessionRuntime.sessionToken}`,
2161
+ authorization: `Bearer ${authedRuntime.sessionToken}`,
2166
2162
  },
2167
2163
  body: JSON.stringify({
2168
2164
  tier: internalTier,
@@ -2256,16 +2252,14 @@ function resolveApiKeyConvexUrl(): string | undefined {
2256
2252
  /**
2257
2253
  * Resolve the Next.js API host the CLI should hit for HTTP calls (e.g.
2258
2254
  * Stripe billing topup, the gateway proxy). Same defense-in-depth
2259
- * pattern as `resolveApiKeyConvexUrl`: env wins, then the resolved
2260
- * Dench host the runtime is targeting, and finally
2261
- * `PRODUCTION_API_URL` as the last resort.
2255
+ * pattern as `resolveApiKeyConvexUrl`: env wins, then the canonical API
2256
+ * origin for the Dench host the runtime is targeting.
2262
2257
  */
2263
2258
  function resolveApiHost(fallbackHost: string): string {
2264
2259
  return (
2265
2260
  process.env.DENCH_API_URL?.trim() ||
2266
2261
  process.env.DENCH_INTERNAL_BASE?.trim() ||
2267
- fallbackHost ||
2268
- PRODUCTION_API_URL
2262
+ apiHostForDenchHost(fallbackHost)
2269
2263
  );
2270
2264
  }
2271
2265
 
@@ -2305,7 +2299,7 @@ async function getRuntime() {
2305
2299
  if (stored.status === "found") {
2306
2300
  return {
2307
2301
  mode: "session" as const,
2308
- host,
2302
+ host: resolveApiHost(host),
2309
2303
  client: new ConvexHttpClient(stored.session.convexUrl),
2310
2304
  sessionToken: stored.session.sessionToken,
2311
2305
  organization: stored.session.organization,
@@ -2406,25 +2400,6 @@ function stripRuntimeFlags(args: string[]) {
2406
2400
  return stripped;
2407
2401
  }
2408
2402
 
2409
- /**
2410
- * `session` mode is required for `agentWorkspace.*` calls because they
2411
- * gate on `validateAgentSession` server-side, which only accepts the
2412
- * `dch_agent_*` token format. Sandboxes that only have a unified
2413
- * `DENCH_API_KEY` (apiKey mode) cannot impersonate a specific agent
2414
- * and will fail at the server with `Invalid agent session` — surface
2415
- * that here as a clear error instead.
2416
- */
2417
- function requireSessionRuntime(
2418
- runtime: Runtime,
2419
- command: string,
2420
- ): Extract<Runtime, { mode: "session" }> {
2421
- if (runtime.mode === "session") return runtime;
2422
- if (runtime.mode === "apiKey") {
2423
- throw agentSessionRequiredError(command);
2424
- }
2425
- throw loginRequiredError(command);
2426
- }
2427
-
2428
2403
  /**
2429
2404
  * Org-scoped commands (`dench crm`, `dench files`, etc.) accept either
2430
2405
  * a `dch_agent_*` agent session token OR a unified Dench API key
@@ -2486,7 +2461,17 @@ function gatewayAuthError(
2486
2461
  });
2487
2462
  }
2488
2463
  if (response.status === 401) {
2489
- return loginRequiredError(command);
2464
+ return new CliError(
2465
+ `${command} could not exchange the active Dench session for a gateway key (${code})`,
2466
+ {
2467
+ code,
2468
+ status: response.status,
2469
+ nextActions: [
2470
+ "Run dench sessions --json to confirm the selected session.",
2471
+ 'Run dench signin --kind <kind> --name "AI Agent - Project" to refresh the agent session if it expired.',
2472
+ ],
2473
+ },
2474
+ );
2490
2475
  }
2491
2476
  return new CliError(`${command} could not get a gateway key (${code})`, {
2492
2477
  code,
@@ -2545,36 +2530,6 @@ function encodeDevCrmSessionToken(args: {
2545
2530
  return `dch_dev_${encodeURIComponent(args.workspaceSlug)}:${encodeURIComponent(args.devKey)}`;
2546
2531
  }
2547
2532
 
2548
- function agentSessionRequiredError(command: string) {
2549
- return new CliError(
2550
- `${command} requires an agent session — DENCH_API_KEY is org-scoped and cannot impersonate a specific agent`,
2551
- {
2552
- code: "agent_session_required",
2553
- nextActions: [
2554
- 'Run dench signin --kind <kind> --name "AI Agent - Project" to mint an agent session for this command.',
2555
- "Or run the equivalent action via the workspace UI / dench crm if it is org-level (e.g. CRM data, files).",
2556
- ],
2557
- },
2558
- );
2559
- }
2560
-
2561
- /**
2562
- * `dench approval request` has separate session vs dev branches. With
2563
- * apiKey mode added, the dev branch would silently receive partial args.
2564
- * Use this to short-circuit that branch with a clear error.
2565
- *
2566
- * Asserts away the `apiKey` mode so the caller's downstream type
2567
- * narrowing (e.g. `runtime.workspaceArgs`) keeps working.
2568
- */
2569
- function rejectApiKeyForAgentCommand(
2570
- runtime: Runtime,
2571
- command: string,
2572
- ): asserts runtime is Exclude<Runtime, { mode: "apiKey" }> {
2573
- if (runtime.mode === "apiKey") {
2574
- throw agentSessionRequiredError(command);
2575
- }
2576
- }
2577
-
2578
2533
  function asRecord(value: unknown): JsonRecord | undefined {
2579
2534
  if (!value || typeof value !== "object" || Array.isArray(value)) {
2580
2535
  return undefined;
@@ -2681,7 +2636,7 @@ async function connectedAppsContext(runtime: Runtime) {
2681
2636
  async function buildContext(runtime: Runtime) {
2682
2637
  let mine: JsonRecord;
2683
2638
  let urls: JsonRecord;
2684
- if (runtime.mode === "session") {
2639
+ if (runtime.mode === "session" || runtime.mode === "apiKey") {
2685
2640
  mine = (await runtime.client.query(
2686
2641
  api.functions.agentWorkspace.whatCanIDoHere,
2687
2642
  { sessionToken: runtime.sessionToken },
@@ -2694,20 +2649,7 @@ async function buildContext(runtime: Runtime) {
2694
2649
  );
2695
2650
  urls = {};
2696
2651
  } else {
2697
- // apiKey mode: no agent session is available, so we can't call
2698
- // `whatCanIDoHere` (it gates on `validateAgentSession`). Return a
2699
- // minimal context derived from sandbox envVars so dench-cli help
2700
- // surfaces still work without a full login.
2701
- mine = {
2702
- authMode: "apiKey",
2703
- organization: runtime.organization ?? null,
2704
- runId: process.env.DENCH_RUN_ID?.trim() ?? null,
2705
- parentRunId: process.env.DENCH_PARENT_RUN_ID?.trim() ?? null,
2706
- rootRunId: process.env.DENCH_ROOT_RUN_ID?.trim() ?? null,
2707
- };
2708
- urls = runtime.host
2709
- ? workspaceUrls(runtime.host, runtime.organization?.slug)
2710
- : {};
2652
+ throw loginRequiredError("dench context");
2711
2653
  }
2712
2654
 
2713
2655
  return {
@@ -2935,7 +2877,7 @@ function summarizeDevMine(workspaceSlug: string, data: WorkspaceOverview) {
2935
2877
  }
2936
2878
 
2937
2879
  async function listArtifacts(runtime: Runtime, limit?: number) {
2938
- if (runtime.mode === "session") {
2880
+ if (runtime.mode === "session" || runtime.mode === "apiKey") {
2939
2881
  try {
2940
2882
  return (await runtime.client.query(
2941
2883
  api.functions.agentWorkspace.agentListArtifacts,
@@ -2953,12 +2895,6 @@ async function listArtifacts(runtime: Runtime, limit?: number) {
2953
2895
  );
2954
2896
  }
2955
2897
  }
2956
- if (runtime.mode === "apiKey") {
2957
- // No agent identity → no artifact list. Return empty so callers
2958
- // (e.g. `dench artifacts`, `dench suggested-work`) degrade gracefully
2959
- // instead of throwing for sandbox callers.
2960
- return [] as JsonRecord[];
2961
- }
2962
2898
  const data = await devOverview(runtime);
2963
2899
  return asRecordArray(data.recentArtifacts);
2964
2900
  }
@@ -4167,15 +4103,15 @@ async function main() {
4167
4103
  }
4168
4104
 
4169
4105
  if (command === "memory") {
4170
- const sessionRuntime = requireSessionRuntime(runtime, "dench memory");
4106
+ const authedRuntime = requireAuthenticatedRuntime(runtime, "dench memory");
4171
4107
  if (subcommand === "search") {
4172
4108
  const query = positionalsFrom(2).join(" ").trim();
4173
4109
  if (!query) throw new Error('Usage: dench memory search "query"');
4174
4110
  print(
4175
- await sessionRuntime.client.query(
4111
+ await authedRuntime.client.query(
4176
4112
  api.functions.agentWorkspace.agentSearchMemory,
4177
4113
  {
4178
- sessionToken: sessionRuntime.sessionToken,
4114
+ sessionToken: authedRuntime.sessionToken,
4179
4115
  query,
4180
4116
  limit: parseNumberOption("--limit"),
4181
4117
  },
@@ -4190,10 +4126,10 @@ async function main() {
4190
4126
  throw new Error('Usage: dench memory save <key> "text"');
4191
4127
  }
4192
4128
  print(
4193
- await sessionRuntime.client.mutation(
4129
+ await authedRuntime.client.mutation(
4194
4130
  api.functions.agentWorkspace.agentSaveMemory,
4195
4131
  {
4196
- sessionToken: sessionRuntime.sessionToken,
4132
+ sessionToken: authedRuntime.sessionToken,
4197
4133
  key,
4198
4134
  kind: (option("--kind") ?? "fact") as never,
4199
4135
  text,
@@ -4218,9 +4154,9 @@ async function main() {
4218
4154
  }
4219
4155
 
4220
4156
  if (command === "status") {
4221
- if (runtime.mode === "session") {
4157
+ if (runtime.mode === "session" || runtime.mode === "apiKey") {
4222
4158
  const status = (await runtime.client.query(
4223
- api.functions.agentWorkspace.agentStatus,
4159
+ api.functions.agentWorkspace.whatCanIDoHere,
4224
4160
  {
4225
4161
  sessionToken: runtime.sessionToken,
4226
4162
  },
@@ -4232,13 +4168,6 @@ async function main() {
4232
4168
  print(status);
4233
4169
  return;
4234
4170
  }
4235
- if (runtime.mode === "apiKey") {
4236
- // No agent session → no agent-status query. Fall back to a thin
4237
- // env-derived snapshot so sandbox callers still get something
4238
- // useful from `dench status`.
4239
- print(await buildContext(runtime));
4240
- return;
4241
- }
4242
4171
  const data = await devOverview(runtime);
4243
4172
  const output = scopedStatus
4244
4173
  ? summarizeDevMine(runtime.workspaceArgs.workspaceSlug, data)
@@ -4250,9 +4179,9 @@ async function main() {
4250
4179
  }
4251
4180
 
4252
4181
  if (command === "agents") {
4253
- if (runtime.mode === "session") {
4182
+ if (runtime.mode === "session" || runtime.mode === "apiKey") {
4254
4183
  const status = await runtime.client.query(
4255
- api.functions.agentWorkspace.agentStatus,
4184
+ api.functions.agentWorkspace.whatCanIDoHere,
4256
4185
  {
4257
4186
  sessionToken: runtime.sessionToken,
4258
4187
  },
@@ -4260,18 +4189,15 @@ async function main() {
4260
4189
  print(status.agents);
4261
4190
  return;
4262
4191
  }
4263
- if (runtime.mode === "apiKey") {
4264
- throw loginRequiredError("dench agents");
4265
- }
4266
4192
  const data = await devOverview(runtime);
4267
4193
  print(data.agents);
4268
4194
  return;
4269
4195
  }
4270
4196
 
4271
4197
  if (command === "approvals") {
4272
- if (runtime.mode === "session") {
4198
+ if (runtime.mode === "session" || runtime.mode === "apiKey") {
4273
4199
  const status = await runtime.client.query(
4274
- api.functions.agentWorkspace.agentStatus,
4200
+ api.functions.agentWorkspace.whatCanIDoHere,
4275
4201
  {
4276
4202
  sessionToken: runtime.sessionToken,
4277
4203
  },
@@ -4279,9 +4205,6 @@ async function main() {
4279
4205
  print(status.approvals);
4280
4206
  return;
4281
4207
  }
4282
- if (runtime.mode === "apiKey") {
4283
- throw loginRequiredError("dench approvals");
4284
- }
4285
4208
  const data = await devOverview(runtime);
4286
4209
  print(data.approvals);
4287
4210
  return;
@@ -4311,7 +4234,7 @@ async function main() {
4311
4234
  if (command === "approval" && subcommand === "request") {
4312
4235
  const title = positional(2);
4313
4236
  if (!title) throw new Error("Missing approval title");
4314
- if (runtime.mode === "session") {
4237
+ if (runtime.mode === "session" || runtime.mode === "apiKey") {
4315
4238
  print(
4316
4239
  await runtime.client.mutation(
4317
4240
  api.functions.agentWorkspace.agentRequestApproval,
@@ -4325,7 +4248,6 @@ async function main() {
4325
4248
  );
4326
4249
  return;
4327
4250
  }
4328
- rejectApiKeyForAgentCommand(runtime, "dench approval request");
4329
4251
  const approvalId = await runtime.client.mutation(
4330
4252
  api.functions.agentWorkspace.devRequestApproval,
4331
4253
  {
@@ -4348,15 +4270,15 @@ async function main() {
4348
4270
  ) {
4349
4271
  const approvalId = positional(2);
4350
4272
  if (!approvalId) throw new Error("Missing approval id");
4351
- const sessionRuntime = requireSessionRuntime(
4273
+ const authedRuntime = requireAuthenticatedRuntime(
4352
4274
  runtime,
4353
4275
  "dench approval approve/reject",
4354
4276
  );
4355
4277
  const decision = subcommand === "approve" ? "approved" : "rejected";
4356
- const result = await sessionRuntime.client.mutation(
4278
+ const result = await authedRuntime.client.mutation(
4357
4279
  api.functions.agentWorkspace.agentDecideApproval,
4358
4280
  {
4359
- sessionToken: sessionRuntime.sessionToken,
4281
+ sessionToken: authedRuntime.sessionToken,
4360
4282
  approvalId: approvalId as never,
4361
4283
  decision,
4362
4284
  evidence: option("--evidence"),
package/host.ts CHANGED
@@ -14,7 +14,8 @@ export const DEFAULT_HOST = PRODUCTION_HOST;
14
14
  * deployment + Next.js host backs `https://www.dench.com`.
15
15
  */
16
16
  export const PRODUCTION_API_URL = "https://www.dench.com";
17
- export const PRODUCTION_CONVEX_URL = "https://beaming-alligator-67.convex.cloud";
17
+ export const PRODUCTION_CONVEX_URL =
18
+ "https://beaming-alligator-67.convex.cloud";
18
19
  export const PRODUCTION_GATEWAY_URL = "https://gateway.merseoriginals.com";
19
20
 
20
21
  export type BackendAlias = "production" | "prod" | "staging" | "local";
@@ -52,6 +53,16 @@ export function isLocalHost(input: string) {
52
53
  );
53
54
  }
54
55
 
56
+ export function apiHostForDenchHost(input: string | undefined) {
57
+ if (!input?.trim()) return PRODUCTION_API_URL;
58
+ const normalized = normalizeHost(input);
59
+ // `dench.com` redirects to `www.dench.com` in production. Fetch drops the
60
+ // Authorization header across that redirect, so CLI API calls must start on
61
+ // the canonical API origin.
62
+ if (normalized === PRODUCTION_HOST) return PRODUCTION_API_URL;
63
+ return normalized;
64
+ }
65
+
55
66
  /**
56
67
  * Resolve `local|staging|prod|production|<url>` -> a normalized host
57
68
  * origin. Aliases let `dench backend set <alias>` (and `--backend
@@ -73,7 +84,11 @@ export function resolveBackendAlias(input: string): string {
73
84
  if (normalized === "staging" || normalized === "stage") {
74
85
  return STAGING_HOST;
75
86
  }
76
- if (normalized === "local" || normalized === "localhost" || normalized === "dev") {
87
+ if (
88
+ normalized === "local" ||
89
+ normalized === "localhost" ||
90
+ normalized === "dev"
91
+ ) {
77
92
  return LOCAL_HOST;
78
93
  }
79
94
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "Dench agent workspace CLI. v2 unifies auth behind `dench signin`; the legacy `dench login` / `dench onboard` / `dench setup` / `dench what-can-i-do` / `dench register` commands now error with a redirect.",
5
5
  "type": "module",
6
6
  "bin": {