@audienti/cli 0.1.145 → 0.2.0-exora.2

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.
Files changed (66) hide show
  1. package/README.md +47 -895
  2. package/bin/audienti-mcp.mjs +2 -0
  3. package/cli/account-intake-operations.mjs +28 -0
  4. package/cli/account-intake.mjs +33 -0
  5. package/cli/account-replies.mjs +35 -0
  6. package/cli/account-reply-operations.mjs +30 -0
  7. package/cli/agents.mjs +107 -0
  8. package/cli/approach-operations.mjs +22 -0
  9. package/cli/assignment-operations.mjs +218 -0
  10. package/cli/assignment.mjs +116 -0
  11. package/cli/audience-operations.mjs +16 -0
  12. package/cli/audiences.mjs +26 -0
  13. package/cli/audienti.mjs +124 -0
  14. package/cli/connection-registry-operations.mjs +7 -0
  15. package/cli/content.mjs +29 -0
  16. package/cli/discovery-operations.mjs +27 -0
  17. package/cli/discovery.mjs +14 -0
  18. package/cli/experiment-operations.mjs +25 -0
  19. package/cli/experiments.mjs +26 -0
  20. package/cli/json-file.mjs +13 -0
  21. package/cli/mcp.mjs +47 -0
  22. package/cli/offer-operations.mjs +12 -0
  23. package/cli/offers.mjs +24 -0
  24. package/cli/owner-account-client.mjs +18 -0
  25. package/cli/profiles.mjs +29 -0
  26. package/cli/progress.mjs +12 -0
  27. package/cli/prospecting-settings-operations.mjs +16 -0
  28. package/cli/prospecting-settings.mjs +15 -0
  29. package/cli/recipient-operations.mjs +13 -0
  30. package/cli/recipients.mjs +30 -0
  31. package/cli/sender-account-operations.mjs +24 -0
  32. package/cli/sender-accounts.mjs +23 -0
  33. package/cli/skills-mcp.mjs +24 -0
  34. package/cli/skills.mjs +56 -0
  35. package/cli/suppression-operations.mjs +8 -0
  36. package/cli/suppression.mjs +12 -0
  37. package/cli/tools-operations.mjs +70 -0
  38. package/cli/tools.mjs +72 -0
  39. package/cli/work-execution-operations.mjs +35 -0
  40. package/cli/work-execution.mjs +29 -0
  41. package/cli/work-feedback-operations.mjs +17 -0
  42. package/cli/work-feedback.mjs +18 -0
  43. package/cli/work-operations.mjs +28 -0
  44. package/cli/work.mjs +27 -0
  45. package/cli/workspaces.mjs +158 -0
  46. package/package.json +15 -26
  47. package/skills/audienti/SKILL.md +61 -323
  48. package/skills/audienti/references/helpers.md +149 -0
  49. package/skills/audienti/references/profile-workflows.md +212 -0
  50. package/skills/audienti/references/workflow-receipt.md +49 -0
  51. package/skills/audienti/scripts/contracts.mjs +72 -0
  52. package/skills/audienti/scripts/profile-helpers.mjs +138 -0
  53. package/skills/audienti/scripts/run.mjs +40 -0
  54. package/source-manifest.json +60 -0
  55. package/.mcp.json +0 -13
  56. package/CHANGELOG.md +0 -1018
  57. package/bin/audienti-mcp.js +0 -5
  58. package/bin/audienti.js +0 -6
  59. package/install +0 -83
  60. package/skills/audienti/references/methodology.md +0 -111
  61. package/skills/audienti-gift-research/SKILL.md +0 -47
  62. package/src/agent-skills.js +0 -60
  63. package/src/api-client.js +0 -1639
  64. package/src/cli.js +0 -16992
  65. package/src/config.js +0 -130
  66. package/src/mcp.js +0 -103
