@dench.com/cli 0.3.8 → 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/chat.ts +215 -8
- package/cron.ts +8 -4
- package/dench.ts +166 -503
- 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;
|
|
@@ -492,6 +479,10 @@ Past chat threads (own + shared in this workspace):
|
|
|
492
479
|
dench chat list [--query <substr>] [--limit N] [--include-archived] [--json]
|
|
493
480
|
dench chat search "<query>" [--limit N] [--include-current] [--json]
|
|
494
481
|
dench chat read <thread-id> [--limit N] [--json]
|
|
482
|
+
dench chat rename <thread-id> <new-title> [--json]
|
|
483
|
+
dench chat rename --from-stdin [--json] (batch via JSON {items:[...]})
|
|
484
|
+
dench chat delete <thread-id> [<thread-id>...] [--yes] [--json]
|
|
485
|
+
dench chat delete --ids id1,id2 [--yes] [--json] (PERMANENTLY deletes threads)
|
|
495
486
|
|
|
496
487
|
Spawn a new chat-turn workflow (same as the web UI):
|
|
497
488
|
dench chat new "<prompt>" [--model <m>] [--file-context <path>] [--visibility private|shared] [--title T] [--yolo] [--follow] [--json]
|
|
@@ -534,6 +525,20 @@ Subagents (Daytona-native rewrite):
|
|
|
534
525
|
|
|
535
526
|
dench --version
|
|
536
527
|
|
|
528
|
+
Self-updating agent harness (OpenClaw parity):
|
|
529
|
+
dench identity show | edit IDENTITY.md (org-wide)
|
|
530
|
+
dench organisation show | edit ORGANISATION.md
|
|
531
|
+
dench user show | edit USER.md (per-member)
|
|
532
|
+
dench tools show | edit TOOLS.md notes section
|
|
533
|
+
dench mem show | append "<text>" | set MEMORY.md aggregate
|
|
534
|
+
dench heartbeat status | enable | disable
|
|
535
|
+
| interval <30m|1h|…> | instructions
|
|
536
|
+
dench bootstrap status | complete | reopen | template
|
|
537
|
+
dench model default <id|clear>
|
|
538
|
+
dench model thread <threadId> <id|clear>
|
|
539
|
+
dench daily today | show <YYYY-MM-DD>
|
|
540
|
+
Help: dench identity help
|
|
541
|
+
|
|
537
542
|
External tools:
|
|
538
543
|
dench apps is an alias for dench tool status -- it lists connected apps.
|
|
539
544
|
dench tool with no subcommand prints tool help.
|
|
@@ -576,16 +581,22 @@ function toolHelp() {
|
|
|
576
581
|
Usage:
|
|
577
582
|
dench apps [--json]
|
|
578
583
|
dench tool status [toolkit] [--json]
|
|
579
|
-
dench tool connect <toolkit> [--json]
|
|
580
|
-
dench tool search "create github issue" [--toolkit github] [--limit 20] [--
|
|
581
|
-
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.
|
|
582
594
|
|
|
583
595
|
Notes:
|
|
584
|
-
dench apps
|
|
585
|
-
dench tool
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
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.
|
|
589
600
|
`);
|
|
590
601
|
}
|
|
591
602
|
|
|
@@ -727,8 +738,10 @@ function appsHelp() {
|
|
|
727
738
|
Usage:
|
|
728
739
|
dench apps [--json]
|
|
729
740
|
|
|
730
|
-
Lists all
|
|
731
|
-
|
|
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\`.
|
|
732
745
|
`);
|
|
733
746
|
}
|
|
734
747
|
|
|
@@ -1794,30 +1807,6 @@ async function getRuntime() {
|
|
|
1794
1807
|
});
|
|
1795
1808
|
}
|
|
1796
1809
|
|
|
1797
|
-
// Sandbox session-mode path: workflow minting provisions a real
|
|
1798
|
-
// dch_agent_* token at sandbox-create time. Prefer it over apiKey so
|
|
1799
|
-
// agentWorkspace.* commands (Composio tools) work inside sandboxes.
|
|
1800
|
-
const sandboxAgentToken = process.env.DENCH_AGENT_SESSION_TOKEN?.trim();
|
|
1801
|
-
const sandboxConvexUrl = resolveApiKeyConvexUrl();
|
|
1802
|
-
if (sandboxAgentToken && sandboxConvexUrl) {
|
|
1803
|
-
const orgId = process.env.DENCH_ORG_ID?.trim();
|
|
1804
|
-
const orgSlug = process.env.DENCH_ORG_SLUG?.trim();
|
|
1805
|
-
const apiHost =
|
|
1806
|
-
process.env.DENCH_API_URL?.trim() ||
|
|
1807
|
-
process.env.DENCH_INTERNAL_BASE?.trim() ||
|
|
1808
|
-
host;
|
|
1809
|
-
return {
|
|
1810
|
-
mode: "session" as const,
|
|
1811
|
-
host: apiHost,
|
|
1812
|
-
client: new ConvexHttpClient(sandboxConvexUrl),
|
|
1813
|
-
sessionToken: sandboxAgentToken,
|
|
1814
|
-
organization:
|
|
1815
|
-
orgId && orgSlug
|
|
1816
|
-
? { id: orgId, name: orgSlug, slug: orgSlug }
|
|
1817
|
-
: undefined,
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
1810
|
// Sandbox / automation path: the unified Dench API key + Convex URL
|
|
1822
1811
|
// baked into sandbox envVars (see
|
|
1823
1812
|
// src/lib/secrets/sandbox-tokens.ts) is enough to drive every
|
|
@@ -1905,10 +1894,7 @@ function requireSessionRuntime(
|
|
|
1905
1894
|
* (`dench_*` / `gw_sk_*` / etc.) baked into sandbox envVars. The
|
|
1906
1895
|
* server-side `requireCrmAccess` helper dispatches on the prefix.
|
|
1907
1896
|
*/
|
|
1908
|
-
type AuthenticatedRuntime = Extract<
|
|
1909
|
-
Runtime,
|
|
1910
|
-
{ mode: "session" | "apiKey" }
|
|
1911
|
-
>;
|
|
1897
|
+
type AuthenticatedRuntime = Extract<Runtime, { mode: "session" | "apiKey" }>;
|
|
1912
1898
|
|
|
1913
1899
|
function requireAuthenticatedRuntime(
|
|
1914
1900
|
runtime: Runtime,
|
|
@@ -1956,25 +1942,6 @@ function rejectApiKeyForAgentCommand(
|
|
|
1956
1942
|
}
|
|
1957
1943
|
}
|
|
1958
1944
|
|
|
1959
|
-
async function printToolStatus(runtime: Runtime, toolkit?: string) {
|
|
1960
|
-
const sessionRuntime = requireSessionRuntime(runtime, "dench tool status");
|
|
1961
|
-
try {
|
|
1962
|
-
print(
|
|
1963
|
-
await sessionRuntime.client.action(
|
|
1964
|
-
api.functions.agentToolsNode.agentToolStatus,
|
|
1965
|
-
{
|
|
1966
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
1967
|
-
toolkit,
|
|
1968
|
-
},
|
|
1969
|
-
),
|
|
1970
|
-
);
|
|
1971
|
-
} catch (error) {
|
|
1972
|
-
const unavailable = toolGatewayUnavailable(error, { toolkit });
|
|
1973
|
-
if (!unavailable) throw error;
|
|
1974
|
-
print(json ? unavailable : formatToolUnavailable(unavailable));
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
1945
|
function asRecord(value: unknown): JsonRecord | undefined {
|
|
1979
1946
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
1980
1947
|
return undefined;
|
|
@@ -2010,7 +1977,7 @@ function numberField(record: JsonRecord | undefined, ...names: string[]) {
|
|
|
2010
1977
|
return undefined;
|
|
2011
1978
|
}
|
|
2012
1979
|
|
|
2013
|
-
function
|
|
1980
|
+
function _booleanField(record: JsonRecord | undefined, ...names: string[]) {
|
|
2014
1981
|
if (!record) return undefined;
|
|
2015
1982
|
for (const name of names) {
|
|
2016
1983
|
const value = record[name];
|
|
@@ -2033,94 +2000,6 @@ function isMissingConvexFunction(error: unknown) {
|
|
|
2033
2000
|
return /Could not find public function/i.test(errorMessage(error));
|
|
2034
2001
|
}
|
|
2035
2002
|
|
|
2036
|
-
function toolGatewayUnavailable(error: unknown, context: JsonRecord = {}) {
|
|
2037
|
-
const message = errorMessage(error);
|
|
2038
|
-
if (!/gateway API key|callGateway|Composio/i.test(message)) {
|
|
2039
|
-
return null;
|
|
2040
|
-
}
|
|
2041
|
-
const invalidKey = /Invalid gateway API key/i.test(message);
|
|
2042
|
-
const query = stringField(context, "query");
|
|
2043
|
-
const toolkit = stringField(context, "toolkit");
|
|
2044
|
-
const retryCommand = query
|
|
2045
|
-
? `Retry dench tool search ${JSON.stringify(query)}${toolkit ? ` --toolkit ${toolkit}` : ""} after the gateway key is fixed.`
|
|
2046
|
-
: "Retry dench apps --json after the gateway key is fixed.";
|
|
2047
|
-
return {
|
|
2048
|
-
ok: false,
|
|
2049
|
-
available: false,
|
|
2050
|
-
code: invalidKey ? "invalid_gateway_api_key" : "tool_gateway_unavailable",
|
|
2051
|
-
reason: invalidKey
|
|
2052
|
-
? "The workspace gateway key is invalid or out of sync."
|
|
2053
|
-
: "The connected-app gateway is unavailable.",
|
|
2054
|
-
...context,
|
|
2055
|
-
nextActions: [
|
|
2056
|
-
"Ask a workspace admin to repair or rotate the Dench gateway key.",
|
|
2057
|
-
retryCommand,
|
|
2058
|
-
],
|
|
2059
|
-
};
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
function formatToolUnavailable(payload: JsonRecord) {
|
|
2063
|
-
return [
|
|
2064
|
-
"Connected apps unavailable.",
|
|
2065
|
-
`Reason: ${stringField(payload, "reason") ?? "tool gateway unavailable"}`,
|
|
2066
|
-
"",
|
|
2067
|
-
"Next actions:",
|
|
2068
|
-
...(
|
|
2069
|
-
(Array.isArray(payload.nextActions)
|
|
2070
|
-
? payload.nextActions
|
|
2071
|
-
: []) as unknown[]
|
|
2072
|
-
)
|
|
2073
|
-
.filter((item): item is string => typeof item === "string")
|
|
2074
|
-
.map((item) => ` - ${item}`),
|
|
2075
|
-
].join("\n");
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
const READ_ONLY_TOOL_ACTIONS = new Set([
|
|
2079
|
-
"FETCH",
|
|
2080
|
-
"GET",
|
|
2081
|
-
"LIST",
|
|
2082
|
-
"SEARCH",
|
|
2083
|
-
"READ",
|
|
2084
|
-
"FIND",
|
|
2085
|
-
]);
|
|
2086
|
-
|
|
2087
|
-
function toolSlugTokens(toolSlug: string) {
|
|
2088
|
-
return toolSlug
|
|
2089
|
-
.toUpperCase()
|
|
2090
|
-
.split(/[^A-Z0-9]+/)
|
|
2091
|
-
.filter(Boolean);
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
function toolApprovalHint(toolSlug: string | undefined) {
|
|
2095
|
-
if (!toolSlug) return "approval unknown";
|
|
2096
|
-
const readOnly = toolSlugTokens(toolSlug).find((token) =>
|
|
2097
|
-
READ_ONLY_TOOL_ACTIONS.has(token),
|
|
2098
|
-
);
|
|
2099
|
-
return readOnly ? `read-only (${readOnly})` : "approval likely";
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
function compactConnection(connection: JsonRecord) {
|
|
2103
|
-
return {
|
|
2104
|
-
toolkit: stringField(connection, "toolkit") ?? "unknown",
|
|
2105
|
-
status: stringField(connection, "status") ?? "unknown",
|
|
2106
|
-
connectedAccountId:
|
|
2107
|
-
stringField(connection, "connectedAccountId", "connected_account_id") ??
|
|
2108
|
-
stringField(connection, "id"),
|
|
2109
|
-
accountLabel:
|
|
2110
|
-
stringField(connection, "accountLabel", "account_label", "label") ?? null,
|
|
2111
|
-
};
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
function summarizeConnectedApps(value: unknown) {
|
|
2115
|
-
const record = asRecord(value);
|
|
2116
|
-
const connections = asRecordArray(record?.connections).map(compactConnection);
|
|
2117
|
-
return {
|
|
2118
|
-
available: true,
|
|
2119
|
-
count: connections.length,
|
|
2120
|
-
connections,
|
|
2121
|
-
};
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
2003
|
function workspaceSlugFromContext(context: JsonRecord) {
|
|
2125
2004
|
const workspace = asRecord(context.workspace);
|
|
2126
2005
|
return (
|
|
@@ -2147,41 +2026,12 @@ function nextContextCommands() {
|
|
|
2147
2026
|
];
|
|
2148
2027
|
}
|
|
2149
2028
|
|
|
2150
|
-
async function connectedAppsContext(
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
connections: [],
|
|
2157
|
-
};
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
try {
|
|
2161
|
-
return summarizeConnectedApps(
|
|
2162
|
-
await runtime.client.action(
|
|
2163
|
-
api.functions.agentToolsNode.agentToolStatus,
|
|
2164
|
-
{
|
|
2165
|
-
sessionToken: runtime.sessionToken,
|
|
2166
|
-
},
|
|
2167
|
-
),
|
|
2168
|
-
);
|
|
2169
|
-
} catch (error) {
|
|
2170
|
-
const unavailable = toolGatewayUnavailable(error);
|
|
2171
|
-
if (unavailable) {
|
|
2172
|
-
return {
|
|
2173
|
-
...unavailable,
|
|
2174
|
-
count: 0,
|
|
2175
|
-
connections: [],
|
|
2176
|
-
};
|
|
2177
|
-
}
|
|
2178
|
-
return {
|
|
2179
|
-
available: false,
|
|
2180
|
-
error: errorMessage(error),
|
|
2181
|
-
count: 0,
|
|
2182
|
-
connections: [],
|
|
2183
|
-
};
|
|
2184
|
-
}
|
|
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();
|
|
2185
2035
|
}
|
|
2186
2036
|
|
|
2187
2037
|
async function buildContext(runtime: Runtime) {
|
|
@@ -2420,223 +2270,6 @@ function formatContext(context: JsonRecord) {
|
|
|
2420
2270
|
return lines.join("\n");
|
|
2421
2271
|
}
|
|
2422
2272
|
|
|
2423
|
-
function toolkitSlugFromTool(tool: JsonRecord) {
|
|
2424
|
-
const toolkit = tool.toolkit;
|
|
2425
|
-
if (typeof toolkit === "string") return toolkit;
|
|
2426
|
-
const toolkitRecord = asRecord(toolkit);
|
|
2427
|
-
return (
|
|
2428
|
-
stringField(toolkitRecord, "slug", "name") ??
|
|
2429
|
-
stringField(tool, "toolkit_slug")
|
|
2430
|
-
);
|
|
2431
|
-
}
|
|
2432
|
-
|
|
2433
|
-
function connectedToolkitSet(data: JsonRecord | undefined) {
|
|
2434
|
-
const connected = new Set<string>();
|
|
2435
|
-
const connectedToolkits = Array.isArray(data?.connected_toolkits)
|
|
2436
|
-
? data?.connected_toolkits
|
|
2437
|
-
: [];
|
|
2438
|
-
for (const toolkit of connectedToolkits) {
|
|
2439
|
-
const value = stringValue(toolkit);
|
|
2440
|
-
if (value) connected.add(value.toLowerCase());
|
|
2441
|
-
}
|
|
2442
|
-
for (const status of asRecordArray(data?.toolkit_connection_statuses)) {
|
|
2443
|
-
const toolkit = stringField(status, "toolkit");
|
|
2444
|
-
const isConnected = booleanField(status, "has_active_connection");
|
|
2445
|
-
if (toolkit && isConnected) connected.add(toolkit.toLowerCase());
|
|
2446
|
-
}
|
|
2447
|
-
return connected;
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
function searchToolItems(searchResult: unknown) {
|
|
2451
|
-
const root = asRecord(searchResult);
|
|
2452
|
-
const data = asRecord(root?.data) ?? root;
|
|
2453
|
-
const directItems = asRecordArray(data?.items);
|
|
2454
|
-
if (directItems.length > 0) {
|
|
2455
|
-
return { data, items: directItems };
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
const schemas = asRecord(data?.tool_schemas);
|
|
2459
|
-
if (!schemas) return { data, items: [] };
|
|
2460
|
-
return {
|
|
2461
|
-
data,
|
|
2462
|
-
items: Object.entries(schemas).flatMap<JsonRecord>(([slug, value]) => {
|
|
2463
|
-
const tool = asRecord(value);
|
|
2464
|
-
return tool
|
|
2465
|
-
? [{ ...tool, tool_slug: stringField(tool, "tool_slug") ?? slug }]
|
|
2466
|
-
: [];
|
|
2467
|
-
}),
|
|
2468
|
-
};
|
|
2469
|
-
}
|
|
2470
|
-
|
|
2471
|
-
function compactToolSearchResult(searchResult: unknown) {
|
|
2472
|
-
const root = asRecord(searchResult);
|
|
2473
|
-
const { data, items } = searchToolItems(searchResult);
|
|
2474
|
-
const connected = connectedToolkitSet(data);
|
|
2475
|
-
const tools = items.map((tool) => {
|
|
2476
|
-
const toolkit = toolkitSlugFromTool(tool);
|
|
2477
|
-
const connectionStatus = asRecord(tool.connection_status);
|
|
2478
|
-
const isConnected =
|
|
2479
|
-
booleanField(connectionStatus, "is_connected") ??
|
|
2480
|
-
(toolkit ? connected.has(toolkit.toLowerCase()) : undefined);
|
|
2481
|
-
const slug = stringField(tool, "slug", "tool_slug") ?? "unknown_tool";
|
|
2482
|
-
return {
|
|
2483
|
-
slug,
|
|
2484
|
-
name: stringField(tool, "display_name", "name") ?? slug,
|
|
2485
|
-
toolkit: toolkit ?? "unknown",
|
|
2486
|
-
approval: toolApprovalHint(slug),
|
|
2487
|
-
connected:
|
|
2488
|
-
isConnected === undefined
|
|
2489
|
-
? "connection unknown"
|
|
2490
|
-
: isConnected
|
|
2491
|
-
? "connected"
|
|
2492
|
-
: "not connected",
|
|
2493
|
-
description: compactLine(tool.description) ?? null,
|
|
2494
|
-
};
|
|
2495
|
-
});
|
|
2496
|
-
|
|
2497
|
-
return {
|
|
2498
|
-
ok: root?.ok ?? true,
|
|
2499
|
-
query: stringField(root, "query") ?? stringField(data, "query") ?? null,
|
|
2500
|
-
returned: tools.length,
|
|
2501
|
-
total:
|
|
2502
|
-
numberField(data, "total_items", "total", "count") ??
|
|
2503
|
-
numberField(root, "total_items", "total", "count") ??
|
|
2504
|
-
tools.length,
|
|
2505
|
-
tools,
|
|
2506
|
-
};
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
|
-
function formatToolSearchResult(searchResult: unknown) {
|
|
2510
|
-
const summary = compactToolSearchResult(searchResult);
|
|
2511
|
-
const query = summary.query ? ` for "${summary.query}"` : "";
|
|
2512
|
-
if (summary.tools.length === 0) {
|
|
2513
|
-
return `No matching tools found${query}.\nUse --json if you need the raw provider response.`;
|
|
2514
|
-
}
|
|
2515
|
-
|
|
2516
|
-
const lines = [`Top tools${query}:`];
|
|
2517
|
-
for (const [index, tool] of summary.tools.entries()) {
|
|
2518
|
-
lines.push(
|
|
2519
|
-
`${index + 1}. ${tool.slug} - ${tool.name}`,
|
|
2520
|
-
` toolkit: ${tool.toolkit} | ${tool.approval} | ${tool.connected}`,
|
|
2521
|
-
);
|
|
2522
|
-
if (tool.description) {
|
|
2523
|
-
lines.push(` ${tool.description}`);
|
|
2524
|
-
}
|
|
2525
|
-
}
|
|
2526
|
-
lines.push(
|
|
2527
|
-
"",
|
|
2528
|
-
"Use dench tool run <slug> --args '{...}' when ready.",
|
|
2529
|
-
"Use --json only when you need full schemas or raw arguments.",
|
|
2530
|
-
);
|
|
2531
|
-
return lines.join("\n");
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
function printToolSearchResult(searchResult: unknown) {
|
|
2535
|
-
print(json ? searchResult : formatToolSearchResult(searchResult));
|
|
2536
|
-
}
|
|
2537
|
-
|
|
2538
|
-
const SENSITIVE_KEY_RE =
|
|
2539
|
-
/(?:otp|oneTime|verification|security|passcode|password|secret|api[_-]?key|access[_-]?token|refresh[_-]?token|authorization)/i;
|
|
2540
|
-
|
|
2541
|
-
function redactSensitiveString(value: string) {
|
|
2542
|
-
return value
|
|
2543
|
-
.replace(
|
|
2544
|
-
/\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,
|
|
2545
|
-
"$1[redacted-code]",
|
|
2546
|
-
)
|
|
2547
|
-
.replace(
|
|
2548
|
-
/\b((?:api[_\s-]?key|secret|token|password)[^.\n\r]{0,60}?)([A-Za-z0-9_=-]{12,})\b/gi,
|
|
2549
|
-
"$1[redacted-secret]",
|
|
2550
|
-
);
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
function redactSensitiveDisplay(value: unknown, key?: string): unknown {
|
|
2554
|
-
if (typeof value === "string") {
|
|
2555
|
-
return key && SENSITIVE_KEY_RE.test(key)
|
|
2556
|
-
? "[redacted]"
|
|
2557
|
-
: redactSensitiveString(value);
|
|
2558
|
-
}
|
|
2559
|
-
if (typeof value === "number" && key && SENSITIVE_KEY_RE.test(key)) {
|
|
2560
|
-
return "[redacted]";
|
|
2561
|
-
}
|
|
2562
|
-
if (Array.isArray(value)) {
|
|
2563
|
-
return value.map((item) => redactSensitiveDisplay(item));
|
|
2564
|
-
}
|
|
2565
|
-
const record = asRecord(value);
|
|
2566
|
-
if (!record) return value;
|
|
2567
|
-
|
|
2568
|
-
return Object.fromEntries(
|
|
2569
|
-
Object.entries(record).map(([entryKey, entryValue]) => [
|
|
2570
|
-
entryKey,
|
|
2571
|
-
redactSensitiveDisplay(entryValue, entryKey),
|
|
2572
|
-
]),
|
|
2573
|
-
);
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
|
-
function connectionUrlFromToolConnect(result: unknown) {
|
|
2577
|
-
const record = asRecord(result);
|
|
2578
|
-
const data = asRecord(record?.data);
|
|
2579
|
-
return (
|
|
2580
|
-
stringField(record, "connectUrl", "redirect_url", "redirectUrl", "url") ??
|
|
2581
|
-
stringField(data, "redirect_url", "redirectUrl", "url")
|
|
2582
|
-
);
|
|
2583
|
-
}
|
|
2584
|
-
|
|
2585
|
-
function withToolConnectSummary(result: unknown) {
|
|
2586
|
-
const record = asRecord(result);
|
|
2587
|
-
if (!record) return result;
|
|
2588
|
-
const toolkit = stringField(record, "toolkit") ?? "app";
|
|
2589
|
-
const connectUrl = connectionUrlFromToolConnect(record);
|
|
2590
|
-
if (!connectUrl) return record;
|
|
2591
|
-
return {
|
|
2592
|
-
...record,
|
|
2593
|
-
connectUrl,
|
|
2594
|
-
linkText: `Connect ${toolkit} here`,
|
|
2595
|
-
message: `Connect ${toolkit} here: ${connectUrl}`,
|
|
2596
|
-
};
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
|
-
function formatToolConnectResult(result: unknown) {
|
|
2600
|
-
const record = asRecord(withToolConnectSummary(result));
|
|
2601
|
-
if (!record) return String(result);
|
|
2602
|
-
const toolkit = stringField(record, "toolkit") ?? "app";
|
|
2603
|
-
const connectUrl = stringField(record, "connectUrl");
|
|
2604
|
-
if (connectUrl) {
|
|
2605
|
-
return `Connect ${toolkit} here: ${connectUrl}`;
|
|
2606
|
-
}
|
|
2607
|
-
return JSON.stringify(record, null, 2);
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
function withToolRunGuidance(runtime: Runtime, result: unknown) {
|
|
2611
|
-
const record = asRecord(result);
|
|
2612
|
-
if (!record || record.requiresApproval !== true) return result;
|
|
2613
|
-
const approvalId = stringField(record, "approvalId");
|
|
2614
|
-
const statusContext =
|
|
2615
|
-
runtime.mode === "session" || runtime.mode === "apiKey"
|
|
2616
|
-
? workspaceUrls(runtime.host, runtime.organization?.slug)
|
|
2617
|
-
: {};
|
|
2618
|
-
const approvalCommand = approvalId
|
|
2619
|
-
? `dench approval approve ${approvalId} --evidence "User said yes in chat" --json`
|
|
2620
|
-
: undefined;
|
|
2621
|
-
return {
|
|
2622
|
-
...record,
|
|
2623
|
-
...statusContext,
|
|
2624
|
-
...(approvalCommand ? { approvalCommand } : {}),
|
|
2625
|
-
nextActions: [
|
|
2626
|
-
"Ask the human for an explicit yes/no in chat.",
|
|
2627
|
-
...(approvalCommand ? [`If yes, run: ${approvalCommand}`] : []),
|
|
2628
|
-
approvalId
|
|
2629
|
-
? `Rerun the exact same dench tool run command with --approval ${approvalId}.`
|
|
2630
|
-
: "Rerun the exact same dench tool run command after approval.",
|
|
2631
|
-
],
|
|
2632
|
-
};
|
|
2633
|
-
}
|
|
2634
|
-
|
|
2635
|
-
function printToolRunResult(runtime: Runtime, result: unknown) {
|
|
2636
|
-
const guided = withToolRunGuidance(runtime, result);
|
|
2637
|
-
print(json ? guided : redactSensitiveDisplay(guided));
|
|
2638
|
-
}
|
|
2639
|
-
|
|
2640
2273
|
async function devOverview(runtime: Awaited<ReturnType<typeof getRuntime>>) {
|
|
2641
2274
|
if (runtime.mode !== "dev") {
|
|
2642
2275
|
throw new Error("Not in dev mode");
|
|
@@ -2829,10 +2462,12 @@ async function runFsCommand() {
|
|
|
2829
2462
|
function normalizeFilesPath(input: string): string {
|
|
2830
2463
|
if (!input || input === "/") return "/";
|
|
2831
2464
|
let cleaned = input.replace(/\\/g, "/").replace(/\/+/g, "/");
|
|
2832
|
-
if (cleaned.startsWith("/workspace/"))
|
|
2465
|
+
if (cleaned.startsWith("/workspace/"))
|
|
2466
|
+
cleaned = cleaned.slice("/workspace".length);
|
|
2833
2467
|
else if (cleaned === "/workspace") cleaned = "/";
|
|
2834
2468
|
if (!cleaned.startsWith("/")) cleaned = `/${cleaned}`;
|
|
2835
|
-
if (cleaned.length > 1 && cleaned.endsWith("/"))
|
|
2469
|
+
if (cleaned.length > 1 && cleaned.endsWith("/"))
|
|
2470
|
+
cleaned = cleaned.slice(0, -1);
|
|
2836
2471
|
return cleaned;
|
|
2837
2472
|
}
|
|
2838
2473
|
|
|
@@ -3010,9 +2645,9 @@ async function runFilesMv() {
|
|
|
3010
2645
|
}
|
|
3011
2646
|
|
|
3012
2647
|
const sourceRows = await listConvexTreeRecursive(runtime, apiKey, from);
|
|
3013
|
-
const sourceSelf = (
|
|
3014
|
-
(
|
|
3015
|
-
);
|
|
2648
|
+
const sourceSelf = (
|
|
2649
|
+
await listConvexTreeAt(runtime, apiKey, parentOfPath(from))
|
|
2650
|
+
).find((row) => row.path === from);
|
|
3016
2651
|
if (!sourceSelf && sourceRows.length === 0) {
|
|
3017
2652
|
throw new CliError(`Source not found in Convex: ${from}`, {
|
|
3018
2653
|
code: "files_mv_not_found",
|
|
@@ -3074,7 +2709,11 @@ async function runFilesRm() {
|
|
|
3074
2709
|
const target = normalizeFilesPath(targetRaw);
|
|
3075
2710
|
const recursive = hasFlag("--recursive") || hasFlag("-r");
|
|
3076
2711
|
|
|
3077
|
-
const parentRows = await listConvexTreeAt(
|
|
2712
|
+
const parentRows = await listConvexTreeAt(
|
|
2713
|
+
runtime,
|
|
2714
|
+
apiKey,
|
|
2715
|
+
parentOfPath(target),
|
|
2716
|
+
);
|
|
3078
2717
|
const self = parentRows.find((row) => row.path === target);
|
|
3079
2718
|
if (!self) {
|
|
3080
2719
|
if (hasFlag("--json")) {
|
|
@@ -3621,6 +3260,25 @@ async function main() {
|
|
|
3621
3260
|
return;
|
|
3622
3261
|
}
|
|
3623
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
|
+
|
|
3624
3282
|
if (command === "chat") {
|
|
3625
3283
|
const subArgs = args.slice(args.indexOf("chat") + 1);
|
|
3626
3284
|
const sub = subArgs[0];
|
|
@@ -3713,8 +3371,11 @@ async function main() {
|
|
|
3713
3371
|
return;
|
|
3714
3372
|
}
|
|
3715
3373
|
|
|
3716
|
-
|
|
3717
|
-
|
|
3374
|
+
// `dench routine` / `dench routines` are UI-friendly aliases for
|
|
3375
|
+
// `dench cron`. Same dispatch, same auth, same handler — only the
|
|
3376
|
+
// top-level command name differs to match the workspace UI rename.
|
|
3377
|
+
if (command === "cron" || command === "routine" || command === "routines") {
|
|
3378
|
+
const subArgs = args.slice(args.indexOf(command) + 1);
|
|
3718
3379
|
const sub = subArgs[0];
|
|
3719
3380
|
// Help is a no-auth path so users can read the surface without a
|
|
3720
3381
|
// session — mirrors `dench crm help`.
|
|
@@ -3733,7 +3394,10 @@ async function main() {
|
|
|
3733
3394
|
// bearer prefix and resolves the org for both. The Bearer token
|
|
3734
3395
|
// must carry `crons:read` (queries) or `crons:write` (mutations)
|
|
3735
3396
|
// scope, or the wildcard `*`.
|
|
3736
|
-
const authedRuntime = requireAuthenticatedRuntime(
|
|
3397
|
+
const authedRuntime = requireAuthenticatedRuntime(
|
|
3398
|
+
runtime,
|
|
3399
|
+
`dench ${command}`,
|
|
3400
|
+
);
|
|
3737
3401
|
await runCronCommand({
|
|
3738
3402
|
convex: authedRuntime.client,
|
|
3739
3403
|
args: subArgs,
|
|
@@ -3742,6 +3406,85 @@ async function main() {
|
|
|
3742
3406
|
return;
|
|
3743
3407
|
}
|
|
3744
3408
|
|
|
3409
|
+
// ── OpenClaw-parity self-update CLI surface ──────────────────────────
|
|
3410
|
+
//
|
|
3411
|
+
// Dispatch all `dench identity / organisation / user / tools / mem /
|
|
3412
|
+
// heartbeat / bootstrap / model / daily` traffic through the shared
|
|
3413
|
+
// agent-config CLI module. Each command requires the same auth shape
|
|
3414
|
+
// as `dench cron` (Bearer DENCH_API_KEY or `dench login` session
|
|
3415
|
+
// token) so server-side `requireCronAccess` accepts both.
|
|
3416
|
+
const AGENT_CONFIG_COMMANDS = new Set([
|
|
3417
|
+
"identity",
|
|
3418
|
+
"organisation",
|
|
3419
|
+
"user",
|
|
3420
|
+
"tools",
|
|
3421
|
+
"mem",
|
|
3422
|
+
"heartbeat",
|
|
3423
|
+
"bootstrap",
|
|
3424
|
+
"model",
|
|
3425
|
+
"daily",
|
|
3426
|
+
]);
|
|
3427
|
+
if (AGENT_CONFIG_COMMANDS.has(command)) {
|
|
3428
|
+
const subArgs = args.slice(args.indexOf(command) + 1);
|
|
3429
|
+
if (
|
|
3430
|
+
subArgs[0] === "help" ||
|
|
3431
|
+
subArgs.includes("--help") ||
|
|
3432
|
+
subArgs.includes("-h")
|
|
3433
|
+
) {
|
|
3434
|
+
const { printAgentConfigCliHelp } = await import("./agent-config");
|
|
3435
|
+
printAgentConfigCliHelp();
|
|
3436
|
+
return;
|
|
3437
|
+
}
|
|
3438
|
+
const runtime = await getRuntime();
|
|
3439
|
+
const authedRuntime = requireAuthenticatedRuntime(
|
|
3440
|
+
runtime,
|
|
3441
|
+
`dench ${command}`,
|
|
3442
|
+
);
|
|
3443
|
+
const ctxBase = {
|
|
3444
|
+
convex: authedRuntime.client,
|
|
3445
|
+
args: subArgs,
|
|
3446
|
+
jsonOutput: json,
|
|
3447
|
+
sessionToken: authedRuntime.sessionToken,
|
|
3448
|
+
};
|
|
3449
|
+
const mod = await import("./agent-config");
|
|
3450
|
+
if (command === "identity") {
|
|
3451
|
+
await mod.runIdentityCommand(ctxBase);
|
|
3452
|
+
return;
|
|
3453
|
+
}
|
|
3454
|
+
if (command === "organisation") {
|
|
3455
|
+
await mod.runOrganisationCommand(ctxBase);
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
if (command === "user") {
|
|
3459
|
+
await mod.runUserCommand(ctxBase);
|
|
3460
|
+
return;
|
|
3461
|
+
}
|
|
3462
|
+
if (command === "tools") {
|
|
3463
|
+
await mod.runToolsCommand(ctxBase);
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
if (command === "mem") {
|
|
3467
|
+
await mod.runMemoryAggregateCommand(ctxBase);
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
if (command === "heartbeat") {
|
|
3471
|
+
await mod.runHeartbeatCommand(ctxBase);
|
|
3472
|
+
return;
|
|
3473
|
+
}
|
|
3474
|
+
if (command === "bootstrap") {
|
|
3475
|
+
await mod.runBootstrapCommand(ctxBase);
|
|
3476
|
+
return;
|
|
3477
|
+
}
|
|
3478
|
+
if (command === "model") {
|
|
3479
|
+
await mod.runModelCommand(ctxBase);
|
|
3480
|
+
return;
|
|
3481
|
+
}
|
|
3482
|
+
if (command === "daily") {
|
|
3483
|
+
await mod.runDailyCommand(ctxBase);
|
|
3484
|
+
return;
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3745
3488
|
if (command === "login") {
|
|
3746
3489
|
await login();
|
|
3747
3490
|
return;
|
|
@@ -3786,11 +3529,6 @@ async function main() {
|
|
|
3786
3529
|
return;
|
|
3787
3530
|
}
|
|
3788
3531
|
|
|
3789
|
-
if (command === "apps") {
|
|
3790
|
-
await printToolStatus(runtime);
|
|
3791
|
-
return;
|
|
3792
|
-
}
|
|
3793
|
-
|
|
3794
3532
|
if (command === "context") {
|
|
3795
3533
|
const context = await buildContext(runtime);
|
|
3796
3534
|
print(json ? context : formatContext(context));
|
|
@@ -3943,9 +3681,7 @@ async function main() {
|
|
|
3943
3681
|
return;
|
|
3944
3682
|
}
|
|
3945
3683
|
if (runtime.mode === "apiKey") {
|
|
3946
|
-
throw loginRequiredError(
|
|
3947
|
-
"dench tasks (agent-scoped task list)",
|
|
3948
|
-
);
|
|
3684
|
+
throw loginRequiredError("dench tasks (agent-scoped task list)");
|
|
3949
3685
|
}
|
|
3950
3686
|
const data = await devOverview(runtime);
|
|
3951
3687
|
print(data.tasks);
|
|
@@ -4235,79 +3971,6 @@ async function main() {
|
|
|
4235
3971
|
return;
|
|
4236
3972
|
}
|
|
4237
3973
|
|
|
4238
|
-
if (command === "tool") {
|
|
4239
|
-
const sessionRuntime = requireSessionRuntime(runtime, "dench tool");
|
|
4240
|
-
|
|
4241
|
-
if (subcommand === "status") {
|
|
4242
|
-
await printToolStatus(sessionRuntime, positional(2));
|
|
4243
|
-
return;
|
|
4244
|
-
}
|
|
4245
|
-
|
|
4246
|
-
if (subcommand === "connect") {
|
|
4247
|
-
const toolkit = positional(2);
|
|
4248
|
-
if (!toolkit) throw new Error("Missing toolkit");
|
|
4249
|
-
const result = await sessionRuntime.client.action(
|
|
4250
|
-
api.functions.agentToolsNode.agentToolConnect,
|
|
4251
|
-
{
|
|
4252
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
4253
|
-
toolkit,
|
|
4254
|
-
callbackUrl: option("--callback-url"),
|
|
4255
|
-
},
|
|
4256
|
-
);
|
|
4257
|
-
print(
|
|
4258
|
-
json ? withToolConnectSummary(result) : formatToolConnectResult(result),
|
|
4259
|
-
);
|
|
4260
|
-
return;
|
|
4261
|
-
}
|
|
4262
|
-
|
|
4263
|
-
if (subcommand === "search") {
|
|
4264
|
-
const query = positional(2);
|
|
4265
|
-
if (!query) throw new Error("Missing search query");
|
|
4266
|
-
try {
|
|
4267
|
-
printToolSearchResult(
|
|
4268
|
-
await sessionRuntime.client.action(
|
|
4269
|
-
api.functions.agentToolsNode.agentToolSearch,
|
|
4270
|
-
{
|
|
4271
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
4272
|
-
query,
|
|
4273
|
-
toolkit: option("--toolkit"),
|
|
4274
|
-
limit: parseNumberOption("--limit"),
|
|
4275
|
-
},
|
|
4276
|
-
),
|
|
4277
|
-
);
|
|
4278
|
-
} catch (error) {
|
|
4279
|
-
const unavailable = toolGatewayUnavailable(error, {
|
|
4280
|
-
query,
|
|
4281
|
-
toolkit: option("--toolkit"),
|
|
4282
|
-
});
|
|
4283
|
-
if (!unavailable) throw error;
|
|
4284
|
-
print(json ? unavailable : formatToolUnavailable(unavailable));
|
|
4285
|
-
}
|
|
4286
|
-
return;
|
|
4287
|
-
}
|
|
4288
|
-
|
|
4289
|
-
if (subcommand === "run") {
|
|
4290
|
-
const toolSlug = positional(2);
|
|
4291
|
-
if (!toolSlug) throw new Error("Missing Composio tool slug");
|
|
4292
|
-
printToolRunResult(
|
|
4293
|
-
sessionRuntime,
|
|
4294
|
-
await sessionRuntime.client.action(
|
|
4295
|
-
api.functions.agentToolsNode.agentToolRun,
|
|
4296
|
-
{
|
|
4297
|
-
sessionToken: sessionRuntime.sessionToken,
|
|
4298
|
-
toolSlug,
|
|
4299
|
-
arguments: parseJsonObjectOption("--args"),
|
|
4300
|
-
connectedAccountId: option("--account"),
|
|
4301
|
-
approvalId: option("--approval") as never,
|
|
4302
|
-
},
|
|
4303
|
-
),
|
|
4304
|
-
);
|
|
4305
|
-
return;
|
|
4306
|
-
}
|
|
4307
|
-
|
|
4308
|
-
throw new Error(`Unknown tool command: ${filteredArgs.join(" ")}`);
|
|
4309
|
-
}
|
|
4310
|
-
|
|
4311
3974
|
if (
|
|
4312
3975
|
command === "approval" &&
|
|
4313
3976
|
(subcommand === "approve" || subcommand === "reject")
|