@dench.com/cli 0.4.0 → 0.4.2
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/chat-spawn.ts +10 -4
- package/crm.ts +278 -7
- package/dench.ts +63 -477
- package/fs-daemon.ts +386 -71
- package/package.json +2 -1
- package/tools.ts +808 -0
package/dench.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
withCurrentSessionSelection,
|
|
39
39
|
withSavedSession,
|
|
40
40
|
} from "./session";
|
|
41
|
+
import { fetchConnectedAppsSummary, runToolCommand } from "./tools";
|
|
41
42
|
|
|
42
43
|
type JsonRecord = Record<string, unknown>;
|
|
43
44
|
|
|
@@ -172,20 +173,6 @@ const api = {
|
|
|
172
173
|
"functions/agentWorkspace:whatCanIDoHere",
|
|
173
174
|
),
|
|
174
175
|
},
|
|
175
|
-
agentToolsNode: {
|
|
176
|
-
agentToolConnect: makeFunctionReference<"action">(
|
|
177
|
-
"functions/agentToolsNode:agentToolConnect",
|
|
178
|
-
),
|
|
179
|
-
agentToolRun: makeFunctionReference<"action">(
|
|
180
|
-
"functions/agentToolsNode:agentToolRun",
|
|
181
|
-
),
|
|
182
|
-
agentToolSearch: makeFunctionReference<"action">(
|
|
183
|
-
"functions/agentToolsNode:agentToolSearch",
|
|
184
|
-
),
|
|
185
|
-
agentToolStatus: makeFunctionReference<"action">(
|
|
186
|
-
"functions/agentToolsNode:agentToolStatus",
|
|
187
|
-
),
|
|
188
|
-
},
|
|
189
176
|
files: {
|
|
190
177
|
listTree: makeFunctionReference<"query">("functions/files:listTree"),
|
|
191
178
|
deleteFile: makeFunctionReference<"mutation">(
|
|
@@ -362,7 +349,7 @@ function normalizeCliError(error: unknown): CliError {
|
|
|
362
349
|
return new CliError(message);
|
|
363
350
|
}
|
|
364
351
|
|
|
365
|
-
function
|
|
352
|
+
function _parseJsonObjectOption(name: string) {
|
|
366
353
|
const raw = option(name);
|
|
367
354
|
if (!raw) return {};
|
|
368
355
|
let parsed: unknown;
|
|
@@ -460,8 +447,9 @@ Usage:
|
|
|
460
447
|
CRM (Daytona-native rewrite):
|
|
461
448
|
dench crm objects <list|get|create|update|rename|delete> ...
|
|
462
449
|
dench crm fields <list|create|update|delete|reorder> <object> ...
|
|
463
|
-
dench crm entries <list|get|create|update|delete|bulk-delete> <object> ...
|
|
450
|
+
dench crm entries <list|get|create|create-many|update|update-many|delete|bulk-delete> <object> ...
|
|
464
451
|
dench crm cells <get|set|append> <object> <entryId> <field> [value]
|
|
452
|
+
dench crm cells set-many <object> --updates '[...]'
|
|
465
453
|
dench crm query <object> --where '...' --select ... --sort ... --limit N
|
|
466
454
|
dench crm sql 'SELECT "Name" FROM lead WHERE ... LIMIT 10'
|
|
467
455
|
dench crm aggregate <object> --field <name>
|
|
@@ -475,6 +463,7 @@ CRM (Daytona-native rewrite):
|
|
|
475
463
|
dench crm docs <list|create|link>
|
|
476
464
|
dench crm actions <list|run> ...
|
|
477
465
|
dench crm batch --file ops.jsonl
|
|
466
|
+
Batch write helpers: entries create-many/update-many, cells set-many
|
|
478
467
|
Help: dench crm help
|
|
479
468
|
|
|
480
469
|
Live web search (Exa via the Dench Cloud Gateway, auths with DENCH_API_KEY):
|
|
@@ -594,16 +583,22 @@ function toolHelp() {
|
|
|
594
583
|
Usage:
|
|
595
584
|
dench apps [--json]
|
|
596
585
|
dench tool status [toolkit] [--json]
|
|
597
|
-
dench tool connect <toolkit> [--json]
|
|
598
|
-
dench tool search "create github issue" [--toolkit github] [--limit 20] [--
|
|
599
|
-
dench tool run <composio_tool_slug> --args '{"key":"value"}' [--account <
|
|
586
|
+
dench tool connect <toolkit> [--callback-url <url>] [--json]
|
|
587
|
+
dench tool search "create github issue" [--toolkit github] [--limit 20] [--json]
|
|
588
|
+
dench tool run <composio_tool_slug> --args '{"key":"value"}' [--account <connectedAccountId>] [--json]
|
|
589
|
+
dench tool disconnect <connectionId> [--json]
|
|
590
|
+
|
|
591
|
+
Auth:
|
|
592
|
+
All commands authenticate with DENCH_API_KEY (Bearer) directly to the
|
|
593
|
+
Dench Cloud Gateway. No \`dench login\` agent session is required —
|
|
594
|
+
inside a Dench sandbox the key is baked into the env automatically;
|
|
595
|
+
outside, export DENCH_API_KEY=<key> first.
|
|
600
596
|
|
|
601
597
|
Notes:
|
|
602
|
-
dench apps
|
|
603
|
-
dench tool
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
Dench enforces the final policy and returns requiresApproval when approval is needed.
|
|
598
|
+
dench apps is an alias for dench tool status.
|
|
599
|
+
dench tool connect prints the OAuth redirect URL for the human to open.
|
|
600
|
+
dench tool run output is redacted for display; pass --json for raw JSON.
|
|
601
|
+
Override the gateway base with DENCH_GATEWAY_URL or GATEWAY_URL.
|
|
607
602
|
`);
|
|
608
603
|
}
|
|
609
604
|
|
|
@@ -745,8 +740,10 @@ function appsHelp() {
|
|
|
745
740
|
Usage:
|
|
746
741
|
dench apps [--json]
|
|
747
742
|
|
|
748
|
-
Lists all
|
|
749
|
-
|
|
743
|
+
Lists all external apps connected to this organization (Slack, Gmail,
|
|
744
|
+
GitHub, etc.). Authenticates with DENCH_API_KEY (Bearer) directly to the
|
|
745
|
+
Dench Cloud Gateway — no \`dench login\` agent session required. Alias
|
|
746
|
+
for \`dench tool status\`.
|
|
750
747
|
`);
|
|
751
748
|
}
|
|
752
749
|
|
|
@@ -1899,10 +1896,7 @@ function requireSessionRuntime(
|
|
|
1899
1896
|
* (`dench_*` / `gw_sk_*` / etc.) baked into sandbox envVars. The
|
|
1900
1897
|
* server-side `requireCrmAccess` helper dispatches on the prefix.
|
|
1901
1898
|
*/
|
|
1902
|
-
type AuthenticatedRuntime = Extract<
|
|
1903
|
-
Runtime,
|
|
1904
|
-
{ mode: "session" | "apiKey" }
|
|
1905
|
-
>;
|
|
1899
|
+
type AuthenticatedRuntime = Extract<Runtime, { mode: "session" | "apiKey" }>;
|
|
1906
1900
|
|
|
1907
1901
|
function requireAuthenticatedRuntime(
|
|
1908
1902
|
runtime: Runtime,
|
|
@@ -1950,25 +1944,6 @@ function rejectApiKeyForAgentCommand(
|
|
|
1950
1944
|
}
|
|
1951
1945
|
}
|
|
1952
1946
|
|
|
1953
|
-
async function printToolStatus(runtime: Runtime, toolkit?: string) {
|
|
1954
|
-
const sessionRuntime = requireSessionRuntime(runtime, "dench tool status");
|
|
1955
|
-
try {
|
|
1956
|
-
print(
|
|
1957
|
-
await sessionRuntime.client.action(
|
|
1958
|
-
api.functions.agentToolsNode.agentToolStatus,
|
|
1959
|
-
{
|
|
1960
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
1961
|
-
toolkit,
|
|
1962
|
-
},
|
|
1963
|
-
),
|
|
1964
|
-
);
|
|
1965
|
-
} catch (error) {
|
|
1966
|
-
const unavailable = toolGatewayUnavailable(error, { toolkit });
|
|
1967
|
-
if (!unavailable) throw error;
|
|
1968
|
-
print(json ? unavailable : formatToolUnavailable(unavailable));
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
1947
|
function asRecord(value: unknown): JsonRecord | undefined {
|
|
1973
1948
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1974
1949
|
return undefined;
|
|
@@ -2004,7 +1979,7 @@ function numberField(record: JsonRecord | undefined, ...names: string[]) {
|
|
|
2004
1979
|
return undefined;
|
|
2005
1980
|
}
|
|
2006
1981
|
|
|
2007
|
-
function
|
|
1982
|
+
function _booleanField(record: JsonRecord | undefined, ...names: string[]) {
|
|
2008
1983
|
if (!record) return undefined;
|
|
2009
1984
|
for (const name of names) {
|
|
2010
1985
|
const value = record[name];
|
|
@@ -2027,94 +2002,6 @@ function isMissingConvexFunction(error: unknown) {
|
|
|
2027
2002
|
return /Could not find public function/i.test(errorMessage(error));
|
|
2028
2003
|
}
|
|
2029
2004
|
|
|
2030
|
-
function toolGatewayUnavailable(error: unknown, context: JsonRecord = {}) {
|
|
2031
|
-
const message = errorMessage(error);
|
|
2032
|
-
if (!/gateway API key|callGateway|Composio/i.test(message)) {
|
|
2033
|
-
return null;
|
|
2034
|
-
}
|
|
2035
|
-
const invalidKey = /Invalid gateway API key/i.test(message);
|
|
2036
|
-
const query = stringField(context, "query");
|
|
2037
|
-
const toolkit = stringField(context, "toolkit");
|
|
2038
|
-
const retryCommand = query
|
|
2039
|
-
? `Retry dench tool search ${JSON.stringify(query)}${toolkit ? ` --toolkit ${toolkit}` : ""} after the gateway key is fixed.`
|
|
2040
|
-
: "Retry dench apps --json after the gateway key is fixed.";
|
|
2041
|
-
return {
|
|
2042
|
-
ok: false,
|
|
2043
|
-
available: false,
|
|
2044
|
-
code: invalidKey ? "invalid_gateway_api_key" : "tool_gateway_unavailable",
|
|
2045
|
-
reason: invalidKey
|
|
2046
|
-
? "The workspace gateway key is invalid or out of sync."
|
|
2047
|
-
: "The connected-app gateway is unavailable.",
|
|
2048
|
-
...context,
|
|
2049
|
-
nextActions: [
|
|
2050
|
-
"Ask a workspace admin to repair or rotate the Dench gateway key.",
|
|
2051
|
-
retryCommand,
|
|
2052
|
-
],
|
|
2053
|
-
};
|
|
2054
|
-
}
|
|
2055
|
-
|
|
2056
|
-
function formatToolUnavailable(payload: JsonRecord) {
|
|
2057
|
-
return [
|
|
2058
|
-
"Connected apps unavailable.",
|
|
2059
|
-
`Reason: ${stringField(payload, "reason") ?? "tool gateway unavailable"}`,
|
|
2060
|
-
"",
|
|
2061
|
-
"Next actions:",
|
|
2062
|
-
...(
|
|
2063
|
-
(Array.isArray(payload.nextActions)
|
|
2064
|
-
? payload.nextActions
|
|
2065
|
-
: []) as unknown[]
|
|
2066
|
-
)
|
|
2067
|
-
.filter((item): item is string => typeof item === "string")
|
|
2068
|
-
.map((item) => ` - ${item}`),
|
|
2069
|
-
].join("\n");
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
const READ_ONLY_TOOL_ACTIONS = new Set([
|
|
2073
|
-
"FETCH",
|
|
2074
|
-
"GET",
|
|
2075
|
-
"LIST",
|
|
2076
|
-
"SEARCH",
|
|
2077
|
-
"READ",
|
|
2078
|
-
"FIND",
|
|
2079
|
-
]);
|
|
2080
|
-
|
|
2081
|
-
function toolSlugTokens(toolSlug: string) {
|
|
2082
|
-
return toolSlug
|
|
2083
|
-
.toUpperCase()
|
|
2084
|
-
.split(/[^A-Z0-9]+/)
|
|
2085
|
-
.filter(Boolean);
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
function toolApprovalHint(toolSlug: string | undefined) {
|
|
2089
|
-
if (!toolSlug) return "approval unknown";
|
|
2090
|
-
const readOnly = toolSlugTokens(toolSlug).find((token) =>
|
|
2091
|
-
READ_ONLY_TOOL_ACTIONS.has(token),
|
|
2092
|
-
);
|
|
2093
|
-
return readOnly ? `read-only (${readOnly})` : "approval likely";
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
function compactConnection(connection: JsonRecord) {
|
|
2097
|
-
return {
|
|
2098
|
-
toolkit: stringField(connection, "toolkit") ?? "unknown",
|
|
2099
|
-
status: stringField(connection, "status") ?? "unknown",
|
|
2100
|
-
connectedAccountId:
|
|
2101
|
-
stringField(connection, "connectedAccountId", "connected_account_id") ??
|
|
2102
|
-
stringField(connection, "id"),
|
|
2103
|
-
accountLabel:
|
|
2104
|
-
stringField(connection, "accountLabel", "account_label", "label") ?? null,
|
|
2105
|
-
};
|
|
2106
|
-
}
|
|
2107
|
-
|
|
2108
|
-
function summarizeConnectedApps(value: unknown) {
|
|
2109
|
-
const record = asRecord(value);
|
|
2110
|
-
const connections = asRecordArray(record?.connections).map(compactConnection);
|
|
2111
|
-
return {
|
|
2112
|
-
available: true,
|
|
2113
|
-
count: connections.length,
|
|
2114
|
-
connections,
|
|
2115
|
-
};
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
2005
|
function workspaceSlugFromContext(context: JsonRecord) {
|
|
2119
2006
|
const workspace = asRecord(context.workspace);
|
|
2120
2007
|
return (
|
|
@@ -2141,41 +2028,12 @@ function nextContextCommands() {
|
|
|
2141
2028
|
];
|
|
2142
2029
|
}
|
|
2143
2030
|
|
|
2144
|
-
async function connectedAppsContext(
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
connections: [],
|
|
2151
|
-
};
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
try {
|
|
2155
|
-
return summarizeConnectedApps(
|
|
2156
|
-
await runtime.client.action(
|
|
2157
|
-
api.functions.agentToolsNode.agentToolStatus,
|
|
2158
|
-
{
|
|
2159
|
-
sessionToken: runtime.sessionToken,
|
|
2160
|
-
},
|
|
2161
|
-
),
|
|
2162
|
-
);
|
|
2163
|
-
} catch (error) {
|
|
2164
|
-
const unavailable = toolGatewayUnavailable(error);
|
|
2165
|
-
if (unavailable) {
|
|
2166
|
-
return {
|
|
2167
|
-
...unavailable,
|
|
2168
|
-
count: 0,
|
|
2169
|
-
connections: [],
|
|
2170
|
-
};
|
|
2171
|
-
}
|
|
2172
|
-
return {
|
|
2173
|
-
available: false,
|
|
2174
|
-
error: errorMessage(error),
|
|
2175
|
-
count: 0,
|
|
2176
|
-
connections: [],
|
|
2177
|
-
};
|
|
2178
|
-
}
|
|
2031
|
+
async function connectedAppsContext(_runtime: Runtime) {
|
|
2032
|
+
// Reach the Dench gateway directly with DENCH_API_KEY (mirrors the
|
|
2033
|
+
// denchclaw pattern). This works for every Runtime mode — session,
|
|
2034
|
+
// apiKey, dev — because the gateway only cares about the bearer key,
|
|
2035
|
+
// not which Convex session is calling.
|
|
2036
|
+
return fetchConnectedAppsSummary();
|
|
2179
2037
|
}
|
|
2180
2038
|
|
|
2181
2039
|
async function buildContext(runtime: Runtime) {
|
|
@@ -2414,223 +2272,6 @@ function formatContext(context: JsonRecord) {
|
|
|
2414
2272
|
return lines.join("\n");
|
|
2415
2273
|
}
|
|
2416
2274
|
|
|
2417
|
-
function toolkitSlugFromTool(tool: JsonRecord) {
|
|
2418
|
-
const toolkit = tool.toolkit;
|
|
2419
|
-
if (typeof toolkit === "string") return toolkit;
|
|
2420
|
-
const toolkitRecord = asRecord(toolkit);
|
|
2421
|
-
return (
|
|
2422
|
-
stringField(toolkitRecord, "slug", "name") ??
|
|
2423
|
-
stringField(tool, "toolkit_slug")
|
|
2424
|
-
);
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
function connectedToolkitSet(data: JsonRecord | undefined) {
|
|
2428
|
-
const connected = new Set<string>();
|
|
2429
|
-
const connectedToolkits = Array.isArray(data?.connected_toolkits)
|
|
2430
|
-
? data?.connected_toolkits
|
|
2431
|
-
: [];
|
|
2432
|
-
for (const toolkit of connectedToolkits) {
|
|
2433
|
-
const value = stringValue(toolkit);
|
|
2434
|
-
if (value) connected.add(value.toLowerCase());
|
|
2435
|
-
}
|
|
2436
|
-
for (const status of asRecordArray(data?.toolkit_connection_statuses)) {
|
|
2437
|
-
const toolkit = stringField(status, "toolkit");
|
|
2438
|
-
const isConnected = booleanField(status, "has_active_connection");
|
|
2439
|
-
if (toolkit && isConnected) connected.add(toolkit.toLowerCase());
|
|
2440
|
-
}
|
|
2441
|
-
return connected;
|
|
2442
|
-
}
|
|
2443
|
-
|
|
2444
|
-
function searchToolItems(searchResult: unknown) {
|
|
2445
|
-
const root = asRecord(searchResult);
|
|
2446
|
-
const data = asRecord(root?.data) ?? root;
|
|
2447
|
-
const directItems = asRecordArray(data?.items);
|
|
2448
|
-
if (directItems.length > 0) {
|
|
2449
|
-
return { data, items: directItems };
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
const schemas = asRecord(data?.tool_schemas);
|
|
2453
|
-
if (!schemas) return { data, items: [] };
|
|
2454
|
-
return {
|
|
2455
|
-
data,
|
|
2456
|
-
items: Object.entries(schemas).flatMap<JsonRecord>(([slug, value]) => {
|
|
2457
|
-
const tool = asRecord(value);
|
|
2458
|
-
return tool
|
|
2459
|
-
? [{ ...tool, tool_slug: stringField(tool, "tool_slug") ?? slug }]
|
|
2460
|
-
: [];
|
|
2461
|
-
}),
|
|
2462
|
-
};
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
function compactToolSearchResult(searchResult: unknown) {
|
|
2466
|
-
const root = asRecord(searchResult);
|
|
2467
|
-
const { data, items } = searchToolItems(searchResult);
|
|
2468
|
-
const connected = connectedToolkitSet(data);
|
|
2469
|
-
const tools = items.map((tool) => {
|
|
2470
|
-
const toolkit = toolkitSlugFromTool(tool);
|
|
2471
|
-
const connectionStatus = asRecord(tool.connection_status);
|
|
2472
|
-
const isConnected =
|
|
2473
|
-
booleanField(connectionStatus, "is_connected") ??
|
|
2474
|
-
(toolkit ? connected.has(toolkit.toLowerCase()) : undefined);
|
|
2475
|
-
const slug = stringField(tool, "slug", "tool_slug") ?? "unknown_tool";
|
|
2476
|
-
return {
|
|
2477
|
-
slug,
|
|
2478
|
-
name: stringField(tool, "display_name", "name") ?? slug,
|
|
2479
|
-
toolkit: toolkit ?? "unknown",
|
|
2480
|
-
approval: toolApprovalHint(slug),
|
|
2481
|
-
connected:
|
|
2482
|
-
isConnected === undefined
|
|
2483
|
-
? "connection unknown"
|
|
2484
|
-
: isConnected
|
|
2485
|
-
? "connected"
|
|
2486
|
-
: "not connected",
|
|
2487
|
-
description: compactLine(tool.description) ?? null,
|
|
2488
|
-
};
|
|
2489
|
-
});
|
|
2490
|
-
|
|
2491
|
-
return {
|
|
2492
|
-
ok: root?.ok ?? true,
|
|
2493
|
-
query: stringField(root, "query") ?? stringField(data, "query") ?? null,
|
|
2494
|
-
returned: tools.length,
|
|
2495
|
-
total:
|
|
2496
|
-
numberField(data, "total_items", "total", "count") ??
|
|
2497
|
-
numberField(root, "total_items", "total", "count") ??
|
|
2498
|
-
tools.length,
|
|
2499
|
-
tools,
|
|
2500
|
-
};
|
|
2501
|
-
}
|
|
2502
|
-
|
|
2503
|
-
function formatToolSearchResult(searchResult: unknown) {
|
|
2504
|
-
const summary = compactToolSearchResult(searchResult);
|
|
2505
|
-
const query = summary.query ? ` for "${summary.query}"` : "";
|
|
2506
|
-
if (summary.tools.length === 0) {
|
|
2507
|
-
return `No matching tools found${query}.\nUse --json if you need the raw provider response.`;
|
|
2508
|
-
}
|
|
2509
|
-
|
|
2510
|
-
const lines = [`Top tools${query}:`];
|
|
2511
|
-
for (const [index, tool] of summary.tools.entries()) {
|
|
2512
|
-
lines.push(
|
|
2513
|
-
`${index + 1}. ${tool.slug} - ${tool.name}`,
|
|
2514
|
-
` toolkit: ${tool.toolkit} | ${tool.approval} | ${tool.connected}`,
|
|
2515
|
-
);
|
|
2516
|
-
if (tool.description) {
|
|
2517
|
-
lines.push(` ${tool.description}`);
|
|
2518
|
-
}
|
|
2519
|
-
}
|
|
2520
|
-
lines.push(
|
|
2521
|
-
"",
|
|
2522
|
-
"Use dench tool run <slug> --args '{...}' when ready.",
|
|
2523
|
-
"Use --json only when you need full schemas or raw arguments.",
|
|
2524
|
-
);
|
|
2525
|
-
return lines.join("\n");
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
function printToolSearchResult(searchResult: unknown) {
|
|
2529
|
-
print(json ? searchResult : formatToolSearchResult(searchResult));
|
|
2530
|
-
}
|
|
2531
|
-
|
|
2532
|
-
const SENSITIVE_KEY_RE =
|
|
2533
|
-
/(?:otp|oneTime|verification|security|passcode|password|secret|api[_-]?key|access[_-]?token|refresh[_-]?token|authorization)/i;
|
|
2534
|
-
|
|
2535
|
-
function redactSensitiveString(value: string) {
|
|
2536
|
-
return value
|
|
2537
|
-
.replace(
|
|
2538
|
-
/\b((?:one[-\s]?time|verification|security|login|sign[-\s]?in|auth(?:entication)?|2fa|mfa|otp|passcode|code)[^.\n\r]{0,80}?)(\d[\d\s-]{2,10}\d)\b/gi,
|
|
2539
|
-
"$1[redacted-code]",
|
|
2540
|
-
)
|
|
2541
|
-
.replace(
|
|
2542
|
-
/\b((?:api[_\s-]?key|secret|token|password)[^.\n\r]{0,60}?)([A-Za-z0-9_=-]{12,})\b/gi,
|
|
2543
|
-
"$1[redacted-secret]",
|
|
2544
|
-
);
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
function redactSensitiveDisplay(value: unknown, key?: string): unknown {
|
|
2548
|
-
if (typeof value === "string") {
|
|
2549
|
-
return key && SENSITIVE_KEY_RE.test(key)
|
|
2550
|
-
? "[redacted]"
|
|
2551
|
-
: redactSensitiveString(value);
|
|
2552
|
-
}
|
|
2553
|
-
if (typeof value === "number" && key && SENSITIVE_KEY_RE.test(key)) {
|
|
2554
|
-
return "[redacted]";
|
|
2555
|
-
}
|
|
2556
|
-
if (Array.isArray(value)) {
|
|
2557
|
-
return value.map((item) => redactSensitiveDisplay(item));
|
|
2558
|
-
}
|
|
2559
|
-
const record = asRecord(value);
|
|
2560
|
-
if (!record) return value;
|
|
2561
|
-
|
|
2562
|
-
return Object.fromEntries(
|
|
2563
|
-
Object.entries(record).map(([entryKey, entryValue]) => [
|
|
2564
|
-
entryKey,
|
|
2565
|
-
redactSensitiveDisplay(entryValue, entryKey),
|
|
2566
|
-
]),
|
|
2567
|
-
);
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
function connectionUrlFromToolConnect(result: unknown) {
|
|
2571
|
-
const record = asRecord(result);
|
|
2572
|
-
const data = asRecord(record?.data);
|
|
2573
|
-
return (
|
|
2574
|
-
stringField(record, "connectUrl", "redirect_url", "redirectUrl", "url") ??
|
|
2575
|
-
stringField(data, "redirect_url", "redirectUrl", "url")
|
|
2576
|
-
);
|
|
2577
|
-
}
|
|
2578
|
-
|
|
2579
|
-
function withToolConnectSummary(result: unknown) {
|
|
2580
|
-
const record = asRecord(result);
|
|
2581
|
-
if (!record) return result;
|
|
2582
|
-
const toolkit = stringField(record, "toolkit") ?? "app";
|
|
2583
|
-
const connectUrl = connectionUrlFromToolConnect(record);
|
|
2584
|
-
if (!connectUrl) return record;
|
|
2585
|
-
return {
|
|
2586
|
-
...record,
|
|
2587
|
-
connectUrl,
|
|
2588
|
-
linkText: `Connect ${toolkit} here`,
|
|
2589
|
-
message: `Connect ${toolkit} here: ${connectUrl}`,
|
|
2590
|
-
};
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
function formatToolConnectResult(result: unknown) {
|
|
2594
|
-
const record = asRecord(withToolConnectSummary(result));
|
|
2595
|
-
if (!record) return String(result);
|
|
2596
|
-
const toolkit = stringField(record, "toolkit") ?? "app";
|
|
2597
|
-
const connectUrl = stringField(record, "connectUrl");
|
|
2598
|
-
if (connectUrl) {
|
|
2599
|
-
return `Connect ${toolkit} here: ${connectUrl}`;
|
|
2600
|
-
}
|
|
2601
|
-
return JSON.stringify(record, null, 2);
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
function withToolRunGuidance(runtime: Runtime, result: unknown) {
|
|
2605
|
-
const record = asRecord(result);
|
|
2606
|
-
if (!record || record.requiresApproval !== true) return result;
|
|
2607
|
-
const approvalId = stringField(record, "approvalId");
|
|
2608
|
-
const statusContext =
|
|
2609
|
-
runtime.mode === "session" || runtime.mode === "apiKey"
|
|
2610
|
-
? workspaceUrls(runtime.host, runtime.organization?.slug)
|
|
2611
|
-
: {};
|
|
2612
|
-
const approvalCommand = approvalId
|
|
2613
|
-
? `dench approval approve ${approvalId} --evidence "User said yes in chat" --json`
|
|
2614
|
-
: undefined;
|
|
2615
|
-
return {
|
|
2616
|
-
...record,
|
|
2617
|
-
...statusContext,
|
|
2618
|
-
...(approvalCommand ? { approvalCommand } : {}),
|
|
2619
|
-
nextActions: [
|
|
2620
|
-
"Ask the human for an explicit yes/no in chat.",
|
|
2621
|
-
...(approvalCommand ? [`If yes, run: ${approvalCommand}`] : []),
|
|
2622
|
-
approvalId
|
|
2623
|
-
? `Rerun the exact same dench tool run command with --approval ${approvalId}.`
|
|
2624
|
-
: "Rerun the exact same dench tool run command after approval.",
|
|
2625
|
-
],
|
|
2626
|
-
};
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
|
-
function printToolRunResult(runtime: Runtime, result: unknown) {
|
|
2630
|
-
const guided = withToolRunGuidance(runtime, result);
|
|
2631
|
-
print(json ? guided : redactSensitiveDisplay(guided));
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
2275
|
async function devOverview(runtime: Awaited<ReturnType<typeof getRuntime>>) {
|
|
2635
2276
|
if (runtime.mode !== "dev") {
|
|
2636
2277
|
throw new Error("Not in dev mode");
|
|
@@ -2823,10 +2464,12 @@ async function runFsCommand() {
|
|
|
2823
2464
|
function normalizeFilesPath(input: string): string {
|
|
2824
2465
|
if (!input || input === "/") return "/";
|
|
2825
2466
|
let cleaned = input.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2826
|
-
if (cleaned.startsWith("/workspace/"))
|
|
2467
|
+
if (cleaned.startsWith("/workspace/"))
|
|
2468
|
+
cleaned = cleaned.slice("/workspace".length);
|
|
2827
2469
|
else if (cleaned === "/workspace") cleaned = "/";
|
|
2828
2470
|
if (!cleaned.startsWith("/")) cleaned = `/${cleaned}`;
|
|
2829
|
-
if (cleaned.length > 1 && cleaned.endsWith("/"))
|
|
2471
|
+
if (cleaned.length > 1 && cleaned.endsWith("/"))
|
|
2472
|
+
cleaned = cleaned.slice(0, -1);
|
|
2830
2473
|
return cleaned;
|
|
2831
2474
|
}
|
|
2832
2475
|
|
|
@@ -3004,9 +2647,9 @@ async function runFilesMv() {
|
|
|
3004
2647
|
}
|
|
3005
2648
|
|
|
3006
2649
|
const sourceRows = await listConvexTreeRecursive(runtime, apiKey, from);
|
|
3007
|
-
const sourceSelf = (
|
|
3008
|
-
(
|
|
3009
|
-
);
|
|
2650
|
+
const sourceSelf = (
|
|
2651
|
+
await listConvexTreeAt(runtime, apiKey, parentOfPath(from))
|
|
2652
|
+
).find((row) => row.path === from);
|
|
3010
2653
|
if (!sourceSelf && sourceRows.length === 0) {
|
|
3011
2654
|
throw new CliError(`Source not found in Convex: ${from}`, {
|
|
3012
2655
|
code: "files_mv_not_found",
|
|
@@ -3068,7 +2711,11 @@ async function runFilesRm() {
|
|
|
3068
2711
|
const target = normalizeFilesPath(targetRaw);
|
|
3069
2712
|
const recursive = hasFlag("--recursive") || hasFlag("-r");
|
|
3070
2713
|
|
|
3071
|
-
const parentRows = await listConvexTreeAt(
|
|
2714
|
+
const parentRows = await listConvexTreeAt(
|
|
2715
|
+
runtime,
|
|
2716
|
+
apiKey,
|
|
2717
|
+
parentOfPath(target),
|
|
2718
|
+
);
|
|
3072
2719
|
const self = parentRows.find((row) => row.path === target);
|
|
3073
2720
|
if (!self) {
|
|
3074
2721
|
if (hasFlag("--json")) {
|
|
@@ -3615,6 +3262,25 @@ async function main() {
|
|
|
3615
3262
|
return;
|
|
3616
3263
|
}
|
|
3617
3264
|
|
|
3265
|
+
if (command === "apps") {
|
|
3266
|
+
// `dench apps` is an alias for `dench tool status`. Both speak
|
|
3267
|
+
// straight HTTP to the Dench Cloud Gateway with DENCH_API_KEY —
|
|
3268
|
+
// mirrors the denchclaw pattern. No Convex session involved.
|
|
3269
|
+
await runToolCommand({ args: ["status"], jsonOutput: json });
|
|
3270
|
+
return;
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
if (command === "tool") {
|
|
3274
|
+
// `dench tool {status,connect,search,run,disconnect}` is the model-facing
|
|
3275
|
+
// composio surface and only needs DENCH_API_KEY (Bearer) on the
|
|
3276
|
+
// gateway. Bypasses requireSessionRuntime entirely so it works in
|
|
3277
|
+
// every sandbox / API-key context.
|
|
3278
|
+
const subArgs = stripRuntimeFlags(args.slice(args.indexOf("tool") + 1));
|
|
3279
|
+
const filteredSubArgs = subArgs.filter((a) => a !== "--json");
|
|
3280
|
+
await runToolCommand({ args: filteredSubArgs, jsonOutput: json });
|
|
3281
|
+
return;
|
|
3282
|
+
}
|
|
3283
|
+
|
|
3618
3284
|
if (command === "chat") {
|
|
3619
3285
|
const subArgs = args.slice(args.indexOf("chat") + 1);
|
|
3620
3286
|
const sub = subArgs[0];
|
|
@@ -3865,11 +3531,6 @@ async function main() {
|
|
|
3865
3531
|
return;
|
|
3866
3532
|
}
|
|
3867
3533
|
|
|
3868
|
-
if (command === "apps") {
|
|
3869
|
-
await printToolStatus(runtime);
|
|
3870
|
-
return;
|
|
3871
|
-
}
|
|
3872
|
-
|
|
3873
3534
|
if (command === "context") {
|
|
3874
3535
|
const context = await buildContext(runtime);
|
|
3875
3536
|
print(json ? context : formatContext(context));
|
|
@@ -4022,9 +3683,7 @@ async function main() {
|
|
|
4022
3683
|
return;
|
|
4023
3684
|
}
|
|
4024
3685
|
if (runtime.mode === "apiKey") {
|
|
4025
|
-
throw loginRequiredError(
|
|
4026
|
-
"dench tasks (agent-scoped task list)",
|
|
4027
|
-
);
|
|
3686
|
+
throw loginRequiredError("dench tasks (agent-scoped task list)");
|
|
4028
3687
|
}
|
|
4029
3688
|
const data = await devOverview(runtime);
|
|
4030
3689
|
print(data.tasks);
|
|
@@ -4314,79 +3973,6 @@ async function main() {
|
|
|
4314
3973
|
return;
|
|
4315
3974
|
}
|
|
4316
3975
|
|
|
4317
|
-
if (command === "tool") {
|
|
4318
|
-
const sessionRuntime = requireSessionRuntime(runtime, "dench tool");
|
|
4319
|
-
|
|
4320
|
-
if (subcommand === "status") {
|
|
4321
|
-
await printToolStatus(sessionRuntime, positional(2));
|
|
4322
|
-
return;
|
|
4323
|
-
}
|
|
4324
|
-
|
|
4325
|
-
if (subcommand === "connect") {
|
|
4326
|
-
const toolkit = positional(2);
|
|
4327
|
-
if (!toolkit) throw new Error("Missing toolkit");
|
|
4328
|
-
const result = await sessionRuntime.client.action(
|
|
4329
|
-
api.functions.agentToolsNode.agentToolConnect,
|
|
4330
|
-
{
|
|
4331
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
4332
|
-
toolkit,
|
|
4333
|
-
callbackUrl: option("--callback-url"),
|
|
4334
|
-
},
|
|
4335
|
-
);
|
|
4336
|
-
print(
|
|
4337
|
-
json ? withToolConnectSummary(result) : formatToolConnectResult(result),
|
|
4338
|
-
);
|
|
4339
|
-
return;
|
|
4340
|
-
}
|
|
4341
|
-
|
|
4342
|
-
if (subcommand === "search") {
|
|
4343
|
-
const query = positional(2);
|
|
4344
|
-
if (!query) throw new Error("Missing search query");
|
|
4345
|
-
try {
|
|
4346
|
-
printToolSearchResult(
|
|
4347
|
-
await sessionRuntime.client.action(
|
|
4348
|
-
api.functions.agentToolsNode.agentToolSearch,
|
|
4349
|
-
{
|
|
4350
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
4351
|
-
query,
|
|
4352
|
-
toolkit: option("--toolkit"),
|
|
4353
|
-
limit: parseNumberOption("--limit"),
|
|
4354
|
-
},
|
|
4355
|
-
),
|
|
4356
|
-
);
|
|
4357
|
-
} catch (error) {
|
|
4358
|
-
const unavailable = toolGatewayUnavailable(error, {
|
|
4359
|
-
query,
|
|
4360
|
-
toolkit: option("--toolkit"),
|
|
4361
|
-
});
|
|
4362
|
-
if (!unavailable) throw error;
|
|
4363
|
-
print(json ? unavailable : formatToolUnavailable(unavailable));
|
|
4364
|
-
}
|
|
4365
|
-
return;
|
|
4366
|
-
}
|
|
4367
|
-
|
|
4368
|
-
if (subcommand === "run") {
|
|
4369
|
-
const toolSlug = positional(2);
|
|
4370
|
-
if (!toolSlug) throw new Error("Missing Composio tool slug");
|
|
4371
|
-
printToolRunResult(
|
|
4372
|
-
sessionRuntime,
|
|
4373
|
-
await sessionRuntime.client.action(
|
|
4374
|
-
api.functions.agentToolsNode.agentToolRun,
|
|
4375
|
-
{
|
|
4376
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
4377
|
-
toolSlug,
|
|
4378
|
-
arguments: parseJsonObjectOption("--args"),
|
|
4379
|
-
connectedAccountId: option("--account"),
|
|
4380
|
-
approvalId: option("--approval") as never,
|
|
4381
|
-
},
|
|
4382
|
-
),
|
|
4383
|
-
);
|
|
4384
|
-
return;
|
|
4385
|
-
}
|
|
4386
|
-
|
|
4387
|
-
throw new Error(`Unknown tool command: ${filteredArgs.join(" ")}`);
|
|
4388
|
-
}
|
|
4389
|
-
|
|
4390
3976
|
if (
|
|
4391
3977
|
command === "approval" &&
|
|
4392
3978
|
(subcommand === "approve" || subcommand === "reject")
|