@dench.com/cli 3.6.3-staging.372 → 3.6.5-staging.384
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/agent-config.ts +10 -16
- package/app-sdk.d.ts +234 -6
- package/crm.ts +62 -16
- package/dench.ts +70 -36
- package/email.ts +207 -2
- package/lib/api-schemas.ts +127 -9
- package/lib/app-sdk-catalog.generated.ts +17 -0
- package/lib/app-sdk-surface.ts +49 -0
- package/lib/command-registry.ts +115 -3
- package/lib/enrichment-gateway.ts +12 -182
- package/lib/enrichment-shapes.ts +182 -0
- package/lib/native-providers.ts +44 -0
- package/package.json +1 -1
- package/tools.ts +106 -61
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { normalizeUnipileToolkitSlug } from "./unipile-toolkits";
|
|
2
|
+
|
|
3
|
+
/** Provider ownership is independent of connection health and search results. */
|
|
4
|
+
export function nativeToolkit(slug: string): string | null {
|
|
5
|
+
const normalized = slug
|
|
6
|
+
.trim()
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[\s_-]+/g, "");
|
|
9
|
+
if (normalized === "slack" || normalized === "slackbot") return "slack";
|
|
10
|
+
return normalizeUnipileToolkitSlug(slug);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function nativeToolkitForTool(slug: string): string | null {
|
|
14
|
+
const parts = slug.trim().split(/[_-]+/);
|
|
15
|
+
// Providers with separator aliases (GOOGLE_CALENDAR, MICROSOFT_OUTLOOK)
|
|
16
|
+
// are resolved before considering the remainder as an action name.
|
|
17
|
+
for (let length = Math.min(3, parts.length - 1); length > 0; length--) {
|
|
18
|
+
const toolkit = nativeToolkit(parts.slice(0, length).join(""));
|
|
19
|
+
if (toolkit) return toolkit;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function isNativeToolRecord(value: {
|
|
25
|
+
slug?: unknown;
|
|
26
|
+
tool_slug?: unknown;
|
|
27
|
+
toolkit_slug?: unknown;
|
|
28
|
+
toolkit?: unknown;
|
|
29
|
+
}): boolean {
|
|
30
|
+
const toolkit =
|
|
31
|
+
value.toolkit && typeof value.toolkit === "object"
|
|
32
|
+
? (value.toolkit as { slug?: unknown; name?: unknown })
|
|
33
|
+
: {};
|
|
34
|
+
const name =
|
|
35
|
+
value.toolkit_slug ??
|
|
36
|
+
(typeof value.toolkit === "string"
|
|
37
|
+
? value.toolkit
|
|
38
|
+
: (toolkit.slug ?? toolkit.name));
|
|
39
|
+
const slug = value.slug ?? value.tool_slug;
|
|
40
|
+
return (
|
|
41
|
+
(typeof name === "string" && nativeToolkit(name) !== null) ||
|
|
42
|
+
(typeof slug === "string" && nativeToolkitForTool(slug) !== null)
|
|
43
|
+
);
|
|
44
|
+
}
|
package/package.json
CHANGED
package/tools.ts
CHANGED
|
@@ -57,6 +57,11 @@ import {
|
|
|
57
57
|
type ConnectionSource,
|
|
58
58
|
connectionSource,
|
|
59
59
|
} from "./lib/connection-source";
|
|
60
|
+
import {
|
|
61
|
+
isNativeToolRecord,
|
|
62
|
+
nativeToolkit,
|
|
63
|
+
nativeToolkitForTool,
|
|
64
|
+
} from "./lib/native-providers";
|
|
60
65
|
import {
|
|
61
66
|
normalizeUnipileToolkitSlug,
|
|
62
67
|
unipileConnectKindFromToolkitSlug,
|
|
@@ -75,6 +80,8 @@ export type ToolCliContext = {
|
|
|
75
80
|
gatewayBaseUrl?: string;
|
|
76
81
|
gatewayHeaders?: Record<string, string>;
|
|
77
82
|
gatewayAuthError?: string;
|
|
83
|
+
/** Resolve the gateway only when a third-party operation actually needs it. */
|
|
84
|
+
resolveGatewayAuth?: () => Promise<GatewayAuthContext>;
|
|
78
85
|
/**
|
|
79
86
|
* Convex client + bearer for the Unipile half of the list. Absent on the
|
|
80
87
|
* help path and whenever the caller could not be authenticated, in which
|
|
@@ -93,7 +100,11 @@ export type ToolCliContext = {
|
|
|
93
100
|
|
|
94
101
|
type GatewayAuthContext = Pick<
|
|
95
102
|
ToolCliContext,
|
|
96
|
-
|
|
103
|
+
| "bearerToken"
|
|
104
|
+
| "gatewayBaseUrl"
|
|
105
|
+
| "gatewayHeaders"
|
|
106
|
+
| "gatewayAuthError"
|
|
107
|
+
| "resolveGatewayAuth"
|
|
97
108
|
>;
|
|
98
109
|
|
|
99
110
|
type UnipileAuthContext = Pick<ToolCliContext, "convex" | "sessionToken">;
|
|
@@ -152,43 +163,9 @@ const connectCalendarRef = makeFunctionReference<"action">(
|
|
|
152
163
|
"functions/calendarUnipileNode:createCalendarHostedAuthLink",
|
|
153
164
|
);
|
|
154
165
|
|
|
155
|
-
/**
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
* never execute, and showing them next to the ones that work just teaches an
|
|
159
|
-
* agent to pick a tool that always answers `composio_not_connected`.
|
|
160
|
-
*
|
|
161
|
-
* The set comes from the server with the tools themselves rather than being
|
|
162
|
-
* hardcoded here, so a fifth platform needs no CLI change.
|
|
163
|
-
*/
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Composio spells some toolkits without a separator (`googlecalendar`), while
|
|
167
|
-
* our registry uses the hyphenated form. Normalize through this map before the
|
|
168
|
-
* membership test, or the dead Composio calendar entries survive the filter
|
|
169
|
-
* and sit next to the working ones under two names for one product.
|
|
170
|
-
*/
|
|
171
|
-
const COMPOSIO_TOOLKIT_ALIASES: Record<string, string> = {
|
|
172
|
-
googlecalendar: "google-calendar",
|
|
173
|
-
outlookcalendar: "outlook-calendar",
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
function withoutShadowedComposioTools(
|
|
177
|
-
items: JsonRecord[],
|
|
178
|
-
ownedToolkits: Set<string>,
|
|
179
|
-
): JsonRecord[] {
|
|
180
|
-
if (ownedToolkits.size === 0) return items;
|
|
181
|
-
return items.filter((item) => {
|
|
182
|
-
const slug = (
|
|
183
|
-
stringField(item, "toolkit_slug") ??
|
|
184
|
-
stringField(asRecord(item.toolkit), "slug", "name") ??
|
|
185
|
-
""
|
|
186
|
-
)
|
|
187
|
-
.toLowerCase()
|
|
188
|
-
.replaceAll("_", "-");
|
|
189
|
-
const canonical = COMPOSIO_TOOLKIT_ALIASES[slug] ?? slug;
|
|
190
|
-
return !canonical || !ownedToolkits.has(canonical);
|
|
191
|
-
});
|
|
166
|
+
/** Native ownership survives empty searches, disconnections and registry failures. */
|
|
167
|
+
function withoutShadowedComposioTools(items: JsonRecord[]): JsonRecord[] {
|
|
168
|
+
return items.filter((item) => !isNativeToolRecord(item));
|
|
192
169
|
}
|
|
193
170
|
|
|
194
171
|
/**
|
|
@@ -277,14 +254,16 @@ export async function fetchConnectedAppsSummary(
|
|
|
277
254
|
connections: unipile.connections,
|
|
278
255
|
...unipileFields,
|
|
279
256
|
};
|
|
280
|
-
return
|
|
281
|
-
? { ...failure, reason:
|
|
282
|
-
:
|
|
257
|
+
return ctx.gatewayAuthError
|
|
258
|
+
? { ...failure, reason: ctx.gatewayAuthError }
|
|
259
|
+
: error instanceof MissingApiKeyError
|
|
260
|
+
? { ...failure, reason: error.message }
|
|
261
|
+
: { ...failure, error: errorMessage(error) };
|
|
283
262
|
}
|
|
284
263
|
const connections = [
|
|
285
264
|
...extractConnections(payload)
|
|
286
265
|
.map(compactConnection)
|
|
287
|
-
.filter((connection) => !
|
|
266
|
+
.filter((connection) => !nativeToolkit(connection.toolkit)),
|
|
288
267
|
...unipile.connections,
|
|
289
268
|
];
|
|
290
269
|
return {
|
|
@@ -305,7 +284,7 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
305
284
|
: ctx.args[0];
|
|
306
285
|
if (requestedToolkit) ctx.args.shift();
|
|
307
286
|
const unipileToolkit = requestedToolkit
|
|
308
|
-
?
|
|
287
|
+
? nativeToolkit(requestedToolkit)
|
|
309
288
|
: null;
|
|
310
289
|
const filterToolkit = unipileToolkit ?? requestedToolkit;
|
|
311
290
|
|
|
@@ -369,7 +348,11 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
369
348
|
}
|
|
370
349
|
|
|
371
350
|
async function runSearchSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
372
|
-
const
|
|
351
|
+
const requestedToolkit = getFlag(ctx.args, "--toolkit");
|
|
352
|
+
const ownedToolkit = requestedToolkit
|
|
353
|
+
? nativeToolkit(requestedToolkit)
|
|
354
|
+
: null;
|
|
355
|
+
const toolkit = ownedToolkit ?? requestedToolkit;
|
|
373
356
|
const limitRaw = getFlag(ctx.args, "--limit");
|
|
374
357
|
const limit = limitRaw ? parsePositiveInt("--limit", limitRaw) : undefined;
|
|
375
358
|
const compact = hasFlag(ctx.args, "--compact");
|
|
@@ -395,28 +378,43 @@ async function runSearchSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
395
378
|
if (toolkit) body.toolkit_slug = toolkit;
|
|
396
379
|
if (limit) body.limit = limit;
|
|
397
380
|
|
|
398
|
-
const [
|
|
399
|
-
|
|
381
|
+
const [gateway, unipile] = await Promise.all([
|
|
382
|
+
ownedToolkit
|
|
383
|
+
? Promise.resolve({
|
|
384
|
+
payload: {} as unknown,
|
|
385
|
+
error: undefined as string | undefined,
|
|
386
|
+
})
|
|
387
|
+
: callGateway(ctx, "POST", "/v1/composio/tools/search", body)
|
|
388
|
+
.then((payload) => ({
|
|
389
|
+
payload,
|
|
390
|
+
error: undefined as string | undefined,
|
|
391
|
+
}))
|
|
392
|
+
.catch((error) => ({
|
|
393
|
+
payload: {} as unknown,
|
|
394
|
+
error: errorMessage(error),
|
|
395
|
+
})),
|
|
400
396
|
fetchUnipileTools(ctx, { query, ...(toolkit ? { toolkit } : {}) }),
|
|
401
397
|
]);
|
|
402
398
|
|
|
403
|
-
const record = asRecord(payload) ?? {};
|
|
404
|
-
const ownedToolkits = new Set(
|
|
405
|
-
unipile.tools
|
|
406
|
-
.map((tool) => stringField(asRecord(tool.toolkit), "slug") ?? "")
|
|
407
|
-
.filter(Boolean),
|
|
408
|
-
);
|
|
399
|
+
const record = asRecord(gateway.payload) ?? {};
|
|
409
400
|
const merged = {
|
|
410
|
-
|
|
401
|
+
query,
|
|
411
402
|
items: [
|
|
403
|
+
...unipile.tools,
|
|
412
404
|
...withoutShadowedComposioTools(
|
|
413
|
-
extractSearchItems(record),
|
|
414
|
-
|
|
405
|
+
extractSearchItems(asRecord(record.data) ?? record),
|
|
406
|
+
),
|
|
407
|
+
].slice(0, limit ?? 100),
|
|
408
|
+
// Do not retain unfiltered nested tool_schemas/data from the gateway.
|
|
409
|
+
connected_toolkits: [
|
|
410
|
+
...[...connectedToolkitSet(asRecord(record.data) ?? record)].filter(
|
|
411
|
+
(slug) => !nativeToolkit(slug),
|
|
415
412
|
),
|
|
416
|
-
...unipile.tools,
|
|
417
413
|
],
|
|
418
414
|
nativeAvailable: unipile.available,
|
|
419
415
|
...(unipile.error ? { nativeError: unipile.error } : {}),
|
|
416
|
+
...(gateway.error ? { gatewayError: gateway.error } : {}),
|
|
417
|
+
...(ownedToolkit ? { toolkit: ownedToolkit } : {}),
|
|
420
418
|
};
|
|
421
419
|
|
|
422
420
|
if (ctx.jsonOutput) {
|
|
@@ -427,20 +425,27 @@ async function runSearchSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
427
425
|
}
|
|
428
426
|
|
|
429
427
|
/**
|
|
430
|
-
*
|
|
431
|
-
* deployment routes everything to Composio exactly as it did before.
|
|
428
|
+
* Resolve a native action without interpreting a broken registry as another provider.
|
|
432
429
|
*/
|
|
433
430
|
async function resolveUnipileTool(
|
|
434
431
|
ctx: UnipileAuthContext,
|
|
435
432
|
slug: string,
|
|
436
433
|
): Promise<JsonRecord | null> {
|
|
437
|
-
if (!ctx.convex)
|
|
434
|
+
if (!ctx.convex) {
|
|
435
|
+
if (nativeToolkitForTool(slug))
|
|
436
|
+
throw new ToolCliError(
|
|
437
|
+
"native_unavailable: Sign in to Dench to use this integration.",
|
|
438
|
+
);
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
438
441
|
try {
|
|
439
442
|
return (
|
|
440
443
|
asRecord(await ctx.convex.query(getUnipileToolRef, { slug } as never)) ??
|
|
441
444
|
null
|
|
442
445
|
);
|
|
443
|
-
} catch {
|
|
446
|
+
} catch (error) {
|
|
447
|
+
if (nativeToolkitForTool(slug))
|
|
448
|
+
throw new ToolCliError(`native_unavailable: ${errorMessage(error)}`);
|
|
444
449
|
return null;
|
|
445
450
|
}
|
|
446
451
|
}
|
|
@@ -528,7 +533,11 @@ async function fetchUnipileTools(
|
|
|
528
533
|
.filter((row): row is JsonRecord => row !== undefined)
|
|
529
534
|
.map((row) =>
|
|
530
535
|
typeof row.source === "string"
|
|
531
|
-
? {
|
|
536
|
+
? {
|
|
537
|
+
...row,
|
|
538
|
+
source: connectionSource(row.source, "dench"),
|
|
539
|
+
connection_status: { is_connected: true },
|
|
540
|
+
}
|
|
532
541
|
: row,
|
|
533
542
|
),
|
|
534
543
|
};
|
|
@@ -565,9 +574,18 @@ async function runRunSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
565
574
|
// routed to Composio, which 404s.
|
|
566
575
|
const unipileTool = await resolveUnipileTool(ctx, toolSlug);
|
|
567
576
|
if (unipileTool) {
|
|
577
|
+
if (account)
|
|
578
|
+
throw new ToolCliError(
|
|
579
|
+
"--account is for third-party connections. Pass the native mailbox/account field in --args.",
|
|
580
|
+
);
|
|
568
581
|
await runUnipileTool(ctx, toolSlug, parsedArgs, approval);
|
|
569
582
|
return;
|
|
570
583
|
}
|
|
584
|
+
if (nativeToolkitForTool(toolSlug)) {
|
|
585
|
+
throw new ToolCliError(
|
|
586
|
+
`unsupported_native_capability: ${toolSlug} is not supported by Dench's native integration. For email drafts use dench email draft create; do not reconnect or try another provider.`,
|
|
587
|
+
);
|
|
588
|
+
}
|
|
571
589
|
|
|
572
590
|
const body: Record<string, unknown> = {
|
|
573
591
|
tool_slug: toolSlug,
|
|
@@ -697,6 +715,21 @@ async function runConnectSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
697
715
|
ctx.args.shift();
|
|
698
716
|
|
|
699
717
|
const normalizedToolkit = toolkit.trim().toLowerCase();
|
|
718
|
+
// Hosted chat owns the human connection UI. Do not mint OAuth credentials
|
|
719
|
+
// in a model command or journal them in a tool result. External CLI clients
|
|
720
|
+
// retain their ordinary link-based flow.
|
|
721
|
+
if (process.env.DENCH_CHAT_ARTIFACTS === "1") {
|
|
722
|
+
console.log(
|
|
723
|
+
JSON.stringify({
|
|
724
|
+
ok: true,
|
|
725
|
+
kind: "connect",
|
|
726
|
+
toolkit: nativeToolkit(toolkit) ?? normalizedToolkit,
|
|
727
|
+
action: "connect",
|
|
728
|
+
}),
|
|
729
|
+
);
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
|
|
700
733
|
if (normalizedToolkit === "slack" || normalizedToolkit === "slackbot") {
|
|
701
734
|
await runSlackConnectSubcommand(ctx, normalizedToolkit);
|
|
702
735
|
return;
|
|
@@ -920,6 +953,11 @@ async function callGateway(
|
|
|
920
953
|
path: string,
|
|
921
954
|
body?: Record<string, unknown>,
|
|
922
955
|
): Promise<unknown> {
|
|
956
|
+
if (ctx.resolveGatewayAuth && !ctx.bearerToken) {
|
|
957
|
+
const resolve = ctx.resolveGatewayAuth;
|
|
958
|
+
ctx.resolveGatewayAuth = undefined;
|
|
959
|
+
Object.assign(ctx, await resolve());
|
|
960
|
+
}
|
|
923
961
|
const bearerToken = requireBearerToken(ctx);
|
|
924
962
|
const baseUrl = resolveGatewayBaseUrl(ctx);
|
|
925
963
|
const init: RequestInit = {
|
|
@@ -1239,6 +1277,13 @@ function formatSearchResult(searchResult: unknown, compact = false): string {
|
|
|
1239
1277
|
const items = extractSearchItems(data);
|
|
1240
1278
|
const connectedSet = connectedToolkitSet(data);
|
|
1241
1279
|
|
|
1280
|
+
if (
|
|
1281
|
+
items.length === 0 &&
|
|
1282
|
+
typeof data?.toolkit === "string" &&
|
|
1283
|
+
nativeToolkit(data.toolkit)
|
|
1284
|
+
) {
|
|
1285
|
+
return `No matching native action for ${data.toolkit}. For email drafts use dench email draft create; otherwise inspect dench tool status ${data.toolkit} --json. Native apps never fall back to Composio.`;
|
|
1286
|
+
}
|
|
1242
1287
|
if (items.length === 0) {
|
|
1243
1288
|
return [
|
|
1244
1289
|
"No matching tools found.",
|