@indemn/cli 1.6.0 → 1.7.0
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 +3 -3
- package/.claude-plugin/plugin.json +9 -4
- package/.github/workflows/ci.yml +10 -0
- package/.github/workflows/publish.yml +2 -2
- package/.mcp.json +1 -4
- package/CONTRIBUTING.md +3 -1
- package/README.md +69 -176
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +226 -18
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/analytics.d.ts +3 -0
- package/dist/cli/analytics.d.ts.map +1 -0
- package/dist/cli/analytics.js +133 -0
- package/dist/cli/analytics.js.map +1 -0
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +62 -17
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/billing.d.ts +3 -0
- package/dist/cli/billing.d.ts.map +1 -0
- package/dist/cli/billing.js +23 -0
- package/dist/cli/billing.js.map +1 -0
- package/dist/cli/campaigns.d.ts +3 -0
- package/dist/cli/campaigns.d.ts.map +1 -0
- package/dist/cli/campaigns.js +34 -0
- package/dist/cli/campaigns.js.map +1 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +69 -102
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +71 -27
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/conversations.d.ts +3 -0
- package/dist/cli/conversations.d.ts.map +1 -0
- package/dist/cli/conversations.js +129 -0
- package/dist/cli/conversations.js.map +1 -0
- package/dist/cli/distribution.d.ts +3 -0
- package/dist/cli/distribution.d.ts.map +1 -0
- package/dist/cli/distribution.js +122 -0
- package/dist/cli/distribution.js.map +1 -0
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +315 -71
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/functions.d.ts.map +1 -1
- package/dist/cli/functions.js +109 -38
- package/dist/cli/functions.js.map +1 -1
- package/dist/cli/index.js +63 -54
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +27 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +152 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/kb.d.ts.map +1 -1
- package/dist/cli/kb.js +141 -15
- package/dist/cli/kb.js.map +1 -1
- package/dist/cli/link.d.ts +21 -0
- package/dist/cli/link.d.ts.map +1 -0
- package/dist/cli/link.js +112 -0
- package/dist/cli/link.js.map +1 -0
- package/dist/cli/org.d.ts.map +1 -1
- package/dist/cli/org.js +79 -67
- package/dist/cli/org.js.map +1 -1
- package/dist/cli/platform.d.ts +3 -0
- package/dist/cli/platform.d.ts.map +1 -0
- package/dist/cli/platform.js +133 -0
- package/dist/cli/platform.js.map +1 -0
- package/dist/cli/tickets.d.ts +3 -0
- package/dist/cli/tickets.d.ts.map +1 -0
- package/dist/cli/tickets.js +125 -0
- package/dist/cli/tickets.js.map +1 -0
- package/dist/cli/utils/output.d.ts +15 -9
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +53 -46
- package/dist/cli/utils/output.js.map +1 -1
- package/dist/cli/voice.d.ts +3 -0
- package/dist/cli/voice.d.ts.map +1 -0
- package/dist/cli/voice.js +206 -0
- package/dist/cli/voice.js.map +1 -0
- package/dist/core/diff.d.ts +16 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +57 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/org-resolver.d.ts +37 -0
- package/dist/core/org-resolver.d.ts.map +1 -0
- package/dist/core/org-resolver.js +73 -0
- package/dist/core/org-resolver.js.map +1 -0
- package/dist/core/runtime.d.ts +36 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +62 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/scope.d.ts +33 -0
- package/dist/core/scope.d.ts.map +1 -0
- package/dist/core/scope.js +37 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/mcp/registry.d.ts +67 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +195 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +87 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-meta.d.ts +21 -0
- package/dist/mcp/tool-meta.d.ts.map +1 -0
- package/dist/mcp/tool-meta.js +203 -0
- package/dist/mcp/tool-meta.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1462 -215
- package/dist/mcp/tools.js.map +1 -1
- package/dist/sdk/agent-bundle.d.ts +99 -0
- package/dist/sdk/agent-bundle.d.ts.map +1 -0
- package/dist/sdk/agent-bundle.js +211 -0
- package/dist/sdk/agent-bundle.js.map +1 -0
- package/dist/sdk/agents.d.ts +54 -1
- package/dist/sdk/agents.d.ts.map +1 -1
- package/dist/sdk/agents.js +59 -5
- package/dist/sdk/agents.js.map +1 -1
- package/dist/sdk/analytics.d.ts +158 -0
- package/dist/sdk/analytics.d.ts.map +1 -0
- package/dist/sdk/analytics.js +185 -0
- package/dist/sdk/analytics.js.map +1 -0
- package/dist/sdk/auth.d.ts +34 -12
- package/dist/sdk/auth.d.ts.map +1 -1
- package/dist/sdk/auth.js +60 -77
- package/dist/sdk/auth.js.map +1 -1
- package/dist/sdk/billing.d.ts +25 -0
- package/dist/sdk/billing.d.ts.map +1 -0
- package/dist/sdk/billing.js +61 -0
- package/dist/sdk/billing.js.map +1 -0
- package/dist/sdk/campaigns.d.ts +35 -0
- package/dist/sdk/campaigns.d.ts.map +1 -0
- package/dist/sdk/campaigns.js +59 -0
- package/dist/sdk/campaigns.js.map +1 -0
- package/dist/sdk/chat.d.ts +25 -2
- package/dist/sdk/chat.d.ts.map +1 -1
- package/dist/sdk/chat.js +37 -10
- package/dist/sdk/chat.js.map +1 -1
- package/dist/sdk/client.d.ts +93 -5
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +362 -100
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/config-change.d.ts +74 -0
- package/dist/sdk/config-change.d.ts.map +1 -0
- package/dist/sdk/config-change.js +117 -0
- package/dist/sdk/config-change.js.map +1 -0
- package/dist/sdk/config.d.ts.map +1 -1
- package/dist/sdk/config.js +2 -1
- package/dist/sdk/config.js.map +1 -1
- package/dist/sdk/conversations.d.ts +101 -0
- package/dist/sdk/conversations.d.ts.map +1 -0
- package/dist/sdk/conversations.js +155 -0
- package/dist/sdk/conversations.js.map +1 -0
- package/dist/sdk/credentials.d.ts +25 -0
- package/dist/sdk/credentials.d.ts.map +1 -0
- package/dist/sdk/credentials.js +44 -0
- package/dist/sdk/credentials.js.map +1 -0
- package/dist/sdk/distribution.d.ts +94 -0
- package/dist/sdk/distribution.d.ts.map +1 -0
- package/dist/sdk/distribution.js +100 -0
- package/dist/sdk/distribution.js.map +1 -0
- package/dist/sdk/eval.d.ts +81 -0
- package/dist/sdk/eval.d.ts.map +1 -1
- package/dist/sdk/eval.js +117 -0
- package/dist/sdk/eval.js.map +1 -1
- package/dist/sdk/functions.d.ts +14 -1
- package/dist/sdk/functions.d.ts.map +1 -1
- package/dist/sdk/functions.js +48 -5
- package/dist/sdk/functions.js.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/jarvis-chat.d.ts +28 -0
- package/dist/sdk/jarvis-chat.d.ts.map +1 -0
- package/dist/sdk/jarvis-chat.js +117 -0
- package/dist/sdk/jarvis-chat.js.map +1 -0
- package/dist/sdk/kb.d.ts +69 -1
- package/dist/sdk/kb.d.ts.map +1 -1
- package/dist/sdk/kb.js +111 -24
- package/dist/sdk/kb.js.map +1 -1
- package/dist/sdk/orgs.d.ts +13 -0
- package/dist/sdk/orgs.d.ts.map +1 -0
- package/dist/sdk/orgs.js +59 -0
- package/dist/sdk/orgs.js.map +1 -0
- package/dist/sdk/platform.d.ts +62 -0
- package/dist/sdk/platform.d.ts.map +1 -0
- package/dist/sdk/platform.js +95 -0
- package/dist/sdk/platform.js.map +1 -0
- package/dist/sdk/session.d.ts +25 -0
- package/dist/sdk/session.d.ts.map +1 -0
- package/dist/sdk/session.js +69 -0
- package/dist/sdk/session.js.map +1 -0
- package/dist/sdk/skills.d.ts +46 -0
- package/dist/sdk/skills.d.ts.map +1 -0
- package/dist/sdk/skills.js +203 -0
- package/dist/sdk/skills.js.map +1 -0
- package/dist/sdk/test-chat.d.ts +52 -0
- package/dist/sdk/test-chat.d.ts.map +1 -0
- package/dist/sdk/test-chat.js +134 -0
- package/dist/sdk/test-chat.js.map +1 -0
- package/dist/sdk/tickets.d.ts +98 -0
- package/dist/sdk/tickets.d.ts.map +1 -0
- package/dist/sdk/tickets.js +91 -0
- package/dist/sdk/tickets.js.map +1 -0
- package/dist/sdk/types.d.ts +91 -6
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/voice.d.ts +147 -0
- package/dist/sdk/voice.d.ts.map +1 -0
- package/dist/sdk/voice.js +236 -0
- package/dist/sdk/voice.js.map +1 -0
- package/package.json +22 -3
- package/scripts/check-skills.mjs +25 -0
- package/skills/agent-builder/SKILL.md +89 -252
- package/skills/evaluations/SKILL.md +23 -234
- package/skills/evaluations/references/analysis.md +95 -0
- package/skills/evaluations/references/cli-reference.md +28 -0
- package/skills/evaluations/references/json-formats.md +100 -0
- package/src/cli/agents.ts +202 -22
- package/src/cli/analytics.ts +138 -0
- package/src/cli/auth.ts +56 -18
- package/src/cli/billing.ts +25 -0
- package/src/cli/campaigns.ts +33 -0
- package/src/cli/chat.ts +66 -115
- package/src/cli/config.ts +65 -31
- package/src/cli/conversations.ts +133 -0
- package/src/cli/distribution.ts +116 -0
- package/src/cli/eval.ts +254 -70
- package/src/cli/functions.ts +105 -39
- package/src/cli/index.ts +58 -55
- package/src/cli/init.ts +154 -0
- package/src/cli/kb.ts +120 -17
- package/src/cli/link.ts +117 -0
- package/src/cli/org.ts +81 -75
- package/src/cli/platform.ts +128 -0
- package/src/cli/tickets.ts +130 -0
- package/src/cli/utils/output.ts +51 -42
- package/src/cli/voice.ts +204 -0
- package/src/core/diff.ts +52 -0
- package/src/core/org-resolver.ts +113 -0
- package/src/core/runtime.ts +73 -0
- package/src/core/scope.ts +64 -0
- package/src/mcp/registry.ts +236 -0
- package/src/mcp/server.ts +84 -2
- package/src/mcp/tool-meta.ts +241 -0
- package/src/mcp/tools.ts +1529 -265
- package/src/sdk/agent-bundle.ts +245 -0
- package/src/sdk/agents.ts +74 -5
- package/src/sdk/analytics.ts +220 -0
- package/src/sdk/auth.ts +87 -107
- package/src/sdk/billing.ts +59 -0
- package/src/sdk/campaigns.ts +60 -0
- package/src/sdk/chat.ts +44 -10
- package/src/sdk/client.ts +373 -102
- package/src/sdk/config-change.ts +148 -0
- package/src/sdk/config.ts +2 -1
- package/src/sdk/conversations.ts +199 -0
- package/src/sdk/credentials.ts +53 -0
- package/src/sdk/distribution.ts +132 -0
- package/src/sdk/eval.ts +140 -0
- package/src/sdk/functions.ts +52 -6
- package/src/sdk/index.ts +2 -0
- package/src/sdk/jarvis-chat.ts +112 -0
- package/src/sdk/kb.ts +116 -29
- package/src/sdk/orgs.ts +62 -0
- package/src/sdk/platform.ts +115 -0
- package/src/sdk/session.ts +90 -0
- package/src/sdk/skills.ts +188 -0
- package/src/sdk/test-chat.ts +151 -0
- package/src/sdk/tickets.ts +120 -0
- package/src/sdk/types.ts +88 -6
- package/src/sdk/voice.ts +262 -0
- package/test/cli/entry.test.ts +34 -0
- package/test/cli/init.test.ts +66 -0
- package/test/cli/link.test.ts +57 -0
- package/test/core/org-resolver.test.ts +92 -0
- package/test/core/runtime.test.ts +30 -0
- package/test/core/scope.test.ts +93 -0
- package/test/harness.test.ts +24 -0
- package/test/helpers/config-home.ts +22 -0
- package/test/helpers/fake-middleware.ts +56 -0
- package/test/helpers/fetch-recorder.ts +75 -0
- package/test/helpers/jwt.ts +9 -0
- package/test/helpers/mcp-pair.ts +38 -0
- package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +145 -0
- package/test/mcp/askers.test.ts +60 -0
- package/test/mcp/elicit-timeout.test.ts +29 -0
- package/test/mcp/registry.test.ts +169 -0
- package/test/mcp/shaping.test.ts +41 -0
- package/test/mcp/tools-snapshot.test.ts +42 -0
- package/test/sdk/agents.test.ts +232 -0
- package/test/sdk/analytics.test.ts +89 -0
- package/test/sdk/auth.test.ts +70 -0
- package/test/sdk/billing.test.ts +47 -0
- package/test/sdk/campaigns.test.ts +43 -0
- package/test/sdk/client.test.ts +80 -0
- package/test/sdk/config-change.test.ts +142 -0
- package/test/sdk/conversations.test.ts +80 -0
- package/test/sdk/credentials.test.ts +33 -0
- package/test/sdk/distribution.test.ts +55 -0
- package/test/sdk/eval.test.ts +155 -0
- package/test/sdk/kb-upload.test.ts +50 -0
- package/test/sdk/kb.test.ts +95 -0
- package/test/sdk/orgs.test.ts +48 -0
- package/test/sdk/platform.test.ts +89 -0
- package/test/sdk/renewal.test.ts +115 -0
- package/test/sdk/session.test.ts +60 -0
- package/test/sdk/skills.test.ts +235 -0
- package/test/sdk/test-chat.test.ts +112 -0
- package/test/sdk/tickets.test.ts +42 -0
- package/test/sdk/voice.test.ts +147 -0
- package/vitest.config.ts +11 -0
package/src/cli/kb.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { confirmOrExit } from './link.js';
|
|
4
|
+
import { CliError } from '../core/runtime.js';
|
|
5
|
+
import chalk from 'chalk';
|
|
2
6
|
import ora from 'ora';
|
|
3
7
|
import { IndemnClient } from '../sdk/client.js';
|
|
4
8
|
import { KBSDK } from '../sdk/kb.js';
|
|
@@ -138,22 +142,24 @@ export function registerKBCommands(program: Command): void {
|
|
|
138
142
|
|
|
139
143
|
kb.command('delete')
|
|
140
144
|
.argument('<kb-id>', 'Knowledge base ID')
|
|
141
|
-
.
|
|
142
|
-
.
|
|
145
|
+
.option('--dry-run', 'Show what would be deleted and stop')
|
|
146
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
147
|
+
.description('Delete a knowledge base and all its sources (unlink it from agents first)')
|
|
148
|
+
.action(async (kbId: string, options) => {
|
|
143
149
|
try {
|
|
144
|
-
const confirmed = await confirm(`Delete knowledge base ${kbId}? This cannot be undone.`);
|
|
145
|
-
if (!confirmed) {
|
|
146
|
-
console.log('Cancelled.');
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
150
|
const client = new IndemnClient();
|
|
151
151
|
const sdk = new KBSDK(client);
|
|
152
|
+
const target = await sdk.getKB(kbId) as unknown as { name?: string; totalDataSourceCount?: number; aiAgents?: Array<{ name?: string }> };
|
|
153
|
+
const agents = (target.aiAgents ?? []).map((a) => a.name).filter(Boolean);
|
|
154
|
+
console.log(`Would delete "${target.name ?? kbId}" with ${target.totalDataSourceCount ?? '?'} source(s).`);
|
|
155
|
+
if (agents.length) {
|
|
156
|
+
throw new CliError(`It is linked to ${agents.join(', ')}. Unlink it first: indemn kb unlink <agent-id> ${kbId}`);
|
|
157
|
+
}
|
|
158
|
+
if (options.dryRun) return;
|
|
159
|
+
await confirmOrExit(`Delete knowledge base "${target.name ?? kbId}"? Its vectors are removed too.`, Boolean(options.yes));
|
|
152
160
|
const spinner = ora('Deleting knowledge base...').start();
|
|
153
|
-
|
|
154
161
|
await sdk.deleteKB(kbId);
|
|
155
162
|
spinner.stop();
|
|
156
|
-
|
|
157
163
|
printSuccess(`Knowledge base ${kbId} deleted.`);
|
|
158
164
|
} catch (err) {
|
|
159
165
|
printError(err);
|
|
@@ -289,13 +295,10 @@ export function registerKBCommands(program: Command): void {
|
|
|
289
295
|
.argument('<kb-id>', 'Knowledge base ID')
|
|
290
296
|
.argument('<source-id>', 'Data source ID')
|
|
291
297
|
.description('Delete a data source')
|
|
292
|
-
.
|
|
298
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
299
|
+
.action(async (kbId: string, sourceId: string, options) => {
|
|
293
300
|
try {
|
|
294
|
-
|
|
295
|
-
if (!confirmed) {
|
|
296
|
-
console.log('Cancelled.');
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
301
|
+
await confirmOrExit(`Delete data source ${sourceId} from knowledge base ${kbId}? Its vectors are removed too.`, Boolean(options.yes));
|
|
299
302
|
|
|
300
303
|
const client = new IndemnClient();
|
|
301
304
|
const sdk = new KBSDK(client);
|
|
@@ -311,6 +314,102 @@ export function registerKBCommands(program: Command): void {
|
|
|
311
314
|
}
|
|
312
315
|
});
|
|
313
316
|
|
|
317
|
+
data.command('chunks')
|
|
318
|
+
.argument('<kb-id>', 'Knowledge base ID')
|
|
319
|
+
.argument('<source-id>', 'Data source ID')
|
|
320
|
+
.option('--json', 'Output as JSON')
|
|
321
|
+
.description('Show the text chunks the agent actually retrieves from a source')
|
|
322
|
+
.action(async (kbId: string, sourceId: string, options) => {
|
|
323
|
+
try {
|
|
324
|
+
const res = await new KBSDK(new IndemnClient()).chunks(kbId, sourceId);
|
|
325
|
+
if (options.json) { printJson(res); return; }
|
|
326
|
+
console.log(chalk.bold(`${res.source ?? sourceId}`) + chalk.dim(` ${res.status ?? ''} · ${res.chunks.length} chunk(s)`));
|
|
327
|
+
res.chunks.forEach((c, i) => console.log(`${chalk.dim(`#${i + 1}`)} ${c}\n`));
|
|
328
|
+
} catch (err) {
|
|
329
|
+
printError(err);
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
data.command('reimport')
|
|
335
|
+
.argument('<kb-id>', 'Knowledge base ID')
|
|
336
|
+
.argument('<source-id>', 'URL data source ID')
|
|
337
|
+
.option('--js-script <file>', 'Script to run on each page before reading it')
|
|
338
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
339
|
+
.description('Crawl a URL source again after the website changed (runs in the background; see `kb status`)')
|
|
340
|
+
.action(async (kbId: string, sourceId: string, options) => {
|
|
341
|
+
try {
|
|
342
|
+
await confirmOrExit(`Re-crawl source ${sourceId}? Its chunks are replaced when the crawl finishes.`, Boolean(options.yes));
|
|
343
|
+
const js = options.jsScript ? readFileSync(options.jsScript, 'utf8') : undefined;
|
|
344
|
+
await new KBSDK(new IndemnClient()).reimport(kbId, sourceId, js);
|
|
345
|
+
printSuccess(`Re-import started. Follow it with: indemn kb status ${kbId}`);
|
|
346
|
+
} catch (err) {
|
|
347
|
+
printError(err);
|
|
348
|
+
process.exit(1);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
kb.command('status')
|
|
353
|
+
.argument('<kb-id>', 'Knowledge base ID')
|
|
354
|
+
.option('--json', 'Output as JSON')
|
|
355
|
+
.description('Progress of website crawls and file imports')
|
|
356
|
+
.action(async (kbId: string, options) => {
|
|
357
|
+
try {
|
|
358
|
+
const rows = await new KBSDK(new IndemnClient()).scrapeStatus(kbId);
|
|
359
|
+
if (options.json) { printJson(rows); return; }
|
|
360
|
+
if (!rows.length) { console.log('No website or file sources yet.'); return; }
|
|
361
|
+
console.log(formatTable(['Source', 'Status', 'Job', 'Error'], rows.map((r) => [r.source ?? '', r.status ?? '', r.job_status ?? '', r.error ?? ''])));
|
|
362
|
+
} catch (err) {
|
|
363
|
+
printError(err);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
kb.command('gaps')
|
|
369
|
+
.option('--agent <id>', 'Only questions asked to this agent')
|
|
370
|
+
.option('--kb <id>', 'Only questions for this knowledge base')
|
|
371
|
+
.option('--themes', 'Group into themes instead of listing questions')
|
|
372
|
+
.option('--days <n>', 'With --themes: look back this many days')
|
|
373
|
+
.option('--include-test', 'Include questions from test conversations')
|
|
374
|
+
.option('--page <n>', 'Page, starting at 0', '0')
|
|
375
|
+
.option('--limit <n>', 'Rows per page (at most 100)', '20')
|
|
376
|
+
.option('--json', 'Output as JSON')
|
|
377
|
+
.description('Questions the agents could not answer: what to add to the knowledge base next')
|
|
378
|
+
.action(async (options) => {
|
|
379
|
+
try {
|
|
380
|
+
const sdk = new KBSDK(new IndemnClient());
|
|
381
|
+
if (options.themes) {
|
|
382
|
+
printJson(await sdk.gapClusters({ days: options.days ? Number(options.days) : undefined, page: Number(options.page), limit: Number(options.limit) }));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const res = await sdk.gaps({ agentId: options.agent, kbId: options.kb, includeTest: options.includeTest, page: Number(options.page), limit: Number(options.limit) }) as { responses?: Array<Record<string, unknown>>; total?: number };
|
|
386
|
+
if (options.json) { printJson(res); return; }
|
|
387
|
+
const rows = res.responses ?? [];
|
|
388
|
+
if (!rows.length) { console.log('No unanswered questions.'); return; }
|
|
389
|
+
console.log(formatTable(['Question', 'Status', 'Channel'], rows.map((r) => [String(r.user_query ?? '').slice(0, 80), String(r.status ?? ''), String(r.channel ?? '')])));
|
|
390
|
+
console.log(chalk.dim(`${res.total ?? rows.length} in total.`));
|
|
391
|
+
} catch (err) {
|
|
392
|
+
printError(err);
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
kb.command('check')
|
|
398
|
+
.argument('<question>', 'A question a customer might ask')
|
|
399
|
+
.option('--json', 'Output as JSON')
|
|
400
|
+
.description('Could the org\'s knowledge answer this? Shows the best matching chunks')
|
|
401
|
+
.action(async (question: string, options) => {
|
|
402
|
+
try {
|
|
403
|
+
const res = await new KBSDK(new IndemnClient()).check(question);
|
|
404
|
+
if (options.json) { printJson(res); return; }
|
|
405
|
+
console.log(res.answerable ? chalk.green(`Answerable (score ${res.top_score})`) : chalk.yellow(`Not answerable (best score ${res.top_score ?? 0})`));
|
|
406
|
+
for (const src of (res.sources ?? []).slice(0, 3) as Array<{ text?: string; score?: number }>) console.log(chalk.dim(`- (${src.score}) ${String(src.text ?? '').slice(0, 200)}`));
|
|
407
|
+
} catch (err) {
|
|
408
|
+
printError(err);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
314
413
|
// Link/unlink commands
|
|
315
414
|
kb.command('link')
|
|
316
415
|
.argument('<agent-id>', 'Agent ID')
|
|
@@ -336,8 +435,10 @@ export function registerKBCommands(program: Command): void {
|
|
|
336
435
|
.argument('<agent-id>', 'Agent ID')
|
|
337
436
|
.argument('<kb-id>', 'Knowledge base ID')
|
|
338
437
|
.description('Unlink a knowledge base from an agent')
|
|
339
|
-
.
|
|
438
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
439
|
+
.action(async (agentId: string, kbId: string, options) => {
|
|
340
440
|
try {
|
|
441
|
+
await confirmOrExit(`Unlink knowledge base ${kbId} from agent ${agentId}? The agent stops using it immediately.`, Boolean(options.yes));
|
|
341
442
|
const client = new IndemnClient();
|
|
342
443
|
const sdk = new KBSDK(client);
|
|
343
444
|
const spinner = ora('Unlinking knowledge base...').start();
|
|
@@ -376,8 +477,10 @@ export function registerKBCommands(program: Command): void {
|
|
|
376
477
|
.argument('<kb-id>', 'Knowledge base ID')
|
|
377
478
|
.description('Import QNA pairs from a JSON file into a knowledge base')
|
|
378
479
|
.requiredOption('--file <path>', 'Path to JSON file with array of {question, answer} pairs')
|
|
480
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
379
481
|
.action(async (kbId: string, options) => {
|
|
380
482
|
try {
|
|
483
|
+
await confirmOrExit(`Import into knowledge base ${kbId}? The agent starts answering from the new content once processed.`, Boolean(options.yes));
|
|
381
484
|
const fs = await import('fs');
|
|
382
485
|
const filePath = options.file as string;
|
|
383
486
|
|
package/src/cli/link.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { existsSync, mkdirSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join, resolve } from 'node:path';
|
|
6
|
+
import { IndemnClient } from '../sdk/client.js';
|
|
7
|
+
import { runWithOrg } from '../core/scope.js';
|
|
8
|
+
import { CliError, EXIT, isNonInteractive } from '../core/runtime.js';
|
|
9
|
+
import { findLinkFile, LINK_RELATIVE_PATH } from '../core/org-resolver.js';
|
|
10
|
+
import { confirm, printError, printSuccess } from './utils/output.js';
|
|
11
|
+
|
|
12
|
+
export interface LinkResult {
|
|
13
|
+
path: string;
|
|
14
|
+
org_id: string;
|
|
15
|
+
org_name?: string;
|
|
16
|
+
environment: string;
|
|
17
|
+
agents: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pin `dir` to one org (and optionally a list of its agents). Every agent is
|
|
22
|
+
* checked against copilot first; nothing is written unless all pass.
|
|
23
|
+
*/
|
|
24
|
+
export async function writeLink(opts: { dir: string; orgFlag?: string; agents: string[] }): Promise<LinkResult> {
|
|
25
|
+
const dir = resolve(opts.dir);
|
|
26
|
+
const real = (path: string) => { try { return realpathSync(path); } catch { return resolve(path); } };
|
|
27
|
+
const homes = new Set([real(homedir()), real(process.env.HOME ?? homedir())]);
|
|
28
|
+
if (homes.has(real(dir))) {
|
|
29
|
+
throw new CliError('Refusing to link your home folder: every folder under it would be pinned. Run this inside a project folder.');
|
|
30
|
+
}
|
|
31
|
+
// Relinking may change the org, so the existing link must not decide it.
|
|
32
|
+
const client = new IndemnClient({ ignoreLink: true });
|
|
33
|
+
const org = await client.resolveOrg(opts.orgFlag);
|
|
34
|
+
const memberships = await client.memberships().catch(() => []);
|
|
35
|
+
const orgName = org.name ?? memberships.find((o) => o.id === org.id)?.name;
|
|
36
|
+
|
|
37
|
+
await runWithOrg(org.id, async () => {
|
|
38
|
+
for (const agentId of opts.agents) {
|
|
39
|
+
try {
|
|
40
|
+
await client.copilotGet(`/organization/:org_id/ai-studio/v2/bots/${agentId}`);
|
|
41
|
+
} catch (err) {
|
|
42
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
43
|
+
throw new CliError(`Agent ${agentId} is not reachable in org ${orgName ?? org.id} (${why}). Nothing was linked.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const path = join(dir, LINK_RELATIVE_PATH);
|
|
49
|
+
const content = {
|
|
50
|
+
org_id: org.id,
|
|
51
|
+
org_name: orgName,
|
|
52
|
+
environment: client.getEnvironment(),
|
|
53
|
+
agents: opts.agents,
|
|
54
|
+
linked_at: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
mkdirSync(join(dir, '.indemn'), { recursive: true });
|
|
57
|
+
writeFileSync(path, JSON.stringify(content, null, 2) + '\n');
|
|
58
|
+
return { path, org_id: org.id, org_name: orgName, environment: content.environment, agents: opts.agents };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function registerLinkCommands(program: Command): void {
|
|
62
|
+
program
|
|
63
|
+
.command('link')
|
|
64
|
+
.description('Pin this folder to one org (and optionally its agents); commands here then never touch another org')
|
|
65
|
+
.argument('[agent-ids...]', 'Agents allowed from this folder (empty: any agent of the org)')
|
|
66
|
+
.option('--json', 'Output as JSON')
|
|
67
|
+
.action(async (agentIds: string[], options, cmd: Command) => {
|
|
68
|
+
try {
|
|
69
|
+
const result = await writeLink({ dir: process.cwd(), orgFlag: cmd.optsWithGlobals().org, agents: agentIds });
|
|
70
|
+
if (options.json) {
|
|
71
|
+
console.log(JSON.stringify(result, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
printSuccess(`Linked ${process.cwd()} to ${result.org_name ?? result.org_id} on ${result.environment}.`);
|
|
75
|
+
console.log(chalk.dim(` File: ${result.path}`));
|
|
76
|
+
console.log(chalk.dim(` Agents: ${result.agents.length ? result.agents.join(', ') : 'any agent in this org'}`));
|
|
77
|
+
} catch (err) {
|
|
78
|
+
printError(err);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
program
|
|
84
|
+
.command('unlink')
|
|
85
|
+
.description('Remove the org link from this folder')
|
|
86
|
+
.action(() => {
|
|
87
|
+
try {
|
|
88
|
+
const here = join(process.cwd(), LINK_RELATIVE_PATH);
|
|
89
|
+
if (existsSync(here)) {
|
|
90
|
+
rmSync(here);
|
|
91
|
+
printSuccess(`Removed ${here}.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const inherited = findLinkFile();
|
|
95
|
+
if (inherited) {
|
|
96
|
+
throw new CliError(`This folder has no link of its own; it inherits ${inherited.path}. Run \`indemn unlink\` in that folder.`);
|
|
97
|
+
}
|
|
98
|
+
console.log('This folder is not linked.');
|
|
99
|
+
} catch (err) {
|
|
100
|
+
printError(err);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Ask before pinning; non-interactive callers must pass --yes. */
|
|
107
|
+
export async function confirmOrExit(question: string, yes: boolean): Promise<void> {
|
|
108
|
+
if (yes) return;
|
|
109
|
+
if (isNonInteractive()) {
|
|
110
|
+
printError(new CliError(`${question} Re-run with --yes to confirm.`, EXIT.NEEDS_INPUT));
|
|
111
|
+
process.exit(EXIT.NEEDS_INPUT);
|
|
112
|
+
}
|
|
113
|
+
if (!(await confirm(question))) {
|
|
114
|
+
console.log('Cancelled.');
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/cli/org.ts
CHANGED
|
@@ -2,75 +2,69 @@ import { Command } from 'commander';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { IndemnClient } from '../sdk/client.js';
|
|
5
|
-
import {
|
|
5
|
+
import { OrgsSDK } from '../sdk/orgs.js';
|
|
6
|
+
import { getAskers, setAskers } from '../core/scope.js';
|
|
7
|
+
import type { OrgContext } from '../sdk/client.js';
|
|
8
|
+
import { confirmOrExit, writeLink } from './link.js';
|
|
9
|
+
import { formatTable, printError, printJson, printSuccess } from './utils/output.js';
|
|
10
|
+
|
|
11
|
+
const HOW: Record<OrgContext['source'], string> = {
|
|
12
|
+
flag: 'from --org',
|
|
13
|
+
env: 'from INDEMN_ORG_ID',
|
|
14
|
+
link: 'linked folder',
|
|
15
|
+
single: 'your only org',
|
|
16
|
+
asked: 'chosen',
|
|
17
|
+
};
|
|
6
18
|
|
|
7
19
|
export function registerOrgCommands(program: Command): void {
|
|
8
|
-
const org = program.command('org').description('
|
|
20
|
+
const org = program.command('org').description('Organizations you belong to');
|
|
9
21
|
|
|
10
22
|
org
|
|
11
23
|
.command('list')
|
|
12
|
-
.description('List organizations you
|
|
24
|
+
.description('List organizations you belong to')
|
|
13
25
|
.option('--json', 'Output as JSON')
|
|
14
26
|
.action(async (options) => {
|
|
15
27
|
try {
|
|
16
28
|
const client = new IndemnClient();
|
|
17
29
|
const spinner = ora('Fetching organizations...').start();
|
|
18
|
-
|
|
19
|
-
// Get all project memberships
|
|
20
|
-
const res = await fetch(client.getHosts().copilot + '/projects', {
|
|
21
|
-
headers: {
|
|
22
|
-
'Authorization': `Bearer ${client.getApiKey()}`,
|
|
23
|
-
'Content-Type': 'application/json',
|
|
24
|
-
},
|
|
25
|
-
});
|
|
26
|
-
if (!res.ok) throw new Error(`Failed to fetch projects (${res.status})`);
|
|
27
|
-
const memberships = await res.json() as Array<{ id_organization?: string }>;
|
|
28
|
-
|
|
29
|
-
// Get unique org IDs
|
|
30
|
-
const orgIds = [...new Set(memberships.map(m => m.id_organization).filter(Boolean))] as string[];
|
|
31
|
-
|
|
32
|
-
// Fetch org details
|
|
33
|
-
const orgs: Array<{ _id: string; name: string; active: boolean }> = [];
|
|
34
|
-
for (const orgId of orgIds) {
|
|
35
|
-
try {
|
|
36
|
-
const orgRes = await fetch(client.getHosts().copilot + '/organization/' + orgId, {
|
|
37
|
-
headers: {
|
|
38
|
-
'Authorization': `Bearer ${client.getApiKey()}`,
|
|
39
|
-
'Content-Type': 'application/json',
|
|
40
|
-
},
|
|
41
|
-
});
|
|
42
|
-
if (orgRes.ok) {
|
|
43
|
-
const data = await orgRes.json() as { organization: { _id: string; name: string; active: boolean } };
|
|
44
|
-
orgs.push(data.organization);
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// Skip orgs we can't fetch
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
30
|
+
const orgs = await new OrgsSDK(client).listOrganizations();
|
|
51
31
|
spinner.stop();
|
|
32
|
+
const linked = client.getLink()?.org_id;
|
|
52
33
|
|
|
53
34
|
if (options.json) {
|
|
54
|
-
printJson(orgs.map(o => ({
|
|
35
|
+
printJson(orgs.map((o) => ({ ...o, linked_here: o.id === linked })));
|
|
55
36
|
return;
|
|
56
37
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Opportunistically sync the cached org_name while we have fresh data.
|
|
61
|
-
const activeOrg = orgs.find(o => o._id === currentOrgId);
|
|
62
|
-
if (activeOrg) {
|
|
63
|
-
IndemnClient.saveConfig({ org_name: activeOrg.name });
|
|
38
|
+
if (orgs.length === 0) {
|
|
39
|
+
console.log('You are not a member of any organization on this platform.');
|
|
40
|
+
return;
|
|
64
41
|
}
|
|
42
|
+
const rows = orgs
|
|
43
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
44
|
+
.map((o) => [o.id, o.name + (o.id === linked ? chalk.green(' (linked here)') : '')]);
|
|
45
|
+
console.log(formatTable(['ID', 'Name'], rows));
|
|
46
|
+
console.log(chalk.dim(`${orgs.length} organizations on ${client.getEnvironment()}. Pick one per command with --org <id>, or pin a folder with \`indemn link\`.`));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
printError(err);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
65
52
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
53
|
+
org
|
|
54
|
+
.command('current')
|
|
55
|
+
.description('Show which org commands in this folder act on, and why (never asks)')
|
|
56
|
+
.option('--json', 'Output as JSON')
|
|
57
|
+
.action(async (options) => {
|
|
58
|
+
try {
|
|
59
|
+
setAskers({ ...getAskers(), chooseOrg: undefined });
|
|
60
|
+
const client = new IndemnClient();
|
|
61
|
+
const resolved = await client.resolveOrg();
|
|
62
|
+
const out = { id: resolved.id, name: resolved.name, source: resolved.source, link: resolved.link?.path, environment: client.getEnvironment() };
|
|
63
|
+
if (options.json) {
|
|
64
|
+
printJson(out);
|
|
65
|
+
return;
|
|
71
66
|
}
|
|
72
|
-
console.log('');
|
|
73
|
-
console.log(chalk.dim(`${orgs.length} organizations. Switch with: indemn org switch <id>`));
|
|
67
|
+
console.log(`${out.name ?? '(unnamed org)'} ${chalk.dim(out.id)} ${chalk.dim(`${HOW[resolved.source]}${out.link ? `: ${out.link}` : ''} · ${out.environment}`)}`);
|
|
74
68
|
} catch (err) {
|
|
75
69
|
printError(err);
|
|
76
70
|
process.exit(1);
|
|
@@ -78,34 +72,46 @@ export function registerOrgCommands(program: Command): void {
|
|
|
78
72
|
});
|
|
79
73
|
|
|
80
74
|
org
|
|
81
|
-
.command('
|
|
82
|
-
.description('
|
|
83
|
-
.
|
|
84
|
-
.action(async (
|
|
75
|
+
.command('members')
|
|
76
|
+
.description('List owners, admins and agents of the org')
|
|
77
|
+
.option('--json', 'Output as JSON')
|
|
78
|
+
.action(async (options) => {
|
|
85
79
|
try {
|
|
86
80
|
const client = new IndemnClient();
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
headers: {
|
|
92
|
-
'Authorization': `Bearer ${client.getApiKey()}`,
|
|
93
|
-
'Content-Type': 'application/json',
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
if (!orgRes.ok) {
|
|
98
|
-
spinner.fail('Organization not found or you don\'t have access.');
|
|
99
|
-
process.exit(1);
|
|
81
|
+
const members = await new OrgsSDK(client).listMembers();
|
|
82
|
+
if (options.json) {
|
|
83
|
+
printJson(members);
|
|
84
|
+
return;
|
|
100
85
|
}
|
|
86
|
+
if (members.length === 0) {
|
|
87
|
+
console.log('No owners, admins or agents found in this org.');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
console.log(formatTable(
|
|
91
|
+
['Email', 'Name', 'Role', 'Status', 'Projects'],
|
|
92
|
+
members.map((m) => [m.email ?? '', m.name ?? '', m.role ?? '', m.status ?? '', m.projects.join(', ')]),
|
|
93
|
+
));
|
|
94
|
+
} catch (err) {
|
|
95
|
+
printError(err);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
101
99
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
org
|
|
101
|
+
.command('switch')
|
|
102
|
+
.description('Pin this folder to an org (there is no global default org any more)')
|
|
103
|
+
.argument('<org-id>', 'Organization ID')
|
|
104
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
105
|
+
.action(async (orgId: string, options) => {
|
|
106
|
+
try {
|
|
107
|
+
const client = new IndemnClient({ ignoreLink: true });
|
|
108
|
+
const orgs = await client.memberships();
|
|
109
|
+
const target = orgs.find((o) => o.id === orgId);
|
|
110
|
+
if (!target) throw new Error(`You are not a member of ${orgId} on ${client.getEnvironment()}. See \`indemn org list\`.`);
|
|
111
|
+
console.log(chalk.dim('The CLI no longer keeps one default org. Commands use --org, the folder link, or ask.'));
|
|
112
|
+
await confirmOrExit(`Pin ${process.cwd()} to ${target.name}?`, Boolean(options.yes));
|
|
113
|
+
const result = await writeLink({ dir: process.cwd(), orgFlag: orgId, agents: [] });
|
|
114
|
+
printSuccess(`Linked ${process.cwd()} to ${result.org_name ?? result.org_id}. File: ${result.path}`);
|
|
109
115
|
} catch (err) {
|
|
110
116
|
printError(err);
|
|
111
117
|
process.exit(1);
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { IndemnClient } from '../sdk/client.js';
|
|
5
|
+
import { PlatformSDK, type PlatformResource } from '../sdk/platform.js';
|
|
6
|
+
import { jarvisChat } from '../sdk/jarvis-chat.js';
|
|
7
|
+
import { CliError, EXIT } from '../core/runtime.js';
|
|
8
|
+
import { confirmOrExit } from './link.js';
|
|
9
|
+
import { printError, printJson, printSuccess } from './utils/output.js';
|
|
10
|
+
|
|
11
|
+
function run(fn: () => Promise<void>): Promise<void> {
|
|
12
|
+
return fn().catch((err) => {
|
|
13
|
+
printError(err);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const sdk = () => new PlatformSDK(new IndemnClient());
|
|
18
|
+
const readJson = (path: string) => JSON.parse(readFileSync(path, 'utf8')) as Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
const RESOURCES: Array<{ cmd: string; resource: PlatformResource; writable: boolean; what: string }> = [
|
|
21
|
+
{ cmd: 'agents', resource: 'agents', writable: true, what: 'V2 agents built from templates' },
|
|
22
|
+
{ cmd: 'templates', resource: 'templates', writable: true, what: 'V2 agent templates (graphs)' },
|
|
23
|
+
{ cmd: 'components', resource: 'components', writable: false, what: 'building blocks of templates (shared catalog, read-only)' },
|
|
24
|
+
{ cmd: 'connectors', resource: 'connectors', writable: false, what: 'actions agents can call (shared catalog, read-only)' },
|
|
25
|
+
{ cmd: 'bindings', resource: 'connector-bindings', writable: true, what: 'connector credentials and settings of the org' },
|
|
26
|
+
{ cmd: 'scenarios', resource: 'scenarios', writable: true, what: 'scripted conversations for simulations' },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export function registerPlatformCommands(program: Command): void {
|
|
30
|
+
const platform = program.command('platform').description('The V2 platform (Darwin): V2 agents, templates, connectors, scenarios, simulations');
|
|
31
|
+
|
|
32
|
+
for (const r of RESOURCES) {
|
|
33
|
+
const g = platform.command(r.cmd).description(r.what);
|
|
34
|
+
g.command('list').option('--agent <id>', 'Only for this agent (scenarios)').action((options) => run(async () => {
|
|
35
|
+
printJson(await sdk().list(r.resource, options.agent ? { agent_id: options.agent } : undefined));
|
|
36
|
+
}));
|
|
37
|
+
g.command('get').argument('<id>').action((id: string) => run(async () => printJson(await sdk().get(r.resource, id))));
|
|
38
|
+
if (!r.writable) continue;
|
|
39
|
+
g.command('create').requiredOption('--file <path>', 'JSON body').option('--yes', 'Do not ask for confirmation').action((options) => run(async () => {
|
|
40
|
+
const body = readJson(options.file);
|
|
41
|
+
await confirmOrExit(`Create ${r.cmd.replace(/s$/, '')} "${String(body.name ?? body.connector_id ?? '')}" on the V2 platform?`, Boolean(options.yes));
|
|
42
|
+
printJson(await sdk().create(r.resource, body));
|
|
43
|
+
}));
|
|
44
|
+
g.command('update').argument('<id>').requiredOption('--file <path>', 'JSON with the fields to change').option('--yes', 'Do not ask for confirmation').action((id: string, options) => run(async () => {
|
|
45
|
+
const body = readJson(options.file);
|
|
46
|
+
await confirmOrExit(`Change ${r.cmd.replace(/s$/, '')} ${id} (${Object.keys(body).join(', ')})?`, Boolean(options.yes));
|
|
47
|
+
printJson(await sdk().update(r.resource, id, body));
|
|
48
|
+
}));
|
|
49
|
+
g.command('delete').argument('<id>').option('--yes', 'Do not ask for confirmation').action((id: string, options) => run(async () => {
|
|
50
|
+
await confirmOrExit(`Delete ${r.cmd.replace(/s$/, '')} ${id} from the V2 platform?`, Boolean(options.yes));
|
|
51
|
+
await sdk().remove(r.resource, id);
|
|
52
|
+
printSuccess(`Deleted ${id}.`);
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
platform.commands.find((c) => c.name() === 'templates')!.command('validate').argument('<id>').description('Check the template graph').action((id: string) => run(async () => {
|
|
57
|
+
const res = await sdk().validateTemplate(id);
|
|
58
|
+
printJson(res);
|
|
59
|
+
if (!res.valid) process.exit(1);
|
|
60
|
+
}));
|
|
61
|
+
platform.commands.find((c) => c.name() === 'connectors')!.command('test').argument('<id>').requiredOption('--input <file>', 'JSON input')
|
|
62
|
+
.description('Validate input against the connector and preview the request (does not call it)')
|
|
63
|
+
.action((id: string, options) => run(async () => printJson(await sdk().testConnector(id, readJson(options.input)))));
|
|
64
|
+
|
|
65
|
+
const sims = platform.command('simulations').description('Run scenarios against a V2 agent');
|
|
66
|
+
sims.command('run').requiredOption('--agent <id>', 'V2 agent').option('--scenarios <ids>', 'Comma-separated scenario ids (default: all of the agent)')
|
|
67
|
+
.option('--wait', 'Wait for the result').option('--yes', 'Do not ask for confirmation')
|
|
68
|
+
.action((options) => run(async () => {
|
|
69
|
+
await confirmOrExit(`Run simulations for agent ${options.agent}? They use paid model calls.`, Boolean(options.yes));
|
|
70
|
+
const s = sdk();
|
|
71
|
+
const job = await s.runSimulation(options.agent, options.scenarios ? options.scenarios.split(',') : []) as { job_id: string };
|
|
72
|
+
if (!options.wait) { printJson(job); return; }
|
|
73
|
+
for (;;) {
|
|
74
|
+
const j = await s.simulationJob(job.job_id) as { status: string; result_id?: string };
|
|
75
|
+
if (j.status === 'completed' || j.status === 'failed') {
|
|
76
|
+
printJson(j.result_id ? await s.simulationResults({ resultId: j.result_id }) : j);
|
|
77
|
+
if (j.status === 'failed') process.exit(1);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await new Promise((r) => setTimeout(r, 5_000));
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
83
|
+
sims.command('jobs').option('--agent <id>').option('--status <status>').action((options) => run(async () => printJson(await sdk().simulationJobs({ agentId: options.agent, status: options.status }))));
|
|
84
|
+
sims.command('results').option('--agent <id>').option('--id <result-id>').option('--limit <n>', 'At most this many', '10')
|
|
85
|
+
.action((options) => run(async () => printJson(await sdk().simulationResults({ agentId: options.agent, resultId: options.id, limit: Number(options.limit) }))));
|
|
86
|
+
|
|
87
|
+
platform.command('org-evaluations').description('Which agents have a baseline evaluation, and which are stale').action(() => run(async () => printJson(await sdk().orgEvaluations())));
|
|
88
|
+
|
|
89
|
+
const jarvis = program.command('jarvis').description('Jarvis, the agent-building assistant of the V2 platform');
|
|
90
|
+
const jobs = jarvis.command('jobs').description('Background Jarvis jobs');
|
|
91
|
+
jobs.command('create')
|
|
92
|
+
.requiredOption('--bots <ids>', 'Comma-separated agent ids (1 to 50) to generate baseline evaluations for')
|
|
93
|
+
.option('--wait', 'Wait until the job completes')
|
|
94
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
95
|
+
.action((options) => run(async () => {
|
|
96
|
+
const ids = String(options.bots).split(',').map((s: string) => s.trim()).filter(Boolean);
|
|
97
|
+
await confirmOrExit(`Ask Jarvis to generate baseline evaluations (rubric and test set) for ${ids.length} agent(s)? It uses paid model calls.`, Boolean(options.yes));
|
|
98
|
+
const s = sdk();
|
|
99
|
+
const job = await s.createJarvisJob(ids);
|
|
100
|
+
if (!options.wait) { printJson(job); return; }
|
|
101
|
+
const done = await s.waitForJarvisJob(job.job_id, { onProgress: (j) => process.stderr.write(chalk.dim(` ${j.status} ${j.progress?.completed ?? 0}/${j.progress?.total ?? ids.length}\n`)) });
|
|
102
|
+
printJson(done);
|
|
103
|
+
if ((done.progress?.results ?? []).some((r) => !r.success)) process.exit(1);
|
|
104
|
+
}));
|
|
105
|
+
jobs.command('list').option('--status <status>').action((options) => run(async () => printJson(await sdk().jarvisJobs(options.status))));
|
|
106
|
+
jobs.command('get').argument('<job-id>').action((id: string) => run(async () => printJson(await sdk().jarvisJob(id))));
|
|
107
|
+
|
|
108
|
+
jarvis.command('chat')
|
|
109
|
+
.argument('<message>', 'What to ask Jarvis')
|
|
110
|
+
.option('--conversation <id>', 'Continue a conversation (id printed after each reply)')
|
|
111
|
+
.option('--agent <v1-agent-id>', 'The V1 agent this is about (as when Jarvis is opened from its page)')
|
|
112
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
113
|
+
.option('--json', 'Print { conversation_id, reply, tools } as JSON')
|
|
114
|
+
.description('One turn with Jarvis. Jarvis can create and change templates, agents and connectors, so this asks first')
|
|
115
|
+
.action((message: string, options) => run(async () => {
|
|
116
|
+
if (!message.trim()) throw new CliError('Give a message.', EXIT.NEEDS_INPUT);
|
|
117
|
+
await confirmOrExit('Send this to Jarvis? It may create or change templates, agents or connectors in the org.', Boolean(options.yes));
|
|
118
|
+
const res = await jarvisChat(new IndemnClient(), message, {
|
|
119
|
+
conversationId: options.conversation,
|
|
120
|
+
context: options.agent ? { federation_bot_id: options.agent } : undefined,
|
|
121
|
+
onText: options.json ? undefined : (t) => process.stdout.write(t),
|
|
122
|
+
onTool: options.json ? undefined : (t) => process.stderr.write(chalk.dim(`\n [${t.name}: ${t.status}]\n`)),
|
|
123
|
+
});
|
|
124
|
+
if (options.json) { printJson(res); return; }
|
|
125
|
+
process.stdout.write('\n');
|
|
126
|
+
console.log(chalk.dim(`Conversation ${res.conversation_id}. Continue with --conversation ${res.conversation_id}.`));
|
|
127
|
+
}));
|
|
128
|
+
}
|