@indemn/cli 1.7.0 → 1.7.2

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.
Files changed (44) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CONTRIBUTING.md +2 -2
  4. package/dist/mcp/registry.d.ts.map +1 -1
  5. package/dist/mcp/registry.js +6 -4
  6. package/dist/mcp/registry.js.map +1 -1
  7. package/dist/mcp/tool-meta.d.ts.map +1 -1
  8. package/dist/mcp/tool-meta.js +35 -9
  9. package/dist/mcp/tool-meta.js.map +1 -1
  10. package/dist/mcp/tools.d.ts.map +1 -1
  11. package/dist/mcp/tools.js +232 -49
  12. package/dist/mcp/tools.js.map +1 -1
  13. package/dist/report/styles.d.ts +1 -0
  14. package/dist/report/styles.d.ts.map +1 -1
  15. package/dist/report/styles.js +5 -1
  16. package/dist/report/styles.js.map +1 -1
  17. package/dist/sdk/auth.js +2 -2
  18. package/dist/sdk/auth.js.map +1 -1
  19. package/dist/sdk/client.d.ts.map +1 -1
  20. package/dist/sdk/client.js +11 -1
  21. package/dist/sdk/client.js.map +1 -1
  22. package/dist/sdk/config-change.d.ts +4 -0
  23. package/dist/sdk/config-change.d.ts.map +1 -1
  24. package/dist/sdk/config-change.js +27 -5
  25. package/dist/sdk/config-change.js.map +1 -1
  26. package/dist/sdk/kb.d.ts +12 -0
  27. package/dist/sdk/kb.d.ts.map +1 -1
  28. package/dist/sdk/kb.js +45 -5
  29. package/dist/sdk/kb.js.map +1 -1
  30. package/dist/sdk/voice.js +5 -5
  31. package/dist/sdk/voice.js.map +1 -1
  32. package/package.json +4 -6
  33. package/src/mcp/registry.ts +9 -4
  34. package/src/mcp/tool-meta.ts +35 -9
  35. package/src/mcp/tools.ts +235 -49
  36. package/src/report/styles.ts +5 -1
  37. package/src/sdk/auth.ts +2 -2
  38. package/src/sdk/client.ts +5 -1
  39. package/src/sdk/config-change.ts +27 -5
  40. package/src/sdk/kb.ts +49 -8
  41. package/src/sdk/voice.ts +5 -5
  42. package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +1 -0
  43. package/test/mcp/darwin-fixes.test.ts +231 -0
  44. package/test/mcp/tools-snapshot.test.ts +1 -1
@@ -20,8 +20,9 @@ type Args = Record<string, unknown>;
20
20
  /** `confirmedByPerson` is true when the host asked the person and they agreed. */
21
21
  type Handler = (args: any, meta: { confirmedByPerson: boolean }) => Promise<CallToolResult>;
22
22
 
23
+ // Compact JSON: the host is a model, and pretty-printing a result costs it about a fifth more tokens.
23
24
  function text(data: unknown, isError = false): CallToolResult {
24
- return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], ...(isError ? { isError: true } : {}) };
25
+ return { content: [{ type: 'text', text: JSON.stringify(data) }], ...(isError ? { isError: true } : {}) };
25
26
  }
26
27
 
27
28
  /**
@@ -34,14 +35,14 @@ export const MAX_RESULT_CHARS = 80_000;
34
35
 
35
36
  /** Keep a result inside the size limit: trim a list and say so, or cut other text with a note. */
