@ai-accounts/ts-core 0.2.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/LICENSE +202 -0
- package/dist/index.cjs +484 -0
- package/dist/index.d.cts +1084 -0
- package/dist/index.d.ts +1084 -0
- package/dist/index.js +453 -0
- package/package.json +27 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
// src/protocol/wire.ts
|
|
2
|
+
var WIRE_PROTOCOL_VERSION = 1;
|
|
3
|
+
|
|
4
|
+
// src/client/index.ts
|
|
5
|
+
async function toError(r) {
|
|
6
|
+
let code = "http_error";
|
|
7
|
+
let message = r.statusText;
|
|
8
|
+
try {
|
|
9
|
+
const body = await r.json();
|
|
10
|
+
if (body.error) {
|
|
11
|
+
code = body.error.code ?? code;
|
|
12
|
+
message = body.error.message ?? message;
|
|
13
|
+
}
|
|
14
|
+
} catch {
|
|
15
|
+
}
|
|
16
|
+
const err = new Error(message);
|
|
17
|
+
err.code = code;
|
|
18
|
+
err.status = r.status;
|
|
19
|
+
return err;
|
|
20
|
+
}
|
|
21
|
+
var AiAccountsClient = class {
|
|
22
|
+
baseUrl;
|
|
23
|
+
token;
|
|
24
|
+
_fetch;
|
|
25
|
+
constructor(opts) {
|
|
26
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
27
|
+
this.token = opts.token;
|
|
28
|
+
this._fetch = opts.fetch ?? fetch;
|
|
29
|
+
}
|
|
30
|
+
headers() {
|
|
31
|
+
const h = { "content-type": "application/json" };
|
|
32
|
+
if (this.token) h["authorization"] = `Bearer ${this.token}`;
|
|
33
|
+
return h;
|
|
34
|
+
}
|
|
35
|
+
async listBackends() {
|
|
36
|
+
const r = await this._fetch(`${this.baseUrl}/api/v1/backends/`, {
|
|
37
|
+
headers: this.headers()
|
|
38
|
+
});
|
|
39
|
+
if (!r.ok) throw await toError(r);
|
|
40
|
+
return await r.json();
|
|
41
|
+
}
|
|
42
|
+
async createBackend(input) {
|
|
43
|
+
const r = await this._fetch(`${this.baseUrl}/api/v1/backends/`, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: this.headers(),
|
|
46
|
+
body: JSON.stringify({ config: {}, ...input })
|
|
47
|
+
});
|
|
48
|
+
if (!r.ok) throw await toError(r);
|
|
49
|
+
return await r.json();
|
|
50
|
+
}
|
|
51
|
+
async getBackend(id) {
|
|
52
|
+
const r = await this._fetch(
|
|
53
|
+
`${this.baseUrl}/api/v1/backends/${encodeURIComponent(id)}`,
|
|
54
|
+
{ headers: this.headers() }
|
|
55
|
+
);
|
|
56
|
+
if (!r.ok) throw await toError(r);
|
|
57
|
+
return await r.json();
|
|
58
|
+
}
|
|
59
|
+
async deleteBackend(id) {
|
|
60
|
+
const r = await this._fetch(
|
|
61
|
+
`${this.baseUrl}/api/v1/backends/${encodeURIComponent(id)}`,
|
|
62
|
+
{ method: "DELETE", headers: this.headers() }
|
|
63
|
+
);
|
|
64
|
+
if (!r.ok) throw await toError(r);
|
|
65
|
+
}
|
|
66
|
+
async detectBackend(id) {
|
|
67
|
+
return this.postAction(id, "detect");
|
|
68
|
+
}
|
|
69
|
+
async loginBackend(id, flowKind, inputs) {
|
|
70
|
+
return this.postAction(id, "login", { flow_kind: flowKind, inputs });
|
|
71
|
+
}
|
|
72
|
+
async pollBackendLogin(id, handle) {
|
|
73
|
+
const r = await this._fetch(
|
|
74
|
+
`${this.baseUrl}/api/v1/backends/${encodeURIComponent(id)}/login/poll`,
|
|
75
|
+
{
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: this.headers(),
|
|
78
|
+
body: JSON.stringify({ handle })
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
if (!r.ok) throw await toError(r);
|
|
82
|
+
return await r.json();
|
|
83
|
+
}
|
|
84
|
+
async validateBackend(id) {
|
|
85
|
+
return this.postAction(id, "validate");
|
|
86
|
+
}
|
|
87
|
+
async startOnboarding() {
|
|
88
|
+
const r = await this._fetch(`${this.baseUrl}/api/v1/onboarding`, {
|
|
89
|
+
method: "POST",
|
|
90
|
+
headers: this.headers()
|
|
91
|
+
});
|
|
92
|
+
if (!r.ok) throw await toError(r);
|
|
93
|
+
return await r.json();
|
|
94
|
+
}
|
|
95
|
+
async getOnboarding(id) {
|
|
96
|
+
const r = await this._fetch(
|
|
97
|
+
`${this.baseUrl}/api/v1/onboarding/${encodeURIComponent(id)}`,
|
|
98
|
+
{ headers: this.headers() }
|
|
99
|
+
);
|
|
100
|
+
if (!r.ok) throw await toError(r);
|
|
101
|
+
return await r.json();
|
|
102
|
+
}
|
|
103
|
+
async detectForOnboarding(id) {
|
|
104
|
+
return this.onboardingAction(id, "detect");
|
|
105
|
+
}
|
|
106
|
+
async pickOnboardingKind(id, kind, displayName) {
|
|
107
|
+
return this.onboardingAction(id, "pick", {
|
|
108
|
+
kind,
|
|
109
|
+
display_name: displayName
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async beginOnboardingLogin(id, flowKind, inputs) {
|
|
113
|
+
return this.onboardingAction(id, "login", {
|
|
114
|
+
flow_kind: flowKind,
|
|
115
|
+
inputs
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async pollOnboardingLogin(id, handle) {
|
|
119
|
+
return this.onboardingAction(id, "login/poll", { handle });
|
|
120
|
+
}
|
|
121
|
+
async finalizeOnboarding(id) {
|
|
122
|
+
return this.onboardingAction(id, "finalize");
|
|
123
|
+
}
|
|
124
|
+
async postAction(id, action, body) {
|
|
125
|
+
const r = await this._fetch(
|
|
126
|
+
`${this.baseUrl}/api/v1/backends/${encodeURIComponent(id)}/${action}`,
|
|
127
|
+
{
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: this.headers(),
|
|
130
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
if (!r.ok) throw await toError(r);
|
|
134
|
+
return await r.json();
|
|
135
|
+
}
|
|
136
|
+
async onboardingAction(id, action, body) {
|
|
137
|
+
const r = await this._fetch(
|
|
138
|
+
`${this.baseUrl}/api/v1/onboarding/${encodeURIComponent(id)}/${action}`,
|
|
139
|
+
{
|
|
140
|
+
method: "POST",
|
|
141
|
+
headers: this.headers(),
|
|
142
|
+
...body !== void 0 ? { body: JSON.stringify(body) } : {}
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
if (!r.ok) throw await toError(r);
|
|
146
|
+
return await r.json();
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/machines/accountWizard.ts
|
|
151
|
+
function createAccountWizard(opts) {
|
|
152
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
153
|
+
const emit = () => listeners.forEach((l) => l());
|
|
154
|
+
let state = "idle";
|
|
155
|
+
let kind;
|
|
156
|
+
let detection;
|
|
157
|
+
let backend;
|
|
158
|
+
let error;
|
|
159
|
+
return {
|
|
160
|
+
get state() {
|
|
161
|
+
return state;
|
|
162
|
+
},
|
|
163
|
+
get kind() {
|
|
164
|
+
return kind;
|
|
165
|
+
},
|
|
166
|
+
get detection() {
|
|
167
|
+
return detection;
|
|
168
|
+
},
|
|
169
|
+
get backend() {
|
|
170
|
+
return backend;
|
|
171
|
+
},
|
|
172
|
+
get error() {
|
|
173
|
+
return error;
|
|
174
|
+
},
|
|
175
|
+
subscribe(listener) {
|
|
176
|
+
listeners.add(listener);
|
|
177
|
+
return () => {
|
|
178
|
+
listeners.delete(listener);
|
|
179
|
+
};
|
|
180
|
+
},
|
|
181
|
+
start() {
|
|
182
|
+
state = "picking_kind";
|
|
183
|
+
kind = void 0;
|
|
184
|
+
detection = void 0;
|
|
185
|
+
backend = void 0;
|
|
186
|
+
error = void 0;
|
|
187
|
+
emit();
|
|
188
|
+
},
|
|
189
|
+
async pickKind(chosen) {
|
|
190
|
+
kind = chosen;
|
|
191
|
+
state = "detecting";
|
|
192
|
+
emit();
|
|
193
|
+
try {
|
|
194
|
+
backend = await opts.client.createBackend({
|
|
195
|
+
kind: chosen,
|
|
196
|
+
display_name: opts.defaultDisplayName ?? `${chosen} account`
|
|
197
|
+
});
|
|
198
|
+
detection = await opts.client.detectBackend(backend.id);
|
|
199
|
+
if (!detection.installed) {
|
|
200
|
+
state = "error";
|
|
201
|
+
error = `${chosen} CLI is not installed on the host`;
|
|
202
|
+
emit();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
state = "entering_credential";
|
|
206
|
+
} catch (e) {
|
|
207
|
+
state = "error";
|
|
208
|
+
error = e instanceof Error ? e.message : "failed to create backend";
|
|
209
|
+
}
|
|
210
|
+
emit();
|
|
211
|
+
},
|
|
212
|
+
async submitCredential(flowKind, inputs) {
|
|
213
|
+
if (!backend) {
|
|
214
|
+
state = "error";
|
|
215
|
+
error = "no backend in progress";
|
|
216
|
+
emit();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
state = "validating";
|
|
220
|
+
emit();
|
|
221
|
+
try {
|
|
222
|
+
const response = await opts.client.loginBackend(backend.id, flowKind, inputs);
|
|
223
|
+
if (response.kind === "pending") {
|
|
224
|
+
state = "error";
|
|
225
|
+
error = "OAuth flows are not supported in AccountWizard; use OnboardingFlow instead";
|
|
226
|
+
emit();
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
backend = await opts.client.validateBackend(backend.id);
|
|
230
|
+
state = "done";
|
|
231
|
+
} catch (e) {
|
|
232
|
+
state = "error";
|
|
233
|
+
error = e.message ?? "validation failed";
|
|
234
|
+
}
|
|
235
|
+
emit();
|
|
236
|
+
},
|
|
237
|
+
reset() {
|
|
238
|
+
state = "idle";
|
|
239
|
+
kind = void 0;
|
|
240
|
+
detection = void 0;
|
|
241
|
+
backend = void 0;
|
|
242
|
+
error = void 0;
|
|
243
|
+
emit();
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/machines/onboardingFlow.ts
|
|
249
|
+
function createOnboardingFlow(opts) {
|
|
250
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
251
|
+
const emit = () => listeners.forEach((l) => l());
|
|
252
|
+
let state = "idle";
|
|
253
|
+
let onboardingId;
|
|
254
|
+
let kinds;
|
|
255
|
+
let selectedKind;
|
|
256
|
+
let selectedBackend;
|
|
257
|
+
let oauthChallenge;
|
|
258
|
+
let createdBackendId;
|
|
259
|
+
let error;
|
|
260
|
+
let pollCancelled = false;
|
|
261
|
+
const pollIntervalMs = opts.pollIntervalMs ?? 2e3;
|
|
262
|
+
const timeoutMs = opts.timeoutMs ?? 15 * 60 * 1e3;
|
|
263
|
+
function setError(msg) {
|
|
264
|
+
state = "error";
|
|
265
|
+
error = msg;
|
|
266
|
+
emit();
|
|
267
|
+
}
|
|
268
|
+
async function finalize() {
|
|
269
|
+
if (!onboardingId) return;
|
|
270
|
+
state = "validating";
|
|
271
|
+
emit();
|
|
272
|
+
try {
|
|
273
|
+
const final = await opts.client.finalizeOnboarding(onboardingId);
|
|
274
|
+
createdBackendId = final.created_backend_id ?? void 0;
|
|
275
|
+
state = "done";
|
|
276
|
+
emit();
|
|
277
|
+
} catch (e) {
|
|
278
|
+
setError(e.message ?? "finalize failed");
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function schedulePoll(handle) {
|
|
282
|
+
pollCancelled = false;
|
|
283
|
+
const deadline = Date.now() + timeoutMs;
|
|
284
|
+
const tick = async () => {
|
|
285
|
+
if (pollCancelled || state !== "oauth_polling") return;
|
|
286
|
+
if (Date.now() > deadline) {
|
|
287
|
+
setError("OAuth login timed out");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (!onboardingId) {
|
|
291
|
+
setError("no onboarding session");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const result = await opts.client.pollOnboardingLogin(onboardingId, handle);
|
|
296
|
+
if (pollCancelled || state !== "oauth_polling") return;
|
|
297
|
+
if (result.kind === "complete") {
|
|
298
|
+
await finalize();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
setTimeout(tick, pollIntervalMs);
|
|
302
|
+
} catch (e) {
|
|
303
|
+
setError(e.message ?? "polling error");
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
setTimeout(tick, pollIntervalMs);
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
get state() {
|
|
310
|
+
return state;
|
|
311
|
+
},
|
|
312
|
+
get kinds() {
|
|
313
|
+
return kinds;
|
|
314
|
+
},
|
|
315
|
+
get selectedKind() {
|
|
316
|
+
return selectedKind;
|
|
317
|
+
},
|
|
318
|
+
get selectedBackend() {
|
|
319
|
+
return selectedBackend;
|
|
320
|
+
},
|
|
321
|
+
get oauthChallenge() {
|
|
322
|
+
return oauthChallenge;
|
|
323
|
+
},
|
|
324
|
+
get createdBackendId() {
|
|
325
|
+
return createdBackendId;
|
|
326
|
+
},
|
|
327
|
+
get error() {
|
|
328
|
+
return error;
|
|
329
|
+
},
|
|
330
|
+
subscribe(listener) {
|
|
331
|
+
listeners.add(listener);
|
|
332
|
+
return () => {
|
|
333
|
+
listeners.delete(listener);
|
|
334
|
+
};
|
|
335
|
+
},
|
|
336
|
+
async start() {
|
|
337
|
+
try {
|
|
338
|
+
const session = await opts.client.startOnboarding();
|
|
339
|
+
onboardingId = session.id;
|
|
340
|
+
state = "started";
|
|
341
|
+
emit();
|
|
342
|
+
} catch (e) {
|
|
343
|
+
setError(e.message ?? "failed to start onboarding");
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
async detect() {
|
|
347
|
+
if (!onboardingId) {
|
|
348
|
+
setError("not started");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
state = "detecting";
|
|
352
|
+
emit();
|
|
353
|
+
try {
|
|
354
|
+
const detect = await opts.client.detectForOnboarding(onboardingId);
|
|
355
|
+
kinds = Object.entries(detect.results).map(([id, detection]) => ({
|
|
356
|
+
id,
|
|
357
|
+
detection
|
|
358
|
+
}));
|
|
359
|
+
state = "picking_kind";
|
|
360
|
+
emit();
|
|
361
|
+
} catch (e) {
|
|
362
|
+
setError(e.message ?? "detect failed");
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
async pickKind(chosen) {
|
|
366
|
+
if (!onboardingId) {
|
|
367
|
+
setError("not started");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
try {
|
|
371
|
+
selectedBackend = await opts.client.pickOnboardingKind(
|
|
372
|
+
onboardingId,
|
|
373
|
+
chosen,
|
|
374
|
+
`${chosen} account`
|
|
375
|
+
);
|
|
376
|
+
selectedKind = chosen;
|
|
377
|
+
state = "entering_credential";
|
|
378
|
+
emit();
|
|
379
|
+
} catch (e) {
|
|
380
|
+
setError(e.message ?? "pickKind failed");
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
async submitApiKey(apiKey) {
|
|
384
|
+
if (!onboardingId) {
|
|
385
|
+
setError("not started");
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
state = "validating";
|
|
389
|
+
emit();
|
|
390
|
+
try {
|
|
391
|
+
const response = await opts.client.beginOnboardingLogin(onboardingId, "api_key", {
|
|
392
|
+
api_key: apiKey
|
|
393
|
+
});
|
|
394
|
+
if (response.kind !== "complete") {
|
|
395
|
+
setError("unexpected pending response from api_key flow");
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
await finalize();
|
|
399
|
+
} catch (e) {
|
|
400
|
+
setError(e.message ?? "login failed");
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
async submitOauthDevice() {
|
|
404
|
+
if (!onboardingId) {
|
|
405
|
+
setError("not started");
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
state = "oauth_challenge";
|
|
409
|
+
emit();
|
|
410
|
+
try {
|
|
411
|
+
const response = await opts.client.beginOnboardingLogin(onboardingId, "oauth_device", {});
|
|
412
|
+
if (response.kind !== "pending" || !response.oauth) {
|
|
413
|
+
setError("expected pending OAuth response");
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
oauthChallenge = response.oauth;
|
|
417
|
+
state = "oauth_polling";
|
|
418
|
+
emit();
|
|
419
|
+
schedulePoll(response.oauth.handle);
|
|
420
|
+
} catch (e) {
|
|
421
|
+
setError(e.message ?? "OAuth start failed");
|
|
422
|
+
}
|
|
423
|
+
},
|
|
424
|
+
cancelOAuth() {
|
|
425
|
+
pollCancelled = true;
|
|
426
|
+
state = "entering_credential";
|
|
427
|
+
oauthChallenge = void 0;
|
|
428
|
+
emit();
|
|
429
|
+
},
|
|
430
|
+
reset() {
|
|
431
|
+
pollCancelled = true;
|
|
432
|
+
state = "idle";
|
|
433
|
+
onboardingId = void 0;
|
|
434
|
+
kinds = void 0;
|
|
435
|
+
selectedKind = void 0;
|
|
436
|
+
selectedBackend = void 0;
|
|
437
|
+
oauthChallenge = void 0;
|
|
438
|
+
createdBackendId = void 0;
|
|
439
|
+
error = void 0;
|
|
440
|
+
emit();
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// src/index.ts
|
|
446
|
+
var version = "0.0.0";
|
|
447
|
+
export {
|
|
448
|
+
AiAccountsClient,
|
|
449
|
+
WIRE_PROTOCOL_VERSION,
|
|
450
|
+
createAccountWizard,
|
|
451
|
+
createOnboardingFlow,
|
|
452
|
+
version
|
|
453
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ai-accounts/ts-core",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Framework-agnostic TypeScript client and protocol for ai-accounts",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"lint": "eslint src",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
26
|
+
}
|
|
27
|
+
}
|