@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.
Files changed (110) 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 +55 -0
  7. package/cli/agents.mjs +160 -0
  8. package/cli/assignment-operations.mjs +218 -0
  9. package/cli/assignment.mjs +116 -0
  10. package/cli/attachment-operations.mjs +9 -0
  11. package/cli/attachments.mjs +28 -0
  12. package/cli/audience-operations.mjs +21 -0
  13. package/cli/audiences.mjs +29 -0
  14. package/cli/audienti.mjs +217 -0
  15. package/cli/audit-operations.mjs +17 -0
  16. package/cli/audit.mjs +39 -0
  17. package/cli/budget-operations.mjs +8 -0
  18. package/cli/budgets.mjs +34 -0
  19. package/cli/campaign-find.mjs +155 -0
  20. package/cli/campaign-operations.mjs +30 -0
  21. package/cli/campaigns.mjs +61 -0
  22. package/cli/cohort-operations.mjs +22 -0
  23. package/cli/cohorts.mjs +45 -0
  24. package/cli/connection-registry-operations.mjs +8 -0
  25. package/cli/content.mjs +29 -0
  26. package/cli/discovery-operations.mjs +28 -0
  27. package/cli/discovery.mjs +15 -0
  28. package/cli/file-operations.mjs +29 -0
  29. package/cli/files.mjs +14 -0
  30. package/cli/guardrails-operations.mjs +12 -0
  31. package/cli/guardrails.mjs +37 -0
  32. package/cli/json-file.mjs +13 -0
  33. package/cli/kv-operations.mjs +17 -0
  34. package/cli/kv.mjs +58 -0
  35. package/cli/mcp.mjs +47 -0
  36. package/cli/notification-operations.mjs +8 -0
  37. package/cli/offer-operations.mjs +12 -0
  38. package/cli/offers.mjs +24 -0
  39. package/cli/outbound-operations.mjs +16 -0
  40. package/cli/outbound.mjs +54 -0
  41. package/cli/outcome-operations.mjs +26 -0
  42. package/cli/outcomes.mjs +16 -0
  43. package/cli/owner-account-client.mjs +18 -0
  44. package/cli/playbook-help.mjs +79 -0
  45. package/cli/playbook-operations.mjs +36 -0
  46. package/cli/profiles.mjs +29 -0
  47. package/cli/progress.mjs +26 -0
  48. package/cli/prospecting-settings-operations.mjs +16 -0
  49. package/cli/prospecting-settings.mjs +15 -0
  50. package/cli/question-operations.mjs +12 -0
  51. package/cli/questions.mjs +32 -0
  52. package/cli/queue-operations.mjs +12 -0
  53. package/cli/queue.mjs +25 -0
  54. package/cli/recipient-operations.mjs +13 -0
  55. package/cli/recipients.mjs +34 -0
  56. package/cli/rooms-operations.mjs +48 -0
  57. package/cli/rooms.mjs +160 -0
  58. package/cli/search-operations.mjs +9 -0
  59. package/cli/secret-operations.mjs +7 -0
  60. package/cli/secrets.mjs +30 -0
  61. package/cli/sender-account-operations.mjs +28 -0
  62. package/cli/sender-accounts.mjs +33 -0
  63. package/cli/skills-mcp.mjs +31 -0
  64. package/cli/skills.mjs +113 -0
  65. package/cli/suppression-operations.mjs +8 -0
  66. package/cli/suppression.mjs +12 -0
  67. package/cli/tools-operations.mjs +78 -0
  68. package/cli/tools.mjs +100 -0
  69. package/cli/usage-operations.mjs +8 -0
  70. package/cli/usage.mjs +41 -0
  71. package/cli/work-execution-operations.mjs +42 -0
  72. package/cli/work-execution.mjs +29 -0
  73. package/cli/work-feedback-operations.mjs +17 -0
  74. package/cli/work-feedback.mjs +18 -0
  75. package/cli/work-operations.mjs +30 -0
  76. package/cli/work.mjs +32 -0
  77. package/cli/workspaces.mjs +255 -0
  78. package/package.json +15 -26
  79. package/skills/agent-rooms/SKILL.md +140 -0
  80. package/skills/agent-rooms/references/commands.md +78 -0
  81. package/skills/audienti/SKILL.md +161 -317
  82. package/skills/audienti/references/helpers.md +162 -0
  83. package/skills/audienti/references/profile-workflows.md +214 -0
  84. package/skills/audienti/references/workflow-receipt.md +49 -0
  85. package/skills/audienti/scripts/contracts.mjs +72 -0
  86. package/skills/audienti/scripts/profile-helpers.mjs +138 -0
  87. package/skills/audienti/scripts/run.mjs +40 -0
  88. package/skills/getting-started/SKILL.md +227 -0
  89. package/skills/getting-started/references/audience.md +143 -0
  90. package/skills/getting-started/references/campaign-draft.md +80 -0
  91. package/skills/getting-started/references/capabilities.md +237 -0
  92. package/skills/getting-started/references/mailbox-check.md +64 -0
  93. package/skills/getting-started/references/outcome-checks.md +114 -0
  94. package/skills/getting-started/references/replies.md +186 -0
  95. package/skills/getting-started/references/reply-checks.md +54 -0
  96. package/skills/playbook-design/SKILL.md +136 -0
  97. package/skills/playbook-design/references/fields.md +130 -0
  98. package/source-manifest.json +104 -0
  99. package/.mcp.json +0 -13
  100. package/CHANGELOG.md +0 -1018
  101. package/bin/audienti-mcp.js +0 -5
  102. package/bin/audienti.js +0 -6
  103. package/install +0 -83
  104. package/skills/audienti/references/methodology.md +0 -111
  105. package/skills/audienti-gift-research/SKILL.md +0 -47
  106. package/src/agent-skills.js +0 -60
  107. package/src/api-client.js +0 -1639
  108. package/src/cli.js +0 -16992
  109. package/src/config.js +0 -130
  110. package/src/mcp.js +0 -103
