@pentatonic-ai/ai-agent-sdk 0.3.0-beta.3

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/bin/cli.js ADDED
@@ -0,0 +1,371 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createInterface } from "readline";
4
+ import { execFileSync } from "child_process";
5
+
6
+ const DEFAULT_ENDPOINT = "https://api.pentatonic.com";
7
+
8
+ function parseArgs() {
9
+ const args = process.argv.slice(2);
10
+ const flags = {};
11
+ for (let i = 0; i < args.length; i++) {
12
+ if (args[i] === "--endpoint" && args[i + 1]) {
13
+ flags.endpoint = args[i + 1];
14
+ i++;
15
+ } else if (args[i].startsWith("--endpoint=")) {
16
+ flags.endpoint = args[i].split("=")[1];
17
+ } else if (!args[i].startsWith("--")) {
18
+ flags.command = args[i];
19
+ }
20
+ }
21
+ return flags;
22
+ }
23
+ const POLL_INTERVAL_MS = 3000;
24
+ const POLL_TIMEOUT_MS = 300000; // 5 minutes
25
+
26
+ let rl = createInterface({ input: process.stdin, output: process.stdout });
27
+
28
+ function ask(question) {
29
+ return new Promise((resolve) => rl.question(question, resolve));
30
+ }
31
+
32
+ function askSecret(question) {
33
+ return new Promise((resolve) => {
34
+ // Close readline so it stops echoing input
35
+ rl.close();
36
+
37
+ process.stdout.write(question);
38
+ const stdin = process.stdin;
39
+ if (stdin.isTTY) stdin.setRawMode(true);
40
+ stdin.resume();
41
+
42
+ let input = "";
43
+ const onData = (ch) => {
44
+ const c = ch.toString();
45
+ if (c === "\n" || c === "\r") {
46
+ stdin.removeListener("data", onData);
47
+ if (stdin.isTTY) stdin.setRawMode(false);
48
+ stdin.pause();
49
+ process.stdout.write("\n");
50
+ // Recreate readline for subsequent prompts
51
+ rl = createInterface({ input: process.stdin, output: process.stdout });
52
+ resolve(input);
53
+ } else if (c === "\u007f" || c === "\b") {
54
+ if (input.length > 0) {
55
+ input = input.slice(0, -1);
56
+ process.stdout.write("\b \b");
57
+ }
58
+ } else if (c === "\u0003") {
59
+ process.exit(1);
60
+ } else {
61
+ input += c;
62
+ process.stdout.write("*");
63
+ }
64
+ };
65
+ stdin.on("data", onData);
66
+ });
67
+ }
68
+
69
+ function askChoice(question, choices) {
70
+ return new Promise((resolve) => {
71
+ const choiceStr = choices.map((c, i) => ` ${i + 1}) ${c}`).join("\n");
72
+ process.stdout.write(`${question}\n${choiceStr}\n`);
73
+ rl.question("? Choice: ", (answer) => {
74
+ const idx = parseInt(answer, 10) - 1;
75
+ if (idx >= 0 && idx < choices.length) {
76
+ resolve(choices[idx]);
77
+ } else {
78
+ const match = choices.find(
79
+ (c) => c.toLowerCase() === answer.trim().toLowerCase()
80
+ );
81
+ resolve(match || choices[0]);
82
+ }
83
+ });
84
+ });
85
+ }
86
+
87
+ function spinner(text) {
88
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
89
+ let i = 0;
90
+ const id = setInterval(() => {
91
+ process.stdout.write(`\r${frames[i++ % frames.length]} ${text}`);
92
+ }, 80);
93
+ return {
94
+ stop(result) {
95
+ clearInterval(id);
96
+ process.stdout.write(`\r✓ ${result}\n`);
97
+ },
98
+ fail(msg) {
99
+ clearInterval(id);
100
+ process.stdout.write(`\r✗ ${msg}\n`);
101
+ },
102
+ };
103
+ }
104
+
105
+ async function httpPost(url, body) {
106
+ const response = await fetch(url, {
107
+ method: "POST",
108
+ headers: { "Content-Type": "application/json" },
109
+ body: JSON.stringify(body),
110
+ });
111
+ const data = await response.json();
112
+ return { status: response.status, ok: response.ok, data };
113
+ }
114
+
115
+ async function graphql(endpoint, token, query, variables) {
116
+ const response = await fetch(`${endpoint}/api/graphql`, {
117
+ method: "POST",
118
+ headers: {
119
+ "Content-Type": "application/json",
120
+ Authorization: `Bearer ${token}`,
121
+ },
122
+ body: JSON.stringify({ query, variables }),
123
+ });
124
+ const data = await response.json();
125
+ if (data.errors?.length) {
126
+ throw new Error(data.errors[0].message);
127
+ }
128
+ return data.data;
129
+ }
130
+
131
+ function toClientId(companyName) {
132
+ return companyName
133
+ .toLowerCase()
134
+ .replace(/[^a-z0-9]+/g, "-")
135
+ .replace(/^-|-$/g, "");
136
+ }
137
+
138
+ async function main() {
139
+ const flags = parseArgs();
140
+ const TES_ENDPOINT = flags.endpoint || DEFAULT_ENDPOINT;
141
+
142
+ if (flags.command !== "init") {
143
+ console.log(`
144
+ @pentatonic-ai/ai-agent-sdk
145
+
146
+ Usage:
147
+ npx @pentatonic-ai/ai-agent-sdk init Set up account and install SDK
148
+ npx @pentatonic-ai/ai-agent-sdk init --endpoint URL Use a custom TES endpoint
149
+
150
+ For docs, see https://api.pentatonic.com
151
+ `);
152
+ process.exit(0);
153
+ }
154
+
155
+ const isLocal = /^http:\/\/(localhost|127\.0\.0\.1)(:\d+)?(\/|$)/.test(TES_ENDPOINT);
156
+ if (!TES_ENDPOINT.startsWith("https://") && !isLocal) {
157
+ console.error(`\n Error: endpoint must use https:// (http:// is only allowed for localhost)\n`);
158
+ process.exit(1);
159
+ }
160
+
161
+ console.log(`\n Welcome to Pentatonic AI Events SDK`);
162
+ if (TES_ENDPOINT !== DEFAULT_ENDPOINT) {
163
+ console.log(` Using endpoint: ${TES_ENDPOINT}`);
164
+ }
165
+ console.log("");
166
+
167
+ // Collect info
168
+ const email = await ask("? Email: ");
169
+ const clientId = toClientId(await ask("? Client ID: "));
170
+ const password = await askSecret("? Password: ");
171
+ const region = await askChoice("? Region:", ["EU", "US"]);
172
+
173
+ // Try login first — account may already be verified from a previous run
174
+ let accessToken = null;
175
+ const loginSpinner = spinner("Checking for existing account...");
176
+ try {
177
+ const { ok, data } = await httpPost(
178
+ `${TES_ENDPOINT}/api/enrollment/login`,
179
+ { email, password, clientId }
180
+ );
181
+ if (ok && data.tokens?.accessToken) {
182
+ accessToken = data.tokens.accessToken;
183
+ loginSpinner.stop("Account already verified!");
184
+ } else {
185
+ loginSpinner.stop("No existing account found.");
186
+ }
187
+ } catch {
188
+ loginSpinner.stop("No existing account found.");
189
+ }
190
+
191
+ // If not already verified, submit enrollment
192
+ if (!accessToken) {
193
+ const enrollSpinner = spinner("Creating account...");
194
+ try {
195
+ const { ok, data } = await httpPost(
196
+ `${TES_ENDPOINT}/api/enrollment/submit`,
197
+ {
198
+ clientId,
199
+ companyName: clientId,
200
+ industryType: "technology",
201
+ authProvider: "native",
202
+ adminEmail: email,
203
+ adminPassword: password,
204
+ region: region.toLowerCase(),
205
+ }
206
+ );
207
+
208
+ if (!ok) {
209
+ const errors = data.errors || {};
210
+ const isPending =
211
+ errors.clientId?.includes("already pending") ||
212
+ errors.adminEmail?.includes("already has a pending");
213
+ const isAlreadyRegistered =
214
+ errors.clientId?.includes("already registered");
215
+
216
+ if (isPending) {
217
+ enrollSpinner.stop("Enrollment already pending — waiting for verification.");
218
+ } else if (isAlreadyRegistered) {
219
+ enrollSpinner.fail(
220
+ "This client ID is already registered.\n" +
221
+ " If you belong to this organization, ask your admin to invite you.\n" +
222
+ " Then run this command again — it will log you in automatically."
223
+ );
224
+ process.exit(1);
225
+ } else {
226
+ enrollSpinner.fail(
227
+ data.message || Object.values(errors).join(", ") || "Enrollment failed"
228
+ );
229
+ process.exit(1);
230
+ }
231
+ } else {
232
+ enrollSpinner.stop("Account created! Check your email to verify.");
233
+ }
234
+ } catch (err) {
235
+ enrollSpinner.fail(`Failed to connect: ${err.message}`);
236
+ process.exit(1);
237
+ }
238
+
239
+ // Poll for verification
240
+ console.log("\n Waiting for email verification...");
241
+ console.log(" (Check your inbox and click the verification link)\n");
242
+
243
+ const pollSpinner = spinner("Waiting for verification...");
244
+ const startTime = Date.now();
245
+
246
+ while (Date.now() - startTime < POLL_TIMEOUT_MS) {
247
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
248
+
249
+ try {
250
+ const { ok, data } = await httpPost(
251
+ `${TES_ENDPOINT}/api/enrollment/login`,
252
+ { email, password, clientId }
253
+ );
254
+
255
+ if (ok && data.tokens?.accessToken) {
256
+ accessToken = data.tokens.accessToken;
257
+ pollSpinner.stop("Email verified!");
258
+ break;
259
+ }
260
+ } catch {
261
+ // Not verified yet, keep polling
262
+ }
263
+ }
264
+
265
+ if (!accessToken) {
266
+ pollSpinner.fail(
267
+ "Verification timed out. Run `npx @pentatonic-ai/ai-agent-sdk init` again — it will resume where you left off."
268
+ );
269
+ process.exit(1);
270
+ }
271
+ }
272
+
273
+ // Get API key — use the service token created during enrollment,
274
+ // or create a new one if not available (e.g., existing account login)
275
+ const keySpinner = spinner("Getting API key...");
276
+ try {
277
+ let apiKey;
278
+
279
+ // Try to retrieve the enrollment service token first (created during verification)
280
+ try {
281
+ const tokenRes = await fetch(
282
+ `${TES_ENDPOINT}/api/enrollment/service-token?client_id=${clientId}`
283
+ );
284
+ if (tokenRes.ok) {
285
+ const tokenData = await tokenRes.json();
286
+ if (tokenData.token) {
287
+ apiKey = tokenData.token;
288
+ }
289
+ }
290
+ } catch {
291
+ // Service token not available, will create one
292
+ }
293
+
294
+ // Fallback: create a new token via GraphQL
295
+ if (!apiKey) {
296
+ const result = await graphql(
297
+ TES_ENDPOINT,
298
+ accessToken,
299
+ `mutation CreateApiToken($clientId: String!, $input: CreateApiTokenInput!) {
300
+ createClientApiToken(clientId: $clientId, input: $input) {
301
+ success
302
+ plainTextToken
303
+ }
304
+ }`,
305
+ {
306
+ clientId,
307
+ input: {
308
+ name: "ai-events-sdk",
309
+ role: "agent-events",
310
+ },
311
+ }
312
+ );
313
+ apiKey = result.createClientApiToken.plainTextToken;
314
+ }
315
+
316
+ keySpinner.stop("API key ready!");
317
+
318
+ // Print credentials
319
+ const clientEndpoint =
320
+ TES_ENDPOINT === DEFAULT_ENDPOINT
321
+ ? `https://${clientId}.api.pentatonic.com`
322
+ : TES_ENDPOINT;
323
+
324
+ console.log("\n Add these to your environment:\n");
325
+ console.log(` TES_ENDPOINT=${clientEndpoint}`);
326
+ console.log(` TES_CLIENT_ID=${clientId}`);
327
+ console.log(` TES_API_KEY=${apiKey}`);
328
+ console.log("");
329
+
330
+ // Install SDK
331
+ const installChoice = await askChoice("Install SDK:", [
332
+ "npm install @pentatonic-ai/ai-agent-sdk",
333
+ "pip install pentatonic-ai-agent-sdk",
334
+ "Skip — I'll install manually",
335
+ ]);
336
+
337
+ if (installChoice.startsWith("npm")) {
338
+ const installSpinner = spinner("Installing @pentatonic-ai/ai-agent-sdk...");
339
+ try {
340
+ execFileSync("npm", ["install", "@pentatonic-ai/ai-agent-sdk"], { stdio: "pipe" });
341
+ installSpinner.stop("@pentatonic-ai/ai-agent-sdk installed!");
342
+ } catch {
343
+ installSpinner.fail("Install failed. Run manually: npm install @pentatonic-ai/ai-agent-sdk");
344
+ }
345
+ } else if (installChoice.startsWith("pip")) {
346
+ const installSpinner = spinner("Installing pentatonic-ai-agent-sdk...");
347
+ try {
348
+ execFileSync("pip", ["install", "pentatonic-ai-agent-sdk"], { stdio: "pipe" });
349
+ installSpinner.stop("pentatonic-ai-agent-sdk installed!");
350
+ } catch {
351
+ installSpinner.fail("Install failed. Run manually: pip install pentatonic-ai-agent-sdk");
352
+ }
353
+ } else {
354
+ console.log("\n Install later with:");
355
+ console.log(" npm install @pentatonic-ai/ai-agent-sdk");
356
+ console.log(" pip install pentatonic-ai-agent-sdk");
357
+ }
358
+
359
+ console.log(" You're ready! See docs at https://api.pentatonic.com\n");
360
+ } catch (err) {
361
+ keySpinner.fail(`Failed to generate key: ${err.message}`);
362
+ process.exit(1);
363
+ }
364
+
365
+ rl.close();
366
+ }
367
+
368
+ main().catch((err) => {
369
+ console.error(`\n Error: ${err.message}\n`);
370
+ process.exit(1);
371
+ });
package/build.js ADDED
@@ -0,0 +1,23 @@
1
+ import { build } from "esbuild";
2
+
3
+ // ESM
4
+ await build({
5
+ entryPoints: ["src/index.js"],
6
+ outfile: "dist/index.js",
7
+ format: "esm",
8
+ bundle: true,
9
+ platform: "neutral",
10
+ target: "es2020",
11
+ });
12
+
13
+ // CJS
14
+ await build({
15
+ entryPoints: ["src/index.js"],
16
+ outfile: "dist/index.cjs",
17
+ format: "cjs",
18
+ bundle: true,
19
+ platform: "neutral",
20
+ target: "es2020",
21
+ });
22
+
23
+ console.log("Built dist/index.js (ESM) and dist/index.cjs (CJS)");