@remcp/docsy-cli 0.0.0-stage → 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 Anton Baider
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 CHANGED
@@ -1,3 +1,25 @@
1
- # Temporary Holding Version
1
+ # @remcp/docsy-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Command-line client for the Docsy public documentation API and MCP setup.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install --global @remcp/docsy-cli
9
+ ```
10
+
11
+ ## Use
12
+
13
+ ```sh
14
+ docsy library react
15
+ docsy search "effect cleanup" --library react
16
+ docsy docs /reactjs/react.dev "useEffect cleanup"
17
+ docsy setup --claude
18
+ docsy mcp
19
+ ```
20
+
21
+ For one-off use, run `npx @remcp/docsy-cli --help`.
22
+
23
+ The `@remcp/docsy-cli` package depends on the same-version `@remcp/docsy-sdk` package; the SDK must be published first. Anonymous queries are limited to public documentation. MIT License, copyright Anton Baider.
24
+
25
+ Documentation: https://docsy.remcp.site/docs
package/dist/index.js ADDED
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ import { Docsy } from "@remcp/docsy-sdk";
3
+ import { setup } from "./setup.js";
4
+ const args = process.argv.slice(2);
5
+ const command = args.shift();
6
+ const value = (flag) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
7
+ const values = (flag) => args.flatMap((item, index) => item === flag && args[index + 1] ? [args[index + 1]] : []);
8
+ const has = (flag) => args.includes(flag);
9
+ const baseUrl = value("--base-url") ?? process.env.DOCSY_BASE_URL ?? "https://docsy.remcp.site";
10
+ const apiKey = value("--api-key") ?? process.env.DOCSY_API_KEY;
11
+ const client = new Docsy({ baseUrl, apiKey, headers: { "x-docsy-client": "cli" } });
12
+ function help() {
13
+ console.log([
14
+ "Docsy — current documentation for AI coding agents",
15
+ "",
16
+ "Usage:",
17
+ " docsy library <name> [query] [--json]",
18
+ " docsy docs <libraryId> <query> [--fast] [--json]",
19
+ " docsy search <query> [--library NAME] [--version VERSION] [--language LANGUAGE] [--json]",
20
+ " docsy refresh <libraryId> [--branch BRANCH]",
21
+ " docsy metrics <libraryId> [--days N]",
22
+ " docsy policies [--patch-json JSON]",
23
+ " docsy add github <repositoryUrl>",
24
+ " docsy add gitlab <repositoryUrl>",
25
+ " docsy add bitbucket <repositoryUrl>",
26
+ " docsy add git <repositoryUrl>",
27
+ " docsy add website <url> [--base-url-scope URL]",
28
+ " docsy add llmstxt <url>",
29
+ " docsy add openapi <url>",
30
+ " docsy setup --cursor|--claude|--codex|--vscode|--gemini|--opencode|--copilot [--project] [--api-key KEY]",
31
+ " docsy mcp",
32
+ "",
33
+ "Options:",
34
+ " --base-url URL Docsy deployment root",
35
+ " --api-key KEY Personal docsy_ API key",
36
+ " --json JSON output",
37
+ " --project Configure current project instead of global client config"
38
+ ].join("\n"));
39
+ }
40
+ async function main() {
41
+ if (!command || command === "help" || has("--help")) {
42
+ help();
43
+ return;
44
+ }
45
+ if (command === "mcp") {
46
+ console.log(baseUrl.replace(/\/$/, "") + "/mcp");
47
+ return;
48
+ }
49
+ if (command === "library") {
50
+ const name = args[0];
51
+ if (!name)
52
+ throw new Error("library name is required");
53
+ const query = args[1] && !args[1].startsWith("--") ? args[1] : name;
54
+ const results = await client.searchLibrary(query, name);
55
+ if (has("--json")) {
56
+ console.log(JSON.stringify(results, null, 2));
57
+ return;
58
+ }
59
+ if (!results.length) {
60
+ console.log("No matching libraries.");
61
+ return;
62
+ }
63
+ for (let i = 0; i < results.length; i++) {
64
+ const x = results[i];
65
+ console.log((i + 1) + ". " + x.name + "\n Library ID: " + x.libraryId + "\n " + x.description + "\n Code Snippets: " + x.codeSnippets + "\n Versions: " + (x.versions.join(", ") || "none") + "\n");
66
+ }
67
+ return;
68
+ }
69
+ if (command === "docs") {
70
+ const libraryId = args[0], query = args[1];
71
+ if (!libraryId || !query)
72
+ throw new Error("docs requires <libraryId> <query>");
73
+ const result = await client.getContext(query, libraryId, { type: has("--json") ? "json" : "txt", fast: has("--fast") });
74
+ console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
75
+ return;
76
+ }
77
+ if (command === "search") {
78
+ const query = args[0];
79
+ if (!query)
80
+ throw new Error("search query is required");
81
+ const libraries = values("--library");
82
+ const result = await client.search(query, {
83
+ type: has("--json") ? "json" : "txt",
84
+ ...(libraries.length ? { library: libraries } : {}),
85
+ ...(value("--version") ? { version: value("--version") } : {}),
86
+ ...(value("--language") ? { language: value("--language") } : {})
87
+ });
88
+ console.log(typeof result === "string" ? result : JSON.stringify(result, null, 2));
89
+ return;
90
+ }
91
+ if (command === "refresh") {
92
+ const libraryId = args[0];
93
+ if (!libraryId)
94
+ throw new Error("refresh requires <libraryId>");
95
+ const result = await client.refreshLibrary(libraryId, { ...(value("--branch") ? { branch: value("--branch") } : {}) });
96
+ console.log(has("--json") ? JSON.stringify(result, null, 2) : result.message);
97
+ return;
98
+ }
99
+ if (command === "metrics") {
100
+ const libraryId = args[0];
101
+ if (!libraryId)
102
+ throw new Error("metrics requires <libraryId>");
103
+ const daysRaw = value("--days");
104
+ const days = daysRaw ? Number(daysRaw) : 30;
105
+ if (!Number.isInteger(days) || days < 1 || days > 365)
106
+ throw new Error("--days must be an integer between 1 and 365");
107
+ const result = await client.getLibraryMetrics(libraryId, days);
108
+ console.log(JSON.stringify(result, null, 2));
109
+ return;
110
+ }
111
+ if (command === "policies") {
112
+ const patch = value("--patch-json");
113
+ const result = patch
114
+ ? await client.updatePolicies(JSON.parse(patch))
115
+ : await client.getPolicies();
116
+ console.log(JSON.stringify(result, null, 2));
117
+ return;
118
+ }
119
+ if (command === "add") {
120
+ const kind = args[0], sourceUrl = args[1];
121
+ if (!kind || !sourceUrl)
122
+ throw new Error("add requires <github|gitlab|bitbucket|git|website|llmstxt|openapi> <url>");
123
+ const result = kind === "github"
124
+ ? await client.addGitHubRepository(sourceUrl)
125
+ : kind === "gitlab"
126
+ ? await client.addGitLabRepository(sourceUrl)
127
+ : kind === "bitbucket"
128
+ ? await client.addBitbucketRepository(sourceUrl)
129
+ : kind === "git"
130
+ ? await client.addGitRepository(sourceUrl)
131
+ : kind === "website"
132
+ ? await client.addWebsite(sourceUrl, { ...(value("--base-url-scope") ? { websiteBaseUrl: value("--base-url-scope") } : {}) })
133
+ : kind === "llmstxt"
134
+ ? await client.addLlmsTxt(sourceUrl)
135
+ : kind === "openapi"
136
+ ? await client.addOpenApi(sourceUrl)
137
+ : (() => { throw new Error("Unknown source type: " + kind); })();
138
+ console.log(has("--json") ? JSON.stringify(result, null, 2) : result.message + "\nLibrary ID: " + result.libraryName);
139
+ return;
140
+ }
141
+ if (command === "setup") {
142
+ const agents = ["cursor", "claude", "codex", "vscode", "gemini", "opencode", "copilot"];
143
+ const agent = agents.find(a => has("--" + a));
144
+ if (!agent)
145
+ throw new Error("Choose --cursor, --claude, --codex, --vscode, --gemini, --opencode or --copilot");
146
+ const result = await setup(agent, has("--project") ? "project" : "global", baseUrl, apiKey);
147
+ console.log("Configured Docsy for " + agent + ".\nMCP: " + result.mcpUrl + "\nConfig: " + result.configPath + "\nRule: " + result.rulePath);
148
+ return;
149
+ }
150
+ throw new Error("Unknown command: " + command);
151
+ }
152
+ main().catch(error => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });
package/dist/setup.js ADDED
@@ -0,0 +1,109 @@
1
+ import { homedir } from "node:os";
2
+ import { dirname, join } from "node:path";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ const RULE = [
5
+ "# Docsy documentation rule",
6
+ "",
7
+ "Use Docsy whenever library, framework, SDK, API, CLI, or cloud-service documentation could affect correctness.",
8
+ "Resolve the library ID before querying documentation unless an exact /org/project[/version] ID is already known.",
9
+ "Prefer version-specific documentation and preserve source URLs when answering.",
10
+ ""
11
+ ].join("\n");
12
+ async function readJson(path) {
13
+ try {
14
+ return JSON.parse(await readFile(path, "utf8"));
15
+ }
16
+ catch {
17
+ return {};
18
+ }
19
+ }
20
+ async function writeJson(path, value) {
21
+ await mkdir(dirname(path), { recursive: true });
22
+ await writeFile(path, JSON.stringify(value, null, 2) + "\n");
23
+ }
24
+ function mergeSection(config, key, entry) {
25
+ const section = (config[key] && typeof config[key] === "object" ? config[key] : {});
26
+ return { ...config, [key]: { ...section, docsy: entry } };
27
+ }
28
+ function httpEntry(url, apiKey, type) {
29
+ return { ...(type ? { type } : {}), url, ...(apiKey ? { headers: { Authorization: "Bearer " + apiKey } } : {}) };
30
+ }
31
+ function home(...parts) { return join(homedir(), ...parts); }
32
+ async function installRule(agent, scope) {
33
+ const cwd = process.cwd();
34
+ let path;
35
+ let prefix = "";
36
+ switch (agent) {
37
+ case "cursor":
38
+ path = scope === "global" ? home(".cursor", "rules", "docsy.mdc") : join(cwd, ".cursor", "rules", "docsy.mdc");
39
+ prefix = "---\nalwaysApply: true\n---\n\n";
40
+ break;
41
+ case "claude":
42
+ path = scope === "global" ? home(".claude", "rules", "docsy.md") : join(cwd, ".claude", "rules", "docsy.md");
43
+ break;
44
+ case "vscode":
45
+ path = scope === "global" ? home(".config", "Code", "User", "prompts", "docsy.instructions.md") : join(cwd, ".github", "instructions", "docsy.instructions.md");
46
+ prefix = '---\napplyTo: "**"\n---\n\n';
47
+ break;
48
+ default:
49
+ path = scope === "global" ? home(".agents", "skills", "docsy", "SKILL.md") : join(cwd, ".agents", "skills", "docsy", "SKILL.md");
50
+ break;
51
+ }
52
+ await mkdir(dirname(path), { recursive: true });
53
+ await writeFile(path, prefix + RULE);
54
+ return path;
55
+ }
56
+ export async function setup(agent, scope, baseUrl, apiKey) {
57
+ const cwd = process.cwd();
58
+ const mcpUrl = baseUrl.replace(/\/$/, "") + "/mcp";
59
+ let path;
60
+ if (agent === "codex") {
61
+ path = scope === "global" ? home(".codex", "config.toml") : join(cwd, ".codex", "config.toml");
62
+ let existing = "";
63
+ try {
64
+ existing = await readFile(path, "utf8");
65
+ }
66
+ catch { }
67
+ const marker = "# --- docsy managed ---";
68
+ const lines = [marker, "[mcp_servers.docsy]", "url = " + JSON.stringify(mcpUrl)];
69
+ if (apiKey)
70
+ lines.push("", "[mcp_servers.docsy.http_headers]", "Authorization = " + JSON.stringify("Bearer " + apiKey));
71
+ lines.push(marker);
72
+ const block = lines.join("\n");
73
+ const start = existing.indexOf(marker);
74
+ let next;
75
+ if (start >= 0) {
76
+ const end = existing.indexOf(marker, start + marker.length);
77
+ next = end >= 0 ? existing.slice(0, start) + block + existing.slice(end + marker.length) : existing + "\n" + block + "\n";
78
+ }
79
+ else
80
+ next = existing.replace(/\s*$/, "") + "\n\n" + block + "\n";
81
+ await mkdir(dirname(path), { recursive: true });
82
+ await writeFile(path, next);
83
+ }
84
+ else {
85
+ const specs = {
86
+ claude: { project: ".mcp.json", global: home(".claude.json"), key: "mcpServers", type: "http" },
87
+ cursor: { project: join(".cursor", "mcp.json"), global: home(".cursor", "mcp.json"), key: "mcpServers" },
88
+ vscode: { project: join(".vscode", "mcp.json"), global: home(".config", "Code", "User", "mcp.json"), key: "servers", type: "http" },
89
+ gemini: { project: join(".gemini", "settings.json"), global: home(".gemini", "settings.json"), key: "mcpServers", urlKey: "httpUrl" },
90
+ opencode: { project: "opencode.json", global: home(".config", "opencode", "opencode.json"), key: "mcp", type: "remote" },
91
+ copilot: { project: ".mcp.json", global: home(".copilot", "mcp-config.json"), key: "mcpServers", type: "http" }
92
+ };
93
+ const spec = specs[agent];
94
+ path = scope === "global" ? spec.global : join(cwd, spec.project);
95
+ const cfg = await readJson(path);
96
+ let e = httpEntry(mcpUrl, apiKey, "type" in spec ? spec.type : undefined);
97
+ if ("urlKey" in spec && spec.urlKey === "httpUrl") {
98
+ const { url, ...rest } = e;
99
+ e = { ...rest, httpUrl: url };
100
+ }
101
+ if (agent === "opencode")
102
+ e = { ...e, enabled: true };
103
+ if (agent === "copilot")
104
+ e = { ...e, tools: ["*"] };
105
+ await writeJson(path, mergeSection(cfg, spec.key, e));
106
+ }
107
+ const rulePath = await installRule(agent, scope);
108
+ return { configPath: path, rulePath, mcpUrl };
109
+ }
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "@remcp/docsy-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Docsy CLI for current documentation and MCP setup",
5
+ "type": "module",
6
+ "bin": {
7
+ "docsy": "./dist/index.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc -p tsconfig.json",
15
+ "typecheck": "tsc -p tsconfig.json --noEmit",
16
+ "test": "vitest run",
17
+ "lint": "eslint src test --ext .ts,.tsx,.js,.mjs"
18
+ },
19
+ "dependencies": {
20
+ "@remcp/docsy-sdk": "0.1.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^24.10.0",
24
+ "typescript": "^5.9.2",
25
+ "vitest": "^4.0.4"
26
+ },
27
+ "license": "MIT",
28
+ "publishConfig": {
29
+ "access": "public"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/antonbaider/docsy.git",
34
+ "directory": "packages/cli"
35
+ },
36
+ "homepage": "https://docsy.remcp.site/",
37
+ "bugs": {
38
+ "url": "https://github.com/antonbaider/docsy/issues"
39
+ }
40
+ }