twelveai 0.3.0 → 0.4.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/dist/cli.cjs ADDED
@@ -0,0 +1,471 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/cli.ts
22
+ var cli_exports = {};
23
+ __export(cli_exports, {
24
+ runCli: () => runCli
25
+ });
26
+ module.exports = __toCommonJS(cli_exports);
27
+ var import_node_fs = require("fs");
28
+
29
+ // src/manage.ts
30
+ function buildManage(request) {
31
+ return {
32
+ agents: {
33
+ list: () => request("GET", "/v1/agents"),
34
+ create: (input) => request("POST", "/v1/agents", { ...input }),
35
+ update: (intent, patch) => request("PUT", "/v1/agents", { intent, ...patch }),
36
+ enable: (intent) => request("PUT", "/v1/agents", { intent, enabled: true }),
37
+ disable: (intent) => request("PUT", "/v1/agents", { intent, enabled: false }),
38
+ delete: (intent) => request("DELETE", `/v1/agents/${encodeURIComponent(intent)}`)
39
+ },
40
+ tools: {
41
+ list: () => request("GET", "/v1/tools"),
42
+ create: (input) => request("POST", "/v1/tools", { ...input }),
43
+ bind: (name, binding) => request("PUT", `/v1/tools/${encodeURIComponent(name)}`, { ...binding }),
44
+ test: (name) => request("POST", `/v1/tools/${encodeURIComponent(name)}/test`),
45
+ importOpenApi: (input) => request("POST", "/v1/tools/import", { ...input })
46
+ },
47
+ plugins: {
48
+ list: () => request("GET", "/v1/plugins"),
49
+ install: (id) => request("POST", `/v1/plugins/${encodeURIComponent(id)}/install`),
50
+ uninstall: (id) => request("POST", `/v1/plugins/${encodeURIComponent(id)}/uninstall`)
51
+ },
52
+ customers: {
53
+ list: (opts = {}) => request("GET", `/v1/customers?limit=${opts.limit ?? 100}${opts.range ? `&range=${opts.range}` : ""}`),
54
+ upsert: (input) => request("POST", "/v1/customers", { ...input }),
55
+ get: (externalId) => request("GET", `/v1/customers/${encodeURIComponent(externalId)}`)
56
+ },
57
+ settings: {
58
+ get: () => request("GET", "/v1/settings"),
59
+ update: (patch) => request("PUT", "/v1/settings", patch)
60
+ },
61
+ billing: {
62
+ get: () => request("GET", "/v1/billing"),
63
+ topup: (amountNgn, email) => request("POST", "/v1/billing/topup", { amount: amountNgn, ...email ? { email } : {} })
64
+ },
65
+ usage: {
66
+ summary: () => request("GET", "/v1/usage"),
67
+ events: (limit = 20) => request("GET", `/v1/usage/events?limit=${limit}`),
68
+ event: (id) => request("GET", `/v1/usage/events/${id}`),
69
+ analytics: (days = 30) => request("GET", `/v1/usage/analytics?days=${days}`)
70
+ },
71
+ manifest: () => request("GET", "/v1/manifest")
72
+ };
73
+ }
74
+
75
+ // src/client.ts
76
+ var DEFAULT_BASE_URL = "https://ai.twelveai.app";
77
+ var TwelveAI = class {
78
+ apiKey;
79
+ baseUrl;
80
+ fetchImpl;
81
+ clientAuth;
82
+ maxHandoffRounds;
83
+ /**
84
+ * Everything the console can do, from code: agents, tools, plugins,
85
+ * customers, settings, billing, usage. `twelve.manage.plugins.install('bills')`.
86
+ */
87
+ manage;
88
+ constructor(options) {
89
+ if (!options?.apiKey) throw new Error("TwelveAI: apiKey is required.");
90
+ this.apiKey = options.apiKey;
91
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
92
+ this.fetchImpl = options.fetch ?? globalThis.fetch;
93
+ this.clientAuth = options.clientAuth;
94
+ this.maxHandoffRounds = options.maxHandoffRounds ?? 3;
95
+ if (typeof this.fetchImpl !== "function") {
96
+ throw new Error("TwelveAI: global fetch is unavailable - pass options.fetch (Node 18+ has fetch built in).");
97
+ }
98
+ this.manage = buildManage((method, path, body) => this.api(method, path, body));
99
+ }
100
+ /**
101
+ * Low-level authenticated call to any engine endpoint. Returns
102
+ * `{ ok, status, ...payload }`; never throws on HTTP errors.
103
+ */
104
+ async api(method, path, body) {
105
+ try {
106
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
107
+ method,
108
+ headers: { "content-type": "application/json", "x-api-key": this.apiKey },
109
+ ...body !== void 0 ? { body: JSON.stringify(body) } : {}
110
+ });
111
+ const data = await res.json().catch(() => ({}));
112
+ return {
113
+ ...data,
114
+ ok: res.ok && data.ok !== false,
115
+ status: res.status,
116
+ ...res.ok ? {} : { error: data.error ?? `Engine returned ${res.status}` }
117
+ };
118
+ } catch (error) {
119
+ return { ok: false, status: 0, error: error?.message || "Could not reach the engine." };
120
+ }
121
+ }
122
+ /**
123
+ * Send one chat turn and return the completed result. Read hand-offs are
124
+ * auto-executed (see class docs); pass `autoExecute: false` to get the raw
125
+ * paused response instead, or `onHandoff` to execute them yourself.
126
+ */
127
+ async chat(input, opts = {}) {
128
+ let res = await this.post("/v1/chat", this.chatBody(input));
129
+ if (opts.autoExecute === false) return res;
130
+ return this.completeHandoffs(res, input, opts.onHandoff);
131
+ }
132
+ /** Resume a paused turn with tool results your system produced. */
133
+ async resume(continuation, toolResults, input = {}) {
134
+ return this.post("/v1/chat", { ...this.chatBody(input), continuation, toolResults });
135
+ }
136
+ /**
137
+ * Approve a pending action (after your PIN/OTP step) - resends the turn with
138
+ * `confirmed: true` so the engine proceeds.
139
+ */
140
+ async confirm(continuation, input = {}) {
141
+ return this.chat({ message: "yes", ...input, continuation, confirmed: true });
142
+ }
143
+ /**
144
+ * Level-0 integration: classify a message WITHOUT running the conversation.
145
+ * Returns the intent, a deterministic confidence score, and cheap extracted
146
+ * entities (amount / account number / phone) - you keep your existing flows
147
+ * and make the call yourself. Free: no tools run, nothing is stored.
148
+ */
149
+ async classify(input) {
150
+ try {
151
+ const res = await this.fetchImpl(`${this.baseUrl}/v1/classify`, {
152
+ method: "POST",
153
+ headers: { "content-type": "application/json", "x-api-key": this.apiKey },
154
+ body: JSON.stringify({ message: input.message, ...input.reasoning ? { reasoning: input.reasoning } : {} })
155
+ });
156
+ const data = await res.json().catch(() => ({}));
157
+ return {
158
+ intent: null,
159
+ label: null,
160
+ confidence: 0,
161
+ alternatives: [],
162
+ entities: {},
163
+ ...data,
164
+ // pass every field through (matchedKeywords, score, margin, classifyOnly, ...)
165
+ ok: res.ok && data.ok !== false,
166
+ status: res.status,
167
+ ...res.ok ? {} : { error: data.error ?? `Engine returned ${res.status}` }
168
+ };
169
+ } catch (error) {
170
+ return { ok: false, intent: null, label: null, confidence: 0, alternatives: [], entities: {}, status: 0, error: error?.message || "Could not reach the engine." };
171
+ }
172
+ }
173
+ /* ------------------------------ internals ------------------------------ */
174
+ chatBody(input) {
175
+ const body = {};
176
+ if (input.message !== void 0) body.message = input.message;
177
+ if (input.customerId !== void 0) body.customerId = input.customerId;
178
+ if (input.continuation) body.continuation = input.continuation;
179
+ if (input.confirmed !== void 0) body.confirmed = input.confirmed;
180
+ if (input.sandbox !== void 0) body.sandbox = input.sandbox;
181
+ if (input.channel) body.channel = input.channel;
182
+ if (input.intent) body.intent = input.intent;
183
+ if (input.tier) body.tier = input.tier;
184
+ if (input.reasoning) body.reasoning = input.reasoning;
185
+ if (input.customerToken) body.customerToken = input.customerToken;
186
+ if (input.attachments?.length) body.attachments = input.attachments;
187
+ if (input.metadata) body.metadata = input.metadata;
188
+ return body;
189
+ }
190
+ async post(path, body) {
191
+ try {
192
+ const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
193
+ method: "POST",
194
+ headers: { "content-type": "application/json", "x-api-key": this.apiKey },
195
+ body: JSON.stringify(body)
196
+ });
197
+ const data = await res.json().catch(() => ({}));
198
+ return {
199
+ ok: res.ok && data.ok !== false,
200
+ message: data.message ?? null,
201
+ intent: data.intent ?? null,
202
+ toolCalls: data.toolCalls ?? [],
203
+ pendingConfirmation: data.pendingConfirmation ?? null,
204
+ pendingToolCalls: data.pendingToolCalls ?? null,
205
+ continuation: data.continuation ?? null,
206
+ ...data,
207
+ status: res.status,
208
+ ...res.ok ? {} : { error: data.error ?? `Engine returned ${res.status}` }
209
+ };
210
+ } catch (error) {
211
+ return {
212
+ ok: false,
213
+ status: 0,
214
+ message: null,
215
+ intent: null,
216
+ toolCalls: [],
217
+ pendingConfirmation: null,
218
+ pendingToolCalls: null,
219
+ continuation: null,
220
+ error: error?.message || "Could not reach the engine."
221
+ };
222
+ }
223
+ }
224
+ /**
225
+ * Execute client-fetch hand-offs and resume until the turn completes. Only
226
+ * calls that carry a resolved `request` are auto-executed - a hand-off
227
+ * without one (e.g. a money move awaiting your PIN flow) stops the loop and
228
+ * is returned to you untouched.
229
+ */
230
+ async completeHandoffs(res, input, onHandoff) {
231
+ const executed = [];
232
+ for (let round = 0; round < this.maxHandoffRounds; round++) {
233
+ const calls = res.pendingToolCalls ?? [];
234
+ if (!calls.length || !res.continuation) break;
235
+ if (!calls.every((c) => c.request?.url)) break;
236
+ const results = [];
237
+ for (const call of calls) {
238
+ const result = onHandoff ? await onHandoff(call) : await this.performHandoff(call);
239
+ executed.push({
240
+ name: call.name,
241
+ method: call.request.method,
242
+ url: call.request.url,
243
+ ok: result?.ok !== false
244
+ });
245
+ results.push({ id: call.id, result });
246
+ }
247
+ const next = await this.resume(res.continuation, results, input);
248
+ if (next.error) break;
249
+ res = next;
250
+ }
251
+ if (executed.length) res.executedHandoffs = executed;
252
+ return res;
253
+ }
254
+ /** Default hand-off executor: perform the request with your clientAuth headers. */
255
+ async performHandoff(call) {
256
+ const req = call.request;
257
+ try {
258
+ const extra = typeof this.clientAuth === "function" ? await this.clientAuth() : this.clientAuth ?? {};
259
+ const res = await this.fetchImpl(req.url, {
260
+ method: req.method,
261
+ headers: { "content-type": "application/json", ...req.headers ?? {}, ...extra },
262
+ ...req.body !== void 0 ? { body: JSON.stringify(req.body) } : {}
263
+ });
264
+ const body = await res.json().catch(() => ({}));
265
+ if (!res.ok) return { ok: false, error: `Endpoint returned ${res.status}` };
266
+ return { ok: true, data: body.data ?? body };
267
+ } catch (error) {
268
+ return { ok: false, error: error?.message || "Request failed." };
269
+ }
270
+ }
271
+ };
272
+
273
+ // src/cli.ts
274
+ var HELP = `twelveai <command> [args]
275
+
276
+ Conversation
277
+ chat "<message>" [--customer <id>] [--sandbox] [--reasoning off|auto|always]
278
+ classify "<message>" [--reasoning off|auto|always]
279
+
280
+ Manage (everything the console does)
281
+ agents list | create --file <agent.json> | enable <intent> | disable <intent> | delete <intent>
282
+ tools list | create --file <tool.json> | bind <name> --file <binding.json> | test <name>
283
+ plugins list | install <id> | uninstall <id>
284
+ customers list | set <externalId> [--tier <tier>] [--name <name>] [--email <email>]
285
+ settings get | set --file <settings.json>
286
+ billing (show balance) | topup <amountNgn>
287
+ usage (summary) | requests [--limit <n>] | request <id>
288
+ manifest (the per-agent tool-call contract)
289
+
290
+ Auth: set TWELVE_API_KEY (and optionally TWELVE_BASE_URL).`;
291
+ function flag(args, name) {
292
+ const i = args.indexOf(`--${name}`);
293
+ return i >= 0 ? args[i + 1] : void 0;
294
+ }
295
+ var has = (args, name) => args.includes(`--${name}`);
296
+ var positional = (args) => args.filter((a, i) => !a.startsWith("--") && !(i > 0 && args[i - 1].startsWith("--")));
297
+ async function runCli(argv, deps) {
298
+ const [cmd, ...rest] = argv;
299
+ if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
300
+ deps.log(HELP);
301
+ return 0;
302
+ }
303
+ const apiKey = deps.env.TWELVE_API_KEY;
304
+ if (!apiKey) {
305
+ deps.error("Set TWELVE_API_KEY to use the CLI (find yours in the console under Settings).");
306
+ return 1;
307
+ }
308
+ const twelve = deps.client ?? new TwelveAI({ apiKey, baseUrl: deps.env.TWELVE_BASE_URL });
309
+ const m = twelve.manage;
310
+ const json = (v) => deps.log(JSON.stringify(v, null, 2));
311
+ const loadJson = (label) => {
312
+ const file = flag(rest, "file");
313
+ if (!file) {
314
+ deps.error(`--file <${label}.json> is required.`);
315
+ return null;
316
+ }
317
+ try {
318
+ return JSON.parse(deps.readFile(file));
319
+ } catch (e) {
320
+ deps.error(`Could not read ${file}: ${e.message}`);
321
+ return null;
322
+ }
323
+ };
324
+ const done = (r) => r.ok ? 0 : 1;
325
+ const pos = positional(rest);
326
+ const sub = pos[0];
327
+ switch (cmd) {
328
+ case "chat": {
329
+ if (!sub) return deps.error('Usage: twelveai chat "<message>"'), 1;
330
+ const r = await twelve.chat({
331
+ message: sub,
332
+ customerId: flag(rest, "customer"),
333
+ sandbox: has(rest, "sandbox") || void 0,
334
+ reasoning: flag(rest, "reasoning")
335
+ });
336
+ json(r);
337
+ return done(r);
338
+ }
339
+ case "classify": {
340
+ if (!sub) return deps.error('Usage: twelveai classify "<message>"'), 1;
341
+ const r = await twelve.classify({ message: sub, reasoning: flag(rest, "reasoning") });
342
+ json(r);
343
+ return done(r);
344
+ }
345
+ case "agents": {
346
+ if (sub === "list" || !sub) return json(await m.agents.list()), 0;
347
+ if (sub === "create") {
348
+ const b = loadJson("agent");
349
+ if (!b) return 1;
350
+ const r = await m.agents.create(b);
351
+ json(r);
352
+ return done(r);
353
+ }
354
+ if (sub === "enable" && pos[1]) {
355
+ const r = await m.agents.enable(pos[1]);
356
+ json(r);
357
+ return done(r);
358
+ }
359
+ if (sub === "disable" && pos[1]) {
360
+ const r = await m.agents.disable(pos[1]);
361
+ json(r);
362
+ return done(r);
363
+ }
364
+ if (sub === "delete" && pos[1]) {
365
+ const r = await m.agents.delete(pos[1]);
366
+ json(r);
367
+ return done(r);
368
+ }
369
+ deps.error("Usage: twelveai agents list | create --file a.json | enable <intent> | disable <intent> | delete <intent>");
370
+ return 1;
371
+ }
372
+ case "tools": {
373
+ if (sub === "list" || !sub) return json(await m.tools.list()), 0;
374
+ if (sub === "create") {
375
+ const b = loadJson("tool");
376
+ if (!b) return 1;
377
+ const r = await m.tools.create(b);
378
+ json(r);
379
+ return done(r);
380
+ }
381
+ if (sub === "bind" && pos[1]) {
382
+ const b = loadJson("binding");
383
+ if (!b) return 1;
384
+ const r = await m.tools.bind(pos[1], b);
385
+ json(r);
386
+ return done(r);
387
+ }
388
+ if (sub === "test" && pos[1]) {
389
+ const r = await m.tools.test(pos[1]);
390
+ json(r);
391
+ return done(r);
392
+ }
393
+ deps.error("Usage: twelveai tools list | create --file t.json | bind <name> --file b.json | test <name>");
394
+ return 1;
395
+ }
396
+ case "plugins": {
397
+ if (sub === "list" || !sub) return json(await m.plugins.list()), 0;
398
+ if (sub === "install" && pos[1]) {
399
+ const r = await m.plugins.install(pos[1]);
400
+ json(r);
401
+ return done(r);
402
+ }
403
+ if (sub === "uninstall" && pos[1]) {
404
+ const r = await m.plugins.uninstall(pos[1]);
405
+ json(r);
406
+ return done(r);
407
+ }
408
+ deps.error("Usage: twelveai plugins list | install <id> | uninstall <id>");
409
+ return 1;
410
+ }
411
+ case "customers": {
412
+ if (sub === "list" || !sub) return json(await m.customers.list({ limit: Number(flag(rest, "limit")) || 100 })), 0;
413
+ if (sub === "set" && pos[1]) {
414
+ const r = await m.customers.upsert({ externalId: pos[1], tier: flag(rest, "tier"), name: flag(rest, "name"), email: flag(rest, "email") });
415
+ json(r);
416
+ return done(r);
417
+ }
418
+ deps.error("Usage: twelveai customers list | set <externalId> --tier <tier>");
419
+ return 1;
420
+ }
421
+ case "settings": {
422
+ if (sub === "get" || !sub) return json(await m.settings.get()), 0;
423
+ if (sub === "set") {
424
+ const b = loadJson("settings");
425
+ if (!b) return 1;
426
+ const r = await m.settings.update(b);
427
+ json(r);
428
+ return done(r);
429
+ }
430
+ deps.error("Usage: twelveai settings get | set --file settings.json");
431
+ return 1;
432
+ }
433
+ case "billing":
434
+ return json(await m.billing.get()), 0;
435
+ case "topup": {
436
+ const amount = Number(sub);
437
+ if (!amount || amount < 5e3) return deps.error("Usage: twelveai topup <amountNgn> (minimum 5000)"), 1;
438
+ const r = await m.billing.topup(amount, flag(rest, "email"));
439
+ json(r);
440
+ return done(r);
441
+ }
442
+ case "usage":
443
+ return json(await m.usage.summary()), 0;
444
+ case "requests":
445
+ return json(await m.usage.events(Number(flag(rest, "limit")) || 20)), 0;
446
+ case "request": {
447
+ if (!sub) return deps.error("Usage: twelveai request <id>"), 1;
448
+ return json(await m.usage.event(sub)), 0;
449
+ }
450
+ case "manifest":
451
+ return json(await m.manifest()), 0;
452
+ default:
453
+ deps.error(`Unknown command "${cmd}".
454
+
455
+ ${HELP}`);
456
+ return 1;
457
+ }
458
+ }
459
+ var isMain = process.argv[1]?.endsWith("cli.js") || process.argv[1]?.endsWith("twelveai");
460
+ if (isMain) {
461
+ runCli(process.argv.slice(2), {
462
+ env: process.env,
463
+ log: (s) => console.log(s),
464
+ error: (s) => console.error(s),
465
+ readFile: (p) => (0, import_node_fs.readFileSync)(p, "utf8")
466
+ }).then((code) => process.exit(code));
467
+ }
468
+ // Annotate the CommonJS export names for ESM import in node:
469
+ 0 && (module.exports = {
470
+ runCli
471
+ });
package/dist/cli.d.cts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { T as TwelveAI } from './client-C5vzks2N.cjs';
3
+
4
+ interface CliDeps {
5
+ env: Record<string, string | undefined>;
6
+ log: (s: string) => void;
7
+ error: (s: string) => void;
8
+ readFile: (path: string) => string;
9
+ client?: TwelveAI;
10
+ }
11
+ declare function runCli(argv: string[], deps: CliDeps): Promise<number>;
12
+
13
+ export { runCli };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import { T as TwelveAI } from './client-C5vzks2N.js';
3
+
4
+ interface CliDeps {
5
+ env: Record<string, string | undefined>;
6
+ log: (s: string) => void;
7
+ error: (s: string) => void;
8
+ readFile: (path: string) => string;
9
+ client?: TwelveAI;
10
+ }
11
+ declare function runCli(argv: string[], deps: CliDeps): Promise<number>;
12
+
13
+ export { runCli };