@dench.com/cli 2.1.1 → 2.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-config.ts +3 -126
- package/browser.ts +507 -0
- package/chat.ts +1 -1
- package/crm.ts +209 -0
- package/dench.ts +173 -49
- package/fs-daemon.ts +201 -2
- package/lib/api-schemas.ts +1095 -0
- package/lib/command-registry.ts +1816 -0
- package/lib/enrichment-gateway.ts +23 -12
- package/lib/organizationSelector.ts +58 -0
- package/linkedin.ts +210 -0
- package/package.json +6 -2
package/crm.ts
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* The full surface mirrors the plan's "dench-cli CRM surface" section:
|
|
18
18
|
* crm objects list / get / create / update / delete / rename
|
|
19
|
+
* crm views list / get / entries
|
|
19
20
|
* crm fields list / create / update / delete / reorder
|
|
20
21
|
* crm entries list / get / create / create-many / update / update-many / delete / bulk-delete
|
|
21
22
|
* crm cells get / set / set-many / append
|
|
@@ -330,6 +331,29 @@ async function callMutation(
|
|
|
330
331
|
}
|
|
331
332
|
}
|
|
332
333
|
|
|
334
|
+
async function suppressForwardedConvexLogs<T>(
|
|
335
|
+
fn: () => Promise<T>,
|
|
336
|
+
): Promise<T> {
|
|
337
|
+
const originalLog = console.log;
|
|
338
|
+
const originalInfo = console.info;
|
|
339
|
+
const shouldSuppress = (args: unknown[]) =>
|
|
340
|
+
typeof args[0] === "string" && args[0].startsWith("[CONVEX ");
|
|
341
|
+
console.log = (...args: Parameters<typeof console.log>) => {
|
|
342
|
+
if (shouldSuppress(args)) return;
|
|
343
|
+
originalLog(...args);
|
|
344
|
+
};
|
|
345
|
+
console.info = (...args: Parameters<typeof console.info>) => {
|
|
346
|
+
if (shouldSuppress(args)) return;
|
|
347
|
+
originalInfo(...args);
|
|
348
|
+
};
|
|
349
|
+
try {
|
|
350
|
+
return await fn();
|
|
351
|
+
} finally {
|
|
352
|
+
console.log = originalLog;
|
|
353
|
+
console.info = originalInfo;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
333
357
|
function out(ctx: CrmCliContext, value: unknown): void {
|
|
334
358
|
if (ctx.jsonOutput) {
|
|
335
359
|
console.log(JSON.stringify(value, null, 2));
|
|
@@ -410,6 +434,9 @@ const api = {
|
|
|
410
434
|
objects: {
|
|
411
435
|
list: makeFunctionReference<"query">("functions/crm/objects:list"),
|
|
412
436
|
get: makeFunctionReference<"query">("functions/crm/objects:get"),
|
|
437
|
+
materializeView: makeFunctionReference<"query">(
|
|
438
|
+
"functions/crm/objects:materializeView",
|
|
439
|
+
),
|
|
413
440
|
create: makeFunctionReference<"mutation">("functions/crm/objects:create"),
|
|
414
441
|
update: makeFunctionReference<"mutation">("functions/crm/objects:update"),
|
|
415
442
|
rename: makeFunctionReference<"mutation">("functions/crm/objects:rename"),
|
|
@@ -507,6 +534,8 @@ export async function runCrmCommand(opts: {
|
|
|
507
534
|
switch (subcommand) {
|
|
508
535
|
case "objects":
|
|
509
536
|
return await runObjectsCommand(ctx);
|
|
537
|
+
case "views":
|
|
538
|
+
return await runViewsCommand(ctx);
|
|
510
539
|
case "fields":
|
|
511
540
|
return await runFieldsCommand(ctx);
|
|
512
541
|
case "entries":
|
|
@@ -636,6 +665,181 @@ async function runObjectsCommand(ctx: CrmCliContext): Promise<void> {
|
|
|
636
665
|
}
|
|
637
666
|
}
|
|
638
667
|
|
|
668
|
+
type SavedViewForCli = Record<string, unknown> & {
|
|
669
|
+
name?: unknown;
|
|
670
|
+
pinnedEntryIds?: unknown;
|
|
671
|
+
filters?: unknown;
|
|
672
|
+
sort?: unknown;
|
|
673
|
+
};
|
|
674
|
+
|
|
675
|
+
type CrmObjectWithViews = {
|
|
676
|
+
name?: string;
|
|
677
|
+
savedViews?: SavedViewForCli[];
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
function savedViewsFromObject(
|
|
681
|
+
objectName: string,
|
|
682
|
+
object: unknown,
|
|
683
|
+
): SavedViewForCli[] {
|
|
684
|
+
if (!object || typeof object !== "object") {
|
|
685
|
+
throw new CrmCliError(`CRM object "${objectName}" not found`);
|
|
686
|
+
}
|
|
687
|
+
const savedViews = (object as CrmObjectWithViews).savedViews;
|
|
688
|
+
return Array.isArray(savedViews) ? savedViews : [];
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function readViewName(view: SavedViewForCli): string | null {
|
|
692
|
+
return typeof view.name === "string" && view.name.trim()
|
|
693
|
+
? view.name.trim()
|
|
694
|
+
: null;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function resolveSavedView(
|
|
698
|
+
objectName: string,
|
|
699
|
+
views: SavedViewForCli[],
|
|
700
|
+
requestedName: string,
|
|
701
|
+
): SavedViewForCli {
|
|
702
|
+
const trimmed = requestedName.trim();
|
|
703
|
+
const exact = views.find((view) => readViewName(view) === trimmed);
|
|
704
|
+
if (exact) return exact;
|
|
705
|
+
|
|
706
|
+
const lower = trimmed.toLowerCase();
|
|
707
|
+
const insensitiveMatches = views.filter(
|
|
708
|
+
(view) => readViewName(view)?.toLowerCase() === lower,
|
|
709
|
+
);
|
|
710
|
+
if (insensitiveMatches.length === 1) return insensitiveMatches[0];
|
|
711
|
+
if (insensitiveMatches.length > 1) {
|
|
712
|
+
throw new CrmCliError(
|
|
713
|
+
`Multiple views named like "${requestedName}" exist on ${objectName}; use the exact case-sensitive name.`,
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const available = views.map(readViewName).filter(Boolean).join(", ");
|
|
718
|
+
throw new CrmCliError(
|
|
719
|
+
`View "${requestedName}" not found on ${objectName}${
|
|
720
|
+
available ? `. Available views: ${available}` : ". No saved views exist."
|
|
721
|
+
}`,
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function summarizeSavedView(view: SavedViewForCli): Record<string, unknown> {
|
|
726
|
+
const pinned = Array.isArray(view.pinnedEntryIds)
|
|
727
|
+
? view.pinnedEntryIds
|
|
728
|
+
: undefined;
|
|
729
|
+
return {
|
|
730
|
+
...view,
|
|
731
|
+
pinnedCount: pinned?.length ?? 0,
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
async function runViewsCommand(ctx: CrmCliContext): Promise<void> {
|
|
736
|
+
const verb = ctx.args.shift();
|
|
737
|
+
switch (verb) {
|
|
738
|
+
case "list": {
|
|
739
|
+
const objectName = shift(ctx.args, "object name");
|
|
740
|
+
assertNoUnknownFlags(ctx.args, "crm views list");
|
|
741
|
+
const object = await callQuery(ctx, api.objects.get, {
|
|
742
|
+
name: objectName,
|
|
743
|
+
});
|
|
744
|
+
const views = savedViewsFromObject(objectName, object).map(
|
|
745
|
+
summarizeSavedView,
|
|
746
|
+
);
|
|
747
|
+
out(ctx, views);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
case "get": {
|
|
751
|
+
const objectName = shift(ctx.args, "object name");
|
|
752
|
+
const viewName = shift(ctx.args, "view name");
|
|
753
|
+
assertNoUnknownFlags(ctx.args, "crm views get");
|
|
754
|
+
const object = await callQuery(ctx, api.objects.get, {
|
|
755
|
+
name: objectName,
|
|
756
|
+
});
|
|
757
|
+
const view = resolveSavedView(
|
|
758
|
+
objectName,
|
|
759
|
+
savedViewsFromObject(objectName, object),
|
|
760
|
+
viewName,
|
|
761
|
+
);
|
|
762
|
+
out(ctx, summarizeSavedView(view));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
case "entries": {
|
|
766
|
+
const objectName = shift(ctx.args, "object name");
|
|
767
|
+
const viewName = shift(ctx.args, "view name");
|
|
768
|
+
const page = Number.parseInt(getFlag(ctx.args, "--page") ?? "1", 10);
|
|
769
|
+
const pageSize = Number.parseInt(
|
|
770
|
+
getFlagWithAliases(ctx.args, "--page-size", ["--limit"]) ?? "100",
|
|
771
|
+
10,
|
|
772
|
+
);
|
|
773
|
+
const withMeta = hasFlag(ctx.args, "--with-meta");
|
|
774
|
+
assertNoUnknownFlags(ctx.args, "crm views entries");
|
|
775
|
+
if (!Number.isFinite(page) || page < 1) {
|
|
776
|
+
throw new CrmCliError(`--page must be a positive integer, got ${page}`);
|
|
777
|
+
}
|
|
778
|
+
if (!Number.isFinite(pageSize) || pageSize < 1) {
|
|
779
|
+
throw new CrmCliError(
|
|
780
|
+
`--page-size/--limit must be a positive integer, got ${pageSize}`,
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
const object = await callQuery(ctx, api.objects.get, {
|
|
785
|
+
name: objectName,
|
|
786
|
+
});
|
|
787
|
+
const view = resolveSavedView(
|
|
788
|
+
objectName,
|
|
789
|
+
savedViewsFromObject(objectName, object),
|
|
790
|
+
viewName,
|
|
791
|
+
);
|
|
792
|
+
const pinnedEntryIds = Array.isArray(view.pinnedEntryIds)
|
|
793
|
+
? view.pinnedEntryIds.filter(
|
|
794
|
+
(id): id is string => typeof id === "string" && id.length > 0,
|
|
795
|
+
)
|
|
796
|
+
: undefined;
|
|
797
|
+
const materialized = (await suppressForwardedConvexLogs(() =>
|
|
798
|
+
callQuery(ctx, api.objects.materializeView, {
|
|
799
|
+
objectName,
|
|
800
|
+
page,
|
|
801
|
+
pageSize,
|
|
802
|
+
filters: view.filters,
|
|
803
|
+
sort: Array.isArray(view.sort) ? view.sort : undefined,
|
|
804
|
+
pinnedEntryIds,
|
|
805
|
+
includeReverseRelations: false,
|
|
806
|
+
}),
|
|
807
|
+
)) as {
|
|
808
|
+
entries?: unknown[];
|
|
809
|
+
totalCount?: number;
|
|
810
|
+
page?: number;
|
|
811
|
+
pageSize?: number;
|
|
812
|
+
truncated?: boolean;
|
|
813
|
+
} | null;
|
|
814
|
+
|
|
815
|
+
if (!materialized) {
|
|
816
|
+
throw new CrmCliError(`CRM object "${objectName}" not found`);
|
|
817
|
+
}
|
|
818
|
+
const entries = Array.isArray(materialized.entries)
|
|
819
|
+
? materialized.entries
|
|
820
|
+
: [];
|
|
821
|
+
if (!withMeta) {
|
|
822
|
+
out(ctx, entries);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
out(ctx, {
|
|
826
|
+
objectName,
|
|
827
|
+
viewName: readViewName(view) ?? viewName,
|
|
828
|
+
view: summarizeSavedView(view),
|
|
829
|
+
totalCount: materialized.totalCount ?? entries.length,
|
|
830
|
+
returnedCount: entries.length,
|
|
831
|
+
page: materialized.page ?? page,
|
|
832
|
+
pageSize: materialized.pageSize ?? pageSize,
|
|
833
|
+
truncated: materialized.truncated ?? false,
|
|
834
|
+
entries,
|
|
835
|
+
});
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
default:
|
|
839
|
+
throw new CrmCliError(`Unknown crm views verb: ${verb ?? "<none>"}`);
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
639
843
|
async function runFieldsCommand(ctx: CrmCliContext): Promise<void> {
|
|
640
844
|
const verb = ctx.args.shift();
|
|
641
845
|
switch (verb) {
|
|
@@ -2418,6 +2622,11 @@ Objects (CRM tables):
|
|
|
2418
2622
|
dench crm objects rename <from> <to>
|
|
2419
2623
|
dench crm objects delete <name>
|
|
2420
2624
|
|
|
2625
|
+
Views (saved CRM subsets):
|
|
2626
|
+
dench crm views list <object> [--json]
|
|
2627
|
+
dench crm views get <object> <view-name> [--json]
|
|
2628
|
+
dench crm views entries <object> <view-name> [--limit N] [--page N] [--with-meta] [--json]
|
|
2629
|
+
|
|
2421
2630
|
Fields (columns):
|
|
2422
2631
|
dench crm fields list <object> [--json]
|
|
2423
2632
|
dench crm fields create <object> <name> --type text|email|phone|url|number|boolean|date|enum|relation|user|tags|action|file|richtext \\
|
package/dench.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
resolveHostFromArgs,
|
|
28
28
|
STAGING_HOST,
|
|
29
29
|
} from "./host";
|
|
30
|
+
import { resolveOrganizationBySelector } from "./lib/organizationSelector";
|
|
30
31
|
import { formatApprovalOpenMessage, openUrl } from "./openUrl";
|
|
31
32
|
import {
|
|
32
33
|
explicitSessionKeyInput,
|
|
@@ -94,6 +95,10 @@ type ConfigFile = {
|
|
|
94
95
|
sessions?: Record<string, StoredSession>;
|
|
95
96
|
};
|
|
96
97
|
|
|
98
|
+
function createCliConvexClient(convexUrl: string): ConvexHttpClient {
|
|
99
|
+
return new ConvexHttpClient(convexUrl, { logger: false });
|
|
100
|
+
}
|
|
101
|
+
|
|
97
102
|
type WorkspaceOverview = {
|
|
98
103
|
agents: Array<{ _id: string } & JsonRecord>;
|
|
99
104
|
projects: JsonRecord[];
|
|
@@ -223,6 +228,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
223
228
|
"--compact",
|
|
224
229
|
"--dev",
|
|
225
230
|
"--follow",
|
|
231
|
+
"--full-page",
|
|
226
232
|
"--help",
|
|
227
233
|
"--mine",
|
|
228
234
|
"--self",
|
|
@@ -470,6 +476,29 @@ Live web search (Exa via the Dench Gateway, auths with the active \`dench signin
|
|
|
470
476
|
dench search answer "<query>" [--json]
|
|
471
477
|
Help: dench search help
|
|
472
478
|
|
|
479
|
+
Cloud browser (Browserbase, runs entirely in Dench Cloud):
|
|
480
|
+
dench browser open [url] [--json]
|
|
481
|
+
dench browser inspect [--json]
|
|
482
|
+
dench browser navigate <url>
|
|
483
|
+
dench browser click --selector <css> | --text "Button text"
|
|
484
|
+
dench browser fill --label "Email" --value "me@example.com"
|
|
485
|
+
dench browser screenshot [--output page.jpg] [--full-page]
|
|
486
|
+
dench browser stop
|
|
487
|
+
Help: dench browser help
|
|
488
|
+
|
|
489
|
+
LinkedIn outreach (Browserbase, verified before CRM update):
|
|
490
|
+
dench linkedin connect <profileUrl> [--entry people:<entryId>] [--json]
|
|
491
|
+
Help: dench linkedin help
|
|
492
|
+
|
|
493
|
+
Managed email campaigns (Dench Emailing Service):
|
|
494
|
+
dench email identity verify --from founder@example.com [--json]
|
|
495
|
+
dench email template create --name "Intro" --subject "Hi {{firstName}}" --html-file body.html [--json]
|
|
496
|
+
dench email campaign create --name "Q2 outreach" --identity <id> --template <id> [--json]
|
|
497
|
+
dench email campaign recipients add --campaign <id> --file recipients.jsonl [--json]
|
|
498
|
+
dench email campaign submit --campaign <id> [--json]
|
|
499
|
+
dench email campaign status --campaign <id> [--json]
|
|
500
|
+
Help: dench email help
|
|
501
|
+
|
|
473
502
|
Image generation / editing (via the Dench Gateway):
|
|
474
503
|
dench image generate "<prompt>" [--size 1024x1024] [--quality auto|low|medium|high] [--format png|jpeg|webp] [--save-path /workspace/path/file.png] [--output <local file>] [--no-write] [--json]
|
|
475
504
|
dench image edit "<prompt>" --input <path> [--input <path> ...] [--mask <path>] [--size ...] [--quality ...] [--format ...] [--save-path ...] [--output ...] [--json]
|
|
@@ -499,6 +528,7 @@ Spawn a new chat-turn workflow (same as the web UI):
|
|
|
499
528
|
File sync:
|
|
500
529
|
dench fs status [--json] [--workspace /workspace] [--no-hash] [--drift-limit N]
|
|
501
530
|
dench fs sync --initial --org-id <orgId> [--workspace /workspace]
|
|
531
|
+
dench fs pull <path> [<path>...] [--wait] [--timeout-ms N] [--json]
|
|
502
532
|
dench fs stream-open <path>
|
|
503
533
|
dench fs stream-append <token> <chunk>
|
|
504
534
|
dench fs stream-close <token>
|
|
@@ -531,8 +561,6 @@ Self-updating agent harness:
|
|
|
531
561
|
dench user show | edit USER.md (per-member)
|
|
532
562
|
dench tools show | edit TOOLS.md notes section
|
|
533
563
|
dench mem show | append "<text>" | set MEMORY.md aggregate
|
|
534
|
-
dench heartbeat status | enable | disable
|
|
535
|
-
| interval <30m|1h|…> | instructions
|
|
536
564
|
dench bootstrap status | complete | reopen | template
|
|
537
565
|
dench model default <id|clear>
|
|
538
566
|
dench model thread <threadId> <id|clear>
|
|
@@ -1585,7 +1613,7 @@ async function signin() {
|
|
|
1585
1613
|
const explicitKey = explicitSessionKeyInput({ args: filteredArgs });
|
|
1586
1614
|
const host = resolveHostFromArgs(filteredArgs, undefined);
|
|
1587
1615
|
const convexUrl = await discoverConvexUrl(host);
|
|
1588
|
-
const client =
|
|
1616
|
+
const client = createCliConvexClient(convexUrl);
|
|
1589
1617
|
|
|
1590
1618
|
const sessionToken = `dch_agent_${randomUrlSafe(32)}`;
|
|
1591
1619
|
const sessionTokenHash = await sha256Hex(sessionToken);
|
|
@@ -1970,11 +1998,9 @@ async function resolveOrgChoiceForOtp(input: {
|
|
|
1970
1998
|
orgSelector: string | null;
|
|
1971
1999
|
}): Promise<{ organizationId?: string; newOrganizationName?: string }> {
|
|
1972
2000
|
if (input.orgSelector) {
|
|
1973
|
-
const match =
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
org.slug === input.orgSelector ||
|
|
1977
|
-
org.name === input.orgSelector,
|
|
2001
|
+
const match = resolveOrganizationBySelector(
|
|
2002
|
+
input.organizations,
|
|
2003
|
+
input.orgSelector,
|
|
1978
2004
|
);
|
|
1979
2005
|
if (!match) {
|
|
1980
2006
|
throw new CliError(
|
|
@@ -2325,7 +2351,7 @@ async function getRuntime() {
|
|
|
2325
2351
|
return {
|
|
2326
2352
|
mode: "session" as const,
|
|
2327
2353
|
host: resolveApiHost(host),
|
|
2328
|
-
client:
|
|
2354
|
+
client: createCliConvexClient(stored.session.convexUrl),
|
|
2329
2355
|
sessionToken: stored.session.sessionToken,
|
|
2330
2356
|
organization: stored.session.organization,
|
|
2331
2357
|
};
|
|
@@ -2340,48 +2366,55 @@ async function getRuntime() {
|
|
|
2340
2366
|
});
|
|
2341
2367
|
}
|
|
2342
2368
|
|
|
2343
|
-
// Sandbox session-mode path: workflow minting provisions a real
|
|
2344
|
-
// dch_agent_* token at sandbox-create time. Prefer it over apiKey so
|
|
2345
|
-
// agentWorkspace.* commands (Composio tools) work inside sandboxes.
|
|
2346
|
-
const sandboxAgentToken = process.env.DENCH_AGENT_SESSION_TOKEN?.trim();
|
|
2347
2369
|
const sandboxConvexUrl = resolveApiKeyConvexUrl();
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2370
|
+
const orgId = process.env.DENCH_ORG_ID?.trim();
|
|
2371
|
+
const orgSlug = process.env.DENCH_ORG_SLUG?.trim();
|
|
2372
|
+
const sandboxOrganization =
|
|
2373
|
+
orgId && orgSlug
|
|
2374
|
+
? { id: orgId, name: orgSlug, slug: orgSlug }
|
|
2375
|
+
: undefined;
|
|
2376
|
+
|
|
2377
|
+
// Sandbox / automation path. PREFER the unified Dench API key over the
|
|
2378
|
+
// `dch_agent_*` agent-session token.
|
|
2379
|
+
//
|
|
2380
|
+
// Both are baked into sandbox envVars (src/lib/secrets/sandbox-tokens.ts)
|
|
2381
|
+
// and the server's `requireCrmAccess` / `requireCliWorkspaceAccess`
|
|
2382
|
+
// accept EITHER for the full CRM + files + integration surface. But the
|
|
2383
|
+
// agent-session token EXPIRES (SANDBOX_AGENT_SESSION_TTL_MS, 7d) while
|
|
2384
|
+
// the API key is org-secret-keyed with no TTL. The old order tried the
|
|
2385
|
+
// agent token first and — critically — `getRuntime` keys the session
|
|
2386
|
+
// branch purely on the token being PRESENT, never on validity, so an
|
|
2387
|
+
// expired token in a long-lived / reused sandbox would be used blindly
|
|
2388
|
+
// and the server would reject it, even though a perfectly valid API key
|
|
2389
|
+
// sat right next to it. The server itself documents the API key as the
|
|
2390
|
+
// "preferred path for dench-cli and any automation outside the browser"
|
|
2391
|
+
// and the agent token as legacy back-compat that "new sandboxes do not
|
|
2392
|
+
// need". So prefer the key; it removes the expired-token failure mode
|
|
2393
|
+
// entirely for unattended cron/heartbeat runs.
|
|
2394
|
+
const apiKey = process.env.DENCH_API_KEY?.trim();
|
|
2395
|
+
if (apiKey && sandboxConvexUrl) {
|
|
2351
2396
|
const apiHost = resolveApiHost(host);
|
|
2352
2397
|
return {
|
|
2353
|
-
mode: "
|
|
2398
|
+
mode: "apiKey" as const,
|
|
2354
2399
|
host: apiHost,
|
|
2355
|
-
client:
|
|
2356
|
-
sessionToken:
|
|
2357
|
-
organization:
|
|
2358
|
-
orgId && orgSlug
|
|
2359
|
-
? { id: orgId, name: orgSlug, slug: orgSlug }
|
|
2360
|
-
: undefined,
|
|
2400
|
+
client: createCliConvexClient(sandboxConvexUrl),
|
|
2401
|
+
sessionToken: apiKey,
|
|
2402
|
+
organization: sandboxOrganization,
|
|
2361
2403
|
};
|
|
2362
2404
|
}
|
|
2363
2405
|
|
|
2364
|
-
//
|
|
2365
|
-
//
|
|
2366
|
-
//
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
// explicit `dench signin` setups keep their existing behavior.
|
|
2370
|
-
const apiKey = process.env.DENCH_API_KEY?.trim();
|
|
2371
|
-
const convexUrl = resolveApiKeyConvexUrl();
|
|
2372
|
-
if (apiKey && convexUrl) {
|
|
2373
|
-
const orgId = process.env.DENCH_ORG_ID?.trim();
|
|
2374
|
-
const orgSlug = process.env.DENCH_ORG_SLUG?.trim();
|
|
2406
|
+
// Fallback: no API key present (older sandbox images / custom setups) —
|
|
2407
|
+
// use the agent-session token. Still subject to expiry, but it's the
|
|
2408
|
+
// only credential available in this branch.
|
|
2409
|
+
const sandboxAgentToken = process.env.DENCH_AGENT_SESSION_TOKEN?.trim();
|
|
2410
|
+
if (sandboxAgentToken && sandboxConvexUrl) {
|
|
2375
2411
|
const apiHost = resolveApiHost(host);
|
|
2376
2412
|
return {
|
|
2377
|
-
mode: "
|
|
2413
|
+
mode: "session" as const,
|
|
2378
2414
|
host: apiHost,
|
|
2379
|
-
client:
|
|
2380
|
-
sessionToken:
|
|
2381
|
-
organization:
|
|
2382
|
-
orgId && orgSlug
|
|
2383
|
-
? { id: orgId, name: orgSlug, slug: orgSlug }
|
|
2384
|
-
: undefined,
|
|
2415
|
+
client: createCliConvexClient(sandboxConvexUrl),
|
|
2416
|
+
sessionToken: sandboxAgentToken,
|
|
2417
|
+
organization: sandboxOrganization,
|
|
2385
2418
|
};
|
|
2386
2419
|
}
|
|
2387
2420
|
|
|
@@ -2394,7 +2427,7 @@ async function getRuntime() {
|
|
|
2394
2427
|
|
|
2395
2428
|
return {
|
|
2396
2429
|
mode: "dev" as const,
|
|
2397
|
-
client:
|
|
2430
|
+
client: createCliConvexClient(readEnv("NEXT_PUBLIC_CONVEX_URL")),
|
|
2398
2431
|
workspaceArgs: {
|
|
2399
2432
|
devKey: readEnv("DENCH_DEV_AGENT_KEY"),
|
|
2400
2433
|
workspaceSlug: readEnv("DENCH_WORKSPACE"),
|
|
@@ -2933,7 +2966,7 @@ async function listArtifacts(runtime: Runtime, limit?: number) {
|
|
|
2933
2966
|
}
|
|
2934
2967
|
|
|
2935
2968
|
async function listSuggestedWork(runtime: Runtime) {
|
|
2936
|
-
if (runtime.mode === "session") {
|
|
2969
|
+
if (runtime.mode === "session" || runtime.mode === "apiKey") {
|
|
2937
2970
|
const context = await buildContext(runtime);
|
|
2938
2971
|
return asRecordArray(asRecord(context)?.suggestedWork);
|
|
2939
2972
|
}
|
|
@@ -3616,6 +3649,44 @@ async function main() {
|
|
|
3616
3649
|
return;
|
|
3617
3650
|
}
|
|
3618
3651
|
|
|
3652
|
+
if (
|
|
3653
|
+
command === "browser" &&
|
|
3654
|
+
(!subcommand || subcommand === "help" || hasFlag("--help"))
|
|
3655
|
+
) {
|
|
3656
|
+
const { runBrowserCommand } = await import("./browser");
|
|
3657
|
+
await runBrowserCommand({
|
|
3658
|
+
args: subcommand ? [subcommand] : ["help"],
|
|
3659
|
+
runtime: { host: "", bearerToken: "" },
|
|
3660
|
+
});
|
|
3661
|
+
return;
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
if (
|
|
3665
|
+
command === "linkedin" &&
|
|
3666
|
+
(!subcommand || subcommand === "help" || hasFlag("--help"))
|
|
3667
|
+
) {
|
|
3668
|
+
const { runLinkedInCommand } = await import("./linkedin");
|
|
3669
|
+
await runLinkedInCommand({
|
|
3670
|
+
args: subcommand ? [subcommand] : ["help"],
|
|
3671
|
+
runtime: { host: "", bearerToken: "" },
|
|
3672
|
+
});
|
|
3673
|
+
return;
|
|
3674
|
+
}
|
|
3675
|
+
|
|
3676
|
+
if (
|
|
3677
|
+
command === "email" &&
|
|
3678
|
+
(!subcommand || subcommand === "help" || hasFlag("--help"))
|
|
3679
|
+
) {
|
|
3680
|
+
const { runEmailCommand } = await import("./email");
|
|
3681
|
+
await runEmailCommand({
|
|
3682
|
+
// biome-ignore lint/suspicious/noExplicitAny: harmless help-only stub
|
|
3683
|
+
convex: {} as any,
|
|
3684
|
+
args: subcommand ? [subcommand] : ["help"],
|
|
3685
|
+
jsonOutput: json,
|
|
3686
|
+
});
|
|
3687
|
+
return;
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3619
3690
|
if (
|
|
3620
3691
|
command === "billing" &&
|
|
3621
3692
|
(!subcommand || subcommand === "help" || hasFlag("--help"))
|
|
@@ -3847,6 +3918,31 @@ async function main() {
|
|
|
3847
3918
|
return;
|
|
3848
3919
|
}
|
|
3849
3920
|
|
|
3921
|
+
if (command === "email") {
|
|
3922
|
+
const subArgs = args.slice(args.indexOf("email") + 1);
|
|
3923
|
+
const { runEmailCommand } = await import("./email");
|
|
3924
|
+
const runtime = await getRuntime();
|
|
3925
|
+
const authedRuntime = requireAuthenticatedRuntime(runtime, "dench email");
|
|
3926
|
+
let gatewayAuth:
|
|
3927
|
+
| Awaited<ReturnType<typeof resolveGatewayCommandAuth>>
|
|
3928
|
+
| undefined;
|
|
3929
|
+
try {
|
|
3930
|
+
gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench email");
|
|
3931
|
+
} catch {
|
|
3932
|
+
gatewayAuth = undefined;
|
|
3933
|
+
}
|
|
3934
|
+
await runEmailCommand({
|
|
3935
|
+
convex: authedRuntime.client,
|
|
3936
|
+
args: subArgs.filter((arg) => arg !== "--json"),
|
|
3937
|
+
jsonOutput: json,
|
|
3938
|
+
sessionToken: authedRuntime.sessionToken,
|
|
3939
|
+
bearerToken: gatewayAuth?.bearerToken ?? authedRuntime.sessionToken,
|
|
3940
|
+
gatewayBaseUrl: gatewayAuth?.gatewayBaseUrl,
|
|
3941
|
+
gatewayHeaders: gatewayAuth?.gatewayHeaders,
|
|
3942
|
+
});
|
|
3943
|
+
return;
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3850
3946
|
if (command === "apps") {
|
|
3851
3947
|
const runtime = await getRuntime();
|
|
3852
3948
|
const gatewayAuth = await resolveGatewayCommandAuth(runtime, "dench apps");
|
|
@@ -3880,6 +3976,39 @@ async function main() {
|
|
|
3880
3976
|
return;
|
|
3881
3977
|
}
|
|
3882
3978
|
|
|
3979
|
+
if (command === "browser") {
|
|
3980
|
+
const subArgs = args.slice(args.indexOf("browser") + 1);
|
|
3981
|
+
const { runBrowserCommand } = await import("./browser");
|
|
3982
|
+
const runtime = await getRuntime();
|
|
3983
|
+
const authedRuntime = requireAuthenticatedRuntime(runtime, "dench browser");
|
|
3984
|
+
await runBrowserCommand({
|
|
3985
|
+
args: subArgs,
|
|
3986
|
+
runtime: {
|
|
3987
|
+
host: authedRuntime.host,
|
|
3988
|
+
bearerToken: authedRuntime.sessionToken,
|
|
3989
|
+
},
|
|
3990
|
+
});
|
|
3991
|
+
return;
|
|
3992
|
+
}
|
|
3993
|
+
|
|
3994
|
+
if (command === "linkedin") {
|
|
3995
|
+
const subArgs = args.slice(args.indexOf("linkedin") + 1);
|
|
3996
|
+
const { runLinkedInCommand } = await import("./linkedin");
|
|
3997
|
+
const runtime = await getRuntime();
|
|
3998
|
+
const authedRuntime = requireAuthenticatedRuntime(
|
|
3999
|
+
runtime,
|
|
4000
|
+
"dench linkedin",
|
|
4001
|
+
);
|
|
4002
|
+
await runLinkedInCommand({
|
|
4003
|
+
args: subArgs,
|
|
4004
|
+
runtime: {
|
|
4005
|
+
host: authedRuntime.host,
|
|
4006
|
+
bearerToken: authedRuntime.sessionToken,
|
|
4007
|
+
},
|
|
4008
|
+
});
|
|
4009
|
+
return;
|
|
4010
|
+
}
|
|
4011
|
+
|
|
3883
4012
|
if (command === "chat") {
|
|
3884
4013
|
const subArgs = args.slice(args.indexOf("chat") + 1);
|
|
3885
4014
|
const sub = subArgs[0];
|
|
@@ -4010,7 +4139,7 @@ async function main() {
|
|
|
4010
4139
|
// ── OpenClaw-parity self-update CLI surface ──────────────────────────
|
|
4011
4140
|
//
|
|
4012
4141
|
// Dispatch all `dench identity / organisation / user / tools / mem /
|
|
4013
|
-
//
|
|
4142
|
+
// bootstrap / model / daily` traffic through the shared
|
|
4014
4143
|
// agent-config CLI module. Each command requires the same auth shape
|
|
4015
4144
|
// as `dench cron` (Bearer DENCH_API_KEY or `dench signin` session
|
|
4016
4145
|
// token) so server-side `requireCronAccess` accepts both.
|
|
@@ -4020,7 +4149,6 @@ async function main() {
|
|
|
4020
4149
|
"user",
|
|
4021
4150
|
"tools",
|
|
4022
4151
|
"mem",
|
|
4023
|
-
"heartbeat",
|
|
4024
4152
|
"bootstrap",
|
|
4025
4153
|
"model",
|
|
4026
4154
|
"daily",
|
|
@@ -4068,10 +4196,6 @@ async function main() {
|
|
|
4068
4196
|
await mod.runMemoryAggregateCommand(ctxBase);
|
|
4069
4197
|
return;
|
|
4070
4198
|
}
|
|
4071
|
-
if (command === "heartbeat") {
|
|
4072
|
-
await mod.runHeartbeatCommand(ctxBase);
|
|
4073
|
-
return;
|
|
4074
|
-
}
|
|
4075
4199
|
if (command === "bootstrap") {
|
|
4076
4200
|
await mod.runBootstrapCommand(ctxBase);
|
|
4077
4201
|
return;
|