@indemn/cli 1.7.5 → 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/README.md +2 -2
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +2 -1
- 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/mcp/tool-meta.d.ts.map +1 -1
- package/dist/mcp/tool-meta.js +6 -0
- package/dist/mcp/tool-meta.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +11 -0
- package/dist/mcp/tools.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 +15 -0
- package/dist/sdk/orgs.js.map +1 -1
- package/package.json +1 -1
- package/skills/sandbox/SKILL.md +26 -0
- package/src/cli/init.ts +2 -1
- package/src/cli/org.ts +29 -0
- package/src/mcp/tool-meta.ts +6 -0
- package/src/mcp/tools.ts +13 -0
- package/src/sdk/orgs.ts +27 -0
- package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +1 -0
- package/test/mcp/sandbox.test.ts +45 -0
- package/test/mcp/tools-snapshot.test.ts +1 -1
- package/test/sdk/orgs.test.ts +15 -0
package/dist/sdk/orgs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../src/sdk/orgs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AASjE,qBAAa,OAAO;IACN,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAExC,+FAA+F;IACzF,iBAAiB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAIhD,8EAA8E;IACxE,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAkCzC,+DAA+D;IACzD,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CAIzC"}
|
|
1
|
+
{"version":3,"file":"orgs.d.ts","sourceRoot":"","sources":["../../src/sdk/orgs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AASjE,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,OAAO;IACN,OAAO,CAAC,MAAM;gBAAN,MAAM,EAAE,YAAY;IAExC,+FAA+F;IACzF,iBAAiB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAIhD,8EAA8E;IACxE,WAAW,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;IAkCzC;;;OAGG;IACG,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IAetD,+DAA+D;IACzD,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;CAIzC"}
|
package/dist/sdk/orgs.js
CHANGED
|
@@ -45,6 +45,21 @@ export class OrgsSDK {
|
|
|
45
45
|
}
|
|
46
46
|
return [...byUser.values()];
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* A sandbox organization for a prospect. Only the Indemn support account may create one (copilot
|
|
50
|
+
* refuses anyone else), and copilot always puts it on the sandbox plan set for the platform.
|
|
51
|
+
*/
|
|
52
|
+
async createSandbox(name) {
|
|
53
|
+
const res = await this.client.copilotPost('/organization/sandbox', { organizationName: name });
|
|
54
|
+
const base = this.client.getHosts().copilot.replace(/\/api\/?$/, '');
|
|
55
|
+
return {
|
|
56
|
+
organization_id: res.id_organization,
|
|
57
|
+
name,
|
|
58
|
+
project_id: res.id_project,
|
|
59
|
+
plan_id: res.id_plan,
|
|
60
|
+
url: `${base}/#/organization/${res.id_organization}/ai-studio/bots`,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
48
63
|
/** Channels an agent can be published on (WEB, VOICE, ...). */
|
|
49
64
|
async listChannels() {
|
|
50
65
|
const channels = await this.client.copilotGet('/organization/:org_id/ai-studio/channels');
|
package/dist/sdk/orgs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orgs.js","sourceRoot":"","sources":["../../src/sdk/orgs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"orgs.js","sourceRoot":"","sources":["../../src/sdk/orgs.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sandbox
|
|
3
|
+
description: Create a sandbox organization for a prospect on the Indemn platform. Use when the person asks for a sandbox, a sandbox org, a trial org, or a new organization for a prospect or customer.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sandbox organizations
|
|
7
|
+
|
|
8
|
+
A sandbox is a new organization for a prospect, set up so they can try the platform. The platform puts every sandbox on its sandbox plan (a trial, then the plan's monthly price on invoice terms), invoiced to the Indemn support account.
|
|
9
|
+
|
|
10
|
+
## Who can do it
|
|
11
|
+
|
|
12
|
+
Only the Indemn support account. The platform refuses anyone else, including other Indemn staff. If the person is not signed in as support, say so and stop; do not try another way.
|
|
13
|
+
|
|
14
|
+
## What to do
|
|
15
|
+
|
|
16
|
+
1. Get the organization's name from the person. If they did not give one, ask for it. Ask for nothing else: never ask for a plan, price, trial length or payment terms. The platform sets them.
|
|
17
|
+
2. Create it:
|
|
18
|
+
- MCP: `create_sandbox_org` with `name`. The person approves it in the approval form.
|
|
19
|
+
- Terminal: `indemn org create-sandbox "<name>"`. It asks the person to confirm; it has no `--yes`.
|
|
20
|
+
3. Reply with the new organization's name, id and dashboard link. Mention that they can rename it in Organization Settings and invite the prospect's users from there.
|
|
21
|
+
|
|
22
|
+
## If it fails
|
|
23
|
+
|
|
24
|
+
- **Only the Indemn support account can create sandbox organizations**: the person is signed in as someone else.
|
|
25
|
+
- **Sandbox plan is not configured**: the platform has no sandbox plan set. Tell the person to contact the Indemn engineering team; do not create an org another way.
|
|
26
|
+
- **Organization created and subscribed, but setting up its project failed**: the org exists. Give its id and tell the person to finish the set-up from the dashboard; do not create it again.
|
package/src/cli/init.ts
CHANGED
|
@@ -47,6 +47,7 @@ The \`indemn\` CLI and its MCP server manage Indemn AI agents. Run \`indemn whoa
|
|
|
47
47
|
|
|
48
48
|
- **agent-builder**: create, configure and test agents (prompt, skills, knowledge bases, voice, chat).
|
|
49
49
|
- **evaluations**: rubrics, test sets, runs, CI gates, reports.
|
|
50
|
+
- **sandbox**: create a sandbox organization for a prospect (support account only).
|
|
50
51
|
`;
|
|
51
52
|
|
|
52
53
|
export interface InitReport {
|
|
@@ -121,7 +122,7 @@ export function initStatus(cwd: string): Array<[string, boolean, string]> {
|
|
|
121
122
|
let link: string | null = null;
|
|
122
123
|
try { link = findLinkFile(cwd)?.path ?? null; } catch { link = null; }
|
|
123
124
|
return [
|
|
124
|
-
['Skills', existsSync(join(cwd, '.claude', 'skills', 'agent-builder', 'SKILL.md')), '.claude/skills/agent-builder, evaluations'],
|
|
125
|
+
['Skills', existsSync(join(cwd, '.claude', 'skills', 'agent-builder', 'SKILL.md')), '.claude/skills/agent-builder, evaluations, sandbox'],
|
|
125
126
|
['Rules', existsSync(join(cwd, '.claude', 'rules', 'indemn.md')), '.claude/rules/indemn.md'],
|
|
126
127
|
['MCP server', Boolean(mcp.mcpServers?.indemn), '.mcp.json'],
|
|
127
128
|
['Permissions', (settings.permissions?.ask ?? []).some((r: string) => r.startsWith('mcp__indemn__')), '.claude/settings.json'],
|
package/src/cli/org.ts
CHANGED
|
@@ -4,6 +4,7 @@ import ora from 'ora';
|
|
|
4
4
|
import { IndemnClient } from '../sdk/client.js';
|
|
5
5
|
import { OrgsSDK } from '../sdk/orgs.js';
|
|
6
6
|
import { getAskers, setAskers } from '../core/scope.js';
|
|
7
|
+
import { CliError, EXIT, isNonInteractive } from '../core/runtime.js';
|
|
7
8
|
import type { OrgContext } from '../sdk/client.js';
|
|
8
9
|
import { confirmOrExit, writeLink } from './link.js';
|
|
9
10
|
import { formatTable, printError, printJson, printSuccess } from './utils/output.js';
|
|
@@ -97,6 +98,34 @@ export function registerOrgCommands(program: Command): void {
|
|
|
97
98
|
}
|
|
98
99
|
});
|
|
99
100
|
|
|
101
|
+
org
|
|
102
|
+
.command('create-sandbox')
|
|
103
|
+
.description('Create a sandbox organization for a prospect (Indemn support account only; the platform picks the sandbox plan)')
|
|
104
|
+
.argument('<name>', 'Name of the new organization')
|
|
105
|
+
.option('--json', 'Output as JSON')
|
|
106
|
+
.action(async (name: string, options) => {
|
|
107
|
+
try {
|
|
108
|
+
const question = `Create organization "${name}" on the sandbox plan? It starts its trial now and is then invoiced to the support account.`;
|
|
109
|
+
// A new org on a paid plan: a person answers here; there is no --yes.
|
|
110
|
+
if (isNonInteractive()) throw new CliError(`${question} Run this in a terminal so a person can confirm it.`, EXIT.NEEDS_INPUT);
|
|
111
|
+
await confirmOrExit(question, false);
|
|
112
|
+
const client = new IndemnClient({ ignoreLink: true });
|
|
113
|
+
const spinner = ora('Creating sandbox organization...').start();
|
|
114
|
+
const created = await new OrgsSDK(client).createSandbox(name);
|
|
115
|
+
spinner.stop();
|
|
116
|
+
if (options.json) {
|
|
117
|
+
printJson(created);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
printSuccess(`Created ${created.name} (${created.organization_id}) on ${client.getEnvironment()}.`);
|
|
121
|
+
console.log(` ${created.url}`);
|
|
122
|
+
console.log(chalk.dim('Rename it any time in Organization Settings.'));
|
|
123
|
+
} catch (err) {
|
|
124
|
+
printError(err);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
100
129
|
org
|
|
101
130
|
.command('switch')
|
|
102
131
|
.description('Pin this folder to an org (there is no global default org any more)')
|
package/src/mcp/tool-meta.ts
CHANGED
|
@@ -39,6 +39,12 @@ export const TOOL_META: Record<string, ToolMeta> = {
|
|
|
39
39
|
// Login and organizations
|
|
40
40
|
whoami: { ...read('Who am I'), orgScoped: false },
|
|
41
41
|
list_organizations: { ...read('List my organizations'), orgScoped: false },
|
|
42
|
+
create_sandbox_org: {
|
|
43
|
+
...write('Create sandbox organization'),
|
|
44
|
+
orgScoped: false,
|
|
45
|
+
guard: true,
|
|
46
|
+
describe: (a) => `This will create a new organization "${a.name}" on the sandbox plan (its trial, then the plan's monthly price, invoiced to the support account).`,
|
|
47
|
+
},
|
|
42
48
|
list_org_members: { ...read('List organization members'), paginate: true },
|
|
43
49
|
list_channels: read('List channels'),
|
|
44
50
|
|
package/src/mcp/tools.ts
CHANGED
|
@@ -241,6 +241,19 @@ export function registerTools(server: McpServer, client: IndemnClient): Registry
|
|
|
241
241
|
},
|
|
242
242
|
);
|
|
243
243
|
|
|
244
|
+
reg.tool(
|
|
245
|
+
'create_sandbox_org',
|
|
246
|
+
'Create a sandbox organization for a prospect when the person asks for "a sandbox" or "a new org" and gives its name. Only the Indemn support account can do this; the platform picks the sandbox plan itself, so never ask for a plan, price or terms. Ask for the name if it is missing. Returns the new org id and its dashboard link; the person can rename it later in Organization Settings. The host asks the person to approve; without an approval form this is refused.',
|
|
247
|
+
{ name: z.string().trim().min(1).describe('Name of the new organization, as the person gave it') },
|
|
248
|
+
async (args, meta) => {
|
|
249
|
+
try {
|
|
250
|
+
// A new org on a paid plan needs the person, not the model: confirm: true is not enough.
|
|
251
|
+
if (!meta.confirmedByPerson) throw new Error('Creating a sandbox organization needs a person to approve it in an approval form, and this host cannot show one. Nothing was created. Run `indemn org create-sandbox` in a terminal instead.');
|
|
252
|
+
return success(await orgs.createSandbox(args.name));
|
|
253
|
+
} catch (err) { return error(err); }
|
|
254
|
+
},
|
|
255
|
+
);
|
|
256
|
+
|
|
244
257
|
reg.tool(
|
|
245
258
|
'list_org_members',
|
|
246
259
|
'List the owners, admins and agents of an organization, with their roles.',
|
package/src/sdk/orgs.ts
CHANGED
|
@@ -8,6 +8,14 @@ interface RawProjectUser {
|
|
|
8
8
|
id_project?: { name?: string } | string | null;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export interface SandboxOrg {
|
|
12
|
+
organization_id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
project_id?: string;
|
|
15
|
+
plan_id?: string;
|
|
16
|
+
url: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
11
19
|
export class OrgsSDK {
|
|
12
20
|
constructor(private client: IndemnClient) {}
|
|
13
21
|
|
|
@@ -51,6 +59,25 @@ export class OrgsSDK {
|
|
|
51
59
|
return [...byUser.values()];
|
|
52
60
|
}
|
|
53
61
|
|
|
62
|
+
/**
|
|
63
|
+
* A sandbox organization for a prospect. Only the Indemn support account may create one (copilot
|
|
64
|
+
* refuses anyone else), and copilot always puts it on the sandbox plan set for the platform.
|
|
65
|
+
*/
|
|
66
|
+
async createSandbox(name: string): Promise<SandboxOrg> {
|
|
67
|
+
const res = await this.client.copilotPost<{ id_organization: string; id_project?: string; id_plan?: string }>(
|
|
68
|
+
'/organization/sandbox',
|
|
69
|
+
{ organizationName: name },
|
|
70
|
+
);
|
|
71
|
+
const base = this.client.getHosts().copilot.replace(/\/api\/?$/, '');
|
|
72
|
+
return {
|
|
73
|
+
organization_id: res.id_organization,
|
|
74
|
+
name,
|
|
75
|
+
project_id: res.id_project,
|
|
76
|
+
plan_id: res.id_plan,
|
|
77
|
+
url: `${base}/#/organization/${res.id_organization}/ai-studio/bots`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
54
81
|
/** Channels an agent can be published on (WEB, VOICE, ...). */
|
|
55
82
|
async listChannels(): Promise<Channel[]> {
|
|
56
83
|
const channels = await this.client.copilotGet<Channel[]>('/organization/:org_id/ai-studio/channels');
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { on, recordFetch } from '../helpers/fetch-recorder.js';
|
|
3
|
+
import { cleanEnv } from '../helpers/config-home.js';
|
|
4
|
+
import { connectPair } from '../helpers/mcp-pair.js';
|
|
5
|
+
import { IndemnClient } from '../../src/sdk/client.js';
|
|
6
|
+
import { registerTools } from '../../src/mcp/tools.js';
|
|
7
|
+
|
|
8
|
+
const ORG = 'aaaaaaaaaaaaaaaaaaaaaaaa';
|
|
9
|
+
const created = on('POST', '/organization/sandbox', { json: { success: true, id_organization: ORG, id_project: 'p1', id_plan: 'plan1' } });
|
|
10
|
+
|
|
11
|
+
async function pair(elicit?: () => { action: 'accept' | 'decline'; content?: { confirm: boolean } }) {
|
|
12
|
+
cleanEnv();
|
|
13
|
+
const client = new IndemnClient({ apiKey: `ind_dev_${'a'.repeat(64)}`, environment: 'dev' });
|
|
14
|
+
return connectPair((server) => { registerTools(server, client); }, { elicit });
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('create_sandbox_org', () => {
|
|
18
|
+
let p: Awaited<ReturnType<typeof pair>> | undefined;
|
|
19
|
+
afterEach(async () => { await p?.close(); p = undefined; });
|
|
20
|
+
|
|
21
|
+
it('creates the org once the person approves, sending only its name', async () => {
|
|
22
|
+
const { calls } = recordFetch([created]);
|
|
23
|
+
p = await pair(() => ({ action: 'accept', content: { confirm: true } }));
|
|
24
|
+
const res = await p.call('create_sandbox_org', { name: 'Acme Insurance' });
|
|
25
|
+
expect(res.isError).toBe(false);
|
|
26
|
+
expect(p.questions[0]?.message).toContain('Acme Insurance');
|
|
27
|
+
expect(calls.find((c) => c.method === 'POST')?.body).toEqual({ organizationName: 'Acme Insurance' });
|
|
28
|
+
expect(res.body.data).toMatchObject({ organization_id: ORG, url: `https://devcopilot.indemn.ai/#/organization/${ORG}/ai-studio/bots` });
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('creates nothing when the person declines', async () => {
|
|
32
|
+
const { calls } = recordFetch([created]);
|
|
33
|
+
p = await pair(() => ({ action: 'decline' }));
|
|
34
|
+
await p.call('create_sandbox_org', { name: 'Acme' });
|
|
35
|
+
expect(calls.some((c) => c.method === 'POST')).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('refuses the model\'s own confirm when the host cannot ask the person', async () => {
|
|
39
|
+
const { calls } = recordFetch([created]);
|
|
40
|
+
p = await pair();
|
|
41
|
+
const res = await p.call('create_sandbox_org', { name: 'Acme', confirm: true });
|
|
42
|
+
expect(JSON.stringify(res.body)).toContain('needs a person to approve it');
|
|
43
|
+
expect(calls.some((c) => c.method === 'POST')).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -16,7 +16,7 @@ describe('MCP tool list', () => {
|
|
|
16
16
|
expect(typeof tool.annotations?.readOnlyHint, tool.name).toBe('boolean');
|
|
17
17
|
}
|
|
18
18
|
expect(tools.map((t) => t.name).sort()).toMatchSnapshot();
|
|
19
|
-
expect(tools).toHaveLength(
|
|
19
|
+
expect(tools).toHaveLength(141); // +numbers // +set_agent_transcriber // +set_org_admin_settings // +remove_holiday // +create_sandbox_org
|
|
20
20
|
const props = (name: string) => Object.keys((tools.find((t) => t.name === name)!.inputSchema as { properties?: object }).properties ?? {});
|
|
21
21
|
for (const name of ['list_agents', 'delete_agent', 'set_prompt', 'list_org_members', 'list_channels', 'chat_with_agent']) {
|
|
22
22
|
expect(props(name), name).toContain('org_id');
|
package/test/sdk/orgs.test.ts
CHANGED
|
@@ -58,6 +58,21 @@ describe('OrgsSDK', () => {
|
|
|
58
58
|
await expect(new OrgsSDK(new IndemnClient({ orgId: A })).listMembers()).resolves.toEqual([]);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
it('creates a sandbox org with only its name and links to it', async () => {
|
|
62
|
+
const { calls } = recordFetch([on('POST', '/organization/sandbox', { json: { success: true, id_organization: A, id_project: 'p1', id_plan: 'plan1' } })]);
|
|
63
|
+
await expect(new OrgsSDK(new IndemnClient()).createSandbox('Acme Insurance')).resolves.toEqual({
|
|
64
|
+
organization_id: A, name: 'Acme Insurance', project_id: 'p1', plan_id: 'plan1',
|
|
65
|
+
url: `https://devcopilot.indemn.ai/#/organization/${A}/ai-studio/bots`,
|
|
66
|
+
});
|
|
67
|
+
expect(new URL(calls[0].url).pathname).toBe('/api/organization/sandbox');
|
|
68
|
+
expect(calls[0].body).toEqual({ organizationName: 'Acme Insurance' });
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('keeps copilot\'s reason when someone other than support asks for a sandbox', async () => {
|
|
72
|
+
recordFetch([on('POST', '/organization/sandbox', { status: 403, json: { success: false, msg: 'Only the Indemn support account can create sandbox organizations.' } })]);
|
|
73
|
+
await expect(new OrgsSDK(new IndemnClient()).createSandbox('Acme')).rejects.toThrow('Not allowed: Only the Indemn support account can create sandbox organizations.');
|
|
74
|
+
});
|
|
75
|
+
|
|
61
76
|
it('lists channels', async () => {
|
|
62
77
|
const { calls } = recordFetch([on('GET', '/ai-studio/channels', { json: { success: true, data: [{ _id: 'c1', name: 'WEB', type: 'web' }] } })]);
|
|
63
78
|
await expect(new OrgsSDK(new IndemnClient({ orgId: A })).listChannels()).resolves.toEqual([{ _id: 'c1', name: 'WEB', type: 'web', description: undefined }]);
|