@@ -0,0 +1,158 @@
1
+ import {approachOperations} from './approach-operations.mjs';
2
+ import {connectionRegistryOperations} from './connection-registry-operations.mjs';
3
+ import { hostedToolOperations, paidToolByName, isPaidToolOperation, paidToolResult } from './tools-operations.mjs';
4
+ import { workspaceExecutionOperations } from './work-execution-operations.mjs';
5
+ import { workFeedbackOperations } from './work-feedback-operations.mjs';
6
+ import { accountReplyOperations } from './account-reply-operations.mjs';
7
+ import { accountIntakeOperations } from './account-intake-operations.mjs';
8
+ import { recipientOperations } from './recipient-operations.mjs';
9
+ import { suppressionOperations } from './suppression-operations.mjs';
10
+ import { workOperations } from './work-operations.mjs';
11
+ import { assignmentOperations } from './assignment-operations.mjs';
12
+ import {discoveryOperations} from './discovery-operations.mjs';
13
+ import {experimentOperations} from './experiment-operations.mjs';
14
+ import {audienceOperations} from './audience-operations.mjs';
15
+ import { readFileSync, writeFileSync, renameSync, mkdirSync, statSync, lstatSync, realpathSync } from 'node:fs';
16
+ import { dirname, resolve, relative, isAbsolute } from 'node:path';
17
+ import { fileURLToPath } from 'node:url';
18
+ import { z } from 'zod';
19
+ import {offerOperations} from './offer-operations.mjs';
20
+ const uuid = z.string().uuid();
21
+ const workspace = { workspaceId: uuid.optional() };
22
+ export const operations = {
23
+ ...approachOperations,
24
+ ...connectionRegistryOperations,
25
+ ...hostedToolOperations,
26
+ ...workOperations,
27
+ ...workspaceExecutionOperations,
28
+ ...workFeedbackOperations,
29
+ ...suppressionOperations,
30
+ ...recipientOperations,
31
+ ...accountIntakeOperations,
32
+ ...accountReplyOperations,
33
+ ...assignmentOperations,
34
+ ...discoveryOperations,
35
+ ...offerOperations,
36
+ ...audienceOperations,
37
+ ...experimentOperations,
38
+ workspace_sending_settings_get: {schema:z.object({...workspace,userId:uuid.optional()}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/sending-settings${a.userId?'?userId='+encodeURIComponent(a.userId):''}`,scoped:true,description:'Read workspace sending defaults, or own member effective hours, ramp and activity limits with userId. A workspace Admin may read another member. External activities remain agent-managed; these values do not authorize sending.'},
39
+ workspace_member: {schema:z.object({...workspace,userId:uuid}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/members/${a.userId}`,scoped:true},
40
+ workspace_agent_access: { schema:z.object(workspace).strict(), method:'GET', path:a=>`/workspaces/${a.workspaceId}/agent-access`, scoped:true },
41
+ workspace_agent_access_set: { schema:z.object({...workspace,userId:uuid,blocked:z.boolean()}).strict(), method:'POST', path:a=>`/workspaces/${a.workspaceId}/members/${a.userId}/agent-access`, body:a=>({blocked:a.blocked}), scoped:true },
42
+ workspace_initialize: { schema: z.object({}).strict(), method: 'POST', path: () => '/workspaces/bootstrap' },
43
+ workspace_list: { schema: z.object({}).strict(), method: 'GET', path: () => '/workspaces' },
44
+ workspace_create: { schema: z.object({ name: z.string().trim().min(1).max(80), requestId: uuid }).strict(), method: 'POST', path: () => '/workspaces', body: a => a },
45
+ workspace_get: { schema: z.object(workspace).strict(), method: 'GET', path: a => `/workspaces/${a.workspaceId}`, scoped: true },
46
+ workspace_invite: { schema: z.object({ ...workspace, email: z.string().trim().email() }).strict(), method: 'POST', path: a => `/workspaces/${a.workspaceId}/invitations`, body: a => ({ email: a.email }), scoped: true },
47
+ workspace_invitation_retry: { schema: z.object({ ...workspace, id: uuid }).strict(), method: 'POST', path: a => `/workspaces/${a.workspaceId}/invitations/${a.id}/retry`, scoped: true },
48
+ workspace_invitation_cancel: { schema: z.object({ ...workspace, id: uuid }).strict(), method: 'POST', path: a => `/workspaces/${a.workspaceId}/invitations/${a.id}/cancel`, scoped: true },
49
+ workspace_invitations: { schema: z.object({}).strict(), method: 'GET', path: () => '/me/invitations' },
50
+ workspace_invitation_get: { schema: z.object({ id: uuid, generation: z.number().int().positive() }).strict(), method: 'GET', path: a => `/invitations/${a.id}?generation=${a.generation}` },
51
+ workspace_invitation_accept: { schema: z.object({ id: uuid, generation: z.number().int().positive() }).strict(), method: 'POST', path: a => `/invitations/${a.id}/accept`, body: a => ({ generation: a.generation }) },
52
+ workspace_select: { schema: z.object({ workspaceId: uuid }).strict(), method: 'GET', path: a => `/workspaces/${a.workspaceId}` },
53
+ };
54
+ function profilePath(path) {
55
+ const target=resolve(path); const parent=dirname(target);
56
+ const repo=realpathSync(fileURLToPath(new URL('..',import.meta.url)));
57
+ const lexical=relative(repo,parent);
58
+ if(!lexical || (!lexical.startsWith('..') && !isAbsolute(lexical))) throw new Error('Store CLI profiles outside the repository.');
59
+ mkdirSync(parent,{recursive:true,mode:0o700});
60
+ const realParent=realpathSync(parent);
61
+ const rel=relative(repo,realParent);
62
+ if (!rel || (!rel.startsWith('..') && !isAbsolute(rel))) throw new Error('Store CLI profiles outside the repository.');
63
+ return resolve(realParent,target.slice(parent.length+1));
64
+ }
65
+ export function readProfile(path) {
66
+ if (!path) return {};
67
+ const target=profilePath(path);
68
+ try {
69
+ if(lstatSync(target).isSymbolicLink()) throw new Error("CLI profiles must not be symbolic links.");
70
+ const stat=statSync(target);
71
+ if ((stat.mode & 0o077) !== 0 || stat.uid !== process.getuid?.()) throw new Error('CLI profile must be owned by you and readable only by you (chmod 600).');
72
+ return JSON.parse(readFileSync(target,'utf8'));
73
+ } catch(error) {if(error.code==='ENOENT') return {};throw new Error('Could not read a private CLI profile. Check ownership, permissions and JSON format.');}
74
+ }
75
+ export function writeProfile(path, profile) {
76
+ if (!path) throw new Error('Supply --profile PATH outside the repository.');
77
+ const target=profilePath(path); const temporary=`${target}.${process.pid}.tmp`;
78
+ writeFileSync(temporary,JSON.stringify(profile)+'\n',{mode:0o600,flag:'wx'}); renameSync(temporary,target);
79
+ }
80
+ export function configuration(path, scope='workspace:access') {
81
+ const profile=readProfile(path);
82
+ const agentProfile = Boolean(profile.agent || profile.pendingAgent || profile.connectionSecret);
83
+ return { url:agentProfile ? profile.url : process.env.AUDIENTI_API_URL ?? profile.url, token:agentProfile ? (scope === 'workspace:access' ? profile.tokens?.['workspace:read'] : undefined) : process.env.AUDIENTI_API_TOKEN ?? (scope === 'workspace:access' ? profile.tokens?.['workspace:read'] ?? profile.tokens?.[scope] : profile.tokens?.[scope]), workspaceId:profile.workspaceId, profile };
84
+ }
85
+ export function validateUrl(value) {
86
+ let url;try {url=new URL(value);}catch{throw new Error('Set AUDIENTI_API_URL to an API URL ending with /api/v1.');}
87
+ if(url.username || url.password || url.search || url.hash || url.pathname.replace(/\/$/,'')!=='/api/v1') throw new Error('The API URL must end with /api/v1 and contain no credentials, query or fragment.');
88
+ if(url.protocol!=='https:' && !(url.protocol==='http:' && ['localhost','127.0.0.1','[::1]'].includes(url.hostname))) throw new Error('Use HTTPS for remote API hosts.');
89
+ return url;
90
+ }
91
+ export class WorkspaceRequestError extends Error {
92
+ constructor(status, code, message, retryAt, blockers, requestTraceId) { super(message); this.status = status; this.code = code; this.retryAt = retryAt; this.blockers = blockers; this.requestTraceId = requestTraceId; }
93
+ toJSON() { return { ...(this.requestId ? {requestId:this.requestId,recovery:this.recovery} : {}), ...(this.requestTraceId ? {requestTraceId:this.requestTraceId} : {}), code:this.code, status:this.status, message:this.message, ...(this.retryAt ? {retryAt:this.retryAt} : {}), ...(this.blockers ? {blockers:this.blockers} : {}) }; }
94
+ }
95
+ export class HostedResponseUnknown extends Error {
96
+ constructor(requestId, reason, requestTraceId) { super('The server response is unknown. Paid work may have started. Retry using the same requestId and principal; never generate a new ID to recover this call.'); this.requestId=requestId; this.reason=reason; this.requestTraceId=requestTraceId; }
97
+ toJSON() { return {...(this.requestTraceId?{requestTraceId:this.requestTraceId}:{}),code:'response_unknown',requestId:this.requestId,reason:this.reason,message:this.message,recovery:'retry_same_request_id'}; }
98
+ }
99
+ export async function callWorkspace(name, args, config) {
100
+ const operation=operations[name]; if(!operation) throw new Error('Unknown workspace operation.');
101
+ const parsed=operation.schema.safeParse(args);if(!parsed.success) throw new Error('Invalid workspace arguments. Check IDs and required fields.');
102
+ const input={...parsed.data};
103
+ if(operation.scoped) { input.workspaceId ??= config.workspaceId;if(!uuid.safeParse(input.workspaceId).success) throw new Error('Select a workspace or supply --workspace ID.'); }
104
+ const url=validateUrl(config.url); if(!config.token) throw new Error('A workspace:access token is required. Configure the environment or a private profile.');
105
+ const target=new URL(`/api/v1${operation.path(input)}`,url);
106
+ let response;try {response=await fetch(target,{method:operation.method,headers:{Authorization:`Bearer ${config.token}`,Accept:'application/json','Content-Type':'application/json',...((operation.traced||name.startsWith('tools_'))?{'X-Audienti-Interface':config.interface==='mcp'?'mcp':'cli'}:{})},...(['POST','PUT','PATCH'].includes(operation.method)?{body:JSON.stringify(operation.body?.(input)??{})}:{}),redirect:'error',signal:AbortSignal.timeout(operation.timeoutMs ?? 10000)});}catch(error){if(isPaidToolOperation(name))throw new HostedResponseUnknown(input.requestId,['TimeoutError','AbortError'].includes(error?.name)?'timeout':'transport_failure');throw new Error('Could not reach the API. Redirects are not followed.');}
107
+ const headerTrace=(operation.traced||name.startsWith('tools_'))&&uuid.safeParse(response.headers.get('x-request-trace-id')).success?response.headers.get('x-request-trace-id'):undefined;
108
+ let result;try{result=await response.json();}catch{if(isPaidToolOperation(name))throw new HostedResponseUnknown(input.requestId,'invalid_response',headerTrace);throw new Error('The API returned an invalid response.');}
109
+ const requestTraceId=uuid.safeParse(result?.requestTraceId).success?result.requestTraceId:headerTrace;
110
+ if(!response.ok) {
111
+ const code = typeof result?.error?.code==='string' && /^[a-z_]+$/.test(result.error.code) ? result.error.code : 'request_failed';
112
+ const retryAt = typeof result?.error?.retryAt === 'string' && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(result.error.retryAt) ? result.error.retryAt : undefined;
113
+ const blockers = code==='execution_blocked' && Array.isArray(result?.error?.blockers) && result.error.blockers.length<=28 && result.error.blockers.every(v=>typeof v==='string' && /^[a-z_]+$/.test(v)) ? result.error.blockers : undefined;
114
+ const error = new WorkspaceRequestError(response.status, code, `Workspace request failed: ${code} (HTTP ${response.status}).${retryAt ? ` Retry after ${retryAt}.` : ''}`, retryAt, blockers, requestTraceId);
115
+ if(isPaidToolOperation(name)){error.requestId=input.requestId;error.recovery=response.status>=500?'retry_same_request_id':'inspect_rejection';}
116
+ throw error;
117
+ }
118
+ if(!result || !Object.hasOwn(result,'data')) {if(isPaidToolOperation(name))throw new HostedResponseUnknown(input.requestId,'invalid_response',requestTraceId);throw new Error('The API returned an invalid response.');}
119
+ const paidResult = paidToolResult(name, result?.data);
120
+ if(isPaidToolOperation(name)&&(!uuid.safeParse(paidResult?.operationId).success||paidResult.requestId!==input.requestId||!['running','succeeded','failed','uncertain','rejected'].includes(paidResult.status)))throw new HostedResponseUnknown(input.requestId,'invalid_response',requestTraceId);
121
+ if(name==='workspace_select') config.workspaceId=input.workspaceId;
122
+ return {data:result.data,...(requestTraceId?{requestTraceId}:{})};
123
+ }
124
+ export async function workspaceCli(args) {
125
+ const flags={}; const position=[];
126
+ for(let i=0;i<args.length;i++) {
127
+ if(args[i]==='--json') continue;
128
+ if(args[i].startsWith('--')) {const key=args[i].slice(2);if(!['profile','workspace','name','request-id','email','generation','scope','user'].includes(key)||!args[i+1]||args[i+1].startsWith('--')) throw new Error('Unknown or incomplete option.');flags[key]=args[++i];}
129
+ else position.push(args[i]);
130
+ }
131
+ if(position[0]==='profile' && position[1]==='save') {
132
+ if(!['account:read','workspace:access','agents:manage'].includes(flags.scope)) throw new Error('Specify --scope account:read, workspace:access or agents:manage.');
133
+ const url=validateUrl(process.env.AUDIENTI_API_URL).toString().replace(/\/$/,'');const token=process.env.AUDIENTI_API_TOKEN;if(!token) throw new Error('Set AUDIENTI_API_TOKEN.');
134
+ const profile=readProfile(flags.profile);if(profile.agent || profile.pendingAgent || profile.connectionSecret) throw new Error('Use a separate user profile; agent profiles cannot store user API tokens.');writeProfile(flags.profile,{...profile,url,tokens:{...profile.tokens,[flags.scope]:token}});console.log('Profile saved.');return;
135
+ }
136
+ const config=configuration(flags.profile);const command=position.slice(1).join(' ');
137
+ let name, input={};
138
+ if(command==='sending-settings') {name='workspace_sending_settings_get';input={workspaceId:flags.workspace,...(flags.user?{userId:flags.user}:{})};}
139
+ else if(command==='agent-access') {name='workspace_agent_access';input={workspaceId:flags.workspace};}
140
+ else if(['agent-access block','agent-access restore'].includes(command)) {name='workspace_agent_access_set';input={workspaceId:flags.workspace,userId:flags.user,blocked:command.endsWith('block')};}
141
+ else if(command==='init') name='workspace_initialize';
142
+ else if(command==='list') name='workspace_list';
143
+ else if(command==='create') {name='workspace_create';input={name:flags.name,requestId:flags['request-id']};}
144
+ else if(command==='show') {name='workspace_get';input={workspaceId:flags.workspace};}
145
+ else if(command==='invite') {name='workspace_invite';input={workspaceId:flags.workspace,email:flags.email};}
146
+ else if(command==='invitations') name='workspace_invitations';
147
+ else if(position[1]==='member' && position.length===3) {name='workspace_member';input={workspaceId:flags.workspace,userId:position[2]};}
148
+ else if(position[1]==='use' && position.length===3) {name='workspace_select';input={workspaceId:position[2]};}
149
+ else if(position[1]==='invitation' && position.length===4) {
150
+ const action=position[2];name={show:'workspace_invitation_get',accept:'workspace_invitation_accept',retry:'workspace_invitation_retry',cancel:'workspace_invitation_cancel'}[action];
151
+ input={id:position[3],...(['retry','cancel'].includes(action)?{workspaceId:flags.workspace}:{generation:Number(flags.generation)})};
152
+ }
153
+ if(!name) throw new Error('Unknown workspace command. Use audienti --help.');
154
+ const result=await callWorkspace(name,input,config);
155
+
156
+ if(name==='workspace_select') writeProfile(flags.profile,{...config.profile,workspaceId:config.workspaceId});
157
+ console.log(JSON.stringify(result));
158
+ }
package/package.json CHANGED
@@ -1,46 +1,35 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.1.145",
4
- "description": "Agent-first command-line client for Audienti.",
3
+ "version": "0.2.0-exora.2",
4
+ "description": "Audienti CLI and MCP client for exo.audienti.com",
5
5
  "type": "module",
6
6
  "bin": {
7
- "audienti": "./bin/audienti.js",
8
- "audienti-mcp": "./bin/audienti-mcp.js"
7
+ "audienti": "cli/audienti.mjs",
8
+ "audienti-mcp": "bin/audienti-mcp.mjs"
9
9
  },
10
10
  "files": [
11
+ "cli/",
11
12
  "bin/",
12
- "src/",
13
13
  "skills/",
14
- ".mcp.json",
15
- "install",
16
14
  "README.md",
17
15
  "LICENSE",
18
- "CHANGELOG.md"
16
+ "source-manifest.json"
19
17
  ],
20
- "scripts": {
21
- "test": "node --test test/*.test.js",
22
- "verify": "node scripts/verify-package.mjs",
23
- "check": "npm run verify && npm test"
24
- },
25
18
  "engines": {
26
- "node": ">=20"
19
+ "node": ">=24 <25"
20
+ },
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "1.32.2",
23
+ "zod": "4.6.7"
27
24
  },
