@audienti/cli 0.2.0-exora.22 → 0.2.0-exora.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/audienti.mjs CHANGED
@@ -81,6 +81,7 @@ Discovery: prospects list|get ID|timeline ID [--limit N]|observation create|cita
81
81
  usage breakdown [--workspace ID] [--since month|7d|30d|90d] [--by tool|provider|model|caller|day] [--format json|csv]
82
82
  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)
83
83
  intent list [--workspace ID] (the closed set of message labels: name, direction, event, channels, typed detail; output is JSON)
84
+ intent suggest (--text TEXT | --text-file PATH) --channel email|linkedin [--direction inbound|outbound] [--context TEXT] [--request-id UUID] [--workspace ID] (paid: what a message may mean, read by a model; prints the price per 1M input tokens first; suggests labels with a quote and reason, records nothing; keep one with intent record)
84
85
  intent record --person ID --evidence ID --label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] [--request-id UUID] [--participation ID] [--workspace ID] (what a stored message means; get EVIDENCE from prospects conversation append|list; --file LABELS.json for several labels or a typed detail such as a stop result or a meeting time; every label is checked and a refusal says what to fix; a message you sent that asks for a meeting takes --label meeting_requested on its outbound evidence)
85
86
  audit list [--workspace ID] [--since 7d] [--actor agent|person] [--category NAME] [--format json|csv]
86
87
  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%)
package/cli/intent.mjs CHANGED
@@ -1,9 +1,50 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { callWorkspace, configuration } from './workspaces.mjs';
3
+ import { readFileSync, statSync } from 'node:fs';
3
4
  import { readJsonFile } from './json-file.mjs';
5
+ import { printPaid } from './tools.mjs';
4
6
 
