@audienti/cli 0.2.0-exora.1 → 0.2.0-exora.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -6
- package/cli/account-reply-operations.mjs +26 -1
- package/cli/agents.mjs +74 -14
- package/cli/assignment-operations.mjs +12 -12
- package/cli/assignment.mjs +6 -6
- package/cli/attachment-operations.mjs +9 -0
- package/cli/attachments.mjs +28 -0
- package/cli/audience-operations.mjs +5 -0
- package/cli/audiences.mjs +7 -4
- package/cli/audienti.mjs +116 -21
- package/cli/audit-operations.mjs +17 -0
- package/cli/audit.mjs +39 -0
- package/cli/budget-operations.mjs +8 -0
- package/cli/budgets.mjs +34 -0
- package/cli/campaign-find.mjs +155 -0
- package/cli/campaign-operations.mjs +30 -0
- package/cli/campaigns.mjs +61 -0
- package/cli/cohort-operations.mjs +22 -0
- package/cli/cohorts.mjs +45 -0
- package/cli/connection-registry-operations.mjs +2 -1
- package/cli/content.mjs +1 -0
- package/cli/discovery-operations.mjs +4 -3
- package/cli/discovery.mjs +3 -2
- package/cli/file-operations.mjs +29 -0
- package/cli/files.mjs +14 -0
- package/cli/guardrails-operations.mjs +12 -0
- package/cli/guardrails.mjs +37 -0
- package/cli/hand-task-operations.mjs +11 -0
- package/cli/hand-tasks.mjs +18 -0
- package/cli/json-file.mjs +1 -1
- package/cli/kv-operations.mjs +17 -0
- package/cli/kv.mjs +58 -0
- package/cli/mcp.mjs +3 -3
- package/cli/notification-operations.mjs +8 -0
- package/cli/offer-operations.mjs +1 -1
- package/cli/outbound-operations.mjs +16 -0
- package/cli/outbound.mjs +54 -0
- package/cli/outcome-operations.mjs +28 -0
- package/cli/outcomes.mjs +16 -0
- package/cli/owner-account-client.mjs +3 -3
- package/cli/playbook-help.mjs +79 -0
- package/cli/playbook-operations.mjs +36 -0
- package/cli/progress.mjs +21 -7
- package/cli/question-operations.mjs +12 -0
- package/cli/questions.mjs +32 -0
- package/cli/queue-operations.mjs +12 -0
- package/cli/queue.mjs +25 -0
- package/cli/recipient-operations.mjs +1 -1
- package/cli/recipients.mjs +6 -2
- package/cli/rooms-operations.mjs +48 -0
- package/cli/rooms.mjs +160 -0
- package/cli/search-operations.mjs +9 -0
- package/cli/secret-operations.mjs +7 -0
- package/cli/secrets.mjs +30 -0
- package/cli/sender-account-operations.mjs +6 -2
- package/cli/sender-accounts.mjs +18 -8
- package/cli/skills-mcp.mjs +13 -6
- package/cli/skills.mjs +73 -16
- package/cli/tools-operations.mjs +18 -10
- package/cli/tools.mjs +48 -20
- package/cli/usage-operations.mjs +8 -0
- package/cli/usage.mjs +41 -0
- package/cli/work-execution-operations.mjs +7 -0
- package/cli/work-execution.mjs +3 -3
- package/cli/work-operations.mjs +7 -5
- package/cli/work.mjs +11 -6
- package/cli/workspaces.mjs +123 -24
- package/package.json +2 -2
- package/skills/agent-rooms/SKILL.md +140 -0
- package/skills/agent-rooms/references/commands.md +78 -0
- package/skills/audienti/SKILL.md +112 -6
- package/skills/audienti/references/helpers.md +15 -2
- package/skills/audienti/references/profile-workflows.md +6 -4
- package/skills/audienti/scripts/contracts.mjs +1 -1
- package/skills/getting-started/SKILL.md +227 -0
- package/skills/getting-started/references/audience.md +143 -0
- package/skills/getting-started/references/campaign-draft.md +80 -0
- package/skills/getting-started/references/capabilities.md +238 -0
- package/skills/getting-started/references/mailbox-check.md +64 -0
- package/skills/getting-started/references/outcome-checks.md +125 -0
- package/skills/getting-started/references/replies.md +186 -0
- package/skills/getting-started/references/reply-checks.md +54 -0
- package/skills/playbook-design/SKILL.md +136 -0
- package/skills/playbook-design/references/fields.md +146 -0
- package/source-manifest.json +86 -39
- package/cli/approach-operations.mjs +0 -12
- package/cli/experiment-operations.mjs +0 -25
- package/cli/experiments.mjs +0 -26
package/cli/tools.mjs
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { hostedToolOperations, paidToolByName, isPaidToolOperation, paidToolResult } from './tools-operations.mjs';
|
|
3
3
|
import { parseArgs } from 'node:util';
|
|
4
4
|
import { readJsonFile } from './json-file.mjs';
|
|
5
5
|
import { callWorkspace, configuration } from './workspaces.mjs';
|
|
6
6
|
const help = `Usage:
|
|
7
|
-
audienti
|
|
8
|
-
audienti tools
|
|
9
|
-
audienti tools
|
|
10
|
-
audienti tools
|
|
7
|
+
audienti call TOOL ['JSON_INPUT'] [--input FILE|-] [--request-id UUID] [--workspace UUID] [--json]
|
|
8
|
+
audienti tools search "WORDS" [--limit N] [--workspace UUID] one server search over tools, audienti commands and skills; rows: id, kind, description, price
|
|
9
|
+
audienti tools get TOOL [--workspace UUID] one tool: inputs, price, availability, your 30-day success rate and speed
|
|
10
|
+
audienti tools list [--workspace UUID] [--json] the whole catalog
|
|
11
11
|
audienti tools runs get UUID [--attempt-after ORDINAL] [--entry-after UUID] | list [--cursor UUID]
|
|
12
12
|
audienti tools balance
|
|
13
|
+
audienti tools NAME --input FILE|- --request-id UUID [--workspace UUID] [--json] same as call NAME; kept for existing scripts
|
|
14
|
+
audienti tools proxy.sessions.close UUID [--workspace UUID]
|
|
15
|
+
|
|
16
|
+
The server catalog is the only source of tool names, inputs and prices: run tools search or tools get, then call.
|
|
17
|
+
call sends {tool,requestId,input} to one route for every tool. Published ids look like audienti.email.verify;
|
|
18
|
+
an old name (for example profiles.enrich) resolves on the server. A tool with no configured provider is not listed or callable.
|
|
19
|
+
Record tools (profile read and enrich, email and mobile find, email.verify, web.search) reuse a saved record when one is
|
|
20
|
+
current; put "cached":false in the input to skip it, make a fresh paid call and save the result.
|
|
21
|
+
Without --request-id, call generates one and prints it to stderr. Save it: replaying the same ID is free and
|
|
22
|
+
returns the saved result; a new ID is a new purchase.
|
|
13
23
|
|
|
14
24
|
email.find and mobile.find may return running: poll tools runs get OPERATION_ID.
|
|
15
25
|
Keep the same request ID after interruptions; never submit a new one to retry a purchase.
|
|
@@ -23,12 +33,12 @@ profiles.fetch accepts a person /in/ or company /company/ URL as {url,fields?,re
|
|
|
23
33
|
Explicit refresh requires paid admission; the result includes saved profile/prospect IDs.
|
|
24
34
|
Fields: name, headline, about, location, experience, currentPositions, education, skills,
|
|
25
35
|
websites, photo, connections, followers, certifications, languages. Company-specific fields:
|
|
26
|
-
industries, employeeCount, employeeCountRange, founded, specialities, locations.
|
|
36
|
+
industries, employeeCount, employeeCountRange, founded, specialities, locations, companyType.
|
|
27
37
|
profiles.enrich accepts the same fields as profiles.fetch, using url or saved profileId.
|
|
28
38
|
Its operation has the same admission identity as profiles.fetch; the Prospect projection is a current free read.
|
|
29
39
|
profiles.discover is the clear external-search alias of profiles.search; profiles list searches saved workspace records.
|
|
30
40
|
profiles.search accepts person filters or {kind:"company",query,companySizes?}; results are partial profiles.
|
|
31
|
-
Use
|
|
41
|
+
Use tools get TOOL for exact fields. No implicit email lookup.
|
|
32
42
|
decide accepts {state,questions}; question types: boolean, choice, score.
|
|
33
43
|
Questions have instructions; choice requires named criteria; score criteria are ordered
|
|
34
44
|
from level zero. State is text, an object or an array. At most 32 KiB, 16 questions,
|
|
@@ -43,13 +53,41 @@ async function readStdin() {
|
|
|
43
53
|
for await(const chunk of process.stdin){size+=chunk.length;if(size>32768)throw new Error('Tool input exceeds 32 KiB.');chunks.push(chunk);}
|
|
44
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.');}
|
|
45
55
|
}
|
|
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){
|
|
58
|
+
try{
|
|
59
|
+
const result=await callWorkspace(name,args,configuration(values.profile));
|
|
60
|
+
const paidResult=paidToolResult(name,result?.data,args);
|
|
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;
|
|
63
|
+
}
|
|
64
|
+
console.log(JSON.stringify(result));
|
|
65
|
+
}catch(error){
|
|
66
|
+
if(isPaidToolOperation(name,args))process.stderr.write(`Retain request ID ${args.requestId}; retry with the same ID to inspect/recover this operation.\n`);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const parseOptions={help:{type:'boolean',short:'h'},json:{type:'boolean'},input:{type:'string'},'request-id':{type:'string'},workspace:{type:'string'},profile:{type:'string'},cursor:{type:'string'},limit:{type:'string'},'attempt-after':{type:'string'},'entry-after':{type:'string'}};
|
|
71
|
+
/** audienti call TOOL ['JSON'] : one generic route for every catalog tool. */
|
|
72
|
+
export async function callCli(argv) {
|
|
73
|
+
const {positionals,values}=parseArgs({args:argv.slice(1),allowPositionals:true,strict:true,options:parseOptions});
|
|
74
|
+
if(values.help||positionals.length===0){console.log(help);return;}
|
|
75
|
+
const [tool,inline]=positionals;
|
|
76
|
+
if(positionals.length>2||(inline!==undefined&&values.input))throw new Error('Give the input once: a JSON argument or --input FILE|-.');
|
|
77
|
+
const input=inline!==undefined?asJson(inline):values.input?(values.input==='-'?await readStdin():readJsonFile(values.input,32768,'Tool input')):{};
|
|
78
|
+
if(!input||typeof input!=='object'||Array.isArray(input))throw new Error('Tool input must be a JSON object.');
|
|
79
|
+
const requestId=values['request-id']??randomUUID();
|
|
80
|
+
if(!values['request-id'])process.stderr.write(`request-id: ${requestId}\n`);
|
|
81
|
+
await printPaid('tools_call',{workspaceId:values.workspace,tool,requestId,input},values);
|
|
82
|
+
}
|
|
46
83
|
export async function toolsCli(argv) {
|
|
47
|
-
const {positionals,values}=parseArgs({args:argv.slice(1),allowPositionals:true,strict:true,options:
|
|
84
|
+
const {positionals,values}=parseArgs({args:argv.slice(1),allowPositionals:true,strict:true,options:parseOptions});
|
|
48
85
|
if(values.help||positionals.length===0){console.log(help);return;}
|
|
49
86
|
const [verb,sub,id]=positionals;
|
|
50
|
-
if(verb==='describe'&&Object.hasOwn(paidToolByName,sub)&&positionals.length===2){console.log(JSON.stringify({name:sub,version:'1',canonicalTool:paidToolByName[sub].canonicalTool??sub,responseOperationPath:paidToolByName[sub].resultKey??null,inputSchema:z.toJSONSchema(paidToolByName[sub].schema.shape.input),limits:paidToolByName[sub].limits??(sub==='decide'?{bytes:32768,depth:32,questions:16,choices:32,scoreLevels:10}:sub==='profiles.fetch'?{providerCalls:1,normalizedBytes:65536,cacheDays:30}:{providerCalls:1,resultCache:false}),retry:'Reuse the same requestId and authenticated principal after interruptions.'},null,values.json?undefined:2));return;}
|
|
51
87
|
let name,args={workspaceId:values.workspace};
|
|
52
88
|
if(verb==='list'&&positionals.length===1)name='tools_list';
|
|
89
|
+
else if(verb==='search'&&positionals.length>=2){name='tools_search';args.query=positionals.slice(1).join(' ');if(values.limit!==undefined)args.limit=Number(values.limit);}
|
|
90
|
+
else if((verb==='get'||verb==='describe')&&positionals.length===2){name='tools_get';args.tool=sub;}
|
|
53
91
|
else if(verb==='balance'&&positionals.length===1)name='tools_balance';
|
|
54
92
|
else if(verb==='runs'&&sub==='get'&&positionals.length===3){name='tools_runs_get';args.operationId=id;args.entryAfter=values['entry-after'];if(values['attempt-after']!==undefined)args.attemptAfter=Number(values['attempt-after']);}
|
|
55
93
|
else if(verb==='runs'&&sub==='list'&&positionals.length===2){name='tools_runs_list';args.cursor=values.cursor;}
|
|
@@ -58,15 +96,5 @@ export async function toolsCli(argv) {
|
|
|
58
96
|
if(!values.input||!values['request-id'])throw new Error(`${verb} requires --input FILE|- and --request-id UUID. Keep the request ID for retries.`);
|
|
59
97
|
name=paidToolByName[verb].name;args.requestId=values['request-id'];args.input=values.input==='-'?await readStdin():readJsonFile(values.input,32768,'Tool input');
|
|
60
98
|
}else throw new Error('Unknown tools command. Use audienti tools --help.');
|
|
61
|
-
|
|
62
|
-
const result=await callWorkspace(name,args,configuration(values.profile));
|
|
63
|
-
const paidResult = paidToolResult(name,result?.data);
|
|
64
|
-
if(['failed','uncertain','rejected'].includes(paidResult?.status)){
|
|
65
|
-
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;
|
|
66
|
-
}
|
|
67
|
-
console.log(JSON.stringify(result));
|
|
68
|
-
}catch(error){
|
|
69
|
-
if(isPaidToolOperation(name))process.stderr.write(`Retain request ID ${args.requestId}; retry with the same ID to inspect/recover this operation.\n`);
|
|
70
|
-
throw error;
|
|
71
|
-
}
|
|
99
|
+
await printPaid(name,args,values);
|
|
72
100
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const usageOperations = {
|
|
3
|
+
workspace_usage_breakdown: {
|
|
4
|
+
schema: z.object({ workspaceId: z.string().uuid().optional(), period: z.enum(['month', '7d', '30d', '90d']).optional() }).strict(),
|
|
5
|
+
method: 'GET', path: a => `/workspaces/${a.workspaceId}/usage/breakdown${a.period ? `?period=${a.period}` : ''}`, scoped: true,
|
|
6
|
+
description: 'Credit cost for a period (default this month) by tool, provider, model and caller, with a zero-filled daily series and share of total. Amounts are pico USD (1 credit = 10,000,000,000). Model is recorded only for AI decision calls. Same figures as Settings > Usage.',
|
|
7
|
+
},
|
|
8
|
+
};
|
package/cli/usage.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { callWorkspace, configuration } from './workspaces.mjs';
|
|
2
|
+
|
|
3
|
+
const PERIODS = { month: 'month', '7d': '7d', '30d': '30d', '90d': '90d' };
|
|
4
|
+
const DIMENSIONS = ['tool', 'provider', 'model', 'caller', 'day'];
|
|
5
|
+
const PICO_PER_CREDIT = 10_000_000_000n;
|
|
6
|
+
const cell = value => { const text = String(value); return /[",\n\r]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text; };
|
|
7
|
+
export function credits(amount) {
|
|
8
|
+
const value = BigInt(amount), whole = value / PICO_PER_CREDIT, fraction = (value % PICO_PER_CREDIT).toString().padStart(10, '0').replace(/0+$/, '');
|
|
9
|
+
return fraction ? `${whole}.${fraction}` : String(whole);
|
|
10
|
+
}
|
|
11
|
+
const percent = share => `${(share * 100).toFixed(1)}%`;
|
|
12
|
+
/** Rows for one dimension: a label, calls, credits and share of the period total. */
|
|
13
|
+
export function breakdownRows(data, by) {
|
|
14
|
+
if (by === 'day') return (data.daily ?? []).map(row => [row.day, row.calls, credits(row.charged), '']);
|
|
15
|
+
const items = { tool: data.byTool, provider: data.byProvider, model: data.byModel, caller: data.byCaller }[by] ?? [];
|
|
16
|
+
const name = row => by === 'tool' ? row.tool : by === 'provider' ? row.provider : by === 'model' ? row.model ?? 'Not recorded' : [row.owner, row.keyName, row.agentName].filter(Boolean).join(' / ');
|
|
17
|
+
return items.map(row => [name(row), row.calls, credits(row.charged), percent(row.share)]);
|
|
18
|
+
}
|
|
19
|
+
export function breakdownCsv(data, by) {
|
|
20
|
+
return [[by, 'calls', 'credits', 'share'], ...breakdownRows(data, by)].map(row => row.map(cell).join(',')).join('\n') + '\n';
|
|
21
|
+
}
|
|
22
|
+
export async function usageCli(args) {
|
|
23
|
+
const flags = {}, positions = [];
|
|
24
|
+
for (let i = 0; i < args.length; i++) {
|
|
25
|
+
const arg = args[i];
|
|
26
|
+
if (arg === '--json') continue;
|
|
27
|
+
if (!arg.startsWith('--')) { positions.push(arg); continue; }
|
|
28
|
+
const key = arg.slice(2);
|
|
29
|
+
if (!['workspace', 'profile', 'since', 'by', 'format'].includes(key) || Object.hasOwn(flags, key) || i + 1 >= args.length) throw Error(`Invalid flag --${key}.`);
|
|
30
|
+
flags[key] = args[++i];
|
|
31
|
+
}
|
|
32
|
+
if (positions.length !== 2 || positions[1] !== 'breakdown') throw Error('Use usage breakdown [--workspace ID] [--since month|7d|30d|90d] [--by tool|provider|model|caller|day] [--format json|csv].');
|
|
33
|
+
if (flags.since && !PERIODS[flags.since]) throw Error('--since must be month, 7d, 30d or 90d.');
|
|
34
|
+
if (flags.by && !DIMENSIONS.includes(flags.by)) throw Error('--by must be tool, provider, model, caller or day.');
|
|
35
|
+
if (flags.format && !['json', 'csv'].includes(flags.format)) throw Error('--format must be json or csv.');
|
|
36
|
+
const input = { workspaceId: flags.workspace };
|
|
37
|
+
if (flags.since) input.period = PERIODS[flags.since];
|
|
38
|
+
const result = await callWorkspace('workspace_usage_breakdown', input, configuration(flags.profile));
|
|
39
|
+
if (flags.format === 'csv') { process.stdout.write(breakdownCsv(result?.data ?? result, flags.by ?? 'tool')); return; }
|
|
40
|
+
console.log(JSON.stringify(result));
|
|
41
|
+
}
|
|
@@ -25,8 +25,15 @@ const body=({workspaceId,planId,clientActionId,attemptId,...rest})=>rest;
|
|
|
25
25
|
export const workspaceExecutionOperations={
|
|
26
26
|
workspace_work_claim:{schema:z.object({...location,expectedRevision:z.number().int().positive(),requestId:uuid}).strict(),scoped:true,method:'POST',path:a=>`${path(a)}/claims`,body},
|
|
27
27
|
workspace_work_start:{schema:z.object({...location,claimId:uuid,requestId:uuid}).strict(),scoped:true,method:'POST',path:a=>`${path(a)}/start`,body},
|
|
28
|
+
workspace_work_send:{schema:z.object({...location,claimId:uuid,requestId:uuid}).strict(),scoped:true,method:'POST',path:a=>`${path(a)}/send`,body,description:'LinkedIn accounts connected through Audienti only. Starts the claimed, approved action and sends it once through the managed account, then records the result. Replaying the same claim returns the recorded send; it never sends again.'},
|
|
28
29
|
workspace_work_attempt_get:{schema:z.object({...location,historyCursor:uuid.optional(),historyLimit:z.number().int().min(1).max(100).optional()}).strict(),scoped:true,method:'GET',path:a=>{const q=new URLSearchParams();for(const key of ['historyCursor','historyLimit'])if(a[key]!==undefined)q.set(key,String(a[key]));return `${path(a)}/attempt?${q}`;}},
|
|
29
30
|
};
|
|
31
|
+
// LinkedIn accounts connected through Audienti. Only the user connects one; agents list them and send through work send.
|
|
32
|
+
export const managedAccountOperations={
|
|
33
|
+
workspace_managed_accounts_list:{schema:z.object({workspaceId:uuid.optional()}).strict(),scoped:true,method:'GET',path:a=>`/workspaces/${a.workspaceId}/managed-accounts`,description:'List the LinkedIn accounts connected through Audienti in this workspace, with their sending account IDs and status.'},
|
|
34
|
+
workspace_managed_account_plan_set:{schema:z.object({workspaceId:uuid.optional(),managedAccountId:uuid,plan:z.enum(['free','premium_career','premium_business','premium_all_in_one','sales_navigator_core','sales_navigator_advanced','recruiter_lite']).nullable()}).strict(),scoped:true,method:'PUT',path:a=>`/workspaces/${a.workspaceId}/managed-accounts/${a.managedAccountId}/plan`,body:a=>({plan:a.plan}),description:'The user only, not agents. States the LinkedIn plan of a managed account, which sets how long a connection note may be. Pass plan null to clear it; the safer figures then apply. Returns the account.'},
|
|
35
|
+
workspace_managed_account_connect:{schema:z.object({workspaceId:uuid.optional()}).strict(),scoped:true,method:'POST',path:a=>`/workspaces/${a.workspaceId}/managed-accounts/connect`,body:()=>({}),description:'The user only, not agents. Returns a sign-in link, valid 15 minutes, where the user connects a LinkedIn account through Audienti.'},
|
|
36
|
+
};
|
|
30
37
|
export const narrowExecutionOperations={
|
|
31
38
|
work_claim_release:{schema:z.object({claimId:uuid,requestId:uuid,reason}).strict(),method:'POST',path:a=>`/work-execution/claims/${a.claimId}/release`,body:({claimId,...rest})=>rest},
|
|
32
39
|
work_attempt_lookup:{schema:z.object({claimId:uuid}).strict(),method:'GET',path:a=>`/work-execution/claims/${a.claimId}/attempt`},
|
package/cli/work-execution.mjs
CHANGED
|
@@ -12,9 +12,9 @@ export async function workExecutionCli(args) {
|
|
|
12
12
|
const arg=args[i];if(arg==='--json')continue;if(!arg.startsWith('--')){positions.push(arg);continue;}
|
|
13
13
|
const key=arg.slice(2);if(!['profile','workspace','action','claim','attempt','file','cursor','limit'].includes(key)||Object.hasOwn(flags,key)||!args[i+1]||args[i+1].startsWith('--'))throw Error('Unknown, repeated or incomplete execution option.');flags[key]=args[++i];
|
|
14
14
|
}
|
|
15
|
-
const command=positions[2],workspace=['claim','start','get'].includes(command),read=['get','lookup'].includes(command);
|
|
16
|
-
const names={claim:'workspace_work_claim',start:'workspace_work_start',get:'workspace_work_attempt_get',release:'work_claim_release',lookup:'work_attempt_lookup',report:'work_result_report'};
|
|
17
|
-
if(!names[command]||positions.length!==(workspace?4:3))throw Error('Use work execution claim|start|get PLAN, release|lookup --claim ID, or report --attempt ID.');
|
|
15
|
+
const command=positions[2],workspace=['claim','start','send','get'].includes(command),read=['get','lookup'].includes(command);
|
|
16
|
+
const names={claim:'workspace_work_claim',start:'workspace_work_start',send:'workspace_work_send',get:'workspace_work_attempt_get',release:'work_claim_release',lookup:'work_attempt_lookup',report:'work_result_report'};
|
|
17
|
+
if(!names[command]||positions.length!==(workspace?4:3))throw Error('Use work execution claim|start|send|get PLAN, release|lookup --claim ID, or report --attempt ID.');
|
|
18
18
|
const allowed=['profile',...(workspace?['workspace','action']:command==='report'?['attempt']:['claim']),...(read?command==='get'?['cursor','limit']:[]:['file'])];
|
|
19
19
|
if(Object.keys(flags).some(key=>!allowed.includes(key)))throw Error('Option does not apply to this execution command.');
|
|
20
20
|
if(workspace&&!flags.action)throw Error('This command requires --action.');
|
package/cli/work-operations.mjs
CHANGED
|
@@ -5,10 +5,10 @@ const page = z.object({cursor: uuid.optional(), limit: z.number().int().min(1).m
|
|
|
5
5
|
const clientId = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/);
|
|
6
6
|
const accountRef = z.string().min(1).max(200).regex(/^[A-Za-z0-9@._:+\/-]+$/);
|
|
7
7
|
const evidence = z.array(uuid).max(100);
|
|
8
|
-
const commonAction = {clientActionId: clientId, reason: z.string().min(1), evidenceIds: evidence, replyToEvidenceId: uuid.optional()};
|
|
8
|
+
const commonAction = {clientActionId: clientId, capability: z.enum(['profile.connection.request','profile.message.send']).optional().describe('The owner action: profile.connection.request or profile.message.send on linkedin.send, profile.message.send on email.send. The sending account\'s mode for it decides whether approval is needed or the action is not allowed. Without it the old per-channel automation switch applies, unless a mode restricts it.'), reason: z.string().min(1), evidenceIds: evidence, replyToEvidenceId: uuid.optional(), playbookStepKey: uuid.optional().describe("The playbook step this action runs; a succeeded result starts that step's wait. Saving the plan checks it: the key must name a step of the playbook pinned to the person's active participation in the plan's Campaign, and the step's channel must match the action type (email.send only with an Email step, linkedin.send only with a LinkedIn step); otherwise the save is refused with invalid_fields.")};
|
|
9
9
|
const email = z.object({...commonAction, type:z.literal('email.send'), content:z.object({subject:z.string(),body:z.string()}).strict(),recipient:z.object({prospectId:uuid,email:z.string(),contactId:uuid.optional()}).strict(),sender:z.object({provider:z.literal('email'),accountRef,senderAccountId:uuid.optional()}).strict()}).strict();
|
|
10
|
-
const linkedin = z.object({...commonAction,type:z.literal('linkedin.send'),content:z.object({body:z.string()}).strict(),recipient:z.object({prospectId:uuid,profileUrl:z.string(),contactId:uuid.optional()}).strict(),sender:z.object({provider:z.literal('linkedin'),accountRef,senderAccountId:uuid.optional()}).strict()}).strict();
|
|
11
|
-
const cadence = z.object({clientActionId:clientId,afterClientActionId:clientId.nullable(),trigger:z.literal('reported_success').nullable(),at:z.string().nullable(),waitSeconds:z.number().int().min(0).max(7776000).nullable(),condition:z.literal('no_pending_reply').nullable()}).strict();
|
|
10
|
+
const linkedin = z.object({...commonAction,type:z.literal('linkedin.send'),content:z.object({body:z.string(),attachments:z.array(z.object({id:uuid}).strict()).min(1).max(1).optional()}).strict(),recipient:z.object({prospectId:uuid,profileUrl:z.string(),contactId:uuid.optional()}).strict(),sender:z.object({provider:z.literal('linkedin'),accountRef,senderAccountId:uuid.optional()}).strict()}).strict();
|
|
11
|
+
const cadence = z.object({notBefore:z.string().datetime().optional(),clientActionId:clientId,afterClientActionId:clientId.nullable(),trigger:z.literal('reported_success').nullable(),at:z.string().nullable(),waitSeconds:z.number().int().min(0).max(7776000).nullable(),condition:z.literal('no_pending_reply').nullable()}).strict();
|
|
12
12
|
const located = {...workspace,planId:uuid,clientActionId:clientId};
|
|
13
13
|
const mutation = {...located,expectedRevision:z.number().int().positive(),requestId:uuid};
|
|
14
14
|
const root = a => `/workspaces/${a.workspaceId}`;
|
|
@@ -18,11 +18,13 @@ const omit = (a, keys) => Object.fromEntries(Object.entries(a).filter(([k])=>!ke
|
|
|
18
18
|
const body = a => omit(a,['workspaceId','planId','clientActionId']);
|
|
19
19
|
const qs = values => { const p=new URLSearchParams(); for(const [k,v] of Object.entries(values)) if(v!==undefined)p.set(k,String(v));return p.toString(); };
|
|
20
20
|
export const workOperations = {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
workspace_work_activity_list: {description:'Read recorded workspace Activity, newest first: actor, fact, person, time and recorded status. Human tokens require workspace:access; connected agents require workspace:read and workspace policy access, acting as their user. Stage decisions follow Admin/current-assignee visibility. Fixed pages of 50; reuse search and both cursor fields. Read only, no decision or inferred send success.',schema:z.object({...workspace,search:z.string().trim().max(120).optional(),cursor:z.object({at:z.string().datetime(),id:z.string().max(160)}).strict().optional()}).strict(),method:'GET',path:a=>`${root(a)}/operator/activity?${qs({search:a.search,cursorAt:a.cursor?.at,cursorId:a.cursor?.id})}`,scoped:true},
|
|
22
|
+
workspace_work_action_plan_save: {schema:z.object({...workspace,planId:uuid.nullable(),prospectId:uuid,campaignId:uuid.nullable(),campaignRevision:z.number().int().positive().nullable(),expectedRevision:version,requestId:uuid,origin:z.discriminatedUnion('kind',[z.object({kind:z.literal('agent'),skill:z.object({id:z.string(),version:z.string()}).strict()}).strict(),z.object({kind:z.literal('human'),editReason:z.string()}).strict()]),rationale:z.string(),evidenceIds:evidence,actions:z.array(z.discriminatedUnion('type',[email,linkedin])).min(1).max(50),cadence:z.array(cadence).max(50)}).strict(), method:'POST',path:a=>`${root(a)}/action-plans`,body:a=>omit(a,['workspaceId']),scoped:true},
|
|
23
|
+
workspace_work_operator_list: {description:'Read workspace proposals. order=priority matches the Operator queue: open replies first, then oldest proposals, with head-page totals. Omit order for legacy oldest-first order. Keep order and filters unchanged while paging. Read only; grants no execution authority.',schema:z.object({...workspace,order:z.enum(['oldest','priority']).optional(),page:page.optional(),filter:z.object({state:z.enum(['open','cancelled','terminal','invalidated']).optional(),reviewStatus:z.enum(['awaiting_revision','invalidated','pending_review','approved','in_progress','reconciliation_required','completed','skipped','cancelled']).optional(),search:z.string().optional(),queue:z.enum(['review','blocked','activity']).optional()}).strict().optional()}).strict(),method:'GET',path:a=>`${root(a)}/operator/work?${qs({...a.page,...a.filter,order:a.order})}`,scoped:true},
|
|
23
24
|
workspace_work_operator_get: {schema:z.object({...workspace,planId:uuid,revision:z.number().int().positive().optional(),history:page.optional()}).strict(),method:'GET',path:a=>`${plan(a)}?${qs({revision:a.revision,historyCursor:a.history?.cursor,historyLimit:a.history?.limit})}`,scoped:true},
|
|
24
25
|
workspace_work_action_approve: {schema:z.object({...mutation,expectedApprovalVersion:version}).strict(),method:'POST',path:a=>`${action(a)}/approve`,body,scoped:true},
|
|
25
26
|
workspace_work_action_withdraw: {schema:z.object({...mutation,expectedApprovalVersion:z.number().int().positive(),reason:z.string().min(1)}).strict(),method:'POST',path:a=>`${action(a)}/withdraw`,body,scoped:true},
|
|
26
27
|
workspace_work_action_cancel: {schema:z.object({...mutation,reason:z.string().min(1)}).strict(),method:'POST',path:a=>`${action(a)}/cancel`,body,scoped:true},
|
|
28
|
+
workspace_work_action_skip: {description:'Human only. Skip one pending touch without sending it; the plan continues with its next step. When no touch is left the plan closes and the person stays in the campaign for the next playbook step. Only skipping the playbook\'s last step for the person ends their campaign, with the note skipped by owner; a skipped reply proposal never does, and the reply stays unanswered.',schema:z.object(mutation).strict(),method:'POST',path:a=>`${action(a)}/skip`,body,scoped:true},
|
|
27
29
|
workspace_work_eligibility_get: {schema:z.object(located).strict(),method:'GET',path:a=>`${action(a)}/eligibility`,scoped:true},
|
|
28
30
|
};
|
package/cli/work.mjs
CHANGED
|
@@ -2,25 +2,30 @@ import { callWorkspace, configuration } from './workspaces.mjs';
|
|
|
2
2
|
import { readJsonFile } from './json-file.mjs';
|
|
3
3
|
export async function workCli(args) {
|
|
4
4
|
const flags={}, position=[];
|
|
5
|
-
const allowed=['profile','workspace','file','request-id','plan-id','client-action-id','cursor','limit','history-cursor','history-limit','revision','state','review-status','queue','search'];
|
|
5
|
+
const allowed=['profile','workspace','file','request-id','plan-id','client-action-id','cursor','limit','history-cursor','history-limit','revision','state','review-status','queue','search','order','cursor-at','cursor-id'];
|
|
6
6
|
for(let i=0;i<args.length;i++) {
|
|
7
7
|
if(args[i]==='--json')continue;
|
|
8
8
|
if(!args[i].startsWith('--')){position.push(args[i]);continue;}
|
|
9
9
|
const key=args[i].slice(2);if(!allowed.includes(key)||Object.hasOwn(flags,key)||args[i+1]===undefined||args[i+1].startsWith('--'))throw new Error('Unknown, repeated, or incomplete work option.');flags[key]=args[++i];
|
|
10
10
|
}
|
|
11
|
-
const names={'work plan save':'workspace_work_action_plan_save','work list':'workspace_work_operator_list','work show':'workspace_work_operator_get','work approve':'workspace_work_action_approve','work withdraw-approval':'workspace_work_action_withdraw','work cancel':'workspace_work_action_cancel','work eligibility':'workspace_work_eligibility_get'};
|
|
12
|
-
const name=names[position.join(' ')];if(!name)throw new Error('Use work plan save, list, show, approve, withdraw-approval, cancel, or eligibility. Writes require --file INPUT.json.');
|
|
13
|
-
const read=['workspace_work_operator_list','workspace_work_operator_get','workspace_work_eligibility_get'].includes(name);
|
|
11
|
+
const names={'work activity':'workspace_work_activity_list','work plan save':'workspace_work_action_plan_save','work list':'workspace_work_operator_list','work show':'workspace_work_operator_get','work approve':'workspace_work_action_approve','work withdraw-approval':'workspace_work_action_withdraw','work cancel':'workspace_work_action_cancel','work skip':'workspace_work_action_skip','work eligibility':'workspace_work_eligibility_get'};
|
|
12
|
+
const name=names[position.join(' ')];if(!name)throw new Error('Use work plan save, list, activity, show, approve, withdraw-approval, cancel, skip, or eligibility. Writes require --file INPUT.json.');
|
|
13
|
+
const read=['workspace_work_activity_list','workspace_work_operator_list','workspace_work_operator_get','workspace_work_eligibility_get'].includes(name);
|
|
14
14
|
const input=read?{}:readJsonFile(flags.file,65536,'Work input');
|
|
15
15
|
for(const [flag,key] of [['workspace','workspaceId'],['request-id','requestId'],['plan-id','planId'],['client-action-id','clientActionId']])if(flags[flag]!==undefined)input[key]=flags[flag];
|
|
16
|
-
if(name==='
|
|
16
|
+
if(name==='workspace_work_activity_list'){
|
|
17
|
+
if(flags.search!==undefined)input.search=flags.search;
|
|
18
|
+
if((flags['cursor-at']===undefined)!==(flags['cursor-id']===undefined))throw new Error('Activity requires both --cursor-at and --cursor-id.');
|
|
19
|
+
if(flags['cursor-at']!==undefined)input.cursor={at:flags['cursor-at'],id:flags['cursor-id']};
|
|
20
|
+
} else if(name==='workspace_work_operator_list'){
|
|
21
|
+
if(flags.order!==undefined)input.order=flags.order;
|
|
17
22
|
input.page={...(flags.cursor===undefined?{}:{cursor:flags.cursor}),...(flags.limit===undefined?{}:{limit:Number(flags.limit)})};
|
|
18
23
|
input.filter={};for(const [flag,key] of [['state','state'],['review-status','reviewStatus'],['search','search'],['queue','queue']])if(flags[flag]!==undefined)input.filter[key]=flags[flag];
|
|
19
24
|
} else if(name==='workspace_work_operator_get'){
|
|
20
25
|
if(flags.revision!==undefined)input.revision=Number(flags.revision);
|
|
21
26
|
input.history={...(flags['history-cursor']===undefined?{}:{cursor:flags['history-cursor']}),...(flags['history-limit']===undefined?{}:{limit:Number(flags['history-limit'])})};
|
|
22
27
|
}
|
|
23
|
-
const applicable=new Set(['profile','workspace','file','request-id','plan-id','client-action-id',...(name==='workspace_work_operator_list'?['cursor','limit','state','review-status','search','queue']:[]),...(name==='workspace_work_operator_get'?['revision','history-cursor','history-limit']:[])]);
|
|
28
|
+
const applicable=new Set(['profile','workspace','file','request-id','plan-id','client-action-id',...(name==='workspace_work_operator_list'?['cursor','limit','state','review-status','search','queue','order']:[]),...(name==='workspace_work_activity_list'?['search','cursor-at','cursor-id']:[]),...(name==='workspace_work_operator_get'?['revision','history-cursor','history-limit']:[])]);
|
|
24
29
|
if(Object.keys(flags).some(k=>!applicable.has(k)))throw new Error('Option does not apply to this work command.');
|
|
25
30
|
if(read&&flags.file!==undefined)throw new Error('Read commands do not accept --file.');
|
|
26
31
|
console.log(JSON.stringify(await callWorkspace(name,input,configuration(flags.profile))));
|
package/cli/workspaces.mjs
CHANGED
|
@@ -1,32 +1,69 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {randomUUID} from 'node:crypto';
|
|
2
|
+
import {notificationOperations} from './notification-operations.mjs';
|
|
3
|
+
import {roomsOperations} from './rooms-operations.mjs';
|
|
4
|
+
import {queueOperations} from './queue-operations.mjs';
|
|
5
|
+
import {handTaskOperations} from './hand-task-operations.mjs';
|
|
6
|
+
import {kvOperations} from './kv-operations.mjs';
|
|
7
|
+
import {outcomeOperations} from './outcome-operations.mjs';
|
|
8
|
+
import {attachmentOperations} from './attachment-operations.mjs';
|
|
9
|
+
import {questionOperations} from './question-operations.mjs';
|
|
10
|
+
import {searchOperations} from './search-operations.mjs';
|
|
11
|
+
import {fileOperations,uploadFileContent} from './file-operations.mjs';
|
|
12
|
+
import {playbookOperations} from './playbook-operations.mjs';
|
|
2
13
|
import {connectionRegistryOperations} from './connection-registry-operations.mjs';
|
|
14
|
+
import { budgetOperations } from './budget-operations.mjs';
|
|
15
|
+
import { secretOperations } from './secret-operations.mjs';
|
|
3
16
|
import { hostedToolOperations, paidToolByName, isPaidToolOperation, paidToolResult } from './tools-operations.mjs';
|
|
4
|
-
import { workspaceExecutionOperations } from './work-execution-operations.mjs';
|
|
17
|
+
import { workspaceExecutionOperations, managedAccountOperations } from './work-execution-operations.mjs';
|
|
5
18
|
import { workFeedbackOperations } from './work-feedback-operations.mjs';
|
|
6
19
|
import { accountReplyOperations } from './account-reply-operations.mjs';
|
|
7
20
|
import { accountIntakeOperations } from './account-intake-operations.mjs';
|
|
8
21
|
import { recipientOperations } from './recipient-operations.mjs';
|
|
9
22
|
import { suppressionOperations } from './suppression-operations.mjs';
|
|
23
|
+
import { guardrailsOperations } from './guardrails-operations.mjs';
|
|
24
|
+
import { auditOperations } from './audit-operations.mjs';
|
|
25
|
+
import { usageOperations } from './usage-operations.mjs';
|
|
26
|
+
import { outboundOperations } from './outbound-operations.mjs';
|
|
27
|
+
import { cohortOperations } from './cohort-operations.mjs';
|
|
10
28
|
import { workOperations } from './work-operations.mjs';
|
|
11
29
|
import { assignmentOperations } from './assignment-operations.mjs';
|
|
12
30
|
import {discoveryOperations} from './discovery-operations.mjs';
|
|
13
|
-
import {
|
|
31
|
+
import {campaignOperations} from './campaign-operations.mjs';
|
|
14
32
|
import {audienceOperations} from './audience-operations.mjs';
|
|
15
33
|
import { readFileSync, writeFileSync, renameSync, mkdirSync, statSync, lstatSync, realpathSync } from 'node:fs';
|
|
16
34
|
import { dirname, resolve, relative, isAbsolute } from 'node:path';
|
|
17
35
|
import { fileURLToPath } from 'node:url';
|
|
18
36
|
import { z } from 'zod';
|
|
19
37
|
import {offerOperations} from './offer-operations.mjs';
|
|
38
|
+
import {readJsonFile} from './json-file.mjs';
|
|
20
39
|
const uuid = z.string().uuid();
|
|
21
40
|
const workspace = { workspaceId: uuid.optional() };
|
|
22
41
|
export const operations = {
|
|
23
|
-
...
|
|
42
|
+
...searchOperations,
|
|
43
|
+
...notificationOperations,
|
|
44
|
+
...roomsOperations,
|
|
45
|
+
...queueOperations,
|
|
46
|
+
...handTaskOperations,
|
|
47
|
+
...kvOperations,
|
|
48
|
+
...outcomeOperations,
|
|
49
|
+
...attachmentOperations,
|
|
50
|
+
...questionOperations,
|
|
51
|
+
...fileOperations,
|
|
52
|
+
...playbookOperations,
|
|
24
53
|
...connectionRegistryOperations,
|
|
54
|
+
...budgetOperations,
|
|
55
|
+
...secretOperations,
|
|
25
56
|
...hostedToolOperations,
|
|
26
57
|
...workOperations,
|
|
27
58
|
...workspaceExecutionOperations,
|
|
59
|
+
...managedAccountOperations,
|
|
28
60
|
...workFeedbackOperations,
|
|
29
61
|
...suppressionOperations,
|
|
62
|
+
...guardrailsOperations,
|
|
63
|
+
...auditOperations,
|
|
64
|
+
...usageOperations,
|
|
65
|
+
...outboundOperations,
|
|
66
|
+
...cohortOperations,
|
|
30
67
|
...recipientOperations,
|
|
31
68
|
...accountIntakeOperations,
|
|
32
69
|
...accountReplyOperations,
|
|
@@ -34,8 +71,13 @@ export const operations = {
|
|
|
34
71
|
...discoveryOperations,
|
|
35
72
|
...offerOperations,
|
|
36
73
|
...audienceOperations,
|
|
37
|
-
...
|
|
74
|
+
...campaignOperations,
|
|
38
75
|
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.'},
|
|
76
|
+
workspace_sending_settings_save: {schema:z.object({...workspace,userId:uuid.optional(),expectedVersion:z.number().int().nonnegative(),expectedDefaultsVersion:z.number().int().nonnegative().optional(),requestId:uuid,reason:z.string().trim().min(1).max(2000),schedule:z.unknown(),limits:z.unknown(),rampPercent:z.union([z.literal(25),z.literal(50),z.literal(75),z.literal(100)]).optional(),raiseEachMonday:z.boolean().optional()}).strict(),method:'POST',path:a=>`/workspaces/${a.workspaceId}/sending-settings`,body:({workspaceId,...a})=>a,scoped:true,description:'Save workspace sending defaults, or one member\'s hours, limits and ramp with userId. People only: agent credentials are refused. Send expectedVersion from the last read; a stale version is rejected. A reason is recorded with the change.'},
|
|
77
|
+
workspace_usage_summary: {schema:z.object(workspace).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/usage/summary`,scoped:true,description:'Paid tool use for the last 30 days, per tool: operations, cache hits, charged amount (pico USD), pending and expired. Same figures as Settings > Usage.'},
|
|
78
|
+
workspace_usage_callers: {schema:z.object(workspace).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/usage/callers`,scoped:true,description:'People, agents and API keys that have called paid tools in this workspace, for filtering usage calls.'},
|
|
79
|
+
workspace_usage_calls: {schema:z.object({...workspace,after:uuid.optional(),caller:z.string().regex(/^(key|agent|user):[0-9a-f-]{36}$/i).optional(),status:z.enum(['all','succeeded','failed','pending']).optional()}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/usage/calls?${new URLSearchParams(Object.entries(a).filter(([k,v])=>k!=='workspaceId'&&v!==undefined).map(([k,v])=>[k,String(v)]))}`,scoped:true,description:'Paid tool calls, newest first, 50 per page, with counts by status. Filter by caller (user:ID, agent:ID or key:ID) and status.'},
|
|
80
|
+
workspace_usage_call: {schema:z.object({...workspace,callId:uuid}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/usage/calls/${a.callId}`,scoped:true,description:'One paid tool call with its request, response and saved result while the payload is kept.'},
|
|
39
81
|
workspace_member: {schema:z.object({...workspace,userId:uuid}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/members/${a.userId}`,scoped:true},
|
|
40
82
|
workspace_agent_access: { schema:z.object(workspace).strict(), method:'GET', path:a=>`/workspaces/${a.workspaceId}/agent-access`, scoped:true },
|
|
41
83
|
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 },
|
|
@@ -49,6 +91,7 @@ export const operations = {
|
|
|
49
91
|
workspace_invitations: { schema: z.object({}).strict(), method: 'GET', path: () => '/me/invitations' },
|
|
50
92
|
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
93
|
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 }) },
|
|
94
|
+
workspace_invitation_decline: { schema: z.object({ id: uuid, generation: z.number().int().positive() }).strict(), method: 'POST', path: a => `/invitations/${a.id}/decline`, body: a => ({ generation: a.generation }) },
|
|
52
95
|
workspace_select: { schema: z.object({ workspaceId: uuid }).strict(), method: 'GET', path: a => `/workspaces/${a.workspaceId}` },
|
|
53
96
|
};
|
|
54
97
|
function profilePath(path) {
|
|
@@ -77,7 +120,11 @@ export function writeProfile(path, profile) {
|
|
|
77
120
|
const target=profilePath(path); const temporary=`${target}.${process.pid}.tmp`;
|
|
78
121
|
writeFileSync(temporary,JSON.stringify(profile)+'\n',{mode:0o600,flag:'wx'}); renameSync(temporary,target);
|
|
79
122
|
}
|
|
123
|
+
/** The profile path: --profile wins, then AUDIENTI_PROFILE (the variable the MCP server reads), else none (#448). */
|
|
124
|
+
export const profileFrom = flag => flag ?? (process.env.AUDIENTI_PROFILE || undefined);
|
|
125
|
+
export const profileHint = 'Pass --profile PATH, or set AUDIENTI_PROFILE to the profile file.';
|
|
80
126
|
export function configuration(path, scope='workspace:access') {
|
|
127
|
+
path=profileFrom(path);
|
|
81
128
|
const profile=readProfile(path);
|
|
82
129
|
const agentProfile = Boolean(profile.agent || profile.pendingAgent || profile.connectionSecret);
|
|
83
130
|
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 };
|
|
@@ -88,9 +135,44 @@ export function validateUrl(value) {
|
|
|
88
135
|
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
136
|
return url;
|
|
90
137
|
}
|
|
138
|
+
// A refused request that never left this machine. It prints like an API error, so a caller reads one format (#451).
|
|
139
|
+
export class InvalidArguments extends Error {
|
|
140
|
+
constructor(message, guidance = {}) { super(message); this.field = guidance.field; this.allowed = guidance.allowed; this.next = guidance.next; }
|
|
141
|
+
toJSON() { return {code:'invalid_arguments',message:this.message,...(this.field ? {field:this.field} : {}),...(this.allowed ? {allowed:this.allowed} : {}),...(this.next ? {next:this.next} : {})}; }
|
|
142
|
+
}
|
|
143
|
+
// The same field, allowed values and next step the server gives (#483), for a mistake the CLI refuses before sending.
|
|
144
|
+
const pathText = path => path.reduce((text, part) => typeof part === 'number' ? `${text}[${part}]` : text ? `${text}.${part}` : String(part), '');
|
|
145
|
+
const FIELD_RULES = 'audienti skills read playbook-design references/fields.md';
|
|
146
|
+
export function localGuidance(name, issues, args) {
|
|
147
|
+
const issue = issues[0]; if(!issue) return {};
|
|
148
|
+
const playbook = name.startsWith('playbook_');
|
|
149
|
+
const path = issue.code === 'unrecognized_keys' ? [...issue.path, issue.keys?.[0] ?? ''] : issue.path;
|
|
150
|
+
const fieldText = pathText(path).slice(0, 200), field = /^[A-Za-z0-9_.[\]-]+$/.test(fieldText) ? fieldText : undefined, last = String(path[path.length - 1] ?? '');
|
|
151
|
+
const given = path.reduce((value, part) => value?.[part], args);
|
|
152
|
+
const shown = typeof given === 'string' ? `"${given.slice(0, 60)}"` : 'that value';
|
|
153
|
+
if(issue.code === 'invalid_value') {
|
|
154
|
+
const allowed = issue.values.filter(value => typeof value === 'string' && value.length <= 80).slice(0, 60);
|
|
155
|
+
const phone = playbook && last === 'channel' ? ' A phone call, voicemail or gift is a Custom step: use channel Custom and write in its instructions what the person does by hand. Your agent prepares the script or text and the person records the outcome.' : '';
|
|
156
|
+
const more = playbook && last === 'actionId' ? ' List every action id with its channel: audienti playbook actions.' : playbook && last === 'when' ? ' List the event names: audienti playbook actions.' : '';
|
|
157
|
+
return {field, ...(allowed.length ? {allowed} : {}), next: `Use one of the allowed values in place of ${shown}.${phone}${more}`};
|
|
158
|
+
}
|
|
159
|
+
if(issue.code === 'unrecognized_keys') return {field, next: `Remove ${issue.keys.map(key => `"${key}"`).join(', ')} or fix the spelling. ${playbook ? `The fields a playbook has are in the example of: audienti playbook save --help. Field rules: ${FIELD_RULES}` : 'Run audienti --help for the options of this command.'}`};
|
|
160
|
+
if(issue.code === 'invalid_type') return {field, next: given === undefined ? `Add ${last || 'the value'}.${playbook ? ` Field rules: ${FIELD_RULES}` : ''}` : `Send a ${issue.expected} for ${last || field}.${playbook ? ` Field rules: ${FIELD_RULES}` : ''}`};
|
|
161
|
+
return field ? {field, next: `Fix this value and send the command again.${playbook ? ` Field rules: ${FIELD_RULES}` : ''}`} : {};
|
|
162
|
+
}
|
|
163
|
+
// The next step an error gives an agent (#483): one sentence, the path of the wrong value and the values that would have worked.
|
|
164
|
+
// Checked here too, so a response never puts the token or control text into the agent's context.
|
|
165
|
+
export function errorGuidance(error, token) {
|
|
166
|
+
const clean = text => typeof text === 'string' && !/[\u0000-\u001f\u007f]/.test(text) && !(token && text.includes(token));
|
|
167
|
+
return {
|
|
168
|
+
next: clean(error?.next) && error.next.length <= 600 ? error.next : undefined,
|
|
169
|
+
field: clean(error?.field) && /^[A-Za-z0-9_.[\]-]{1,200}$/.test(error.field) ? error.field : undefined,
|
|
170
|
+
allowed: Array.isArray(error?.allowed) && error.allowed.length > 0 && error.allowed.length <= 60 && error.allowed.every(value => clean(value) && value.length <= 80) ? error.allowed : undefined
|
|
171
|
+
};
|
|
172
|
+
}
|
|
91
173
|
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} : {}) }; }
|
|
174
|
+
constructor(status, code, message, retryAt, blockers, requestTraceId, guidance = {}) { super(message); this.status = status; this.code = code; this.retryAt = retryAt; this.blockers = blockers; this.requestTraceId = requestTraceId; this.next = guidance.next; this.field = guidance.field; this.allowed = guidance.allowed; }
|
|
175
|
+
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.field ? {field:this.field} : {}), ...(this.allowed ? {allowed:this.allowed} : {}), ...(this.next ? {next:this.next} : {}), ...(this.currentRevision!==undefined ? {currentRevision:this.currentRevision} : {}), ...(this.campaigns ? {campaigns:this.campaigns,campaignCount:this.campaignCount} : {}), ...(this.retryAt ? {retryAt:this.retryAt} : {}), ...(this.blockers ? {blockers:this.blockers} : {}), ...(this.errors ? {errors:this.errors} : {}), ...(this.charged ? {charged:this.charged} : {}) }; }
|
|
94
176
|
}
|
|
95
177
|
export class HostedResponseUnknown extends Error {
|
|
96
178
|
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; }
|
|
@@ -98,61 +180,78 @@ export class HostedResponseUnknown extends Error {
|
|
|
98
180
|
}
|
|
99
181
|
export async function callWorkspace(name, args, config) {
|
|
100
182
|
const operation=operations[name]; if(!operation) throw new Error('Unknown workspace operation.');
|
|
101
|
-
const parsed=operation.schema.safeParse(args);if(!parsed.success) throw new
|
|
183
|
+
const parsed=operation.schema.safeParse(args);if(!parsed.success) throw new InvalidArguments(`Invalid workspace arguments. Check IDs and required fields: ${parsed.error.issues.slice(0,5).map(i=>`${i.path.join('.')||'input'}: ${i.message}`).join('; ')}.`,localGuidance(name,parsed.error.issues,args));
|
|
102
184
|
const input={...parsed.data};
|
|
103
|
-
if(operation.
|
|
104
|
-
|
|
185
|
+
if(operation.localUpload)return uploadFileContent(input,(operationName,fields)=>callWorkspace(operationName,fields,config));
|
|
186
|
+
// #333: an agent's own queue covers all its workspaces; a user's reads the selected workspace, as before.
|
|
187
|
+
if(operation.scoped || (operation.agentWide && !/^agt_/.test(config.token ?? ''))) { input.workspaceId ??= config.workspaceId;if(!uuid.safeParse(input.workspaceId).success) throw new Error(`Select a workspace or supply --workspace ID. ${config.profile&&Object.keys(config.profile).length?'Run workspace use WORKSPACE_ID once for this profile.':profileHint}`); }
|
|
188
|
+
const url=validateUrl(config.url); if(!config.token) throw new Error(`A workspace:access token is required. ${profileHint} Or set AUDIENTI_API_URL and AUDIENTI_API_TOKEN.`);
|
|
105
189
|
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.');}
|
|
190
|
+
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,input)||operation.recoverable)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
191
|
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.');}
|
|
192
|
+
let result;try{result=await response.json();}catch{if(isPaidToolOperation(name,input)||operation.recoverable)throw new HostedResponseUnknown(input.requestId,'invalid_response',headerTrace);throw new Error('The API returned an invalid response.');}
|
|
109
193
|
const requestTraceId=uuid.safeParse(result?.requestTraceId).success?result.requestTraceId:headerTrace;
|
|
110
194
|
if(!response.ok) {
|
|
111
195
|
const code = typeof result?.error?.code==='string' && /^[a-z_]+$/.test(result.error.code) ? result.error.code : 'request_failed';
|
|
112
196
|
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
|
|
114
|
-
const
|
|
115
|
-
|
|
197
|
+
const detail = ((response.status===400 && (code==='invalid_fields' || code==='validation_error' || code==='secret_rejected')) || (response.status===404 && code==='not_found') || (response.status===422 && code==='playbook_cannot_work') || (response.status===409 && (code==='revision_conflict' || code==='playbook_in_use' || code==='definition_archived' || code==='playbook_archived' || code==='starter_playbook' || code==='identity_conflict' || code==='task_not_for_you' || code==='version_conflict' || code==='request_conflict' || code==='campaign_not_ready' || code==='campaign_not_paced' || code==='wrong_event_name' || code==='mark_task_done_first' || code==='no_event' || code==='prepare_first' || code==='task_closed' || code==='not_claimed'))) && typeof result?.error?.message==='string' && result.error.message.length<=(code==='playbook_cannot_work'?2000:1000) && !/[\u0000-\u001f\u007f]/.test(result.error.message) && !/^Workspace request failed/.test(result.error.message) && !(config.token && result.error.message.includes(config.token)) ? result.error.message.trim() : '';
|
|
198
|
+
const blockers = (code==='execution_blocked' || code==='campaign_not_ready') && 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;
|
|
199
|
+
const error = new WorkspaceRequestError(response.status, code, `Workspace request failed: ${code} (HTTP ${response.status}).${detail ? ` ${detail}` : ''}${retryAt ? ` Retry after ${retryAt}.` : ''}`, retryAt, blockers, requestTraceId, errorGuidance(result?.error, config.token));
|
|
200
|
+
if(code==='playbook_cannot_work'&&Array.isArray(result?.error?.errors)&&result.error.errors.length<=30)error.errors=result.error.errors.filter(item=>item&&typeof item.id==='string'&&typeof item.message==='string').map(item=>({id:item.id,level:item.level,message:item.message,...(typeof item.stageKey==='string'?{stageKey:item.stageKey}:{}),...(item.fix&&typeof item.fix.label==='string'?{fix:{id:item.fix.id,label:item.fix.label}}:{})}));
|
|
201
|
+
if(code==='revision_conflict'&&Number.isInteger(result?.error?.currentRevision)&&result.error.currentRevision>=0)error.currentRevision=result.error.currentRevision;
|
|
202
|
+
// A playbook that is in use names the campaigns that use it (#459).
|
|
203
|
+
if(code==='playbook_in_use'&&Array.isArray(result?.error?.campaigns)&&Number.isInteger(result.error.campaignCount)){error.campaigns=result.error.campaigns.slice(0,20).filter(c=>uuid.safeParse(c?.id).success&&typeof c.name==='string'&&c.name.length<=200&&!/[\u0000-\u001f\u007f]/.test(c.name)).map(c=>({id:c.id,name:c.name}));error.campaignCount=result.error.campaignCount;}
|
|
204
|
+
// What a failed paid check already spent (pico USD), so a caller counting its spend never loses it.
|
|
205
|
+
if(typeof result?.error?.charged==='string'&&/^\d{1,20}$/.test(result.error.charged))error.charged=result.error.charged;
|
|
206
|
+
if(isPaidToolOperation(name,input)){error.requestId=input.requestId;error.recovery=response.status>=500?'retry_same_request_id':'inspect_rejection';}
|
|
116
207
|
throw error;
|
|
117
208
|
}
|
|
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);
|
|
209
|
+
if(!result || !Object.hasOwn(result,'data')) {if(isPaidToolOperation(name,input)||operation.recoverable)throw new HostedResponseUnknown(input.requestId,'invalid_response',requestTraceId);throw new Error('The API returned an invalid response.');}
|
|
210
|
+
const paidResult = paidToolResult(name, result?.data, input);
|
|
211
|
+
if(isPaidToolOperation(name,input)&&(!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
212
|
if(name==='workspace_select') config.workspaceId=input.workspaceId;
|
|
122
|
-
return {data:result.data,...(requestTraceId?{requestTraceId}:{})};
|
|
213
|
+
return {data:operation.transform?operation.transform(result.data,input):result.data,...(requestTraceId?{requestTraceId}:{})};
|
|
123
214
|
}
|
|
124
215
|
export async function workspaceCli(args) {
|
|
125
216
|
const flags={}; const position=[];
|
|
126
217
|
for(let i=0;i<args.length;i++) {
|
|
127
218
|
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];}
|
|
219
|
+
if(args[i].startsWith('--')) {const key=args[i].slice(2);if(!['profile','workspace','name','request-id','email','generation','scope','user','file','caller','status','after','query','people-only','recent','unread-only','cursor','key','read','through'].includes(key)||!args[i+1]||args[i+1].startsWith('--')) throw new Error('Unknown or incomplete option.');flags[key]=args[++i];}
|
|
129
220
|
else position.push(args[i]);
|
|
130
221
|
}
|
|
131
222
|
if(position[0]==='profile' && position[1]==='save') {
|
|
132
223
|
if(!['account:read','workspace:access','agents:manage'].includes(flags.scope)) throw new Error('Specify --scope account:read, workspace:access or agents:manage.');
|
|
133
224
|
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;
|
|
225
|
+
const profile=readProfile(profileFrom(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(profileFrom(flags.profile),{...profile,url,tokens:{...profile.tokens,[flags.scope]:token}});console.log('Profile saved.');return;
|
|
135
226
|
}
|
|
136
227
|
const config=configuration(flags.profile);const command=position.slice(1).join(' ');
|
|
137
228
|
let name, input={};
|
|
138
229
|
if(command==='sending-settings') {name='workspace_sending_settings_get';input={workspaceId:flags.workspace,...(flags.user?{userId:flags.user}:{})};}
|
|
230
|
+
else if(command==='sending-settings save') {if(!flags.file) throw new Error('Supply the settings with --file PATH.');const body=readJsonFile(flags.file,65536,'Settings file');if(!body||typeof body!=='object'||Array.isArray(body)) throw new Error('Settings must be an object.');name='workspace_sending_settings_save';input={...body,workspaceId:flags.workspace??body.workspaceId,...(flags.user?{userId:flags.user}:{})};}
|
|
231
|
+
else if(command==='usage summary') {name='workspace_usage_summary';input={workspaceId:flags.workspace};}
|
|
232
|
+
else if(command==='usage callers') {name='workspace_usage_callers';input={workspaceId:flags.workspace};}
|
|
233
|
+
else if(command==='usage calls') {name='workspace_usage_calls';input={workspaceId:flags.workspace,...(flags.after?{after:flags.after}:{}),...(flags.caller?{caller:flags.caller}:{}),...(flags.status?{status:flags.status}:{})};}
|
|
234
|
+
else if(position[1]==='usage'&&position[2]==='call'&&position.length===4) {name='workspace_usage_call';input={workspaceId:flags.workspace,callId:position[3]};}
|
|
139
235
|
else if(command==='agent-access') {name='workspace_agent_access';input={workspaceId:flags.workspace};}
|
|
140
236
|
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
237
|
else if(command==='init') name='workspace_initialize';
|
|
142
238
|
else if(command==='list') name='workspace_list';
|
|
143
|
-
else if(command==='
|
|
239
|
+
else if(command==='notifications') {if(flags['unread-only']&&!['true','false'].includes(flags['unread-only']))throw new Error('--unread-only must be true or false.');name='workspace_notifications_list';input={workspaceId:flags.workspace,unreadOnly:flags['unread-only']==='true',...(flags.cursor?{cursor:readJsonFile(flags.cursor,4096,'Notification cursor')}:{})};}
|
|
240
|
+
else if(command==='notification-read') {if(!['true','false'].includes(flags.read))throw new Error('--read must be true or false.');name='workspace_notifications_mark';input={workspaceId:flags.workspace,sourceKey:flags.key,isRead:flags.read==='true',...(flags.through?{through:flags.through}:{})};}
|
|
241
|
+
else if(command==='search') {if(flags['people-only']&&!['true','false'].includes(flags['people-only']))throw new Error('--people-only must be true or false.');name='workspace_search';input={workspaceId:flags.workspace,query:flags.query??'',peopleOnly:flags['people-only']==='true',recent:flags.recent?readJsonFile(flags.recent,4096,'Recent references'):[]};}
|
|
242
|
+
else if(command==='create') {name='workspace_create';input={name:flags.name,requestId:flags['request-id']??randomUUID()};}
|
|
144
243
|
else if(command==='show') {name='workspace_get';input={workspaceId:flags.workspace};}
|
|
145
244
|
else if(command==='invite') {name='workspace_invite';input={workspaceId:flags.workspace,email:flags.email};}
|
|
146
245
|
else if(command==='invitations') name='workspace_invitations';
|
|
147
246
|
else if(position[1]==='member' && position.length===3) {name='workspace_member';input={workspaceId:flags.workspace,userId:position[2]};}
|
|
148
247
|
else if(position[1]==='use' && position.length===3) {name='workspace_select';input={workspaceId:position[2]};}
|
|
149
248
|
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];
|
|
249
|
+
const action=position[2];name={show:'workspace_invitation_get',accept:'workspace_invitation_accept',decline:'workspace_invitation_decline',retry:'workspace_invitation_retry',cancel:'workspace_invitation_cancel'}[action];
|
|
151
250
|
input={id:position[3],...(['retry','cancel'].includes(action)?{workspaceId:flags.workspace}:{generation:Number(flags.generation)})};
|
|
152
251
|
}
|
|
153
252
|
if(!name) throw new Error('Unknown workspace command. Use audienti --help.');
|
|
154
253
|
const result=await callWorkspace(name,input,config);
|
|
155
254
|
|
|
156
|
-
if(name==='workspace_select') writeProfile(flags.profile,{...config.profile,workspaceId:config.workspaceId});
|
|
255
|
+
if(name==='workspace_select') writeProfile(profileFrom(flags.profile),{...config.profile,workspaceId:config.workspaceId});
|
|
157
256
|
console.log(JSON.stringify(result));
|
|
158
257
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@audienti/cli",
|
|
3
|
-
"version": "0.2.0-exora.
|
|
4
|
-
"description": "Audienti CLI and MCP client for
|
|
3
|
+
"version": "0.2.0-exora.11",
|
|
4
|
+
"description": "Audienti CLI and MCP client for app.audienti.com",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"audienti": "cli/audienti.mjs",
|