@stablebaseline/cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Orixian Solutions Pty Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # @stablebaseline/cli — `sb`
2
+
3
+ [![npm](https://img.shields.io/npm/v/@stablebaseline/cli?color=orange)](https://www.npmjs.com/package/@stablebaseline/cli)
4
+
5
+ Command-line client for **[Stable Baseline](https://stablebaseline.io)** — the simplest, most complete, end-to-end agent-managed company brain. Drive 163 MCP tools (docs, diagrams, plans, Knowledge Graph, members, billing, settings) from your terminal.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g @stablebaseline/cli
11
+ ```
12
+
13
+ Or run without installing:
14
+
15
+ ```bash
16
+ npx @stablebaseline/cli auth login --api-key sta_...
17
+ ```
18
+
19
+ ## First-time setup
20
+
21
+ ```bash
22
+ # Mint a key at app.stablebaseline.io/settings/mcp-keys, then:
23
+ sb auth login --api-key sta_xxx
24
+
25
+ sb auth whoami
26
+ # ✓ Vineet Nair
27
+ # email: vineet@example.com
28
+ # user_id: 8d8b55ea-...
29
+ # source: file: ~/.config/stablebaseline/auth.json
30
+ ```
31
+
32
+ OAuth-from-the-CLI (PKCE loopback) is on the roadmap (CLI v0.2). For non-interactive use, the API-key path is the cleanest today.
33
+
34
+ ## Usage
35
+
36
+ ```bash
37
+ # Discover tools
38
+ sb tool list # all categories grouped
39
+ sb tool list --category=documents # filter by category
40
+ sb tool search "schedule a task" # natural-language search
41
+
42
+ # Call any tool (163 of them)
43
+ sb tool call listOrganisations
44
+ sb tool call createDocument --json '{"folderId":"<uuid>","title":"X","cdmd":"# Hi"}'
45
+ sb tool call kg_search --json '{"query":"compliance posture","mode":"global"}' --pretty
46
+
47
+ # JSON output is the default — pipe into jq for filtering
48
+ sb tool call listProjects --json '{"workspaceId":"<uuid>"}' | jq '.[].name'
49
+
50
+ # Use a JSON file for big inputs
51
+ sb tool call insertDiagramInDocument --json-file diagram.json
52
+ ```
53
+
54
+ ## Auth
55
+
56
+ | Source | How |
57
+ |---|---|
58
+ | **API key** (recommended for scripts/CI) | `sb auth login --api-key sta_...` writes to `~/.config/stablebaseline/auth.json` (0600 perms). |
59
+ | **Env override** | `SB_API_KEY=sta_...` or `SB_ACCESS_TOKEN=...` — wins over the on-disk credential. Useful for CI. |
60
+ | **OAuth (browser)** | `sb auth login` opens the browser. Full PKCE-loopback in CLI v0.2; for now, paste the access token via `SB_ACCESS_TOKEN`. |
61
+
62
+ Forget the credential:
63
+
64
+ ```bash
65
+ sb auth logout
66
+ ```
67
+
68
+ ## Output formats
69
+
70
+ By default `sb tool call` prints compact JSON to stdout (machine-friendly). Use `--pretty` for indented JSON, or pipe to `jq`/`fx` for further processing.
71
+
72
+ `sb tool list` prints a grouped human-readable table by default; pass `--json` for raw JSON.
73
+
74
+ ## What this CLI is (and isn't)
75
+
76
+ It's a **thin formatter on top of the [Stable Baseline REST API](https://api.stablebaseline.io/functions/v1/cloud-serve/api/v1/docs)** — every command maps to a single REST call against the same handlers the MCP server uses. There is no business logic in this package; if a feature works on the MCP server, it works here on the same day, no CLI release needed.
77
+
78
+ ## Companion packages
79
+
80
+ | Surface | Package | Use case |
81
+ |---|---|---|
82
+ | **CLI** (this) | `@stablebaseline/cli` (binary `sb`) | Shells, scripts, CI/CD |
83
+ | **TypeScript SDK** | `@stablebaseline/sdk` | Node, browsers, Deno, Bun |
84
+ | **Python SDK** | `stablebaseline` (PyPI) | Python apps, data work |
85
+ | **MCP server** | `https://api.stablebaseline.io/functions/v1/cloud-serve/mcp` | AI agents (Claude Code, Cursor, Windsurf, ChatGPT, Gemini, …) |
86
+
87
+ ## License
88
+
89
+ MIT — see [LICENSE](../../LICENSE) at the repo root. The Stable Baseline product itself is proprietary SaaS at [stablebaseline.io](https://stablebaseline.io).
package/dist/sb.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/sb.js ADDED
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/bin/sb.ts
4
+ import { Command } from "commander";
5
+
6
+ // src/commands/auth.ts
7
+ import kleur from "kleur";
8
+ import open from "open";
9
+
10
+ // src/config.ts
11
+ import { readFile, writeFile, mkdir, chmod, unlink } from "fs/promises";
12
+ import { existsSync } from "fs";
13
+ import path from "path";
14
+ import os from "os";
15
+ function configDir() {
16
+ if (process.platform === "win32") {
17
+ const appData = process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming");
18
+ return path.join(appData, "stablebaseline");
19
+ }
20
+ const xdg = process.env.XDG_CONFIG_HOME;
21
+ return path.join(xdg ?? path.join(os.homedir(), ".config"), "stablebaseline");
22
+ }
23
+ function authFile() {
24
+ return path.join(configDir(), "auth.json");
25
+ }
26
+ async function readCredential() {
27
+ const file = authFile();
28
+ if (!existsSync(file)) return null;
29
+ try {
30
+ const raw = await readFile(file, "utf8");
31
+ return JSON.parse(raw);
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function writeCredential(cred) {
37
+ const dir = configDir();
38
+ if (!existsSync(dir)) {
39
+ await mkdir(dir, { recursive: true });
40
+ }
41
+ const file = authFile();
42
+ await writeFile(file, JSON.stringify(cred, null, 2) + "\n", "utf8");
43
+ if (process.platform !== "win32") {
44
+ try {
45
+ await chmod(file, 384);
46
+ } catch {
47
+ }
48
+ }
49
+ return file;
50
+ }
51
+ async function clearCredential() {
52
+ const file = authFile();
53
+ if (!existsSync(file)) return false;
54
+ await unlink(file);
55
+ return true;
56
+ }
57
+ function getStoredCredentialPath() {
58
+ return authFile();
59
+ }
60
+ var DEFAULT_BASE_URL = process.env.SB_API_BASE_URL || "https://api.stablebaseline.io/functions/v1/cloud-serve/api/v1";
61
+
62
+ // src/client.ts
63
+ import { StableBaseline } from "@stablebaseline/sdk";
64
+ async function makeSdkClient() {
65
+ const envKey = process.env.SB_API_KEY;
66
+ const envToken = process.env.SB_ACCESS_TOKEN;
67
+ if (envKey) {
68
+ return new StableBaseline({ apiKey: envKey, baseUrl: DEFAULT_BASE_URL });
69
+ }
70
+ if (envToken) {
71
+ return new StableBaseline({ accessToken: envToken, baseUrl: DEFAULT_BASE_URL });
72
+ }
73
+ const cred = await readCredential();
74
+ if (!cred) {
75
+ throw new NotAuthenticatedError();
76
+ }
77
+ if (cred.type === "api_key") {
78
+ return new StableBaseline({ apiKey: cred.value, baseUrl: DEFAULT_BASE_URL });
79
+ }
80
+ return new StableBaseline({ accessToken: cred.value, baseUrl: DEFAULT_BASE_URL });
81
+ }
82
+ var NotAuthenticatedError = class extends Error {
83
+ constructor() {
84
+ super(
85
+ "Not authenticated. Run `sb auth login --api-key sta_...` (or `sb auth login` for OAuth via browser)."
86
+ );
87
+ this.name = "NotAuthenticatedError";
88
+ }
89
+ };
90
+
91
+ // src/commands/auth.ts
92
+ var OAUTH_AUTHORIZE_URL = "https://app.stablebaseline.io/oauth/authorize";
93
+ var KEYS_PAGE = "https://app.stablebaseline.io/settings/mcp-keys";
94
+ function registerAuthCommands(program) {
95
+ const auth = program.command("auth").description("Sign in / out and inspect the active credential.");
96
+ auth.command("login").description("Sign in. Default uses OAuth in the browser; pass --api-key to skip OAuth.").option("--api-key <key>", "Use an API key (sta_*) directly. Mint at " + KEYS_PAGE).action(async (opts) => {
97
+ if (opts.apiKey) {
98
+ if (!opts.apiKey.startsWith("sta_")) {
99
+ console.error(kleur.red("\u2717 ") + "API key must start with `sta_`.");
100
+ console.error(" Mint a key at " + kleur.cyan(KEYS_PAGE) + ".");
101
+ process.exitCode = 1;
102
+ return;
103
+ }
104
+ const path2 = await writeCredential({ type: "api_key", value: opts.apiKey });
105
+ console.log(kleur.green("\u2713 ") + `Saved API key to ${kleur.dim(path2)}`);
106
+ return;
107
+ }
108
+ console.log(kleur.bold("Sign in to Stable Baseline"));
109
+ console.log(" " + kleur.cyan(OAUTH_AUTHORIZE_URL));
110
+ console.log("");
111
+ console.log(" Or run " + kleur.bold("sb auth login --api-key sta_...") + " for a non-interactive flow.");
112
+ console.log(" Mint an API key at " + kleur.cyan(KEYS_PAGE) + ".");
113
+ console.log("");
114
+ try {
115
+ await open(OAUTH_AUTHORIZE_URL);
116
+ } catch {
117
+ }
118
+ console.log(kleur.yellow("Note") + ": full PKCE-loopback OAuth from the CLI is on the roadmap (CLI v0.2). For now, prefer --api-key.");
119
+ });
120
+ auth.command("logout").description("Forget the stored credential.").action(async () => {
121
+ const removed = await clearCredential();
122
+ if (removed) {
123
+ console.log(kleur.green("\u2713 ") + "Logged out.");
124
+ } else {
125
+ console.log(kleur.dim("Already logged out."));
126
+ }
127
+ });
128
+ auth.command("whoami").description("Show the current credential (truncated) and the user it's bound to.").action(async () => {
129
+ const cred = await readCredential();
130
+ if (!cred && !process.env.SB_API_KEY && !process.env.SB_ACCESS_TOKEN) {
131
+ console.log(kleur.dim("Not authenticated."));
132
+ console.log(kleur.dim(" Run `sb auth login` (or `sb auth login --api-key sta_...`)."));
133
+ process.exitCode = 1;
134
+ return;
135
+ }
136
+ const sdk = await makeSdkClient();
137
+ try {
138
+ const user = await sdk.callTool("getCurrentUser", {});
139
+ const source = process.env.SB_API_KEY ? "env: SB_API_KEY" : process.env.SB_ACCESS_TOKEN ? "env: SB_ACCESS_TOKEN" : `file: ${getStoredCredentialPath()}`;
140
+ console.log(kleur.green("\u2713 ") + (user.display_name || user.full_name || user.user_id));
141
+ if (user.email) console.log(" " + kleur.dim("email: ") + user.email);
142
+ console.log(" " + kleur.dim("user_id: ") + user.user_id);
143
+ console.log(" " + kleur.dim("source: ") + source);
144
+ } catch (err) {
145
+ const e = err;
146
+ console.error(kleur.red("\u2717 ") + (e.message || String(err)));
147
+ process.exitCode = 1;
148
+ }
149
+ });
150
+ }
151
+
152
+ // src/commands/tool.ts
153
+ import kleur2 from "kleur";
154
+ import { readFile as readFile2 } from "fs/promises";
155
+ function registerToolCommands(program) {
156
+ const tool = program.command("tool").description("Direct tool dispatch \u2014 call any of the 163 MCP tools.");
157
+ tool.command("list").description("List all tools (catalogue summary).").option("--category <category>", "Filter by category (e.g. documents, plans, knowledge_graph)").option("--json", "Output JSON instead of a table").action(async (opts) => {
158
+ const sdk = await makeSdkClient();
159
+ const { tools } = await sdk.listTools();
160
+ const filtered = opts.category ? tools.filter((t) => t.category === opts.category) : tools;
161
+ if (opts.json) {
162
+ console.log(JSON.stringify(filtered, null, 2));
163
+ return;
164
+ }
165
+ const byCategory = /* @__PURE__ */ new Map();
166
+ for (const t of filtered) {
167
+ const arr = byCategory.get(t.category) ?? [];
168
+ arr.push(t);
169
+ byCategory.set(t.category, arr);
170
+ }
171
+ const cats = [...byCategory.keys()].sort();
172
+ for (const cat of cats) {
173
+ console.log(kleur2.bold(kleur2.cyan(cat)) + kleur2.dim(` (${byCategory.get(cat).length})`));
174
+ for (const t of byCategory.get(cat)) {
175
+ const desc = t.description.slice(0, 80) + (t.description.length > 80 ? "\u2026" : "");
176
+ console.log(` ${kleur2.green(t.name)} ${kleur2.dim("\u2014 " + desc)}`);
177
+ }
178
+ console.log("");
179
+ }
180
+ console.log(kleur2.dim(`${filtered.length} tools`));
181
+ });
182
+ tool.command("call <name>").description("Call a tool by name. Provide input via --json '{...}' or --json-file path.").option("--json <json>", "Inline JSON input").option("--json-file <path>", "Path to a file containing JSON input").option("--pretty", "Pretty-print the response").action(async (name, opts) => {
183
+ let input = {};
184
+ if (opts.json && opts.jsonFile) {
185
+ console.error(kleur2.red("\u2717 ") + "Pass either --json or --json-file, not both.");
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ if (opts.jsonFile) {
190
+ const raw = await readFile2(opts.jsonFile, "utf8");
191
+ input = JSON.parse(raw);
192
+ } else if (opts.json) {
193
+ input = JSON.parse(opts.json);
194
+ }
195
+ const sdk = await makeSdkClient();
196
+ try {
197
+ const result = await sdk.callTool(name, input);
198
+ const out = opts.pretty ? JSON.stringify(result, null, 2) : JSON.stringify(result);
199
+ console.log(out);
200
+ } catch (err) {
201
+ const e = err;
202
+ console.error(kleur2.red(`\u2717 ${e.code ?? "error"}: `) + (e.message ?? String(err)));
203
+ if (e.status) console.error(kleur2.dim(` HTTP ${e.status}`));
204
+ process.exitCode = 1;
205
+ }
206
+ });
207
+ tool.command("search <query>").description("Search the tool catalogue (passthrough to the searchTools meta-tool).").action(async (query) => {
208
+ const sdk = await makeSdkClient();
209
+ try {
210
+ const result = await sdk.callTool("searchTools", { query });
211
+ console.log(JSON.stringify(result, null, 2));
212
+ } catch (err) {
213
+ console.error(kleur2.red("\u2717 ") + (err.message ?? String(err)));
214
+ process.exitCode = 1;
215
+ }
216
+ });
217
+ }
218
+
219
+ // src/bin/sb.ts
220
+ async function getVersion() {
221
+ try {
222
+ const url = new URL("../package.json", import.meta.url);
223
+ const { readFile: readFile3 } = await import("fs/promises");
224
+ const text = await readFile3(url, "utf8");
225
+ return JSON.parse(text).version ?? "0.0.0";
226
+ } catch {
227
+ return "0.0.0";
228
+ }
229
+ }
230
+ async function main() {
231
+ const program = new Command();
232
+ program.name("sb").description(
233
+ "Stable Baseline CLI \u2014 end-to-end agent-managed company brain. Docs, diagrams, plans, and a self-learning Knowledge Graph from your terminal."
234
+ ).version(await getVersion(), "-v, --version", "Print version and exit");
235
+ registerAuthCommands(program);
236
+ registerToolCommands(program);
237
+ program.addHelpText(
238
+ "after",
239
+ `
240
+ Examples:
241
+ $ sb auth login --api-key sta_xxx
242
+ $ sb auth whoami
243
+ $ sb tool list --category=documents
244
+ $ sb tool call listOrganisations
245
+ $ sb tool call createDocument --json '{"folderId":"...","title":"X","cdmd":"# Hi"}'
246
+ $ sb tool search "build a plan"
247
+
248
+ Docs: https://stablebaseline.io/docs/mcp
249
+ Issues: https://github.com/stablebaseline/mcp/issues
250
+ `
251
+ );
252
+ await program.parseAsync(process.argv);
253
+ }
254
+ main().catch((err) => {
255
+ console.error(err instanceof Error ? err.message : err);
256
+ process.exit(1);
257
+ });
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@stablebaseline/cli",
3
+ "version": "0.1.0",
4
+ "description": "sb — command-line client for Stable Baseline. End-to-end agent-managed company brain (docs, diagrams, plans, Knowledge Graph). 163 tools across 16 categories, all exposed at the CLI.",
5
+ "homepage": "https://stablebaseline.io",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/stablebaseline/mcp.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "bugs": "https://github.com/stablebaseline/mcp/issues",
12
+ "license": "MIT",
13
+ "keywords": [
14
+ "stablebaseline",
15
+ "stable-baseline",
16
+ "sb",
17
+ "cli",
18
+ "mcp",
19
+ "model-context-protocol",
20
+ "knowledge-graph",
21
+ "company-brain",
22
+ "documentation",
23
+ "diagrams",
24
+ "plans",
25
+ "ai-agent"
26
+ ],
27
+ "type": "module",
28
+ "bin": {
29
+ "sb": "./dist/sb.js"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup src/bin/sb.ts --format esm --dts --clean --shims",
38
+ "dev": "tsx src/bin/sb.ts",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "dependencies": {
42
+ "@stablebaseline/sdk": "^0.1.0",
43
+ "commander": "^12.1.0",
44
+ "kleur": "^4.1.5",
45
+ "open": "^10.1.0"
46
+ },
47
+ "devDependencies": {
48
+ "tsup": "^8.3.5",
49
+ "tsx": "^4.19.2",
50
+ "typescript": "^5.6.3",
51
+ "@types/node": "^22.10.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=20"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ }
59
+ }