@topolo/sdk 0.6.0 → 0.8.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.
@@ -1,399 +0,0 @@
1
- // src/node/credential-store.ts
2
- import { mkdir, readFile, writeFile, chmod } from "fs/promises";
3
- import { existsSync } from "fs";
4
- import { homedir, platform } from "os";
5
- import { dirname, join } from "path";
6
-
7
- // src/errors.ts
8
- var TopoloSdkError = class extends Error {
9
- code;
10
- constructor(code, message) {
11
- super(message);
12
- this.name = "TopoloSdkError";
13
- this.code = code;
14
- }
15
- };
16
- var TopoloAuthError = class extends TopoloSdkError {
17
- constructor(message, code = "auth_error") {
18
- super(code, message);
19
- this.name = "TopoloAuthError";
20
- }
21
- };
22
- var TopoloHttpError = class extends TopoloSdkError {
23
- status;
24
- body;
25
- service;
26
- path;
27
- constructor(service, path, status, body, message) {
28
- super("http_error", message ?? `HTTP ${status} from ${service}${path}`);
29
- this.name = "TopoloHttpError";
30
- this.status = status;
31
- this.body = body;
32
- this.service = service;
33
- this.path = path;
34
- }
35
- };
36
-
37
- // src/services.generated.ts
38
- var APP_API_URLS = {
39
- agent: "https://agent.topolo.app",
40
- auth: "https://auth.topolo.app",
41
- backup: "https://backup.topolo.app",
42
- books: "https://books.topolo.app",
43
- bugfix: "https://bugfix.topolo.app",
44
- bytes: "https://bytes.topolo.app",
45
- calendar: "https://calendar.topolo.app",
46
- capacity: "https://capacity.topolo.app",
47
- chat: "https://chat.topolo.app",
48
- commerce: "https://commerce.topolo.app",
49
- compose: "https://compose.topolo.app",
50
- consent: "https://consent.topolo.app",
51
- crm: "https://crm.topolo.app",
52
- developers: "https://developers.topolo.app",
53
- director: "https://director.topolo.app",
54
- feed: "https://feed.topolo.app",
55
- flow: "https://flow.topolo.app",
56
- forecast: "https://forecast.topolo.app",
57
- forms: "https://forms.topolo.app",
58
- home: "https://home.topolo.app",
59
- insights: "https://insights.topolo.app",
60
- inventory: "https://inventory.topolo.app",
61
- learn: "https://learn.topolo.app",
62
- localize: "https://localize.topolo.app",
63
- mail: "https://mail.topolo.app",
64
- mdm: "https://mdm.topolo.app",
65
- messages: "https://messages.topolo.app",
66
- nexus: "https://nexus.topolo.app",
67
- notify: "https://notify.topolo.app",
68
- observability_watch: "https://observability-watch.topolo.app",
69
- one: "https://www.topolo.app",
70
- p2p: "https://p2p.topolo.app",
71
- pay: "https://pay.topolo.app",
72
- people: "https://people.topolo.app",
73
- "quro.api": "https://api.ol0.me",
74
- "quro.redirect": "https://ol0.me",
75
- roadmapper: "https://roadmapper.topolo.app",
76
- sign: "https://sign.topolo.app",
77
- social_studio: "https://studio.topolo.app",
78
- socialize: "https://socialize.topolo.app",
79
- spaces: "https://spaces.topolo.app",
80
- status: "https://status.topolo.app",
81
- success: "https://success.topolo.app",
82
- support: "https://support.topolo.app",
83
- survey: "https://survey.topolo.app",
84
- voice: "https://voice.topolo.app",
85
- web: "https://web.topolo.app"
86
- };
87
-
88
- // src/services.ts
89
- var DEFAULT_APP_URLS = APP_API_URLS;
90
- function resolveServiceUrl(service, overrides) {
91
- const override = resolveServiceUrlOverride([service], overrides);
92
- if (override) return override;
93
- return DEFAULT_APP_URLS[service];
94
- }
95
- function resolveServiceUrlOverride(keys, overrides) {
96
- for (const key of keys) {
97
- const direct = overrides?.[key];
98
- if (direct) return direct;
99
- const envStyleKey = key.replace(/\./g, "_");
100
- const envStyle = overrides?.[envStyleKey];
101
- if (envStyle) return envStyle;
102
- }
103
- for (const key of keys) {
104
- const envKey = `TOPOLO_APP_URL_${key.replace(/\./g, "_").toUpperCase()}`;
105
- const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
106
- if (envValue) return envValue;
107
- }
108
- return null;
109
- }
110
-
111
- // src/oauth.ts
112
- var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
113
- var TopoloOAuth = class {
114
- baseUrl;
115
- fetchImpl;
116
- constructor(options = {}) {
117
- this.baseUrl = resolveServiceUrl("auth", options.serviceUrls);
118
- this.fetchImpl = options.fetch ?? fetch;
119
- }
120
- /**
121
- * RFC 8628 device authorization grant — starts the flow by asking the server
122
- * for a device_code + user_code. The caller prints the user_code +
123
- * verification_uri and then polls `pollDeviceToken` until approval.
124
- */
125
- async requestDeviceCode(params) {
126
- const body = new URLSearchParams();
127
- body.set("client_id", params.clientId);
128
- if (params.scope) {
129
- body.set("scope", Array.isArray(params.scope) ? params.scope.join(" ") : params.scope);
130
- }
131
- return this.#postForm("/api/developer-oauth/device_authorization", body);
132
- }
133
- /**
134
- * Polls the token endpoint once. Returns the token pair on success, or
135
- * throws a `TopoloAuthError` whose `code` is one of the RFC 8628 poll
136
- * states: `authorization_pending`, `slow_down`, `access_denied`,
137
- * `expired_token`. Callers should back off on `slow_down`, wait at least
138
- * one `interval` on `authorization_pending`, and give up on the rest.
139
- */
140
- async pollDeviceToken(params) {
141
- const body = new URLSearchParams();
142
- body.set("grant_type", DEVICE_GRANT);
143
- body.set("client_id", params.clientId);
144
- body.set("device_code", params.deviceCode);
145
- if (params.clientSecret) body.set("client_secret", params.clientSecret);
146
- return this.#postForm("/api/developer-oauth/token", body);
147
- }
148
- /** Exchange an authorization code (with PKCE) for tokens. */
149
- async exchangeAuthorizationCode(params) {
150
- const body = new URLSearchParams();
151
- body.set("grant_type", "authorization_code");
152
- body.set("client_id", params.clientId);
153
- body.set("code", params.code);
154
- body.set("redirect_uri", params.redirectUri);
155
- if (params.codeVerifier) body.set("code_verifier", params.codeVerifier);
156
- if (params.clientSecret) body.set("client_secret", params.clientSecret);
157
- return this.#postForm("/api/developer-oauth/token", body);
158
- }
159
- /** Rotate a refresh token for a new token pair. */
160
- async refreshToken(params) {
161
- const body = new URLSearchParams();
162
- body.set("grant_type", "refresh_token");
163
- body.set("client_id", params.clientId);
164
- body.set("refresh_token", params.refreshToken);
165
- if (params.clientSecret) body.set("client_secret", params.clientSecret);
166
- return this.#postForm("/api/developer-oauth/token", body);
167
- }
168
- async #postForm(path, body) {
169
- const url = new URL(path, this.baseUrl.endsWith("/") ? this.baseUrl : `${this.baseUrl}/`);
170
- const res = await this.fetchImpl(url.toString(), {
171
- method: "POST",
172
- headers: {
173
- "Content-Type": "application/x-www-form-urlencoded",
174
- Accept: "application/json"
175
- },
176
- body: body.toString()
177
- });
178
- const contentType = res.headers.get("Content-Type") ?? "";
179
- const parsed = contentType.includes("application/json") ? await res.json().catch(() => null) : await res.text().catch(() => null);
180
- if (!res.ok) {
181
- if (parsed && typeof parsed === "object" && "error" in parsed) {
182
- const err = parsed;
183
- throw new TopoloAuthError(err.error_description || err.error, err.error);
184
- }
185
- throw new TopoloHttpError("auth", path, res.status, parsed);
186
- }
187
- return parsed;
188
- }
189
- };
190
-
191
- // src/node/credential-store.ts
192
- var TOPOLO_ENVIRONMENTS = ["production", "staging", "development", "local"];
193
- var ENVIRONMENT_DEFAULT_SERVICE_URLS = {
194
- production: {
195
- developers: "https://developers.topolo.app"
196
- },
197
- staging: {
198
- auth: "https://auth.stg.topolo.us",
199
- developers: "https://developers.stg.topolo.us",
200
- seed: "https://seed.stg.topolo.us"
201
- },
202
- development: {
203
- auth: "https://auth.topolo.dev",
204
- developers: "https://developers.topolo.dev"
205
- },
206
- local: {}
207
- };
208
- function configPath() {
209
- const override = process.env.TOPOLO_CONFIG_PATH;
210
- if (override) return override;
211
- const base = process.env.XDG_CONFIG_HOME || (platform() === "win32" ? join(homedir(), "AppData", "Roaming") : join(homedir(), ".config"));
212
- return join(base, "topolo", "config.json");
213
- }
214
- async function readConfig() {
215
- const path = configPath();
216
- if (!existsSync(path)) return {};
217
- try {
218
- const raw = await readFile(path, "utf8");
219
- return JSON.parse(raw);
220
- } catch {
221
- return {};
222
- }
223
- }
224
- async function writeConfig(config) {
225
- const path = configPath();
226
- await mkdir(dirname(path), { recursive: true });
227
- await writeFile(path, JSON.stringify(config, null, 2) + "\n", "utf8");
228
- if (platform() !== "win32") {
229
- try {
230
- await chmod(path, 384);
231
- } catch {
232
- }
233
- }
234
- }
235
- function normalizeEnvironment(value) {
236
- const normalized = (value ?? "production").trim().toLowerCase();
237
- if (normalized === "" || normalized === "production" || normalized === "prod") {
238
- return "production";
239
- }
240
- if (normalized === "staging" || normalized === "stage" || normalized === "stg") {
241
- return "staging";
242
- }
243
- if (normalized === "development" || normalized === "dev") {
244
- return "development";
245
- }
246
- if (normalized === "local" || normalized === "localhost") {
247
- return "local";
248
- }
249
- throw new Error(
250
- `Unknown Topolo environment "${value}". Expected production, staging, development, or local.`
251
- );
252
- }
253
- function selectedEnvironment() {
254
- return normalizeEnvironment(process.env.TOPOLO_ENV);
255
- }
256
- function envVarPrefix(environment) {
257
- if (environment === "production") return "TOPOLO_PRODUCTION";
258
- if (environment === "staging") return "TOPOLO_STAGING";
259
- if (environment === "development") return "TOPOLO_DEVELOPMENT";
260
- return "TOPOLO_LOCAL";
261
- }
262
- function profileForEnvironment(config, environment = selectedEnvironment()) {
263
- const profile = config.profiles?.[environment] ?? {};
264
- if (environment === "production") {
265
- return {
266
- credential: config.credential,
267
- oauthClientId: config.oauthClientId,
268
- ...profile,
269
- serviceUrls: {
270
- ...config.serviceUrls ?? {},
271
- ...profile.serviceUrls ?? {}
272
- },
273
- agentName: profile.agentName ?? config.agentName
274
- };
275
- }
276
- return profile;
277
- }
278
- function writeProfileConfig(config, environment, profile) {
279
- const profiles = { ...config.profiles ?? {} };
280
- profiles[environment] = {
281
- ...profiles[environment] ?? {},
282
- ...profile
283
- };
284
- return { ...config, profiles };
285
- }
286
- function clearProfileCredential(config, environment) {
287
- const next = { ...config };
288
- if (environment === "production") {
289
- delete next.credential;
290
- delete next.oauthClientId;
291
- }
292
- const profiles = { ...config.profiles ?? {} };
293
- const profile = { ...profiles[environment] ?? {} };
294
- delete profile.credential;
295
- delete profile.oauthClientId;
296
- profiles[environment] = profile;
297
- return { ...next, profiles };
298
- }
299
- function credentialSourceFromConfig(config, environment = selectedEnvironment()) {
300
- const prefix = envVarPrefix(environment);
301
- if (process.env[`${prefix}_API_KEY`]) return `env:${prefix}_API_KEY`;
302
- if (process.env[`${prefix}_ACCESS_TOKEN`]) return `env:${prefix}_ACCESS_TOKEN`;
303
- if (process.env.TOPOLO_API_KEY) return "env:TOPOLO_API_KEY";
304
- if (process.env.TOPOLO_ACCESS_TOKEN) return "env:TOPOLO_ACCESS_TOKEN";
305
- return profileForEnvironment(config, environment).credential ? "config-file" : "none";
306
- }
307
- async function resolveCredential(environment = selectedEnvironment()) {
308
- const prefix = envVarPrefix(environment);
309
- const apiKey = process.env[`${prefix}_API_KEY`] ?? process.env.TOPOLO_API_KEY;
310
- if (apiKey) return { kind: "api_key", apiKey };
311
- const accessToken = process.env[`${prefix}_ACCESS_TOKEN`] ?? process.env.TOPOLO_ACCESS_TOKEN;
312
- if (accessToken) return { kind: "access_token", accessToken };
313
- const config = await readConfig();
314
- return profileForEnvironment(config, environment).credential ?? null;
315
- }
316
- var REFRESH_SKEW_SECONDS = 60;
317
- async function refreshCredentialIfNeeded(environment = selectedEnvironment()) {
318
- const prefix = envVarPrefix(environment);
319
- if (process.env[`${prefix}_API_KEY`] || process.env[`${prefix}_ACCESS_TOKEN`] || process.env.TOPOLO_API_KEY || process.env.TOPOLO_ACCESS_TOKEN) {
320
- return resolveCredential(environment);
321
- }
322
- const config = await readConfig();
323
- const profile = profileForEnvironment(config, environment);
324
- const cred = profile.credential;
325
- if (!cred || cred.kind !== "access_token") return cred ?? null;
326
- if (!cred.refreshToken || !cred.expiresAt || !profile.oauthClientId) return cred;
327
- const now = Math.floor(Date.now() / 1e3);
328
- if (cred.expiresAt - REFRESH_SKEW_SECONDS > now) return cred;
329
- try {
330
- const oauth = new TopoloOAuth({
331
- serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
332
- });
333
- const tokens = await oauth.refreshToken({
334
- clientId: profile.oauthClientId,
335
- refreshToken: cred.refreshToken
336
- });
337
- const refreshed = {
338
- kind: "access_token",
339
- accessToken: tokens.access_token,
340
- ...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: cred.refreshToken },
341
- expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
342
- };
343
- await writeConfig(
344
- environment === "production" && !config.profiles?.production ? { ...config, credential: refreshed } : writeProfileConfig(config, environment, { credential: refreshed })
345
- );
346
- return refreshed;
347
- } catch {
348
- return cred;
349
- }
350
- }
351
- function resolveAgentName(configAgentName) {
352
- return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
353
- }
354
- function resolveServiceUrls(configUrls, environment = selectedEnvironment()) {
355
- const merged = {
356
- ...ENVIRONMENT_DEFAULT_SERVICE_URLS[environment],
357
- ...configUrls ?? {}
358
- };
359
- const prefix = `${envVarPrefix(environment)}_SERVICE_URL_`;
360
- for (const [k, v] of Object.entries(process.env)) {
361
- if (k.startsWith(prefix) && v) {
362
- merged[k.slice(prefix.length).toLowerCase()] = v;
363
- }
364
- }
365
- for (const [k, v] of Object.entries(process.env)) {
366
- if (k.startsWith("TOPOLO_SERVICE_URL_") && v) {
367
- merged[k.slice("TOPOLO_SERVICE_URL_".length).toLowerCase()] = v;
368
- }
369
- }
370
- return Object.keys(merged).length > 0 ? merged : void 0;
371
- }
372
- function resolvedProfileConfig(config, environment = selectedEnvironment()) {
373
- const profile = profileForEnvironment(config, environment);
374
- return {
375
- credential: profile.credential,
376
- oauthClientId: profile.oauthClientId,
377
- agentName: profile.agentName,
378
- serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
379
- };
380
- }
381
- export {
382
- ENVIRONMENT_DEFAULT_SERVICE_URLS,
383
- TOPOLO_ENVIRONMENTS,
384
- clearProfileCredential,
385
- configPath,
386
- credentialSourceFromConfig,
387
- envVarPrefix,
388
- normalizeEnvironment,
389
- profileForEnvironment,
390
- readConfig,
391
- refreshCredentialIfNeeded,
392
- resolveAgentName,
393
- resolveCredential,
394
- resolveServiceUrls,
395
- resolvedProfileConfig,
396
- selectedEnvironment,
397
- writeConfig,
398
- writeProfileConfig
399
- };