@topolo/cli 0.1.5 → 0.1.7
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 +25 -5
- package/dist/index.js +93 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,12 +4,12 @@ Command-line interface for the Topolo platform. Designed for both humans and
|
|
|
4
4
|
third-party agents (Claude Code, Codex, Cursor, etc.) that prefer to interact
|
|
5
5
|
via `stdin`/`stdout`.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Current scope
|
|
8
8
|
|
|
9
9
|
- API-key authentication (long-lived, service-scoped).
|
|
10
|
-
- Access-token authentication
|
|
11
|
-
-
|
|
12
|
-
-
|
|
10
|
+
- Access-token authentication via OAuth device flow when a CLI OAuth client id is configured.
|
|
11
|
+
- Stored access-token refresh when the config includes a refresh token and OAuth client id.
|
|
12
|
+
- Commands: `auth login/logout/status`, `whoami`, `services`, `crm contacts:list`, `crm contacts:get`, and generic `api` passthrough for SDK-registered services.
|
|
13
13
|
|
|
14
14
|
## Install
|
|
15
15
|
|
|
@@ -30,11 +30,16 @@ Credentials come from (in priority order):
|
|
|
30
30
|
For interactive use:
|
|
31
31
|
|
|
32
32
|
```bash
|
|
33
|
-
topolo auth login
|
|
33
|
+
topolo auth login --client-id doac_... # device flow
|
|
34
|
+
topolo auth login --api-key topo_live_... # store API key directly
|
|
34
35
|
topolo auth status # shows which source is active
|
|
35
36
|
topolo auth logout # clears the stored credential
|
|
36
37
|
```
|
|
37
38
|
|
|
39
|
+
If no client id is configured, `topolo auth login` falls back to an API-key
|
|
40
|
+
prompt in an interactive terminal. Non-interactive sessions should pass
|
|
41
|
+
`--api-key` or set `TOPOLO_CLI_CLIENT_ID`.
|
|
42
|
+
|
|
38
43
|
## Cross-org isolation
|
|
39
44
|
|
|
40
45
|
The CLI **never accepts an `orgId` argument**. Every command is scoped to the
|
|
@@ -89,6 +94,14 @@ topolo crm contacts:list -q "acme" -p 2 -s 50 --json
|
|
|
89
94
|
topolo crm contacts:get contact_abc123
|
|
90
95
|
```
|
|
91
96
|
|
|
97
|
+
### `topolo services`
|
|
98
|
+
|
|
99
|
+
Lists every service id and effective URL known to the generated SDK registry.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
topolo services --json
|
|
103
|
+
```
|
|
104
|
+
|
|
92
105
|
### `topolo api <service> <method> <path>`
|
|
93
106
|
|
|
94
107
|
Escape hatch for endpoints without typed modules yet.
|
|
@@ -98,6 +111,9 @@ topolo api auth GET /api/me
|
|
|
98
111
|
topolo api crm POST /api/contacts --data '{"first":"Jane"}' --confirm
|
|
99
112
|
```
|
|
100
113
|
|
|
114
|
+
Use `topolo services --json` to discover valid service ids before calling a
|
|
115
|
+
service that does not have a typed command yet.
|
|
116
|
+
|
|
101
117
|
## Output modes
|
|
102
118
|
|
|
103
119
|
- Pretty when stdout is a TTY.
|
|
@@ -137,6 +153,10 @@ Both docs cover the same CLI surface: credential handling, the JSON invocation
|
|
|
137
153
|
contract, the write-safety `--confirm` guardrail, and the cross-org isolation
|
|
138
154
|
rules. Keep them in sync when adding new commands.
|
|
139
155
|
|
|
156
|
+
For agents that support MCP, also register `@topolo/mcp`; the skill/AGENTS
|
|
157
|
+
guidance still matters because it tells the agent when to prefer MCP tools,
|
|
158
|
+
when to fall back to shell commands, and how to handle writes.
|
|
159
|
+
|
|
140
160
|
## Debug output
|
|
141
161
|
|
|
142
162
|
Set `TOPOLO_DEBUG=1` to trace every SDK request to stderr (stdout stays clean
|
package/dist/index.js
CHANGED
|
@@ -64,17 +64,38 @@ var TopoloHttpError = class extends TopoloSdkError {
|
|
|
64
64
|
this.path = path;
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
|
-
var
|
|
67
|
+
var SERVICES = {
|
|
68
|
+
agent: "https://topolo-agent-worker.topolo.workers.dev",
|
|
68
69
|
auth: "https://auth.topolo.app",
|
|
69
|
-
|
|
70
|
+
bugfix: "https://bugfix.topolo.app",
|
|
71
|
+
bytes: "https://r2-media-manager-worker.topolo.workers.dev",
|
|
72
|
+
chat: "https://chat.topolo.app",
|
|
73
|
+
commerce: "https://topolo-commerce-api.topolo.workers.dev",
|
|
74
|
+
crm: "https://crm-api.topolo.app",
|
|
75
|
+
forecast: "https://forecast-worker-production.topolo.workers.dev",
|
|
76
|
+
learn: "https://topololearn-api.topolo.workers.dev",
|
|
77
|
+
mdm: "https://nodo-mdm-api.topolo.app",
|
|
78
|
+
messaging: "https://messages.topolo.app",
|
|
79
|
+
nexus: "https://topolo-nexus-gateway.topolo.workers.dev",
|
|
80
|
+
one: "https://topoloone-worker.topolo.workers.dev",
|
|
81
|
+
pay: "https://topolo-pay.topolo.workers.dev",
|
|
82
|
+
"quro.api": "https://api.ol0.me",
|
|
83
|
+
"quro.redirect": "https://ol0.me",
|
|
84
|
+
roadmapper: "https://roadmapper-api.topolo.app",
|
|
85
|
+
social_studio: "https://topolo-social-studio-api.topolo.workers.dev",
|
|
86
|
+
socialize: "https://socialize-prod-api.topolo.workers.dev",
|
|
87
|
+
support: "https://support.topolo.app",
|
|
88
|
+
"web.api": "https://web-api.topolo.app",
|
|
89
|
+
"web.runtime": "https://sites.topolo.app"
|
|
70
90
|
};
|
|
71
91
|
var PLATFORM_SERVICE_IDS = {
|
|
72
92
|
crm: "srv_iCwM4jGXcwlj"
|
|
73
93
|
};
|
|
94
|
+
var DEFAULT_SERVICE_URLS = SERVICES;
|
|
74
95
|
function resolveServiceUrl(service, overrides) {
|
|
75
96
|
const override = overrides?.[service];
|
|
76
97
|
if (override) return override;
|
|
77
|
-
const envKey = `TOPOLO_SERVICE_URL_${service.toUpperCase()}`;
|
|
98
|
+
const envKey = `TOPOLO_SERVICE_URL_${service.replace(/\./g, "_").toUpperCase()}`;
|
|
78
99
|
const envValue = typeof process !== "undefined" ? process.env?.[envKey] : void 0;
|
|
79
100
|
if (envValue) return envValue;
|
|
80
101
|
return DEFAULT_SERVICE_URLS[service];
|
|
@@ -744,7 +765,7 @@ function sleep(ms) {
|
|
|
744
765
|
}
|
|
745
766
|
|
|
746
767
|
// src/version.ts
|
|
747
|
-
var CLI_VERSION = "0.1.
|
|
768
|
+
var CLI_VERSION = "0.1.7";
|
|
748
769
|
|
|
749
770
|
// src/client.ts
|
|
750
771
|
async function buildClient(options) {
|
|
@@ -885,24 +906,33 @@ function formatContact(c) {
|
|
|
885
906
|
}
|
|
886
907
|
|
|
887
908
|
// src/commands/api.ts
|
|
888
|
-
var KNOWN_SERVICES =
|
|
909
|
+
var KNOWN_SERVICES = Object.keys(DEFAULT_SERVICE_URLS).sort();
|
|
910
|
+
var HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
889
911
|
var WRITE_METHODS2 = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
890
912
|
function registerApiCommand(program2) {
|
|
891
|
-
program2.command("api <service> <method> <path>").description(
|
|
913
|
+
program2.command("api <service> <method> <path>").description(
|
|
914
|
+
`Generic passthrough to a Topolo service. Known services: ${KNOWN_SERVICES.join(", ")}.`
|
|
915
|
+
).option("-d, --data <json>", "JSON body (for POST/PUT/PATCH)").option(
|
|
916
|
+
"-q, --query <kv...>",
|
|
917
|
+
"Query params as key=value (repeatable), e.g. --query source=cli page=2"
|
|
918
|
+
).option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
|
|
892
919
|
async (service, method, path, opts) => {
|
|
893
920
|
const mode = resolveOutputMode(opts.json);
|
|
894
921
|
try {
|
|
895
922
|
const upperMethod = method.toUpperCase();
|
|
896
|
-
if (!
|
|
897
|
-
throw new Error(
|
|
923
|
+
if (!HTTP_METHODS.includes(upperMethod)) {
|
|
924
|
+
throw new Error(
|
|
925
|
+
`Unsupported method "${method}". Known: ${HTTP_METHODS.join(", ")}`
|
|
926
|
+
);
|
|
898
927
|
}
|
|
928
|
+
const httpMethod = upperMethod;
|
|
899
929
|
if (!KNOWN_SERVICES.includes(service)) {
|
|
900
930
|
throw new Error(
|
|
901
931
|
`Unknown service "${service}". Known services: ${KNOWN_SERVICES.join(", ")}`
|
|
902
932
|
);
|
|
903
933
|
}
|
|
904
|
-
if (WRITE_METHODS2.has(
|
|
905
|
-
throw new Error(`Mutating ${
|
|
934
|
+
if (WRITE_METHODS2.has(httpMethod) && !opts.confirm) {
|
|
935
|
+
throw new Error(`Mutating ${httpMethod} requires --confirm.`);
|
|
906
936
|
}
|
|
907
937
|
let body = void 0;
|
|
908
938
|
if (opts.data) {
|
|
@@ -912,6 +942,7 @@ function registerApiCommand(program2) {
|
|
|
912
942
|
throw new Error("--data must be valid JSON");
|
|
913
943
|
}
|
|
914
944
|
}
|
|
945
|
+
const query = parseQueryPairs(opts.query);
|
|
915
946
|
const topolo = await buildClient();
|
|
916
947
|
if (!topolo) {
|
|
917
948
|
throw new Error(
|
|
@@ -920,9 +951,10 @@ function registerApiCommand(program2) {
|
|
|
920
951
|
}
|
|
921
952
|
const result = await topolo.client.request({
|
|
922
953
|
service,
|
|
923
|
-
method:
|
|
954
|
+
method: httpMethod,
|
|
924
955
|
path,
|
|
925
956
|
body,
|
|
957
|
+
...query ? { query } : {},
|
|
926
958
|
confirm: opts.confirm === true
|
|
927
959
|
});
|
|
928
960
|
emit(result, mode, () => JSON.stringify(result, null, 2));
|
|
@@ -932,6 +964,55 @@ function registerApiCommand(program2) {
|
|
|
932
964
|
}
|
|
933
965
|
);
|
|
934
966
|
}
|
|
967
|
+
function parseQueryPairs(pairs) {
|
|
968
|
+
if (!pairs || pairs.length === 0) return void 0;
|
|
969
|
+
const out = {};
|
|
970
|
+
for (const pair of pairs) {
|
|
971
|
+
const eq = pair.indexOf("=");
|
|
972
|
+
if (eq <= 0) {
|
|
973
|
+
throw new Error(`Invalid --query entry "${pair}". Expected key=value.`);
|
|
974
|
+
}
|
|
975
|
+
const key = pair.slice(0, eq);
|
|
976
|
+
const value = pair.slice(eq + 1);
|
|
977
|
+
out[key] = value;
|
|
978
|
+
}
|
|
979
|
+
return out;
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// src/commands/services.ts
|
|
983
|
+
function registerServicesCommand(program2) {
|
|
984
|
+
program2.command("services").description("List every Topolo service the SDK can route to").option("--json", "Output JSON", true).option("--no-json", "Output human-readable table").action((opts) => {
|
|
985
|
+
const mode = resolveOutputMode(opts.json);
|
|
986
|
+
try {
|
|
987
|
+
const rows = Object.keys(DEFAULT_SERVICE_URLS).sort().map((id) => {
|
|
988
|
+
const defaultUrl = DEFAULT_SERVICE_URLS[id];
|
|
989
|
+
const effectiveUrl = resolveServiceUrl(id);
|
|
990
|
+
return {
|
|
991
|
+
id,
|
|
992
|
+
defaultUrl,
|
|
993
|
+
effectiveUrl,
|
|
994
|
+
overridden: defaultUrl !== effectiveUrl
|
|
995
|
+
};
|
|
996
|
+
});
|
|
997
|
+
emit({ services: rows }, mode, () => formatRows(rows));
|
|
998
|
+
} catch (err) {
|
|
999
|
+
fail(err, mode);
|
|
1000
|
+
}
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
function formatRows(rows) {
|
|
1004
|
+
const idWidth = Math.max(3, ...rows.map((r) => r.id.length));
|
|
1005
|
+
const urlWidth = Math.max(7, ...rows.map((r) => r.effectiveUrl.length));
|
|
1006
|
+
const lines = [];
|
|
1007
|
+
lines.push(`${"ID".padEnd(idWidth)} ${"URL".padEnd(urlWidth)} OVERRIDE`);
|
|
1008
|
+
lines.push(`${"-".repeat(idWidth)} ${"-".repeat(urlWidth)} --------`);
|
|
1009
|
+
for (const r of rows) {
|
|
1010
|
+
lines.push(
|
|
1011
|
+
`${r.id.padEnd(idWidth)} ${r.effectiveUrl.padEnd(urlWidth)} ${r.overridden ? "yes" : ""}`
|
|
1012
|
+
);
|
|
1013
|
+
}
|
|
1014
|
+
return lines.join("\n");
|
|
1015
|
+
}
|
|
935
1016
|
|
|
936
1017
|
// src/index.ts
|
|
937
1018
|
var program = new Command();
|
|
@@ -942,6 +1023,7 @@ registerAuthCommands(program);
|
|
|
942
1023
|
registerWhoamiCommand(program);
|
|
943
1024
|
registerCrmCommands(program);
|
|
944
1025
|
registerApiCommand(program);
|
|
1026
|
+
registerServicesCommand(program);
|
|
945
1027
|
program.parseAsync(process.argv).catch((err) => {
|
|
946
1028
|
process.stderr.write(`Unexpected error: ${err instanceof Error ? err.message : String(err)}
|
|
947
1029
|
`);
|
package/package.json
CHANGED