@audienti/cli 0.2.0-exora.26 → 0.2.0-exora.28

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
@@ -70,6 +70,7 @@ Discovery: prospects list|get ID|timeline ID [--limit N]|observation create|cita
70
70
  prospects contacts list|record PERSON [--file INPUT.json] [--request-id UUID] --workspace ID
71
71
  prospects contacts get|retire PERSON CONTACT --workspace ID
72
72
  prospects suppression get|set ID --workspace ID
73
+ prospects duplicates list --workspace ID | get|merge|dismiss PERSON --workspace ID [--primary PROFILE_ID] (same name and photo: the newer record is held until you decide)
73
74
  guardrails show|pause|resume --workspace ID
74
75
  dnc list|add VALUE|remove VALUE --workspace ID
75
76
  dnc import --file LIST.csv --workspace ID (at most 5,000 rows; reports added, skipped and invalid rows)
@@ -114,7 +115,8 @@ Search: workspace search --workspace ID --query TEXT [--people-only true] [--rec
114
115
  Invitation commands: workspace invitation show|accept|decline|retry|cancel ID
115
116
  Options: --workspace ID --name NAME --request-id UUID --email EMAIL --generation N
116
117
  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
118
+ Save an environment token: profile save --scope account:read|workspace:read|workspace:access|agents:manage --profile PATH
119
+ Tokens: token show [--profile PATH] (what the token can do; workspace:read can look but is refused on every change)
118
120
  Start local MCP stdio server: mcp (audienti mcp --help for the settings)
119
121
  --json writes the API response envelope.
120
122
  `;
@@ -177,6 +179,7 @@ async function main() {
177
179
  if(args[0]==='budgets'){const {budgetsCli}=await import('./budgets.mjs');await budgetsCli(args);return;}
178
180
  if(args[0]==='guardrails'||args[0]==='dnc'){const {guardrailsCli}=await import('./guardrails.mjs');await guardrailsCli(args);return;}
179
181
  if(args[0]==='prospects'&&args[1]==='suppression'){const {suppressionCli}=await import('./suppression.mjs');await suppressionCli(args);return;}
182
+ if(args[0]==='prospects'&&args[1]==='duplicates'){const {duplicatesCli}=await import('./duplicates.mjs');await duplicatesCli(args);return;}
180
183
  if(args[0]==='call'){const {callCli}=await import('./tools.mjs');await callCli(args);return;}
181
184
  if(args[0]==='tools'){const {toolsCli}=await import('./tools.mjs');await toolsCli(args);return;}
182
185
  if(['topics','posts'].includes(args[0])){const {contentCli}=await import('./content.mjs');await contentCli(args);return;}
@@ -201,7 +204,7 @@ async function main() {
201
204
  if(args[0]==='offer'){const {offerCli}=await import('./offers.mjs');await offerCli(args);return;}
202
205
  if (args[0] === 'agent') { const { agentCli } = await import('./agents.mjs'); await agentCli(args); return; }
203
206
  if (isAssignmentCommand(args)) { await assignmentCli(args); return; }
204
- if (['workspace', 'profile'].includes(args[0])) { const { workspaceCli } = await import('./workspaces.mjs'); await workspaceCli(args); return; }
207
+ if (['workspace', 'profile', 'token'].includes(args[0])) { const { workspaceCli } = await import('./workspaces.mjs'); await workspaceCli(args); return; }
205
208
  if (args[0] === 'profiles') { const { profilesCli } = await import('./profiles.mjs'); await profilesCli(args); return; }
206
209
  if (args[0] === 'skills') { const { skillsCli } = await import('./skills.mjs'); await skillsCli(args); return; }
207
210
  if (args[0] === 'mcp' && args.length === 1) { await import('./mcp.mjs'); return; }
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ const uuid = z.string().uuid();
3
+ const located = { workspaceId: uuid.optional(), prospectId: uuid };
4
+ const path = a => `/workspaces/${a.workspaceId}/prospects/${a.prospectId}/duplicate`;
5
+ export const duplicateOperations = {
6
+ workspace_possible_duplicates: {
7
+ description: 'List the people in a workspace that look like the same real person as another record (same name and the same profile photo, or the same name, company and title). The newer record is held: no outreach until a decision. Open flags only.',
8
+ schema: z.object({ workspaceId: uuid.optional() }).strict(), method: 'GET', path: a => `/workspaces/${a.workspaceId}/possible-duplicates`, scoped: true,
9
+ },
10
+ workspace_prospect_duplicate: {
11
+ description: 'Read the possible-duplicate flag of one person: the other record, why they match, and whether this person is held. duplicate is null when there is no open flag.',
12
+ schema: z.object(located).strict(), method: 'GET', path, scoped: true,
13
+ },
14
+ workspace_prospect_duplicate_merge: {
15
+ description: 'Merge a possible duplicate into one person. The older record survives and keeps both LinkedIn profiles, the email, the observations and the timelines. The LinkedIn profile that outreach uses is the main channel and the other LinkedIn channel is retired. Set primaryProfileId to choose it; without a choice the suggestedPrimary of the flag applies (the profile of the person who has an email, else the newest). Set keepBoth to true to leave both channels active.',
16
+ schema: z.object({ ...located, primaryProfileId: uuid.optional(), keepBoth: z.boolean().optional() }).strict(), method: 'POST', path: a => `${path(a)}/merge`, body: ({ workspaceId, prospectId, ...body }) => body, scoped: true,
17
+ },
18
+ workspace_prospect_duplicate_dismiss: {
19
+ description: 'Say these two records are not the same person. The hold lifts and the pair is never flagged again.',
20
+ schema: z.object(located).strict(), method: 'POST', path: a => `${path(a)}/dismiss`, body: () => ({}), scoped: true,
21
+ },
22
+ };
@@ -0,0 +1,22 @@
1
+ import { callWorkspace, configuration } from './workspaces.mjs';
2
+ // audienti prospects duplicates list | get ID | merge ID [--primary PROFILE_ID | --keep-both] | dismiss ID (all with --workspace ID)
3
+ export async function duplicatesCli(args) {
4
+ const flags = {}, positions = [], allowed = ['workspace', 'profile', 'primary'];
5
+ for (let i = 0; i < args.length; i++) {
6
+ const arg = args[i];
7
+ if (arg === '--json') continue;
8
+ if (arg === '--keep-both') { if (Object.hasOwn(flags, 'keepBoth')) throw Error('Unknown, repeated or incomplete duplicates option.'); flags.keepBoth = true; continue; }
9
+ if (!arg.startsWith('--')) { positions.push(arg); continue; }
10
+ const key = arg.slice(2);
11
+ if (!allowed.includes(key) || Object.hasOwn(flags, key) || args[i + 1] === undefined || args[i + 1].startsWith('--')) throw Error('Unknown, repeated or incomplete duplicates option.');
12
+ flags[key] = args[++i];
13
+ }
14
+ const verb = positions[2];
15
+ const usage = 'Use prospects duplicates list --workspace ID, or prospects duplicates get|merge|dismiss PERSON --workspace ID [--primary PROFILE_ID | --keep-both].';
16
+ if (positions[0] !== 'prospects' || positions[1] !== 'duplicates' || !['list', 'get', 'merge', 'dismiss'].includes(verb) || positions.length !== (verb === 'list' ? 3 : 4)) throw Error(usage);
17
+ if ((flags.primary !== undefined || flags.keepBoth) && verb !== 'merge') throw Error('--primary and --keep-both apply to merge only.');
18
+ if (flags.primary !== undefined && flags.keepBoth) throw Error('Use --primary or --keep-both, not both.');
19
+ const tool = { list: 'workspace_possible_duplicates', get: 'workspace_prospect_duplicate', merge: 'workspace_prospect_duplicate_merge', dismiss: 'workspace_prospect_duplicate_dismiss' }[verb];
20
+ const input = { workspaceId: flags.workspace, ...(verb === 'list' ? {} : { prospectId: positions[3] }), ...(flags.primary === undefined ? {} : { primaryProfileId: flags.primary }), ...(flags.keepBoth ? { keepBoth: true } : {}) };
21
+ console.log(JSON.stringify(await callWorkspace(tool, input, configuration(flags.profile))));
22
+ }
@@ -20,6 +20,7 @@ import { accountReplyOperations } from './account-reply-operations.mjs';
20
20
  import { accountIntakeOperations } from './account-intake-operations.mjs';
21
21
  import { recipientOperations } from './recipient-operations.mjs';
22
22
  import { suppressionOperations } from './suppression-operations.mjs';
23
+ import { duplicateOperations } from './duplicate-operations.mjs';
23
24
  import { guardrailsOperations } from './guardrails-operations.mjs';
24
25
  import { auditOperations } from './audit-operations.mjs';
25
26
  import { intentOperations } from './intent-operations.mjs';
@@ -61,6 +62,7 @@ export const operations = {
61
62
  ...managedAccountOperations,
62
63
  ...workFeedbackOperations,
63
64
  ...suppressionOperations,
65
+ ...duplicateOperations,
64
66
  ...guardrailsOperations,
65
67
  ...auditOperations,
66
68
  ...intentOperations,
@@ -85,6 +87,7 @@ export const operations = {
85
87
  workspace_member: {schema:z.object({...workspace,userId:uuid}).strict(),method:'GET',path:a=>`/workspaces/${a.workspaceId}/members/${a.userId}`,scoped:true},
86
88
  workspace_agent_access: { schema:z.object(workspace).strict(), method:'GET', path:a=>`/workspaces/${a.workspaceId}/agent-access`, scoped:true },
87
89
  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 },
90
+ 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
91
  workspace_initialize: { schema: z.object({}).strict(), method: 'POST', path: () => '/workspaces/bootstrap' },
89
92
  workspace_list: { schema: z.object({}).strict(), method: 'GET', path: () => '/workspaces' },
90
93
  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 +137,7 @@ export function configuration(path, scope='workspace:access') {
134
137
  path=profileFrom(path);
135
138
  const profile=readProfile(path);
136
139
  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:read'] ?? profile.tokens?.[scope] : profile.tokens?.[scope]), workspaceId:profile.workspaceId, profile };
140
+ 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
141
  }
139
142
  export function validateUrl(value) {
140
143
  let url;try {url=new URL(value);}catch{throw new Error('Set AUDIENTI_API_URL to an API URL ending with /api/v1.');}
@@ -201,7 +204,7 @@ export async function callWorkspace(name, args, config) {
201
204
  if(operation.localUpload)return uploadFileContent(input,(operationName,fields)=>callWorkspace(operationName,fields,config));
202
205
  // #333: an agent's own queue covers all its workspaces; a user's reads the selected workspace, as before.
203
206
  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.`}`); }
204
- 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.`);
207
+ 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.`);
205
208
  const target=new URL(`/api/v1${operation.path(input)}`,url);
206
209
  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.');}
207
210
  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;
@@ -210,7 +213,7 @@ export async function callWorkspace(name, args, config) {
210
213
  if(!response.ok) {
211
214
  const code = typeof result?.error?.code==='string' && /^[a-z_]+$/.test(result.error.code) ? result.error.code : 'request_failed';
212
215
  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;
213
- 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() : '';
216
+ 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() : '';
214
217
  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;
215
218
  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));
216
219
  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}}:{})}));
@@ -237,11 +240,13 @@ export async function workspaceCli(args) {
237
240
  else position.push(args[i]);
238
241
  }
239
242
  if(position[0]==='profile' && position[1]==='save') {
240
- if(!['account:read','workspace:access','agents:manage'].includes(flags.scope)) throw new Error('Specify --scope account:read, workspace:access or agents:manage.');
243
+ 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.');
241
244
  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.');
242
245
  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;
243
246
  }
244
247
  const config=configuration(flags.profile);const command=position.slice(1).join(' ');
248
+ // token show works with any saved user token, so it also tries the other slots when there is no workspace token.
249
+ 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'];
245
250
  let name, input={};
246
251
  if(command==='sending-settings') {name='workspace_sending_settings_get';input={workspaceId:flags.workspace,...(flags.user?{userId:flags.user}:{})};}
247
252
  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}:{})};}
@@ -251,6 +256,7 @@ export async function workspaceCli(args) {
251
256
  else if(position[1]==='usage'&&position[2]==='call'&&position.length===4) {name='workspace_usage_call';input={workspaceId:flags.workspace,callId:position[3]};}
252
257
  else if(command==='agent-access') {name='workspace_agent_access';input={workspaceId:flags.workspace};}
253
258
  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')};}
259
+ else if(position[0]==='token'&&command==='show') name='token_show';
254
260
  else if(command==='init') name='workspace_initialize';
255
261
  else if(command==='list') name='workspace_list';
256
262
  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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@audienti/cli",
3
- "version": "0.2.0-exora.26",
3
+ "version": "0.2.0-exora.28",
4
4
  "description": "Audienti CLI and MCP client for app.audienti.com",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "repository": "https://github.com/omalab/exora",
3
- "sourceCommit": "12b6349e33300c2867a516871532b009361e2af3",
3
+ "sourceCommit": "3ace57bf9aaff4acbeb92b8bbb5216495011eead",
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": "fa7a7e479eb95d60678fa7b24dbbbbc84aacddcfdcd8edc7373f1859b86c8ba4",
19
+ "cli/audienti.mjs": "7f081c32c0ad5abf84fb5dc6dc504ebd2371e530e50828767f2f0470bca509a7",
20
20
  "cli/audit-operations.mjs": "afaf87d592a1e5f6bff2a8649a6a64a8f471950b7d99e98995d9205aa5910398",
21
21
  "cli/audit.mjs": "a7becaed67c58b063e668b5e66844b6dc15c11a63b428faa171b542ad922c001",
22
22
  "cli/budget-operations.mjs": "d74916551ef393e29734c6e13cf9021251e0ed126eeaa529e18cf2332146e1e0",
@@ -32,6 +32,8 @@
32
32
  "cli/decision.mjs": "3d89d48b333e2620bce0191cf528ef25541ee841a7f8ae6d60249aead434b651",
33
33
  "cli/discovery-operations.mjs": "9b6873d586d27bf67106a592db8ced75da1abf1a1330f192e69d34f81b2d080f",
34
34
  "cli/discovery.mjs": "34b3794ee6ca539dbbccef77ec2085fd5411023046bdfb9b83e2537414542a41",
35
+ "cli/duplicate-operations.mjs": "a07ba6ff411bc27246a454ee43b0508e024974d2fa2b4258c709583767c17950",
36
+ "cli/duplicates.mjs": "341e5c8dae67a819340abdad3502470577cf8b6375bf60acb00068c7188344a7",
35
37
  "cli/file-operations.mjs": "564f334dd9d52e9c56f44d58b5c3b0bda59ff72f54126b755bc472144069349e",
36
38
  "cli/files.mjs": "2221b067d6ade7ef5cc8965929aea2eb485781953d3f2b50600995b65fbdedae",
37
39
  "cli/guardrails-operations.mjs": "aa5a911663133b3287bea805df9e2a5d8b5bbc4a3df972c52f91fee315954ded",
@@ -85,8 +87,8 @@
85
87
  "cli/work-feedback.mjs": "f3b77ecfd7ea1aba9c9f03c9d894137547e9c5575d9daa359ed94505ad025852",
86
88
  "cli/work-operations.mjs": "8b3a84ca15e8e4ad79ef92ced4c5adaffd5129f630e3bfc2c74a844a90867600",
87
89
  "cli/work.mjs": "751107ecc3de5a6be9db4a068cddc41f3ad8e77c87e59686eaf522862caf9e9f",
88
- "cli/workspaces.mjs": "fb1b809eafa0c761d0e6124a172acf55056488e7d8a0e25ceeeb1cd6a036fb5c",
89
- "package.json": "91e96eb6bc639bea60b9741498f268220ef56ef70f3d6ce8baf07f82f2082f6c",
90
+ "cli/workspaces.mjs": "b2621958f426ed12db6ac7e7484c9c7d4eb84d25474d3090d1f75c667ebcd36f",
91
+ "package.json": "853558fe662b32288defea4af0e62aa05922eccb1ff32fc7e11273fd0cd6d5b2",
90
92
  "skills/agent-rooms/SKILL.md": "08f917ff85fee1d23499a4499056ce845b39ce6727029b104819dad41ee87cc2",
91
93
  "skills/agent-rooms/references/commands.md": "ef5afa5543ca9da967a147f638774647998ea1c1a2a821959f3bf87d54c292bd",
92
94
  "skills/audienti/SKILL.md": "8f2124065bfc3a005b44e194e7dcce9bbf14062ab332e7db66d441eeccb518ed",