@indemn/cli 1.7.4 → 1.7.6

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 (137) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.mcp.json +1 -1
  4. package/README.md +4 -4
  5. package/dist/cli/auth.d.ts.map +1 -1
  6. package/dist/cli/auth.js +10 -6
  7. package/dist/cli/auth.js.map +1 -1
  8. package/dist/cli/eval.d.ts.map +1 -1
  9. package/dist/cli/eval.js +8 -2
  10. package/dist/cli/eval.js.map +1 -1
  11. package/dist/cli/index.js +2 -2
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/cli/init.d.ts +1 -1
  14. package/dist/cli/init.d.ts.map +1 -1
  15. package/dist/cli/init.js +17 -6
  16. package/dist/cli/init.js.map +1 -1
  17. package/dist/cli/org.d.ts.map +1 -1
  18. package/dist/cli/org.js +30 -0
  19. package/dist/cli/org.js.map +1 -1
  20. package/dist/cli/utils/output.d.ts +0 -1
  21. package/dist/cli/utils/output.d.ts.map +1 -1
  22. package/dist/cli/utils/output.js +0 -4
  23. package/dist/cli/utils/output.js.map +1 -1
  24. package/dist/cli/voice.d.ts.map +1 -1
  25. package/dist/cli/voice.js +4 -1
  26. package/dist/cli/voice.js.map +1 -1
  27. package/dist/core/org-resolver.d.ts +4 -0
  28. package/dist/core/org-resolver.d.ts.map +1 -1
  29. package/dist/core/org-resolver.js +11 -0
  30. package/dist/core/org-resolver.js.map +1 -1
  31. package/dist/core/runtime.d.ts +9 -0
  32. package/dist/core/runtime.d.ts.map +1 -1
  33. package/dist/core/runtime.js +49 -0
  34. package/dist/core/runtime.js.map +1 -1
  35. package/dist/core/scope.d.ts +0 -1
  36. package/dist/core/scope.d.ts.map +1 -1
  37. package/dist/core/scope.js.map +1 -1
  38. package/dist/mcp/registry.d.ts.map +1 -1
  39. package/dist/mcp/registry.js +15 -2
  40. package/dist/mcp/registry.js.map +1 -1
  41. package/dist/mcp/server.d.ts +1 -1
  42. package/dist/mcp/server.d.ts.map +1 -1
  43. package/dist/mcp/server.js +2 -19
  44. package/dist/mcp/server.js.map +1 -1
  45. package/dist/mcp/tool-meta.d.ts.map +1 -1
  46. package/dist/mcp/tool-meta.js +11 -5
  47. package/dist/mcp/tool-meta.js.map +1 -1
  48. package/dist/mcp/tools.d.ts.map +1 -1
  49. package/dist/mcp/tools.js +12 -1
  50. package/dist/mcp/tools.js.map +1 -1
  51. package/dist/report/export.d.ts.map +1 -1
  52. package/dist/report/export.js +2 -3
  53. package/dist/report/export.js.map +1 -1
  54. package/dist/report/generate.d.ts.map +1 -1
  55. package/dist/report/generate.js +3 -3
  56. package/dist/report/generate.js.map +1 -1
  57. package/dist/sdk/auth.d.ts +8 -3
  58. package/dist/sdk/auth.d.ts.map +1 -1
  59. package/dist/sdk/auth.js +13 -5
  60. package/dist/sdk/auth.js.map +1 -1
  61. package/dist/sdk/chat.d.ts.map +1 -1
  62. package/dist/sdk/chat.js +1 -0
  63. package/dist/sdk/chat.js.map +1 -1
  64. package/dist/sdk/client.d.ts +11 -9
  65. package/dist/sdk/client.d.ts.map +1 -1
  66. package/dist/sdk/client.js +50 -67
  67. package/dist/sdk/client.js.map +1 -1
  68. package/dist/sdk/config-change.js +1 -1
  69. package/dist/sdk/config-change.js.map +1 -1
  70. package/dist/sdk/conversations.js +1 -1
  71. package/dist/sdk/conversations.js.map +1 -1
  72. package/dist/sdk/functions.js +2 -2
  73. package/dist/sdk/functions.js.map +1 -1
  74. package/dist/sdk/jarvis-chat.js +2 -2
  75. package/dist/sdk/jarvis-chat.js.map +1 -1
  76. package/dist/sdk/kb.d.ts.map +1 -1
  77. package/dist/sdk/kb.js +3 -2
  78. package/dist/sdk/kb.js.map +1 -1
  79. package/dist/sdk/orgs.d.ts +12 -0
  80. package/dist/sdk/orgs.d.ts.map +1 -1
  81. package/dist/sdk/orgs.js +16 -6
  82. package/dist/sdk/orgs.js.map +1 -1
  83. package/dist/sdk/session.d.ts +2 -9
  84. package/dist/sdk/session.d.ts.map +1 -1
  85. package/dist/sdk/session.js +2 -27
  86. package/dist/sdk/session.js.map +1 -1
  87. package/dist/sdk/test-chat.d.ts.map +1 -1
  88. package/dist/sdk/test-chat.js +17 -6
  89. package/dist/sdk/test-chat.js.map +1 -1
  90. package/package.json +1 -1
  91. package/skills/sandbox/SKILL.md +26 -0
  92. package/src/cli/auth.ts +9 -6
  93. package/src/cli/eval.ts +6 -2
  94. package/src/cli/index.ts +2 -2
  95. package/src/cli/init.ts +16 -7
  96. package/src/cli/org.ts +29 -0
  97. package/src/cli/utils/output.ts +0 -4
  98. package/src/cli/voice.ts +3 -1
  99. package/src/core/org-resolver.ts +18 -0
  100. package/src/core/runtime.ts +46 -0
  101. package/src/core/scope.ts +0 -1
  102. package/src/mcp/registry.ts +17 -2
  103. package/src/mcp/server.ts +2 -19
  104. package/src/mcp/tool-meta.ts +11 -5
  105. package/src/mcp/tools.ts +14 -1
  106. package/src/report/export.ts +2 -1
  107. package/src/report/generate.tsx +3 -2
  108. package/src/sdk/auth.ts +15 -5
  109. package/src/sdk/chat.ts +1 -0
  110. package/src/sdk/client.ts +50 -72
  111. package/src/sdk/config-change.ts +1 -1
  112. package/src/sdk/conversations.ts +1 -1
  113. package/src/sdk/functions.ts +2 -2
  114. package/src/sdk/jarvis-chat.ts +2 -2
  115. package/src/sdk/kb.ts +3 -2
  116. package/src/sdk/orgs.ts +28 -4
  117. package/src/sdk/session.ts +2 -25
  118. package/src/sdk/test-chat.ts +16 -6
  119. package/test/cli/init.test.ts +14 -0
  120. package/test/core/org-resolver.test.ts +38 -2
  121. package/test/core/scope.test.ts +12 -0
  122. package/test/helpers/mcp-pair.ts +10 -1
  123. package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +1 -0
  124. package/test/mcp/darwin-fixes.test.ts +18 -17
  125. package/test/mcp/registry.test.ts +11 -0
  126. package/test/mcp/sandbox.test.ts +45 -0
  127. package/test/mcp/shaping.test.ts +18 -0
  128. package/test/mcp/tools-snapshot.test.ts +5 -1
  129. package/test/sdk/auth.test.ts +11 -2
  130. package/test/sdk/client.test.ts +13 -0
  131. package/test/sdk/config-change.test.ts +1 -1
  132. package/test/sdk/kb-upload.test.ts +21 -0
  133. package/test/sdk/orgs.test.ts +33 -0
  134. package/test/sdk/renewal.test.ts +43 -64
  135. package/test/sdk/session.test.ts +1 -23
  136. package/test/sdk/skills.test.ts +20 -18
  137. package/test/sdk/test-chat.test.ts +29 -0
