@dench.com/cli 0.3.1 → 0.3.3
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 +57 -0
- package/agent.ts +391 -0
- package/chat.ts +216 -0
- package/crm.ts +16 -5
- package/dench.ts +639 -29
- package/fs-daemon.ts +802 -28
- package/package.json +3 -1
package/dench.ts
CHANGED
|
@@ -179,6 +179,21 @@ const api = {
|
|
|
179
179
|
"functions/agentToolsNode:agentToolStatus",
|
|
180
180
|
),
|
|
181
181
|
},
|
|
182
|
+
files: {
|
|
183
|
+
listTree: makeFunctionReference<"query">("functions/files:listTree"),
|
|
184
|
+
deleteFile: makeFunctionReference<"mutation">(
|
|
185
|
+
"functions/files:deleteFile",
|
|
186
|
+
),
|
|
187
|
+
generateUploadUrl: makeFunctionReference<"mutation">(
|
|
188
|
+
"functions/files:generateUploadUrl",
|
|
189
|
+
),
|
|
190
|
+
commitFileUpload: makeFunctionReference<"mutation">(
|
|
191
|
+
"functions/files:commitFileUpload",
|
|
192
|
+
),
|
|
193
|
+
getFileSignedDownloadUrl: makeFunctionReference<"action">(
|
|
194
|
+
"functions/files:getFileSignedDownloadUrl",
|
|
195
|
+
),
|
|
196
|
+
},
|
|
182
197
|
},
|
|
183
198
|
};
|
|
184
199
|
const CONFIG_DIR = join(homedir(), ".dench");
|
|
@@ -233,6 +248,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
233
248
|
"--all",
|
|
234
249
|
"--compact",
|
|
235
250
|
"--dev",
|
|
251
|
+
"--follow",
|
|
236
252
|
"--help",
|
|
237
253
|
"--mine",
|
|
238
254
|
"--self",
|
|
@@ -240,6 +256,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
240
256
|
"--prod",
|
|
241
257
|
"--no-open",
|
|
242
258
|
"--version",
|
|
259
|
+
"--yolo",
|
|
243
260
|
"--ask-before-publishing",
|
|
244
261
|
"--ask-before-spending",
|
|
245
262
|
"--ask-before-external-messages",
|
|
@@ -391,7 +408,7 @@ Usage:
|
|
|
391
408
|
dench tool connect <toolkit> [--json]
|
|
392
409
|
dench tool search "create github issue" [--toolkit github] [--limit 20] [--compact] [--json]
|
|
393
410
|
dench tool run <composio_tool_slug> --args '{"key":"value"}' [--account <connected_account_id>] [--approval <approvalId>] [--json]
|
|
394
|
-
dench autonomous run "Inspect this repo and summarize the relevant feature. Do not edit files, publish, deploy, spend money, send external messages, access secrets, or change production data." [--duration 1h] [--model <model>] [--api-base http://localhost:3000] [--json]
|
|
411
|
+
dench autonomous run "Inspect this repo and summarize the relevant feature. Do not edit files, publish, deploy, spend money, send external messages, access secrets, or change production data." [--duration 1h] [--model <model>] [--api-base http://localhost:3000] [--yolo] [--json]
|
|
395
412
|
|
|
396
413
|
CRM (Daytona-native rewrite):
|
|
397
414
|
dench crm objects <list|get|create|update|rename|delete> ...
|
|
@@ -413,12 +430,32 @@ CRM (Daytona-native rewrite):
|
|
|
413
430
|
dench crm batch --file ops.jsonl
|
|
414
431
|
Help: dench crm help
|
|
415
432
|
|
|
433
|
+
Past chat threads (own + shared in this workspace):
|
|
434
|
+
dench chat list [--query <substr>] [--limit N] [--include-archived] [--json]
|
|
435
|
+
dench chat search "<query>" [--limit N] [--include-current] [--json]
|
|
436
|
+
dench chat read <thread-id> [--limit N] [--json]
|
|
437
|
+
|
|
438
|
+
Spawn a new chat-turn workflow (same as the web UI):
|
|
439
|
+
dench chat new "<prompt>" [--model <m>] [--file-context <path>] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]
|
|
440
|
+
dench chat send <thread-id> "<message>" [--model <m>] [--yolo] [--follow] [--json]
|
|
441
|
+
dench agent new ... (alias for dench chat new)
|
|
442
|
+
dench agent send ... (alias for dench chat send)
|
|
443
|
+
|
|
444
|
+
Help: dench chat help
|
|
445
|
+
|
|
416
446
|
File sync (Daytona-native rewrite):
|
|
447
|
+
dench fs status [--json] [--workspace /workspace] [--no-hash] [--drift-limit N]
|
|
417
448
|
dench fs sync --initial --org-id <orgId> [--workspace /workspace]
|
|
418
449
|
dench fs stream-open <path>
|
|
419
450
|
dench fs stream-append <token> <chunk>
|
|
420
451
|
dench fs stream-close <token>
|
|
421
452
|
|
|
453
|
+
Workspace files (use these instead of mv/rm in /workspace; rename(2)
|
|
454
|
+
returns ENOSYS on the FUSE-backed volume mount):
|
|
455
|
+
dench files ls <path> [--json]
|
|
456
|
+
dench files mv <src> <dst> [--json]
|
|
457
|
+
dench files rm <path> [--recursive] [--json]
|
|
458
|
+
|
|
422
459
|
Subagents (Daytona-native rewrite):
|
|
423
460
|
dench agent spawn '<goal>' [--parent-run-id <id>] [--sandbox own|share_parent] [--time-budget-ms N]
|
|
424
461
|
dench agent await --hook <token> | --children id1,id2,id3
|
|
@@ -491,7 +528,7 @@ function autonomousHelp() {
|
|
|
491
528
|
console.log(`Dench Long Session commands
|
|
492
529
|
|
|
493
530
|
Usage:
|
|
494
|
-
dench autonomous run "Inspect this repo and summarize the relevant feature. Do not edit files, publish, deploy, spend money, send external messages, access secrets, or change production data." [--duration 1h] [--model <model>] [--api-base http://localhost:3000] [--json]
|
|
531
|
+
dench autonomous run "Inspect this repo and summarize the relevant feature. Do not edit files, publish, deploy, spend money, send external messages, access secrets, or change production data." [--duration 1h] [--model <model>] [--api-base http://localhost:3000] [--yolo] [--json]
|
|
495
532
|
|
|
496
533
|
Notes:
|
|
497
534
|
Starts a Long Session through /api/runs.
|
|
@@ -501,6 +538,8 @@ Notes:
|
|
|
501
538
|
If needed, run dench login --host <api-base> once so the CLI can authenticate.
|
|
502
539
|
Omit --model to use the server default.
|
|
503
540
|
Approval flags: --ask-before-publishing --ask-before-spending --ask-before-external-messages --ask-before-production-changes --ask-before-secrets.
|
|
541
|
+
--yolo: skip every approval gate (overrides --ask-before-* / --approval-rules). Equivalent to the workspace's YOLO mode in /<slug>/settings -> Approvals.
|
|
542
|
+
Without any approval flags, the run inherits the workspace's Approvals policy from /<slug>/settings.
|
|
504
543
|
Safe goals should explicitly say: ${SAFE_LONG_SESSION_CONSTRAINTS.join(", ")}.
|
|
505
544
|
`);
|
|
506
545
|
}
|
|
@@ -1417,6 +1456,9 @@ function durationPreset(rawDuration?: string) {
|
|
|
1417
1456
|
}
|
|
1418
1457
|
|
|
1419
1458
|
function parseApprovalRules() {
|
|
1459
|
+
// --yolo wins over everything else: every approval gate is off,
|
|
1460
|
+
// mirroring the org-level YOLO mode (settings -> Approvals).
|
|
1461
|
+
if (hasFlag("--yolo")) return [];
|
|
1420
1462
|
const explicit = option("--approval-rules")
|
|
1421
1463
|
?.split(",")
|
|
1422
1464
|
.map((rule) => rule.trim())
|
|
@@ -1620,11 +1662,27 @@ function hasDevEnv() {
|
|
|
1620
1662
|
);
|
|
1621
1663
|
}
|
|
1622
1664
|
|
|
1665
|
+
/**
|
|
1666
|
+
* Resolve the Convex URL the API-key runtime should talk to. Sandboxes
|
|
1667
|
+
* bake `CONVEX_URL` (see `src/lib/secrets/sandbox-tokens.ts`); local
|
|
1668
|
+
* dev typically only has `NEXT_PUBLIC_CONVEX_URL` exported. We accept
|
|
1669
|
+
* either so the same code path works in both shells without callers
|
|
1670
|
+
* needing to know which env var is set.
|
|
1671
|
+
*/
|
|
1672
|
+
function resolveApiKeyConvexUrl(): string | undefined {
|
|
1673
|
+
return (
|
|
1674
|
+
process.env.CONVEX_URL?.trim() ||
|
|
1675
|
+
process.env.NEXT_PUBLIC_CONVEX_URL?.trim() ||
|
|
1676
|
+
undefined
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1623
1680
|
function loginRequiredError(command = "this command") {
|
|
1624
1681
|
return new CliError(`${command} requires dench login`, {
|
|
1625
1682
|
code: "login_required",
|
|
1626
1683
|
nextActions: [
|
|
1627
|
-
|
|
1684
|
+
"Set DENCH_API_KEY (and CONVEX_URL) in your env if running inside a sandbox or CI.",
|
|
1685
|
+
'Or run dench onboard --kind <kind> --name "AI Agent - Project" to mint an agent session.',
|
|
1628
1686
|
"If already approved, run dench sessions --json, then dench use <session-key-or-workspace-slug>.",
|
|
1629
1687
|
],
|
|
1630
1688
|
});
|
|
@@ -1670,6 +1728,33 @@ async function getRuntime() {
|
|
|
1670
1728
|
],
|
|
1671
1729
|
});
|
|
1672
1730
|
}
|
|
1731
|
+
|
|
1732
|
+
// Sandbox / automation path: the unified Dench API key + Convex URL
|
|
1733
|
+
// baked into sandbox envVars (see
|
|
1734
|
+
// src/lib/secrets/sandbox-tokens.ts) is enough to drive every
|
|
1735
|
+
// command that goes through `requireCrmAccess` server-side. We only
|
|
1736
|
+
// fall into this branch when no stored session is present, so
|
|
1737
|
+
// explicit `dench login` setups keep their existing behavior.
|
|
1738
|
+
const apiKey = process.env.DENCH_API_KEY?.trim();
|
|
1739
|
+
const convexUrl = resolveApiKeyConvexUrl();
|
|
1740
|
+
if (apiKey && convexUrl) {
|
|
1741
|
+
const orgId = process.env.DENCH_ORG_ID?.trim();
|
|
1742
|
+
const orgSlug = process.env.DENCH_ORG_SLUG?.trim();
|
|
1743
|
+
const apiHost =
|
|
1744
|
+
process.env.DENCH_API_URL?.trim() ||
|
|
1745
|
+
process.env.DENCH_INTERNAL_BASE?.trim() ||
|
|
1746
|
+
host;
|
|
1747
|
+
return {
|
|
1748
|
+
mode: "apiKey" as const,
|
|
1749
|
+
host: apiHost,
|
|
1750
|
+
client: new ConvexHttpClient(convexUrl),
|
|
1751
|
+
sessionToken: apiKey,
|
|
1752
|
+
organization:
|
|
1753
|
+
orgId && orgSlug
|
|
1754
|
+
? { id: orgId, name: orgSlug, slug: orgSlug }
|
|
1755
|
+
: undefined,
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1673
1758
|
}
|
|
1674
1759
|
|
|
1675
1760
|
if (!hasDevEnv()) {
|
|
@@ -1688,14 +1773,73 @@ async function getRuntime() {
|
|
|
1688
1773
|
|
|
1689
1774
|
type Runtime = Awaited<ReturnType<typeof getRuntime>>;
|
|
1690
1775
|
|
|
1776
|
+
/**
|
|
1777
|
+
* `session` mode is required for `agentWorkspace.*` calls because they
|
|
1778
|
+
* gate on `validateAgentSession` server-side, which only accepts the
|
|
1779
|
+
* `dch_agent_*` token format. Sandboxes that only have a unified
|
|
1780
|
+
* `DENCH_API_KEY` (apiKey mode) cannot impersonate a specific agent
|
|
1781
|
+
* and will fail at the server with `Invalid agent session` — surface
|
|
1782
|
+
* that here as a clear error instead.
|
|
1783
|
+
*/
|
|
1691
1784
|
function requireSessionRuntime(
|
|
1692
1785
|
runtime: Runtime,
|
|
1693
1786
|
command: string,
|
|
1694
1787
|
): Extract<Runtime, { mode: "session" }> {
|
|
1695
|
-
if (runtime.mode
|
|
1696
|
-
|
|
1788
|
+
if (runtime.mode === "session") return runtime;
|
|
1789
|
+
if (runtime.mode === "apiKey") {
|
|
1790
|
+
throw agentSessionRequiredError(command);
|
|
1791
|
+
}
|
|
1792
|
+
throw loginRequiredError(command);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
/**
|
|
1796
|
+
* Org-scoped commands (`dench crm`, `dench files`, etc.) accept either
|
|
1797
|
+
* a `dch_agent_*` agent session token OR a unified Dench API key
|
|
1798
|
+
* (`dench_*` / `gw_sk_*` / etc.) baked into sandbox envVars. The
|
|
1799
|
+
* server-side `requireCrmAccess` helper dispatches on the prefix.
|
|
1800
|
+
*/
|
|
1801
|
+
type AuthenticatedRuntime = Extract<
|
|
1802
|
+
Runtime,
|
|
1803
|
+
{ mode: "session" | "apiKey" }
|
|
1804
|
+
>;
|
|
1805
|
+
|
|
1806
|
+
function requireAuthenticatedRuntime(
|
|
1807
|
+
runtime: Runtime,
|
|
1808
|
+
command: string,
|
|
1809
|
+
): AuthenticatedRuntime {
|
|
1810
|
+
if (runtime.mode === "session" || runtime.mode === "apiKey") return runtime;
|
|
1811
|
+
throw loginRequiredError(command);
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
function agentSessionRequiredError(command: string) {
|
|
1815
|
+
return new CliError(
|
|
1816
|
+
`${command} requires an agent session — DENCH_API_KEY is org-scoped and cannot impersonate a specific agent`,
|
|
1817
|
+
{
|
|
1818
|
+
code: "agent_session_required",
|
|
1819
|
+
nextActions: [
|
|
1820
|
+
'Run dench onboard --kind <kind> --name "AI Agent - Project" to mint an agent session for this command.',
|
|
1821
|
+
"Or run the equivalent action via the workspace UI / dench crm if it is org-level (e.g. CRM data, files).",
|
|
1822
|
+
],
|
|
1823
|
+
},
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
/**
|
|
1828
|
+
* Some legacy `agentWorkspace.*` commands (task create / claim / log /
|
|
1829
|
+
* approval request) have separate session vs dev branches. With apiKey
|
|
1830
|
+
* mode added, the dev branch would silently receive partial args. Use
|
|
1831
|
+
* this to short-circuit those branches with a clear error.
|
|
1832
|
+
*
|
|
1833
|
+
* Asserts away the `apiKey` mode so the caller's downstream type
|
|
1834
|
+
* narrowing (e.g. `runtime.workspaceArgs`) keeps working.
|
|
1835
|
+
*/
|
|
1836
|
+
function rejectApiKeyForAgentCommand(
|
|
1837
|
+
runtime: Runtime,
|
|
1838
|
+
command: string,
|
|
1839
|
+
): asserts runtime is Exclude<Runtime, { mode: "apiKey" }> {
|
|
1840
|
+
if (runtime.mode === "apiKey") {
|
|
1841
|
+
throw agentSessionRequiredError(command);
|
|
1697
1842
|
}
|
|
1698
|
-
return runtime;
|
|
1699
1843
|
}
|
|
1700
1844
|
|
|
1701
1845
|
async function printToolStatus(runtime: Runtime, toolkit?: string) {
|
|
@@ -1927,23 +2071,36 @@ async function connectedAppsContext(runtime: Runtime) {
|
|
|
1927
2071
|
}
|
|
1928
2072
|
|
|
1929
2073
|
async function buildContext(runtime: Runtime) {
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2074
|
+
let mine: JsonRecord;
|
|
2075
|
+
let urls: JsonRecord;
|
|
2076
|
+
if (runtime.mode === "session") {
|
|
2077
|
+
mine = (await runtime.client.query(
|
|
2078
|
+
api.functions.agentWorkspace.whatCanIDoHere,
|
|
2079
|
+
{ sessionToken: runtime.sessionToken },
|
|
2080
|
+
)) as JsonRecord;
|
|
2081
|
+
urls = workspaceUrls(runtime.host, workspaceSlugFromContext(mine));
|
|
2082
|
+
} else if (runtime.mode === "dev") {
|
|
2083
|
+
mine = summarizeDevMine(
|
|
2084
|
+
runtime.workspaceArgs.workspaceSlug,
|
|
2085
|
+
await devOverview(runtime),
|
|
2086
|
+
);
|
|
2087
|
+
urls = {};
|
|
2088
|
+
} else {
|
|
2089
|
+
// apiKey mode: no agent session is available, so we can't call
|
|
2090
|
+
// `whatCanIDoHere` (it gates on `validateAgentSession`). Return a
|
|
2091
|
+
// minimal context derived from sandbox envVars so dench-cli help
|
|
2092
|
+
// surfaces still work without a full login.
|
|
2093
|
+
mine = {
|
|
2094
|
+
authMode: "apiKey",
|
|
2095
|
+
organization: runtime.organization ?? null,
|
|
2096
|
+
runId: process.env.DENCH_RUN_ID?.trim() ?? null,
|
|
2097
|
+
parentRunId: process.env.DENCH_PARENT_RUN_ID?.trim() ?? null,
|
|
2098
|
+
rootRunId: process.env.DENCH_ROOT_RUN_ID?.trim() ?? null,
|
|
2099
|
+
};
|
|
2100
|
+
urls = runtime.host
|
|
2101
|
+
? workspaceUrls(runtime.host, runtime.organization?.slug)
|
|
1946
2102
|
: {};
|
|
2103
|
+
}
|
|
1947
2104
|
|
|
1948
2105
|
return {
|
|
1949
2106
|
...mine,
|
|
@@ -2341,7 +2498,7 @@ function withToolRunGuidance(runtime: Runtime, result: unknown) {
|
|
|
2341
2498
|
if (!record || record.requiresApproval !== true) return result;
|
|
2342
2499
|
const approvalId = stringField(record, "approvalId");
|
|
2343
2500
|
const statusContext =
|
|
2344
|
-
runtime.mode === "session"
|
|
2501
|
+
runtime.mode === "session" || runtime.mode === "apiKey"
|
|
2345
2502
|
? workspaceUrls(runtime.host, runtime.organization?.slug)
|
|
2346
2503
|
: {};
|
|
2347
2504
|
const approvalCommand = approvalId
|
|
@@ -2501,6 +2658,12 @@ async function listArtifacts(runtime: Runtime, limit?: number) {
|
|
|
2501
2658
|
);
|
|
2502
2659
|
}
|
|
2503
2660
|
}
|
|
2661
|
+
if (runtime.mode === "apiKey") {
|
|
2662
|
+
// No agent identity → no artifact list. Return empty so callers
|
|
2663
|
+
// (e.g. `dench artifacts`, `dench suggested-work`) degrade gracefully
|
|
2664
|
+
// instead of throwing for sandbox callers.
|
|
2665
|
+
return [] as JsonRecord[];
|
|
2666
|
+
}
|
|
2504
2667
|
const data = await devOverview(runtime);
|
|
2505
2668
|
return asRecordArray(data.recentArtifacts);
|
|
2506
2669
|
}
|
|
@@ -2542,6 +2705,354 @@ async function runFsCommand() {
|
|
|
2542
2705
|
}
|
|
2543
2706
|
}
|
|
2544
2707
|
|
|
2708
|
+
// ── dench files <ls|mv|rm> ────────────────────────────────────────────────
|
|
2709
|
+
//
|
|
2710
|
+
// Escape hatches the model needs every time something on /workspace
|
|
2711
|
+
// can't be done with `mv` / `rm` — Mountpoint-S3 doesn't implement
|
|
2712
|
+
// rename(2) and the file daemon needs a hint to prune Convex rows.
|
|
2713
|
+
// All three commands take + emit POSIX paths under /workspace.
|
|
2714
|
+
|
|
2715
|
+
function normalizeFilesPath(input: string): string {
|
|
2716
|
+
if (!input || input === "/") return "/";
|
|
2717
|
+
let cleaned = input.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2718
|
+
if (cleaned.startsWith("/workspace/")) cleaned = cleaned.slice("/workspace".length);
|
|
2719
|
+
else if (cleaned === "/workspace") cleaned = "/";
|
|
2720
|
+
if (!cleaned.startsWith("/")) cleaned = `/${cleaned}`;
|
|
2721
|
+
if (cleaned.length > 1 && cleaned.endsWith("/")) cleaned = cleaned.slice(0, -1);
|
|
2722
|
+
return cleaned;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
function filesApiKey(runtime: Runtime): string | undefined {
|
|
2726
|
+
if (runtime.mode === "apiKey") return runtime.sessionToken;
|
|
2727
|
+
return process.env.DENCH_API_KEY?.trim() || undefined;
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
function requireFilesApiKey(runtime: Runtime, command: string): string {
|
|
2731
|
+
const apiKey = filesApiKey(runtime);
|
|
2732
|
+
if (!apiKey) {
|
|
2733
|
+
throw new CliError(
|
|
2734
|
+
`${command} needs DENCH_API_KEY (or a sandbox-baked api-key session). It bypasses the user-session auth path.`,
|
|
2735
|
+
{
|
|
2736
|
+
code: "files_api_key_required",
|
|
2737
|
+
nextActions: [
|
|
2738
|
+
"Run inside a Dench sandbox where DENCH_API_KEY is baked in.",
|
|
2739
|
+
"Or export DENCH_API_KEY=<key> first (see dench login output).",
|
|
2740
|
+
],
|
|
2741
|
+
},
|
|
2742
|
+
);
|
|
2743
|
+
}
|
|
2744
|
+
return apiKey;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
type FileTreeRow = {
|
|
2748
|
+
path: string;
|
|
2749
|
+
contentHash?: string;
|
|
2750
|
+
isDir: boolean;
|
|
2751
|
+
size?: number;
|
|
2752
|
+
mtime?: number;
|
|
2753
|
+
};
|
|
2754
|
+
|
|
2755
|
+
async function listConvexTreeAt(
|
|
2756
|
+
runtime: Runtime,
|
|
2757
|
+
apiKey: string,
|
|
2758
|
+
prefix: string,
|
|
2759
|
+
): Promise<FileTreeRow[]> {
|
|
2760
|
+
return (await runtime.client.query(api.functions.files.listTree, {
|
|
2761
|
+
prefix,
|
|
2762
|
+
apiKey,
|
|
2763
|
+
} as never)) as FileTreeRow[];
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
async function listConvexTreeRecursive(
|
|
2767
|
+
runtime: Runtime,
|
|
2768
|
+
apiKey: string,
|
|
2769
|
+
prefix: string,
|
|
2770
|
+
): Promise<FileTreeRow[]> {
|
|
2771
|
+
const rows = await listConvexTreeAt(runtime, apiKey, prefix);
|
|
2772
|
+
const out: FileTreeRow[] = [...rows];
|
|
2773
|
+
for (const row of rows) {
|
|
2774
|
+
if (row.isDir) {
|
|
2775
|
+
const children = await listConvexTreeRecursive(runtime, apiKey, row.path);
|
|
2776
|
+
out.push(...children);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
return out;
|
|
2780
|
+
}
|
|
2781
|
+
|
|
2782
|
+
async function downloadConvexFileBytes(
|
|
2783
|
+
runtime: Runtime,
|
|
2784
|
+
apiKey: string,
|
|
2785
|
+
path: string,
|
|
2786
|
+
): Promise<Uint8Array | null> {
|
|
2787
|
+
const url = (await runtime.client.action(
|
|
2788
|
+
api.functions.files.getFileSignedDownloadUrl,
|
|
2789
|
+
{ path, apiKey } as never,
|
|
2790
|
+
)) as string | null;
|
|
2791
|
+
if (!url) return null;
|
|
2792
|
+
const response = await fetch(url);
|
|
2793
|
+
if (!response.ok) {
|
|
2794
|
+
throw new Error(
|
|
2795
|
+
`signed download failed (${response.status}): ${await response
|
|
2796
|
+
.text()
|
|
2797
|
+
.catch(() => "")}`,
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
async function uploadConvexFileBytes(
|
|
2804
|
+
runtime: Runtime,
|
|
2805
|
+
apiKey: string,
|
|
2806
|
+
args: { path: string; bytes: Uint8Array },
|
|
2807
|
+
): Promise<{ path: string; storageId: string; contentHash: string }> {
|
|
2808
|
+
const uploadUrl = (await runtime.client.mutation(
|
|
2809
|
+
api.functions.files.generateUploadUrl,
|
|
2810
|
+
{ path: args.path, apiKey } as never,
|
|
2811
|
+
)) as string;
|
|
2812
|
+
const response = await fetch(uploadUrl, {
|
|
2813
|
+
method: "POST",
|
|
2814
|
+
headers: { "content-type": "application/octet-stream" },
|
|
2815
|
+
body: new Blob([args.bytes as BlobPart]),
|
|
2816
|
+
});
|
|
2817
|
+
if (!response.ok) {
|
|
2818
|
+
throw new Error(
|
|
2819
|
+
`convex storage upload failed (${response.status}): ${await response
|
|
2820
|
+
.text()
|
|
2821
|
+
.catch(() => "")}`,
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
const { storageId } = (await response.json()) as { storageId: string };
|
|
2825
|
+
const hash = sha256HexSync(args.bytes);
|
|
2826
|
+
const inlineEligible = args.bytes.byteLength <= 100_000;
|
|
2827
|
+
const inlineText = inlineEligible
|
|
2828
|
+
? new TextDecoder("utf-8", { fatal: false }).decode(args.bytes)
|
|
2829
|
+
: undefined;
|
|
2830
|
+
await runtime.client.mutation(api.functions.files.commitFileUpload, {
|
|
2831
|
+
path: args.path,
|
|
2832
|
+
storageId,
|
|
2833
|
+
contentHash: hash,
|
|
2834
|
+
size: args.bytes.byteLength,
|
|
2835
|
+
text: inlineText,
|
|
2836
|
+
lastModifiedBy: "cli",
|
|
2837
|
+
apiKey,
|
|
2838
|
+
} as never);
|
|
2839
|
+
return { path: args.path, storageId, contentHash: hash };
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
async function runFilesLs() {
|
|
2843
|
+
const runtime = await getRuntime();
|
|
2844
|
+
const apiKey = requireFilesApiKey(runtime, "dench files ls");
|
|
2845
|
+
const target = normalizeFilesPath(positional(2) ?? "/");
|
|
2846
|
+
const recursive = hasFlag("--recursive") || hasFlag("-r");
|
|
2847
|
+
const rows = recursive
|
|
2848
|
+
? await listConvexTreeRecursive(runtime, apiKey, target)
|
|
2849
|
+
: await listConvexTreeAt(runtime, apiKey, target);
|
|
2850
|
+
if (hasFlag("--json")) {
|
|
2851
|
+
print({
|
|
2852
|
+
ok: true,
|
|
2853
|
+
prefix: target,
|
|
2854
|
+
recursive,
|
|
2855
|
+
count: rows.length,
|
|
2856
|
+
entries: rows.map((row) => ({
|
|
2857
|
+
path: row.path,
|
|
2858
|
+
isDir: row.isDir,
|
|
2859
|
+
size: row.size,
|
|
2860
|
+
mtime: row.mtime,
|
|
2861
|
+
contentHash: row.contentHash,
|
|
2862
|
+
})),
|
|
2863
|
+
});
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
if (rows.length === 0) {
|
|
2867
|
+
process.stdout.write(`(no entries under ${target})\n`);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2870
|
+
for (const row of rows) {
|
|
2871
|
+
const kind = row.isDir ? "DIR " : "FILE";
|
|
2872
|
+
const size = row.isDir
|
|
2873
|
+
? " -"
|
|
2874
|
+
: (row.size ?? 0).toString().padStart(8, " ");
|
|
2875
|
+
process.stdout.write(`${kind} ${size} ${row.path}\n`);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
async function runFilesMv() {
|
|
2880
|
+
const runtime = await getRuntime();
|
|
2881
|
+
const apiKey = requireFilesApiKey(runtime, "dench files mv");
|
|
2882
|
+
const fromRaw = positional(2);
|
|
2883
|
+
const toRaw = positional(3);
|
|
2884
|
+
if (!fromRaw || !toRaw) {
|
|
2885
|
+
throw new CliError("Usage: dench files mv <src> <dst>", {
|
|
2886
|
+
code: "files_mv_usage",
|
|
2887
|
+
nextActions: ["Pass both source and destination paths."],
|
|
2888
|
+
});
|
|
2889
|
+
}
|
|
2890
|
+
const from = normalizeFilesPath(fromRaw);
|
|
2891
|
+
const to = normalizeFilesPath(toRaw);
|
|
2892
|
+
if (from === to) {
|
|
2893
|
+
throw new CliError("Source and destination are the same path", {
|
|
2894
|
+
code: "files_mv_noop",
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
const sourceRows = await listConvexTreeRecursive(runtime, apiKey, from);
|
|
2899
|
+
const sourceSelf = (await listConvexTreeAt(runtime, apiKey, parentOfPath(from))).find(
|
|
2900
|
+
(row) => row.path === from,
|
|
2901
|
+
);
|
|
2902
|
+
if (!sourceSelf && sourceRows.length === 0) {
|
|
2903
|
+
throw new CliError(`Source not found in Convex: ${from}`, {
|
|
2904
|
+
code: "files_mv_not_found",
|
|
2905
|
+
nextActions: [
|
|
2906
|
+
"Run `dench files ls` on the parent directory to confirm.",
|
|
2907
|
+
"If the file only exists on the sandbox FS, run the daemon (or `dench fs sync --initial`) first.",
|
|
2908
|
+
],
|
|
2909
|
+
});
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
const moved: Array<{ from: string; to: string }> = [];
|
|
2913
|
+
if (sourceSelf && !sourceSelf.isDir) {
|
|
2914
|
+
const bytes = await downloadConvexFileBytes(runtime, apiKey, from);
|
|
2915
|
+
if (!bytes) {
|
|
2916
|
+
throw new CliError(`Could not download bytes for ${from}`, {
|
|
2917
|
+
code: "files_mv_download",
|
|
2918
|
+
});
|
|
2919
|
+
}
|
|
2920
|
+
await uploadConvexFileBytes(runtime, apiKey, { path: to, bytes });
|
|
2921
|
+
await runtime.client.mutation(api.functions.files.deleteFile, {
|
|
2922
|
+
path: from,
|
|
2923
|
+
apiKey,
|
|
2924
|
+
} as never);
|
|
2925
|
+
moved.push({ from, to });
|
|
2926
|
+
} else {
|
|
2927
|
+
for (const row of sourceRows) {
|
|
2928
|
+
if (row.isDir) continue;
|
|
2929
|
+
const rel = row.path.slice(from.length).replace(/^\/+/, "");
|
|
2930
|
+
const dest = rel ? `${to}/${rel}` : to;
|
|
2931
|
+
const bytes = await downloadConvexFileBytes(runtime, apiKey, row.path);
|
|
2932
|
+
if (!bytes) continue;
|
|
2933
|
+
await uploadConvexFileBytes(runtime, apiKey, { path: dest, bytes });
|
|
2934
|
+
await runtime.client.mutation(api.functions.files.deleteFile, {
|
|
2935
|
+
path: row.path,
|
|
2936
|
+
apiKey,
|
|
2937
|
+
} as never);
|
|
2938
|
+
moved.push({ from: row.path, to: dest });
|
|
2939
|
+
}
|
|
2940
|
+
if (sourceSelf?.isDir) {
|
|
2941
|
+
await runtime.client.mutation(api.functions.files.deleteFile, {
|
|
2942
|
+
path: from,
|
|
2943
|
+
apiKey,
|
|
2944
|
+
} as never);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
print({ ok: true, from, to, moved });
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2951
|
+
async function runFilesRm() {
|
|
2952
|
+
const runtime = await getRuntime();
|
|
2953
|
+
const apiKey = requireFilesApiKey(runtime, "dench files rm");
|
|
2954
|
+
const targetRaw = positional(2);
|
|
2955
|
+
if (!targetRaw) {
|
|
2956
|
+
throw new CliError("Usage: dench files rm <path> [--recursive]", {
|
|
2957
|
+
code: "files_rm_usage",
|
|
2958
|
+
});
|
|
2959
|
+
}
|
|
2960
|
+
const target = normalizeFilesPath(targetRaw);
|
|
2961
|
+
const recursive = hasFlag("--recursive") || hasFlag("-r");
|
|
2962
|
+
|
|
2963
|
+
const parentRows = await listConvexTreeAt(runtime, apiKey, parentOfPath(target));
|
|
2964
|
+
const self = parentRows.find((row) => row.path === target);
|
|
2965
|
+
if (!self) {
|
|
2966
|
+
if (hasFlag("--json")) {
|
|
2967
|
+
print({ ok: true, path: target, removedFiles: 0, removedDirs: 0 });
|
|
2968
|
+
} else {
|
|
2969
|
+
process.stdout.write(
|
|
2970
|
+
`dench files rm: ${target} not present in Convex (already absent)\n`,
|
|
2971
|
+
);
|
|
2972
|
+
}
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
if (self.isDir && !recursive) {
|
|
2976
|
+
throw new CliError(
|
|
2977
|
+
`${target} is a directory; pass --recursive to delete it.`,
|
|
2978
|
+
{ code: "files_rm_recursive" },
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
const toDelete = self.isDir
|
|
2983
|
+
? await listConvexTreeRecursive(runtime, apiKey, target)
|
|
2984
|
+
: [];
|
|
2985
|
+
const fileRows = toDelete.filter((row) => !row.isDir);
|
|
2986
|
+
const dirRows = toDelete.filter((row) => row.isDir);
|
|
2987
|
+
// Children first, parents last.
|
|
2988
|
+
dirRows.sort((a, b) => b.path.length - a.path.length);
|
|
2989
|
+
|
|
2990
|
+
for (const row of fileRows) {
|
|
2991
|
+
await runtime.client.mutation(api.functions.files.deleteFile, {
|
|
2992
|
+
path: row.path,
|
|
2993
|
+
apiKey,
|
|
2994
|
+
} as never);
|
|
2995
|
+
}
|
|
2996
|
+
for (const row of dirRows) {
|
|
2997
|
+
await runtime.client.mutation(api.functions.files.deleteFile, {
|
|
2998
|
+
path: row.path,
|
|
2999
|
+
apiKey,
|
|
3000
|
+
} as never);
|
|
3001
|
+
}
|
|
3002
|
+
await runtime.client.mutation(api.functions.files.deleteFile, {
|
|
3003
|
+
path: target,
|
|
3004
|
+
apiKey,
|
|
3005
|
+
} as never);
|
|
3006
|
+
|
|
3007
|
+
print({
|
|
3008
|
+
ok: true,
|
|
3009
|
+
path: target,
|
|
3010
|
+
removedFiles: fileRows.length + (self.isDir ? 0 : 1),
|
|
3011
|
+
removedDirs: dirRows.length + (self.isDir ? 1 : 0),
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
async function runFilesCommand() {
|
|
3016
|
+
const sub = positional(1);
|
|
3017
|
+
if (!sub || sub === "help" || hasFlag("--help")) {
|
|
3018
|
+
process.stdout.write(
|
|
3019
|
+
[
|
|
3020
|
+
"dench files <ls|mv|rm> — workspace file ops that update the UI tree",
|
|
3021
|
+
"",
|
|
3022
|
+
" dench files ls [<path>] [--recursive] [--json]",
|
|
3023
|
+
" dench files mv <src> <dst> [--json]",
|
|
3024
|
+
" dench files rm <path> [--recursive] [--json]",
|
|
3025
|
+
"",
|
|
3026
|
+
"Paths are POSIX paths under /workspace; you can pass either form.",
|
|
3027
|
+
"These mutate Convex directly (and the daemon syncs to the volume).",
|
|
3028
|
+
].join("\n"),
|
|
3029
|
+
);
|
|
3030
|
+
return;
|
|
3031
|
+
}
|
|
3032
|
+
if (sub === "ls") return await runFilesLs();
|
|
3033
|
+
if (sub === "mv") return await runFilesMv();
|
|
3034
|
+
if (sub === "rm") return await runFilesRm();
|
|
3035
|
+
throw new CliError(`Unknown subcommand: dench files ${sub}`, {
|
|
3036
|
+
code: "files_unknown_subcommand",
|
|
3037
|
+
nextActions: ["Run dench files help for usage."],
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
function parentOfPath(input: string): string {
|
|
3042
|
+
const normalized = normalizeFilesPath(input);
|
|
3043
|
+
if (normalized === "/") return "/";
|
|
3044
|
+
const idx = normalized.lastIndexOf("/");
|
|
3045
|
+
return idx <= 0 ? "/" : normalized.slice(0, idx);
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
function sha256HexSync(bytes: Uint8Array): string {
|
|
3049
|
+
// Used by the dench files commands so we can compute the hash
|
|
3050
|
+
// alongside the upload synchronously. Bun + Node both ship
|
|
3051
|
+
// node:crypto, so this is free.
|
|
3052
|
+
const { createHash } = require("node:crypto") as typeof import("node:crypto");
|
|
3053
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
3054
|
+
}
|
|
3055
|
+
|
|
2545
3056
|
async function spawnInherited(binary: string, commandArgs: string[]) {
|
|
2546
3057
|
return await new Promise<number>((resolve, reject) => {
|
|
2547
3058
|
const child = spawn(binary, commandArgs, {
|
|
@@ -2675,14 +3186,16 @@ async function main() {
|
|
|
2675
3186
|
}
|
|
2676
3187
|
const { runCrmCommand } = await import("./crm");
|
|
2677
3188
|
const runtime = await getRuntime();
|
|
2678
|
-
|
|
3189
|
+
// CRM is org-scoped — accept either an agent session token
|
|
3190
|
+
// (`dch_agent_*` from `dench login`) or a unified Dench API key
|
|
3191
|
+
// (`DENCH_API_KEY` baked into sandbox envVars). Server-side
|
|
3192
|
+
// `requireCrmAccess` (convex/lib/crm/access.ts) dispatches on the
|
|
3193
|
+
// bearer prefix and resolves the org for both.
|
|
3194
|
+
const authedRuntime = requireAuthenticatedRuntime(runtime, "dench crm");
|
|
2679
3195
|
await runCrmCommand({
|
|
2680
|
-
convex:
|
|
3196
|
+
convex: authedRuntime.client,
|
|
2681
3197
|
args: subArgs,
|
|
2682
|
-
|
|
2683
|
-
// this token and resolves the agent's organization without a Convex
|
|
2684
|
-
// Auth cookie. Mirrors the pattern used by every agentWorkspace fn.
|
|
2685
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
3198
|
+
sessionToken: authedRuntime.sessionToken,
|
|
2686
3199
|
});
|
|
2687
3200
|
return;
|
|
2688
3201
|
}
|
|
@@ -2692,6 +3205,60 @@ async function main() {
|
|
|
2692
3205
|
return;
|
|
2693
3206
|
}
|
|
2694
3207
|
|
|
3208
|
+
if (command === "files") {
|
|
3209
|
+
await runFilesCommand();
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
if (command === "chat") {
|
|
3214
|
+
const subArgs = args.slice(args.indexOf("chat") + 1);
|
|
3215
|
+
const sub = subArgs[0];
|
|
3216
|
+
if (!sub || sub === "help" || sub === "--help") {
|
|
3217
|
+
const { runChatCommand } = await import("./chat");
|
|
3218
|
+
// biome-ignore lint/suspicious/noExplicitAny: harmless help-only stub
|
|
3219
|
+
await runChatCommand({ convex: {} as any, args: subArgs });
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
// `chat new` / `chat send` spawn a chat-turn workflow via
|
|
3223
|
+
// /api/chat/cli (the same workflow the web UI uses). They need
|
|
3224
|
+
// an HTTP host + bearer token, not a Convex client. The
|
|
3225
|
+
// remaining subcommands (list / search / read) are Convex
|
|
3226
|
+
// queries and keep going through `runChatCommand`.
|
|
3227
|
+
if (sub === "new" || sub === "send") {
|
|
3228
|
+
const { runChatSpawnCommand } = await import("./chat-spawn");
|
|
3229
|
+
const runtime = await getRuntime();
|
|
3230
|
+
const authedRuntime = requireAuthenticatedRuntime(
|
|
3231
|
+
runtime,
|
|
3232
|
+
`dench chat ${sub}`,
|
|
3233
|
+
);
|
|
3234
|
+
// Drop the leading subcommand from subArgs — runChatSpawnCommand
|
|
3235
|
+
// expects the remaining tokens (prompt + flags).
|
|
3236
|
+
await runChatSpawnCommand({
|
|
3237
|
+
runtime: {
|
|
3238
|
+
host: authedRuntime.host,
|
|
3239
|
+
bearerToken: authedRuntime.sessionToken,
|
|
3240
|
+
},
|
|
3241
|
+
args: subArgs.slice(1),
|
|
3242
|
+
subcommand: sub,
|
|
3243
|
+
});
|
|
3244
|
+
return;
|
|
3245
|
+
}
|
|
3246
|
+
const { runChatCommand } = await import("./chat");
|
|
3247
|
+
const runtime = await getRuntime();
|
|
3248
|
+
// Past-chat queries are org+user-scoped: the server resolves the
|
|
3249
|
+
// user from the session token, OR from `runId` when authenticating
|
|
3250
|
+
// with a Dench API key. Pass DENCH_RUN_ID through automatically so
|
|
3251
|
+
// `dench chat …` works inside a sandbox without extra flags.
|
|
3252
|
+
const authedRuntime = requireAuthenticatedRuntime(runtime, "dench chat");
|
|
3253
|
+
await runChatCommand({
|
|
3254
|
+
convex: authedRuntime.client,
|
|
3255
|
+
args: subArgs,
|
|
3256
|
+
sessionToken: authedRuntime.sessionToken,
|
|
3257
|
+
runId: process.env.DENCH_RUN_ID?.trim() || undefined,
|
|
3258
|
+
});
|
|
3259
|
+
return;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
2695
3262
|
if (command === "agent") {
|
|
2696
3263
|
const subArgs = args.slice(args.indexOf("agent") + 1);
|
|
2697
3264
|
const sub = subArgs[0];
|
|
@@ -2701,6 +3268,27 @@ async function main() {
|
|
|
2701
3268
|
await runAgentCommand({ convex: {} as any, args: subArgs });
|
|
2702
3269
|
return;
|
|
2703
3270
|
}
|
|
3271
|
+
// Aliases: `dench agent new` / `dench agent send` -> the chat-spawn
|
|
3272
|
+
// implementation. Surfaced under `agent` so users who associate
|
|
3273
|
+
// chat threads with the agent surface still find them; the actual
|
|
3274
|
+
// workflow is identical to `dench chat new` / `dench chat send`.
|
|
3275
|
+
if (sub === "new" || sub === "send") {
|
|
3276
|
+
const { runChatSpawnCommand } = await import("./chat-spawn");
|
|
3277
|
+
const runtime = await getRuntime();
|
|
3278
|
+
const authedRuntime = requireAuthenticatedRuntime(
|
|
3279
|
+
runtime,
|
|
3280
|
+
`dench agent ${sub}`,
|
|
3281
|
+
);
|
|
3282
|
+
await runChatSpawnCommand({
|
|
3283
|
+
runtime: {
|
|
3284
|
+
host: authedRuntime.host,
|
|
3285
|
+
bearerToken: authedRuntime.sessionToken,
|
|
3286
|
+
},
|
|
3287
|
+
args: subArgs.slice(1),
|
|
3288
|
+
subcommand: sub,
|
|
3289
|
+
});
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
2704
3292
|
const { runAgentCommand } = await import("./agent");
|
|
2705
3293
|
const runtime = await getRuntime();
|
|
2706
3294
|
await runAgentCommand({
|
|
@@ -2881,6 +3469,13 @@ async function main() {
|
|
|
2881
3469
|
print(status);
|
|
2882
3470
|
return;
|
|
2883
3471
|
}
|
|
3472
|
+
if (runtime.mode === "apiKey") {
|
|
3473
|
+
// No agent session → no agent-status query. Fall back to a thin
|
|
3474
|
+
// env-derived snapshot so sandbox callers still get something
|
|
3475
|
+
// useful from `dench status`.
|
|
3476
|
+
print(await buildContext(runtime));
|
|
3477
|
+
return;
|
|
3478
|
+
}
|
|
2884
3479
|
const data = await devOverview(runtime);
|
|
2885
3480
|
const output = scopedStatus
|
|
2886
3481
|
? summarizeDevMine(runtime.workspaceArgs.workspaceSlug, data)
|
|
@@ -2903,6 +3498,11 @@ async function main() {
|
|
|
2903
3498
|
);
|
|
2904
3499
|
return;
|
|
2905
3500
|
}
|
|
3501
|
+
if (runtime.mode === "apiKey") {
|
|
3502
|
+
throw loginRequiredError(
|
|
3503
|
+
"dench tasks (agent-scoped task list)",
|
|
3504
|
+
);
|
|
3505
|
+
}
|
|
2906
3506
|
const data = await devOverview(runtime);
|
|
2907
3507
|
print(data.tasks);
|
|
2908
3508
|
return;
|
|
@@ -2919,6 +3519,9 @@ async function main() {
|
|
|
2919
3519
|
print(status.agents);
|
|
2920
3520
|
return;
|
|
2921
3521
|
}
|
|
3522
|
+
if (runtime.mode === "apiKey") {
|
|
3523
|
+
throw loginRequiredError("dench agents");
|
|
3524
|
+
}
|
|
2922
3525
|
const data = await devOverview(runtime);
|
|
2923
3526
|
print(data.agents);
|
|
2924
3527
|
return;
|
|
@@ -2935,6 +3538,9 @@ async function main() {
|
|
|
2935
3538
|
print(status.approvals);
|
|
2936
3539
|
return;
|
|
2937
3540
|
}
|
|
3541
|
+
if (runtime.mode === "apiKey") {
|
|
3542
|
+
throw loginRequiredError("dench approvals");
|
|
3543
|
+
}
|
|
2938
3544
|
const data = await devOverview(runtime);
|
|
2939
3545
|
print(data.approvals);
|
|
2940
3546
|
return;
|
|
@@ -3000,6 +3606,7 @@ async function main() {
|
|
|
3000
3606
|
);
|
|
3001
3607
|
return;
|
|
3002
3608
|
}
|
|
3609
|
+
rejectApiKeyForAgentCommand(runtime, "dench task create");
|
|
3003
3610
|
const taskId = await runtime.client.mutation(
|
|
3004
3611
|
api.functions.agentWorkspace.devCreateTask,
|
|
3005
3612
|
{
|
|
@@ -3108,6 +3715,7 @@ async function main() {
|
|
|
3108
3715
|
);
|
|
3109
3716
|
return;
|
|
3110
3717
|
}
|
|
3718
|
+
rejectApiKeyForAgentCommand(runtime, "dench claim");
|
|
3111
3719
|
const agentId = option("--agent") ?? (await defaultDevAgentId(runtime));
|
|
3112
3720
|
print(
|
|
3113
3721
|
await runtime.client.mutation(api.functions.agentWorkspace.devClaimTask, {
|
|
@@ -3135,6 +3743,7 @@ async function main() {
|
|
|
3135
3743
|
);
|
|
3136
3744
|
return;
|
|
3137
3745
|
}
|
|
3746
|
+
rejectApiKeyForAgentCommand(runtime, "dench log");
|
|
3138
3747
|
print(
|
|
3139
3748
|
await runtime.client.mutation(api.functions.agentWorkspace.devAppendLog, {
|
|
3140
3749
|
...runtime.workspaceArgs,
|
|
@@ -3164,6 +3773,7 @@ async function main() {
|
|
|
3164
3773
|
);
|
|
3165
3774
|
return;
|
|
3166
3775
|
}
|
|
3776
|
+
rejectApiKeyForAgentCommand(runtime, "dench approval request");
|
|
3167
3777
|
const approvalId = await runtime.client.mutation(
|
|
3168
3778
|
api.functions.agentWorkspace.devRequestApproval,
|
|
3169
3779
|
{
|