@audienti/cli 0.1.145 → 0.2.0-exora.10
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 +47 -895
- package/bin/audienti-mcp.mjs +2 -0
- package/cli/account-intake-operations.mjs +28 -0
- package/cli/account-intake.mjs +33 -0
- package/cli/account-replies.mjs +35 -0
- package/cli/account-reply-operations.mjs +55 -0
- package/cli/agents.mjs +160 -0
- package/cli/assignment-operations.mjs +218 -0
- package/cli/assignment.mjs +116 -0
- package/cli/attachment-operations.mjs +9 -0
- package/cli/attachments.mjs +28 -0
- package/cli/audience-operations.mjs +21 -0
- package/cli/audiences.mjs +29 -0
- package/cli/audienti.mjs +217 -0
- package/cli/audit-operations.mjs +17 -0
- package/cli/audit.mjs +39 -0
- package/cli/budget-operations.mjs +8 -0
- package/cli/budgets.mjs +34 -0
- package/cli/campaign-find.mjs +155 -0
- package/cli/campaign-operations.mjs +30 -0
- package/cli/campaigns.mjs +61 -0
- package/cli/cohort-operations.mjs +22 -0
- package/cli/cohorts.mjs +45 -0
- package/cli/connection-registry-operations.mjs +8 -0
- package/cli/content.mjs +29 -0
- package/cli/discovery-operations.mjs +28 -0
- package/cli/discovery.mjs +15 -0
- package/cli/file-operations.mjs +29 -0
- package/cli/files.mjs +14 -0
- package/cli/guardrails-operations.mjs +12 -0
- package/cli/guardrails.mjs +37 -0
- package/cli/json-file.mjs +13 -0
- package/cli/kv-operations.mjs +17 -0
- package/cli/kv.mjs +58 -0
- package/cli/mcp.mjs +47 -0
- package/cli/notification-operations.mjs +8 -0
- package/cli/offer-operations.mjs +12 -0
- package/cli/offers.mjs +24 -0
- package/cli/outbound-operations.mjs +16 -0
- package/cli/outbound.mjs +54 -0
- package/cli/outcome-operations.mjs +26 -0
- package/cli/outcomes.mjs +16 -0
- package/cli/owner-account-client.mjs +18 -0
- package/cli/playbook-help.mjs +79 -0
- package/cli/playbook-operations.mjs +36 -0
- package/cli/profiles.mjs +29 -0
- package/cli/progress.mjs +26 -0
- package/cli/prospecting-settings-operations.mjs +16 -0
- package/cli/prospecting-settings.mjs +15 -0
- package/cli/question-operations.mjs +12 -0
- package/cli/questions.mjs +32 -0
- package/cli/queue-operations.mjs +12 -0
- package/cli/queue.mjs +25 -0
- package/cli/recipient-operations.mjs +13 -0
- package/cli/recipients.mjs +34 -0
- package/cli/rooms-operations.mjs +48 -0
- package/cli/rooms.mjs +160 -0
- package/cli/search-operations.mjs +9 -0
- package/cli/secret-operations.mjs +7 -0
- package/cli/secrets.mjs +30 -0
- package/cli/sender-account-operations.mjs +28 -0
- package/cli/sender-accounts.mjs +33 -0
- package/cli/skills-mcp.mjs +31 -0
- package/cli/skills.mjs +113 -0
- package/cli/suppression-operations.mjs +8 -0
- package/cli/suppression.mjs +12 -0
- package/cli/tools-operations.mjs +78 -0
- package/cli/tools.mjs +100 -0
- package/cli/usage-operations.mjs +8 -0
- package/cli/usage.mjs +41 -0
- package/cli/work-execution-operations.mjs +42 -0
- package/cli/work-execution.mjs +29 -0
- package/cli/work-feedback-operations.mjs +17 -0
- package/cli/work-feedback.mjs +18 -0
- package/cli/work-operations.mjs +30 -0
- package/cli/work.mjs +32 -0
- package/cli/workspaces.mjs +255 -0
- package/package.json +15 -26
- package/skills/agent-rooms/SKILL.md +140 -0
- package/skills/agent-rooms/references/commands.md +78 -0
- package/skills/audienti/SKILL.md +161 -317
- package/skills/audienti/references/helpers.md +162 -0
- package/skills/audienti/references/profile-workflows.md +214 -0
- package/skills/audienti/references/workflow-receipt.md +49 -0
- package/skills/audienti/scripts/contracts.mjs +72 -0
- package/skills/audienti/scripts/profile-helpers.mjs +138 -0
- package/skills/audienti/scripts/run.mjs +40 -0
- package/skills/getting-started/SKILL.md +227 -0
- package/skills/getting-started/references/audience.md +143 -0
- package/skills/getting-started/references/campaign-draft.md +80 -0
- package/skills/getting-started/references/capabilities.md +237 -0
- package/skills/getting-started/references/mailbox-check.md +64 -0
- package/skills/getting-started/references/outcome-checks.md +114 -0
- package/skills/getting-started/references/replies.md +186 -0
- package/skills/getting-started/references/reply-checks.md +54 -0
- package/skills/playbook-design/SKILL.md +136 -0
- package/skills/playbook-design/references/fields.md +130 -0
- package/source-manifest.json +104 -0
- package/.mcp.json +0 -13
- package/CHANGELOG.md +0 -1018
- package/bin/audienti-mcp.js +0 -5
- package/bin/audienti.js +0 -6
- package/install +0 -83
- package/skills/audienti/references/methodology.md +0 -111
- package/skills/audienti-gift-research/SKILL.md +0 -47
- package/src/agent-skills.js +0 -60
- package/src/api-client.js +0 -1639
- package/src/cli.js +0 -16992
- package/src/config.js +0 -130
- package/src/mcp.js +0 -103
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const uuid = z.string().uuid();
|
|
3
|
+
// JSON Schema counts Unicode code points. Domain validation and normalization stay
|
|
4
|
+
// server-owned; z.string().max() would instead reject valid astral text by UTF-16 length.
|
|
5
|
+
const bounded = maxLength => z.string().meta({ maxLength });
|
|
6
|
+
const dateTime = () => z.string().meta({ format: 'date-time' });
|
|
7
|
+
const identity = z.union([
|
|
8
|
+
z.object({ kind: z.literal('person'), provider: bounded(64), providerId: bounded(256) }).strict(),
|
|
9
|
+
z.object({ kind: z.literal('person'), provider: bounded(64), url: bounded(2048) }).strict(),
|
|
10
|
+
]);
|
|
11
|
+
const target = z.union([
|
|
12
|
+
z.object({ kind: z.literal('existing'), prospectId: uuid }).strict(),
|
|
13
|
+
z.object({ kind: z.literal('create'), identity, displayName: bounded(200).optional(), profileObservedAt: dateTime().nullable().optional() }).strict(),
|
|
14
|
+
]);
|
|
15
|
+
const payload = z.object({
|
|
16
|
+
direction: z.enum(['inbound', 'outbound']), conversationType: z.enum(['direct', 'public_thread']), occurredAt: dateTime(),
|
|
17
|
+
subject: bounded(512).nullable(), body: bounded(32000).nullable(),
|
|
18
|
+
participants: z.array(z.object({ role: z.enum(['sender', 'recipient', 'cc', 'bcc']), value: bounded(320) }).strict()).max(20).nullable(),
|
|
19
|
+
}).strict();
|
|
20
|
+
const item = z.object({ source: z.object({ provider: bounded(64), accountRef: bounded(200), itemId: bounded(256), threadId: bounded(256).nullable().optional() }).strict(), payload }).strict();
|
|
21
|
+
const body = ({ workspaceId, prospectId, ...rest }) => rest;
|
|
22
|
+
const root = a => `/workspaces/${a.workspaceId}/prospects/${a.prospectId}/conversation-evidence`;
|
|
23
|
+
export const accountIntakeOperations = {
|
|
24
|
+
workspace_prospect_intake: { schema: z.object({ workspaceId: uuid.optional(), requestId: uuid, target, item }).strict(), method: 'POST', scoped: true, path: a => `/workspaces/${a.workspaceId}/prospects/intake`, body },
|
|
25
|
+
workspace_conversation_append: { schema: z.object({ workspaceId: uuid.optional(), prospectId: uuid, requestId: uuid, item }).strict(), method: 'POST', scoped: true, path: root, body },
|
|
26
|
+
workspace_conversation_list: { schema: z.object({ workspaceId: uuid.optional(), prospectId: uuid, cursor: uuid.optional(), limit: z.number().int().min(1).max(100).optional() }).strict(), method: 'GET', scoped: true,
|
|
27
|
+
path: a => { const q = new URLSearchParams(); for (const k of ['cursor', 'limit']) if (a[k] !== undefined) q.set(k, String(a[k])); return root(a) + (q.size ? '?' + q : ''); } },
|
|
28
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { callWorkspace, configuration } from './workspaces.mjs';
|
|
3
|
+
export async function accountIntakeCli(args) {
|
|
4
|
+
const flags = {}, positions = [];
|
|
5
|
+
for (let i = 0; i < args.length; i++) {
|
|
6
|
+
const arg = args[i]; if (arg === '--json') continue;
|
|
7
|
+
if (!arg.startsWith('--')) { positions.push(arg); continue; }
|
|
8
|
+
const key = arg.slice(2);
|
|
9
|
+
if (!['workspace', 'profile', 'file', 'cursor', 'limit'].includes(key) || Object.hasOwn(flags, key) || !args[i+1] || args[i+1].startsWith('--')) throw Error('Unknown, repeated or incomplete intake option.');
|
|
10
|
+
flags[key] = args[++i];
|
|
11
|
+
}
|
|
12
|
+
const intake = positions[1] === 'intake';
|
|
13
|
+
const command = intake ? 'intake' : positions[2];
|
|
14
|
+
if (positions[0] !== 'prospects' || (!intake && positions[1] !== 'conversation') || !['intake', 'append', 'list'].includes(command) || positions.length !== (intake ? 2 : 4)) throw Error('Use prospects intake or prospects conversation append|list PERSON --workspace ID.');
|
|
15
|
+
const allowed = ['workspace', 'profile', ...(command === 'list' ? ['cursor', 'limit'] : ['file'])];
|
|
16
|
+
if (Object.keys(flags).some(k => !allowed.includes(k))) throw Error('Option does not apply to this intake command.');
|
|
17
|
+
let input = {};
|
|
18
|
+
if (command !== 'list') {
|
|
19
|
+
if (!flags.file) throw Error('--file INPUT.json is required.');
|
|
20
|
+
let raw; try { raw = readFileSync(flags.file); } catch { throw Error('Could not read the intake input file.'); }
|
|
21
|
+
if (raw.length > 65536) throw Error('payload_too_large');
|
|
22
|
+
try { input = JSON.parse(raw.toString('utf8')); } catch { throw Error('validation_error'); }
|
|
23
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || ['workspaceId', 'prospectId'].some(k => Object.hasOwn(input, k))) throw Error('invalid_fields');
|
|
24
|
+
}
|
|
25
|
+
input.workspaceId = flags.workspace;
|
|
26
|
+
if (!intake) input.prospectId = positions[3];
|
|
27
|
+
if (flags.cursor !== undefined) input.cursor = flags.cursor;
|
|
28
|
+
if (flags.limit !== undefined) {
|
|
29
|
+
if (!/^\d+$/.test(flags.limit)) throw Error('invalid_fields');
|
|
30
|
+
input.limit = Number(flags.limit);
|
|
31
|
+
}
|
|
32
|
+
console.log(JSON.stringify(await callWorkspace({ intake: 'workspace_prospect_intake', append: 'workspace_conversation_append', list: 'workspace_conversation_list' }[command], input, configuration(flags.profile))));
|
|
33
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { callWorkspace, configuration } from './workspaces.mjs';
|
|
3
|
+
export async function accountRepliesCli(args) {
|
|
4
|
+
const flags = {}, positions = [];
|
|
5
|
+
for (let i = 0; i < args.length; i++) {
|
|
6
|
+
const arg = args[i];
|
|
7
|
+
if (arg === '--json') continue;
|
|
8
|
+
if (!arg.startsWith('--')) { positions.push(arg); continue; }
|
|
9
|
+
const key = arg.slice(2);
|
|
10
|
+
if (!['workspace', 'profile', 'file', 'evidence', 'cursor', 'limit', 'history-cursor', 'history-limit', 'status'].includes(key) || Object.hasOwn(flags, key) || !args[i+1] || args[i+1].startsWith('--')) throw Error('Unknown, repeated or incomplete reply option.');
|
|
11
|
+
flags[key] = args[++i];
|
|
12
|
+
}
|
|
13
|
+
const command = positions[2];
|
|
14
|
+
if (positions[0] !== 'prospects' || positions[1] !== 'replies' || !['set', 'get', 'list'].includes(command) || positions.length !== 4) throw Error('Use prospects replies set|get|list PERSON --workspace ID.');
|
|
15
|
+
const allowed = ['workspace', 'profile', ...(command === 'set' ? ['file', 'evidence'] : command === 'get' ? ['evidence', 'history-cursor', 'history-limit'] : ['cursor', 'limit', 'status'])];
|
|
16
|
+
if (Object.keys(flags).some(k => !allowed.includes(k))) throw Error('Option does not apply to this reply command.');
|
|
17
|
+
if (command !== 'list' && !flags.evidence) throw Error('--evidence EVIDENCE is required.');
|
|
18
|
+
let input = {};
|
|
19
|
+
if (command === 'set') {
|
|
20
|
+
if (!flags.file) throw Error('--file INPUT.json is required.');
|
|
21
|
+
let raw; try { raw = readFileSync(flags.file); } catch { throw Error('Could not read the reply input file.'); }
|
|
22
|
+
if (raw.length > 65536) throw Error('payload_too_large');
|
|
23
|
+
try { input = JSON.parse(raw.toString('utf8')); } catch { throw Error('validation_error'); }
|
|
24
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || ['workspaceId', 'prospectId', 'evidenceId'].some(k => Object.hasOwn(input, k))) throw Error('invalid_fields');
|
|
25
|
+
}
|
|
26
|
+
input.workspaceId = flags.workspace;
|
|
27
|
+
input.prospectId = positions[3];
|
|
28
|
+
if (flags.evidence !== undefined) input.evidenceId = flags.evidence;
|
|
29
|
+
for (const [flag, key] of [['cursor', 'cursor'], ['history-cursor', 'historyCursor'], ['status', 'status']]) if (flags[flag] !== undefined) input[key] = flags[flag];
|
|
30
|
+
for (const [flag, key] of [['limit', 'limit'], ['history-limit', 'historyLimit']]) if (flags[flag] !== undefined) {
|
|
31
|
+
if (!/^\d+$/.test(flags[flag])) throw Error('invalid_fields');
|
|
32
|
+
input[key] = Number(flags[flag]);
|
|
33
|
+
}
|
|
34
|
+
console.log(JSON.stringify(await callWorkspace(`workspace_reply_${command}`, input, configuration(flags.profile))));
|
|
35
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const uuid = z.string().uuid();
|
|
3
|
+
const scope = { workspaceId: uuid.optional(), prospectId: uuid };
|
|
4
|
+
const item = { ...scope, evidenceId: uuid };
|
|
5
|
+
const status = z.enum(['needs_handling', 'handled', 'not_reply']);
|
|
6
|
+
const outcome = z.enum(['response_recorded', 'closed_without_response', 'suppressed']);
|
|
7
|
+
const limit = z.number().int().min(1).max(100);
|
|
8
|
+
const classification = z.enum(['interested', 'question', 'later', 'out_of_office', 'stop', 'bounce', 'referral', 'wrong_person']);
|
|
9
|
+
const slot = z.object({ start: z.string().describe('UTC start, ending in Z.'), end: z.string().describe('UTC end, ending in Z.'), timeZone: z.string().describe("The user's IANA time zone, for display.") }).strict();
|
|
10
|
+
const draft = z.object({
|
|
11
|
+
type: z.enum(['email.send', 'linkedin.send']),
|
|
12
|
+
content: z.record(z.string(), z.unknown()).describe('Email: { subject, body }. LinkedIn: { body }.'),
|
|
13
|
+
recipient: z.record(z.string(), z.unknown()).describe('Email: { prospectId, email, contactId? }. LinkedIn: { prospectId, profileUrl, contactId? }. Bind contactId and senderAccountId, or the answer waits in Blocked.'),
|
|
14
|
+
sender: z.record(z.string(), z.unknown()).describe("{ provider, accountRef, senderAccountId? }: the assignee's mailbox or LinkedIn account the inbound came to."),
|
|
15
|
+
reason: z.string().describe('Why this answer, in one or two sentences.'),
|
|
16
|
+
slots: z.array(slot).min(1).max(6).optional().describe('Free times from your calendar tool that the answer offers. Leave out without a calendar; the answer then asks for a time.'),
|
|
17
|
+
skill: z.object({ id: z.string(), version: z.string() }).strict().optional(),
|
|
18
|
+
}).strict();
|
|
19
|
+
// Brief 12: what a stop, bounce or referral means, as a typed detail next to the classification it matches.
|
|
20
|
+
const quote = z.string().meta({ minLength: 1, maxLength: 500 }).describe("The prospect's words, verbatim.");
|
|
21
|
+
const result = z.discriminatedUnion('type', [
|
|
22
|
+
z.object({ type: z.literal('email.bounce'), address: z.string().meta({ minLength: 3, maxLength: 320 }).describe('The address that bounced.'), kind: z.enum(['hard', 'soft']).describe('hard: the address does not exist or refuses mail. soft: a full mailbox or a temporary failure.'), evidence: z.string().meta({ minLength: 1, maxLength: 2000 }).describe('The bounce message, its status code and diagnostic.') }).strict().describe('Classification bounce. A hard bounce, or a second bounce, marks the address bounced and blocks it in every campaign.'),
|
|
23
|
+
z.object({ type: z.literal('reply.stop'), scope: z.enum(['person', 'company']).describe('company only when the prospect asked to stop for their whole company.'), quote }).strict().describe('Classification stop, with status handled and outcome suppressed. The person, or everyone at the company, is suppressed for good.'),
|
|
24
|
+
z.object({ type: z.literal('reply.referral'), name: z.string().meta({ minLength: 1, maxLength: 200 }).describe('The person the prospect named.'), title: z.string().meta({ maxLength: 200 }).optional(), email: z.string().meta({ maxLength: 320 }).optional(), linkedinUrl: z.string().meta({ maxLength: 500 }).optional(), quote }).strict().describe('Classification referral, with status needs_handling and the thank-you as the draft. The named person is checked against the audience before any contact.'),
|
|
25
|
+
]);
|
|
26
|
+
const root = a => `/workspaces/${a.workspaceId}/prospects/${a.prospectId}`;
|
|
27
|
+
const detail = a => `${root(a)}/conversation-evidence/${a.evidenceId}/reply`;
|
|
28
|
+
const query = (path, a, keys) => {
|
|
29
|
+
const q = new URLSearchParams();
|
|
30
|
+
for (const key of keys) if (a[key] !== undefined) q.set(key, String(a[key]));
|
|
31
|
+
return path + (q.size ? `?${q}` : '');
|
|
32
|
+
};
|
|
33
|
+
export const accountReplyOperations = {
|
|
34
|
+
workspace_reply_set: {
|
|
35
|
+
schema: z.object({ ...item, requestId: uuid, expectedVersion: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER), status,
|
|
36
|
+
outcome: outcome.nullable().describe("Null for needs_handling/not_reply; a handling outcome is required for handled."), reason: z.string().meta({ minLength: 1, maxLength: 2000 }).describe("Required trimmed NFC reason, 1–2000 codepoints. Interior control characters including newline and tab are rejected."), responseEvidenceId: uuid.nullable().describe("Required for response_recorded; null otherwise. Must identify matching outbound account evidence."),
|
|
37
|
+
classification: classification.optional().describe("What the reply is. Records a replied event on the person's active campaigns, except for bounce and out_of_office. An out_of_office reply snoozes the person instead; a wrong_person reply with no name given stops the sends and asks who the right person is. Someone who names the right person is a referral."),
|
|
38
|
+
returnsOn: z.string().datetime().optional().describe("Only with out_of_office: the date the person is back (UTC, ending in Z). The snooze lasts until then; with no date it follows the playbook's out-of-office rule, 7 days by default."),
|
|
39
|
+
invite: z.literal(true).optional().describe("The prospect sent a calendar invite you have not processed yet. Requires a classification; a meetings.confirm task follows."),
|
|
40
|
+
draft: draft.optional().describe("Your drafted answer. Saved as a reply plan revision the user reviews in Operator; requires status needs_handling. Never sent until approved."),
|
|
41
|
+
result: result.optional().describe("Brief 12: the typed detail for a bounce, stop or referral. Its type must match the classification."),
|
|
42
|
+
meeting: z.object({ at: z.string().datetime(), source: z.enum(['sent_invite', 'received_invite']) }).strict().optional().describe("A booked meeting: when, and whether the user's calendar sent the invite or the prospect did. Records meeting_booked."),
|
|
43
|
+
meetingState: z.enum(['requested', 'accepted', 'declined']).optional().describe("Where a meeting that is not booked yet stands after this reply: requested (a time was asked for), accepted (they agreed, no time yet) or declined. Records the matching meeting event once. Ignored once a meeting is booked.") }).strict().describe("Explicit inbound classification. needs_handling/not_reply require null outcome and responseEvidenceId. handled requires an outcome; response_recorded requires matching outbound evidence, closed_without_response an explicit closure reason, and suppressed active DNC or Reject."),
|
|
44
|
+
method: 'POST', scoped: true, path: detail,
|
|
45
|
+
body: ({ workspaceId, prospectId, evidenceId, ...rest }) => rest,
|
|
46
|
+
},
|
|
47
|
+
workspace_reply_get: {
|
|
48
|
+
schema: z.object({ ...item, historyCursor: uuid.optional(), historyLimit: limit.optional() }).strict(),
|
|
49
|
+
method: 'GET', scoped: true, path: a => query(detail(a), a, ['historyCursor', 'historyLimit']),
|
|
50
|
+
},
|
|
51
|
+
workspace_reply_list: {
|
|
52
|
+
schema: z.object({ ...scope, cursor: uuid.optional(), limit: limit.optional(), status: status.optional() }).strict(),
|
|
53
|
+
method: 'GET', scoped: true, path: a => query(`${root(a)}/replies`, a, ['cursor', 'limit', 'status']),
|
|
54
|
+
},
|
|
55
|
+
};
|
package/cli/agents.mjs
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { readProfile, writeProfile, validateUrl, configuration, profileFrom, WorkspaceRequestError, errorGuidance } from './workspaces.mjs';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import {readJsonFile} from './json-file.mjs';
|
|
4
|
+
|
|
5
|
+
export const agentManagementOperations = ['agent_list','agent_revoke','agent_permission','agent_capabilities_set','agent_capabilities_get','agent_inventory_get','agent_capability_sharing_get','agent_capability_sharing_set','agent_account_authorizations_get','agent_account_authorization_set'];
|
|
6
|
+
// The seven capability groups and the 'approve' guardrail (#120). 'listen' is the retired name of 'research'; the server still accepts it.
|
|
7
|
+
const capabilityKeys=['find','enrich','expand','research','engage','sync','notify','approve','listen'];
|
|
8
|
+
const inventoryLabel=z.string().trim().min(1).max(80);
|
|
9
|
+
// Capability rows (ADR 015). The server checks ids, providers and access against the catalog; the client checks the shape.
|
|
10
|
+
const capabilityRow=z.object({capability:z.string().min(1).max(64),provider:z.string().min(1).max(32).optional(),via:z.string().min(1).max(140),account:z.string().min(1).max(200).optional(),access:z.enum(['read','write','send']),cost:z.enum(['owner','credits']).optional(),status:z.enum(['connected','needs_sign_in','unavailable']),limits:z.object({daily:z.number().int().min(0).max(100000).optional(),hourly:z.number().int().min(0).max(100000).optional()}).strict().optional(),identity:z.object({namespace:z.string().min(1).max(64),accountId:z.string().min(1).max(256)}).strict().optional()}).strict();
|
|
11
|
+
// A LinkedIn seat's cadence, InMail and safe limits (brief 09); the server checks the bounds.
|
|
12
|
+
const seatLimit=z.number().int().min(0).max(100000);
|
|
13
|
+
const seatFacts={checkEverySeconds:z.number().int().min(3600).max(604800).optional(),inmail:z.object({available:z.boolean(),quotaPerMonth:seatLimit.optional(),creditsLeft:seatLimit.optional()}).strict().optional(),plan:z.enum(['free','premium_career','premium_business','premium_all_in_one','sales_navigator_core','sales_navigator_advanced','recruiter_lite']).optional(),limits:z.object({connectionRequestsPerDay:seatLimit.optional(),connectionRequestsPerWeek:seatLimit.optional(),messagesPerDay:seatLimit.optional(),inmailPerMonth:seatLimit.optional()}).strict().optional()};
|
|
14
|
+
const accountEntry=z.object({capability:z.string(),tools:z.array(z.string()).min(1).max(8)}).strict();
|
|
15
|
+
const accountReport=z.object({provider:z.enum(['email','linkedin']),accountRef:z.string().min(1).max(200),status:z.enum(['healthy','unavailable','disconnected']),accountIdentity:z.object({namespace:z.string(),accountId:z.string()}).strict().optional(),capabilities:z.array(accountEntry).max(16),...seatFacts}).strict();
|
|
16
|
+
const capabilityReport=z.object({runtime:z.object({name:z.string().trim().min(1).max(80),model:z.string().trim().min(1).max(80).optional()}).strict(),capabilities:z.array(capabilityRow).max(64),seats:z.array(z.object({provider:z.literal('linkedin'),account:z.string().min(1).max(200),...seatFacts}).strict()).max(50).optional()}).strict();
|
|
17
|
+
const eitherReport=z.union([capabilityReport,accountReport]);
|
|
18
|
+
const inventoryEntry=z.object({category:z.enum(['web_search','web_browse','people_data','company_data','email','linkedin','social','crm','spreadsheet','docs','chat','calendar','tasks','files','code','scheduling','memory','other']),name:inventoryLabel,access:z.array(z.enum(['read','write','send'])).min(1).max(3),status:z.enum(['connected','needs_sign_in','unavailable']),via:inventoryLabel.optional(),tools:z.array(z.string().min(1).max(128)).max(16).optional()}).strict();
|
|
19
|
+
export const agentOperations = {
|
|
20
|
+
agent_inventory_report:{description:'Older shape, kept for one release; use agent_capabilities_report instead. Reports the tools and connectors this agent already has (search, mailboxes, CRM, sheets, chat, scheduling, memory). Replaces the whole inventory. List only what is already available; never connect anything new and never include credentials. Requires an active agent profile.',schema:z.object({runtime:z.object({name:inventoryLabel,model:inventoryLabel.optional()}).strict(),entries:z.array(inventoryEntry).max(64)}).strict()},
|
|
21
|
+
agent_inventory_show:{description:'Read this agent\u2019s own reported tool inventory. Requires an active agent profile.',schema:z.object({}).strict()},
|
|
22
|
+
agent_inventory_get:{schema:z.object({id:z.string().uuid()}).strict()},
|
|
23
|
+
agent_account_authorizations_get:{schema:z.object({id:z.string().uuid()}).strict()},
|
|
24
|
+
agent_account_authorizations_show:{schema:z.object({}).strict()},
|
|
25
|
+
agent_account_authorization_set:{schema:z.object({id:z.string().uuid(),provider:z.enum(['email','linkedin']),accountRef:z.string().min(1).max(200),authorized:z.boolean()}).strict()},
|
|
26
|
+
agent_capability_sharing_get: {schema:z.object({id:z.string().uuid()}).strict()},
|
|
27
|
+
agent_capability_sharing_set: {schema:z.object({id:z.string().uuid(),provider:z.enum(['email','linkedin']),accountRef:z.string().min(1).max(200),workspaceId:z.string().uuid(),shared:z.boolean()}).strict()},
|
|
28
|
+
// One object schema for MCP clients; callAgent then requires exactly one of the two report shapes.
|
|
29
|
+
agent_capabilities_report: {description:'Getting started step 2: report what this agent can do as capability rows ({ runtime, capabilities: [{ capability, provider?, via, account?, access, cost?, status, limits?, identity? }], seats?: [{ provider: "linkedin", account, checkEverySeconds?, plan?, inmail?, limits? }] }), ids from the capability catalog. A LinkedIn account sends only when its rows carry identity { namespace: "linkedin", accountId: the member id }; a mailbox without one is matched by its address. Replaces the previous report. The old per-account shape (provider, accountRef, status, capabilities) is still accepted. Never use via "audienti" and never include credentials. Requires an active agent profile.',schema:z.object({runtime:capabilityReport.shape.runtime.optional(),provider:accountReport.shape.provider.optional(),accountRef:accountReport.shape.accountRef.optional(),status:accountReport.shape.status.optional(),accountIdentity:accountReport.shape.accountIdentity,capabilities:z.array(z.union([capabilityRow,accountEntry])).max(64),seats:capabilityReport.shape.seats,...seatFacts}).strict(),shape:eitherReport},
|
|
30
|
+
agent_capabilities_list: {description:'Read what this agent can do now: its own capability rows plus Audienti hosted rows for the workspace (when the workspace has credits and the user approved the group). Uses the profile workspace unless workspaceId is given. Requires an active agent profile.',schema:z.object({workspaceId:z.string().uuid().optional()}).strict()},
|
|
31
|
+
agent_capabilities_show: {schema:z.object({}).strict()},
|
|
32
|
+
agent_capabilities_get: {schema:z.object({id:z.string().uuid()}).strict()},
|
|
33
|
+
agent_permission: {schema:z.object({id:z.string().uuid(),scope:z.enum(['workspace:read','workspace:access'])}).strict()},
|
|
34
|
+
agent_capabilities_set: {schema:z.object({id:z.string().uuid(),capabilities:z.array(z.enum(capabilityKeys)).min(1).max(9)}).strict()},
|
|
35
|
+
agent_connect: { schema: z.object({ name: z.string().trim().min(1).max(80), capabilities: z.array(z.enum(capabilityKeys)).max(9).optional(), write: z.boolean().optional() }).strict() },
|
|
36
|
+
agent_poll: { schema: z.object({}).strict() },
|
|
37
|
+
agent_cancel: { schema: z.object({}).strict() },
|
|
38
|
+
agent_list: { schema: z.object({}).strict() },
|
|
39
|
+
agent_revoke: { schema: z.object({ id: z.string().uuid() }).strict() },
|
|
40
|
+
};
|
|
41
|
+
async function request(config, path, body, authenticated = false, method = body === undefined ? 'GET' : 'POST') {
|
|
42
|
+
const url = validateUrl(config.url);
|
|
43
|
+
let response;
|
|
44
|
+
try { response = await fetch(new URL(`/api/v1${path}`, url), { method, headers: { Accept: 'application/json', 'Content-Type': 'application/json', ...(authenticated ? { Authorization: `Bearer ${config.token ?? ''}` } : {}) }, ...(body === undefined ? {} : { body: JSON.stringify(body) }), redirect: 'error', signal: AbortSignal.timeout(10000) }); }
|
|
45
|
+
catch { throw new Error('Could not reach the API. Redirects are not followed.'); }
|
|
46
|
+
let result; try { result = await response.json(); } catch { throw new Error('The API returned an invalid response.'); }
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
const code = typeof result?.error?.code === 'string' && /^[a-z_]+$/.test(result.error.code) ? result.error.code : 'request_failed';
|
|
49
|
+
// A refused report says why (ADR 015), with the same safety checks as the workspace client.
|
|
50
|
+
const message = result?.error?.message;
|
|
51
|
+
const detail = response.status === 400 && (code === 'validation_error' || code === 'hosted_via_reserved') && typeof message === 'string' && message.length <= 300 && !/[\u0000-\u001f\u007f]/.test(message) && !(config.token && message.includes(config.token)) ? ` ${message}` : '';
|
|
52
|
+
throw new WorkspaceRequestError(response.status, code, `Agent request failed: ${code} (HTTP ${response.status}).${detail}`, undefined, undefined, undefined, errorGuidance(result?.error, config.token));
|
|
53
|
+
}
|
|
54
|
+
if (!result || !Object.hasOwn(result, 'data')) throw new Error('The API returned an invalid response.');
|
|
55
|
+
return result.data;
|
|
56
|
+
}
|
|
57
|
+
export async function callAgent(name, args, config, profilePath) {
|
|
58
|
+
const input = agentOperations[name]?.schema.safeParse(args);
|
|
59
|
+
if (!input?.success || (agentOperations[name].shape && !agentOperations[name].shape.safeParse(args).success)) throw new Error('Invalid agent command or arguments.');
|
|
60
|
+
if (agentManagementOperations.includes(name) && (config.profile?.agent || config.profile?.pendingAgent)) throw new Error('Use a separate user management profile with an agents:manage token.');
|
|
61
|
+
if(name==='agent_account_authorizations_get') return {data:await request(config,`/agents/${input.data.id}/account-authorizations`,undefined,true)};
|
|
62
|
+
if(name==='agent_account_authorization_set') {const {id,...body}=input.data;return {data:await request(config,`/agents/${id}/account-authorizations`,body,true)};}
|
|
63
|
+
if(name==='agent_account_authorizations_show') {
|
|
64
|
+
if(!config.profile?.agent || !/^agt_/.test(config.token ?? '')) throw new Error('Use an active agent profile for authorization self readback.');
|
|
65
|
+
return {data:await request(config,'/agent-account-authorizations',undefined,true)};
|
|
66
|
+
}
|
|
67
|
+
if (name === 'agent_capability_sharing_get') return {data:await request(config,`/agents/${input.data.id}/capability-sharing`,undefined,true)};
|
|
68
|
+
if (name === 'agent_capability_sharing_set') {const {id,...body}=input.data;return {data:await request(config,`/agents/${id}/capability-sharing`,body,true)};}
|
|
69
|
+
if (name === 'agent_inventory_get') return {data:await request(config,`/agents/${input.data.id}/inventory`,undefined,true)};
|
|
70
|
+
if (name === 'agent_inventory_show' || name === 'agent_inventory_report') {
|
|
71
|
+
if (!config.profile?.agent || !/^agt_/.test(config.token ?? '')) throw new Error('Use an active agent profile for tool inventory report and self readback.');
|
|
72
|
+
return {data:await request(config,'/agent-inventory',name==='agent_inventory_report' ? input.data : undefined,true,name==='agent_inventory_report' ? 'PUT' : 'GET')};
|
|
73
|
+
}
|
|
74
|
+
if (name === 'agent_capabilities_get') return {data:await request(config,`/agents/${input.data.id}/capabilities`,undefined,true)};
|
|
75
|
+
if (name === 'agent_capabilities_list') {
|
|
76
|
+
if (!config.profile?.agent || !/^agt_/.test(config.token ?? '')) throw new Error('Use an active agent profile for capability check-in and self readback.');
|
|
77
|
+
const workspaceId = input.data.workspaceId ?? config.workspaceId;
|
|
78
|
+
return {data:await request(config,`/agent-capabilities${workspaceId ? `?workspaceId=${encodeURIComponent(workspaceId)}` : ''}`,undefined,true)};
|
|
79
|
+
}
|
|
80
|
+
if (name === 'agent_capabilities_show' || name === 'agent_capabilities_report') {
|
|
81
|
+
if (!config.profile?.agent || !/^agt_/.test(config.token ?? '')) throw new Error('Use an active agent profile for capability check-in and self readback.');
|
|
82
|
+
return {data:await request(config,'/agent-capabilities',name==='agent_capabilities_report' ? input.data : undefined,true,name==='agent_capabilities_report' ? 'PUT' : 'GET')};
|
|
83
|
+
}
|
|
84
|
+
if (name === 'agent_list') return { data: await request(config, '/agents', undefined, true) };
|
|
85
|
+
if (name === 'agent_permission') return {data:await request(config, `/agents/${input.data.id}/permission`, {scope:input.data.scope}, true)};
|
|
86
|
+
if (name === 'agent_capabilities_set') return {data:await request(config, `/agents/${input.data.id}/approved-capabilities`, {capabilities:input.data.capabilities}, true)};
|
|
87
|
+
if (name === 'agent_revoke') return { data: await request(config, `/agents/${input.data.id}/revoke`, {}, true) };
|
|
88
|
+
if (!profilePath) throw new Error('Use a separate private --profile PATH for each agent.');
|
|
89
|
+
const profile = readProfile(profilePath);
|
|
90
|
+
const url = validateUrl(config.url).toString().replace(/\/$/, '');
|
|
91
|
+
if (name === 'agent_connect') {
|
|
92
|
+
if (profile.pendingAgent || profile.agent) throw new Error('This profile already has an agent connection. Poll/cancel the pending connection or choose a new profile.');
|
|
93
|
+
if (Object.keys(profile.tokens ?? {}).length) throw new Error('Use a new agent profile without user API tokens.');
|
|
94
|
+
// Prove that private persistence is possible before asking the server for secrets.
|
|
95
|
+
writeProfile(profilePath, { ...profile, url });
|
|
96
|
+
const data = await request(config, '/agent-connections', input.data);
|
|
97
|
+
if (!/^adc_[A-Za-z0-9_-]{43}$/.test(data.secret) || !/^agt_[A-Za-z0-9_-]{43}$/.test(data.credential) || !z.string().uuid().safeParse(data.id).success || !/^[A-F0-9]{4}-[A-F0-9]{4}$/.test(data.code)) throw new Error('Invalid connection response.');
|
|
98
|
+
writeProfile(profilePath, { ...profile, url, pendingAgent: { id: data.id, secret: data.secret, credential: data.credential, expiresAt: data.expiresAt } });
|
|
99
|
+
config.token = undefined; config.workspaceId = undefined;
|
|
100
|
+
return { data: { status: 'pending', id: data.id, code: data.code, approvalUrl: data.approvalUrl, approvalPath: `/agents/connect/${data.id}`, expiresAt: data.expiresAt, interval: 5, instructions: 'Give your user the approval URL and the code, then run agent poll with this profile every interval seconds, in this same turn and without waiting for them to reply, until the status is active, denied or expired.' } };
|
|
101
|
+
}
|
|
102
|
+
const secret = profile.pendingAgent?.secret ?? profile.connectionSecret;
|
|
103
|
+
if (!secret) throw new Error('No connection in this profile.');
|
|
104
|
+
if (url !== profile.url) throw new Error('The pending connection belongs to a different API host.');
|
|
105
|
+
const data = await request(config, name === 'agent_poll' ? '/agent-connections/poll' : '/agent-connections/cancel', { secret });
|
|
106
|
+
if (data.status === 'active') {
|
|
107
|
+
const credential = profile.pendingAgent?.credential ?? profile.tokens?.['workspace:read'];
|
|
108
|
+
writeProfile(profilePath, { ...profile, agent: data.agent, connectionSecret: secret, pendingAgent: undefined, tokens: { 'workspace:read': credential } });
|
|
109
|
+
config.token = credential;
|
|
110
|
+
} else if (['denied', 'expired'].includes(data.status)) { writeProfile(profilePath, { ...profile, pendingAgent: undefined, agent: undefined, connectionSecret: undefined, tokens: undefined, workspaceId: undefined }); config.token = undefined; config.workspaceId = undefined; }
|
|
111
|
+
return { data };
|
|
112
|
+
}
|
|
113
|
+
export async function agentCli(args) {
|
|
114
|
+
const flags = {}; const positions = []; let json = false;
|
|
115
|
+
for (let i = 0; i < args.length; i++) {
|
|
116
|
+
if (args[i] === '--json') { json = true; continue; }
|
|
117
|
+
if (args[i].startsWith('--')) {
|
|
118
|
+
const key = args[i].slice(2);
|
|
119
|
+
if (key === 'write') { flags.write = true; continue; }
|
|
120
|
+
if (!['profile', 'name','capabilities','scope','file','id','provider','account','workspace','shared','authorized','set'].includes(key) || !args[i+1] || args[i+1].startsWith('--')) throw new Error('Unknown or incomplete agent option.');
|
|
121
|
+
flags[key] = args[++i];
|
|
122
|
+
} else positions.push(args[i]);
|
|
123
|
+
}
|
|
124
|
+
let name = `agent_${positions[1]}`;
|
|
125
|
+
let input = positions[1] === 'permission' ? {id:positions[2],scope:flags.scope} : positions[1] === 'connect' ? { name: flags.name, ...(flags.write ? { write: true } : {}), ...(flags.capabilities ? { capabilities: flags.capabilities.split(',').map(key => key.trim()).filter(Boolean) } : {}) } : positions[1] === 'revoke' ? { id: positions[2] } : {};
|
|
126
|
+
if(positions[1]==='accounts') {
|
|
127
|
+
if(positions[2]==='show') {name=flags.id ? 'agent_account_authorizations_get' : 'agent_account_authorizations_show';input=flags.id ? {id:flags.id} : {};}
|
|
128
|
+
else if(positions[2]==='authorize' && ['true','false'].includes(flags.authorized)) {name='agent_account_authorization_set';input={id:flags.id,provider:flags.provider,accountRef:flags.account,authorized:flags.authorized==='true'};}
|
|
129
|
+
else throw new Error('Use agent accounts show [--id ID] or authorize --id ID --provider PROVIDER --account REF --authorized true|false.');
|
|
130
|
+
}
|
|
131
|
+
if (positions[1] === 'inventory') {
|
|
132
|
+
if (positions[2] === 'report' && flags.file && !flags.id) {name='agent_inventory_report';input=readJsonFile(flags.file,32768,'Inventory file');console.error('Notice: agent capabilities report --file PATH is the current command; inventory reports are translated into capability rows.');}
|
|
133
|
+
else if (positions[2] === 'show' && !flags.file) {name=flags.id ? 'agent_inventory_get' : 'agent_inventory_show';input=flags.id ? {id:flags.id} : {};}
|
|
134
|
+
else throw new Error('Use agent inventory report --file PATH or show [--id ID].');
|
|
135
|
+
}
|
|
136
|
+
if (positions[1] === 'capabilities') {
|
|
137
|
+
if (flags.set !== undefined && positions[2]) {name='agent_capabilities_set';input={id:positions[2],capabilities:flags.set.split(',').map(key => key.trim()).filter(Boolean)};}
|
|
138
|
+
else if (positions[2] === 'sharing' && flags.id) {name='agent_capability_sharing_get';input={id:flags.id};}
|
|
139
|
+
else if (positions[2] === 'share' && flags.id && ['true','false'].includes(flags.shared)) {name='agent_capability_sharing_set';input={id:flags.id,provider:flags.provider,accountRef:flags.account,workspaceId:flags.workspace,shared:flags.shared==='true'};}
|
|
140
|
+
else if (positions[2] === 'report' && flags.file && !flags.id) {name='agent_capabilities_report';input=readCapabilityFile(flags.file);}
|
|
141
|
+
else if (positions[2] === 'show' && !flags.file) {name=flags.id ? 'agent_capabilities_get' : 'agent_capabilities_show';input=flags.id ? {id:flags.id} : {};}
|
|
142
|
+
else if (positions[2] === undefined && !flags.file && !flags.id) {name='agent_capabilities_list';input=flags.workspace ? {workspaceId:flags.workspace} : {};}
|
|
143
|
+
else throw new Error('Use agent capabilities [--workspace ID] [--json], report --file PATH or show [--id ID].');
|
|
144
|
+
}
|
|
145
|
+
const result = await callAgent(name, input, configuration(flags.profile, agentManagementOperations.includes(name) ? 'agents:manage' : 'workspace:access'), profileFrom(flags.profile));
|
|
146
|
+
console.log(name === 'agent_capabilities_list' && !json ? capabilityText(result.data) : JSON.stringify(result));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Plain text for agent capabilities: own rows that work now, then rows that work with Audienti hosted tools. */
|
|
150
|
+
export function capabilityText(data) {
|
|
151
|
+
const rows = Array.isArray(data?.effective) ? data.effective : [];
|
|
152
|
+
const line = row => ` ${row.capability}${row.provider ? ` (${row.provider})` : ''} via ${row.via}${row.account ? `, account ${row.account}` : ''}${row.status === 'connected' ? '' : `, ${row.status.replaceAll('_', ' ')}`}${row.hostedTool ? `, ${row.hostedTool.id}${row.hostedTool.credits === undefined ? '' : ` about ${row.hostedTool.credits} credits a call`}` : ''}${row.stale ? ', stale' : ''}`;
|
|
153
|
+
const own = rows.filter(row => row.via !== 'audienti'), hosted = rows.filter(row => row.via === 'audienti');
|
|
154
|
+
return [
|
|
155
|
+
`Works now${data?.stale ? ' (report is over seven days old; report again)' : ''}:`, ...(own.length ? own.map(line) : [' nothing reported yet: audienti agent capabilities report --file CAPABILITIES.json']),
|
|
156
|
+
'Works with Audienti tools:', ...(hosted.length ? hosted.map(line) : [data?.workspaceId ? ' none: the workspace has no credits or the user has not approved these groups' : ' pick a workspace to see them: --workspace ID']),
|
|
157
|
+
].join('\n');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function readCapabilityFile(path) {return readJsonFile(path,32768,'Capability file');}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const uuid = z.string().uuid();
|
|
3
|
+
const workspace = { workspaceId: uuid.optional() };
|
|
4
|
+
const request = { ...workspace, requestId: uuid };
|
|
5
|
+
const version = z.number().int().nonnegative();
|
|
6
|
+
const page = z
|
|
7
|
+
.object({
|
|
8
|
+
cursor: uuid.optional(),
|
|
9
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
10
|
+
})
|
|
11
|
+
.strict();
|
|
12
|
+
const location = z
|
|
13
|
+
.object({
|
|
14
|
+
key: z.string().nullable().optional(),
|
|
15
|
+
providerId: z.string().nullable().optional(),
|
|
16
|
+
name: z.string(),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
const criteria = z
|
|
20
|
+
.object({
|
|
21
|
+
campaignIds: z.array(uuid).max(50).optional(),
|
|
22
|
+
companyLocations: z.array(location).max(50).optional(),
|
|
23
|
+
personLocations: z.array(location).max(50).optional(),
|
|
24
|
+
personTitles: z.array(z.string()).max(50).optional(),
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
const rule = z
|
|
28
|
+
.object({
|
|
29
|
+
id: uuid,
|
|
30
|
+
name: z.string(),
|
|
31
|
+
enabled: z.boolean(),
|
|
32
|
+
assigneeUserId: uuid,
|
|
33
|
+
criteria,
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
const root = (a) => `/workspaces/${a.workspaceId}`;
|
|
37
|
+
const person = (a) => `${root(a)}/prospects/${a.prospectId}`;
|
|
38
|
+
const omit = (a, keys) =>
|
|
39
|
+
Object.fromEntries(Object.entries(a).filter(([k]) => !keys.includes(k)));
|
|
40
|
+
const body = (a) => omit(a, ["workspaceId", "prospectId"]);
|
|
41
|
+
function qs(a, keys = ["workspaceId", "prospectId", "campaignId"]) {
|
|
42
|
+
const values = {};
|
|
43
|
+
for (const [k, v] of Object.entries(omit(a, keys))) {
|
|
44
|
+
if (v === undefined) continue;
|
|
45
|
+
if (typeof v === "object" && v !== null)
|
|
46
|
+
for (const [child, value] of Object.entries(v))
|
|
47
|
+
values[k + child[0].toUpperCase() + child.slice(1)] = String(value);
|
|
48
|
+
else values[k] = String(v);
|
|
49
|
+
}
|
|
50
|
+
return new URLSearchParams(values).toString();
|
|
51
|
+
}
|
|
52
|
+
export const assignmentOperations = {
|
|
53
|
+
workspace_prospect_assignments: {
|
|
54
|
+
schema: z
|
|
55
|
+
.object({
|
|
56
|
+
...workspace,
|
|
57
|
+
assignedToUserId: uuid.optional(),
|
|
58
|
+
unassigned: z.boolean().optional(),
|
|
59
|
+
campaignId: uuid.optional(),
|
|
60
|
+
search: z.string().optional(),
|
|
61
|
+
cursor: uuid.optional(),
|
|
62
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
63
|
+
})
|
|
64
|
+
.strict(),
|
|
65
|
+
method: "GET",
|
|
66
|
+
path: (a) => root(a) + "/assignments?" + qs(a, ["workspaceId"]),
|
|
67
|
+
scoped: true,
|
|
68
|
+
},
|
|
69
|
+
workspace_prospect_assignment: {
|
|
70
|
+
schema: z
|
|
71
|
+
.object({
|
|
72
|
+
...workspace,
|
|
73
|
+
prospectId: uuid,
|
|
74
|
+
history: page.optional(),
|
|
75
|
+
participations: page.optional(),
|
|
76
|
+
participationEvents: page.optional(),
|
|
77
|
+
routingReports: page.optional(),
|
|
78
|
+
})
|
|
79
|
+
.strict(),
|
|
80
|
+
method: "GET",
|
|
81
|
+
path: (a) => person(a) + "/assignment?" + qs(a),
|
|
82
|
+
scoped: true,
|
|
83
|
+
},
|
|
84
|
+
workspace_prospect_campaign_fit: {
|
|
85
|
+
schema: z
|
|
86
|
+
.object({
|
|
87
|
+
...workspace,
|
|
88
|
+
prospectId: uuid,
|
|
89
|
+
campaignId: uuid,
|
|
90
|
+
overrides: page.optional(),
|
|
91
|
+
})
|
|
92
|
+
.strict(),
|
|
93
|
+
method: "GET",
|
|
94
|
+
path: (a) => person(a) + `/campaigns/${a.campaignId}/fit?` + qs(a),
|
|
95
|
+
scoped: true,
|
|
96
|
+
},
|
|
97
|
+
workspace_prospect_assignment_set: {
|
|
98
|
+
schema: z
|
|
99
|
+
.object({
|
|
100
|
+
...request,
|
|
101
|
+
prospectId: uuid,
|
|
102
|
+
assigneeUserId: uuid.nullable(),
|
|
103
|
+
expectedAssignmentVersion: version,
|
|
104
|
+
reason: z.string(),
|
|
105
|
+
})
|
|
106
|
+
.strict(),
|
|
107
|
+
method: "POST",
|
|
108
|
+
path: (a) => person(a) + "/assignment",
|
|
109
|
+
body,
|
|
110
|
+
scoped: true,
|
|
111
|
+
},
|
|
112
|
+
workspace_routing_rules_get: {
|
|
113
|
+
schema: z.object(workspace).strict(),
|
|
114
|
+
method: "GET",
|
|
115
|
+
path: (a) => root(a) + "/routing-rules",
|
|
116
|
+
scoped: true,
|
|
117
|
+
},
|
|
118
|
+
workspace_routing_rules_replace: {
|
|
119
|
+
schema: z
|
|
120
|
+
.object({
|
|
121
|
+
...request,
|
|
122
|
+
expectedRulesVersion: version,
|
|
123
|
+
rules: z.array(rule).max(100),
|
|
124
|
+
})
|
|
125
|
+
.strict(),
|
|
126
|
+
method: "PUT",
|
|
127
|
+
path: (a) => root(a) + "/routing-rules",
|
|
128
|
+
body: (a) => omit(a, ["workspaceId"]),
|
|
129
|
+
scoped: true,
|
|
130
|
+
},
|
|
131
|
+
workspace_routing_apply: {
|
|
132
|
+
schema: z
|
|
133
|
+
.object({
|
|
134
|
+
...request,
|
|
135
|
+
prospectId: uuid,
|
|
136
|
+
campaignId: uuid.optional(),
|
|
137
|
+
expectedAssignmentVersion: version,
|
|
138
|
+
rulesVersion: version,
|
|
139
|
+
outcomes: z
|
|
140
|
+
.array(
|
|
141
|
+
z
|
|
142
|
+
.object({
|
|
143
|
+
ruleId: uuid,
|
|
144
|
+
result: z.enum(["match", "no_match", "unknown"]),
|
|
145
|
+
reason: z.string().optional(),
|
|
146
|
+
})
|
|
147
|
+
.strict(),
|
|
148
|
+
)
|
|
149
|
+
.max(100),
|
|
150
|
+
skill: z.object({ id: z.string(), version: z.string() }).strict(),
|
|
151
|
+
})
|
|
152
|
+
.strict(),
|
|
153
|
+
method: "POST",
|
|
154
|
+
path: (a) => person(a) + "/routing",
|
|
155
|
+
body,
|
|
156
|
+
scoped: true,
|
|
157
|
+
},
|
|
158
|
+
workspace_fit_override_set: {
|
|
159
|
+
schema: z
|
|
160
|
+
.object({
|
|
161
|
+
...request,
|
|
162
|
+
prospectId: uuid,
|
|
163
|
+
campaignId: uuid,
|
|
164
|
+
campaignRevision: z.number().int().positive(),
|
|
165
|
+
expectedOverrideVersion: version,
|
|
166
|
+
action: z.enum(["set", "clear"]),
|
|
167
|
+
value: z.enum(["high", "moderate", "low", "no_fit"]).optional(),
|
|
168
|
+
reason: z.string(),
|
|
169
|
+
})
|
|
170
|
+
.strict(),
|
|
171
|
+
method: "POST",
|
|
172
|
+
path: (a) => person(a) + "/fit-override",
|
|
173
|
+
body,
|
|
174
|
+
scoped: true,
|
|
175
|
+
},
|
|
176
|
+
workspace_prospect_move: {
|
|
177
|
+
schema: z
|
|
178
|
+
.object({
|
|
179
|
+
...request,
|
|
180
|
+
prospectId: uuid,
|
|
181
|
+
expectedSourceParticipationId: uuid,
|
|
182
|
+
sourceCampaignId: uuid,
|
|
183
|
+
destinationCampaignId: uuid,
|
|
184
|
+
destinationCampaignRevision: z.number().int().positive(),
|
|
185
|
+
destinationQualificationReceiptId: uuid,
|
|
186
|
+
expectedAssignmentVersion: version,
|
|
187
|
+
reason: z.string(),
|
|
188
|
+
})
|
|
189
|
+
.strict(),
|
|
190
|
+
method: "POST",
|
|
191
|
+
path: (a) => person(a) + "/move",
|
|
192
|
+
body,
|
|
193
|
+
scoped: true,
|
|
194
|
+
},
|
|
195
|
+
workspace_member_remove: {
|
|
196
|
+
schema: z
|
|
197
|
+
.object({
|
|
198
|
+
...request,
|
|
199
|
+
expectedMembershipId: uuid,
|
|
200
|
+
memberUserId: uuid,
|
|
201
|
+
reason: z.string(),
|
|
202
|
+
})
|
|
203
|
+
.strict(),
|
|
204
|
+
method: "POST",
|
|
205
|
+
path: (a) => root(a) + "/members/remove",
|
|
206
|
+
body: (a) => omit(a, ["workspaceId"]),
|
|
207
|
+
scoped: true,
|
|
208
|
+
},
|
|
209
|
+
workspace_leave: {
|
|
210
|
+
schema: z
|
|
211
|
+
.object({ ...request, expectedMembershipId: uuid, reason: z.string() })
|
|
212
|
+
.strict(),
|
|
213
|
+
method: "POST",
|
|
214
|
+
path: (a) => root(a) + "/members/leave",
|
|
215
|
+
body: (a) => omit(a, ["workspaceId"]),
|
|
216
|
+
scoped: true,
|
|
217
|
+
},
|
|
218
|
+
};
|