@@ -13,6 +13,7 @@ import { registerTools } from '../../src/mcp/tools.js';
13
13
 
14
14
  const ORG = 'aaaaaaaaaaaaaaaaaaaaaaaa';
15
15
  const AGENT = '111111111111111111111111';
16
+ const agentFound = on('GET', `/v2/bots/${AGENT}`, { json: { success: true, data: [{ _id: AGENT, name: 'Bot' }] } }); // named in approvals
16
17
 
17
18
  describe('skill names', () => {
18
19
  it('reduces spelling variants of the FAQ retriever to one key, like copilot', () => {
@@ -64,9 +65,9 @@ describe('skills SDK and tools', () => {
64
65
  on('GET', `/bots/${AGENT}/functions`, { json: { success: true, data: [{ _id: 'f1', type: 'rest_api',
65
66
  configurationSchema: { endpoint: 'https://x/date', method: 'POST', payload: '{"date_str":"{{event_date}}","n":"{{other}}"}', authorization: { type: 'no-auth' } } }] } }),
66
67
  on('POST', '/functions/f1/test-rest-api', { json: { statusCode: 200, response: { valid: true } } }),
67
- ]);
68
+ agentFound]);
68
69
  const pair = await connectPair((server) => { registerTools(server, new IndemnClient()); });
69
- await pair.call('test_function', { agent_id: AGENT, function_id: 'f1', params: { event_date: '2027-06-12' } });
70
+ await pair.call('test_function', { confirm: true, agent_id: AGENT, function_id: 'f1', params: { event_date: '2027-06-12' } });
70
71
  expect(calls.find((c) => c.url.endsWith('/test-rest-api'))?.body).toEqual({ endpoint: 'https://x/date', method: 'POST',
71
72
  payload: '{"date_str":"2027-06-12","n":"{{other}}"}', authorization: { type: 'no-auth' } });
72
73
  await pair.close();
@@ -78,7 +79,7 @@ describe('skills SDK and tools', () => {
78
79
  const { calls } = recordFetch([
79
80
  on('GET', '/functions/f1/parameters', { json: { success: true, data: [saved] } }),
80
81
  on('POST', '/functions/f1/parameters', { json: { success: true, data: [saved] } }),
81
- ]);
82
+ agentFound]);
82
83
  const sdk = new FunctionsSDK(new IndemnClient());
83
84
  await sdk.addParameter(AGENT, 'f1', { name: 'coverage', type: 'string', required: true, allowedValues: ['Liability', 'Both'] });
84
85
  const posts = () => calls.filter((c) => c.method === 'POST').map((c) => (c.body as unknown[])[0]);
@@ -100,7 +101,7 @@ describe('skills SDK and tools', () => {
100
101
  });
101
102
 
102
103
  it('bulk-creates with the body copilot expects', async () => {
103
- const { calls } = recordFetch([on('POST', '/functions/bulk-create', { json: { success: true, data: [{ _id: 'f1' }] } })]);
104
+ const { calls } = recordFetch([on('POST', '/functions/bulk-create', { json: { success: true, data: [{ _id: 'f1' }] } }), agentFound]);
104
105
  await new FunctionsSDK(new IndemnClient()).bulkCreate(AGENT, [{ type: 'rest_api', name: 'quote', description: 'q' }]);
105
106
  expect(calls[0].body).toEqual({ functions: [{ type: 'rest_api', name: 'quote', description: 'q' }] });
106
107
  });
@@ -116,31 +117,32 @@ describe('skills SDK and tools', () => {
116
117
  on('GET', `/bots/${AGENT}/functions`, { json: { success: true, data: [{ _id: 'f1', type: 'rest_api', description: 'Checks a date',
117
118
  configurationSchema: { endpoint: 'https://x/date', method: 'POST', authorization: { type: 'bearer-token', token: 't' } } }] } }),
118
119
  on('PUT', `/bots/${AGENT}/functions/f1`, { json: { success: true, data: { _id: 'f1' } } }),
119
- ]);
120
+ on('GET', `/v1-agents/${AGENT}`, { json: { id: AGENT, lifecycle: 'prototype' } }), // not live: no approval asked
121
+ , agentFound]);
120
122
  const pair = await connectPair((server) => { registerTools(server, new IndemnClient()); });