36
37
  function fit(body: { context: ResultContext; data: unknown }): CallToolResult {
37
- const full = JSON.stringify(body, null, 2);
38
+ const full = JSON.stringify(body);
38
39
  if (full.length <= MAX_RESULT_CHARS) return text(body);
39
40
  const hint = 'Result was too large. Ask for less: use limit/page, a filter, or response_format "concise".';
40
41
  const list = Array.isArray(body.data) ? body.data : (body.data as { items?: unknown[] } | null)?.items;
41
42
  if (Array.isArray(list)) {
42
43
  const shaped = (keep: number) => ({ context: body.context, data: { items: list.slice(0, keep), returned: keep, total: list.length, truncated: true, hint } });
43
44
  let keep = list.length;
44
- while (keep > 0 && JSON.stringify(shaped(keep), null, 2).length > MAX_RESULT_CHARS) keep = Math.floor(keep * 0.7);
45
+ while (keep > 0 && JSON.stringify(shaped(keep)).length > MAX_RESULT_CHARS) keep = Math.floor(keep * 0.7);
45
46
  return text(shaped(keep));
46
47
  }
47
48
  return { content: [{ type: 'text', text: `${full.slice(0, MAX_RESULT_CHARS)}\n... [truncated: ${full.length - MAX_RESULT_CHARS} more characters. ${hint}]` }] };
@@ -202,7 +203,11 @@ export function createRegistry(
202
203
  return { content: [{ type: 'text', text: `Error: Not found: ${found.missing.join(', ')}. Nothing was changed and nothing was asked. Look it up again (list tools give the ids) and retry.` }], isError: true };
203
204
  }
204
205
  const names: Record<string, string> = found.names;
205
- const named = (s: string) => Object.entries(names).reduce((t, [id, label]) => t.split(id).join(`"${label}"`), s);
206
+ // Whole ids only, and not inside a link: an org id in a url must stay an id.
207
+ const named = (s: string) => Object.entries(names).reduce(
208
+ (t, [id, label]) => t.replace(new RegExp(`(?<![\\w/=.-])${id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![\\w/-])`, 'g'), `"${label}"`),
209
+ s,
210
+ );
206
211
  const base = named(extra.preview ? await extra.preview(args) : meta.describe ? meta.describe(args) : defaultPreview(name, meta, args));
207
212
  const ctx = await opts.context();
208
213
  const orgLabel = org ? org.name ?? names[org.id] ?? org.id : undefined;
@@ -47,7 +47,7 @@ export const TOOL_META: Record<string, ToolMeta> = {
47
47
  get_agent: read('Get agent'),
48
48
  create_agent: write('Create agent'),
49
49
  update_agent: { ...write('Update agent', { idempotentHint: true }), guard: true, describe: (a) => `This will change agent ${a.agent_id}.` },
50
- delete_agent: { ...destroy('Delete agent'), guard: true, describe: (a) => `This will delete agent ${a.agent_id}. It cannot be restored from the CLI.` },
50
+ delete_agent: { ...destroy('Delete agent'), guard: true, describe: (a) => `This will delete agent ${a.agent_id}. It cannot be restored.` },
51
51
  agent_summary: read('Summarize agent'),
52
52
  agent_stats: read('Agent conversation metrics'),
53
53
  set_agent_lifecycle: {
@@ -93,8 +93,9 @@ export const TOOL_META: Record<string, ToolMeta> = {
93
93
  describe: (a) => `This will create ${((a.functions as unknown[] | undefined) ?? []).length} skill(s) on agent ${a.agent_id}.`,
94
94
  },
95
95
  list_function_parameters: read('List skill parameters'),
96
- add_function_parameter: write('Add skill parameter'),
97
- update_function_parameter: write('Update skill parameter', { idempotentHint: true }),
96
+ // Guarded like update_function: asked only when the agent may be live (liveChange in tools.ts).
97
+ add_function_parameter: { ...write('Add skill parameter'), guard: true },
98
+ update_function_parameter: { ...write('Update skill parameter', { idempotentHint: true }), guard: true },
98
99
  delete_function_parameter: { ...destroy('Delete skill parameter'), guard: true, describe: (a) => `This will delete parameter ${a.parameter_id} of skill ${a.function_id} on agent ${a.agent_id}.` },
99
100
  list_master_functions: { ...read('List skill templates'), paginate: true },
100
101
  list_llm_models: { ...read('List LLM models'), paginate: true },
@@ -109,12 +110,28 @@ export const TOOL_META: Record<string, ToolMeta> = {
109
110
  add_kb_data: write('Add knowledge base entry'),
110
111
  scrape_url: write('Scrape a website into a knowledge base', { openWorldHint: true }),
111
112
  upload_kb_file: write('Upload file to knowledge base'),
112
- update_kb_data: write('Update knowledge base entry', { idempotentHint: true }),
113
+ update_kb_data: {
114
+ ...write('Update knowledge base entry', { idempotentHint: true }),
115
+ guard: true,
116
+ describe: (a) => `This will change entry ${a.source_id} of knowledge base ${a.kb_id}${a.answer ? ` to the answer: "${String(a.answer)}"` : ''}.`,
117
+ },
113
118
  delete_kb_data: { ...destroy('Delete knowledge base entry'), guard: true, describe: (a) => `This will delete source ${a.source_id} from knowledge base ${a.kb_id}, with its vectors.` },
114
- link_kb: write('Link knowledge bases to agent', { idempotentHint: true }),
119
+ link_kb: {
120
+ ...write('Link knowledge bases to agent', { idempotentHint: true }),
121
+ guard: true,
122
+ describe: (a) => `This will let agent ${a.agent_id} answer from knowledge base(s) ${((a.kb_ids as string[] | undefined) ?? []).join(', ')}; customers get answers from them right away.`,
123
+ },
115
124
  unlink_kb: { ...destroy('Unlink knowledge base from agent'), guard: true, describe: (a) => `This will unlink knowledge base ${a.kb_id} from agent ${a.agent_id}; the agent stops using it immediately.` },
116
125
  export_kb: read('Export knowledge base'),
117
- import_kb: { ...destroy('Import into knowledge base'), guard: true, describe: (a) => `This will import new content into knowledge base ${a.kb_id}.` },
126
+ import_kb: {
127
+ ...destroy('Import into knowledge base'),
128
+ guard: true,
129
+ describe: (a) => {
130
+ const sources = ((a.data as { sources?: Array<{ name?: string; url?: string }> } | undefined)?.sources ?? []);
131
+ const files = sources.map((s) => s.name ?? s.url?.split('?')[0].split('/').pop() ?? 'a file');
132
+ return `This will import ${files.length ? files.map((f) => `"${f}"`).join(', ') : 'new content'} into knowledge base ${a.kb_id}.`;
133
+ },
134
+ },
118
135
  kb_chunks: read('Show what the agent learned from a source'),
119
136
  reimport_kb_source: {
120
137
  ...write('Re-crawl a website source', { openWorldHint: true }),
@@ -144,7 +161,8 @@ export const TOOL_META: Record<string, ToolMeta> = {
144
161
  attach_phone_number: { ...write('Attach phone number'), guard: true, describe: (a) => `This will attach ${a.phone_number} to agent ${a.agent_id}; its voice settings reset to the defaults. Numbers the org does not own are refused (never bought).` },
145
162
  search_phone_numbers: read('Search phone numbers to buy'),
146
163
  get_phone_number: read('Get phone number details'),
147
- buy_phone_number: { ...write('Buy phone number'), guard: true, describe: (a) => `This will BUY ${a.phone_number} from Twilio and attach it to agent ${a.agent_id}. The org pays for it every month, and it cannot be undone from here.` },
164
+ // Money leaves the org and nothing here undoes it: shown like a delete.
165
+ buy_phone_number: { ...destroy('Buy phone number'), guard: true, describe: (a) => `This will BUY ${a.phone_number} from Twilio and attach it to agent ${a.agent_id}. The org pays for it every month, and it cannot be undone from here.` },
148
166
  detach_phone_number: { ...destroy('Detach phone number'), guard: true, describe: (a) => `This will detach ${a.phone_number} from agent ${a.agent_id}; calls to it stop reaching the agent.` },
149
167
  voice_preview: write('Start a test voice call'),
150
168
 
@@ -166,8 +184,9 @@ export const TOOL_META: Record<string, ToolMeta> = {
166
184
  get_custom_chart: read('Get custom chart data'),
167
185
  get_org_settings: read('Get org settings'),
168
186
  set_org_timezone: { ...write('Set org timezone', { idempotentHint: true }), guard: true, describe: (a) => `This will set the org timezone to ${a.timezone}.` },
169
- set_business_hours: { ...destroy('Replace business hours', { idempotentHint: true }), guard: true, describe: (a) => `This will replace the org business hours (enabled: ${a.enabled}).` },
187
+ set_business_hours: { ...destroy('Replace business hours', { idempotentHint: true }), guard: true },
170
188
  add_holiday: { ...write('Add holiday'), guard: true, describe: (a) => `This will close the org on ${a.date} (${a.name}).` },
189
+ remove_holiday: { ...destroy('Remove holiday'), guard: true, describe: (a) => `This will remove the holiday on ${a.date}; the org is open that day again.` },
171
190
  set_org_admin_settings: { ...write('Change org admin settings', { idempotentHint: true }), guard: true },
172
191
  list_audit_logs: read('List audit log'),
173
192
 
@@ -178,7 +197,14 @@ export const TOOL_META: Record<string, ToolMeta> = {
178
197
  update_ticket: write('Update, assign or close a ticket'),
179
198
  archive_ticket: { ...destroy('Archive ticket'), guard: true, describe: (a) => `This will archive ticket ${a.ticket_id}.` },
180
199
  list_ticket_rules: read('List ticket rules'),
181
- save_ticket_rule: write('Create or change a ticket rule'),
200
+ // A standing rule: agents open tickets on their own from now on.
201
+ save_ticket_rule: {
202
+ ...write('Create or change a ticket rule'),
203
+ guard: true,
204
+ describe: (a) => a.rule_id
205
+ ? `This will change ticket rule ${a.rule_id}${a.prompt ? ` to open a ticket when: ${a.prompt}` : ''}${a.enabled === false ? ' and turn it off' : ''}.`
206
+ : `This will add ticket rule "${a.name ?? 'untitled'}": agents open a ticket whenever ${a.prompt}, for ${(a.agent_ids as string[] | undefined)?.length ? 'the chosen agents' : 'every agent'}, from now on.`,
207
+ },
182
208
  delete_ticket_rule: { ...destroy('Delete ticket rule'), guard: true, describe: (a) => `This will delete ticket rule ${a.rule_id}; agents stop opening tickets for it.` },
183
209
 
184
210
  // Billing (read-only)