@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/README.md +4 -4
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +10 -6
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +8 -2
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +17 -6
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/org.d.ts.map +1 -1
- package/dist/cli/org.js +30 -0
- package/dist/cli/org.js.map +1 -1
- package/dist/cli/utils/output.d.ts +0 -1
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +0 -4
- package/dist/cli/utils/output.js.map +1 -1
- package/dist/cli/voice.d.ts.map +1 -1
- package/dist/cli/voice.js +4 -1
- package/dist/cli/voice.js.map +1 -1
- package/dist/core/org-resolver.d.ts +4 -0
- package/dist/core/org-resolver.d.ts.map +1 -1
- package/dist/core/org-resolver.js +11 -0
- package/dist/core/org-resolver.js.map +1 -1
- package/dist/core/runtime.d.ts +9 -0
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +49 -0
- package/dist/core/runtime.js.map +1 -1
- package/dist/core/scope.d.ts +0 -1
- package/dist/core/scope.d.ts.map +1 -1
- package/dist/core/scope.js.map +1 -1
- package/dist/mcp/registry.d.ts.map +1 -1
- package/dist/mcp/registry.js +15 -2
- package/dist/mcp/registry.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-meta.d.ts.map +1 -1
- package/dist/mcp/tool-meta.js +11 -5
- package/dist/mcp/tool-meta.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +12 -1
- package/dist/mcp/tools.js.map +1 -1
- package/dist/report/export.d.ts.map +1 -1
- package/dist/report/export.js +2 -3
- package/dist/report/export.js.map +1 -1
- package/dist/report/generate.d.ts.map +1 -1
- package/dist/report/generate.js +3 -3
- package/dist/report/generate.js.map +1 -1
- package/dist/sdk/auth.d.ts +8 -3
- package/dist/sdk/auth.d.ts.map +1 -1
- package/dist/sdk/auth.js +13 -5
- package/dist/sdk/auth.js.map +1 -1
- package/dist/sdk/chat.d.ts.map +1 -1
- package/dist/sdk/chat.js +1 -0
- package/dist/sdk/chat.js.map +1 -1
- package/dist/sdk/client.d.ts +11 -9
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +50 -67
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/config-change.js +1 -1
- package/dist/sdk/config-change.js.map +1 -1
- package/dist/sdk/conversations.js +1 -1
- package/dist/sdk/conversations.js.map +1 -1
- package/dist/sdk/functions.js +2 -2
- package/dist/sdk/functions.js.map +1 -1
- package/dist/sdk/jarvis-chat.js +2 -2
- package/dist/sdk/jarvis-chat.js.map +1 -1
- package/dist/sdk/kb.d.ts.map +1 -1
- package/dist/sdk/kb.js +3 -2
- package/dist/sdk/kb.js.map +1 -1
- package/dist/sdk/orgs.d.ts +12 -0
- package/dist/sdk/orgs.d.ts.map +1 -1
- package/dist/sdk/orgs.js +16 -6
- package/dist/sdk/orgs.js.map +1 -1
- package/dist/sdk/session.d.ts +2 -9
- package/dist/sdk/session.d.ts.map +1 -1
- package/dist/sdk/session.js +2 -27
- package/dist/sdk/session.js.map +1 -1
- package/dist/sdk/test-chat.d.ts.map +1 -1
- package/dist/sdk/test-chat.js +17 -6
- package/dist/sdk/test-chat.js.map +1 -1
- package/package.json +1 -1
- package/skills/sandbox/SKILL.md +26 -0
- package/src/cli/auth.ts +9 -6
- package/src/cli/eval.ts +6 -2
- package/src/cli/index.ts +2 -2
- package/src/cli/init.ts +16 -7
- package/src/cli/org.ts +29 -0
- package/src/cli/utils/output.ts +0 -4
- package/src/cli/voice.ts +3 -1
- package/src/core/org-resolver.ts +18 -0
- package/src/core/runtime.ts +46 -0
- package/src/core/scope.ts +0 -1
- package/src/mcp/registry.ts +17 -2
- package/src/mcp/server.ts +2 -19
- package/src/mcp/tool-meta.ts +11 -5
- package/src/mcp/tools.ts +14 -1
- package/src/report/export.ts +2 -1
- package/src/report/generate.tsx +3 -2
- package/src/sdk/auth.ts +15 -5
- package/src/sdk/chat.ts +1 -0
- package/src/sdk/client.ts +50 -72
- package/src/sdk/config-change.ts +1 -1
- package/src/sdk/conversations.ts +1 -1
- package/src/sdk/functions.ts +2 -2
- package/src/sdk/jarvis-chat.ts +2 -2
- package/src/sdk/kb.ts +3 -2
- package/src/sdk/orgs.ts +28 -4
- package/src/sdk/session.ts +2 -25
- package/src/sdk/test-chat.ts +16 -6
- package/test/cli/init.test.ts +14 -0
- package/test/core/org-resolver.test.ts +38 -2
- package/test/core/scope.test.ts +12 -0
- package/test/helpers/mcp-pair.ts +10 -1
- package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +1 -0
- package/test/mcp/darwin-fixes.test.ts +18 -17
- package/test/mcp/registry.test.ts +11 -0
- package/test/mcp/sandbox.test.ts +45 -0
- package/test/mcp/shaping.test.ts +18 -0
- package/test/mcp/tools-snapshot.test.ts +5 -1
- package/test/sdk/auth.test.ts +11 -2
- package/test/sdk/client.test.ts +13 -0
- package/test/sdk/config-change.test.ts +1 -1
- package/test/sdk/kb-upload.test.ts +21 -0
- package/test/sdk/orgs.test.ts +33 -0
- package/test/sdk/renewal.test.ts +43 -64
- package/test/sdk/session.test.ts +1 -23
- package/test/sdk/skills.test.ts +20 -18
- package/test/sdk/test-chat.test.ts +29 -0
package/test/sdk/skills.test.ts
CHANGED
|
@@ -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 } }))]);
|