121
- await pair.call('create_function', { agent_id: AGENT, type: 'rest_api', name: 'validate_date', description: 'Checks a date', url: 'https://x/date', method: 'post', payload: { date_str: '{{event_date}}' } });
123
+ await pair.call('create_function', { confirm: true, agent_id: AGENT, type: 'rest_api', name: 'validate_date', description: 'Checks a date', url: 'https://x/date', method: 'post', payload: { date_str: '{{event_date}}' } });
122
124
  expect(calls.find((c) => c.method === 'POST')?.body).toEqual({ type: 'rest_api', name: 'validate_date', description: 'Checks a date' });
123
125
  expect(calls.filter((c) => c.method === 'PUT')[0].body).toEqual({ type: 'rest_api', description: 'Checks a date',
124
126
  configurationSchema: { endpoint: 'https://x/date', method: 'POST', payload: '{"date_str":"{{event_date}}"}', authorization: { type: 'no-auth' }, craftResponse: true } });
125
127
  await pair.call('update_function', { agent_id: AGENT, function_id: 'f1', response_field: 'data.valid' });
126
128
  expect(calls.filter((c) => c.method === 'PUT')[1].body).toEqual({ type: 'rest_api', description: 'Checks a date',
127
129
  configurationSchema: { endpoint: 'https://x/date', method: 'POST', authorization: { type: 'bearer-token', token: 't' }, responseField: 'data.valid' } });
