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

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.
@@ -5,7 +5,7 @@ const item = { ...scope, evidenceId: uuid };
5
5
  const status = z.enum(['needs_handling', 'handled', 'not_reply']);
6
6
  const outcome = z.enum(['response_recorded', 'closed_without_response', 'suppressed']);
7
7
  const limit = z.number().int().min(1).max(100);
8
- const classification = z.enum(['interested', 'question', 'later', 'out_of_office', 'stop', 'bounce', 'referral', 'wrong_person']);
8
+ const classification = z.enum(['interested', 'question', 'later', 'not_interested', 'out_of_office', 'stop', 'bounce', 'referral', 'wrong_person']);
9
9
  const slot = z.object({ start: z.string().describe('UTC start, ending in Z.'), end: z.string().describe('UTC end, ending in Z.'), timeZone: z.string().describe("The user's IANA time zone, for display.") }).strict();
10
10
  const draft = z.object({
11
11
  type: z.enum(['email.send', 'linkedin.send']),
package/cli/audienti.mjs CHANGED
@@ -90,7 +90,7 @@ Find prospects: campaign find CAMPAIGN_ID --count N --max-credits C (searches w
90
90
  campaign check CAMPAIGN_ID --url LINKEDIN_URL [--request-id UUID] (checks one person the same way; reuse the request ID after an interruption)
91
91
  Target companies: campaign targets CAMPAIGN_ID [--limit N --after CURSOR] (reads the companies people must work at to join)
92
92
  campaign targets CAMPAIGN_ID --add acme.com,linkedin.com/company/globex [--remove old.com] | --set FILE|- (one company per line; saves a new revision)
93
- Playbooks: playbook list [--archived] [--limit N --cursor ID]|actions|show ID [--revision N]|save --file REQUEST.json|archive ID|restore ID|draft show ID|save ID --file REQUEST.json|discard ID [--expected-updated-at T]|publish ID [--request-id UUID]|delete ID|progress PARTICIPATION_ID|record --file REQUEST.json (audienti playbook SUBCOMMAND --help for the request shape)
93
+ Playbooks: playbook list [--archived] [--limit N --cursor ID]|actions|show ID [--revision N]|save --file REQUEST.json|archive ID|restore ID|draft show ID|save ID --file REQUEST.json|discard ID [--expected-updated-at T]|publish ID [--request-id UUID]|delete ID|progress PARTICIPATION_ID|record --file REQUEST.json|event PARTICIPATION_ID --kind KIND --reason TEXT (audienti playbook SUBCOMMAND --help for the request shape)
94
94
  Analytics: analytics show --from ISO --to ISO [--campaign ID] [--playbook-version ID] [--format json|csv]
95
95
  analytics outbound [--workspace ID] [--days N | --from ISO --to ISO] [--by sender|action|day] [--format json|csv]
96
96
  analytics activity [--workspace ID] [--days N | --from ISO --to ISO] [--format json|csv]
@@ -67,6 +67,10 @@ export const playbookCommands = {
67
67
  record: {
68
68
  usage: 'playbook record --file REQUEST.json [--workspace ID] [--profile PATH]',
69
69
  about: 'Report that a person moved to a stage or off-ramp, or finished the current stage play. The file has participationId, expectedVersion (the version from playbook progress), requestId (a fresh UUID), exactly one of stageKey, offRampKey or finishStage true, reason, and evidence (a list of {kind: observation|conversation, id}).'
70
+ },
71
+ event: {
72
+ usage: 'playbook event PARTICIPATION_ID --kind KIND --reason TEXT [--evidence conversation:ID|observation:ID ...] [--at ISO] [--request-id UUID] [--file REQUEST.json] [--workspace ID] [--profile PATH]',
73
+ about: 'Record one event for a person, then let the playbook settle. KIND is meeting_requested (a meeting was asked for), meeting_accepted (agreed to, no time yet), meeting_declined, meeting_booked (a meeting has a time), not_a_fit (the person is not a fit) or steps_done (the steps of the current stage are done). A stage that lists the event as an entry event takes the person forward; the playbook rules decide the rest. not_a_fit ends the person in the not_fit off-ramp when the playbook has one, and otherwise only shows in the history. steps_done applies the current stage completion (next stage, stay or off-ramp). REASON is 1 to 300 characters and shows in the history. --evidence names stored evidence about this person: conversation:ID is a message id (the conversation ids in the person timeline, audienti prospects timeline), observation:ID a profile observation id. Repeat it for each piece, up to 20. --at is an ISO time with a timezone offset, for example 2026-11-06T14:30:00Z. Left out, the time is now; a future time counts as now, and a time before the person entered the stage counts as just after it. --request-id is made for you when left out; pass the same UUID to retry safely. Only the person\'s assignee or a workspace Admin may record. A repeat writes nothing and answers recorded false. A person who has left the campaign is refused with participation_ended, with two exceptions: meeting_booked is still recorded, and a repeat of meeting_booked or not_a_fit answers recorded false with the eventId already recorded.'
70
74
  }
71
75
  };
72
76
  const lines = Object.values(playbookCommands).map(command => command.usage);
@@ -9,7 +9,7 @@ const eventRule=z.object({key:z.string().uuid(),when:z.string().min(1).max(60).d
9
9
  const stepRoutes=z.object({accept:routeTarget.optional(),reply:routeTarget.optional(),meeting:routeTarget.optional(),timeout:routeTarget.optional()}).strict().describe("Where an event that reaches the person after this step ran sends them. A route wins over the stage's jump list; an event with no route follows the stage. Going to an earlier stage needs rules.allowBackward.");
10
10
  const playbookStepSchema=z.object({key:z.string().uuid(),channel:z.enum(['LinkedIn','Email','Custom']),action:label,condition:z.enum(['Always','Only if viable','Only if connected']).optional().describe('Left out: Only if connected for a LinkedIn message (it can only reach a connection), Always for everything else. Only if connected skips the step for a person who was asked to connect and has not accepted. Only if viable needs viability.'),viability:text.optional(),instructions:text.optional().describe('What the agent should write or do. Left out, one coaching sentence for the kind of action.'),wait:z.object({until:z.enum(['accept','reply','meeting','timeout','event']),days:z.number().int().min(1).max(60),event:label.optional().describe('Custom steps only, with until event: the name of the event a person records to end the wait, for example Gift delivered.'),followUps:z.number().int().min(0).max(10).optional().describe('Meeting waits only: how many follow-ups your agent writes, one after each wait, before the meeting is marked No answer. Default 3.')}).strict().nullable().optional().describe('How long the step waits and for what. Left out on a new step that sends: a reply wait of 3 days (a connection request waits 7 days for the accept). null means no wait. A step the playbook already has keeps what it has.'),waitDays:z.number().int().min(1).max(60).optional().describe('Older form of wait: the same as wait {until: timeout, days}.'),side:z.boolean().optional(),onTimeout:z.enum(['next','off_ramp']).optional(),humanize:z.boolean().optional().describe('Email only. Rewrite the email text before approval so it reads as written by a person. On by default; set false to send the text as written.'),actionId:z.string().max(80).optional().describe('An action id from the outreach rulebook, for example linkedin.message.send.'),routes:stepRoutes.optional(),eventRules:z.array(eventRule).max(10).optional()}).strict();
11
11
  const completion=z.discriminatedUnion('kind',[z.object({kind:z.literal('next')}).strict(),z.object({kind:z.literal('stay')}).strict(),z.object({kind:z.literal('off_ramp'),offRampKey:z.string().uuid()}).strict()]);
12
- const stageFields=identity.extend({owner:z.enum(['Agent','Assignee']).optional(),goalDays:z.number().int().min(0).max(365).optional().describe('Days a person should take in this stage. Left out: the waits of its steps plus 2 days, at least 3.'),enabled:z.boolean().optional().describe('false skips the stage: people are never moved into it. The first stage and the goal stage cannot be skipped.'),steps:z.array(playbookStepSchema).max(20).optional(),completion:completion.optional(),on:z.array(z.enum(['accept','reply','meeting'])).max(3).optional().describe('The events that jump a person forward to this stage: accept, reply, meeting (a booked meeting). A person who has one of these events moves to the furthest later stage that lists it, and the stages passed over are recorded as skipped. Leave it out for a stage people reach when the stage before it finishes, or when a person moves them. Not for the first stage.'),eventRules:z.array(eventRule).max(10).optional()});
12
+ const stageFields=identity.extend({owner:z.enum(['Agent','Assignee']).optional(),goalDays:z.number().int().min(0).max(365).optional().describe('Days a person should take in this stage. Left out: the waits of its steps plus 2 days, at least 3.'),enabled:z.boolean().optional().describe('false skips the stage: people are never moved into it. The first stage and the goal stage cannot be skipped.'),steps:z.array(playbookStepSchema).max(20).optional(),completion:completion.optional(),on:z.array(z.enum(['accept','reply','meeting_requested','meeting_accepted','meeting_declined','meeting'])).max(6).optional().describe('The events that jump a person forward to this stage: accept, reply, meeting_requested (a meeting was asked for), meeting_accepted (agreed to, no time yet), meeting_declined, meeting (a booked meeting). A person who has one of these events moves to the furthest later stage that lists it, and the stages passed over are recorded as skipped. Leave it out for a stage people reach when the stage before it finishes, or when a person moves them. Not for the first stage.'),eventRules:z.array(eventRule).max(10).optional()});
13
13
  // Settings the product no longer asks for (#457). An older request may still carry them: they are accepted, ignored and left out of the new version.
14
14
  // autoMove: conflict 20, approval lives on the account. flagOverdue, emailWindow, nurtureDays: no code read them. optional (stage): use enabled. movesInWhen (words) and 'manual' in on: the stage's events say when a person enters it.
15
15
  const dropKeys=keys=>value=>{if(!value||typeof value!=='object'||Array.isArray(value)||!keys.some(key=>key in value))return value;return Object.fromEntries(Object.entries(value).filter(([key])=>!keys.includes(key)));};
@@ -34,5 +34,6 @@ export const playbookOperations={
34
34
  workspace_pipeline_get:{schema:z.object({...scope,campaignId:uuid.optional(),pinId:uuid.optional()}).strict(),method:'GET',path:a=>`${base(a)}/pipeline?${query(a)}`,scoped:true,description:'Read the pipeline board: stages of one playbook version, the people currently in each stage, and counts of people closed out through each off-ramp. Free CRM read.'},
35
35
  playbook_progress_get:{schema:z.object({...scope,participationId:uuid.describe('A participation ID (one person in one campaign, from the pipeline), not a playbook ID.')}).strict(),method:'GET',path:a=>`${base(a)}/participations/${a.participationId}/progress`,scoped:true,description:'Read the participation’s admitted playbook pin and recorded stage/outcome history. Takes a participation ID (a person in a campaign; each person in the pipeline has one), not a playbook ID.'},
36
36
  playbook_progress_record:{schema:z.object({...scope,participationId:uuid,expectedVersion:z.number().int().nonnegative(),requestId:uuid,stageKey:uuid.optional(),offRampKey:uuid.optional(),finishStage:z.literal(true).optional(),reason:z.string().trim().min(1).max(2000),evidence:z.array(z.object({kind:z.enum(['observation','conversation']),id:uuid}).strict()).max(20)}).strict(),method:'POST',path:a=>`${base(a)}/participations/${a.participationId}/progress`,body:({workspaceId,participationId,...a})=>a,scoped:true,description:'Report a stage, off-ramp, or finishStage:true after externally completing the current play and waits. Finish resolves next/stay/off-ramp from the admitted version. Stored evidence required for agents; human-review and backward-move rules apply. Assigned owner/Admin only. Goal ends prospecting; post-goal forward reporting never reactivates. No send or provider call.'},
37
+ playbook_event_record:{schema:z.object({...scope,participationId:uuid.describe('A participation ID (one person in one campaign, from the pipeline), not a playbook ID.'),requestId:uuid,kind:z.enum(['meeting_requested','meeting_accepted','meeting_declined','meeting_booked','not_a_fit','steps_done']).describe('meeting_requested: a meeting was asked for. meeting_accepted: agreed to, no time yet. meeting_declined: turned down. meeting_booked: a meeting has a time. not_a_fit: the person is not a fit. steps_done: the steps of the current stage are done.'),reason:z.string().trim().min(1).max(300).describe('Why, in one or two sentences. It shows in the person’s history.'),evidence:z.array(z.object({kind:z.enum(['observation','conversation']),id:uuid}).strict()).max(20).optional(),at:z.string().datetime({offset:true}).optional().describe('When it happened, ISO 8601. Defaults to now and is never later than now.')}).strict(),method:'POST',path:a=>`${base(a)}/participations/${a.participationId}/events`,body:({workspaceId,participationId,...a})=>a,scoped:true,description:'Record one event for a person: a meeting asked for, agreed to, turned down or booked, not a fit, or the steps of the current stage done. The playbook then settles: stages that list the event as an entry event take the person forward, and the rules decide the rest (not_a_fit ends the person in the not_fit off-ramp when the playbook has one). Safe to repeat: a second call writes nothing and answers recorded false. Ended participation is refused with participation_ended. Assigned owner/Admin only. No send or provider call.'},
37
38
  workspace_analytics_get:{schema:z.object({...scope,campaignId:uuid.optional(),ownerUserId:uuid.optional(),offerId:uuid.optional(),audienceId:uuid.optional(),tag:z.string().min(1).max(64).optional(),pinId:uuid.optional(),from:z.string().datetime(),to:z.string().datetime()}).strict(),method:'GET',path:a=>`${base(a)}/analytics?${query(a)}`,scoped:true,description:'Read UTC-period distinct outcomes and explicit stage events. Missing activity or cost attribution is not tracked, never fabricated zero. Optional filters: campaignId, ownerUserId (campaign owner), offerId, audienceId, tag (campaign tag); list the values with workspace_analytics_filters. Maximum 366 days; narrow large scopes.'}
38
39
  };
package/cli/progress.mjs CHANGED
@@ -3,22 +3,23 @@ import {callWorkspace,configuration} from './workspaces.mjs';
3
3
  import {readJsonFile} from './json-file.mjs';
4
4
  import {playbookUsageError,playbookCommands} from './playbook-help.mjs';
5
5
  export async function progressCli(args){
6
- const positions=[],flags={};for(let i=0;i<args.length;i++){const arg=args[i];if(!arg.startsWith('--')){positions.push(arg);continue;}const key=arg.slice(2);if(!['profile','workspace','file','cursor','limit','revision','from','to','campaign','playbook-version','kind','user','format','owner','offer','audience','tag','archived','request-id','expected-updated-at'].includes(key)||(key!=='archived'&&(!args[i+1]||args[i+1].startsWith('--')))||Object.hasOwn(flags,key))throw new Error(`Unknown or incomplete option --${key}.${args[0]==='playbook'?' Run audienti playbook SUBCOMMAND --help.':''}`);flags[key]=key==='archived'&&(!args[i+1]||args[i+1].startsWith('--'))?'true':args[++i];}
7
- const names={playbook:{list:'playbook_list',actions:'playbook_actions_list',show:'playbook_get',save:'playbook_save',archive:'playbook_archive',restore:'playbook_restore',draft_show:'playbook_draft_get',draft_save:'playbook_draft_save',draft_discard:'playbook_draft_discard',draft_publish:'playbook_draft_publish',delete:'playbook_delete',progress:'playbook_progress_get',record:'playbook_progress_record'},analytics:{show:'workspace_analytics_get'},pipeline:{show:'workspace_pipeline_get'},connections:{list:'workspace_connections_list',register:'workspace_connection_register',report:'workspace_connection_report',disconnect:'workspace_connection_disconnect'}};
6
+ const positions=[],flags={};for(let i=0;i<args.length;i++){const arg=args[i];if(!arg.startsWith('--')){positions.push(arg);continue;}const key=arg.slice(2);if(!['profile','workspace','file','cursor','limit','revision','from','to','campaign','playbook-version','kind','user','format','owner','offer','audience','tag','archived','request-id','expected-updated-at','reason','evidence','at'].includes(key)||(key!=='archived'&&(!args[i+1]||args[i+1].startsWith('--')))||(key!=='evidence'&&Object.hasOwn(flags,key)))throw new Error(`Unknown or incomplete option --${key}.${args[0]==='playbook'?' Run audienti playbook SUBCOMMAND --help.':''}`);if(key==='evidence'){(flags.evidence??=[]).push(args[++i]);continue;}flags[key]=key==='archived'&&(!args[i+1]||args[i+1].startsWith('--'))?'true':args[++i];}
7
+ const names={playbook:{list:'playbook_list',actions:'playbook_actions_list',show:'playbook_get',save:'playbook_save',archive:'playbook_archive',restore:'playbook_restore',draft_show:'playbook_draft_get',draft_save:'playbook_draft_save',draft_discard:'playbook_draft_discard',draft_publish:'playbook_draft_publish',delete:'playbook_delete',progress:'playbook_progress_get',record:'playbook_progress_record',event:'playbook_event_record'},analytics:{show:'workspace_analytics_get'},pipeline:{show:'workspace_pipeline_get'},connections:{list:'workspace_connections_list',register:'workspace_connection_register',report:'workspace_connection_report',disconnect:'workspace_connection_disconnect'}};
8
8
  // playbook draft show|save|discard|publish ID is one command name in two words.
9
9
  if(positions[0]==='playbook'&&positions[1]==='draft'&&['show','save','discard','publish'].includes(positions[2]))positions.splice(1,2,`draft_${positions[2]}`);
10
10
  const name=names[positions[0]]?.[positions[1]];if(!name)throw new Error(positions[0]==='playbook'?playbookUsageError:'Use analytics show --from ISO --to ISO; pipeline show; connections list|register --file PATH|report --file PATH|disconnect --file PATH.');
11
11
  if(flags.format&&(name!=='workspace_analytics_get'||!['json','csv'].includes(flags.format)))throw new Error('--format json|csv is only accepted for analytics show.');
12
- const fileNames=['playbook_save','playbook_draft_save','playbook_progress_record','workspace_connection_register','workspace_connection_report','workspace_connection_disconnect'];const withId=['playbook_get','playbook_progress_get','playbook_archive','playbook_restore','playbook_delete','playbook_draft_get','playbook_draft_save','playbook_draft_discard','playbook_draft_publish'].includes(name);if(positions.length!==(withId?3:2))throw new Error(positions[0]==='playbook'?`Unexpected arguments. Usage: audienti ${playbookCommands[positions[1]].usage}`:'Unexpected arguments.');
13
- if(fileNames.includes(name)&&!flags.file)throw new Error('Supply a JSON request with --file PATH.');if(!fileNames.includes(name)&&flags.file)throw new Error('--file is only accepted for mutations.');
14
- const input=fileNames.includes(name)?readJsonFile(flags.file,65536,'Request file'):{};if(!input||typeof input!=='object'||Array.isArray(input))throw new Error('Request must be an object.');if(flags.workspace)input.workspaceId=flags.workspace;if(withId)input[name==='playbook_progress_get'?'participationId':'id']=positions[2];
15
- if(flags['request-id']!==undefined&&name!=='playbook_draft_publish')throw new Error('--request-id is only accepted for playbook draft publish.');
12
+ const fileNames=['playbook_save','playbook_draft_save','playbook_progress_record','workspace_connection_register','workspace_connection_report','workspace_connection_disconnect'];const withId=['playbook_get','playbook_progress_get','playbook_archive','playbook_restore','playbook_delete','playbook_draft_get','playbook_draft_save','playbook_draft_discard','playbook_draft_publish','playbook_event_record'].includes(name);if(positions.length!==(withId?3:2))throw new Error(positions[0]==='playbook'?`Unexpected arguments. Usage: audienti ${playbookCommands[positions[1]].usage}`:'Unexpected arguments.');
13
+ const takesFile=fileNames.includes(name)||name==='playbook_event_record';if(fileNames.includes(name)&&!flags.file)throw new Error('Supply a JSON request with --file PATH.');if(!takesFile&&flags.file)throw new Error('--file is only accepted for mutations.');
14
+ const input=takesFile&&flags.file?readJsonFile(flags.file,65536,'Request file'):{};if(!input||typeof input!=='object'||Array.isArray(input))throw new Error('Request must be an object.');if(flags.workspace)input.workspaceId=flags.workspace;if(withId)input[['playbook_progress_get','playbook_event_record'].includes(name)?'participationId':'id']=positions[2];
15
+ if(flags['request-id']!==undefined&&!['playbook_draft_publish','playbook_event_record'].includes(name))throw new Error('--request-id is only accepted for playbook draft publish and playbook event.');
16
+ if(name==='playbook_event_record')input.requestId=flags['request-id']??input.requestId??randomUUID();else if(flags.reason!==undefined||flags.evidence!==undefined||flags.at!==undefined)throw new Error('--reason, --evidence and --at are only accepted for playbook event.');
16
17
  if(flags['expected-updated-at']!==undefined&&name!=='playbook_draft_discard')throw new Error('--expected-updated-at is only accepted for playbook draft discard.');
17
18
  if(flags['expected-updated-at']!==undefined)input.expectedUpdatedAt=flags['expected-updated-at'];
18
19
  // A generated request ID is printed, so a retry of a call that may have worked can use the same one.
19
20
  if(name==='playbook_draft_publish'){input.requestId=flags['request-id']??randomUUID();if(flags['request-id']===undefined)console.error(`requestId ${input.requestId} (retry with --request-id ${input.requestId} if this call may have worked)`);}
20
21
  if(flags.archived!==undefined&&(name!=='playbook_list'||!['true','false'].includes(flags.archived)))throw new Error(name==='playbook_list'?'--archived takes true or false, or no value.':'--archived is only accepted for playbook list.');
21
- for(const[key,value]of Object.entries(flags)){if(['workspace','profile','file','format','request-id','expected-updated-at'].includes(key))continue;if(key==='archived'){input.archived=value==='true';continue;}const field={campaign:'campaignId','playbook-version':'pinId',user:'userId',owner:'ownerUserId',offer:'offerId',audience:'audienceId'}[key]??key;input[field]=['limit','revision'].includes(key)?Number(value):value;}
22
+ for(const[key,value]of Object.entries(flags)){if(['workspace','profile','file','format','request-id','expected-updated-at'].includes(key))continue;if(key==='evidence'){input.evidence=value.map(item=>{const [kind,id,...extra]=item.split(':');if(extra.length||!['observation','conversation'].includes(kind)||!id)throw new Error('--evidence takes conversation:ID or observation:ID.');return{kind,id};});continue;}if(key==='archived'){input.archived=value==='true';continue;}const field={campaign:'campaignId','playbook-version':'pinId',user:'userId',owner:'ownerUserId',offer:'offerId',audience:'audienceId'}[key]??key;input[field]=['limit','revision'].includes(key)?Number(value):value;}
22
23
  const result=await callWorkspace(name,input,configuration(flags.profile));
23
24
  if(flags.format==='csv'){process.stdout.write(analyticsCsv(result?.data??result));return;}
24
25
  console.log(JSON.stringify(result));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.2.0-exora.21",
3
+ "version": "0.2.0-exora.22",
4
4
  "description": "Audienti CLI and MCP client for app.audienti.com",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,8 +6,8 @@ approved: inbound wins, and you record it. What the user approves is your answer
6
6
  ## What to do
7
7
 
8
8
  1. Read the conversation: `audienti prospects conversation list PERSON --workspace ID`.
9
- 2. Classify the reply as one of `interested`, `question`, `later`, `out_of_office`,
10
- `stop`, `bounce`, `referral` or `wrong_person` (the prospect is the wrong person and
9
+ 2. Classify the reply as one of `interested`, `question`, `later`, `not_interested` (a clear no thanks),
10
+ `out_of_office`, `stop`, `bounce`, `referral` or `wrong_person` (the prospect is the wrong person and
11
11
  names no one else). A stop, a bounce or a referral must carry its typed
12
12
  `result`, or the report is refused; see [Stop, bounce and referral](#stop-bounce-and-referral).
13
13
  3. Draft the answer from the mailbox or LinkedIn account the reply came to (the assignee's).
@@ -30,6 +30,10 @@ Use this skill when:
30
30
  playbook, so the two can be compared in Analytics.
31
31
  - Saving a playbook never changes campaigns already pinned to it. Pin the new
32
32
  revision on purpose.
33
+ - Give the playbook an off-ramp with reason `rejected` and one with reason `not_fit`. The default
34
+ rules use them: a reply that says no thanks ends the person in the first, and a `not_a_fit` event
35
+ ends the person in the second. Without them the event only shows in the history. The guide
36
+ `docs/guides/playbook-agent-guide.md` (Events and rules) lists every event.
33
37
  - Never invent results or proof points for the voice. Use the user's words.
34
38
  - Every first email ends with one plain sentence that lets the prospect opt out, such
35
39
  as "If this is not for you, reply and I will not write again." Say so in the first
@@ -16,7 +16,7 @@
16
16
  | `name` | 1 to 120 characters. Name the strategy, not the campaign. |
17
17
  | `stages` | 2 to 30 stages, in order. Leave it out for the default skeleton (see Defaults). |
18
18
  | `goalKey` | The `key` of the goal stage. Not the first stage. Left out, it is the last stage (with `stages` left out too, the skeleton's last stage). |
19
- | `offRamps` | Up to 30 ways out: `{ key, name, reason }`. `reason` is `rejected`, `not_fit`, `nurture`, `unresponsive` or `other`. The system sets `taken_over`, `bounced` and `skipped` itself; do not choose them. "Bad data" and "repair" are not off-ramps. |
19
+ | `offRamps` | Up to 30 ways out: `{ key, name, reason }`. `reason` is `rejected`, `not_fit`, `nurture`, `unresponsive` or `other`. The default rules end a person in the off-ramp with reason `rejected` when the reply says no thanks (`not_interested`), and in the one with reason `not_fit` when you record `not_a_fit`. A playbook without that off-ramp keeps the person where they are. The system sets `taken_over`, `bounced` and `skipped` itself; do not choose them. "Bad data" and "repair" are not off-ramps. |
20
20
  | `voice` | How messages should sound, in the user's style. |
21
21
  | `eventRules` | Optional: what happens when an event reaches a person, for any stage and step. A new playbook starts with three (below); leave the field out to keep them, or send the whole list to replace it. See Event rules. |
22
22
  | `rules` | Optional: `allowBackward` (boolean, default false): let a move go to an earlier stage. Approval of a move is a setting on the account, not a playbook rule. |
@@ -61,7 +61,7 @@ The other items are warnings; read them after each save and clear them:
61
61
  | --- | --- |
62
62
  | `key` | A UUID, unique in the playbook. Keep it across revisions. |
63
63
  | `name` | Unique in the playbook. |
64
- | `on` | The events that jump a prospect forward to this stage: `accept` (they accept your LinkedIn connection request, not a meeting invite), `reply` or `meeting` (a booked meeting). Do not list `accept` or `reply` on the goal stage unless a reply or an accept should count as a win. An event moves the prospect to the furthest later stage that lists it; the stages passed over are recorded as skipped, never as done. Leave it out for a stage people reach when the stage before it finishes, or when a person moves them. Do not set it on the first stage. People and the pipeline read "Starts when they reply" from it. The older `movesInWhen` text and the value `manual` are accepted and ignored. |
64
+ | `on` | The events that jump a prospect forward to this stage: `accept` (they accept your LinkedIn connection request, not a meeting invite), `reply`, `meeting_requested` (a meeting was asked for), `meeting_accepted` (they said yes, no time yet), `meeting_declined` or `meeting` (a booked meeting). Do not list `accept` or `reply` on the goal stage unless a reply or an accept should count as a win. An event moves the prospect to the furthest later stage that lists it; the stages passed over are recorded as skipped, never as done. Leave it out for a stage people reach when the stage before it finishes, or when a person moves them. Do not set it on the first stage. People and the pipeline read "Starts when they reply" from it. The older `movesInWhen` text and the value `manual` are accepted and ignored. |
65
65
  | `owner` | `Agent` or `Assignee` (the person assigned to the prospect). |
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. |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "https://github.com/omalab/exora",
3
- "sourceCommit": "4b513cb7f361cb647bc3a7a79caf80cac77cf5a0",
3
+ "sourceCommit": "d81f4cd72b9dedb857fc90c6178f1e704a56b366",
4
4
  "files": {
5
5
  "LICENSE": "220f297cfcd5c38eb64e6b4b4a77cab4ebc7f4686d4d99c6260a475d02ca11e5",
6
6
  "README.md": "7ff32cc8ac89dd54b64d7b39c0b41b1f37e154b7ea10ad5a7bceefc3df77d87f",
@@ -8,7 +8,7 @@
8
8
  "cli/account-intake-operations.mjs": "b43d764c628beabafad753d711ddb2af2c64276e4a6506ed0b854839091ea5db",
9
9
  "cli/account-intake.mjs": "aa77f2f96fd030f8830d6de278932186aea7c82cfda086a5a6cf569202feebc8",
10
10
  "cli/account-replies.mjs": "699e34e5c736ba316df8f164b4c50f82a7e4e2d5e51303e6b13944c3f2c854ab",
11
- "cli/account-reply-operations.mjs": "21f1441e708d9de8af83ec8f7444a5ca5d686fab90be669b904dbe1454eb7a13",
11
+ "cli/account-reply-operations.mjs": "08cf9642f040d2ef577ad704b893bde4a5b97d4930d3fadac24f2aebcb87ae24",
12
12
  "cli/agents.mjs": "e311dd08d1dc09648881a37eb9765bf6d155786f0b713204f51de29bc8134293",
13
13
  "cli/assignment-operations.mjs": "fc74e3fd79975cfcbac14891d5dec5f25e662777050fc46a8ad3aae5cf361c59",
14
14
  "cli/assignment.mjs": "90cb85e0df8aadca2ff8391c99e778b41261183b5f38e8c6c32afee2f98f871a",
@@ -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": "c910e05bc15312132b5790e783c0ba5fdfb959a3ffcee7e22c3caaf07e206784",
19
+ "cli/audienti.mjs": "8beaf1a8a052766a0478fdeda10de1ae0194e9bb095a8a1c937542adfaf0ede0",
20
20
  "cli/audit-operations.mjs": "afaf87d592a1e5f6bff2a8649a6a64a8f471950b7d99e98995d9205aa5910398",
21
21
  "cli/audit.mjs": "a7becaed67c58b063e668b5e66844b6dc15c11a63b428faa171b542ad922c001",
22
22
  "cli/budget-operations.mjs": "d74916551ef393e29734c6e13cf9021251e0ed126eeaa529e18cf2332146e1e0",
@@ -50,10 +50,10 @@
50
50
  "cli/outcome-operations.mjs": "cf133097799f240a006264a38a4c6bd4094078d6dec78a71679700ac3bdeacd5",
51
51
  "cli/outcomes.mjs": "765ccb112a90fc4d228b2a41e5e19c5e1f3e2fe14bc1d8128b0090fc95cdbf3f",
52
52
  "cli/owner-account-client.mjs": "91ab4d9b4b93c7feea668e5a3087cd9aa0766cf4cc657b7cac34e606d3a4b7a5",
53
- "cli/playbook-help.mjs": "cb3ccb3ac3538bdc360f505b6eb317cf07a9c58e75b9ddcf3b2bfbd96635eaf6",
54
- "cli/playbook-operations.mjs": "e8c065ce2ffeaf15994a218ed730e1aeb5563551d877c678412e116e62ac26a3",
53
+ "cli/playbook-help.mjs": "dad00cae19eb107f84c7934812800431535c6cf1dbcdcdd22c16522cfaddadf3",
54
+ "cli/playbook-operations.mjs": "431738b47b56091e10f0466f03673c602ae00653f8b455a20d39a8242d845e21",
55
55
  "cli/profiles.mjs": "45929b224ffc916b6dbd7a94e06ac09a1ff7feec430633b10d160c1608fc2d32",
56
- "cli/progress.mjs": "a3052d8ce40fbbf2f38ac0efef035d8f0a964838350d6e63e5fc38a738e0fa34",
56
+ "cli/progress.mjs": "6908f2937adb87a83ebefc1f4324ed18adac65228547b0524a474ac3513b5c67",
57
57
  "cli/prospecting-settings-operations.mjs": "1829eb2fe7208f58601988143756072429adbfe00f8deb5a52613bf650a9eacc",
58
58
  "cli/prospecting-settings.mjs": "b09f47c84416d9892d7d5b17dcf684cbb560e1cacb25513826ff04ef9307626e",
59
59
  "cli/question-operations.mjs": "c7070dd1b5ec9c5b0df93012ef08d0d6b4d6e5c8da267a37beb1acc5c3cf4ea1",
@@ -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": "e811f3db6ee2ff011d86d6e9f087d838a6e6915b4191dae66d6ad5e9a5ffaf95",
87
+ "package.json": "c79712c719b810476b4e3d929d95d67437c5c1d532308fff93c8d4ad41f5fc55",
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,9 +100,9 @@
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": "4a89c2310a7f4b9d3f01dee44557bd71bc63e9ccba84500f6d8f7291dd99c1b0",
103
+ "skills/getting-started/references/replies.md": "ec0f2f1fa4d2f13b3a6cac426baf6bd388b709313e3f97ea0dd546b950372cf7",
104
104
  "skills/getting-started/references/reply-checks.md": "85fe692ad6b7bc73e204de313b27d225be3da1a0af71e8e7bb9945ae511a0dc3",
105
- "skills/playbook-design/SKILL.md": "eec38bb7cd1586e11c6c27cccf328c1731d404f0767bd1df0c3ac8273495cb50",
106
- "skills/playbook-design/references/fields.md": "7d4a22451c3b77bb21a76d211d25a44b84a1bc46cc6524472ed121e77d2866aa"
105
+ "skills/playbook-design/SKILL.md": "56aa5692c1bbe3843f11819f0f9270988ddd0424b16f38db72a854864155407f",
106
+ "skills/playbook-design/references/fields.md": "7af0c8a2ad7b2ee83ff006fe4707a90c3edacdb8ccf83c09fa17f692b5fa2c81"
107
107
  }
108
108
  }