@topolo/cli 0.3.0 → 0.4.1
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/index.js +3 -45
- package/package.json +1 -1
- package/skills/topolo/SKILL.md +13 -0
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { platform as platform2 } from "os";
|
|
|
9
9
|
import { createInterface } from "readline/promises";
|
|
10
10
|
import { stdin as input } from "process";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// ../../../topolo-platform/packages/topolo-sdk/dist/index.js
|
|
13
13
|
function applyAuthHeaders(headers, credential) {
|
|
14
14
|
if (credential.kind === "api_key") {
|
|
15
15
|
headers.set("X-Api-Key", credential.apiKey);
|
|
@@ -3453,7 +3453,7 @@ function createTopolo(options) {
|
|
|
3453
3453
|
};
|
|
3454
3454
|
}
|
|
3455
3455
|
|
|
3456
|
-
//
|
|
3456
|
+
// ../../../topolo-platform/packages/topolo-sdk/dist/node/credential-store.js
|
|
3457
3457
|
import { mkdir, readFile, writeFile, chmod } from "fs/promises";
|
|
3458
3458
|
import { existsSync } from "fs";
|
|
3459
3459
|
import { homedir, platform } from "os";
|
|
@@ -3819,46 +3819,6 @@ async function refreshCredentialIfNeeded(environment = selectedEnvironment(), op
|
|
|
3819
3819
|
return cred;
|
|
3820
3820
|
}
|
|
3821
3821
|
}
|
|
3822
|
-
async function refreshContextCredentialIfNeeded(environment, key, options = {}) {
|
|
3823
|
-
const config = await readConfig();
|
|
3824
|
-
const profile = profileForEnvironment(config, environment);
|
|
3825
|
-
const context = profile.contexts?.[key];
|
|
3826
|
-
const credential = context?.credential;
|
|
3827
|
-
if (!credential || credential.kind !== "access_token") return credential ?? null;
|
|
3828
|
-
const oauthClientId = context.oauthClientId ?? profile.oauthClientId;
|
|
3829
|
-
if (!credential.refreshToken || !credential.expiresAt || !oauthClientId) return credential;
|
|
3830
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
3831
|
-
if (!options.force && credential.expiresAt - REFRESH_SKEW_SECONDS > now) return credential;
|
|
3832
|
-
try {
|
|
3833
|
-
const oauth = new TopoloOAuth2({
|
|
3834
|
-
serviceUrls: resolveServiceUrls(profile.serviceUrls, environment)
|
|
3835
|
-
});
|
|
3836
|
-
const tokens = await oauth.refreshToken({
|
|
3837
|
-
clientId: oauthClientId,
|
|
3838
|
-
refreshToken: credential.refreshToken
|
|
3839
|
-
});
|
|
3840
|
-
const refreshed = {
|
|
3841
|
-
kind: "access_token",
|
|
3842
|
-
accessToken: tokens.access_token,
|
|
3843
|
-
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : { refreshToken: credential.refreshToken },
|
|
3844
|
-
expiresAt: Math.floor(Date.now() / 1e3) + tokens.expires_in
|
|
3845
|
-
};
|
|
3846
|
-
await writeConfig(writeProfileConfig(config, environment, {
|
|
3847
|
-
...profile.activeContext === key ? { credential: refreshed } : {},
|
|
3848
|
-
contexts: {
|
|
3849
|
-
...profile.contexts ?? {},
|
|
3850
|
-
[key]: {
|
|
3851
|
-
...context,
|
|
3852
|
-
credential: refreshed,
|
|
3853
|
-
oauthClientId
|
|
3854
|
-
}
|
|
3855
|
-
}
|
|
3856
|
-
}));
|
|
3857
|
-
return refreshed;
|
|
3858
|
-
} catch {
|
|
3859
|
-
return credential;
|
|
3860
|
-
}
|
|
3861
|
-
}
|
|
3862
3822
|
function resolveAgentName(configAgentName) {
|
|
3863
3823
|
return process.env.TOPOLO_AGENT_NAME ?? configAgentName;
|
|
3864
3824
|
}
|
|
@@ -11268,7 +11228,6 @@ function registerContextCommand(program2) {
|
|
|
11268
11228
|
}
|
|
11269
11229
|
const wantPersonal = /^(personal|me)$/i.test(target.trim());
|
|
11270
11230
|
let resolved;
|
|
11271
|
-
let exchangeCredential = cred;
|
|
11272
11231
|
const needle = target.trim().toLowerCase();
|
|
11273
11232
|
const storedEntry = Object.entries(profile.contexts ?? {}).find(([, candidate]) => {
|
|
11274
11233
|
if (wantPersonal) return candidate.contextType === "personal";
|
|
@@ -11276,7 +11235,6 @@ function registerContextCommand(program2) {
|
|
|
11276
11235
|
});
|
|
11277
11236
|
const storedContext = storedEntry?.[1];
|
|
11278
11237
|
if (storedEntry && storedContext?.credential) {
|
|
11279
|
-
exchangeCredential = await refreshContextCredentialIfNeeded(environment, storedEntry[0]) ?? storedContext.credential;
|
|
11280
11238
|
resolved = {
|
|
11281
11239
|
contextType: storedContext.contextType,
|
|
11282
11240
|
organizationId: storedContext.organizationId,
|
|
@@ -11305,7 +11263,7 @@ function registerContextCommand(program2) {
|
|
|
11305
11263
|
}
|
|
11306
11264
|
const key = contextKey(resolved.contextType, resolved.organizationId);
|
|
11307
11265
|
const existing = profile.contexts?.[key];
|
|
11308
|
-
const newCredential = await exchangeForContext(
|
|
11266
|
+
const newCredential = await exchangeForContext(cred, serviceUrls, {
|
|
11309
11267
|
type: resolved.contextType,
|
|
11310
11268
|
id: resolved.organizationId
|
|
11311
11269
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@topolo/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Command-line interface for Topolo developers, operators, and agents, with internal first-party scaffolding available under the topolo platform namespace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/skills/topolo/SKILL.md
CHANGED
|
@@ -48,6 +48,15 @@ topolo context use <organization-name|personal> --json
|
|
|
48
48
|
topolo whoami --json
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
For concurrent agents or multiple MCP connections, never coordinate by changing
|
|
52
|
+
one shared active context. Register one named `topolo-mcp` process per context
|
|
53
|
+
with both `TOPOLO_ENV` and `TOPOLO_CONTEXT`. The context selector accepts an
|
|
54
|
+
exact key returned by `topolo context ls --json` (such as `personal` or
|
|
55
|
+
`org:<orgId>`) or a unique stored organization id, slug, or name. Each process
|
|
56
|
+
reads and refreshes only its pinned credential without changing the CLI or web
|
|
57
|
+
session. Do not set `TOPOLO_CONTEXT` alongside an injected API key or access
|
|
58
|
+
token because that credential already carries its immutable context.
|
|
59
|
+
|
|
51
60
|
## Mandatory clean-room workflow
|
|
52
61
|
|
|
53
62
|
For every Topolo task, follow this sequence.
|
|
@@ -114,6 +123,10 @@ topolo actions call <read-action-id> \
|
|
|
114
123
|
|
|
115
124
|
Provide `--resource-type` and `--resource-id` together. MCP read actions use `topolo_read_action` with the same separate resource fields.
|
|
116
125
|
|
|
126
|
+
For API-key creation or updates, do not use the application's general resource list as the authorization decision. Discover and call Auth's `api_keys.options` action with the selected application and active organization. Its response is the complete caller-specific contract for that key operation: organization-wide scopes, deduplicated resource types, authorized resources, each resource's access level and allowed scopes, and whether key creation is allowed.
|
|
127
|
+
|
|
128
|
+
Choose resource bindings before scopes. With no bindings, use only `organizationWideScopes`. With one or more bindings, use only the intersection of `allowedScopes` across every selected resource. Then validate and plan `api_keys.create` or `api_keys.update` with that exact selection. Never widen, merge, or infer eligibility from application metadata, organization ownership alone, or a resource returned in another organization context.
|
|
129
|
+
|
|
117
130
|
### 4. Validate locally
|
|
118
131
|
|
|
119
132
|
Before every call, validate exact input against the published JSON Schema:
|