128
- await pair.call('create_function', { agent_id: AGENT, type: 'rest_api', name: 'said', url: 'https://x/a', method: 'GET', output_instructions: 'Your quote is {{response.total}}.' });
130
+ await pair.call('create_function', { confirm: true, agent_id: AGENT, type: 'rest_api', name: 'said', url: 'https://x/a', method: 'GET', output_instructions: 'Your quote is {{response.total}}.' });
129
131
  expect(calls.filter((c) => c.method === 'PUT')[2].body.configurationSchema).toMatchObject({ outputInstructions: 'Your quote is {{response.total}}.' });
130
132
  expect(calls.filter((c) => c.method === 'PUT')[2].body.configurationSchema.craftResponse).toBeUndefined();
131
- const missing = await pair.call('create_function', { agent_id: AGENT, type: 'rest_api', name: 'no_url' });
133
+ const missing = await pair.call('create_function', { confirm: true, agent_id: AGENT, type: 'rest_api', name: 'no_url' });
132
134
  expect(missing.isError).toBe(true);
133
135
  await pair.close();
134
- });
136
+ }, 30_000); // four skill changes, each previewed before it is confirmed
135
137
 
136
138
  it('MCP skill tools take typed REST auth and JSON text for free-form objects (as Gemini sends them)', async () => {
137
139
  const NEW = 'fn-new';
138
140
  const { calls } = recordFetch([
139
141
  on('POST', `/ai-studio/bots/${AGENT}/functions`, { json: { success: true, data: { _id: NEW, type: 'rest_api', name: 'get_quote' } } }),
140
142
  on('PUT', `/functions/${NEW}`, { json: { success: true, data: {} } }),
141
- ]);
143
+ agentFound]);
142
144
  const pair = await connectPair((server) => { registerTools(server, new IndemnClient()); });
143
- const res = await pair.call('create_function', {
145
+ const res = await pair.call('create_function', { confirm: true,
144
146
  agent_id: AGENT, type: 'rest_api', name: 'get_quote', url: 'https://api.example.com/quote', method: 'POST',
145
147
  auth_type: 'bearer-token', auth_token: 'abc', auth_key: '', auth_username: '',
146
148
  payload: '{"event_date": "{{event_date}}", "guests": {{guests}}}',
@@ -160,9 +162,9 @@ describe('skills SDK and tools', () => {
160
162
  on('GET', `/v2/bots/${AGENT}`, { json: [{ _id: AGENT, name: 'Bot' }] }),
161
163
  on('GET', `/ai-studio/bots/${AGENT}/functions`, { json: { success: true, data: [existing] } }),
162
164
  on('DELETE', '/functions/f1', { json: { success: true } }),
163
- ]);
165
+ agentFound]);
164
166
  const pair = await connectPair((server) => { registerTools(server, new IndemnClient()); });
165
- const dup = await pair.call('create_function', { agent_id: AGENT, type: 'rest_api', name: 'Get_Quote', url: 'https://y', method: 'POST' });
167
+ const dup = await pair.call('create_function', { confirm: true, agent_id: AGENT, type: 'rest_api', name: 'Get_Quote', url: 'https://y', method: 'POST' });
166
168
  expect(dup.isError).toBe(true);
167
169
  expect(String(dup.body)).toMatch(/already has a skill named "Get_Quote" \(function_id f1\)\. Change it with update_function/);
168
170
  expect(calls.some((c) => c.method === 'POST')).toBe(false);
@@ -180,20 +182,20 @@ describe('skills SDK and tools', () => {
180
182
  on('POST', `/ai-studio/bots/${AGENT}/functions`, { json: { success: true, data: { _id: 'fx', type: 'rest_api', name: 'quote' } } }),
181
183
  on('PUT', '/functions/fx', { status: 500, json: { error: 'Payload must be a valid JSON string.' } }),
182
184
  on('DELETE', '/functions/fx', { json: { success: true } }),
183
- ]);
185
+ agentFound]);
184
186
  const pair = await connectPair((server) => { registerTools(server, new IndemnClient()); });
185
- const bad = await pair.call('create_function', { agent_id: AGENT, type: 'rest_api', name: 'quote', url: 'https://x', method: 'POST', payload: '{"a": 1,}' });
187
+ const bad = await pair.call('create_function', { confirm: true, agent_id: AGENT, type: 'rest_api', name: 'quote', url: 'https://x', method: 'POST', payload: '{"a": 1,}' });
186
188
  expect(bad.isError).toBe(true);