28
25
  "repository": {
29
26
  "type": "git",
30
- "url": "git+https://github.com/audienti/cli.git"
31
- },
32
- "homepage": "https://github.com/audienti/cli#readme",
33
- "bugs": {
34
- "url": "https://github.com/audienti/cli/issues"
27
+ "url": "git+https://github.com/audienti/cli.git",
28
+ "directory": "exora"
35
29
  },
36
30
  "license": "SEE LICENSE IN LICENSE",
37
- "keywords": [
38
- "audienti",
39
- "cli",
40
- "agents",
41
- "go-to-market"
42
- ],
43
31
  "publishConfig": {
44
- "access": "public"
32
+ "access": "public",
33
+ "tag": "exora"
45
34
  }
46
35
  }
@@ -1,337 +1,75 @@
1
1
  ---
2
2
  name: audienti
3
- description: Use when the user wants to operate Audienti through the production CLI or app-hosted MCP endpoint, including account selection, plays, prospect imports, lists and routing rules, message previews, analytics, Inbox Ops, Network Ops, or supported operator outcomes.
3
+ description: Find, enrich, and expand CRM profile research using Audienti's account-scoped records, explicit paid tools, and executable deterministic helpers. Use for saved Prospect/Profile lookup, known profile URLs, relevant posts and their participants, or discovering additional profiles for the same identity.
4
4
  ---