5
- const RECORD_FLAGS = ['workspace', 'profile', 'person', 'evidence', 'label', 'quote', 'reason', 'confidence', 'request-id', 'participation', 'file'];
6
- const USAGE = 'Use intent list [--workspace ID], or intent record --person ID --evidence ID (--label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] | --file LABELS.json) [--request-id UUID] [--participation ID] [--workspace ID].';
7
+ const RECORD_FLAGS = ['workspace', 'profile', 'person', 'evidence', 'label', 'quote', 'reason', 'confidence', 'request-id', 'participation', 'file', 'text', 'text-file', 'direction', 'channel', 'context'];
8
+ const SUGGEST_FLAGS = ['workspace', 'profile', 'text', 'text-file', 'direction', 'channel', 'context', 'request-id'];
9
+ const TEXT_MAX_BYTES = 16384;
10
+ const TEXT_MAX_CHARACTERS = 4000;
11
+ const CONTEXT_MAX_CHARACTERS = 2000;
12
+ const RECORD_ONLY_OK = ['workspace', 'profile', 'person', 'evidence', 'label', 'quote', 'reason', 'confidence', 'request-id', 'participation', 'file'];
13
+
14
+ function readText(path) {
15
+ let stat;
16
+ try { stat = statSync(path); } catch { throw Error(`Cannot read the message file ${path}. Check the path and try again.`); }
17
+ if (!stat.isFile() || stat.size > TEXT_MAX_BYTES) throw Error(`The message file must be a regular text file no larger than ${TEXT_MAX_BYTES / 1024} KiB: ${path}`);
18
+ try { return new TextDecoder('utf-8', { fatal: true }).decode(readFileSync(path)); } catch { throw Error(`The message file must be UTF-8 text: ${path}`); }
19
+ }
20
+ /** What the person should do after a call that did not give suggestions, in plain words. The JSON above it is for programs. */
21
+ const explain = requestId => result => result?.status === 'rejected'
22
+ ? 'Nothing was charged. The call was not accepted. If the reason above is insufficient_credits, add credits in the app under Settings, then Billing, and run the command again.'
23
+ : `The model did not answer, so there are no suggestions. The charge is not settled yet. Run the same command again with --request-id ${requestId} to check or retry: the same request ID is never charged twice.`;
24
+ const money = value => value >= 1 ? value.toFixed(2) : value.toPrecision(2);
25
+
26
+ /** Suggest labels for a message. Paid: shows the price per million input tokens first, then makes the call. Records nothing. */
27
+ async function suggest(flags) {
28
+ if (Object.keys(flags).some(key => !SUGGEST_FLAGS.includes(key))) throw Error('Option does not apply to intent suggest.');
29
+ if ((flags.text === undefined) === (flags['text-file'] === undefined)) throw Error(`Give the message once: --text TEXT or --text-file PATH. ${SUGGEST_USAGE}`);
30
+ if (!flags.channel) throw Error(`--channel is required (for example email or linkedin). ${SUGGEST_USAGE}`);
31
+ const direction = flags.direction ?? 'inbound';
32
+ if (!['inbound', 'outbound'].includes(direction)) throw Error(`--direction must be inbound (a message they sent) or outbound (one we sent). ${SUGGEST_USAGE}`);
33
+ const text = flags.text ?? readText(flags['text-file']);
34
+ if (!text.trim()) throw Error('The message has no words. Give the text of one message.');
35
+ if (Array.from(text).length > TEXT_MAX_CHARACTERS) throw Error(`The message is ${Array.from(text).length} characters; the most is ${TEXT_MAX_CHARACTERS}. Send only the new message, without the quoted thread.`);
36
+ if (flags.context !== undefined && Array.from(flags.context).length > CONTEXT_MAX_CHARACTERS) throw Error(`--context is too long; the most is ${CONTEXT_MAX_CHARACTERS} characters.`);
37
+ const config = configuration(flags.profile);
38
+ const entry = await callWorkspace('tools_get', { workspaceId: flags.workspace, tool: 'audienti.intent.label' }, config);
39
+ const price = entry?.data?.tokenPrice;
40
+ if (!price || typeof price.usd !== 'number') throw Error('Could not read the price of intent suggest. Nothing was charged.');
41
+ process.stderr.write(`Price: ${money(price.credits)} credits (about $${money(price.usd)}) per 1M input tokens. You pay for the tokens measured, so one short message costs a small part of a credit (for example, about ${money(price.credits * 3000 / 1e6)} credits for a 1,000-character message). Output is free. Nothing is recorded.\n`);
42
+ const requestId = flags['request-id'] ?? randomUUID();
43
+ if (!flags['request-id']) process.stderr.write(`request-id: ${requestId}\n`);
44
+ await printPaid('tools_intent_label', { workspaceId: flags.workspace, requestId, input: { text, direction, channel: flags.channel, ...(flags.context ? { context: flags.context } : {}) } }, { profile: flags.profile, explain: explain(requestId) });
45
+ }
46
+ const SUGGEST_USAGE = 'Use: intent suggest (--text TEXT | --text-file PATH) --channel email|linkedin [--direction inbound|outbound] [--context TEXT] [--request-id UUID] [--workspace ID].';
47
+ const USAGE = 'Use intent list [--workspace ID], intent suggest (--text TEXT | --text-file PATH) --channel email|linkedin [--direction inbound|outbound] [--context TEXT] [--request-id UUID] [--workspace ID], or intent record --person ID --evidence ID (--label NAME --quote TEXT --reason TEXT [--confidence high|medium|low] | --file LABELS.json) [--request-id UUID] [--participation ID] [--workspace ID].';
7
48
 