187
189
  expect(String(bad.body)).toMatch(/request body is not valid JSON.*Nothing was saved/);
188
190
  expect(calls.some((c) => c.method === 'POST')).toBe(false);
189
- const refused = await pair.call('create_function', { agent_id: AGENT, type: 'rest_api', name: 'quote', url: 'https://x', method: 'POST' });
191
+ const refused = await pair.call('create_function', { confirm: true, agent_id: AGENT, type: 'rest_api', name: 'quote', url: 'https://x', method: 'POST' });
190
192
  expect(String(refused.body)).toMatch(/Nothing was saved: the skill's configuration was refused/);
191
193
  expect(calls.filter((c) => c.method === 'DELETE').map((c) => c.url)).toEqual([expect.stringContaining('/functions/fx')]);
192
194
  await pair.close();
193
195
  });
194
196
 
195
197
  it('MCP import_functions warns that everything is replaced and waits for confirmation', async () => {
196
- const { calls } = recordFetch([on('GET', `/v2/bots/${AGENT}`, { json: [{ _id: AGENT, name: 'Bot' }] })]);
198
+ const { calls } = recordFetch([on('GET', `/v2/bots/${AGENT}`, { json: [{ _id: AGENT, name: 'Bot' }] }), agentFound]);
197
199
  const pair = await connectPair((server) => { registerTools(server, new IndemnClient()); });
198
200
  const res = await pair.call('import_functions', { agent_id: AGENT, url: 'https://x/skills.json' });
199
201
  expect(res.body).toMatchObject({ dry_run: true });
@@ -212,7 +214,7 @@ describe('skills SDK and tools', () => {
212
214
  on('PUT', `/bots/${AGENT}/functions/f1`, { json: { success: true, data: { _id: 'f1' } } }),
213
215
  on('PUT', `/bots/${AGENT}`, { json: { success: true, data: { _id: AGENT } } }),
214
216
  on('PATCH', `/bots/${AGENT}`, { json: { success: true, data: { _id: AGENT } } }),
215
- ]);
217
+ agentFound]);
216
218
  const pair = await connectPair((server) => { registerTools(server, new IndemnClient()); });
217
219
  const writes = () => calls.filter((c) => c.method === 'PUT' || c.method === 'PATCH').length;
218
220
 
@@ -70,6 +70,35 @@ describe('CLI chats are always test conversations', () => {
70
70
  expect(mw.userMessages).toHaveLength(0);
71
71
  });
72
72
 
73
+ it("never sends to, or ends, a conversation named by the caller that is a customer's live one", async () => {
74
+ recordFetch([agentOk, stored({ isTestMode: false })]);
75
+ await expect(openTestChat(new IndemnClient(), AGENT, { sessionId: 'cust-1' })).rejects.toThrow(/stored as a live conversation/);
76
+ await new Promise((r) => setTimeout(r, 50));
77
+ expect(mw.sessionRequests).toHaveLength(0); // checked before connecting
78
+ expect(mw.closed).toHaveLength(0);
79
+ });
80
+
81
+ it('sends nothing until the greeting has finished', async () => {
82
+ const send = vi.fn();
83
+ const chat = { resumed: false, transcript: [] as string[], isReceiving: () => false, session: { send } } as unknown as Parameters<typeof exchange>[0];
84
+ await expect(exchange(chat, 'hi', { greetingWaitMs: 30 })).rejects.toThrow(/greeting.*nothing was sent/i);
85
+ expect(send).not.toHaveBeenCalled();
86
+ });
87
+
88
+ it('a reply that never finishes streaming still returns by the deadline', async () => {
89
+ const transcript = ['Hello there'];
90
+ let checks = 0;
91
+ const chat = {
92
+ resumed: true, transcript,
93
+ isReceiving: () => ++checks > 1, // the reply arrives, then a stream starts that never ends
94
+ session: { send: () => { transcript.push('echo: hi'); } },
95
+ } as unknown as Parameters<typeof exchange>[0];
96
+ const started = Date.now();
97
+ const out = await exchange(chat, 'hi', { timeoutMs: 300, settleMs: 20 });
98
+ expect(out.response).toBe('echo: hi');
99
+ expect(Date.now() - started).toBeLessThan(2_000);
100
+ });
101
+
73
102
  it('waits for the conversation to appear, then accepts it', async () => {
74
103
  let tries = 0;
75
104
  recordFetch([on('GET', '/requests/', () => (++tries < 3 ? { status: 404, text: 'nope' } : { json: { request_id: 's', isTestMode: true, id_organization: ORG } }))]);