@indemn/cli 1.5.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 +125 -35
- 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 +122 -36
- 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/agents.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { IndemnClient } from '../sdk/client.js';
|
|
4
|
-
import { AgentsSDK } from '../sdk/agents.js';
|
|
4
|
+
import { AgentInsightsSDK, AgentsSDK, type Lifecycle } from '../sdk/agents.js';
|
|
5
|
+
import { CliError, EXIT } from '../core/runtime.js';
|
|
6
|
+
import { confirmOrExit } from './link.js';
|
|
7
|
+
import { DistributionSDK } from '../sdk/distribution.js';
|
|
8
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { isNonInteractive } from '../core/runtime.js';
|
|
11
|
+
import { askLine } from './utils/output.js';
|
|
12
|
+
import {
|
|
13
|
+
createAgentFromSkeleton, exportAgent, importAgent, missingSkeletonFields, parseModel, type AgentBundle, type AgentSkeleton,
|
|
14
|
+
} from '../sdk/agent-bundle.js';
|
|
5
15
|
import { exportAgentCard } from '../report/export.js';
|
|
6
16
|
import { formatTable, printJson, printError, printSuccess, confirm } from './utils/output.js';
|
|
7
17
|
|
|
@@ -98,22 +108,108 @@ export function registerAgentCommands(program: Command): void {
|
|
|
98
108
|
|
|
99
109
|
agents
|
|
100
110
|
.command('create')
|
|
101
|
-
.description('Create a
|
|
102
|
-
.
|
|
103
|
-
.option('--channels <channels>', 'Comma-separated channels', 'WEB')
|
|
111
|
+
.description('Create an agent with its basics: name, channels, purpose, prompt and model. Asks for missing ones in a terminal.')
|
|
112
|
+
.option('--name <name>', 'Agent name')
|
|
113
|
+
.option('--channels <channels>', 'Comma-separated channels (see `indemn channels list`)', 'WEB')
|
|
114
|
+
.option('--description <text>', 'What the agent is for')
|
|
115
|
+
.option('--prompt <text>', 'System prompt')
|
|
116
|
+
.option('--prompt-file <path>', 'Read the system prompt from a file')
|
|
117
|
+
.option('--model <provider/model>', 'LLM, for example openai/gpt-4.1 (see `indemn models`)')
|
|
118
|
+
.option('--allow-duplicate', 'Create even if an agent with this name exists in the org')
|
|
119
|
+
.option('--template <id>', 'Copy a template instead (see `indemn templates list`); keeps its prompt, model, skills and knowledge')
|
|
120
|
+
.option('--json', 'Output as JSON')
|
|
104
121
|
.action(async (options) => {
|
|
105
122
|
try {
|
|
123
|
+
if (options.template) {
|
|
124
|
+
if (!options.name) throw new CliError('Name the new agent: --name "<name>".', EXIT.NEEDS_INPUT);
|
|
125
|
+
const client = new IndemnClient();
|
|
126
|
+
if (!options.allowDuplicate && (await new AgentsSDK(client).findByName(options.name)).length) {
|
|
127
|
+
throw new CliError(`An agent named "${options.name}" already exists in this org. Choose another name or pass --allow-duplicate.`, EXIT.NEEDS_INPUT);
|
|
128
|
+
}
|
|
129
|
+
const res = await new DistributionSDK(client).createFromTemplate(options.template, {
|
|
130
|
+
name: options.name, channels: String(options.channels).split(',').map((c: string) => c.trim()).filter(Boolean),
|
|
131
|
+
});
|
|
132
|
+
if (options.json) { printJson(res); return; }
|
|
133
|
+
printSuccess(`Agent created from template: ${res.new_agent_id} (${res.new_agent_name})`);
|
|
134
|
+
console.log(chalk.dim(' Not copied: phone numbers, voice settings, website distributions.'));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const skeleton: AgentSkeleton = {
|
|
138
|
+
name: options.name ?? '',
|
|
139
|
+
channels: String(options.channels).split(',').map((c: string) => c.trim()).filter(Boolean),
|
|
140
|
+
description: options.description,
|
|
141
|
+
system_prompt: options.promptFile ? readFileSync(options.promptFile, 'utf8') : options.prompt,
|
|
142
|
+
model: options.model ? parseModel(options.model) : undefined,
|
|
143
|
+
};
|
|
144
|
+
const missing = missingSkeletonFields(skeleton);
|
|
145
|
+
if (missing.length && !isNonInteractive()) {
|
|
146
|
+
if (missing.includes('name')) skeleton.name = await askLine('Agent name: ');
|
|
147
|
+
if (missing.includes('description')) skeleton.description = await askLine('What is this agent for? (one line, Enter to skip): ');
|
|
148
|
+
if (missing.includes('system_prompt')) skeleton.system_prompt = await askLine('System prompt (Enter to skip; use --prompt-file for long prompts): ');
|
|
149
|
+
}
|
|
150
|
+
if (!skeleton.name.trim()) throw new CliError('An agent needs a name: --name "<name>".', EXIT.NEEDS_INPUT);
|
|
151
|
+
|
|
106
152
|
const client = new IndemnClient();
|
|
107
|
-
const sdk = new AgentsSDK(client);
|
|
108
153
|
const spinner = ora('Creating agent...').start();
|
|
109
|
-
|
|
110
|
-
const channels = options.channels.split(',').map((c: string) => c.trim());
|
|
111
|
-
const agent = await sdk.createAgent({ name: options.name, channels });
|
|
154
|
+
const { agent, set } = await createAgentFromSkeleton(client, skeleton, { allowDuplicate: options.allowDuplicate });
|
|
112
155
|
spinner.stop();
|
|
113
|
-
|
|
156
|
+
if (options.json) { printJson({ agent, set }); return; }
|
|
114
157
|
printSuccess(`Agent created: ${agent._id}`);
|
|
115
158
|
console.log(` Name: ${agent.name}`);
|
|
116
159
|
console.log(` Channels: ${formatChannels(agent.channels || [])}`);
|
|
160
|
+
console.log(` Set: ${set.join(', ')}`);
|
|
161
|
+
const unset = missingSkeletonFields(skeleton);
|
|
162
|
+
if (unset.length) console.log(chalk.dim(` Not set yet: ${unset.join(', ')}. Use \`indemn config set ${agent._id}\`.`));
|
|
163
|
+
} catch (err) {
|
|
164
|
+
printError(err);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
agents
|
|
170
|
+
.command('export')
|
|
171
|
+
.argument('<id>', 'Agent ID')
|
|
172
|
+
.option('-o, --output <file>', 'Write to a file instead of stdout')
|
|
173
|
+
.option('--include-secrets', 'Keep API keys and tokens inside skill settings (default: redacted)')
|
|
174
|
+
.description('Save an agent (prompt, greeting, model, skills, linked knowledge bases) to a JSON bundle')
|
|
175
|
+
.action(async (id: string, options) => {
|
|
176
|
+
try {
|
|
177
|
+
const bundle = await exportAgent(new IndemnClient(), id, { includeSecrets: options.includeSecrets });
|
|
178
|
+
const text = JSON.stringify(bundle, null, 2) + '\n';
|
|
179
|
+
if (options.output) {
|
|
180
|
+
writeFileSync(options.output, text, { mode: options.includeSecrets ? 0o600 : 0o644 });
|
|
181
|
+
printSuccess(`Wrote ${options.output}.`);
|
|
182
|
+
if (bundle.redacted.length) console.log(chalk.dim(` Redacted ${bundle.redacted.length} secret value(s); fill them in after import.`));
|
|
183
|
+
} else {
|
|
184
|
+
process.stdout.write(text);
|
|
185
|
+
}
|
|
186
|
+
} catch (err) {
|
|
187
|
+
printError(err);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
agents
|
|
193
|
+
.command('import')
|
|
194
|
+
.argument('<file>', 'Bundle written by `indemn agents export`')
|
|
195
|
+
.option('--name <name>', 'Name for the new agent (default: the bundle name)')
|
|
196
|
+
.option('--link-kbs', 'Link the same knowledge base ids if they exist in this org')
|
|
197
|
+
.option('--allow-duplicate', 'Create even if an agent with this name exists in the org')
|
|
198
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
199
|
+
.option('--json', 'Output as JSON')
|
|
200
|
+
.description('Create a new agent in the chosen org from an exported bundle')
|
|
201
|
+
.action(async (file: string, options) => {
|
|
202
|
+
try {
|
|
203
|
+
const bundle = JSON.parse(readFileSync(file, 'utf8')) as AgentBundle;
|
|
204
|
+
const client = new IndemnClient();
|
|
205
|
+
const org = await client.resolveOrg();
|
|
206
|
+
await confirmOrExit(`Create agent "${options.name ?? bundle.agent?.name}" with ${bundle.skills?.length ?? 0} skill(s) in ${org.name ?? org.id} on ${client.getEnvironment()}?`, Boolean(options.yes));
|
|
207
|
+
const report = await importAgent(client, bundle, { name: options.name, allowDuplicate: options.allowDuplicate, linkKnowledgeBases: options.linkKbs });
|
|
208
|
+
if (options.json) { printJson(report); return; }
|
|
209
|
+
printSuccess(`Agent created: ${report.agent._id} (${report.skills_created} skill(s) created, ${report.built_in_skills_reused.length} built-in reused)`);
|
|
210
|
+
for (const f of report.skills_failed) console.log(chalk.yellow(` Skill ${f.name ?? '(unnamed)'} failed: ${f.error}`));
|
|
211
|
+
if (report.needs_secrets.length) console.log(chalk.yellow(` Fill in ${report.needs_secrets.length} redacted secret(s) in the skills.`));
|
|
212
|
+
if (report.knowledge_bases_skipped.length) console.log(chalk.dim(` Knowledge bases not linked: ${report.knowledge_bases_skipped.join(', ')}`));
|
|
117
213
|
} catch (err) {
|
|
118
214
|
printError(err);
|
|
119
215
|
process.exit(1);
|
|
@@ -156,22 +252,18 @@ export function registerAgentCommands(program: Command): void {
|
|
|
156
252
|
agents
|
|
157
253
|
.command('delete')
|
|
158
254
|
.argument('<id>', 'Agent ID')
|
|
159
|
-
.
|
|
160
|
-
.
|
|
255
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
256
|
+
.description('Delete an agent (asks first; --yes for scripts)')
|
|
257
|
+
.action(async (id: string, options) => {
|
|
161
258
|
try {
|
|
162
|
-
const confirmed = await confirm(`Delete agent ${id}? This cannot be undone.`);
|
|
163
|
-
if (!confirmed) {
|
|
164
|
-
console.log('Cancelled.');
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
259
|
const client = new IndemnClient();
|
|
169
260
|
const sdk = new AgentsSDK(client);
|
|
261
|
+
const agent = await sdk.getAgent(id);
|
|
262
|
+
const org = await client.resolveOrg();
|
|
263
|
+
await confirmOrExit(`Delete agent "${agent?.name ?? id}" (${id}) in org ${org.name ?? org.id} on ${client.getEnvironment()}? This cannot be undone.`, Boolean(options.yes));
|
|
170
264
|
const spinner = ora('Deleting agent...').start();
|
|
171
|
-
|
|
172
265
|
await sdk.deleteAgent(id);
|
|
173
266
|
spinner.stop();
|
|
174
|
-
|
|
175
267
|
printSuccess(`Agent ${id} deleted.`);
|
|
176
268
|
} catch (err) {
|
|
177
269
|
printError(err);
|
|
@@ -179,6 +271,93 @@ export function registerAgentCommands(program: Command): void {
|
|
|
179
271
|
}
|
|
180
272
|
});
|
|
181
273
|
|
|
274
|
+
agents
|
|
275
|
+
.command('lifecycle')
|
|
276
|
+
.argument('<id>', 'Agent ID')
|
|
277
|
+
.argument('<stage>', 'prototype, beta or published')
|
|
278
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
279
|
+
.description('Change the lifecycle stage (only the owner or an org admin may; checked by the V2 platform)')
|
|
280
|
+
.action(async (id: string, stage: string, options) => {
|
|
281
|
+
try {
|
|
282
|
+
if (!['prototype', 'beta', 'published'].includes(stage)) {
|
|
283
|
+
throw new CliError('Stage must be prototype, beta or published.', EXIT.NEEDS_INPUT);
|
|
284
|
+
}
|
|
285
|
+
const client = new IndemnClient();
|
|
286
|
+
const insights = new AgentInsightsSDK(client);
|
|
287
|
+
const before = await insights.detail(id);
|
|
288
|
+
if (before.lifecycle === stage) {
|
|
289
|
+
console.log(`Agent ${id} is already ${stage}.`);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
await confirmOrExit(`Move "${before.name}" from ${before.lifecycle ?? 'prototype'} to ${stage}?`, Boolean(options.yes));
|
|
293
|
+
const after = await insights.setLifecycle(id, stage as Lifecycle);
|
|
294
|
+
printSuccess(`Agent ${id} is now ${after.lifecycle}.`);
|
|
295
|
+
} catch (err) {
|
|
296
|
+
printError(err);
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
agents
|
|
302
|
+
.command('owner')
|
|
303
|
+
.argument('<id>', 'Agent ID')
|
|
304
|
+
.argument('[user-id]', 'New owner (user id); omit to show the current owner')
|
|
305
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
306
|
+
.option('--json', 'Output as JSON')
|
|
307
|
+
.description('Show or transfer the owner (transfer: owner or org admin only)')
|
|
308
|
+
.action(async (id: string, userId: string | undefined, options) => {
|
|
309
|
+
try {
|
|
310
|
+
const client = new IndemnClient();
|
|
311
|
+
const insights = new AgentInsightsSDK(client);
|
|
312
|
+
const detail = await insights.detail(id);
|
|
313
|
+
if (!userId) {
|
|
314
|
+
if (options.json) { printJson({ owner: detail.owner ?? null, lifecycle: detail.lifecycle }); return; }
|
|
315
|
+
const o = detail.owner;
|
|
316
|
+
console.log(o ? `${[o.firstname, o.lastname].filter(Boolean).join(' ')} <${o.email ?? ''}> (${o.id})` : 'No owner recorded.');
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await confirmOrExit(`Transfer "${detail.name}" to user ${userId}?`, Boolean(options.yes));
|
|
320
|
+
await insights.transferOwner(id, userId);
|
|
321
|
+
printSuccess(`Owner of ${id} is now ${userId}.`);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
printError(err);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
agents
|
|
329
|
+
.command('summary')
|
|
330
|
+
.argument('<id>', 'Agent ID')
|
|
331
|
+
.option('--full', 'Skills and knowledge sources instead of one paragraph')
|
|
332
|
+
.option('--json', 'Output as JSON')
|
|
333
|
+
.description('What the agent does, written for a person or an AI to read before changing it')
|
|
334
|
+
.action(async (id: string, options) => {
|
|
335
|
+
try {
|
|
336
|
+
const result = await new AgentInsightsSDK(new IndemnClient()).summary(id, { text: !options.full });
|
|
337
|
+
if (options.json || options.full) { printJson(result); return; }
|
|
338
|
+
console.log((result as { summary?: string }).summary ?? '');
|
|
339
|
+
} catch (err) {
|
|
340
|
+
printError(err);
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
agents
|
|
346
|
+
.command('stats')
|
|
347
|
+
.argument('<id>', 'Agent ID')
|
|
348
|
+
.option('--from <date>', 'YYYY-MM-DD (default: 30 days ago)')
|
|
349
|
+
.option('--to <date>', 'YYYY-MM-DD (default: today)')
|
|
350
|
+
.option('--channel <channel>', 'web, voice or all', 'all')
|
|
351
|
+
.description('Conversation metrics for one agent (test conversations excluded)')
|
|
352
|
+
.action(async (id: string, options) => {
|
|
353
|
+
try {
|
|
354
|
+
printJson(await new AgentInsightsSDK(new IndemnClient()).stats(id, { from: options.from, to: options.to, channel: options.channel }));
|
|
355
|
+
} catch (err) {
|
|
356
|
+
printError(err);
|
|
357
|
+
process.exit(1);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
182
361
|
agents
|
|
183
362
|
.command('card')
|
|
184
363
|
.argument('<id>', 'Agent ID')
|
|
@@ -203,14 +382,15 @@ export function registerAgentCommands(program: Command): void {
|
|
|
203
382
|
agents
|
|
204
383
|
.command('clone')
|
|
205
384
|
.argument('<id>', 'Agent ID to clone')
|
|
206
|
-
.
|
|
207
|
-
.
|
|
385
|
+
.option('--name <name>', 'Name of the copy')
|
|
386
|
+
.description('Copy an agent of this org into a new agent in the same org')
|
|
387
|
+
.action(async (id: string, options) => {
|
|
208
388
|
try {
|
|
209
389
|
const client = new IndemnClient();
|
|
210
390
|
const sdk = new AgentsSDK(client);
|
|
211
391
|
const spinner = ora('Cloning agent...').start();
|
|
212
392
|
|
|
213
|
-
const result = await sdk.cloneAgent(id);
|
|
393
|
+
const result = await sdk.cloneAgent(id, { name: options.name });
|
|
214
394
|
spinner.stop();
|
|
215
395
|
|
|
216
396
|
// Clone returns { new_agent_id, new_agent_name } or an Agent object
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { IndemnClient } from '../sdk/client.js';
|
|
4
|
+
import { AnalyticsSDK, AuditSDK, OrgSettingsSDK, type MetricKind } from '../sdk/analytics.js';
|
|
5
|
+
import { CliError, EXIT } from '../core/runtime.js';
|
|
6
|
+
import { confirmOrExit } from './link.js';
|
|
7
|
+
import { formatTable, printError, printJson, printSuccess } from './utils/output.js';
|
|
8
|
+
|
|
9
|
+
function run(fn: () => Promise<void>): Promise<void> {
|
|
10
|
+
return fn().catch((err) => {
|
|
11
|
+
printError(err);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function withRange(cmd: Command, agent = true): Command {
|
|
17
|
+
cmd.option('--from <date>', 'YYYY-MM-DD (default: 30 days ago)').option('--to <date>', 'YYYY-MM-DD (default: today)');
|
|
18
|
+
if (agent) cmd.option('--agent <id>', 'Only this agent').option('--channel <channel>', 'web, voice or all', 'all');
|
|
19
|
+
return cmd;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const KINDS: Record<string, MetricKind> = {
|
|
23
|
+
metrics: 'metrics',
|
|
24
|
+
performance: 'model-performance',
|
|
25
|
+
'performance-by-agent': 'model-performance/bots',
|
|
26
|
+
volume: 'agent-volume',
|
|
27
|
+
handoffs: 'handoff-precursors',
|
|
28
|
+
feedback: 'critical-feedback',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function registerAnalyticsCommands(program: Command): void {
|
|
32
|
+
const analytics = program.command('analytics').description('The numbers of the dashboard analytics pages (test conversations excluded)');
|
|
33
|
+
|
|
34
|
+
for (const [name, kind] of Object.entries(KINDS)) {
|
|
35
|
+
const perAgent = !['performance-by-agent', 'volume'].includes(name);
|
|
36
|
+
withRange(analytics.command(name).description(`Analytics: ${kind}`), perAgent)
|
|
37
|
+
.action((options) => run(async () => {
|
|
38
|
+
printJson(await new AnalyticsSDK(new IndemnClient()).get(kind, { from: options.from, to: options.to, channel: options.channel, agentId: options.agent }));
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
withRange(analytics.command('conversations').description('The conversations behind a number, e.g. --dimension outcome --value handoff'))
|
|
43
|
+
.option('--dimension <name>', 'outcome, feedback, sentiment, precursor, reason, critical_feedback_theme or attribute:<key>')
|
|
44
|
+
.option('--value <value>', 'Value of the dimension')
|
|
45
|
+
.option('--search <text>', 'Text search')
|
|
46
|
+
.option('--page <n>', 'Page, starting at 1', '1')
|
|
47
|
+
.option('--page-size <n>', 'Rows per page (at most 200)', '50')
|
|
48
|
+
.action((options) => run(async () => {
|
|
49
|
+
printJson(await new AnalyticsSDK(new IndemnClient()).conversations({
|
|
50
|
+
from: options.from, to: options.to, channel: options.channel, agentId: options.agent, dimension: options.dimension, value: options.value,
|
|
51
|
+
search: options.search, page: Number(options.page), pageSize: Number(options.pageSize),
|
|
52
|
+
}));
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
analytics.command('charts').description('Custom charts configured for the org').action(() => run(async () => {
|
|
56
|
+
printJson(await new AnalyticsSDK(new IndemnClient()).charts());
|
|
57
|
+
}));
|
|
58
|
+
|
|
59
|
+
withRange(analytics.command('chart').argument('<chart-id>', 'Chart ID from `analytics charts`').description('Data of one custom chart'))
|
|
60
|
+
.action((id: string, options) => run(async () => {
|
|
61
|
+
printJson(await new AnalyticsSDK(new IndemnClient()).chart(id, { from: options.from, to: options.to, channel: options.channel, agentId: options.agent }));
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
const org = program.commands.find((c) => c.name() === 'org');
|
|
65
|
+
if (!org) throw new Error('org command group must be registered before analytics commands');
|
|
66
|
+
const config = org.command('config').description('Org settings: timezone, business hours, holidays');
|
|
67
|
+
|
|
68
|
+
config.command('get').description('All org settings (handoff settings are read-only)').action(() => run(async () => {
|
|
69
|
+
printJson(await new OrgSettingsSDK(new IndemnClient()).get());
|
|
70
|
+
}));
|
|
71
|
+
config.command('availability').description('Is the org inside business hours now?').action(() => run(async () => {
|
|
72
|
+
printJson(await new OrgSettingsSDK(new IndemnClient()).availability());
|
|
73
|
+
}));
|
|
74
|
+
config.command('timezone')
|
|
75
|
+
.argument('<zone>', 'For example America/New_York')
|
|
76
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
77
|
+
.action((zone: string, options) => run(async () => {
|
|
78
|
+
await confirmOrExit(`Set the org timezone to ${zone}? Business hours are read in this zone.`, Boolean(options.yes));
|
|
79
|
+
await new OrgSettingsSDK(new IndemnClient()).setTimezone(zone);
|
|
80
|
+
printSuccess(`Timezone set to ${zone}.`);
|
|
81
|
+
}));
|
|
82
|
+
config.command('business-hours')
|
|
83
|
+
.requiredOption('--file <path>', 'JSON: { "businessHoursEnabled": true, "businessHours": [{ "day": 1, "slots": [...] }], "businessHoursUtterance": "..." }')
|
|
84
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
85
|
+
.description('Replace the business hours (days 0 Sunday to 6; empty slots close that day)')
|
|
86
|
+
.action((options) => run(async () => {
|
|
87
|
+
const hours = JSON.parse(readFileSync(options.file, 'utf8'));
|
|
88
|
+
await confirmOrExit('Replace the org business hours? Agents hand off only inside them.', Boolean(options.yes));
|
|
89
|
+
printJson(await new OrgSettingsSDK(new IndemnClient()).setBusinessHours(hours));
|
|
90
|
+
}));
|
|
91
|
+
const holidays = config.command('holidays').description('Days the org is closed');
|
|
92
|
+
holidays.command('add')
|
|
93
|
+
.requiredOption('--date <date>', 'YYYY-MM-DD')
|
|
94
|
+
.requiredOption('--name <name>', 'Holiday name')
|
|
95
|
+
.option('--message <text>', 'What agents say that day')
|
|
96
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
97
|
+
.action((options) => run(async () => {
|
|
98
|
+
await confirmOrExit(`Close the org on ${options.date} (${options.name})?`, Boolean(options.yes));
|
|
99
|
+
printJson(await new OrgSettingsSDK(new IndemnClient()).addHolidays([{ name: options.name, date: options.date, ...(options.message ? { holidayFallbackUtterance: options.message } : {}) }]));
|
|
100
|
+
}));
|
|
101
|
+
holidays.command('remove')
|
|
102
|
+
.argument('<dates...>', 'YYYY-MM-DD dates')
|
|
103
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
104
|
+
.action((dates: string[], options) => run(async () => {
|
|
105
|
+
await confirmOrExit(`Remove ${dates.length} holiday(s)?`, Boolean(options.yes));
|
|
106
|
+
printJson(await new OrgSettingsSDK(new IndemnClient()).removeHolidays(dates));
|
|
107
|
+
}));
|
|
108
|
+
config.command('admin')
|
|
109
|
+
.argument('<settings...>', 'key=value pairs, e.g. enableTickets=true')
|
|
110
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
111
|
+
.description('Indemn-only feature switches (the server refuses non-Indemn logins)')
|
|
112
|
+
.action((pairs: string[], options) => run(async () => {
|
|
113
|
+
const settings: Record<string, unknown> = {};
|
|
114
|
+
for (const pair of pairs) {
|
|
115
|
+
const i = pair.indexOf('=');
|
|
116
|
+
if (i <= 0) throw new CliError(`"${pair}" is not key=value.`, EXIT.NEEDS_INPUT);
|
|
117
|
+
const raw = pair.slice(i + 1);
|
|
118
|
+
settings[pair.slice(0, i)] = raw === 'true' ? true : raw === 'false' ? false : raw === 'null' ? null : Number.isFinite(Number(raw)) && raw !== '' ? Number(raw) : raw;
|
|
119
|
+
}
|
|
120
|
+
await confirmOrExit(`Change ${Object.keys(settings).join(', ')} for the org?`, Boolean(options.yes));
|
|
121
|
+
printJson(await new OrgSettingsSDK(new IndemnClient()).setAdminSettings(settings));
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
program.command('audit').description('Who changed what in the org').command('list')
|
|
125
|
+
.option('--since <date>', 'Only entries from this date')
|
|
126
|
+
.option('--action <name>', 'Only this action or event type')
|
|
127
|
+
.option('--resource <type>', 'Only this resource type')
|
|
128
|
+
.option('--user <id>', 'Only this user')
|
|
129
|
+
.option('--limit <n>', 'At most this many (newest first)', '50')
|
|
130
|
+
.option('--json', 'Output as JSON')
|
|
131
|
+
.action((options) => run(async () => {
|
|
132
|
+
const rows = await new AuditSDK(new IndemnClient()).list({ since: options.since, action: options.action, resourceType: options.resource, user: options.user, limit: Number(options.limit) });
|
|
133
|
+
if (options.json) { printJson(rows); return; }
|
|
134
|
+
console.log(formatTable(['When', 'Action', 'Resource', 'User', 'Status'], rows.map((e) => [
|
|
135
|
+
String(e.createdAt ?? ''), String(e.action ?? e.event_type ?? ''), `${e.resource_type ?? ''} ${e.resource_display ?? e.resource_id ?? ''}`.trim(), String(e.id_user ?? ''), String(e.status ?? ''),
|
|
136
|
+
])));
|
|
137
|
+
}));
|
|
138
|
+
}
|
package/src/cli/auth.ts
CHANGED
|
@@ -3,7 +3,10 @@ import chalk from 'chalk';
|
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import * as readline from 'readline';
|
|
5
5
|
import { Writable } from 'node:stream';
|
|
6
|
-
import { login, logout, whoami } from '../sdk/auth.js';
|
|
6
|
+
import { login, logout, whoami, type WhoAmI } from '../sdk/auth.js';
|
|
7
|
+
import { findLinkFile } from '../core/org-resolver.js';
|
|
8
|
+
import { getOrgFlag } from '../core/scope.js';
|
|
9
|
+
import { SessionExpiredError } from '../core/runtime.js';
|
|
7
10
|
import type { Environment } from '../sdk/types.js';
|
|
8
11
|
import { printError, printSuccess, printJson } from './utils/output.js';
|
|
9
12
|
|
|
@@ -39,6 +42,25 @@ function promptPassword(question: string): Promise<string> {
|
|
|
39
42
|
});
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
function describeCredential(info: WhoAmI): string {
|
|
46
|
+
if (info.credential === 'session') {
|
|
47
|
+
return info.source === 'env' ? 'session token from INDEMN_TOKEN (supplied by a host)' : 'login session';
|
|
48
|
+
}
|
|
49
|
+
return info.source === 'env' ? 'API key from INDEMN_API_KEY' : 'API key saved by an older login (run `indemn login` to switch to a session)';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Which org commands in this folder would use, without asking anything. */
|
|
53
|
+
function describeOrgRule(info: WhoAmI): string {
|
|
54
|
+
const flag = getOrgFlag();
|
|
55
|
+
if (flag) return `${flag} (from --org)`;
|
|
56
|
+
if (process.env.INDEMN_ORG_ID) return `${process.env.INDEMN_ORG_ID} (from INDEMN_ORG_ID)`;
|
|
57
|
+
const link = findLinkFile();
|
|
58
|
+
if (link) return `${link.org_name ?? link.org_id} (linked in ${link.path})`;
|
|
59
|
+
if (info.orgs.length === 1) return `${info.orgs[0].name} (your only org)`;
|
|
60
|
+
if (info.orgs.length > 1) return `asked for each command; you belong to ${info.orgs.length}. Use --org <id> or \`indemn link\`.`;
|
|
61
|
+
return 'looked up on first use';
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
export function registerAuthCommands(program: Command): void {
|
|
43
65
|
program
|
|
44
66
|
.command('login')
|
|
@@ -79,10 +101,19 @@ export function registerAuthCommands(program: Command): void {
|
|
|
79
101
|
return code;
|
|
80
102
|
},
|
|
81
103
|
});
|
|
82
|
-
spinner.succeed(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
spinner.succeed(`Logged in as ${result.email} on ${result.environment}`);
|
|
105
|
+
if (result.expiresAt) {
|
|
106
|
+
console.log(chalk.dim(` Session valid until ${new Date(result.expiresAt).toLocaleString()}; it renews itself.`));
|
|
107
|
+
}
|
|
108
|
+
if (result.orgs.length === 0) {
|
|
109
|
+
console.log(chalk.yellow(' You are not a member of any organization yet. Ask an admin to add you.'));
|
|
110
|
+
} else {
|
|
111
|
+
console.log(chalk.dim(` Organizations (${result.orgs.length}):`));
|
|
112
|
+
for (const o of result.orgs) console.log(chalk.dim(` ${o.id} ${o.name}`));
|
|
113
|
+
if (result.orgs.length > 1) {
|
|
114
|
+
console.log(chalk.dim(' Choose one per command with --org <id>, or pin a project folder with `indemn link --org <id>`.'));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
86
117
|
} catch (err) {
|
|
87
118
|
spinner.fail('Authentication failed');
|
|
88
119
|
if (err instanceof Error) {
|
|
@@ -100,11 +131,11 @@ export function registerAuthCommands(program: Command): void {
|
|
|
100
131
|
|
|
101
132
|
program
|
|
102
133
|
.command('logout')
|
|
103
|
-
.description('
|
|
104
|
-
.action(() => {
|
|
134
|
+
.description('Revoke the session and clear stored credentials')
|
|
135
|
+
.action(async () => {
|
|
105
136
|
try {
|
|
106
|
-
logout();
|
|
107
|
-
printSuccess('Logged out
|
|
137
|
+
const { revoked } = await logout();
|
|
138
|
+
printSuccess(revoked ? 'Logged out. The session was revoked and removed from this machine.' : 'Logged out. Credentials removed from this machine.');
|
|
108
139
|
} catch (err) {
|
|
109
140
|
printError(err);
|
|
110
141
|
process.exit(1);
|
|
@@ -119,23 +150,30 @@ export function registerAuthCommands(program: Command): void {
|
|
|
119
150
|
try {
|
|
120
151
|
const info = whoami();
|
|
121
152
|
if (!info) {
|
|
122
|
-
printError('Not
|
|
123
|
-
process.exit(
|
|
153
|
+
printError(new SessionExpiredError('Not logged in. Run `indemn login` first.'));
|
|
154
|
+
process.exit(3);
|
|
124
155
|
}
|
|
125
156
|
|
|
126
157
|
if (options.json) {
|
|
127
|
-
printJson(info);
|
|
158
|
+
printJson({ ...info, org_rule: describeOrgRule(info) });
|
|
128
159
|
return;
|
|
129
160
|
}
|
|
130
161
|
|
|
131
|
-
console.log(chalk.bold('Current
|
|
132
|
-
console.log(`
|
|
133
|
-
console.log(`
|
|
134
|
-
console.log(`
|
|
135
|
-
|
|
162
|
+
console.log(chalk.bold('Current login'));
|
|
163
|
+
console.log(` Platform: ${info.environment}`);
|
|
164
|
+
if (info.email) console.log(` User: ${info.email}`);
|
|
165
|
+
console.log(` Credential: ${describeCredential(info)}`);
|
|
166
|
+
if (info.sessionExpiresAt) {
|
|
167
|
+
console.log(` Session: valid until ${new Date(info.sessionExpiresAt).toLocaleString()} (renews itself)`);
|
|
168
|
+
}
|
|
169
|
+
console.log(` Org here: ${describeOrgRule(info)}`);
|
|
170
|
+
if (info.orgs.length) {
|
|
171
|
+
console.log(` Orgs:`);
|
|
172
|
+
for (const o of info.orgs) console.log(` ${o.id} ${o.name}`);
|
|
173
|
+
}
|
|
136
174
|
console.log(` Copilot: ${info.hosts.copilot}`);
|
|
137
175
|
console.log(` Evaluations: ${info.hosts.evaluations}`);
|
|
138
|
-
console.log(` Middleware:
|
|
176
|
+
console.log(` Middleware: ${info.hosts.middleware}`);
|
|
139
177
|
} catch (err) {
|
|
140
178
|
printError(err);
|
|
141
179
|
process.exit(1);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { IndemnClient } from '../sdk/client.js';
|
|
3
|
+
import { BillingSDK } from '../sdk/billing.js';
|
|
4
|
+
import { printError, printJson } from './utils/output.js';
|
|
5
|
+
|
|
6
|
+
function run(fn: () => Promise<void>): Promise<void> {
|
|
7
|
+
return fn().catch((err) => {
|
|
8
|
+
printError(err);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
const sdk = () => new BillingSDK(new IndemnClient());
|
|
13
|
+
|
|
14
|
+
export function registerBillingCommands(program: Command): void {
|
|
15
|
+
const billing = program.command('billing').description('Plan, usage and invoices (read-only: nothing here moves money)');
|
|
16
|
+
billing.command('status').description('Subscription status and current period').action(() => run(async () => {
|
|
17
|
+
const s = await sdk().status();
|
|
18
|
+
printJson(s ?? { subscription: null, message: 'This org has no paid subscription.' });
|
|
19
|
+
}));
|
|
20
|
+
billing.command('invoices').option('--unpaid', 'Only open invoices, with their payment links').action((options) => run(async () => {
|
|
21
|
+
printJson(options.unpaid ? await sdk().unpaid() : await sdk().invoices());
|
|
22
|
+
}));
|
|
23
|
+
billing.command('usage-forecast').description('Usage so far this period and the projected cost').action(() => run(async () => printJson(await sdk().usageForecast())));
|
|
24
|
+
billing.command('plans').description('The org\'s plans and the public plan list').action(() => run(async () => printJson(await sdk().plans())));
|
|
25
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { IndemnClient } from '../sdk/client.js';
|
|
3
|
+
import { CampaignsSDK } from '../sdk/campaigns.js';
|
|
4
|
+
import { formatTable, printError, printJson } from './utils/output.js';
|
|
5
|
+
|
|
6
|
+
function run(fn: () => Promise<void>): Promise<void> {
|
|
7
|
+
return fn().catch((err) => {
|
|
8
|
+
printError(err);
|
|
9
|
+
process.exit(1);
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
const sdk = () => new CampaignsSDK(new IndemnClient());
|
|
13
|
+
|
|
14
|
+
export function registerCampaignCommands(program: Command): void {
|
|
15
|
+
const campaigns = program.command('campaigns').description('Renewal and outreach campaigns (read-only)');
|
|
16
|
+
campaigns.command('list')
|
|
17
|
+
.option('--status <status>').option('--solution <id>').option('--limit <n>', 'At most this many', '50').option('--json', 'Output as JSON')
|
|
18
|
+
.action((options) => run(async () => {
|
|
19
|
+
const res = await sdk().list({ status: options.status, solutionId: options.solution, limit: Number(options.limit) });
|
|
20
|
+
if (options.json) { printJson(res); return; }
|
|
21
|
+
const items = (res.items ?? []) as Array<Record<string, unknown>>;
|
|
22
|
+
console.log(formatTable(['ID', 'Name', 'Status'], items.map((c) => [String(c._id ?? c.id ?? ''), String(c.name ?? ''), String(c.status ?? '')])));
|
|
23
|
+
}));
|
|
24
|
+
campaigns.command('get').argument('<campaign-id>').action((id: string) => run(async () => printJson(await sdk().get(id))));
|
|
25
|
+
for (const what of ['counts', 'forecast', 'next-touchpoints', 'versions'] as const) {
|
|
26
|
+
campaigns.command(what).argument('<campaign-id>').option('--days <n>', 'For forecast and next-touchpoints')
|
|
27
|
+
.action((id: string, options) => run(async () => printJson(await sdk().detail(id, what, options.days ? Number(options.days) : undefined))));
|
|
28
|
+
}
|
|
29
|
+
const templates = campaigns.command('templates').description('Message templates of campaigns');
|
|
30
|
+
templates.command('list').option('--channel <channel>').option('--solution <id>').action((options) => run(async () => printJson(await sdk().templates({ channel: options.channel, solutionId: options.solution }))));
|
|
31
|
+
templates.command('get').argument('<template-id>').option('--versions', 'Show versions (subject and body)').action((id: string, options) => run(async () => printJson(await sdk().template(id, Boolean(options.versions)))));
|
|
32
|
+
templates.command('skills').option('--solution <id>').action((options) => run(async () => printJson(await sdk().templateSkills(options.solution))));
|
|
33
|
+
}
|