@audienti/cli 0.2.0-exora.25 → 0.2.0-exora.27
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
|
@@ -114,7 +114,8 @@ Search: workspace search --workspace ID --query TEXT [--people-only true] [--rec
|
|
|
114
114
|
Invitation commands: workspace invitation show|accept|decline|retry|cancel ID
|
|
115
115
|
Options: --workspace ID --name NAME --request-id UUID --email EMAIL --generation N
|
|
116
116
|
Optional private profile: --profile PATH (outside this repository), or the AUDIENTI_PROFILE variable. --profile wins.
|
|
117
|
-
Save an environment token: profile save --scope account:read|workspace:access|agents:manage --profile PATH
|
|
117
|
+
Save an environment token: profile save --scope account:read|workspace:read|workspace:access|agents:manage --profile PATH
|
|
118
|
+
Tokens: token show [--profile PATH] (what the token can do; workspace:read can look but is refused on every change)
|
|
118
119
|
Start local MCP stdio server: mcp (audienti mcp --help for the settings)
|
|
119
120
|
--json writes the API response envelope.
|
|
120
121
|
`;
|
|
@@ -201,7 +202,7 @@ async function main() {
|
|
|
201
202
|
if(args[0]==='offer'){const {offerCli}=await import('./offers.mjs');await offerCli(args);return;}
|
|
202
203
|
if (args[0] === 'agent') { const { agentCli } = await import('./agents.mjs'); await agentCli(args); return; }
|
|
203
204
|
if (isAssignmentCommand(args)) { await assignmentCli(args); return; }
|
|
204
|
-
if (['workspace', 'profile'].includes(args[0])) { const { workspaceCli } = await import('./workspaces.mjs'); await workspaceCli(args); return; }
|
|
205
|
+
if (['workspace', 'profile', 'token'].includes(args[0])) { const { workspaceCli } = await import('./workspaces.mjs'); await workspaceCli(args); return; }
|
|
205
206
|
if (args[0] === 'profiles') { const { profilesCli } = await import('./profiles.mjs'); await profilesCli(args); return; }
|
|
206
207
|
if (args[0] === 'skills') { const { skillsCli } = await import('./skills.mjs'); await skillsCli(args); return; }
|
|
207
208
|
if (args[0] === 'mcp' && args.length === 1) { await import('./mcp.mjs'); return; }
|
package/cli/workspaces.mjs
CHANGED
|
@@ -85,6 +85,7 @@ export const operations = {
|
|
|
85
85
|
workspace_member: {schema:z.object({...workspace,userId:uuid}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/members/${a.userId}`,scoped:true},
|
|
86
86
|
workspace_agent_access: { schema:z.object(workspace).strict(), method:'GET', path:a=>`/workspaces/${a.workspaceId}/agent-access`, scoped:true },
|
|
87
87
|
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 },
|
|
88
|
+
token_show: { schema: z.object({}).strict(), method: 'GET', path: () => '/token', description: 'Say what the credential you are using can do: its permission and whether it can change things. Works with any valid user API token or agent credential. A read-only token (workspace:read) is refused on every change; ask your user for a workspace:access token to change things.' },
|
|
88
89
|
workspace_initialize: { schema: z.object({}).strict(), method: 'POST', path: () => '/workspaces/bootstrap' },
|
|
89
90
|
workspace_list: { schema: z.object({}).strict(), method: 'GET', path: () => '/workspaces' },
|
|
90
91
|
workspace_create: { schema: z.object({ name: z.string().trim().min(1).max(80), requestId: uuid }).strict(), method: 'POST', path: () => '/workspaces', body: a => a },
|
|
@@ -134,7 +135,7 @@ export function configuration(path, scope='workspace:access') {
|
|
|
134
135
|
path=profileFrom(path);
|
|
135
136
|
const profile=readProfile(path);
|
|
136
137
|
const agentProfile = Boolean(profile.agent || profile.pendingAgent || profile.connectionSecret);
|
|
137
|
-
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:
|
|
138
|
+
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:access'] ?? profile.tokens?.['workspace:read'] : profile.tokens?.[scope]), workspaceId:profile.workspaceId, profile };
|
|
138
139
|
}
|
|
139
140
|
export function validateUrl(value) {
|
|
140
141
|
let url;try {url=new URL(value);}catch{throw new Error('Set AUDIENTI_API_URL to an API URL ending with /api/v1.');}
|
|
@@ -163,6 +164,10 @@ export function localGuidance(name, issues, args) {
|
|
|
163
164
|
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.' : '';
|
|
164
165
|
return {field, ...(allowed.length ? {allowed} : {}), next: `Use one of the allowed values in place of ${shown}.${phone}${more}`};
|
|
165
166
|
}
|
|
167
|
+
if(issue.code === 'invalid_union' && issue.discriminator && Array.isArray(issue.options)) {
|
|
168
|
+
const allowed = issue.options.filter(value => typeof value === 'string' && value.length <= 80).slice(0, 60);
|
|
169
|
+
return {field, ...(allowed.length ? {allowed} : {}), next: `Use one of the allowed values in place of ${shown}, spelled exactly.${playbook ? ` Field rules: ${FIELD_RULES}` : ''}`};
|
|
170
|
+
}
|
|
166
171
|
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.'}`};
|
|
167
172
|
if(issue.code === 'invalid_type' && given === undefined && last === 'expectedUpdatedAt') return {field, next: 'Add expectedUpdatedAt (null when there is no draft; otherwise the updatedAt that playbook draft show returned).'};
|
|
168
173
|
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}` : ''}`};
|
|
@@ -197,7 +202,7 @@ export async function callWorkspace(name, args, config) {
|
|
|
197
202
|
if(operation.localUpload)return uploadFileContent(input,(operationName,fields)=>callWorkspace(operationName,fields,config));
|
|
198
203
|
// #333: an agent's own queue covers all its workspaces; a user's reads the selected workspace, as before.
|
|
199
204
|
if(operation.scoped || (operation.agentWide && !/^agt_/.test(config.token ?? ''))) { input.workspaceId ??= config.workspaceId;if(!uuid.safeParse(input.workspaceId).success) throw new Error(config.interface==='mcp'?`Pass workspaceId in the tool input, or call workspace_select once. ${config.profile&&Object.keys(config.profile).length?'':profileHint}`.trim():`Select a workspace or supply --workspace ID. ${config.profile&&Object.keys(config.profile).length?'Run workspace use WORKSPACE_ID once for this profile.':`${profileHint} An agent that is not connected yet runs agent connect first; then run workspace use WORKSPACE_ID once.`}`); }
|
|
200
|
-
const url=validateUrl(config.url); if(!config.token) throw new Error(`A workspace
|
|
205
|
+
const url=validateUrl(config.url); if(!config.token) throw new Error(`A workspace token is required: workspace:access to read and change, or workspace:read to only read. ${profileHint} Or set AUDIENTI_API_URL and AUDIENTI_API_TOKEN.`);
|
|
201
206
|
const target=new URL(`/api/v1${operation.path(input)}`,url);
|
|
202
207
|
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.');}
|
|
203
208
|
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;
|
|
@@ -206,7 +211,7 @@ export async function callWorkspace(name, args, config) {
|
|
|
206
211
|
if(!response.ok) {
|
|
207
212
|
const code = typeof result?.error?.code==='string' && /^[a-z_]+$/.test(result.error.code) ? result.error.code : 'request_failed';
|
|
208
213
|
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;
|
|
209
|
-
const detail = (INTENT_REFUSALS.has(code) || (name.startsWith('decision_') && DECISION_REFUSALS.has(code)) || (response.status===400 && (code==='invalid_fields' || code==='validation_error' || code==='secret_rejected')) || (response.status===404 && (code==='not_found' || code==='no_draft')) || (response.status===422 && code==='playbook_cannot_work') || (response.status===409 && (code==='revision_conflict' || code==='draft_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() : '';
|
|
214
|
+
const detail = (INTENT_REFUSALS.has(code) || (name.startsWith('decision_') && DECISION_REFUSALS.has(code)) || (response.status===400 && (code==='invalid_fields' || code==='validation_error' || code==='secret_rejected')) || (response.status===403 && code==='insufficient_scope' && /^This API token can read but not change\./.test(result?.error?.message ?? '')) || (response.status===404 && (code==='not_found' || code==='no_draft')) || (response.status===422 && code==='playbook_cannot_work') || (response.status===409 && (code==='revision_conflict' || code==='draft_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() : '';
|
|
210
215
|
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;
|
|
211
216
|
const error = new WorkspaceRequestError(response.status, code, name==='intent_record' && (INTENT_REFUSALS.has(code) || code==='invalid_fields') && detail ? surfaceWording(detail, config) : `Workspace request failed: ${code} (HTTP ${response.status}).${detail ? ` ${surfaceWording(detail, config)}` : ''}${retryAt ? ` Retry after ${retryAt}.` : ''}`, retryAt, blockers, requestTraceId, errorGuidance(result?.error, config.token, config));
|
|
212
217
|
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}}:{})}));
|
|
@@ -233,11 +238,13 @@ export async function workspaceCli(args) {
|
|
|
233
238
|
else position.push(args[i]);
|
|
234
239
|
}
|
|
235
240
|
if(position[0]==='profile' && position[1]==='save') {
|
|
236
|
-
if(!['account:read','workspace:access','agents:manage'].includes(flags.scope)) throw new Error('Specify --scope account:read, workspace:access or agents:manage.');
|
|
241
|
+
if(!['account:read','workspace:read','workspace:access','agents:manage'].includes(flags.scope)) throw new Error('Specify --scope account:read, workspace:read, workspace:access or agents:manage.');
|
|
237
242
|
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.');
|
|
238
243
|
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;
|
|
239
244
|
}
|
|
240
245
|
const config=configuration(flags.profile);const command=position.slice(1).join(' ');
|
|
246
|
+
// token show works with any saved user token, so it also tries the other slots when there is no workspace token.
|
|
247
|
+
if(position[0]==='token'&&!config.token&&!process.env.AUDIENTI_API_TOKEN&&!(config.profile.agent||config.profile.pendingAgent||config.profile.connectionSecret))config.token=config.profile.tokens?.['agents:manage']??config.profile.tokens?.['account:read'];
|
|
241
248
|
let name, input={};
|
|
242
249
|
if(command==='sending-settings') {name='workspace_sending_settings_get';input={workspaceId:flags.workspace,...(flags.user?{userId:flags.user}:{})};}
|
|
243
250
|
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}:{})};}
|
|
@@ -247,6 +254,7 @@ export async function workspaceCli(args) {
|
|
|
247
254
|
else if(position[1]==='usage'&&position[2]==='call'&&position.length===4) {name='workspace_usage_call';input={workspaceId:flags.workspace,callId:position[3]};}
|
|
248
255
|
else if(command==='agent-access') {name='workspace_agent_access';input={workspaceId:flags.workspace};}
|
|
249
256
|
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')};}
|
|
257
|
+
else if(position[0]==='token'&&command==='show') name='token_show';
|
|
250
258
|
else if(command==='init') name='workspace_initialize';
|
|
251
259
|
else if(command==='list') name='workspace_list';
|
|
252
260
|
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')}:{})};}
|
package/package.json
CHANGED
|
@@ -35,7 +35,7 @@ When you change a playbook you already have, a part you leave out (`stages`, `go
|
|
|
35
35
|
| a stage `completion` | A stage with steps that an agent works, other than the goal, closes people out as Unresponsive. Other stages stay. |
|
|
36
36
|
| a step `condition` | `Only if connected` for a LinkedIn message, else `Always`. |
|
|
37
37
|
| a step `instructions` | One coaching sentence for the kind of action. |
|
|
38
|
-
| a main step `wait` | Only for a step that is new in this save. A step that sends waits 3 days for a reply; a connection request waits 7 days for the accept; a step that sends nothing has no wait. A step the playbook already has keeps what it has, and `"wait": null` means no wait. |
|
|
38
|
+
| a main step `wait` | Only for a step that is new in this save. A step that sends waits 3 days for a reply; a connection request waits 7 days for the accept; a step that sends nothing has no wait. A step the playbook already has keeps what it has, and `"wait": null` means no wait. The saved step carries the wait it got, so `playbook show` and the API read give the actual days. |
|
|
39
39
|
|
|
40
40
|
## The Playbook check
|
|
41
41
|
|
|
@@ -66,7 +66,7 @@ The other items are warnings; read them after each save and clear them:
|
|
|
66
66
|
| `goalDays` | 0 to 365. The days after which a prospect in this stage is overdue (the pipeline card turns orange). 0 means never overdue. |
|
|
67
67
|
| `enabled` | Defaults to true. `false` skips the stage: no one is moved into it. The first stage and the goal stage cannot be skipped. |
|
|
68
68
|
| `steps` | Up to 20 steps. |
|
|
69
|
-
| `completion` | `{ "kind": "next" }`, `{ "kind": "stay" }` or `{ "kind": "off_ramp", "offRampKey": KEY }`. The last enabled stage cannot use `next`. |
|
|
69
|
+
| `completion` | `{ "kind": "next" }`, `{ "kind": "stay" }` or `{ "kind": "off_ramp", "offRampKey": KEY }`. The last enabled stage cannot use `next`. Spell the kind exactly: `off_ramp`, not `offRamp`. A kind that matches none is refused with the list of kinds that work. |
|
|
70
70
|
|
|
71
71
|
## Step
|
|
72
72
|
|
package/source-manifest.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"repository": "https://github.com/omalab/exora",
|
|
3
|
-
"sourceCommit": "
|
|
3
|
+
"sourceCommit": "22bd7faad251f47226c59035b267f222ea1943b4",
|
|
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": "
|
|
19
|
+
"cli/audienti.mjs": "afdba3fa4acc93faaa708a0214fbaa57444bfee20204a2c2817d9fd320a53933",
|
|
20
20
|
"cli/audit-operations.mjs": "afaf87d592a1e5f6bff2a8649a6a64a8f471950b7d99e98995d9205aa5910398",
|
|
21
21
|
"cli/audit.mjs": "a7becaed67c58b063e668b5e66844b6dc15c11a63b428faa171b542ad922c001",
|
|
22
22
|
"cli/budget-operations.mjs": "d74916551ef393e29734c6e13cf9021251e0ed126eeaa529e18cf2332146e1e0",
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
"cli/work-feedback.mjs": "f3b77ecfd7ea1aba9c9f03c9d894137547e9c5575d9daa359ed94505ad025852",
|
|
86
86
|
"cli/work-operations.mjs": "8b3a84ca15e8e4ad79ef92ced4c5adaffd5129f630e3bfc2c74a844a90867600",
|
|
87
87
|
"cli/work.mjs": "751107ecc3de5a6be9db4a068cddc41f3ad8e77c87e59686eaf522862caf9e9f",
|
|
88
|
-
"cli/workspaces.mjs": "
|
|
89
|
-
"package.json": "
|
|
88
|
+
"cli/workspaces.mjs": "5b179691b8492d00b9d639dd13521fe96828aa392190550de34dd91cd0bdba53",
|
|
89
|
+
"package.json": "af224a943c29b27cad8a385a5f670d6e2927de40915e97107721d4e7bafd7509",
|
|
90
90
|
"skills/agent-rooms/SKILL.md": "08f917ff85fee1d23499a4499056ce845b39ce6727029b104819dad41ee87cc2",
|
|
91
91
|
"skills/agent-rooms/references/commands.md": "ef5afa5543ca9da967a147f638774647998ea1c1a2a821959f3bf87d54c292bd",
|
|
92
92
|
"skills/audienti/SKILL.md": "8f2124065bfc3a005b44e194e7dcce9bbf14062ab332e7db66d441eeccb518ed",
|
|
@@ -105,6 +105,6 @@
|
|
|
105
105
|
"skills/getting-started/references/replies.md": "a372a01078f7c0a750b0fb98aff23ca9e7d9155c6b4ac549dd621444325e402b",
|
|
106
106
|
"skills/getting-started/references/reply-checks.md": "85fe692ad6b7bc73e204de313b27d225be3da1a0af71e8e7bb9945ae511a0dc3",
|
|
107
107
|
"skills/playbook-design/SKILL.md": "56aa5692c1bbe3843f11819f0f9270988ddd0424b16f38db72a854864155407f",
|
|
108
|
-
"skills/playbook-design/references/fields.md": "
|
|
108
|
+
"skills/playbook-design/references/fields.md": "c7ba7a0bf8ac1325e70a5e4d98734716d752c311588d064bf2aba849925357aa"
|
|
109
109
|
}
|
|
110
110
|
}
|