@dench.com/cli 2.8.0-staging.107 → 2.8.2-staging.112
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/crm.ts +12 -1
- package/dench.ts +21 -10
- package/leads.ts +585 -0
- package/lib/api-schemas.ts +29 -1
- package/lib/crm-enrichment.ts +24 -1
- package/lib/enrichment-gateway.ts +9 -0
- package/package.json +2 -1
- package/tools.ts +13 -13
package/crm.ts
CHANGED
|
@@ -3093,6 +3093,13 @@ async function resolveEnrichmentTarget(
|
|
|
3093
3093
|
* Email (no first/last name reached the gateway, so the validator
|
|
3094
3094
|
* rejected the request before any lookup ran).
|
|
3095
3095
|
*/
|
|
3096
|
+
const CONTACT_REQUIRED_FIELDS = new Set([
|
|
3097
|
+
"email",
|
|
3098
|
+
"work_email",
|
|
3099
|
+
"personal_email",
|
|
3100
|
+
"phone",
|
|
3101
|
+
]);
|
|
3102
|
+
|
|
3096
3103
|
async function callEnrichmentGatewayForEntry(args: {
|
|
3097
3104
|
target: EnrichmentTarget;
|
|
3098
3105
|
entry: CrmEntryForEnrichment;
|
|
@@ -3118,7 +3125,11 @@ async function callEnrichmentGatewayForEntry(args: {
|
|
|
3118
3125
|
// Contact cells stay on /person/contact. A LinkedIn URL for a profile
|
|
3119
3126
|
// field (name, title, headline) uses /person so the gateway waterfall
|
|
3120
3127
|
// can fill graph data without a contact job.
|
|
3121
|
-
const isContactField =
|
|
3128
|
+
const isContactField =
|
|
3129
|
+
Boolean(target.column.enrichFields?.length) ||
|
|
3130
|
+
target.column.requiredFields.some((field) =>
|
|
3131
|
+
CONTACT_REQUIRED_FIELDS.has(field),
|
|
3132
|
+
);
|
|
3122
3133
|
if (!isContactField && personBody.linkedinUrl) {
|
|
3123
3134
|
return aviatoEnrichPerson(
|
|
3124
3135
|
{
|
package/dench.ts
CHANGED
|
@@ -44,7 +44,11 @@ import {
|
|
|
44
44
|
withCurrentSessionSelection,
|
|
45
45
|
withSavedSession,
|
|
46
46
|
} from "./session";
|
|
47
|
-
import {
|
|
47
|
+
import {
|
|
48
|
+
fetchConnectedAppsSummary,
|
|
49
|
+
fetchUnipileConnections,
|
|
50
|
+
runToolCommand,
|
|
51
|
+
} from "./tools";
|
|
48
52
|
|
|
49
53
|
type JsonRecord = Record<string, unknown>;
|
|
50
54
|
|
|
@@ -474,9 +478,9 @@ Usage:
|
|
|
474
478
|
dench artifacts list [--limit 25] [--json]
|
|
475
479
|
dench artifacts show <id> [--json]
|
|
476
480
|
dench artifacts import <id> --to lead|company|job_posting [--view "Q3 outbound"] [--pin] [--json]
|
|
477
|
-
dench leads find --titles "VP Sales" [--locations "San Francisco"] [--limit 500] [--json]
|
|
478
|
-
dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--limit 500] [--json]
|
|
479
|
-
dench jobs find --titles "Account Executive" [--locations "San Francisco"] [--limit 500] [--json]
|
|
481
|
+
dench leads find --titles "VP Sales" [--locations "San Francisco"] [--limit 500] [--page-token <token>] [--json]
|
|
482
|
+
dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--limit 500] [--page-token <token>] [--json]
|
|
483
|
+
dench jobs find --titles "Account Executive" [--locations "San Francisco"] [--limit 500] [--page-token <token>] [--json]
|
|
480
484
|
dench suggested-work [--json]
|
|
481
485
|
dench approval request "message" [--json]
|
|
482
486
|
dench approval approve <approvalId> [--evidence "User said yes in chat"] [--json]
|
|
@@ -757,7 +761,7 @@ function leadsHelp() {
|
|
|
757
761
|
console.log(`Dench leads
|
|
758
762
|
|
|
759
763
|
Usage:
|
|
760
|
-
dench leads find --titles "VP Sales" --locations "San Francisco" [--company-domain acme.com] [--keywords fintech] [--limit 500] [--json]
|
|
764
|
+
dench leads find --titles "VP Sales" --locations "San Francisco" [--company-domain acme.com] [--keywords fintech] [--limit 500] [--page-token <token>] [--json]
|
|
761
765
|
`);
|
|
762
766
|
}
|
|
763
767
|
|
|
@@ -765,7 +769,7 @@ function companiesHelp() {
|
|
|
765
769
|
console.log(`Dench companies
|
|
766
770
|
|
|
767
771
|
Usage:
|
|
768
|
-
dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--industries SaaS] [--locations "San Francisco"] [--limit 500] [--json]
|
|
772
|
+
dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--industries SaaS] [--locations "San Francisco"] [--limit 500] [--page-token <token>] [--json]
|
|
769
773
|
`);
|
|
770
774
|
}
|
|
771
775
|
|
|
@@ -773,7 +777,7 @@ function jobsHelp() {
|
|
|
773
777
|
console.log(`Dench jobs
|
|
774
778
|
|
|
775
779
|
Usage:
|
|
776
|
-
dench jobs find --titles "Account Executive" --locations "San Francisco" [--company-domain acme.com] [--keywords remote] [--limit 500] [--json]
|
|
780
|
+
dench jobs find --titles "Account Executive" --locations "San Francisco" [--company-domain acme.com] [--keywords remote] [--limit 500] [--page-token <token>] [--json]
|
|
777
781
|
`);
|
|
778
782
|
}
|
|
779
783
|
|
|
@@ -2879,11 +2883,14 @@ async function connectedAppsContext(runtime: Runtime) {
|
|
|
2879
2883
|
...unipileQueryAuth(runtime),
|
|
2880
2884
|
});
|
|
2881
2885
|
} catch (error) {
|
|
2886
|
+
const unipile = await fetchUnipileConnections(unipileQueryAuth(runtime));
|
|
2882
2887
|
return {
|
|
2883
2888
|
available: false,
|
|
2884
2889
|
reason: errorMessage(error),
|
|
2885
|
-
count:
|
|
2886
|
-
connections:
|
|
2890
|
+
count: unipile.connections.length,
|
|
2891
|
+
connections: unipile.connections,
|
|
2892
|
+
unipileAvailable: unipile.available,
|
|
2893
|
+
...(unipile.error ? { unipileError: unipile.error } : {}),
|
|
2887
2894
|
};
|
|
2888
2895
|
}
|
|
2889
2896
|
}
|
|
@@ -4979,6 +4986,7 @@ async function main() {
|
|
|
4979
4986
|
args.slice(args.indexOf("artifacts") + 1),
|
|
4980
4987
|
);
|
|
4981
4988
|
if (
|
|
4989
|
+
runtime.mode === "dev" ||
|
|
4982
4990
|
runtime.mode === "session" ||
|
|
4983
4991
|
runtime.mode === "apiKey"
|
|
4984
4992
|
) {
|
|
@@ -4987,7 +4995,10 @@ async function main() {
|
|
|
4987
4995
|
convex: runtime.client,
|
|
4988
4996
|
args: artifactArgs,
|
|
4989
4997
|
jsonOutput: json,
|
|
4990
|
-
sessionToken:
|
|
4998
|
+
sessionToken:
|
|
4999
|
+
runtime.mode === "dev"
|
|
5000
|
+
? encodeDevCrmSessionToken(runtime.workspaceArgs)
|
|
5001
|
+
: runtime.sessionToken,
|
|
4991
5002
|
});
|
|
4992
5003
|
if (handled) return;
|
|
4993
5004
|
}
|
package/leads.ts
ADDED
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dench leads find`, `dench jobs find`, and workspace artifact
|
|
3
|
+
* list/show/import. Find writes a durable table artifact; import is the
|
|
4
|
+
* same path as the chat card.
|
|
5
|
+
*/
|
|
6
|
+
import type { ConvexHttpClient } from "convex/browser";
|
|
7
|
+
import { makeFunctionReference } from "convex/server";
|
|
8
|
+
import {
|
|
9
|
+
assertNoUnknownFlags as assertNoUnknownFlagsRaw,
|
|
10
|
+
CliArgError,
|
|
11
|
+
getFlag,
|
|
12
|
+
hasFlag,
|
|
13
|
+
shift as shiftRaw,
|
|
14
|
+
} from "./lib/cli-args";
|
|
15
|
+
import {
|
|
16
|
+
EnrichmentGatewayError,
|
|
17
|
+
type EnrichmentGatewayOptions,
|
|
18
|
+
searchCompanies,
|
|
19
|
+
searchJobs,
|
|
20
|
+
searchPeople,
|
|
21
|
+
} from "./lib/enrichment-gateway";
|
|
22
|
+
|
|
23
|
+
class FindCliError extends Error {
|
|
24
|
+
constructor(message: string) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "FindCliError";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type FindCliContext = {
|
|
31
|
+
convex: ConvexHttpClient;
|
|
32
|
+
args: string[];
|
|
33
|
+
jsonOutput: boolean;
|
|
34
|
+
sessionToken?: string;
|
|
35
|
+
enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const api = {
|
|
39
|
+
saveFind: makeFunctionReference<"action">(
|
|
40
|
+
"functions/workspaceArtifacts:saveFind",
|
|
41
|
+
),
|
|
42
|
+
list: makeFunctionReference<"query">("functions/workspaceArtifacts:list"),
|
|
43
|
+
get: makeFunctionReference<"query">("functions/workspaceArtifacts:get"),
|
|
44
|
+
importArtifact: makeFunctionReference<"action">(
|
|
45
|
+
"functions/workspaceArtifacts:importArtifact",
|
|
46
|
+
),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function shift(args: string[], expected: string): string {
|
|
50
|
+
try {
|
|
51
|
+
return shiftRaw(args, expected);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if (error instanceof CliArgError) throw new FindCliError(error.message);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function assertNoUnknownFlags(args: readonly string[], command: string): void {
|
|
59
|
+
try {
|
|
60
|
+
assertNoUnknownFlagsRaw(args, command);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
if (error instanceof CliArgError) throw new FindCliError(error.message);
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseLeadsFindFlags(args: string[]): {
|
|
68
|
+
titles?: string[];
|
|
69
|
+
locations?: string[];
|
|
70
|
+
seniorities?: string[];
|
|
71
|
+
industries?: string[];
|
|
72
|
+
functions?: string[];
|
|
73
|
+
companyDomain?: string;
|
|
74
|
+
keywords?: string;
|
|
75
|
+
limit?: number;
|
|
76
|
+
pageToken?: string;
|
|
77
|
+
} {
|
|
78
|
+
const titles = csvFlag(args, "--titles");
|
|
79
|
+
const locations = csvFlag(args, "--locations");
|
|
80
|
+
const seniorities = csvFlag(args, "--seniorities");
|
|
81
|
+
const industries = csvFlag(args, "--industries");
|
|
82
|
+
const functions = csvFlag(args, "--functions");
|
|
83
|
+
const companyDomain = getFlag(args, "--company-domain");
|
|
84
|
+
const keywords = getFlag(args, "--keywords");
|
|
85
|
+
const limit = parseLimit(args);
|
|
86
|
+
const pageToken = getFlag(args, "--page-token");
|
|
87
|
+
assertNoUnknownFlags(args, "leads find");
|
|
88
|
+
if (
|
|
89
|
+
!titles &&
|
|
90
|
+
!locations &&
|
|
91
|
+
!companyDomain &&
|
|
92
|
+
!keywords &&
|
|
93
|
+
!industries &&
|
|
94
|
+
!seniorities
|
|
95
|
+
) {
|
|
96
|
+
throw new FindCliError(
|
|
97
|
+
"dench leads find requires at least one of --titles, --locations, --company-domain, --keywords, --industries, --seniorities",
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
titles,
|
|
102
|
+
locations,
|
|
103
|
+
seniorities,
|
|
104
|
+
industries,
|
|
105
|
+
functions,
|
|
106
|
+
companyDomain,
|
|
107
|
+
keywords,
|
|
108
|
+
limit,
|
|
109
|
+
pageToken,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function assertLeadsFindArgs(args: string[]): void {
|
|
114
|
+
const copy = [...args];
|
|
115
|
+
hasFlag(copy, "--json");
|
|
116
|
+
const verb = copy[0];
|
|
117
|
+
if (verb !== "find") return;
|
|
118
|
+
parseLeadsFindFlags(copy.slice(1));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function csvFlag(args: string[], name: string): string[] | undefined {
|
|
122
|
+
const raw = getFlag(args, name);
|
|
123
|
+
if (!raw) return undefined;
|
|
124
|
+
return raw
|
|
125
|
+
.split(",")
|
|
126
|
+
.map((part) => part.trim())
|
|
127
|
+
.filter(Boolean);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function parseLimit(args: string[]): number | undefined {
|
|
131
|
+
const raw = getFlag(args, "--limit");
|
|
132
|
+
if (!raw) return undefined;
|
|
133
|
+
const parsed = Number(raw);
|
|
134
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
135
|
+
throw new FindCliError("--limit must be a positive integer");
|
|
136
|
+
}
|
|
137
|
+
return Math.min(parsed, 5000);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function out(ctx: FindCliContext, value: unknown): void {
|
|
141
|
+
console.log(JSON.stringify(value, null, ctx.jsonOutput ? 2 : 0));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function callQuery(
|
|
145
|
+
ctx: FindCliContext,
|
|
146
|
+
fn: Parameters<ConvexHttpClient["query"]>[0],
|
|
147
|
+
args: Record<string, unknown> = {},
|
|
148
|
+
): Promise<unknown> {
|
|
149
|
+
return await ctx.convex.query(fn, {
|
|
150
|
+
...args,
|
|
151
|
+
...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
|
|
152
|
+
} as never);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function callAction(
|
|
156
|
+
ctx: FindCliContext,
|
|
157
|
+
fn: Parameters<ConvexHttpClient["action"]>[0],
|
|
158
|
+
args: Record<string, unknown> = {},
|
|
159
|
+
): Promise<unknown> {
|
|
160
|
+
return await ctx.convex.action(fn, {
|
|
161
|
+
...args,
|
|
162
|
+
...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
|
|
163
|
+
} as never);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function asArray(value: unknown): unknown[] {
|
|
167
|
+
return Array.isArray(value) ? value : [];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function findTitle(
|
|
171
|
+
kind: "people" | "companies" | "jobs",
|
|
172
|
+
parts: string[],
|
|
173
|
+
): string {
|
|
174
|
+
const first = parts.find((part) => part.length > 0);
|
|
175
|
+
if (kind === "people") return first ? `People: ${first}` : "People search";
|
|
176
|
+
if (kind === "companies") {
|
|
177
|
+
return first ? `Companies: ${first}` : "Company search";
|
|
178
|
+
}
|
|
179
|
+
return first ? `Jobs: ${first}` : "Job search";
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function persist(
|
|
183
|
+
ctx: FindCliContext,
|
|
184
|
+
args: {
|
|
185
|
+
createdFrom: "find_people" | "find_companies" | "find_jobs";
|
|
186
|
+
title: string;
|
|
187
|
+
filters: Record<string, unknown>;
|
|
188
|
+
people?: unknown[];
|
|
189
|
+
companies?: unknown[];
|
|
190
|
+
jobs?: unknown[];
|
|
191
|
+
},
|
|
192
|
+
): Promise<unknown> {
|
|
193
|
+
return await callAction(ctx, api.saveFind, args);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function leadsHelp(): void {
|
|
197
|
+
console.log(`Dench leads
|
|
198
|
+
|
|
199
|
+
Usage:
|
|
200
|
+
dench leads find --titles "VP Sales" --locations "San Francisco" [--company-domain acme.com] [--limit 500] [--page-token <token>] [--json]
|
|
201
|
+
dench leads find --keywords "fintech" [--industries SaaS] [--seniorities director,vp] [--json]
|
|
202
|
+
|
|
203
|
+
Finds people through Dench search and writes a workspace artifact.
|
|
204
|
+
Import with \`dench artifacts import <id> --to lead\`.
|
|
205
|
+
`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function companiesHelp(): void {
|
|
209
|
+
console.log(`Dench companies
|
|
210
|
+
|
|
211
|
+
Usage:
|
|
212
|
+
dench companies find --name "Acme" [--domain acme.com] [--keywords fintech] [--industries SaaS] [--locations "San Francisco"] [--linkedin https://linkedin.com/company/acme] [--limit 500] [--page-token <token>] [--json]
|
|
213
|
+
|
|
214
|
+
Finds companies through Dench search and writes a workspace artifact.
|
|
215
|
+
Import with \`dench artifacts import <id> --to lead\`.
|
|
216
|
+
`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function jobsHelp(): void {
|
|
220
|
+
console.log(`Dench jobs
|
|
221
|
+
|
|
222
|
+
Usage:
|
|
223
|
+
dench jobs find --titles "Account Executive" --locations "San Francisco" [--company-domain acme.com] [--keywords remote] [--limit 500] [--page-token <token>] [--json]
|
|
224
|
+
|
|
225
|
+
Finds job postings through Dench search and writes a workspace artifact.
|
|
226
|
+
Import with \`dench artifacts import <id> --to job_posting\`.
|
|
227
|
+
`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function workspaceArtifactsHelp(): void {
|
|
231
|
+
console.log(`Dench artifacts
|
|
232
|
+
|
|
233
|
+
Usage:
|
|
234
|
+
dench artifacts [--limit 25] [--json]
|
|
235
|
+
dench artifacts list [--limit 25] [--json]
|
|
236
|
+
dench artifacts show <id> [--json]
|
|
237
|
+
dench artifacts import <id> --to lead|company|job_posting [--view "Q3 outbound"] [--pin] [--json]
|
|
238
|
+
|
|
239
|
+
\`list\` / \`show\` / \`import\` operate on find results (lead lists and job
|
|
240
|
+
searches). Bare \`dench artifacts\` still lists agent suggested-work artifacts.
|
|
241
|
+
`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function assertJobsFindArgs(args: string[]): void {
|
|
245
|
+
const copy = [...args];
|
|
246
|
+
hasFlag(copy, "--json");
|
|
247
|
+
if (copy[0] !== "find") return;
|
|
248
|
+
if (
|
|
249
|
+
!copy.includes("--titles") &&
|
|
250
|
+
!copy.includes("--locations") &&
|
|
251
|
+
!copy.includes("--company-domain") &&
|
|
252
|
+
!copy.includes("--company") &&
|
|
253
|
+
!copy.includes("--keywords")
|
|
254
|
+
) {
|
|
255
|
+
throw new FindCliError(
|
|
256
|
+
"dench jobs find requires at least one of --titles, --locations, --company-domain, --company, --keywords",
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function assertCompaniesFindArgs(args: string[]): void {
|
|
262
|
+
const copy = [...args];
|
|
263
|
+
hasFlag(copy, "--json");
|
|
264
|
+
if (copy[0] !== "find") return;
|
|
265
|
+
if (
|
|
266
|
+
!copy.includes("--domain") &&
|
|
267
|
+
!copy.includes("--name") &&
|
|
268
|
+
!copy.includes("--keywords") &&
|
|
269
|
+
!copy.includes("--industries") &&
|
|
270
|
+
!copy.includes("--locations") &&
|
|
271
|
+
!copy.includes("--linkedin") &&
|
|
272
|
+
!copy.includes("--headcount")
|
|
273
|
+
) {
|
|
274
|
+
throw new FindCliError(
|
|
275
|
+
"dench companies find requires at least one of --name, --domain, --keywords, --industries, --locations, --linkedin, --headcount",
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function runLeadsCommand(opts: {
|
|
281
|
+
convex: ConvexHttpClient;
|
|
282
|
+
args: string[];
|
|
283
|
+
jsonOutput?: boolean;
|
|
284
|
+
sessionToken?: string;
|
|
285
|
+
enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
|
|
286
|
+
}): Promise<void> {
|
|
287
|
+
const args = [...opts.args];
|
|
288
|
+
const jsonFromArgs = hasFlag(args, "--json");
|
|
289
|
+
const ctx: FindCliContext = {
|
|
290
|
+
convex: opts.convex,
|
|
291
|
+
args,
|
|
292
|
+
jsonOutput: opts.jsonOutput === true || jsonFromArgs,
|
|
293
|
+
sessionToken: opts.sessionToken,
|
|
294
|
+
enrichmentGateway: opts.enrichmentGateway,
|
|
295
|
+
};
|
|
296
|
+
const verb = args.shift();
|
|
297
|
+
if (!verb || verb === "help" || verb === "--help") {
|
|
298
|
+
leadsHelp();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (verb !== "find") {
|
|
302
|
+
throw new FindCliError(`Unknown leads verb: ${verb}`);
|
|
303
|
+
}
|
|
304
|
+
const {
|
|
305
|
+
titles,
|
|
306
|
+
locations,
|
|
307
|
+
seniorities,
|
|
308
|
+
industries,
|
|
309
|
+
functions,
|
|
310
|
+
companyDomain,
|
|
311
|
+
keywords,
|
|
312
|
+
limit,
|
|
313
|
+
pageToken,
|
|
314
|
+
} = parseLeadsFindFlags(args);
|
|
315
|
+
try {
|
|
316
|
+
const result = await searchPeople(
|
|
317
|
+
{
|
|
318
|
+
personTitles: titles,
|
|
319
|
+
personLocations: locations,
|
|
320
|
+
personSeniorities: seniorities,
|
|
321
|
+
personFunctions: functions,
|
|
322
|
+
organizationDomains: companyDomain ? [companyDomain] : undefined,
|
|
323
|
+
organizationIndustries: industries,
|
|
324
|
+
keywords,
|
|
325
|
+
limit,
|
|
326
|
+
pageToken,
|
|
327
|
+
},
|
|
328
|
+
ctx.enrichmentGateway,
|
|
329
|
+
);
|
|
330
|
+
const people = asArray(result.people);
|
|
331
|
+
const artifact = await persist(ctx, {
|
|
332
|
+
createdFrom: "find_people",
|
|
333
|
+
title: findTitle("people", [
|
|
334
|
+
titles?.[0] ?? "",
|
|
335
|
+
keywords ?? "",
|
|
336
|
+
companyDomain ?? "",
|
|
337
|
+
]),
|
|
338
|
+
filters: {
|
|
339
|
+
titles,
|
|
340
|
+
locations,
|
|
341
|
+
seniorities,
|
|
342
|
+
industries,
|
|
343
|
+
companyDomain,
|
|
344
|
+
keywords,
|
|
345
|
+
limit,
|
|
346
|
+
},
|
|
347
|
+
people,
|
|
348
|
+
});
|
|
349
|
+
// metadata carries total / nextPageToken — pass the token back via
|
|
350
|
+
// --page-token to continue where this page stopped.
|
|
351
|
+
out(ctx, {
|
|
352
|
+
...(artifact as Record<string, unknown>),
|
|
353
|
+
metadata: result.metadata,
|
|
354
|
+
});
|
|
355
|
+
} catch (error) {
|
|
356
|
+
throw new FindCliError(
|
|
357
|
+
error instanceof EnrichmentGatewayError || error instanceof Error
|
|
358
|
+
? error.message
|
|
359
|
+
: String(error),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function runJobsCommand(opts: {
|
|
365
|
+
convex: ConvexHttpClient;
|
|
366
|
+
args: string[];
|
|
367
|
+
jsonOutput?: boolean;
|
|
368
|
+
sessionToken?: string;
|
|
369
|
+
enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
|
|
370
|
+
}): Promise<void> {
|
|
371
|
+
const args = [...opts.args];
|
|
372
|
+
const jsonFromArgs = hasFlag(args, "--json");
|
|
373
|
+
const ctx: FindCliContext = {
|
|
374
|
+
convex: opts.convex,
|
|
375
|
+
args,
|
|
376
|
+
jsonOutput: opts.jsonOutput === true || jsonFromArgs,
|
|
377
|
+
sessionToken: opts.sessionToken,
|
|
378
|
+
enrichmentGateway: opts.enrichmentGateway,
|
|
379
|
+
};
|
|
380
|
+
const verb = args.shift();
|
|
381
|
+
if (!verb || verb === "help" || verb === "--help") {
|
|
382
|
+
jobsHelp();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (verb !== "find") {
|
|
386
|
+
throw new FindCliError(`Unknown jobs verb: ${verb}`);
|
|
387
|
+
}
|
|
388
|
+
const titles = csvFlag(args, "--titles");
|
|
389
|
+
const locations = csvFlag(args, "--locations");
|
|
390
|
+
const companyDomain = getFlag(args, "--company-domain");
|
|
391
|
+
const companyName = getFlag(args, "--company");
|
|
392
|
+
const keywords = getFlag(args, "--keywords");
|
|
393
|
+
const postedAfter = getFlag(args, "--posted-after");
|
|
394
|
+
const limit = parseLimit(args);
|
|
395
|
+
const pageToken = getFlag(args, "--page-token");
|
|
396
|
+
assertNoUnknownFlags(args, "jobs find");
|
|
397
|
+
if (!titles && !locations && !companyDomain && !companyName && !keywords) {
|
|
398
|
+
throw new FindCliError(
|
|
399
|
+
"dench jobs find requires at least one of --titles, --locations, --company-domain, --company, --keywords",
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
const result = await searchJobs(
|
|
404
|
+
{
|
|
405
|
+
titles,
|
|
406
|
+
locations,
|
|
407
|
+
companyDomain,
|
|
408
|
+
companyName,
|
|
409
|
+
keywords,
|
|
410
|
+
postedAfter,
|
|
411
|
+
limit,
|
|
412
|
+
pageToken,
|
|
413
|
+
},
|
|
414
|
+
ctx.enrichmentGateway,
|
|
415
|
+
);
|
|
416
|
+
const jobs = asArray(result.jobs);
|
|
417
|
+
const artifact = await persist(ctx, {
|
|
418
|
+
createdFrom: "find_jobs",
|
|
419
|
+
title: findTitle("jobs", [titles?.[0] ?? "", companyName ?? "", keywords ?? ""]),
|
|
420
|
+
filters: {
|
|
421
|
+
titles,
|
|
422
|
+
locations,
|
|
423
|
+
companyDomain,
|
|
424
|
+
companyName,
|
|
425
|
+
keywords,
|
|
426
|
+
postedAfter,
|
|
427
|
+
limit,
|
|
428
|
+
},
|
|
429
|
+
jobs,
|
|
430
|
+
});
|
|
431
|
+
out(ctx, {
|
|
432
|
+
...(artifact as Record<string, unknown>),
|
|
433
|
+
metadata: result.metadata,
|
|
434
|
+
});
|
|
435
|
+
} catch (error) {
|
|
436
|
+
throw new FindCliError(
|
|
437
|
+
error instanceof EnrichmentGatewayError || error instanceof Error
|
|
438
|
+
? error.message
|
|
439
|
+
: String(error),
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export async function runCompaniesCommand(opts: {
|
|
445
|
+
convex: ConvexHttpClient;
|
|
446
|
+
args: string[];
|
|
447
|
+
jsonOutput?: boolean;
|
|
448
|
+
sessionToken?: string;
|
|
449
|
+
enrichmentGateway?: Pick<EnrichmentGatewayOptions, "apiKey" | "baseUrl">;
|
|
450
|
+
}): Promise<void> {
|
|
451
|
+
const args = [...opts.args];
|
|
452
|
+
const jsonFromArgs = hasFlag(args, "--json");
|
|
453
|
+
const ctx: FindCliContext = {
|
|
454
|
+
convex: opts.convex,
|
|
455
|
+
args,
|
|
456
|
+
jsonOutput: opts.jsonOutput === true || jsonFromArgs,
|
|
457
|
+
sessionToken: opts.sessionToken,
|
|
458
|
+
enrichmentGateway: opts.enrichmentGateway,
|
|
459
|
+
};
|
|
460
|
+
const verb = args.shift();
|
|
461
|
+
if (!verb || verb === "help" || verb === "--help") {
|
|
462
|
+
companiesHelp();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
if (verb !== "find") {
|
|
466
|
+
throw new FindCliError(`Unknown companies verb: ${verb}`);
|
|
467
|
+
}
|
|
468
|
+
const domain = getFlag(args, "--domain");
|
|
469
|
+
const name = getFlag(args, "--name");
|
|
470
|
+
const keywords = getFlag(args, "--keywords");
|
|
471
|
+
const industries = csvFlag(args, "--industries");
|
|
472
|
+
const locations = csvFlag(args, "--locations");
|
|
473
|
+
const linkedinUrl = getFlag(args, "--linkedin");
|
|
474
|
+
const headcountRanges = csvFlag(args, "--headcount");
|
|
475
|
+
const limit = parseLimit(args);
|
|
476
|
+
const pageToken = getFlag(args, "--page-token");
|
|
477
|
+
assertNoUnknownFlags(args, "companies find");
|
|
478
|
+
if (
|
|
479
|
+
!domain &&
|
|
480
|
+
!name &&
|
|
481
|
+
!keywords &&
|
|
482
|
+
!industries &&
|
|
483
|
+
!locations &&
|
|
484
|
+
!linkedinUrl &&
|
|
485
|
+
!headcountRanges
|
|
486
|
+
) {
|
|
487
|
+
throw new FindCliError(
|
|
488
|
+
"dench companies find requires at least one of --name, --domain, --keywords, --industries, --locations, --linkedin, --headcount",
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
const result = await searchCompanies(
|
|
493
|
+
{
|
|
494
|
+
domain,
|
|
495
|
+
name,
|
|
496
|
+
keywords,
|
|
497
|
+
industries,
|
|
498
|
+
locations,
|
|
499
|
+
linkedinUrl,
|
|
500
|
+
headcountRanges,
|
|
501
|
+
limit,
|
|
502
|
+
pageToken,
|
|
503
|
+
},
|
|
504
|
+
ctx.enrichmentGateway,
|
|
505
|
+
);
|
|
506
|
+
const artifact = await persist(ctx, {
|
|
507
|
+
createdFrom: "find_companies",
|
|
508
|
+
title: findTitle("companies", [name ?? "", domain ?? "", keywords ?? ""]),
|
|
509
|
+
filters: {
|
|
510
|
+
domain,
|
|
511
|
+
name,
|
|
512
|
+
keywords,
|
|
513
|
+
industries,
|
|
514
|
+
locations,
|
|
515
|
+
linkedinUrl,
|
|
516
|
+
headcountRanges,
|
|
517
|
+
limit,
|
|
518
|
+
},
|
|
519
|
+
companies: asArray(result.companies),
|
|
520
|
+
});
|
|
521
|
+
out(ctx, {
|
|
522
|
+
...(artifact as Record<string, unknown>),
|
|
523
|
+
metadata: result.metadata,
|
|
524
|
+
});
|
|
525
|
+
} catch (error) {
|
|
526
|
+
throw new FindCliError(
|
|
527
|
+
error instanceof EnrichmentGatewayError || error instanceof Error
|
|
528
|
+
? error.message
|
|
529
|
+
: String(error),
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export async function runWorkspaceArtifactsCommand(opts: {
|
|
535
|
+
convex: ConvexHttpClient;
|
|
536
|
+
args: string[];
|
|
537
|
+
jsonOutput?: boolean;
|
|
538
|
+
sessionToken?: string;
|
|
539
|
+
}): Promise<boolean> {
|
|
540
|
+
const args = [...opts.args];
|
|
541
|
+
const verb = args[0];
|
|
542
|
+
if (verb !== "list" && verb !== "show" && verb !== "import") {
|
|
543
|
+
return false;
|
|
544
|
+
}
|
|
545
|
+
args.shift();
|
|
546
|
+
const ctx: FindCliContext = {
|
|
547
|
+
convex: opts.convex,
|
|
548
|
+
args,
|
|
549
|
+
jsonOutput: opts.jsonOutput === true || hasFlag(args, "--json"),
|
|
550
|
+
sessionToken: opts.sessionToken,
|
|
551
|
+
};
|
|
552
|
+
if (verb === "list") {
|
|
553
|
+
const limitRaw = getFlag(args, "--limit");
|
|
554
|
+
const limit = limitRaw ? Number(limitRaw) : 25;
|
|
555
|
+
assertNoUnknownFlags(args, "artifacts list");
|
|
556
|
+
out(ctx, await callQuery(ctx, api.list, { limit }));
|
|
557
|
+
return true;
|
|
558
|
+
}
|
|
559
|
+
if (verb === "show") {
|
|
560
|
+
const artifactId = shift(args, "artifact id");
|
|
561
|
+
assertNoUnknownFlags(args, "artifacts show");
|
|
562
|
+
out(ctx, await callQuery(ctx, api.get, { artifactId }));
|
|
563
|
+
return true;
|
|
564
|
+
}
|
|
565
|
+
const artifactId = shift(args, "artifact id");
|
|
566
|
+
const to = getFlag(args, "--to");
|
|
567
|
+
if (to !== "lead" && to !== "job_posting" && to !== "company") {
|
|
568
|
+
throw new FindCliError(
|
|
569
|
+
"dench artifacts import <id> --to lead|company|job_posting [--view ...] [--pin]",
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
const viewName = getFlag(args, "--view");
|
|
573
|
+
const pin = hasFlag(args, "--pin");
|
|
574
|
+
assertNoUnknownFlags(args, "artifacts import");
|
|
575
|
+
out(
|
|
576
|
+
ctx,
|
|
577
|
+
await callAction(ctx, api.importArtifact, {
|
|
578
|
+
artifactId,
|
|
579
|
+
to,
|
|
580
|
+
viewName,
|
|
581
|
+
pin,
|
|
582
|
+
}),
|
|
583
|
+
);
|
|
584
|
+
return true;
|
|
585
|
+
}
|
package/lib/api-schemas.ts
CHANGED
|
@@ -949,8 +949,23 @@ const toolRunResponse = z
|
|
|
949
949
|
|
|
950
950
|
const enrichmentMetadata = z
|
|
951
951
|
.object({
|
|
952
|
-
total: z
|
|
952
|
+
total: z
|
|
953
|
+
.number()
|
|
954
|
+
.optional()
|
|
955
|
+
.describe(
|
|
956
|
+
"Total matches for the search. Stable across pages; absent when only an estimate exists.",
|
|
957
|
+
),
|
|
958
|
+
totalIsEstimate: z
|
|
959
|
+
.boolean()
|
|
960
|
+
.optional()
|
|
961
|
+
.describe("True when the total is a lower bound rather than exact."),
|
|
953
962
|
offset: z.number().optional(),
|
|
963
|
+
nextPageToken: z
|
|
964
|
+
.string()
|
|
965
|
+
.optional()
|
|
966
|
+
.describe(
|
|
967
|
+
"Opaque token for the next page. Pass it back as pageToken; absent on the last page.",
|
|
968
|
+
),
|
|
954
969
|
})
|
|
955
970
|
.passthrough()
|
|
956
971
|
.optional();
|
|
@@ -1189,6 +1204,10 @@ const peopleSearch = z.object({
|
|
|
1189
1204
|
exactMatch: z.boolean().optional(),
|
|
1190
1205
|
limit: z.number().int().positive().max(5000).optional(),
|
|
1191
1206
|
offset: z.number().int().nonnegative().optional(),
|
|
1207
|
+
pageToken: z
|
|
1208
|
+
.string()
|
|
1209
|
+
.optional()
|
|
1210
|
+
.describe("Opaque token from a previous response's metadata.nextPageToken."),
|
|
1192
1211
|
preferCache: z.boolean().optional(),
|
|
1193
1212
|
});
|
|
1194
1213
|
|
|
@@ -1199,6 +1218,10 @@ const companySearch = z.object({
|
|
|
1199
1218
|
exactMatch: z.boolean().optional(),
|
|
1200
1219
|
limit: z.number().int().positive().max(5000).optional(),
|
|
1201
1220
|
offset: z.number().int().nonnegative().optional(),
|
|
1221
|
+
pageToken: z
|
|
1222
|
+
.string()
|
|
1223
|
+
.optional()
|
|
1224
|
+
.describe("Opaque token from a previous response's metadata.nextPageToken."),
|
|
1202
1225
|
preferCache: z.boolean().optional(),
|
|
1203
1226
|
});
|
|
1204
1227
|
|
|
@@ -1211,6 +1234,10 @@ const jobsSearch = z.object({
|
|
|
1211
1234
|
keywords: z.string().optional(),
|
|
1212
1235
|
postedAfter: z.string().optional(),
|
|
1213
1236
|
limit: z.number().int().positive().max(5000).optional(),
|
|
1237
|
+
pageToken: z
|
|
1238
|
+
.string()
|
|
1239
|
+
.optional()
|
|
1240
|
+
.describe("Opaque token from a previous response's metadata.nextPageToken."),
|
|
1214
1241
|
preferCache: z.boolean().optional(),
|
|
1215
1242
|
});
|
|
1216
1243
|
|
|
@@ -1231,6 +1258,7 @@ const jobsSearchResponse = z
|
|
|
1231
1258
|
.passthrough(),
|
|
1232
1259
|
)
|
|
1233
1260
|
.describe("Job postings in the canonical job_posting shape."),
|
|
1261
|
+
metadata: enrichmentMetadata,
|
|
1234
1262
|
})
|
|
1235
1263
|
.passthrough();
|
|
1236
1264
|
|
package/lib/crm-enrichment.ts
CHANGED
|
@@ -91,6 +91,22 @@ export const PEOPLE_ENRICHMENT_COLUMNS: readonly EnrichmentColumnDef[] = [
|
|
|
91
91
|
extractionFallbacks: ["email"],
|
|
92
92
|
enrichFields: ["work_emails"],
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
label: "Work Email",
|
|
96
|
+
key: "person.work_email",
|
|
97
|
+
fieldType: "email",
|
|
98
|
+
apolloPath: "person.work_email",
|
|
99
|
+
requiredFields: ["work_email"],
|
|
100
|
+
enrichFields: ["work_emails"],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
label: "Personal Email",
|
|
104
|
+
key: "person.personal_email",
|
|
105
|
+
fieldType: "email",
|
|
106
|
+
apolloPath: "person.personal_email",
|
|
107
|
+
requiredFields: ["personal_email"],
|
|
108
|
+
enrichFields: ["personal_emails"],
|
|
109
|
+
},
|
|
94
110
|
{
|
|
95
111
|
label: "Headline",
|
|
96
112
|
key: "person.headline",
|
|
@@ -336,7 +352,14 @@ export function getRequiredFieldsForApolloPath(
|
|
|
336
352
|
const column = getEnrichmentColumns(category).find(
|
|
337
353
|
(candidate) => candidate.apolloPath === apolloPath,
|
|
338
354
|
);
|
|
339
|
-
return column
|
|
355
|
+
if (column) return column.requiredFields;
|
|
356
|
+
if (category !== "people") return [];
|
|
357
|
+
if (apolloPath.includes("personal_email")) return ["personal_email"];
|
|
358
|
+
if (apolloPath.includes("phone")) return ["phone"];
|
|
359
|
+
if (apolloPath.includes("work_email") || /(^|\.)email$/.test(apolloPath)) {
|
|
360
|
+
return ["work_email"];
|
|
361
|
+
}
|
|
362
|
+
return [];
|
|
340
363
|
}
|
|
341
364
|
|
|
342
365
|
export function buildEnrichmentMeta(
|
|
@@ -69,6 +69,8 @@ export type SearchPeopleBody = {
|
|
|
69
69
|
limit?: number;
|
|
70
70
|
offset?: number;
|
|
71
71
|
searchAfter?: unknown;
|
|
72
|
+
/** Opaque token from a previous response's `metadata.nextPageToken`. */
|
|
73
|
+
pageToken?: string;
|
|
72
74
|
// Rich filters, gateway-named. Merged with the legacy person*/organization*
|
|
73
75
|
// aliases above; entries may carry per-value exclude / exactMatch.
|
|
74
76
|
titles?: SearchFilterItem[];
|
|
@@ -104,6 +106,8 @@ export type SearchCompaniesBody = {
|
|
|
104
106
|
keywords?: string;
|
|
105
107
|
limit?: number;
|
|
106
108
|
offset?: number;
|
|
109
|
+
/** Opaque token from a previous response's `metadata.nextPageToken`. */
|
|
110
|
+
pageToken?: string;
|
|
107
111
|
};
|
|
108
112
|
|
|
109
113
|
export type SearchJobsBody = {
|
|
@@ -115,6 +119,8 @@ export type SearchJobsBody = {
|
|
|
115
119
|
keywords?: string;
|
|
116
120
|
postedAfter?: string;
|
|
117
121
|
limit?: number;
|
|
122
|
+
/** Opaque token from a previous response's `metadata.nextPageToken`. */
|
|
123
|
+
pageToken?: string;
|
|
118
124
|
};
|
|
119
125
|
|
|
120
126
|
export type EnrichmentGatewayOptions = {
|
|
@@ -1315,6 +1321,7 @@ export async function searchPeople(
|
|
|
1315
1321
|
else if (body.perPage !== undefined) payload.limit = body.perPage;
|
|
1316
1322
|
if (body.offset !== undefined) payload.offset = body.offset;
|
|
1317
1323
|
if (body.searchAfter !== undefined) payload.searchAfter = body.searchAfter;
|
|
1324
|
+
if (body.pageToken) payload.pageToken = body.pageToken;
|
|
1318
1325
|
|
|
1319
1326
|
return callGatewayJson("/v1/enrichment/people/search", {
|
|
1320
1327
|
method: "POST",
|
|
@@ -1338,6 +1345,7 @@ export async function searchCompanies(
|
|
|
1338
1345
|
if (body.keywords) payload.keywords = body.keywords;
|
|
1339
1346
|
if (body.limit !== undefined) payload.limit = body.limit;
|
|
1340
1347
|
if (body.offset !== undefined) payload.offset = body.offset;
|
|
1348
|
+
if (body.pageToken) payload.pageToken = body.pageToken;
|
|
1341
1349
|
return callGatewayJson("/v1/enrichment/company/search", {
|
|
1342
1350
|
method: "POST",
|
|
1343
1351
|
body: payload,
|
|
@@ -1359,6 +1367,7 @@ export async function searchJobs(
|
|
|
1359
1367
|
if (body.keywords) payload.keywords = body.keywords;
|
|
1360
1368
|
if (body.postedAfter) payload.postedAfter = body.postedAfter;
|
|
1361
1369
|
if (body.limit !== undefined) payload.limit = body.limit;
|
|
1370
|
+
if (body.pageToken) payload.pageToken = body.pageToken;
|
|
1362
1371
|
return callGatewayJson("/v1/enrichment/jobs/search", {
|
|
1363
1372
|
method: "POST",
|
|
1364
1373
|
body: payload,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.2-staging.112",
|
|
4
4
|
"description": "Dench agent workspace CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"search.ts",
|
|
33
33
|
"web.ts",
|
|
34
34
|
"image.ts",
|
|
35
|
+
"leads.ts",
|
|
35
36
|
"tools.ts",
|
|
36
37
|
"apps.ts",
|
|
37
38
|
"agentKind.ts",
|
package/tools.ts
CHANGED
|
@@ -169,7 +169,9 @@ function withoutShadowedComposioTools(
|
|
|
169
169
|
* whole command over that would take `dench integrations` from "missing the
|
|
170
170
|
* calendar" to "broken", which is strictly worse.
|
|
171
171
|
*/
|
|
172
|
-
async function fetchUnipileConnections(
|
|
172
|
+
export async function fetchUnipileConnections(
|
|
173
|
+
ctx: UnipileAuthContext,
|
|
174
|
+
): Promise<{
|
|
173
175
|
available: boolean;
|
|
174
176
|
connections: CompactConnection[];
|
|
175
177
|
error?: string;
|
|
@@ -257,12 +259,9 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
257
259
|
const filterToolkit = ctx.args[0]?.startsWith("--") ? undefined : ctx.args[0];
|
|
258
260
|
if (filterToolkit) ctx.args.shift();
|
|
259
261
|
|
|
260
|
-
const
|
|
261
|
-
callGateway(ctx, "GET", "/v1/composio/connections"),
|
|
262
|
-
fetchUnipileConnections(ctx),
|
|
263
|
-
]);
|
|
262
|
+
const connectionsSummary = await fetchConnectedAppsSummary(ctx);
|
|
264
263
|
let toolkitsPayload: unknown = null;
|
|
265
|
-
if (filterToolkit) {
|
|
264
|
+
if (filterToolkit && connectionsSummary.available) {
|
|
266
265
|
const search = encodeURIComponent(filterToolkit);
|
|
267
266
|
toolkitsPayload = await callGateway(
|
|
268
267
|
ctx,
|
|
@@ -271,10 +270,7 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
271
270
|
);
|
|
272
271
|
}
|
|
273
272
|
|
|
274
|
-
const allConnections =
|
|
275
|
-
...extractConnections(connectionsPayload).map(compactConnection),
|
|
276
|
-
...unipile.connections,
|
|
277
|
-
];
|
|
273
|
+
const allConnections = connectionsSummary.connections;
|
|
278
274
|
// Filtering after the merge is what makes `dench tool status gmail` answer
|
|
279
275
|
// with the Unipile mailbox instead of nothing.
|
|
280
276
|
const filteredConnections = filterToolkit
|
|
@@ -285,12 +281,16 @@ async function runStatusSubcommand(ctx: ToolCliContext): Promise<void> {
|
|
|
285
281
|
|
|
286
282
|
const summary = {
|
|
287
283
|
ok: true,
|
|
288
|
-
available:
|
|
284
|
+
available: connectionsSummary.available,
|
|
289
285
|
toolkit: filterToolkit ?? null,
|
|
290
286
|
count: filteredConnections.length,
|
|
291
287
|
connections: filteredConnections,
|
|
292
|
-
unipileAvailable:
|
|
293
|
-
...(
|
|
288
|
+
unipileAvailable: connectionsSummary.unipileAvailable,
|
|
289
|
+
...(connectionsSummary.unipileError
|
|
290
|
+
? { unipileError: connectionsSummary.unipileError }
|
|
291
|
+
: {}),
|
|
292
|
+
...(connectionsSummary.error ? { error: connectionsSummary.error } : {}),
|
|
293
|
+
...(connectionsSummary.reason ? { reason: connectionsSummary.reason } : {}),
|
|
294
294
|
...(toolkitsPayload
|
|
295
295
|
? { toolkit_info: extractToolkits(toolkitsPayload) }
|
|
296
296
|
: {}),
|