5
5
 
6
- # Audienti CLI and MCP
6
+ # Audienti profile research
7
7
 
8
- Use the installed `audienti` command or Audienti's app-hosted MCP endpoint as
9
- the production contract. The packaged `audienti-mcp` command is only a local
10
- stdio bridge for MCP hosts. Do not build a parallel wrapper or call
11
- undocumented API endpoints.
8
+ You own the research workflow. Audienti keeps canonical Prospect/Profile records,
9
+ citations, authorization and paid-tool accounting. A Prospect is a person or company;
10
+ it can have many source Profiles. Employment links a person and company through
11
+ citations; it does not make them one identity.
12
12
 
13
- Before creating, changing, or judging any experiment, read the complete
14
- [outbound methodology](references/methodology.md), also available through
15
- `audienti help methodology`. The customer owns the strategy. Record a written
16
- baseline, target and scheduled review date before starting; judge only matured
17
- cohorts. `experiments`, `experiment`, and `plays` are aliases for `motions`,
18
- including command help. CLI reminders are guidance; existing authorization and
19
- sending controls still apply.
13
+ First inspect the selected workspace, current permissions and available tools. Use
14
+ the account the user selected; never search another workspace or global cache as a
15
+ substitute. Discover current contracts with `audienti tools list`, `tools describe`
16
+ and resource help. Read [the workflow recipes](references/profile-workflows.md)
17
+ and [helper contracts and installation](references/helpers.md). Keep an
18
+ [agent research receipt](references/workflow-receipt.md) as you work.
20
19
 
