@topolo/sdk 0.4.10 → 0.6.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/README.md +4 -4
- package/dist/index.cjs +160 -158
- package/dist/index.d.cts +136 -138
- package/dist/index.d.ts +136 -138
- package/dist/index.js +159 -157
- package/dist/node/credential-store.cjs +440 -0
- package/dist/node/credential-store.d.cts +81 -0
- package/dist/node/credential-store.d.ts +81 -0
- package/dist/node/credential-store.js +399 -0
- package/package.json +7 -2
- package/src/application-requirements.ts +16 -16
- package/src/applications.generated.ts +98 -98
- package/src/client.test.ts +56 -56
- package/src/client.ts +48 -48
- package/src/index.test.ts +3 -3
- package/src/index.ts +2 -2
- package/src/modules/crm.test.ts +4 -4
- package/src/modules/identity.test.ts +4 -4
- package/src/modules/p2p.test.ts +4 -8
- package/src/modules/p2p.ts +0 -3
- package/src/node/credential-store.ts +311 -0
- package/src/oauth.ts +2 -2
- package/src/services.generated.ts +2 -2
- package/src/services.test.ts +29 -29
- package/src/services.ts +19 -19
- package/src/widgets.test.ts +14 -14
- package/src/widgets.ts +8 -8
|
@@ -0,0 +1,399 @@
|
|
|
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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Typed client SDK for the Topolo platform. Used by TopoloCli, TopoloMCP, and third-party agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
13
|
"require": "./dist/index.cjs"
|
|
14
|
+
},
|
|
15
|
+
"./node": {
|
|
16
|
+
"types": "./dist/node/credential-store.d.ts",
|
|
17
|
+
"import": "./dist/node/credential-store.js",
|
|
18
|
+
"require": "./dist/node/credential-store.cjs"
|
|
14
19
|
}
|
|
15
20
|
},
|
|
16
21
|
"files": [
|
|
@@ -21,7 +26,7 @@
|
|
|
21
26
|
"generate:services": "node scripts/generate-services.mjs",
|
|
22
27
|
"check:generated": "node scripts/check-generated.mjs",
|
|
23
28
|
"prebuild": "node scripts/generate-services.mjs",
|
|
24
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --clean",
|
|
29
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --clean && tsup src/node/credential-store.ts --format esm,cjs --dts --out-dir dist/node",
|
|
25
30
|
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
26
31
|
"typecheck": "node scripts/generate-services.mjs && tsc --noEmit",
|
|
27
32
|
"test": "vitest run",
|
|
@@ -105,12 +105,12 @@ export const APPLICATION_REQUIREMENTS = [
|
|
|
105
105
|
id: 'shared-auth-boundary',
|
|
106
106
|
title: 'Use the shared Topolo auth boundary',
|
|
107
107
|
summary:
|
|
108
|
-
'Applications must derive identity, organization, scopes, and
|
|
108
|
+
'Applications must derive identity, organization, scopes, and app entitlement from TopoloAuth rather than inventing parallel auth state.',
|
|
109
109
|
appliesTo: ['browser', 'api', 'tooling', 'agent_surface'],
|
|
110
110
|
evidence: [
|
|
111
111
|
'@topolo/auth-client or @topolo-io/worker-runtime is used where applicable',
|
|
112
112
|
'No public command, tool, or SDK method accepts orgId',
|
|
113
|
-
'
|
|
113
|
+
'App slug and Auth app registration are documented for protected APIs',
|
|
114
114
|
],
|
|
115
115
|
implementation: [
|
|
116
116
|
'Use shared auth/runtime packages instead of copying token parsing, gateway, or browser storage flows',
|
|
@@ -144,21 +144,21 @@ export const APPLICATION_REQUIREMENTS = [
|
|
|
144
144
|
],
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
|
-
id: '
|
|
148
|
-
title: 'Register protected APIs as platform
|
|
147
|
+
id: 'app-registration-and-scopes',
|
|
148
|
+
title: 'Register protected APIs as platform apps',
|
|
149
149
|
summary:
|
|
150
|
-
'Callable app APIs need a stable
|
|
150
|
+
'Callable app APIs need a stable app ID, app slug, permissions, role bundles, and API-key scopes before agents or users rely on them.',
|
|
151
151
|
appliesTo: ['api'],
|
|
152
152
|
evidence: [
|
|
153
|
-
'Auth
|
|
154
|
-
'Permissions, role bundles, and API-key scopes exist for the
|
|
155
|
-
'SDK, CLI, and MCP
|
|
156
|
-
'CloudControl production HTTP targets classify agent reachability with
|
|
153
|
+
'Auth app catalog contains the app ID and slug',
|
|
154
|
+
'Permissions, role bundles, and API-key scopes exist for the app',
|
|
155
|
+
'SDK, CLI, and MCP app discovery include the callable production API',
|
|
156
|
+
'CloudControl production HTTP targets classify agent reachability with sdk_app_id or agent_callable:false',
|
|
157
157
|
],
|
|
158
158
|
implementation: [
|
|
159
|
-
'Add production Worker api_url metadata and
|
|
159
|
+
'Add production Worker api_url metadata and sdk_app_id for every agent-callable API target',
|
|
160
160
|
'Mark browser, documentation, and runtime-only Worker targets with agent_callable:false',
|
|
161
|
-
'Expose stable
|
|
161
|
+
'Expose stable app IDs through generated DEFAULT_APP_URLS and live catalog discovery',
|
|
162
162
|
'Keep typed SDK/CLI/MCP surfaces in sync when an API contract stabilizes',
|
|
163
163
|
],
|
|
164
164
|
},
|
|
@@ -281,12 +281,12 @@ export function applicationRequirementScopes(
|
|
|
281
281
|
// the generated kind union to "worker" (no "pages" literal remains).
|
|
282
282
|
application.deployTargets.some((target) => String(target.kind) === 'pages');
|
|
283
283
|
// An API surface means an agent-callable service — keyed on a resolved
|
|
284
|
-
//
|
|
284
|
+
// appId, NOT a bare apiUrl. After the Pages->Worker migration every
|
|
285
285
|
// frontend shell carries an apiUrl (its own serving domain), so apiUrl is no
|
|
286
|
-
// longer a signal for "exposes a callable API";
|
|
286
|
+
// longer a signal for "exposes a callable API"; appId is.
|
|
287
287
|
const hasApiSurface =
|
|
288
288
|
application.services.length > 0 ||
|
|
289
|
-
application.deployTargets.some((target) => Boolean(target.
|
|
289
|
+
application.deployTargets.some((target) => Boolean(target.appId));
|
|
290
290
|
|
|
291
291
|
if (hasBrowserSurface) scopes.add('browser');
|
|
292
292
|
if (hasApiSurface) scopes.add('api');
|
|
@@ -410,7 +410,7 @@ function evaluateRequirement(
|
|
|
410
410
|
'Catalog metadata cannot verify shared shell, launcher, and account menu adoption.',
|
|
411
411
|
evidence,
|
|
412
412
|
);
|
|
413
|
-
case '
|
|
413
|
+
case 'app-registration-and-scopes': {
|
|
414
414
|
if (application.services.length > 0) {
|
|
415
415
|
evidence.push(`services=${application.services.join(',')}`);
|
|
416
416
|
return finding(
|
|
@@ -420,7 +420,7 @@ function evaluateRequirement(
|
|
|
420
420
|
evidence,
|
|
421
421
|
);
|
|
422
422
|
}
|
|
423
|
-
return finding(requirement, 'missing', 'No SDK
|
|
423
|
+
return finding(requirement, 'missing', 'No SDK app API id is registered for this callable API surface.', evidence);
|
|
424
424
|
}
|
|
425
425
|
case 'organization-scoped-data':
|
|
426
426
|
return finding(
|