@dench.com/cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dench.ts +60 -476
- 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;
|
|
@@ -594,16 +581,22 @@ function toolHelp() {
|
|
|
594
581
|
Usage:
|
|
595
582
|
dench apps [--json]
|
|
596
583
|
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 <
|
|
584
|
+
dench tool connect <toolkit> [--callback-url <url>] [--json]
|
|
585
|
+
dench tool search "create github issue" [--toolkit github] [--limit 20] [--json]
|
|
586
|
+
dench tool run <composio_tool_slug> --args '{"key":"value"}' [--account <connectedAccountId>] [--json]
|
|
587
|
+
dench tool disconnect <connectionId> [--json]
|
|
588
|
+
|
|
589
|
+
Auth:
|
|
590
|
+
All commands authenticate with DENCH_API_KEY (Bearer) directly to the
|
|
591
|
+
Dench Cloud Gateway. No \`dench login\` agent session is required —
|
|
592
|
+
inside a Dench sandbox the key is baked into the env automatically;
|
|
593
|
+
outside, export DENCH_API_KEY=<key> first.
|
|
600
594
|
|
|
601
595
|
Notes:
|
|
602
|
-
dench apps
|
|
603
|
-
dench tool
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
Dench enforces the final policy and returns requiresApproval when approval is needed.
|
|
596
|
+
dench apps is an alias for dench tool status.
|
|
597
|
+
dench tool connect prints the OAuth redirect URL for the human to open.
|
|
598
|
+
dench tool run output is redacted for display; pass --json for raw JSON.
|
|
599
|
+
Override the gateway base with DENCH_GATEWAY_URL or GATEWAY_URL.
|
|
607
600
|
`);
|
|
608
601
|
}
|
|
609
602
|
|
|
@@ -745,8 +738,10 @@ function appsHelp() {
|
|
|
745
738
|
Usage:
|
|
746
739
|
dench apps [--json]
|
|
747
740
|
|
|
748
|
-
Lists all
|
|
749
|
-
|
|
741
|
+
Lists all external apps connected to this organization (Slack, Gmail,
|
|
742
|
+
GitHub, etc.). Authenticates with DENCH_API_KEY (Bearer) directly to the
|
|
743
|
+
Dench Cloud Gateway — no \`dench login\` agent session required. Alias
|
|
744
|
+
for \`dench tool status\`.
|
|
750
745
|
`);
|
|
751
746
|
}
|
|
752
747
|
|
|
@@ -1899,10 +1894,7 @@ function requireSessionRuntime(
|
|
|
1899
1894
|
* (`dench_*` / `gw_sk_*` / etc.) baked into sandbox envVars. The
|
|
1900
1895
|
* server-side `requireCrmAccess` helper dispatches on the prefix.
|
|
1901
1896
|
*/
|
|
1902
|
-
type AuthenticatedRuntime = Extract<
|
|
1903
|
-
Runtime,
|
|
1904
|
-
{ mode: "session" | "apiKey" }
|
|
1905
|
-
>;
|
|
1897
|
+
type AuthenticatedRuntime = Extract<Runtime, { mode: "session" | "apiKey" }>;
|
|
1906
1898
|
|
|
1907
1899
|
function requireAuthenticatedRuntime(
|
|
1908
1900
|
runtime: Runtime,
|
|
@@ -1950,25 +1942,6 @@ function rejectApiKeyForAgentCommand(
|
|
|
1950
1942
|
}
|
|
1951
1943
|
}
|
|
1952
1944
|
|
|
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
1945
|
function asRecord(value: unknown): JsonRecord | undefined {
|
|
1973
1946
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1974
1947
|
return undefined;
|
|
@@ -2004,7 +1977,7 @@ function numberField(record: JsonRecord | undefined, ...names: string[]) {
|
|
|
2004
1977
|
return undefined;
|
|
2005
1978
|
}
|
|
2006
1979
|
|
|
2007
|
-
function
|
|
1980
|
+
function _booleanField(record: JsonRecord | undefined, ...names: string[]) {
|
|
2008
1981
|
if (!record) return undefined;
|
|
2009
1982
|
for (const name of names) {
|
|
2010
1983
|
const value = record[name];
|
|
@@ -2027,94 +2000,6 @@ function isMissingConvexFunction(error: unknown) {
|
|
|
2027
2000
|
return /Could not find public function/i.test(errorMessage(error));
|
|
2028
2001
|
}
|
|
2029
2002
|
|
|
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
2003
|
function workspaceSlugFromContext(context: JsonRecord) {
|
|
2119
2004
|
const workspace = asRecord(context.workspace);
|
|
2120
2005
|
return (
|
|
@@ -2141,41 +2026,12 @@ function nextContextCommands() {
|
|
|
2141
2026
|
];
|
|
2142
2027
|
}
|
|
2143
2028
|
|
|
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
|
-
}
|
|
2029
|
+
async function connectedAppsContext(_runtime: Runtime) {
|
|
2030
|
+
// Reach the Dench gateway directly with DENCH_API_KEY (mirrors the
|
|
2031
|
+
// denchclaw pattern). This works for every Runtime mode — session,
|
|
2032
|
+
// apiKey, dev — because the gateway only cares about the bearer key,
|
|
2033
|
+
// not which Convex session is calling.
|
|
2034
|
+
return fetchConnectedAppsSummary();
|
|
2179
2035
|
}
|
|
2180
2036
|
|
|
2181
2037
|
async function buildContext(runtime: Runtime) {
|
|
@@ -2414,223 +2270,6 @@ function formatContext(context: JsonRecord) {
|
|
|
2414
2270
|
return lines.join("\n");
|
|
2415
2271
|
}
|
|
2416
2272
|
|
|
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
2273
|
async function devOverview(runtime: Awaited<ReturnType<typeof getRuntime>>) {
|
|
2635
2274
|
if (runtime.mode !== "dev") {
|
|
2636
2275
|
throw new Error("Not in dev mode");
|
|
@@ -2823,10 +2462,12 @@ async function runFsCommand() {
|
|
|
2823
2462
|
function normalizeFilesPath(input: string): string {
|
|
2824
2463
|
if (!input || input === "/") return "/";
|
|
2825
2464
|
let cleaned = input.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2826
|
-
if (cleaned.startsWith("/workspace/"))
|
|
2465
|
+
if (cleaned.startsWith("/workspace/"))
|
|
2466
|
+
cleaned = cleaned.slice("/workspace".length);
|
|
2827
2467
|
else if (cleaned === "/workspace") cleaned = "/";
|
|
2828
2468
|
if (!cleaned.startsWith("/")) cleaned = `/${cleaned}`;
|
|
2829
|
-
if (cleaned.length > 1 && cleaned.endsWith("/"))
|
|
2469
|
+
if (cleaned.length > 1 && cleaned.endsWith("/"))
|
|
2470
|
+
cleaned = cleaned.slice(0, -1);
|
|
2830
2471
|
return cleaned;
|
|
2831
2472
|
}
|
|
2832
2473
|
|
|
@@ -3004,9 +2645,9 @@ async function runFilesMv() {
|
|
|
3004
2645
|
}
|
|
3005
2646
|
|
|
3006
2647
|
const sourceRows = await listConvexTreeRecursive(runtime, apiKey, from);
|
|
3007
|
-
const sourceSelf = (
|
|
3008
|
-
(
|
|
3009
|
-
);
|
|
2648
|
+
const sourceSelf = (
|
|
2649
|
+
await listConvexTreeAt(runtime, apiKey, parentOfPath(from))
|
|
2650
|
+
).find((row) => row.path === from);
|
|
3010
2651
|
if (!sourceSelf && sourceRows.length === 0) {
|
|
3011
2652
|
throw new CliError(`Source not found in Convex: ${from}`, {
|
|
3012
2653
|
code: "files_mv_not_found",
|
|
@@ -3068,7 +2709,11 @@ async function runFilesRm() {
|
|
|
3068
2709
|
const target = normalizeFilesPath(targetRaw);
|
|
3069
2710
|
const recursive = hasFlag("--recursive") || hasFlag("-r");
|
|
3070
2711
|
|
|
3071
|
-
const parentRows = await listConvexTreeAt(
|
|
2712
|
+
const parentRows = await listConvexTreeAt(
|
|
2713
|
+
runtime,
|
|
2714
|
+
apiKey,
|
|
2715
|
+
parentOfPath(target),
|
|
2716
|
+
);
|
|
3072
2717
|
const self = parentRows.find((row) => row.path === target);
|
|
3073
2718
|
if (!self) {
|
|
3074
2719
|
if (hasFlag("--json")) {
|
|
@@ -3615,6 +3260,25 @@ async function main() {
|
|
|
3615
3260
|
return;
|
|
3616
3261
|
}
|
|
3617
3262
|
|
|
3263
|
+
if (command === "apps") {
|
|
3264
|
+
// `dench apps` is an alias for `dench tool status`. Both speak
|
|
3265
|
+
// straight HTTP to the Dench Cloud Gateway with DENCH_API_KEY —
|
|
3266
|
+
// mirrors the denchclaw pattern. No Convex session involved.
|
|
3267
|
+
await runToolCommand({ args: ["status"], jsonOutput: json });
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
|
|
3271
|
+
if (command === "tool") {
|
|
3272
|
+
// `dench tool {status,connect,search,run,disconnect}` is the model-facing
|
|
3273
|
+
// composio surface and only needs DENCH_API_KEY (Bearer) on the
|
|
3274
|
+
// gateway. Bypasses requireSessionRuntime entirely so it works in
|
|
3275
|
+
// every sandbox / API-key context.
|
|
3276
|
+
const subArgs = stripRuntimeFlags(args.slice(args.indexOf("tool") + 1));
|
|
3277
|
+
const filteredSubArgs = subArgs.filter((a) => a !== "--json");
|
|
3278
|
+
await runToolCommand({ args: filteredSubArgs, jsonOutput: json });
|
|
3279
|
+
return;
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3618
3282
|
if (command === "chat") {
|
|
3619
3283
|
const subArgs = args.slice(args.indexOf("chat") + 1);
|
|
3620
3284
|
const sub = subArgs[0];
|
|
@@ -3865,11 +3529,6 @@ async function main() {
|
|
|
3865
3529
|
return;
|
|
3866
3530
|
}
|
|
3867
3531
|
|
|
3868
|
-
if (command === "apps") {
|
|
3869
|
-
await printToolStatus(runtime);
|
|
3870
|
-
return;
|
|
3871
|
-
}
|
|
3872
|
-
|
|
3873
3532
|
if (command === "context") {
|
|
3874
3533
|
const context = await buildContext(runtime);
|
|
3875
3534
|
print(json ? context : formatContext(context));
|
|
@@ -4022,9 +3681,7 @@ async function main() {
|
|
|
4022
3681
|
return;
|
|
4023
3682
|
}
|
|
4024
3683
|
if (runtime.mode === "apiKey") {
|
|
4025
|
-
throw loginRequiredError(
|
|
4026
|
-
"dench tasks (agent-scoped task list)",
|
|
4027
|
-
);
|
|
3684
|
+
throw loginRequiredError("dench tasks (agent-scoped task list)");
|
|
4028
3685
|
}
|
|
4029
3686
|
const data = await devOverview(runtime);
|
|
4030
3687
|
print(data.tasks);
|
|
@@ -4314,79 +3971,6 @@ async function main() {
|
|
|
4314
3971
|
return;
|
|
4315
3972
|
}
|
|
4316
3973
|
|
|
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
3974
|
if (
|
|
4391
3975
|
command === "approval" &&
|
|
4392
3976
|
(subcommand === "approve" || subcommand === "reject")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Dench agent workspace CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"cron.ts",
|
|
22
22
|
"search.ts",
|
|
23
23
|
"image.ts",
|
|
24
|
+
"tools.ts",
|
|
24
25
|
"agentKind.ts",
|
|
25
26
|
"host.ts",
|
|
26
27
|
"openUrl.ts",
|