21
- For website-only gift research, run `audienti tools gift-research --url <website>`
22
- and follow the returned skill using your own browsing tools. The command supplies
23
- instructions; it does not perform research or call the app. The bundled
24
- `audienti-gift-research` skill also works independently of this production
25
- workflow. Do not use offer scans or create gifts for this research exercise.
20
+ ## Executable helpers
26
21
 
27
- Use `audienti skills list` and `audienti skills show <name>` to discover bundled
28
- skills or the Audienti marketplace snapshot. Marketplace entries provide source
29
- links and install commands, not bundled upstream implementations. Read the
30
- upstream README and applicable skill before use; its dependencies and permissions
31
- still apply. Installation requires a separate instruction from the user.
22
+ The skill includes four deterministic helpers. They consume supplied JSON; none
23
+ performs research, calls a model, reads the CRM, writes records or charges credits.
32
24
 
33
- Before using a social-network finder, inspect the local environment for available
34
- browser/search tools, connected MCP tools or apps, installed skills and CLIs,
35
- and existing secure credential configuration without printing secrets. Read their
36
- documentation and permissions to choose supported read-only access to that network.
37
- Do not assume Apify, a browser session or credentials are available. If suitable
38
- access is missing, report the missing capability and ask the owner; do not invent
39
- findings, install tools or silently switch networks. Read the environment check
40
- returned by `audienti skills show <finder>` before following its upstream skill.
41
- The full returned check applies. It does not authorize granting access, sending
42
- messages or posting comments to test access.
43
-
44
- Set `approach` in a Motion create/update payload, or use `motions update <id> --approach "..."`. A nonblank Approach automatically guides adaptive planning and writing; blank, nil, or whitespace keeps the existing sequence. The simple `--approach ""` flag explicitly clears the guidance and restores that sequence. There is no separate planning-mode selector, and existing sending controls still apply.
45
-
46
- To clear an Inbox Ops backlog, run `inbox-ops queue` first: it follows every page, numbers each row, and saves the numbered list locally for the selected account. Then use `inbox-ops ignore`, `filter-domain`, `filter-sender`, `allow-domain`, or `allow-sender` with numbers, ranges (`1-25,30`), row ids, `--domain`, or `--sender`. Always run `--dry-run` first when acting for a person, read the per-row planned/skipped/rejected results back, and only then re-run with `--yes`. Numbers stay valid until `inbox-ops queue` runs again; ignore hides one thread until a new reply, while filter and allow rules are durable across all of the owner's accounts. Rule verbs given only `--domain` or `--sender` write the keyed rule directly. Through MCP use `inbox_ops.queue`, then `inbox_ops.actions` with `{operation, row_ids, dry_run}`.
47
-
48
- When Operator exposes `answer_planner_question`, read its prompt and candidate options with `operator queue` or `operator next`. Use `operator answer <row_id> --choice <id>` or `--answer "..."`, exactly one input. This command refetches the exact row and submits the current row and decision fingerprints. Do not substitute a generic outcome, a reusable steer note, or a guessed send. A 409 means refresh the question; 202 means the answer was recorded and planning continues. Answers apply only to that decision.
49
-
50
- ## Setup
51
-
52
- 1. Verify the command is installed:
53
-
54
- ```bash
55
- audienti --help
56
- ```
57
-
58
- 2. If it is unavailable, install the public package:
59
-
60
- ```bash
61
- curl -fsSL https://cli.audienti.com/install | bash
25
+ ```sh
26
+ audienti skills helper profile-queries --file context.json --json
27
+ audienti skills helper profile-candidates --file results.json --json
28
+ audienti skills helper profile-completeness --file field-states.json --json
29
+ audienti skills helper profile-evidence --file supplied-text.json --json
62
30
  ```
63
31
 
64
- 3. Authentication is explicit and per machine. Do not ask a user to paste a
65
- production token into chat, a repository file, an issue, or a CI secret. For a
66
- person at a terminal, the friendly first run is:
32
+ Without the CLI, run the same bundled code with Node:
67
33
 
68
- ```bash
69
- audienti start
70
- audienti setup
34
+ ```sh
35
+ node scripts/run.mjs profile-queries --file context.json
71
36
  ```