8
49
  export async function intentCli(args) {
9
50
  const flags = {}, positions = [];
@@ -16,12 +57,14 @@ export async function intentCli(args) {
16
57
  flags[key] = args[++i];
17
58
  }
18
59
  const command = positions[1];
19
- if (positions.length !== 2 || positions[0] !== 'intent' || !['list', 'record'].includes(command)) throw Error(USAGE);
60
+ if (positions.length !== 2 || positions[0] !== 'intent' || !['list', 'suggest', 'record'].includes(command)) throw Error(USAGE);
20
61
  if (command === 'list') {
21
62
  if (Object.keys(flags).some(key => !['workspace', 'profile'].includes(key))) throw Error('Option does not apply to intent list.');
22
63
  console.log(JSON.stringify(await callWorkspace('intent_labels_list', { workspaceId: flags.workspace }, configuration(flags.profile))));
23
64
  return;
24
65
  }
66
+ if (command === 'suggest') { await suggest(flags); return; }
67
+ if (Object.keys(flags).some(key => !RECORD_ONLY_OK.includes(key))) throw Error('Option does not apply to intent record.');
25
68
  if (!flags.person || !flags.evidence) throw Error(`--person ID and --evidence ID are required. ${USAGE}`);
26
69
  const single = ['label', 'quote', 'reason', 'confidence'].filter(key => flags[key] !== undefined);
27
70
  if (flags.file && single.length) throw Error('Give the labels in --file, or one label with --label, --quote and --reason. Not both.');
@@ -43,6 +43,7 @@ export const hostedToolOperations = {
43
43
  tools_proxy_sessions_create:{tool:'proxy.sessions.create',paid:true,limits:{maxSeconds:3600,concurrentWorkspaceConnections:32,meter:'upstream_tcp_payload_bytes'},schema:z.object({...workspace,requestId:uuid,input:z.object({ttlSeconds:z.number().int().min(60).max(3600).optional(),parentOperationId:uuid.optional()}).strict()}).strict(),method:'POST',path:a=>root(a)+'/proxy.sessions.create',body:a=>({requestId:a.requestId,input:a.input}),scoped:true,timeoutMs:30000},
44
44
  tools_proxy_sessions_close:{tool:'proxy.sessions.close',schema:z.object({...workspace,operationId:uuid}).strict(),method:'POST',path:a=>root(a)+'/proxy.sessions.close',body:a=>({operationId:a.operationId}),scoped:true},
45
45
  tools_decide:{tool:'decide',paid:true,schema:z.object({...workspace,requestId:uuid,input:z.object({state:z.union([z.string(),z.record(z.string(),z.unknown()),z.array(z.unknown())]),questions:z.record(key,question).refine(v=>Object.keys(v).length>=1&&Object.keys(v).length<=16)}).strict()}).strict(),method:'POST',path:a=>root(a)+'/decide',body:a=>({requestId:a.requestId,input:a.input}),scoped:true,timeoutMs:30000},
46
+ tools_intent_label:{tool:'intent.label',paid:true,limits:{textCharacters:4000,contextCharacters:2000,suggestions:2},description:'Suggest what a message means. Paid: built on decide, billed on the input tokens the provider measures at the decide price (read it first with tools_get audienti.intent.label: tokenPrice). Give the message text, direction (inbound or outbound) and channel, and optionally context. Returns up to two suggestions from the intent label list (one reading, one meeting label), each with a quote that is words of the message, a plain reason, a confidence and any typed detail still to add (needs). Records nothing and sends nothing: record a suggestion you keep with intent_record, which runs every check again. Reuse requestId after any interruption; a replay is free.',schema:z.object({...workspace,requestId:uuid,input:z.object({text:z.string().min(1).max(4000),direction:z.enum(['inbound','outbound']),channel:z.enum(['email','linkedin']),context:z.string().trim().min(1).max(2000).optional()}).strict()}).strict(),method:'POST',path:a=>root(a)+'/intent.label',body:a=>({requestId:a.requestId,input:a.input}),scoped:true,timeoutMs:30000},
46
47
  tools_profiles_fetch:{tool:'profiles.fetch',paid:true,schema:z.object({...workspace,requestId:uuid,input:z.object({url:z.string().min(1).max(1024),refresh:z.boolean().optional(),cached:z.boolean().optional(),fields:z.array(z.enum(['name','headline','about','location','experience','currentPositions','education','skills','websites','photo','connections','followers','certifications','languages','industries','employeeCount','employeeCountRange','founded','specialities','locations','companyType'])).min(1).max(20).optional()}).strict()}).strict(),method:'POST',path:a=>root(a)+'/profiles.fetch',body:a=>({requestId:a.requestId,input:a.input}),scoped:true,timeoutMs:30000},
47
48
  tools_runs_get:{schema:z.object({...workspace,operationId:uuid,attemptAfter:z.number().int().min(0).max(2147483647).optional(),entryAfter:uuid.optional()}).strict(),method:'GET',path:a=>root(a)+`/runs/${a.operationId}`+((a.attemptAfter!==undefined||a.entryAfter)?`?${new URLSearchParams({...a.attemptAfter!==undefined?{attemptAfter:String(a.attemptAfter)}:{},...a.entryAfter?{entryAfter:a.entryAfter}:{}})}`:''),scoped:true},
48
49
  tools_runs_list:{schema:z.object({...workspace,cursor:uuid.optional()}).strict(),method:'GET',path:a=>root(a)+'/runs'+(a.cursor?`?cursor=${encodeURIComponent(a.cursor)}`:''),scoped:true},
package/cli/tools.mjs CHANGED
@@ -54,12 +54,12 @@ async function readStdin() {
54
54
  try{return JSON.parse(new TextDecoder('utf-8',{fatal:true}).decode(Buffer.concat(chunks)));}catch{throw new Error('Tool input must be valid UTF-8 JSON.');}
55
55
  }
56
56
  const asJson=value=>{try{return JSON.parse(value);}catch{throw new Error('Tool input must be valid JSON.');}};
57
- async function printPaid(name,args,values){
57
+ export async function printPaid(name,args,values){
58
58
  try{
59
59
  const result=await callWorkspace(name,args,configuration(values.profile));
60
60
  const paidResult=paidToolResult(name,result?.data,args);
61
61
  if(['failed','uncertain','rejected'].includes(paidResult?.status)){
62
- process.stderr.write(JSON.stringify({error:{code:paidResult.error?.code??'tool_incomplete'},operationId:paidResult.operationId,requestId:args.requestId,billing:paidResult.billing,...(paidResult.result?.persistence?{result:paidResult.result}:{}),requestTraceId:result.requestTraceId,recovery:paidResult.status==='rejected'?'resolve_rejection_then_new_request_id':paidResult.result?.persistence?.recovery??'retry_same_request_id'})+'\n');process.exitCode=1;return;
62
+ process.stderr.write(JSON.stringify({error:{code:paidResult.error?.code??'tool_incomplete'},operationId:paidResult.operationId,requestId:args.requestId,billing:paidResult.billing,...(paidResult.result?.persistence?{result:paidResult.result}:{}),requestTraceId:result.requestTraceId,recovery:paidResult.status==='rejected'?'resolve_rejection_then_new_request_id':paidResult.result?.persistence?.recovery??'retry_same_request_id'})+'\n');if(values.explain)process.stderr.write(values.explain(paidResult)+'\n');process.exitCode=1;return;
63
63
  }
64
64
  console.log(JSON.stringify(result));
65
65
  }catch(error){
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.2.0-exora.22",
3
+ "version": "0.2.0-exora.23",
4
4
  "description": "Audienti CLI and MCP client for app.audienti.com",
5
5
  "type": "module",
6
6
  "bin": {
@@ -113,6 +113,12 @@ values and the `next` step. The checks:
113
113
  reply and at most one meeting label.
114
114
  - The typed detail is present: a `stop`, `bounce` or `referral` needs its `result`; `meeting_booked` needs `meeting`.
115
115
 
116
+ To get a second reading before you record, `audienti intent suggest --channel email --text "..."` asks a model
117
+ to suggest labels. It costs credits: the call prints the price per million input tokens first, and a message
118
+ uses a small part of that. It returns up to two suggestions, each with a `quote`, a `reason`, a `confidence` and
119
+ `needs` (the typed detail to add, for example the `scope` of a stop). It records nothing. Keep, change or drop
120
+ a suggestion, then record it with `intent record`, which runs every check above again.
121
+
116
122
  `replies set` records the same labels. Add `"intent": {"quote": "...", "reason": "..."}` next to `classification`
117
123
  or `meetingState` and it runs the same checks and stores the words on the event. Without `intent` it works as before.
118
124
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "https://github.com/omalab/exora",
3
- "sourceCommit": "d81f4cd72b9dedb857fc90c6178f1e704a56b366",
3
+ "sourceCommit": "5baa5f3af7b95f75d8e8f9d7467f85b98a1029f2",
4
4
  "files": {
5
5
  "LICENSE": "220f297cfcd5c38eb64e6b4b4a77cab4ebc7f4686d4d99c6260a475d02ca11e5",
6
6
  "README.md": "7ff32cc8ac89dd54b64d7b39c0b41b1f37e154b7ea10ad5a7bceefc3df77d87f",
@@ -16,7 +16,7 @@
16
16
  "cli/attachments.mjs": "2fee6b4abbf82da9847d4a927a3d4d3ca947a198a903a78927596a2ea858cbf4",
17
17
  "cli/audience-operations.mjs": "133b72dc6b9e3a6b4f173ce28f1524163f9c7b049505b885115f83b9e4af7170",
18
18
  "cli/audiences.mjs": "3c377346efc1a8bdde432a16c2d19a3f51f34f3333d344d3a2bfa94c188a017d",
19
- "cli/audienti.mjs": "8beaf1a8a052766a0478fdeda10de1ae0194e9bb095a8a1c937542adfaf0ede0",
19
+ "cli/audienti.mjs": "4e9e93e9a9ded80f5de70c9566f0699e8c16cf4887eb8614d13aff89aebe7b6c",
20
20
  "cli/audit-operations.mjs": "afaf87d592a1e5f6bff2a8649a6a64a8f471950b7d99e98995d9205aa5910398",
21
21
  "cli/audit.mjs": "a7becaed67c58b063e668b5e66844b6dc15c11a63b428faa171b542ad922c001",
22
22
  "cli/budget-operations.mjs": "d74916551ef393e29734c6e13cf9021251e0ed126eeaa529e18cf2332146e1e0",
@@ -37,7 +37,7 @@
37
37
  "cli/hand-task-operations.mjs": "473473fe95324c8c8733e037feab5ac468c07b0658fabd739cfd7d400a7faa83",
38
38
  "cli/hand-tasks.mjs": "2c895cf6b30c594a7e27cc4ec4b7eddeb39f941cd195b9255c6e0d6b4a56371f",
39
39
  "cli/intent-operations.mjs": "97b08e7474cf0fd8201650f08fa5ca5a2be2b0ddf5ea2079eb644e21aa2a0153",
40
- "cli/intent.mjs": "db0ccb85977fca26f865970dacd0538e81b070d7c0ec078f38358f33c458a96a",
40
+ "cli/intent.mjs": "5aa62593f65f58f2714bb402365624005ae17f73f65da3952b06f45e3c109450",
41
41
  "cli/json-file.mjs": "96431b18215834c2531f5901e2526875c634dd2e85a9465fcffc937fbec52150",
42
42
  "cli/kv-operations.mjs": "505d79d0d535d3441883f8a37817c30c73367216a35dddb07181f0f0e799df79",
43
43
  "cli/kv.mjs": "05783610001dd002af07a4bb8f26e64d74aa4981bbe38a2100552b810cb3bc72",
@@ -73,8 +73,8 @@
73
73
  "cli/skills.mjs": "ea8dfef32664714f9c63251d9a6bbdb44d77d41e272df46bb102ebfcd8df8b66",
74
74
  "cli/suppression-operations.mjs": "d1f827a785670fc6e44d87b18e4cfb6596b09e5be7c4f84c4b9a75ed13a962c6",
75
75
  "cli/suppression.mjs": "2b10be67e290deec9ed915139c3d98d45d7d10fd41abb67b5a35a630d636a4ed",
76
- "cli/tools-operations.mjs": "aafa52336c0bda8b05d4a091548c18d8634608d282d433aee45a744044b68770",
77
- "cli/tools.mjs": "6d9c7f27cc50e5e2fc0859c9c2d62bc3e77b5a46d0e2aeaa6cf31e2867138758",
76
+ "cli/tools-operations.mjs": "f64e58f75ef0c02b9317c6b6e07ccc7b9891464cf29824cee75f0fcae9026eb2",
77
+ "cli/tools.mjs": "16967f23a2b2460d28e766c827721a254e83d34b2df5195981cc9f899f6a5ed0",
78
78
  "cli/usage-operations.mjs": "64dd1ae850f52bb72c7648be78a8ab2dfab986afba2d2d2cd08034c4f7b3ace6",
79
79
  "cli/usage.mjs": "cd582c061f83ca4f220ddd3cd2a174a1a3a3c0ee308b3d5a5f47752a501e322e",
80
80
  "cli/work-execution-operations.mjs": "842d706b36bb035aa54e1dee8bf950f0619c495bcae84faa1926fbb05f6842bc",
@@ -84,7 +84,7 @@
84
84
  "cli/work-operations.mjs": "8b3a84ca15e8e4ad79ef92ced4c5adaffd5129f630e3bfc2c74a844a90867600",
85
85
  "cli/work.mjs": "751107ecc3de5a6be9db4a068cddc41f3ad8e77c87e59686eaf522862caf9e9f",
86
86
  "cli/workspaces.mjs": "eebe2c4bfc6ddedb2fb0dd551d5fa348eeda2fdf2105babd514b29a97f4d6225",
87
- "package.json": "c79712c719b810476b4e3d929d95d67437c5c1d532308fff93c8d4ad41f5fc55",
87
+ "package.json": "7083b89b9b44dd81b5d32685dd59728564d3c5193a1ef98bbbf0a5bd5ab9b606",
88
88
  "skills/agent-rooms/SKILL.md": "08f917ff85fee1d23499a4499056ce845b39ce6727029b104819dad41ee87cc2",
89
89
  "skills/agent-rooms/references/commands.md": "ef5afa5543ca9da967a147f638774647998ea1c1a2a821959f3bf87d54c292bd",
90
90
  "skills/audienti/SKILL.md": "8f2124065bfc3a005b44e194e7dcce9bbf14062ab332e7db66d441eeccb518ed",
@@ -100,7 +100,7 @@
100
100
  "skills/getting-started/references/capabilities.md": "982e3583a47ba4ac832efae2f6af3a4d8c0c18cace159c76b2a4ad1a8d8e910e",
101
101
  "skills/getting-started/references/mailbox-check.md": "113574bb5a573125620dcde4e65ebf44164f7b0727cc936fb41234f5d57bba73",
102
102
  "skills/getting-started/references/outcome-checks.md": "094d09d231b38d4935aa9eb8ac089e46ba8e698489852fa97040e6b5eac77a9b",
103
- "skills/getting-started/references/replies.md": "ec0f2f1fa4d2f13b3a6cac426baf6bd388b709313e3f97ea0dd546b950372cf7",
103
+ "skills/getting-started/references/replies.md": "acb5b77833cd9272ffeb05c27c24f9f2a8b1723da4e9a25cd6e8e6e666db2865",
104
104
  "skills/getting-started/references/reply-checks.md": "85fe692ad6b7bc73e204de313b27d225be3da1a0af71e8e7bb9945ae511a0dc3",
105
105
  "skills/playbook-design/SKILL.md": "56aa5692c1bbe3843f11819f0f9270988ddd0424b16f38db72a854864155407f",
106
106
  "skills/playbook-design/references/fields.md": "7af0c8a2ad7b2ee83ff006fe4707a90c3edacdb8ccf83c09fa17f692b5fa2c81"