@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.
Files changed (3) hide show
  1. package/README.md +25 -5
  2. package/dist/index.js +93 -11
  3. 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
- ## Phase 1 scope
7
+ ## Current scope
8
8
 
9
9
  - API-key authentication (long-lived, service-scoped).
10
- - Access-token authentication (short-lived, cross-service — used for dev/testing).
11
- - Commands: `auth login/logout/status`, `whoami`, `crm contacts:list`, `crm contacts:get`, generic `api` passthrough.
12
- - OAuth2 authorization-code + device flows will arrive in Phase 2; until then, `auth login` expects a paste-in API key minted at the Topolo Developers console.
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 # prompts for API key
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 DEFAULT_SERVICE_URLS = {
67
+ var SERVICES = {
68
+ agent: "https://topolo-agent-worker.topolo.workers.dev",
68
69
  auth: "https://auth.topolo.app",
69
- crm: "https://topolo-crm-worker.topolo.workers.dev"
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.4";
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 = ["auth", "crm"];
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("Generic passthrough to a Topolo service (escape hatch for untyped endpoints)").option("-d, --data <json>", "JSON body (for POST/PUT/PATCH)").option("--confirm", "Acknowledge a mutating request").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
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 (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(upperMethod)) {
897
- throw new Error(`Unsupported method: ${method}`);
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(upperMethod) && !opts.confirm) {
905
- throw new Error(`Mutating ${upperMethod} requires --confirm.`);
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: upperMethod,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Command-line interface for the Topolo platform. Designed for humans and for agents (Claude, Codex, etc.) that prefer shell invocation.",
5
5
  "type": "module",
6
6
  "bin": {