72
37
 
73
- `audienti start` signs in through the browser (or creates an account), picks
74
- the account, hands an unpaid account to the web pay page (or takes a signup
75
- code), and runs setup in the terminal. `audienti start --json` never prompts; it
76
- reports `sign_in_required`, `payment_required` (with `payment_url`) or `ready`.
77
- Never pay or enter a signup code for the user. LinkedIn connect is finished on
78
- the web setup page (`/quick_start/go_live`); that page acts on the browser's current
79
- account, so tell the user to switch the browser to the CLI account first. Browser login alone is:
80
-
81
- ```bash
82
- audienti auth login
83
- ```
84
-
85
- `audienti setup --url <company_url> --sell-to "<who they sell to>" --ask "<what
86
- prospects say yes to>" --json` drafts a first experiment without creating it.
87
- Add `--city`, `--state` and `--country` when setup says it needs the place. Show
88
- the draft to the user and add `--yes` only after they approve creating it.
89
-
90
- Use `audienti auth token` only after the user supplies a token through an
91
- approved secure channel.
92
-
93
- 4. Start with discovery, not mutation:
94
-
95
- ```bash
96
- audienti auth status
97
- audienti accounts list --json
98
- audienti help agent-workflows
99
- ```
100
-
101
- For MCP hosts, configure the packaged `.mcp.json` or run:
102
-
103
- ```bash
104
- audienti-mcp
105
- ```
106
-
107
- The bridge reads the same local config as the CLI and forwards MCP JSON-RPC to
108
- the app-hosted `/mcp` endpoint. Before starting the bridge, select account
109
- context:
110
-
111
- ```bash
112
- audienti auth login
113
- audienti accounts list --json
114
- audienti accounts create --name Doppel --parent OMALab --json
115
- audienti accounts select <acct_id>
116
- audienti users select me
117
- ```
118
-
119
- If the MCP host can call HTTP directly, use `POST /mcp` with the same bearer
120
- token as the API. Call `tools/list` first, then call named tools with
121
- `tools/call`. Account-scoped tools accept `account_id`; the local bridge injects
122
- the selected CLI account when it is omitted.
123
-
124
- Useful MCP tools:
125
-
126
- - `auth.me` confirms the token identity.
127
- - `accounts.list` shows accessible accounts.
128
- - `setup.play_preflight` returns connected-account readiness and setup or
129
- mapping URLs, including the selected sender's active days, working hours,
130
- effective timezone, current in-window status, configured user fallback
131
- location, effective proxy geography and source, and server-calculated
132
- LinkedIn pacing and outstanding-invitation inventory authority under
133
- `social_cookie.automation`. Treat `ready: true` as connection readiness, not
134
- approval to start automation.
135
- - `offers.create`, `icps.create`, and `motions.create` set up the offer, ICP,
136
- and play.
137
- - Use `audienti icps list --status all` to audit active and archived ICPs. Use
138
- `audienti icps archive <icp_id>` for reversible cleanup and `restore` to make
139
- the ICP selectable again; restore never reactivates motions.
140
- - Use `audienti motions abm-companies <motn_id> add --file <txt|json>` to attach
141
- a positive company filter list for isolated ABM discovery runs.
142
- - `analytics.stages` returns stage conversion cohorts and stage aging.
143
- - `analytics.cohort_lists.create` materializes event cohorts as reusable lists.
144
-
145
- ## Operating Rules
146
-
147
- - Use `--json` whenever another agent or tool will consume the response.
148
- - Use `audienti <resource> <action> help` before a mutation when the accepted
149
- payload or behavior is unclear.
150
- - Inspect the current resource before a create, update, attach, delete, or
151
- operator outcome writeback.
152
- - For account-user automation, run `users automation show`, then preview the
153
- exact payload without `--apply`. Apply only after explicit authorization and
154
- verify the applied response/readback.
155
- - Treat the production API as the source of truth. Persist durable work in
156
- Audienti rather than leaving it only in agent prose.
157
- - Keep current gaps explicit. Do not imply that unsupported actions execute.
158
-
159
- ## Common Entry Points
160
-
161
- ```bash
162
- audienti help agent-workflows
163
- audienti prospects list --query "name or company" --wide --json
164
- audienti prospects list --assigned-user unassigned --json
165
- audienti prospects assign <prsp_id> --assigned-user me --json
166
- audienti prospects move-account <prsp_id> --account <source_acct_id> --target-account <target_acct_id> --json
167
- audienti prospects move-account <prsp_id> --account <source_acct_id> --target-account <target_acct_id> --assigned-user me --apply --json
168
- audienti prospects set-status <prsp_id> --status not_fit --json
169
- audienti prospects lock <prsp_id> --note "Emergency hold" --json
170
- audienti prospects unlock <prsp_id> --json
171
- audienti users activity me --window 7d --json
172
- audienti users automation show <account_user_id|me> --platform linkedin --json --account <acct_id>
173
- audienti users automation update <account_user_id|me> --payload automation-controls.json --json --account <acct_id>
174
- audienti users automation update <account_user_id|me> --payload automation-controls.json --apply --json --account <acct_id>
175
- audienti social-cookies sync-messages <scok_id> [--folder <folder>] [--retry] --json
176
- audienti prospects import-batch --file prospects.csv --motion <motn_id> --assigned-user me --json
177
- audienti lists create --name "Target list" --json
178
- audienti lists routing-rules <list_id> list --json
179
- audienti lists routing-rules <list_id> create --payload routing-rule.json --json
180
- audienti lists routing-rules <list_id> move <rule_id> up --json
181
- audienti lists routing-rules <list_id> apply --json
182
- audienti motions quick-start --url https://example.com --wait --confirm --json
183
- audienti motions abm-companies <motn_id> add --file abm-domains.txt --json
184
- audienti motions abm-companies <motn_id> list --json
185
- audienti motions update <motn_id> --status paused --json
186
- audienti motions update <motn_id> --start-date 2026-09-01 --end-date 2026-09-30 --maximum-company-count 25 --json
187
- audienti motions update <motn_id> --payload motion-principal-list.json --json
188
- audienti motions activate <motn_id> --json
189
- audienti motions delete <motn_id> --confirm yes --json
190
- audienti operator next --json
191
- audienti operator next --plan
192
- audienti network-ops queue --json
193
- audienti network-ops accept <row_id> --account <acct_id> --json
194
- audienti network-ops decline <row_id> --account <acct_id> --json
195
- audienti inbox-ops queue --json
196
- audienti inbox-ops queue --group-by domain
197
- audienti inbox-ops ignore 1-25 --dry-run --json
198
- audienti inbox-ops ignore 1-25 --yes --json
199
- audienti inbox-ops filter-domain --domain alerts.example.com --yes --json
200
- audienti inbox-ops filter-sender 3,7 --yes --json
201
- audienti inbox-ops filters --json
202
- audienti inbox-ops rule <row_id> --scope <sender|domain> --disposition <allow|filter> --json
203
- audienti analytics motions --json
204
- audienti analytics icps --json
205
- audienti analytics prospects --window 24h --json
206
- audienti analytics stages --window 30d
207
- audienti analytics visibility --window 24h --user me --json
208
- audienti analytics content --window week --json
209
- audienti tools list --json
210
- audienti tools humanize --file draft.txt --tone professional --json
211
- audienti tools email-find --first-name Ada --last-name Lovelace --company-domain example.com --wait --json
212
- audienti tools email-find --linkedin-url https://www.linkedin.com/in/example --wait --json
213
- audienti tools linkedin-enrich --url https://www.linkedin.com/in/example --wait --json
214
- audienti tools signals-find --icp "<ICP description>" --question "<signal question>" --wait --json
215
- audienti tools write --purpose "<goal>" --audience "<who>" --fact "<fact>" --channel email --wait --json
216
- audienti tools runs show <trun_id> --json
217
- audienti tools runs results <trun_id> --json
218
- audienti network list --cookie <scok_id> --kind connection --json
219
- audienti tools linkedin-review --url https://www.linkedin.com/in/example --icp <icp_id> --json
220
- audienti tools linkedin-review reports --json
221
- audienti tools linkedin-review show <rprt_id> --json
222
- audienti tools linkedin-review status <rprt_id> --json
223
- audienti writer test-run <prsp_id>
224
- audienti writer test-run <prsp_id> --mode report --report <rprt_id>
225
- audienti writer test-run <prsp_id> --mode step --branch no-accept --step 3 --report <rprt_id>
226
- audienti writer test-run <prsp_id> --mode step --branch no-accept --step 3 --report <rprt_id> --no-wait
227
- audienti writer test-run show <prsp_id> <rprt_id>
228
- ```
229
-
230
- ## Prospect Account Moves
231
-
232
- Use `audienti prospects move-account` only to transfer one prospect and its
233
- account-scoped history, not to copy it. `--account` is the source account and
234
- `--target-account` is the destination. The authenticated user must administer
235
- both accounts.
236
-
237
- Start with a preview and inspect `eligible`, `blockers`, `mappings`,
238
- `dispositions`, and `expected_state`:
239
-
240
- ```bash
241
- audienti prospects move-account <prsp_id> \
242
- --account <source_acct_id> \
243
- --target-account <target_acct_id> \
244
- --assigned-user <target_account_user_id|me> \
245
- --target-motion <target_motn_id> \
246
- --target-list <target_list_id> \
247
- --json
248
- ```
249
-
250
- Omit mappings that were not explicitly chosen; never infer a target motion,
251
- list, agent, or research mapping by name. Apply only after the preview is
252
- eligible and the user authorizes the move:
253
-
254
- ```bash
255
- audienti prospects move-account <prsp_id> \
256
- --account <source_acct_id> \
257
- --target-account <target_acct_id> \
258
- --assigned-user <target_account_user_id|me> \
259
- --target-motion <target_motn_id> \
260
- --target-list <target_list_id> \
261
- --apply \
262
- --json
263
- ```
264
-
265
- With `--apply`, the CLI performs a fresh preview and submits that response's
266
- `manifest_digest`; it does not reuse an older preview. Treat a stale manifest,
267
- any blocker, or any failed response as not applied, then inspect current source
268
- and target state before retrying.
269
-
270
- ## Account-User Automation Safety
271
-
272
- `audienti users automation show <account_user_id|me>` resolves one connected
273
- account through the selected account, account user, and LinkedIn platform. Use
274
- per-command `--account` for administrative inspection without changing the
275
- saved default. Fail closed on API ambiguity or missing access; do not substitute
276
- another connected account.
277
-
278
- Automation updates are preview-first:
279
-
280
- ```bash
281
- audienti users automation show 136 --platform linkedin --json --account acct_example
282
- audienti users automation update 136 --payload automation-controls.json --json --account acct_example
283
- audienti users automation update 136 --payload automation-controls.json --apply --json --account acct_example
284
- audienti users automation show 136 --platform linkedin --json --account acct_example
285
- ```
286
-
287
- The payload object accepts `automation_controls`, `action_limits`, and
288
- `visibility_ramp`. Action-limit keys are `profile_view`, `follow`, `like`,
289
- `invite`, `message`, `comment`, and aggregate `visibility`; each action limit
290
- may provide `hourly`, `daily`, and `weekly`. `follow` covers follow/unfollow,
291
- `like` covers like/unlike, and `message` covers messages/InMail. A ramp accepts
292
- `enabled`, `starting_daily_limit`, `weekly_increment`, and optional
293
- `started_at`.
294
-
295
- The CLI wraps the file as `{ automation: { ...payload, platform: "linkedin",
296
- apply: false|true } }`; file values cannot override the command's platform or
297
- apply decision. Omitted settings are preserved by the server. Use unchanged
298
- JSON output to compare current, before, and after snapshots, including policy
299
- source, schedule/timezone, configured/default/effective limits, used and
300
- remaining capacity, binding limits, preview/applied state, and applied audit id.
301
-
302
- For a new account, keep contact and writing controls disabled, enable risk
303
- cooldown, and bound `visibility` to the authorized active-day warm-up level.
304
- Never infer that `ready: true`, paused motions, or prepared queue work authorizes
305
- provider execution.
306
-
307
- List routing-rule create and update commands accept the same normalized
308
- condition and action data as the list UI. Location and company_location labels
309
- must resolve to a known geography or the request returns 422. Inspect the
310
- ordered rules first, use `audienti lists routing-rules help` for the payload
311
- contract, and treat `apply` as an asynchronous relaunch: it queues the existing
312
- routing job and does not mean every list member has finished routing.
313
-
314
- Motion start and end dates are optional lifecycle controls. A due start can
315
- activate a configured preparing or paused motion; an end date is the final
316
- active day and pauses the motion afterward. `--maximum-company-count` caps
317
- durable company admissions only: research and people discovery for admitted
318
- companies, prospect intake, and automation continue. Use `none` to clear any
319
- of these settings.
320
-
321
- `audienti writer test-run <prsp_id>` starts a report-backed writing session and
322
- builds the campaign timeline without drafting every message. The printed
323
- `Report: rprt_...` id is the session handle. Pass `--report <rprt_id>` when
324
- drafting every message with `--mode report` or one selected row with
325
- `--mode step`; the server report supplies prior drafted rows as context. Use
326
- `--timeout-seconds <n>` for longer waits, or `--no-wait` to launch and return
327
- immediately. Use `writer test-run show` with the report id to fetch the
328
- completed report later.
329
-
330
- ## Standalone Tools Versus Prospect Tools
331
-
332
- - For an email lookup with no prospect, use `audienti tools email-find`. `audienti tools get email` imports a prospect into the account; use it only when that is wanted.
333
- - `tools email-find`, `linkedin-enrich`, `signals-find` and `write` return a run id immediately. Pass `--wait --json` to get results in one call, or poll `tools runs show <trun_id>` and read `tools runs results <trun_id>`. Nothing is sent and no prospects or experiments are created.
334
- - Pass `--request-key <key>` when retrying a submission; the same key and input return the same run.
335
- - `audienti network list|export` reads only the requester's own connected account's saved network. A 404 means the requester is not that account's owner. Coverage `unsupported_capture` or `not_yet_observed` is not zero.
336
-
337
- Create a child workspace with `audienti accounts create --name <name> --parent <name-or-id> [--json]`. The parent can be a visible name, unique name fragment, numeric ID, or prefixed ID. You must administer a root team account. All parent admins receive access to the child; ordinary members do not. Ambiguous names show matching IDs and stop. A duplicate child name under the same parent fails clearly. The response includes the new account ID. Creation keeps your selected account unchanged: use `audienti accounts select <new_acct_id>` or `--account <new_acct_id>` before creating a motion there.
38
+ Use `audienti skills show audienti --json` for the exact version, digest and JSON
39
+ schemas. The local MCP tools expose the same helpers. An HTTP-only agent without
40
+ local execution cannot run these helpers: report that capability gap rather than
41
+ claiming to have executed them. Compatible research tools already available to you
42
+ can supply inputs; using Audienti's paid tools is optional.
43
+
44
+ ## Preserve the boundary
45
+
46
+ - Search saved Prospects and Profiles before purchasing fresh data.
47
+ `profiles.enrich` accepts a known LinkedIn URL or authorized saved Profile ID,
48
+ saves the result and returns its current canonical Prospect and profile page.
49
+ Complete eligible cached data is reused; request `refresh` only deliberately.
50
+ - Execute each search, retrieval, contact lookup or optional AI decision explicitly.
51
+ Helpers return inputs for you to use; they never continue a research chain.
52
+ - A candidate rank is not an identity match, qualification, contact permission or
53
+ assurance that a source is trustworthy. `trusted_source` is a legacy V10 ranking
54
+ feature only. Submit attributed facts and citations to the CRM's shared owners.
55
+ - Reuse the returned Prospect/Profile IDs. Do not manufacture merge authority,
56
+ provider verification, or image-match evidence. The CRM owns final identity
57
+ reconciliation. If the installed API lacks the required guarded operation, keep
58
+ the evidence and report the missing capability; do not set foreign keys yourself.
59
+ - Recover an unknown paid outcome using the existing request/operation ID and run
60
+ history before retrying. A new request ID is new work, not a recovery mechanism.
61
+ Do not bypass a zero-credit refusal with another alias or credential.
62
+ - Persist requested relevant evidence even when a candidate fails an experiment's
63
+ fit criteria. Separate source facts from your interpretation and cite both.
64
+ Include this skill's identity/version on supported evidence and assessment writes.
65
+ - Profile research does not authorize a message, follow, contact purchase or change
66
+ to an approval. Use the customer's instructions and current server permissions.
67
+
68
+ Return canonical records read back from the selected workspace, evidence collected,
69
+ profiles matched or still unresolved, operation IDs for interrupted work, and the
70
+ specific remaining evidence/capability gap. Do not claim a complete enrichment or
71
+ expansion when only search results or advisory rankings are available.
72
+
73
+ For field coverage use `profile-completeness` with supplied field states. For observed
74
+ bio/website links and mentions use `profile-evidence`. Both run locally; neither
75
+ chooses a paid call or asserts an identity. See [helper contracts](references/helpers.md).