@indemn/cli 1.6.0 → 1.7.1
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 +62 -79
- 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 +21 -4
- 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 +89 -109
- 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/sdk/auth.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { initializeApp, getApp } from 'firebase/app';
|
|
2
|
-
import { getAuth, signInWithEmailAndPassword } from 'firebase/auth';
|
|
3
|
-
import { IndemnClient } from './client.js';
|
|
4
|
-
import
|
|
1
|
+
import { initializeApp, getApp } from '@firebase/app';
|
|
2
|
+
import { getAuth, signInWithEmailAndPassword } from '@firebase/auth';
|
|
3
|
+
import { DEFAULT_HOSTS, IndemnClient } from './client.js';
|
|
4
|
+
import { exchangeIdToken, toStoredSession } from './session.js';
|
|
5
|
+
import type { Credential } from './credentials.js';
|
|
6
|
+
import type { Environment, EnvironmentHosts, OrgSummary } from './types.js';
|
|
5
7
|
|
|
6
8
|
// Firebase client configs per environment — public configuration (not secrets),
|
|
7
9
|
// bundled the same way the Copilot Dashboard bundles them via envsubst
|
|
8
|
-
const FIREBASE_CONFIGS: Record<Environment, Record<string, string>> = {
|
|
10
|
+
export const FIREBASE_CONFIGS: Record<Environment, Record<string, string>> = {
|
|
9
11
|
dev: {
|
|
10
12
|
apiKey: 'AIzaSyB8bA3w8oaMbyMxYA7lyQy_OvrPcPfW7x8',
|
|
11
13
|
authDomain: 'agent-449107.firebaseapp.com',
|
|
@@ -24,127 +26,105 @@ const FIREBASE_CONFIGS: Record<Environment, Record<string, string>> = {
|
|
|
24
26
|
},
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
export
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
orgId: string;
|
|
32
|
-
userId: string;
|
|
33
|
-
environment: Environment;
|
|
34
|
-
}> {
|
|
35
|
-
// Step 1: Firebase client SDK auth (environment-specific config, separate app per env)
|
|
36
|
-
const firebaseConfig = FIREBASE_CONFIGS[env];
|
|
29
|
+
export type FirebaseSignIn = (email: string, password: string, env: Environment) => Promise<{ idToken: string; refreshToken: string }>;
|
|
30
|
+
|
|
31
|
+
/** Email and password sign-in with the Firebase web SDK, as the dashboard does. */
|
|
32
|
+
const firebaseSignIn: FirebaseSignIn = async (email, password, env) => {
|
|
37
33
|
const appName = `indemn-cli-${env}`;
|
|
38
34
|
let app;
|
|
39
|
-
try { app = getApp(appName); } catch { app = initializeApp(
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Step 2: Exchange Firebase ID token for Copilot Server JWT
|
|
45
|
-
const client = new IndemnClient({ environment: env });
|
|
46
|
-
const signinResponse = await client.copilotPostUnauth<{
|
|
47
|
-
success: boolean;
|
|
48
|
-
requiresMfa: boolean;
|
|
49
|
-
mfaSessionId?: string;
|
|
50
|
-
mfaEmail?: string;
|
|
51
|
-
token?: string;
|
|
52
|
-
user?: { _id: string; email: string };
|
|
53
|
-
}>('/auth/firebase/signin', { idToken });
|
|
54
|
-
|
|
55
|
-
let jwt: string;
|
|
56
|
-
let userId: string;
|
|
35
|
+
try { app = getApp(appName); } catch { app = initializeApp(FIREBASE_CONFIGS[env], appName); }
|
|
36
|
+
const credential = await signInWithEmailAndPassword(getAuth(app), email, password);
|
|
37
|
+
return { idToken: await credential.user.getIdToken(), refreshToken: credential.user.refreshToken };
|
|
38
|
+
};
|
|
57
39
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
success: boolean;
|
|
66
|
-
token: string;
|
|
67
|
-
user: { _id: string; email: string };
|
|
68
|
-
}>('/auth/firebase/mfa/verify', {
|
|
69
|
-
sessionId: signinResponse.mfaSessionId,
|
|
70
|
-
code,
|
|
71
|
-
});
|
|
72
|
-
jwt = mfaResponse.token.replace(/^JWT\s+/i, '');
|
|
73
|
-
userId = mfaResponse.user._id;
|
|
74
|
-
} else {
|
|
75
|
-
// No MFA — direct JWT
|
|
76
|
-
jwt = signinResponse.token!.replace(/^JWT\s+/i, '');
|
|
77
|
-
userId = signinResponse.user!._id;
|
|
78
|
-
}
|
|
40
|
+
export interface LoginResult {
|
|
41
|
+
userId: string;
|
|
42
|
+
email: string;
|
|
43
|
+
environment: Environment;
|
|
44
|
+
orgs: OrgSummary[];
|
|
45
|
+
expiresAt?: string;
|
|
46
|
+
}
|
|
79
47
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Log in and keep the copilot session token, the same one the dashboard uses.
|
|
50
|
+
* No API key is created. The Firebase refresh token is kept so the session
|
|
51
|
+
* can renew itself.
|
|
52
|
+
*/
|
|
53
|
+
export async function login(email: string, password: string, env: Environment = 'dev', opts?: {
|
|
54
|
+
mfaCodePrompt?: () => Promise<string>;
|
|
55
|
+
firebaseSignIn?: FirebaseSignIn;
|
|
56
|
+
}): Promise<LoginResult> {
|
|
57
|
+
const { idToken, refreshToken } = await (opts?.firebaseSignIn ?? firebaseSignIn)(email, password, env);
|
|
89
58
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
59
|
+
const unauth = new IndemnClient({ environment: env });
|
|
60
|
+
const session = await exchangeIdToken(
|
|
61
|
+
(path, body, headers) => unauth.copilotPostUnauth(path, body, headers),
|
|
62
|
+
idToken,
|
|
63
|
+
opts?.mfaCodePrompt,
|
|
64
|
+
);
|
|
93
65
|
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
headers: {
|
|
99
|
-
'Authorization': `Bearer ${apiKeyResponse.key}`,
|
|
100
|
-
'Content-Type': 'application/json',
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
if (orgRes.ok) {
|
|
104
|
-
const data = await orgRes.json() as { organization?: { name?: string } };
|
|
105
|
-
orgName = data.organization?.name;
|
|
106
|
-
}
|
|
107
|
-
} catch {
|
|
108
|
-
// Non-fatal — banner falls back to org ID if name unavailable
|
|
109
|
-
}
|
|
66
|
+
// Which orgs this person belongs to, asked with the new session itself.
|
|
67
|
+
const authed = new IndemnClient({ environment: env, token: session.token });
|
|
68
|
+
const res = await authed.copilotGet<{ userOrganizations?: Array<{ _id: string; name: string }> }>('/organization');
|
|
69
|
+
const orgs = (res.userOrganizations ?? []).map((o) => ({ id: String(o._id), name: o.name }));
|
|
110
70
|
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
org_name: orgName,
|
|
115
|
-
user_id: userId,
|
|
71
|
+
const stored = toStoredSession(session.token, refreshToken);
|
|
72
|
+
IndemnClient.clearConfig();
|
|
73
|
+
IndemnClient.saveConfig({
|
|
116
74
|
environment: env,
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
75
|
+
user_id: session.userId,
|
|
76
|
+
email: session.email ?? email,
|
|
77
|
+
session: stored,
|
|
78
|
+
orgs,
|
|
79
|
+
hosts: DEFAULT_HOSTS,
|
|
80
|
+
});
|
|
121
81
|
|
|
122
|
-
return {
|
|
123
|
-
apiKey: config.api_key,
|
|
124
|
-
orgId: config.org_id,
|
|
125
|
-
userId: config.user_id,
|
|
126
|
-
environment: env,
|
|
127
|
-
};
|
|
82
|
+
return { userId: session.userId, email: session.email ?? email, environment: env, orgs, expiresAt: stored.expires_at };
|
|
128
83
|
}
|
|
129
84
|
|
|
130
|
-
|
|
85
|
+
/** Revoke the session on the server, then forget it locally. */
|
|
86
|
+
export async function logout(): Promise<{ revoked: boolean }> {
|
|
87
|
+
const config = IndemnClient.loadConfig();
|
|
88
|
+
let revoked = false;
|
|
89
|
+
if (config?.session?.token) {
|
|
90
|
+
try {
|
|
91
|
+
const client = new IndemnClient({ environment: config.environment });
|
|
92
|
+
await client.copilotPostUnauth('/auth/firebase/logout', undefined, { Authorization: `JWT ${config.session.token}` });
|
|
93
|
+
revoked = true;
|
|
94
|
+
} catch {
|
|
95
|
+
// Offline or already expired: still forget it locally.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
131
98
|
IndemnClient.clearConfig();
|
|
99
|
+
return { revoked };
|
|
132
100
|
}
|
|
133
101
|
|
|
134
|
-
export
|
|
135
|
-
apiKey: string;
|
|
136
|
-
orgId: string;
|
|
137
|
-
userId: string;
|
|
102
|
+
export interface WhoAmI {
|
|
138
103
|
environment: Environment;
|
|
104
|
+
email?: string;
|
|
105
|
+
userId?: string;
|
|
106
|
+
credential: 'session' | 'api_key';
|
|
107
|
+
source: Credential['source'];
|
|
108
|
+
sessionExpiresAt?: string;
|
|
109
|
+
orgs: OrgSummary[];
|
|
139
110
|
hosts: EnvironmentHosts;
|
|
140
|
-
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Who and where, without any secret. */
|
|
114
|
+
export function whoami(): WhoAmI | null {
|
|
115
|
+
const client = new IndemnClient();
|
|
116
|
+
const cred = client.getCredential();
|
|
117
|
+
if (!cred) return null;
|
|
141
118
|
const config = IndemnClient.loadConfig();
|
|
142
|
-
|
|
119
|
+
const fromConfig = cred.source === 'config';
|
|
143
120
|
return {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
userId: config
|
|
147
|
-
|
|
148
|
-
|
|
121
|
+
environment: client.getEnvironment(),
|
|
122
|
+
email: fromConfig ? config?.email : undefined,
|
|
123
|
+
userId: fromConfig ? config?.user_id : undefined,
|
|
124
|
+
credential: cred.kind === 'token' ? 'session' : 'api_key',
|
|
125
|
+
source: cred.source,
|
|
126
|
+
sessionExpiresAt: fromConfig && cred.kind === 'token' ? config?.session?.expires_at : undefined,
|
|
127
|
+
orgs: fromConfig ? config?.orgs ?? [] : [],
|
|
128
|
+
hosts: client.getHosts(),
|
|
149
129
|
};
|
|
150
130
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { IndemnClient } from './client.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Billing, READ-ONLY by design: every call here is a GET. The CLI never
|
|
5
|
+
* changes a plan, pays, pauses, cancels, posts meters or edits prices; those
|
|
6
|
+
* stay in the dashboard. (A test fails if this file gains a write.)
|
|
7
|
+
*/
|
|
8
|
+
export class BillingSDK {
|
|
9
|
+
constructor(private client: IndemnClient) {}
|
|
10
|
+
|
|
11
|
+
/** The org's subscription, or null when it has none (copilot answers 500 then) or never pays (infinite plan). */
|
|
12
|
+
async status(): Promise<Record<string, unknown> | null> {
|
|
13
|
+
try {
|
|
14
|
+
const res = await this.client.copilotGet<Record<string, unknown>>('/organization/:org_id/billing-subscription');
|
|
15
|
+
return res;
|
|
16
|
+
} catch (err) {
|
|
17
|
+
if (err instanceof Error && /\(500\)|infinite subscription|Subscription does not exist/i.test(err.message)) return null;
|
|
18
|
+
throw err;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private async subscriptionId(): Promise<string | null> {
|
|
23
|
+
const sub = await this.status();
|
|
24
|
+
return sub && sub._id ? String(sub._id) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Invoices, newest first (empty when there are none; copilot answers 500 then). */
|
|
28
|
+
async invoices(): Promise<unknown[]> {
|
|
29
|
+
const sid = await this.subscriptionId();
|
|
30
|
+
if (!sid) return [];
|
|
31
|
+
try {
|
|
32
|
+
const res = await this.client.copilotGet<{ invoices?: unknown[] }>(`/invoice/organization/:org_id/subscription/${sid}`);
|
|
33
|
+
return res.invoices ?? [];
|
|
34
|
+
} catch (err) {
|
|
35
|
+
if (err instanceof Error && /\(500\)|Could not fetch invoices/i.test(err.message)) return [];
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Open invoices with their payment links. */
|
|
41
|
+
async unpaid(): Promise<unknown[]> {
|
|
42
|
+
const sid = await this.subscriptionId();
|
|
43
|
+
if (!sid) return [];
|
|
44
|
+
const res = await this.client.copilotGet<{ unpaidInvoices?: unknown[] }>(`/invoice/organization/:org_id/subscription/${sid}/unpaid`);
|
|
45
|
+
return res.unpaidInvoices ?? [];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Usage so far this period and the projected cost per meter. */
|
|
49
|
+
usageForecast(): Promise<unknown> {
|
|
50
|
+
return this.client.copilotGet('/invoice/organization/:org_id/usage-forecast');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The org's own plans and the public plan list. */
|
|
54
|
+
async plans(): Promise<{ org_plans: unknown[]; public_plans: unknown[] }> {
|
|
55
|
+
const org = await this.client.copilotGet<{ plans?: unknown[] }>('/organization/:org_id/customer-plans').catch(() => ({ plans: [] }));
|
|
56
|
+
const pub = await this.client.copilotGet<unknown[]>('/subscription-plans').catch(() => []);
|
|
57
|
+
return { org_plans: org.plans ?? [], public_plans: Array.isArray(pub) ? pub : [] };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IndemnClient } from './client.js';
|
|
2
|
+
import { CliError, EXIT } from '../core/runtime.js';
|
|
3
|
+
|
|
4
|
+
/** Paths of the campaigns service the CLI may read. Everything else, and every write, is refused. */
|
|
5
|
+
const READABLE = [
|
|
6
|
+
/^\/campaigns$/,
|
|
7
|
+
/^\/campaigns\/[\w-]+$/,
|
|
8
|
+
/^\/campaigns\/[\w-]+\/(versions|counts|forecast|next-touchpoints)$/,
|
|
9
|
+
/^\/templates$/,
|
|
10
|
+
/^\/templates\/[\w-]+$/,
|
|
11
|
+
/^\/templates\/[\w-]+\/versions$/,
|
|
12
|
+
/^\/template-skills$/,
|
|
13
|
+
/^\/template-skills\/[\w-]+$/,
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Campaigns, READ-ONLY by design, through copilot's proxy to the campaigns
|
|
18
|
+
* service. The CLI never publishes a campaign, sends a communication or
|
|
19
|
+
* triggers a book sync; those act on real customers.
|
|
20
|
+
*/
|
|
21
|
+
export class CampaignsSDK {
|
|
22
|
+
constructor(private client: IndemnClient) {}
|
|
23
|
+
|
|
24
|
+
private async read<T>(path: string, query?: Record<string, string>): Promise<T> {
|
|
25
|
+
if (!READABLE.some((r) => r.test(path))) throw new CliError(`Campaigns path ${path} is not readable from the CLI.`, EXIT.NEEDS_INPUT);
|
|
26
|
+
return this.client.copilotGet<T>(`/organization/:org_id/campaigns-service${path}`, query);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
list(filter: { status?: string; solutionId?: string; limit?: number; skip?: number } = {}): Promise<{ total?: number; items?: unknown[] }> {
|
|
30
|
+
const q: Record<string, string> = { limit: String(Math.min(filter.limit ?? 50, 500)), skip: String(filter.skip ?? 0) };
|
|
31
|
+
if (filter.status) q.status = filter.status;
|
|
32
|
+
if (filter.solutionId) q.solution_id = filter.solutionId;
|
|
33
|
+
return this.read('/campaigns', q);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get(id: string): Promise<unknown> {
|
|
37
|
+
return this.read(`/campaigns/${id}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Enrollment counts, forecast of the next days, or upcoming touchpoints of a campaign. */
|
|
41
|
+
detail(id: string, what: 'counts' | 'forecast' | 'next-touchpoints' | 'versions', days?: number): Promise<unknown> {
|
|
42
|
+
return this.read(`/campaigns/${id}/${what}`, days ? { days: String(days) } : undefined);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
templates(filter: { channel?: string; solutionId?: string; limit?: number } = {}): Promise<{ total?: number; items?: unknown[] }> {
|
|
46
|
+
const q: Record<string, string> = { limit: String(Math.min(filter.limit ?? 50, 500)) };
|
|
47
|
+
if (filter.channel) q.channel = filter.channel;
|
|
48
|
+
if (filter.solutionId) q.solution_id = filter.solutionId;
|
|
49
|
+
return this.read('/templates', q);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** A template; with versions: true, its versions (where subject and body live). */
|
|
53
|
+
template(id: string, versions = false): Promise<unknown> {
|
|
54
|
+
return this.read(versions ? `/templates/${id}/versions` : `/templates/${id}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
templateSkills(solutionId?: string): Promise<unknown> {
|
|
58
|
+
return this.read('/template-skills', solutionId ? { solution_id: solutionId } : undefined);
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/sdk/chat.ts
CHANGED
|
@@ -11,19 +11,42 @@ export class ChatSession extends EventEmitter {
|
|
|
11
11
|
private connected = false;
|
|
12
12
|
private streamBuffers: Map<string, string> = new Map();
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
private customData: Record<string, unknown>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Every CLI conversation is a test conversation: `isTestMode` is always
|
|
18
|
+
* true and cannot be overridden. `tester` and `preview` let the dashboard
|
|
19
|
+
* show who tested, as it does for its own "Talk to agent" window.
|
|
20
|
+
*/
|
|
21
|
+
constructor(
|
|
22
|
+
botType: string,
|
|
23
|
+
middlewareUrl: string,
|
|
24
|
+
sessionId?: string,
|
|
25
|
+
opts: { tester?: string | null; preview?: string | null; customData?: Record<string, unknown> } = {},
|
|
26
|
+
) {
|
|
15
27
|
super();
|
|
16
28
|
this.botType = botType;
|
|
17
29
|
this.middlewareUrl = middlewareUrl;
|
|
18
30
|
this.userId = `cli-user-${Date.now()}`;
|
|
19
31
|
this.sessionId = sessionId || null;
|
|
32
|
+
this.customData = {
|
|
33
|
+
...opts.customData,
|
|
34
|
+
...(opts.tester ? { conversation_tester: opts.tester } : {}),
|
|
35
|
+
...(opts.preview ? { preview: opts.preview } : {}),
|
|
36
|
+
isTestMode: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The custom data sent with session_request (for tests and diagnostics). */
|
|
41
|
+
getCustomData(): Record<string, unknown> {
|
|
42
|
+
return { ...this.customData };
|
|
20
43
|
}
|
|
21
44
|
|
|
22
45
|
async connect(): Promise<string> {
|
|
23
46
|
return new Promise<string>((resolve, reject) => {
|
|
24
47
|
const timeout = setTimeout(() => {
|
|
25
48
|
reject(new Error('Connection timed out after 10s'));
|
|
26
|
-
this.disconnect();
|
|
49
|
+
void this.disconnect({ end: false });
|
|
27
50
|
}, 10000);
|
|
28
51
|
|
|
29
52
|
this.socket = io(this.middlewareUrl, {
|
|
@@ -42,7 +65,7 @@ export class ChatSession extends EventEmitter {
|
|
|
42
65
|
session_id: this.sessionId,
|
|
43
66
|
bot_type: this.botType,
|
|
44
67
|
user_id: this.userId,
|
|
45
|
-
customData:
|
|
68
|
+
customData: this.customData,
|
|
46
69
|
};
|
|
47
70
|
this.socket!.emit('session_request', request);
|
|
48
71
|
});
|
|
@@ -115,15 +138,26 @@ export class ChatSession extends EventEmitter {
|
|
|
115
138
|
this.socket.emit('user_uttered', payload);
|
|
116
139
|
}
|
|
117
140
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
141
|
+
/**
|
|
142
|
+
* Leave the conversation. By default it is also ended on the platform
|
|
143
|
+
* (session_closed). `{ end: false }` only drops the socket, so the same
|
|
144
|
+
* session id can continue the conversation later.
|
|
145
|
+
*
|
|
146
|
+
* The close message is given time to arrive before the socket drops: an
|
|
147
|
+
* event that reaches Socket.IO right before the disconnect is discarded.
|
|
148
|
+
*/
|
|
149
|
+
async disconnect(opts: { end?: boolean; flushMs?: number } = {}): Promise<void> {
|
|
150
|
+
const socket = this.socket;
|
|
151
|
+
this.socket = null;
|
|
152
|
+
this.connected = false;
|
|
153
|
+
if (socket) {
|
|
154
|
+
if (this.sessionId && opts.end !== false && socket.connected) {
|
|
155
|
+
// The middleware accepts only allow-listed reasons; this one records a deliberate close.
|
|
156
|
+
socket.emit('session_closed', { session_id: this.sessionId, reason: 'client_close_conversation' });
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, opts.flushMs ?? 300));
|
|
122
158
|
}
|
|
123
|
-
|
|
124
|
-
this.socket = null;
|
|
159
|
+
socket.disconnect();
|
|
125
160
|
}
|
|
126
|
-
this.connected = false;
|
|
127
161
|
this.sessionId = null;
|
|
128
162
|
}
|
|
129
163
|
|