@@ -0,0 +1,116 @@
1
+ import { callWorkspace, configuration } from "./workspaces.mjs";
2
+ import { readJsonFile } from "./json-file.mjs";
3
+ export function isAssignmentCommand(args) {
4
+ return (
5
+ args[0] === "workspace" &&
6
+ (["assignments", "prospect", "routing-rules", "routing", "leave"].includes(
7
+ args[1],
8
+ ) ||
9
+ (args[1] === "member" && args[2] === "remove"))
10
+ );
11
+ }
12
+ export async function assignmentCli(args) {
13
+ const flags = {},
14
+ position = [];
15
+ const children = [
16
+ "history",
17
+ "participations",
18
+ "participation-events",
19
+ "routing-reports",
20
+ "overrides",
21
+ ];
22
+ const allowed = [
23
+ "profile",
24
+ "workspace",
25
+ "file",
26
+ "request-id",
27
+ "prospect-id",
28
+ "campaign-id",
29
+ "assigned-to-user-id",
30
+ "unassigned",
31
+ "search",
32
+ "cursor",
33
+ "limit",
34
+ ...children.flatMap((k) => [k + "-cursor", k + "-limit"]),
35
+ ];
36
+ for (let i = 0; i < args.length; i++) {
37
+ if (args[i] === "--json") continue;
38
+ if (!args[i].startsWith("--")) {
39
+ position.push(args[i]);
40
+ continue;
41
+ }
42
+ const key = args[i].slice(2);
43
+ if (
44
+ !allowed.includes(key) ||
45
+ args[i + 1] === undefined ||
46
+ args[i + 1].startsWith("--") ||
47
+ Object.hasOwn(flags, key)
48
+ )
49
+ throw new Error("Unknown, repeated, or incomplete assignment option.");
50
+ flags[key] = args[++i];
51
+ }
52
+ const command = position.join(" ");
53
+ const names = {
54
+ "workspace assignments list": "workspace_prospect_assignments",
55
+ "workspace prospect assignment get": "workspace_prospect_assignment",
56
+ "workspace prospect assignment set": "workspace_prospect_assignment_set",
57
+ "workspace prospect campaign-fit get":
58
+ "workspace_prospect_campaign_fit",
59
+ "workspace routing-rules get": "workspace_routing_rules_get",
60
+ "workspace routing-rules replace": "workspace_routing_rules_replace",
61
+ "workspace routing apply": "workspace_routing_apply",
62
+ "workspace prospect fit-override set": "workspace_fit_override_set",
63
+ "workspace prospect fit-override clear": "workspace_fit_override_set",
64
+ "workspace prospect move": "workspace_prospect_move",
65
+ "workspace member remove": "workspace_member_remove",
66
+ "workspace leave": "workspace_leave",
67
+ };
68
+ const name = names[command];
69
+ if (!name)
70
+ throw new Error(
71
+ "Unknown assignment command. Use workspace assignments list, prospect assignment get/set, routing-rules get/replace, routing apply, prospect campaign-fit get, prospect fit-override set/clear, prospect move, member remove, or leave. Mutations require --file INPUT.json.",
72
+ );
73
+ const read = [
74
+ "workspace_prospect_assignments",
75
+ "workspace_prospect_assignment",
76
+ "workspace_prospect_campaign_fit",
77
+ "workspace_routing_rules_get",
78
+ ].includes(name);
79
+ let input = read ? {} : readJsonFile(flags.file, 65536, "Assignment file");
80
+ for (const [flag, key] of [
81
+ ["workspace", "workspaceId"],
82
+ ["request-id", "requestId"],
83
+ ["prospect-id", "prospectId"],
84
+ ["campaign-id", "campaignId"],
85
+ ["assigned-to-user-id", "assignedToUserId"],
86
+ ["search", "search"],
87
+ ["cursor", "cursor"],
88
+ ])
89
+ if (flags[flag] !== undefined) input[key] = flags[flag];
90
+ if (flags.limit !== undefined) input.limit = Number(flags.limit);
91
+ if (flags.unassigned !== undefined) {
92
+ if (!["true", "false"].includes(flags.unassigned))
93
+ throw new Error("--unassigned must be true or false.");
94
+ input.unassigned = flags.unassigned === "true";
95
+ }
96
+ for (const child of children) {
97
+ const cursor = flags[child + "-cursor"],
98
+ limit = flags[child + "-limit"];
99
+ if (cursor !== undefined || limit !== undefined)
100
+ input[child.replace(/-([a-z])/g, (_, c) => c.toUpperCase())] = {
101
+ ...(cursor === undefined ? {} : { cursor }),
102
+ ...(limit === undefined ? {} : { limit: Number(limit) }),
103
+ };
104
+ }
105
+ if (name === "workspace_fit_override_set") {
106
+ const action = position.at(-1);
107
+ if (input.action !== undefined && input.action !== action)
108
+ throw new Error("File action must match the command.");
109
+ input.action = action;
110
+ }
111
+ console.log(
112
+ JSON.stringify(
113
+ await callWorkspace(name, input, configuration(flags.profile)),
114
+ ),
115
+ );
116
+ }
@@ -0,0 +1,9 @@
1
+ import {z} from 'zod';
2
+ // #413: a file is uploaded first and named by id in a LinkedIn message (content.attachments). An image is at most 5 MB;
3
+ // a voice note is at most 2 minutes and 5 MB.
4
+ const uuid=z.string().uuid(),maxBytes=5*1024*1024;
5
+ const base64=z.string().min(4).max(4*Math.ceil(maxBytes/3)+8).refine(value=>value.length%4===0&&/^[A-Za-z0-9+/]+={0,2}$/.test(value));
6
+ export const attachmentOperations={
7
+ attachment_upload:{schema:z.object({workspaceId:uuid.optional(),name:z.string().trim().min(1).max(255),contentType:z.string().max(127),contentBase64:base64,durationSeconds:z.number().int().positive().max(120).optional()}).strict(),method:'POST',path:a=>`${a.workspaceId?`/workspaces/${a.workspaceId}`:''}/attachments`,body:a=>({name:a.name,contentType:a.contentType,contentBase64:a.contentBase64,...(a.durationSeconds?{durationSeconds:a.durationSeconds}:{})}),scoped:true,description:'Store an image (jpeg, png, gif, webp; at most 5 MB) or a voice note (mpeg, mp4, ogg, wav, webm; at most 5 MB and 2 minutes) and return its id. Name the id in content.attachments of a linkedin.send action tagged profile.message.send. A voice note needs durationSeconds and goes only to a connection. Needs workspace:access.'},
8
+ attachment_get:{schema:z.object({workspaceId:uuid.optional(),attachmentId:uuid}).strict(),method:'GET',path:a=>`${a.workspaceId?`/workspaces/${a.workspaceId}`:''}/attachments/${a.attachmentId}`,scoped:true,description:'One uploaded file with its content as base64. An agent that sends from its own LinkedIn account fetches the file here, then attaches it in LinkedIn.'},
9
+ };
@@ -0,0 +1,28 @@
1
+ import {readFileSync,writeFileSync,statSync} from 'node:fs';
2
+ import {basename,extname} from 'node:path';
3
+ import {callWorkspace,configuration} from './workspaces.mjs';
4
+ const help='Use attachments upload FILE [--type MIME] [--name NAME] [--duration SECONDS] [--workspace ID] [--profile PATH], or attachments get ATTACHMENT_ID --out PATH [--workspace ID] [--profile PATH]. An image is at most 5 MB; a voice note is at most 2 minutes and 5 MB, and its length goes in --duration.';
5
+ const TYPES={'.jpg':'image/jpeg','.jpeg':'image/jpeg','.png':'image/png','.gif':'image/gif','.webp':'image/webp','.mp3':'audio/mpeg','.m4a':'audio/mp4','.ogg':'audio/ogg','.wav':'audio/wav'};
6
+ export async function attachmentsCli(args){
7
+ const position=[],flags={};for(let i=1;i<args.length;i++){
8
+ if(!args[i].startsWith('--')){position.push(args[i]);continue}
9
+ const key=args[i].slice(2);if(key==='json')continue;if(!['profile','workspace','type','name','duration','out'].includes(key)||!args[i+1]||args[i+1].startsWith('--')||Object.hasOwn(flags,key))throw new Error(`Unknown or incomplete attachments option. ${help}`);
10
+ flags[key]=args[++i];
11
+ }
12
+ const config=configuration(flags.profile);
13
+ if(position[0]==='upload'&&position.length===2){
14
+ const file=position[1],type=flags.type??TYPES[extname(file).toLowerCase()];
15
+ if(!type)throw new Error(`Give --type for this file. ${help}`);
16
+ const stat=statSync(file);if(!stat.isFile()||stat.size>5*1024*1024)throw new Error('The file must be a regular file of at most 5 MB.');
17
+ const duration=flags.duration===undefined?undefined:Number(flags.duration);
18
+ if(duration!==undefined&&!Number.isInteger(duration))throw new Error('--duration is a whole number of seconds.');
19
+ const result=await callWorkspace('attachment_upload',{workspaceId:flags.workspace,name:flags.name??basename(file),contentType:type,contentBase64:readFileSync(file).toString('base64'),...(duration===undefined?{}:{durationSeconds:duration})},config);
20
+ console.log(JSON.stringify(result.data));return;
21
+ }
22
+ if(position[0]==='get'&&position.length===2&&flags.out){
23
+ const result=await callWorkspace('attachment_get',{workspaceId:flags.workspace,attachmentId:position[1]},config);
24
+ const {contentBase64,...rest}=result.data;writeFileSync(flags.out,Buffer.from(contentBase64,'base64'));
25
+ console.log(JSON.stringify({...rest,savedTo:flags.out}));return;
26
+ }
27
+ throw new Error(help);
28
+ }
@@ -0,0 +1,21 @@
1
+ import {z} from 'zod';
2
+ const uuid=z.string().uuid(),workspace={workspaceId:uuid.optional()},locator={...workspace,audienceId:uuid};
3
+ const criterion=z.object({name:z.string(),providerId:z.string().nullable().optional(),negative:z.boolean().optional()}).strict();
4
+ const location=criterion.extend({key:z.string().nullable().optional()}).strict();
5
+ const strings=z.array(z.string()).optional(),criteria=z.array(criterion).optional(),locations=z.array(location).optional();
6
+ const fields=z.object({name:z.string(),tags:strings,textCriteria:z.string().optional(),notes:z.string().optional(),discoveryKeyword:z.string().optional(),seniorityMatchMode:z.enum(['at_least','exact']).optional(),company:z.object({industries:criteria,industryGroups:strings,sizes:criteria,types:criteria,locations,keywords:z.object({include:strings,exclude:strings}).strict().optional()}).strict(),person:z.object({locations,currentTitles:criteria,pastTitles:criteria,seniorities:criteria,functions:criteria,bioPhrases:z.array(z.object({phrase:z.string(),negative:z.boolean().optional()}).strict()).optional(),allowedTitleExceptions:strings}).strict()}).strict();
7
+ const path=a=>`/workspaces/${a.workspaceId}/audiences`;
8
+ export const audienceOperations={
9
+ audience_options:{schema:z.object({...workspace,kind:z.enum(['industries','industry_groups','sizes','types','seniorities','titles','locations','functions']),q:z.string().max(200).optional()}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/audience-options?`+new URLSearchParams({kind:a.kind,...(a.q!==undefined?{q:a.q}:{})}),scoped:true},
10
+ audience_list:{schema:z.object({...workspace,archived:z.boolean().optional(),limit:z.number().int().optional(),after:uuid.optional(),q:z.string().optional()}).strict(),method:'GET',path:a=>path(a)+'?'+new URLSearchParams(Object.fromEntries(Object.entries({archived:a.archived,limit:a.limit,after:a.after,q:a.q}).filter(([,v])=>v!==undefined).map(([k,v])=>[k,String(v)]))),scoped:true},
11
+ audience_get:{schema:z.object({...locator,revision:z.number().int().optional()}).strict(),method:'GET',path:a=>path(a)+`/${a.audienceId}`+(a.revision===undefined?'':`?revision=${a.revision}`),scoped:true},
12
+ audience_create:{schema:z.object({...workspace,requestId:uuid,fields}).strict(),method:'POST',path,body:a=>({requestId:a.requestId,fields:a.fields}),scoped:true},
13
+ audience_update:{schema:z.object({...locator,expectedRevision:z.number().int(),fields}).strict(),method:'POST',path:a=>path(a)+`/${a.audienceId}/revisions`,body:a=>({expectedRevision:a.expectedRevision,fields:a.fields}),scoped:true},
14
+ audience_archive:{schema:z.object(locator).strict(),method:'POST',path:a=>path(a)+`/${a.audienceId}/archive`,scoped:true},
15
+ audience_restore:{schema:z.object(locator).strict(),method:'POST',path:a=>path(a)+`/${a.audienceId}/restore`,scoped:true},
16
+ audience_summary:{schema:z.object({...workspace,audienceId:uuid.optional()}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/audience-summary`+(a.audienceId?`?audienceId=${a.audienceId}`:''),scoped:true,description:'Counts of active and archived Audiences, the campaigns using each (with people, engaged and at-goal counts), and people found per Audience (recent means admitted in the last 7 days). With audienceId, sizes gives that Audience\'s approximate reachable size against the 1,000-person minimum (null when it cannot be estimated; sizePending while the estimate runs).'},
17
+ audience_people:{schema:z.object(locator).strict(),method:'GET',path:a=>path(a)+`/${a.audienceId}/people`,scoped:true,description:'People found by any campaign on this Audience, newest first, at most 100, with their latest participation.'},
18
+ audience_companies:{schema:z.object(locator).strict(),method:'GET',path:a=>path(a)+`/${a.audienceId}/companies`,scoped:true,description:'Companies where this Audience has found people, most people first, at most 100; withoutCompany counts people with no company.'},
19
+ audience_fit_check_improve:{schema:z.object({...workspace,text:z.string().trim().min(1).max(5000)}).strict(),method:'POST',path:a=>`/workspaces/${a.workspaceId}/audience-fit-checks`,body:a=>({text:a.text}),scoped:true,description:'Review a smart rule (gut check) and suggest a clearer rewrite a reviewer can judge from a profile. Returns whether it is usable, what counts as a match and a contradiction, and the rewrite. Saves nothing.'},
20
+ audience_prospect_test:{schema:z.object({...locator,url:z.string(),requestId:uuid,revision:z.number().int().positive().optional()}).strict(),method:'POST',path:a=>path(a)+`/${a.audienceId}/prospect-tests`,body:a=>({url:a.url,requestId:a.requestId,...(a.revision?{revision:a.revision}:{})}),scoped:true,description:'Check one LinkedIn person URL against the Audience rules and name the deciding rule. Read-only: never admits the person. Saved profiles are reused free; otherwise the person and company fetches spend hosted credits. Generate requestId once and reuse it after an interruption so a retry never pays twice.'},
21
+ };
@@ -0,0 +1,29 @@
1
+ import {callWorkspace,configuration} from './workspaces.mjs';
2
+ import {readJsonFile} from './json-file.mjs';
3
+ export async function audienceCli(args){
4
+ const position=[],flags={};for(let i=0;i<args.length;i++){
5
+ if(!args[i].startsWith('--')){position.push(args[i]);continue}
6
+ const key=args[i].slice(2);if(!['profile','workspace','file','request-id','expected-revision','revision','archived','limit','after','q','kind','url','audience','text'].includes(key)||!args[i+1]||args[i+1].startsWith('--')||Object.hasOwn(flags,key))throw new Error('Unknown or incomplete audience option.');
7
+ flags[key]=args[++i];
8
+ }
9
+ const command=position[1],name={options:'audience_options',list:'audience_list',show:'audience_get',create:'audience_create',update:'audience_update',archive:'audience_archive',restore:'audience_restore',summary:'audience_summary',people:'audience_people',companies:'audience_companies',test:'audience_prospect_test',improve:'audience_fit_check_improve'}[command];
10
+ if(!name||position.length!==(['list','create','options','summary','improve'].includes(command)?2:3))throw new Error('Use audience options --kind KIND [--q TEXT]|list|show ID|create|update ID|archive ID|restore ID|summary|people ID|companies ID|test ID --url URL --request-id UUID [--revision N]|improve --text RULE.');
11
+ const input={workspaceId:flags.workspace};
12
+ if(position[2])input.audienceId=position[2];
13
+ if(command==='options'){input.kind=flags.kind;if(flags.q!==undefined)input.q=flags.q;}
14
+ if(command==='list'){
15
+ if(flags.archived!==undefined&&!['true','false'].includes(flags.archived))throw new Error('Archived must be true or false.');
16
+ if(flags.archived!==undefined)input.archived=flags.archived==='true';
17
+ if(flags.limit!==undefined)input.limit=Number(flags.limit);
18
+ if(flags.after)input.after=flags.after;
19
+ if(flags.q!==undefined)input.q=flags.q;
20
+ }
21
+ if(command==='summary'&&flags.audience!==undefined)input.audienceId=flags.audience;
22
+ if(command==='test'){if(!flags.url)throw new Error('Supply --url with a LinkedIn profile URL.');input.url=flags.url;input.requestId=flags['request-id'];}
23
+ if(command==='improve'){if(!flags.text)throw new Error('Supply the smart rule with --text.');input.text=flags.text;}
24
+ if(['show','test'].includes(command)&&flags.revision!==undefined)input.revision=Number(flags.revision);
25
+ if(['create','update'].includes(command)){if(!flags.file)throw new Error('Supply --file PATH containing Audience fields.');input.fields=readJsonFile(flags.file,65536,'Audience file');}
26
+ if(command==='create')input.requestId=flags['request-id'];
27
+ if(command==='update')input.expectedRevision=Number(flags['expected-revision']);
28
+ console.log(JSON.stringify(await callWorkspace(name,input,configuration(flags.profile))));
29
+ }
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env node
2
+ import { isAssignmentCommand, assignmentCli } from './assignment.mjs';
3
+ import { playbookHelp } from './playbook-help.mjs';
4
+ // The CLI is an HTTP client. It has no database access or separate business rules.
5
+ const args = process.argv.slice(2);
6
+ const usage = `Profiles: audienti profiles list|search|get ID | match evaluate|apply SOURCE CANDIDATE | history RESOLUTION (saved workspace records)
7
+ Skills: audienti skills list [--compact]|show SKILL|read SKILL RESOURCE|helper NAME --file INPUT.json|- [--json]
8
+ Tools: audienti call TOOL ['JSON'] | tools search "WORDS"|get TOOL|list|runs|balance [--help]
9
+ Sender: audienti sender accounts list|get|alias|register|bind|unbind|state|automation|activity report [--profile PATH]
10
+ Managed LinkedIn: audienti sender managed list|connect [--workspace ID] [--profile PATH] (connect: the user only; returns a sign-in link)
11
+ audienti sender managed plan MANAGED_ACCOUNT_ID --plan free|premium_career|premium_business|premium_all_in_one|sales_navigator_core|sales_navigator_advanced|recruiter_lite|none [--workspace ID] (the user only; sets the connection note length; none clears it)
12
+ Prospecting: audienti prospecting settings show|save|accounts|account-show|account-save|history [--profile PATH]
13
+ Usage: audienti whoami [--json]
14
+ audienti workspace assignments list [--workspace ID]
15
+ audienti workspace prospect assignment get/set --prospect-id ID [--file INPUT.json]
16
+ audienti workspace routing-rules get/replace [--file INPUT.json]
17
+ audienti workspace routing apply --file INPUT.json
18
+ audienti workspace prospect campaign-fit get --prospect-id ID --campaign-id ID
19
+ audienti workspace prospect fit-override set/clear --file INPUT.json
20
+ audienti workspace prospect move --file INPUT.json
21
+ audienti workspace member remove --file INPUT.json
22
+ audienti workspace leave --file INPUT.json
23
+
24
+ Required environment:
25
+ AUDIENTI_API_URL Backend base URL including /api/v1
26
+ AUDIENTI_API_TOKEN Account API token (create it from your Account page)
27
+
28
+ Use HTTPS for remote hosts. Local HTTP is supported on loopback only.
29
+ Agent commands: agent connect --name NAME [--write (ask for Allow workspace changes up front)] [--capabilities find,enrich,expand,research,engage,sync,notify,approve]|poll|cancel|list|revoke ID --profile PATH
30
+ Owner permission change: agent permission ID --scope workspace:read|workspace:access --profile USER_PROFILE
31
+ Owner capability change: agent capabilities ID --set find,enrich,expand,research,engage,sync,notify,approve --profile USER_PROFILE (agents are refused outside this set)
32
+ Agent capabilities: agent capabilities report --file CAPABILITIES.json --profile AGENT_PROFILE (capability rows; the old per-account shape still works)
33
+ What the agent can do: agent capabilities [--workspace ID] [--json] --profile AGENT_PROFILE (works now, and works with Audienti tools)
34
+ Read capabilities: agent capabilities show [--id AGENT_ID] --profile PROFILE
35
+ Getting started: skills show getting-started, then agent capabilities report --file CAPABILITIES.json --profile AGENT_PROFILE
36
+ Read tool inventory: agent inventory show [--id AGENT_ID] --profile PROFILE (agent inventory report still works; capabilities report is current)
37
+ Account authorization: agent accounts show [--id AGENT_ID] --profile PROFILE
38
+ Authorize/revoke: agent accounts authorize --id AGENT_ID --provider email|linkedin --account REF --authorized true|false --profile OWNER_PROFILE
39
+ Sharing: agent capabilities sharing --id AGENT_ID --profile OWNER_PROFILE
40
+ Share/unshare: agent capabilities share --id AGENT_ID --provider email|linkedin --account REF --workspace ID --shared true|false --profile OWNER_PROFILE
41
+ Team member: workspace member USER_ID [--workspace ID] --profile PROFILE
42
+ Work execution: work execution claim|start|send|get PLAN --action ID (send: managed LinkedIn accounts, --file with claimId and requestId); release|lookup --claim ID; report --attempt ID
43
+ Work feedback: work changes request|list|get|respond|withdraw PLAN --workspace ID
44
+ Work: work plan save|list|show|approve|withdraw-approval|cancel|skip|eligibility
45
+ Activity: work activity --workspace ID [--search TEXT] [--cursor-at ISO --cursor-id ID]
46
+ Work list: work list --workspace ID [--order oldest|priority] [--queue review|blocked|activity]
47
+ a plan that only waits for its cadence is in neither review nor blocked; it returns to review when the wait ends
48
+ Work writes: --file INPUT.json; human review needs a human workspace:access token
49
+ Audiences: audience options --kind industries|locations|sizes|types|seniorities|titles|functions [--q TEXT]
50
+ audience list|show ID|create|update ID|archive ID|restore ID
51
+ audience summary [--audience ID]|people ID|companies ID
52
+ audience test ID --url LINKEDIN_URL --request-id UUID (spends hosted credits unless the profile is saved)
53
+ Files: file upload PATH --type MIME|list|prepare|register|download|delete ID (personal files, human token only)
54
+ Queue: audienti queue [list]|next|claim ID [--note TEXT]|done ID [--note TEXT]|skip ID --note TEXT|release ID [--workspace ID] (start here after you connect; without --workspace an agent reads every workspace its user approved it in; done or skip again with a new --note corrects the note of a finished task)
55
+ Attachments: audienti attachments upload FILE [--duration SECONDS] (store an image or voice note, get its id for content.attachments)
56
+ Outcome checks: audienti outcomes report TASK_ID --file RESULTS.json (results for the people an outcomes.check task lists)
57
+ Questions: audienti ask "QUESTION" [--context TEXT] [--option TEXT]... [--campaign ID] [--task ID] (questions for your user go to Operator, not chat)
58
+ audienti questions [list] [--state open|answered|withdrawn|all] | show ID | withdraw ID | answer ID --answer TEXT
59
+ Rooms: audienti rooms directory|inbox|ack|send|thread|threads|create|list|read|retire|restore|redact|resume|pause|profile|hook|watch|wait (audienti rooms help)
60
+ KV: audienti kv get KEY|set KEY VALUE [--json] [--nx] [--if-version N] [--ttl 7d|--expires-at TIME]|list [--prefix P]|search [--key TEXT] [--value TEXT]|delete KEY (small state between runs; never credentials)
61
+ Offers: offer list|show ID|create|update ID|archive ID|restore ID
62
+ Offer options: --file FIELDS.json --request-id UUID --expected-revision N --revision N --archived true|false
63
+ audienti prospects replies set|get|list PERSON --workspace ID [--evidence EVIDENCE] [--file INPUT.json]
64
+ Account intake: prospects intake --workspace ID --file INPUT.json
65
+ prospects conversation append|list PERSON --workspace ID
66
+ Discovery: prospects list|get ID|timeline ID [--limit N]|observation create|citation create|evidence create
67
+ prospects contacts list|record PERSON [--file INPUT.json] [--request-id UUID] --workspace ID
68
+ prospects contacts get|retire PERSON CONTACT --workspace ID
69
+ prospects suppression get|set ID --workspace ID
70
+ guardrails show|pause|resume --workspace ID
71
+ dnc list|add VALUE|remove VALUE --workspace ID
72
+ dnc import --file LIST.csv --workspace ID (at most 5,000 rows; reports added, skipped and invalid rows)
73
+ dnc export [--output FILE.csv] --workspace ID
74
+ secret list [--workspace ID] (names only)
75
+ secret get NAME [--workspace ID] [--json] (prints the value; the secret records who read it last and when)
76
+ budgets show --workspace ID
77
+ budgets alerts --workspace ID [--limit N]
78
+ usage breakdown [--workspace ID] [--since month|7d|30d|90d] [--by tool|provider|model|caller|day] [--format json|csv]
79
+ workspace usage summary|callers|calls [--after ID] [--caller user:ID|agent:ID|key:ID] [--status all|succeeded|failed|pending]|call ID [--workspace ID] (paid tool use, last 30 days, and single calls)
80
+ audit list [--workspace ID] [--since 7d] [--actor agent|person] [--category NAME] [--format json|csv]
81
+ budgets set --workspace ID [--agent ID --cap N|none] [--new-agent-cap N|none] [--at-limit pause|ask] [--alert80 on|off] (alert80 covers Operator and email at 80%)
82
+ Qualification: campaigns qualification submit --file INPUT.json
83
+ Campaign summary: campaign summary [--workspace ID] (campaign counts per state, and per campaign its audience, offer, playbook, goal stage, people, engaged and at-goal counts)
84
+ Find prospects: campaign find CAMPAIGN_ID --count N --max-credits C (searches with the audience filters, checks each person, admits only fits; stops at N fits or the credit cap and reports the spend)
85
+ campaign check CAMPAIGN_ID --url LINKEDIN_URL [--request-id UUID] (checks one person the same way; reuse the request ID after an interruption)
86
+ Target companies: campaign targets CAMPAIGN_ID [--limit N --after CURSOR] (reads the companies people must work at to join)
87
+ campaign targets CAMPAIGN_ID --add acme.com,linkedin.com/company/globex [--remove old.com] | --set FILE|- (one company per line; saves a new revision)
88
+ Playbooks: playbook list [--archived] [--limit N --cursor ID]|actions|show ID [--revision N]|save --file REQUEST.json|archive ID|restore ID|delete ID|progress PARTICIPATION_ID|record --file REQUEST.json|suggest --file REQUEST.json|suggestions|decide --file REQUEST.json (audienti playbook SUBCOMMAND --help for the request shape)
89
+ Analytics: analytics show --from ISO --to ISO [--campaign ID] [--playbook-version ID] [--format json|csv]
90
+ analytics outbound [--workspace ID] [--days N | --from ISO --to ISO] [--by sender|action|day] [--format json|csv]
91
+ analytics activity [--workspace ID] [--days N | --from ISO --to ISO] [--format json|csv]
92
+ analytics cohorts [--workspace ID] [--stage goal|KEY] [--cohorts N] [--horizon N] [filters] [--format json|csv]
93
+ analytics aging [--workspace ID] [--threshold-days N] [filters] [--format json|csv]
94
+ analytics filters [--workspace ID] (lists campaign, owner, offer, audience and tag values)
95
+ (filters: --campaign ID --owner ID --offer ID --audience ID --tag TAG; also accepted by analytics show)
96
+ Pipeline: pipeline show [--campaign ID] [--playbook-version ID]
97
+ Connections: connections list|register --file REQUEST.json|report --file REQUEST.json
98
+ Discovery writes use --file INPUT.json, --workspace ID and stable --request-id UUID
99
+ Workspace commands: workspace init|list|create|show|search|invite|invitations|use
100
+ Notifications: workspace notifications --workspace ID [--unread-only true] [--cursor FILE.json]
101
+ Read state: workspace notification-read --workspace ID --key KEY --read true|false [--through OBSERVED_AT]
102
+ Search: workspace search --workspace ID --query TEXT [--people-only true] [--recent FILE.json]
103
+ Invitation commands: workspace invitation show|accept|decline|retry|cancel ID
104
+ Options: --workspace ID --name NAME --request-id UUID --email EMAIL --generation N
105
+ Optional private profile: --profile PATH (outside this repository), or the AUDIENTI_PROFILE variable. --profile wins.
106
+ Save an environment token: profile save --scope account:read|workspace:access|agents:manage --profile PATH
107
+ Start local MCP stdio server: mcp
108
+ --json writes the API response envelope.
109
+ `;
110
+
111
+ // Example inputs for the commands that save prospects, so `--help` shows a working shape.
112
+ const examples = {
113
+ 'prospects observation create': { requestId: 'UUID', kind: 'person', source: { provider: 'linkedin', url: 'https://www.linkedin.com/in/jane-doe' }, fields: { name: 'Jane Doe', title: 'VP Sales', imageUrl: 'https://media.licdn.com/dms/image/PHOTO_ID' }, provenance: { url: 'https://example.com/team/jane' }, observedAt: '2026-01-01T00:00:00Z', skill: { id: 'signal-prospect-research', version: '1' } },
114
+ 'prospects citation create': { requestId: 'UUID', source: { kind: 'prospect', id: 'PERSON_PROSPECT_ID' }, target: { kind: 'prospect', id: 'COMPANY_PROSPECT_ID' }, edgeType: 'works_at', roles: [{ roleId: 'role-1', title: 'VP Sales', current: true, observedAt: '2026-01-01T00:00:00Z' }] },
115
+ 'prospects evidence create': { requestId: 'UUID', subject: { kind: 'person', prospectId: 'PROSPECT_ID' }, campaignId: 'CAMPAIGN_ID', campaignRevision: 1, kind: 'interpretation', data: { conclusion: 'Leads a 40-person sales team', reason: 'Team page lists her as VP Sales' }, sourceUrl: 'https://example.com/team', observedAt: '2026-01-01T00:00:00Z', skill: { id: 'signal-prospect-research', version: '1' } },
116
+ 'prospects contacts record': { kind: 'email|linkedin|mobile|phone', value: 'jane@example.com, or +15551234567 for a mobile or a direct office phone', reason: 'Published on https://example.com/contact', requestId: 'UUID', provenance: { observationId: 'OBSERVATION_ID' } },
117
+ 'campaigns qualification submit': { requestId: 'UUID', campaignId: 'CAMPAIGN_ID', campaignRevision: 1, prospectId: 'PERSON_PROSPECT_ID', companyProspectId: 'COMPANY_PROSPECT_ID', companyIcp: { status: 'pass|fail|unverified', evidenceIds: ['COMPANY_EVIDENCE_ID'] }, personIcp: { status: 'pass|fail|unverified', evidenceIds: ['PERSON_EVIDENCE_ID'] }, signal: { status: 'match|no_match|unverified|not_required', evidenceIds: [] }, fit: { status: 'high|moderate|low|no_fit|unscored', evidenceIds: ['PERSON_EVIDENCE_ID'], reason: 'why' }, skill: { id: 'signal-prospect-research', version: '1' } },
118
+ };
119
+
120
+ function commandHelp() {
121
+ const words = args.slice(0, args.findIndex(value => value.startsWith('-'))).filter(Boolean);
122
+ const lines = usage.split('\n');
123
+ let shown = [];
124
+ for (let n = Math.min(words.length, 2); n > 0 && shown.length === 0; n--) {
125
+ const prefix = words.slice(0, n).join(' ');
126
+ shown = lines.filter(line => line.includes(prefix));
127
+ }
128
+ console.log(shown.length ? shown.map(line => line.trim()).join('\n') : usage);
129
+ const example = Object.entries(examples).find(([command]) => words.join(' ').startsWith(command));
130
+ if (example) console.log(`\nExample --file INPUT.json for ${example[0]}:\n${JSON.stringify(example[1], null, 1)}\nSteps and rules: the "Save prospects and contacts" section of llms.txt.`);
131
+ }
132
+
133
+ async function main() {
134
+ if (args[0] === 'playbook' && args.slice(1).some(value => value === '--help' || value === '-h')) { console.log(playbookHelp(args[1]?.startsWith('-') ? undefined : args[1])); return; }
135
+ if (args.length > 1 && args.slice(1).some(value => value === '--help' || value === '-h') && !['tools', 'call', 'skills'].includes(args[0])) { commandHelp(); return; }
136
+ if(args[0]==='prospects'&&['intake','conversation'].includes(args[1])){const {accountIntakeCli}=await import('./account-intake.mjs');await accountIntakeCli(args);return;}
137
+ if(args[0]==='sender'){const {senderAccountsCli}=await import('./sender-accounts.mjs');await senderAccountsCli(args);return;}
138
+ if(args[0]==='prospecting'){const {prospectingSettingsCli}=await import('./prospecting-settings.mjs');await prospectingSettingsCli(args);return;}
139
+ if(args[0]==='prospects'&&args[1]==='replies'){const {accountRepliesCli}=await import('./account-replies.mjs');await accountRepliesCli(args);return;}
140
+ if(args[0]==='prospects'&&args[1]==='contacts'){const {recipientsCli}=await import('./recipients.mjs');await recipientsCli(args);return;}
141
+ if(args[0]==='usage'){const {usageCli}=await import('./usage.mjs');await usageCli(args);return;}
142
+ if(args[0]==='audit'){const {auditCli}=await import('./audit.mjs');await auditCli(args);return;}
143
+ if(args[0]==='secret'||args[0]==='secrets'){const {secretsCli}=await import('./secrets.mjs');await secretsCli(args);return;}
144
+ if(args[0]==='budgets'){const {budgetsCli}=await import('./budgets.mjs');await budgetsCli(args);return;}
145
+ if(args[0]==='guardrails'||args[0]==='dnc'){const {guardrailsCli}=await import('./guardrails.mjs');await guardrailsCli(args);return;}
146
+ if(args[0]==='prospects'&&args[1]==='suppression'){const {suppressionCli}=await import('./suppression.mjs');await suppressionCli(args);return;}
147
+ if(args[0]==='call'){const {callCli}=await import('./tools.mjs');await callCli(args);return;}
148
+ if(args[0]==='tools'){const {toolsCli}=await import('./tools.mjs');await toolsCli(args);return;}
149
+ if(['topics','posts'].includes(args[0])){const {contentCli}=await import('./content.mjs');await contentCli(args);return;}
150
+ if(args[0]==='work'&&args[1]==='execution'){const {workExecutionCli}=await import('./work-execution.mjs');await workExecutionCli(args);return;}
151
+ if(args[0]==='work'&&args[1]==='changes'){const {workFeedbackCli}=await import('./work-feedback.mjs');await workFeedbackCli(args);return;}
152
+ if(args[0]==='work'){const {workCli}=await import('./work.mjs');await workCli(args);return;}
153
+ if(['prospects','campaigns'].includes(args[0])){const {discoveryCli}=await import('./discovery.mjs');await discoveryCli(args);return;}
154
+ if(args[0]==='analytics'&&['cohorts','aging','filters'].includes(args[1])){const {cohortCli}=await import('./cohorts.mjs');await cohortCli(args);return;}
155
+ if(args[0]==='analytics'&&['outbound','activity'].includes(args[1])){const {outboundCli}=await import('./outbound.mjs');await outboundCli(args);return;}
156
+ if(['playbook','analytics','pipeline','connections'].includes(args[0])){const {progressCli}=await import('./progress.mjs');await progressCli(args);return;}
157
+ if(args[0]==='campaign'){const {campaignCli}=await import('./campaigns.mjs');await campaignCli(args);return;}
158
+ if(args[0]==='audience'){const {audienceCli}=await import('./audiences.mjs');await audienceCli(args);return;}
159
+ if(args[0]==='file'){const {fileCli}=await import('./files.mjs');await fileCli(args);return;}
160
+ if(args[0]==='ask'){const {askCli}=await import('./questions.mjs');await askCli(args);return;}
161
+ if(args[0]==='questions'){const {questionsCli}=await import('./questions.mjs');await questionsCli(args);return;}
162
+ if(args[0]==='queue'){const {queueCli}=await import('./queue.mjs');await queueCli(args);return;}
163
+ if(args[0]==='attachments'){const {attachmentsCli}=await import('./attachments.mjs');await attachmentsCli(args);return;}
164
+ if(args[0]==='outcomes'){const {outcomesCli}=await import('./outcomes.mjs');await outcomesCli(args);return;}
165
+ if(args[0]==='rooms'){const {roomsCli}=await import('./rooms.mjs');await roomsCli(args);return;}
166
+ if(args[0]==='kv'){const {kvCli}=await import('./kv.mjs');await kvCli(args);return;}
167
+ if(args[0]==='offer'){const {offerCli}=await import('./offers.mjs');await offerCli(args);return;}
168
+ if (args[0] === 'agent') { const { agentCli } = await import('./agents.mjs'); await agentCli(args); return; }
169
+ if (isAssignmentCommand(args)) { await assignmentCli(args); return; }
170
+ if (['workspace', 'profile'].includes(args[0])) { const { workspaceCli } = await import('./workspaces.mjs'); await workspaceCli(args); return; }
171
+ if (args[0] === 'profiles') { const { profilesCli } = await import('./profiles.mjs'); await profilesCli(args); return; }
172
+ if (args[0] === 'skills') { const { skillsCli } = await import('./skills.mjs'); await skillsCli(args); return; }
173
+ if (args[0] === 'mcp' && args.length === 1) { await import('./mcp.mjs'); return; }
174
+ if (args.length === 0 || (args.length === 1 && ['--help', '-h'].includes(args[0]))) { console.log(usage); return; }
175
+ let profileConfig;
176
+ const profileIndex = args.indexOf('--profile');
177
+ if (args[0] === 'whoami' && profileIndex > 0 && args[profileIndex + 1]) {
178
+ const { configuration } = await import('./workspaces.mjs');
179
+ profileConfig = configuration(args[profileIndex + 1], 'account:read');
180
+ if (!profileConfig.token) throw new Error('whoami needs a human account token. Agents confirm their connection with: audienti workspace list --profile PATH');
181
+ args.splice(profileIndex, 2);
182
+ } else if (args[0] === 'whoami' && profileIndex < 0 && process.env.AUDIENTI_PROFILE && !(process.env.AUDIENTI_API_URL && process.env.AUDIENTI_API_TOKEN)) {
183
+ // The profile named by AUDIENTI_PROFILE (#448), unless the URL and token are in the environment.
184
+ const { configuration } = await import('./workspaces.mjs');
185
+ profileConfig = configuration(undefined, 'account:read');
186
+ if (!profileConfig.token) throw new Error('whoami needs a human account token, and AUDIENTI_PROFILE holds none. Agents confirm their connection with: audienti workspace list');
187
+ }
188
+ if (args[0] !== 'whoami' || args.length > 2 || (args[1] && args[1] !== '--json')) throw new Error('Unknown command or option. Use audienti --help.');
189
+ const rawUrl = profileConfig ? profileConfig.url : process.env.AUDIENTI_API_URL;
190
+ const token = profileConfig ? profileConfig.token : process.env.AUDIENTI_API_TOKEN;
191
+ if (!rawUrl || !token) throw new Error('Set AUDIENTI_API_URL and AUDIENTI_API_TOKEN, or pass --profile PATH (or set AUDIENTI_PROFILE) to a profile with an account:read token. Create a token from your Account page.');
192
+ let base;
193
+ try { base = new URL(rawUrl); } catch { throw new Error('AUDIENTI_API_URL must be a valid API base URL.'); }
194
+ if (base.username || base.password || base.search || base.hash) throw new Error('The API URL must not contain credentials, query parameters, or a fragment.');
195
+ const local = ['localhost', '127.0.0.1', '[::1]'].includes(base.hostname);
196
+ if (base.protocol !== 'https:' && !(base.protocol === 'http:' && local)) throw new Error('Use HTTPS for remote API hosts. HTTP is only allowed on loopback.');
197
+ if (base.pathname.replace(/\/$/, '') !== '/api/v1') throw new Error('AUDIENTI_API_URL must end with /api/v1.');
198
+ base.pathname = '/api/v1/account';
199
+ let response;
200
+ try {
201
+ response = await fetch(base, { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, redirect: 'error', signal: AbortSignal.timeout(10_000) });
202
+ } catch { throw new Error('Could not reach the API. Check its URL and availability. Redirects are not followed.'); }
203
+ if (!response.ok) {
204
+ if (response.status === 401) throw new Error('Authentication failed. Check whether your token is invalid, expired, or revoked.');
205
+ if (response.status === 403) throw new Error('This token lacks account:read permission.');
206
+ throw new Error(`API request failed (HTTP ${response.status}).`);
207
+ }
208
+ let body;
209
+ try { body = await response.json(); } catch { throw new Error('The API returned an invalid response.'); }
210
+ const data = body?.data;
211
+ if (!data || typeof data.id !== 'string' || !('email' in data) || !('username' in data) || ![data.email, data.username].every(v => v === null || typeof v === 'string')) throw new Error('The API returned an invalid account profile.');
212
+ // Select the documented fields, so an unexpected response cannot print credentials.
213
+ const profile = { id: data.id, email: data.email, username: data.username };
214
+ console.log(args[1] === '--json' ? JSON.stringify({ data: profile }) : `${profile.email ?? profile.username ?? profile.id}\nAccount ID: ${profile.id}`);
215
+ }
216
+
217
+ main().catch(error => { console.error(JSON.stringify({ error: error.toJSON ? error.toJSON() : { code: 'client_error', message: error.message } })); process.exitCode = 1; });
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ const query = a => new URLSearchParams(Object.entries(a).filter(([k, v]) => k !== 'workspaceId' && v !== undefined).map(([k, v]) => [k, String(v)]));
3
+ export const auditOperations = {
4
+ workspace_audit_log_list: {
5
+ schema: z.object({
6
+ workspaceId: z.string().uuid().optional(),
7
+ actor: z.enum(['person', 'agent']).optional(),
8
+ category: z.enum(['guardrails', 'budgets', 'approvals', 'suppression', 'members', 'agents']).optional(),
9
+ since: z.string().datetime().optional(),
10
+ until: z.string().datetime().optional(),
11
+ cursor: z.string().max(500).optional(),
12
+ limit: z.number().int().min(1).max(200).optional(),
13
+ }).strict(),
14
+ method: 'GET', path: a => `/workspaces/${a.workspaceId}/audit-events?${query(a)}`, scoped: true,
15
+ description: 'List the read-only workspace audit log: who changed guardrails, budgets, approvals, suppression, members and agent access, newest first. Admin only. Page with cursor.',
16
+ },
17
+ };
package/cli/audit.mjs ADDED
@@ -0,0 +1,39 @@
1
+ import { callWorkspace, configuration } from './workspaces.mjs';
2
+
3
+ const UNITS = { m: 60_000, h: 3_600_000, d: 86_400_000, w: 604_800_000 };
4
+ export function sinceToIso(value, now = Date.now()) {
5
+ const match = /^(\d{1,4})([mhdw])$/.exec(value);
6
+ if (match) return new Date(now - Number(match[1]) * UNITS[match[2]]).toISOString();
7
+ const date = new Date(value);
8
+ if (Number.isNaN(date.getTime())) throw Error('--since must look like 7d, 12h or an ISO date.');
9
+ return date.toISOString();
10
+ }
11
+ const cell = value => /[",\n\r]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
12
+ export function auditCsv(items) {
13
+ const rows = items.map(item => [item.at, item.actor?.kind ?? '', item.actor?.name ?? '', item.category, item.action, item.summary, item.object?.label ?? '', item.object?.href ?? '']);
14
+ return [['when', 'actor_kind', 'actor', 'category', 'action', 'summary', 'object', 'link'], ...rows].map(row => row.map(cell).join(',')).join('\n') + '\n';
15
+ }
16
+ export async function auditCli(args) {
17
+ const flags = {}, positions = [];
18
+ for (let i = 0; i < args.length; i++) {
19
+ const arg = args[i];
20
+ if (arg === '--json') continue;
21
+ if (!arg.startsWith('--')) { positions.push(arg); continue; }
22
+ const key = arg.slice(2);
23
+ if (!['workspace', 'profile', 'since', 'until', 'actor', 'category', 'limit', 'cursor', 'format'].includes(key) || Object.hasOwn(flags, key) || i + 1 >= args.length) throw Error(`Invalid flag --${key}.`);
24
+ flags[key] = args[++i];
25
+ }
26
+ if (positions.length !== 2 || positions[1] !== 'list') throw Error('Use audit list [--workspace ID] [--since 7d] [--actor agent|person] [--format json|csv].');
27
+ if (flags.actor && !['agent', 'person'].includes(flags.actor)) throw Error('--actor must be agent or person.');
28
+ if (flags.format && !['json', 'csv'].includes(flags.format)) throw Error('--format must be json or csv.');
29
+ const input = { workspaceId: flags.workspace };
30
+ if (flags.actor) input.actor = flags.actor;
31
+ if (flags.category) input.category = flags.category;
32
+ if (flags.since) input.since = sinceToIso(flags.since);
33
+ if (flags.until) input.until = sinceToIso(flags.until);
34
+ if (flags.cursor) input.cursor = flags.cursor;
35
+ if (flags.limit) { input.limit = Number(flags.limit); if (!Number.isInteger(input.limit)) throw Error('--limit must be a whole number.'); }
36
+ const result = await callWorkspace('workspace_audit_log_list', input, configuration(flags.profile));
37
+ if (flags.format === 'csv') { process.stdout.write(auditCsv((result?.data ?? result)?.items ?? [])); return; }
38
+ console.log(JSON.stringify(result));
39
+ }
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod';
2
+ const workspace = { workspaceId: z.string().uuid().optional() };
3
+ const cap = z.number().int().min(0).max(10000000).nullable();
4
+ export const budgetOperations = {
5
+ workspace_budgets_get: { schema: z.object(workspace).strict(), method: 'GET', path: a => `/workspaces/${a.workspaceId}/budgets`, scoped: true, description: 'Read spend budgets: credits used this UTC month, each agent’s monthly cap (null means unlimited), the cap for newly registered agents, what happens at a cap and the 80% warning switch (Operator and email) and the ten latest alerts.' },
6
+ workspace_budget_alerts: { schema: z.object({ ...workspace, limit: z.number().int().min(1).max(200).optional() }).strict(), method: 'GET', path: a => `/workspaces/${a.workspaceId}/budget-alerts${a.limit ? `?limit=${a.limit}` : ''}`, scoped: true, description: 'List recent low-credit and budget alerts, newest first. Workspace credits and each agent’s monthly cap alert once per UTC month at 80% and at 100%; owners and admins are emailed and see them in Operator.' },
7
+ workspace_budgets_set: { schema: z.object({ ...workspace, agentCaps: z.array(z.object({ agentId: z.string().uuid(), monthlyCap: cap }).strict()).max(200).optional(), newAgentCap: cap.optional(), atLimit: z.enum(['pause', 'ask']).optional(), alert80: z.boolean().optional(), expectedVersion: z.number().int().min(0).optional() }).strict(), method: 'POST', path: a => `/workspaces/${a.workspaceId}/budgets`, body: ({ workspaceId, ...rest }) => rest, scoped: true, description: 'Change spend budgets. Send only the fields to change; null removes a cap. Admin only and only a person: an agent cannot raise its own cap. An agent at its cap gets budget_exceeded on hosted tool calls until the month resets or the cap is raised.' },
8
+ };
@@ -0,0 +1,34 @@
1
+ import { callWorkspace, configuration } from './workspaces.mjs';
2
+ const cap = (name, value) => {
3
+ if (value === 'none' || value === 'unlimited') return null;
4
+ if (!/^\d+$/.test(value ?? '')) throw Error(`--${name} needs a whole number of credits, or "none" for no cap.`);
5
+ return Number(value);
6
+ };
7
+ export async function budgetsCli(args) {
8
+ const flags = {}, positions = [];
9
+ for (let i = 0; i < args.length; i++) {
10
+ const arg = args[i];
11
+ if (arg === '--json') continue;
12
+ if (!arg.startsWith('--')) { positions.push(arg); continue; }
13
+ const key = arg.slice(2);
14
+ if (!['workspace', 'profile', 'agent', 'cap', 'new-agent-cap', 'at-limit', 'alert80', 'limit'].includes(key) || Object.hasOwn(flags, key) || i + 1 >= args.length) throw Error(`Invalid flag --${key}.`);
15
+ flags[key] = args[++i];
16
+ }
17
+ if (positions.length !== 2 || positions[0] !== 'budgets' || !['show', 'set', 'alerts'].includes(positions[1])) throw Error('Use budgets show|set|alerts --workspace ID. See audienti help.');
18
+ const input = { workspaceId: flags.workspace };
19
+ let name = 'workspace_budgets_get';
20
+ if (positions[1] === 'alerts') {
21
+ name = 'workspace_budget_alerts';
22
+ if (flags.limit !== undefined) { if (!/^\d+$/.test(flags.limit) || Number(flags.limit) < 1 || Number(flags.limit) > 200) throw Error('--limit is a whole number from 1 to 200.'); input.limit = Number(flags.limit); }
23
+ }
24
+ if (positions[1] === 'set') {
25
+ name = 'workspace_budgets_set';
26
+ if ((flags.agent === undefined) !== (flags.cap === undefined)) throw Error('--agent and --cap go together.');
27
+ if (flags.agent) input.agentCaps = [{ agentId: flags.agent, monthlyCap: cap('cap', flags.cap) }];
28
+ if (flags['new-agent-cap'] !== undefined) input.newAgentCap = cap('new-agent-cap', flags['new-agent-cap']);
29
+ if (flags['at-limit'] !== undefined) { if (!['pause', 'ask'].includes(flags['at-limit'])) throw Error('--at-limit is pause or ask.'); input.atLimit = flags['at-limit']; }
30
+ if (flags.alert80 !== undefined) { if (!['on', 'off'].includes(flags.alert80)) throw Error('--alert80 is on or off.'); input.alert80 = flags.alert80 === 'on'; }
31
+ if (Object.keys(input).length < 2) throw Error('Nothing to change. Use --agent ID --cap N, --new-agent-cap N, --at-limit pause|ask or --alert80 on|off.');
32
+ }
33
+ console.log(JSON.stringify(await callWorkspace(name, input, configuration(flags.profile))));
34
+ }