bitfab-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/dist/claude.d.ts +2 -0
- package/dist/claude.js +49 -0
- package/dist/codex.d.ts +7 -0
- package/dist/codex.js +48 -0
- package/dist/cursor.d.ts +1 -0
- package/dist/cursor.js +40 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +56 -0
- package/dist/init.d.ts +5 -0
- package/dist/init.js +91 -0
- package/package.json +51 -0
package/dist/claude.d.ts
ADDED
package/dist/claude.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const REPO = "Project-White-Rabbit/bitfab-claude-plugin";
|
|
6
|
+
const MARKETPLACE = "bitfab";
|
|
7
|
+
const PLUGIN_KEY = "bitfab@bitfab";
|
|
8
|
+
const SETUP_COMMAND = "/bitfab:setup";
|
|
9
|
+
export async function runClaudeInit() {
|
|
10
|
+
runStep("Adding bitfab marketplace", () => {
|
|
11
|
+
runClaude(["plugin", "marketplace", "add", REPO]);
|
|
12
|
+
});
|
|
13
|
+
runStep("Enabling auto-updates for bitfab marketplace", () => {
|
|
14
|
+
enableAutoUpdate(path.join(os.homedir(), ".claude", "settings.json"));
|
|
15
|
+
});
|
|
16
|
+
runStep("Installing bitfab plugin", () => {
|
|
17
|
+
runClaude(["plugin", "install", PLUGIN_KEY, "--scope", "user"]);
|
|
18
|
+
});
|
|
19
|
+
process.stdout.write(`\n✓ Bitfab plugin installed in Claude Code\n`);
|
|
20
|
+
process.stdout.write(` Launching ${SETUP_COMMAND}...\n\n`);
|
|
21
|
+
spawnSync("claude", [SETUP_COMMAND], { stdio: "inherit" });
|
|
22
|
+
}
|
|
23
|
+
function runStep(label, fn) {
|
|
24
|
+
process.stdout.write(`→ ${label}\n`);
|
|
25
|
+
fn();
|
|
26
|
+
}
|
|
27
|
+
function runClaude(args) {
|
|
28
|
+
const result = spawnSync("claude", [...args], { stdio: "inherit" });
|
|
29
|
+
if (result.status !== 0) {
|
|
30
|
+
throw new Error(`claude ${args.join(" ")} failed${result.status === null ? "" : ` with status ${result.status}`}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function enableAutoUpdate(settingsPath) {
|
|
34
|
+
let settings = {};
|
|
35
|
+
if (fs.existsSync(settingsPath)) {
|
|
36
|
+
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
37
|
+
settings = raw.trim() === "" ? {} : JSON.parse(raw);
|
|
38
|
+
}
|
|
39
|
+
const marketplaces = settings.extraKnownMarketplaces ?? {};
|
|
40
|
+
const existing = marketplaces[MARKETPLACE] ?? {};
|
|
41
|
+
marketplaces[MARKETPLACE] = {
|
|
42
|
+
...existing,
|
|
43
|
+
source: existing.source ?? { source: "github", repo: REPO },
|
|
44
|
+
autoUpdate: true,
|
|
45
|
+
};
|
|
46
|
+
settings.extraKnownMarketplaces = marketplaces;
|
|
47
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
48
|
+
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
49
|
+
}
|
package/dist/codex.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function runCodexInit(): Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* Ensure `[plugins."bitfab@bitfab"] enabled = true` is present in the Codex
|
|
4
|
+
* config TOML. Append-or-replace via regex — we only touch this one section
|
|
5
|
+
* and never re-emit the rest of the file.
|
|
6
|
+
*/
|
|
7
|
+
export declare function enableCodexPlugin(configPath: string): void;
|
package/dist/codex.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
const REPO = "Project-White-Rabbit/bitfab-codex-plugin";
|
|
6
|
+
const PLUGIN_KEY = "bitfab@bitfab";
|
|
7
|
+
const SETUP_COMMAND = "$bitfab:setup";
|
|
8
|
+
export async function runCodexInit() {
|
|
9
|
+
process.stdout.write("→ Adding bitfab marketplace\n");
|
|
10
|
+
const result = spawnSync("codex", ["plugin", "marketplace", "add", REPO], {
|
|
11
|
+
stdio: "inherit",
|
|
12
|
+
});
|
|
13
|
+
if (result.status !== 0) {
|
|
14
|
+
throw new Error(`codex plugin marketplace add failed${result.status === null ? "" : ` with status ${result.status}`}`);
|
|
15
|
+
}
|
|
16
|
+
process.stdout.write("→ Enabling bitfab plugin in ~/.codex/config.toml\n");
|
|
17
|
+
enableCodexPlugin(path.join(os.homedir(), ".codex", "config.toml"));
|
|
18
|
+
process.stdout.write(`\n✓ Bitfab plugin installed in Codex\n`);
|
|
19
|
+
process.stdout.write(` Launching ${SETUP_COMMAND}...\n\n`);
|
|
20
|
+
spawnSync("codex", [SETUP_COMMAND], { stdio: "inherit" });
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Ensure `[plugins."bitfab@bitfab"] enabled = true` is present in the Codex
|
|
24
|
+
* config TOML. Append-or-replace via regex — we only touch this one section
|
|
25
|
+
* and never re-emit the rest of the file.
|
|
26
|
+
*/
|
|
27
|
+
export function enableCodexPlugin(configPath) {
|
|
28
|
+
let content = "";
|
|
29
|
+
if (fs.existsSync(configPath)) {
|
|
30
|
+
content = fs.readFileSync(configPath, "utf-8");
|
|
31
|
+
}
|
|
32
|
+
const header = `[plugins."${PLUGIN_KEY}"]`;
|
|
33
|
+
const desiredSection = `${header}\nenabled = true\n`;
|
|
34
|
+
const sectionRegex = new RegExp(`\\[plugins\\."${escapeRegex(PLUGIN_KEY)}"\\][^\\[]*`, "m");
|
|
35
|
+
let next;
|
|
36
|
+
if (sectionRegex.test(content)) {
|
|
37
|
+
next = content.replace(sectionRegex, desiredSection);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
const trimmed = content.replace(/\s+$/, "");
|
|
41
|
+
next = trimmed === "" ? desiredSection : `${trimmed}\n\n${desiredSection}`;
|
|
42
|
+
}
|
|
43
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
44
|
+
fs.writeFileSync(configPath, next);
|
|
45
|
+
}
|
|
46
|
+
function escapeRegex(s) {
|
|
47
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
48
|
+
}
|
package/dist/cursor.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runCursorInit(): Promise<void>;
|
package/dist/cursor.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
const REPO = "Project-White-Rabbit/bitfab-cursor-plugin";
|
|
4
|
+
const ADD_PLUGIN_CMD = `/add-plugin ${REPO}`;
|
|
5
|
+
const SETUP_COMMAND = "/bitfab-setup";
|
|
6
|
+
export async function runCursorInit() {
|
|
7
|
+
const copied = copyToClipboard(ADD_PLUGIN_CMD);
|
|
8
|
+
if (copied) {
|
|
9
|
+
process.stdout.write(`✓ Copied to clipboard: ${ADD_PLUGIN_CMD}\n\n`);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
process.stdout.write(`Copy the following command:\n ${ADD_PLUGIN_CMD}\n\n`);
|
|
13
|
+
}
|
|
14
|
+
process.stdout.write("To finish setup in Cursor:\n");
|
|
15
|
+
process.stdout.write(` 1. Run ${ADD_PLUGIN_CMD} (already in your clipboard)\n`);
|
|
16
|
+
process.stdout.write(` 2. Then run ${SETUP_COMMAND}\n\n`);
|
|
17
|
+
process.stdout.write("Opening Cursor...\n");
|
|
18
|
+
const child = spawn("cursor", ["."], { stdio: "ignore", detached: true });
|
|
19
|
+
child.unref();
|
|
20
|
+
}
|
|
21
|
+
function copyToClipboard(text) {
|
|
22
|
+
const cmd = clipboardCommand();
|
|
23
|
+
if (!cmd) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
const result = spawnSync(cmd.bin, cmd.args, {
|
|
27
|
+
input: text,
|
|
28
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
29
|
+
});
|
|
30
|
+
return result.status === 0;
|
|
31
|
+
}
|
|
32
|
+
function clipboardCommand() {
|
|
33
|
+
if (os.platform() === "darwin") {
|
|
34
|
+
return { bin: "pbcopy", args: [] };
|
|
35
|
+
}
|
|
36
|
+
if (os.platform() === "linux") {
|
|
37
|
+
return { bin: "xclip", args: ["-selection", "clipboard"] };
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runInit } from "./init.js";
|
|
3
|
+
const HELP_TEXT = `Usage: bitfab-cli <command> [options]
|
|
4
|
+
|
|
5
|
+
Commands:
|
|
6
|
+
init [--editor <name>] Install the Bitfab plugin in one editor and launch /bitfab:setup
|
|
7
|
+
Editor names: claude, codex, cursor
|
|
8
|
+
help Show this help
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
bitfab-cli init Detect editors, prompt if multiple
|
|
12
|
+
bitfab-cli init --editor claude Install for Claude Code, skip prompt
|
|
13
|
+
bitfab-cli init --editor codex
|
|
14
|
+
bitfab-cli init --editor cursor
|
|
15
|
+
`;
|
|
16
|
+
function parseArgs(argv) {
|
|
17
|
+
const [command, ...rest] = argv;
|
|
18
|
+
let editor;
|
|
19
|
+
for (let i = 0; i < rest.length; i++) {
|
|
20
|
+
const arg = rest[i];
|
|
21
|
+
if (arg === "--editor" || arg === "-e") {
|
|
22
|
+
if (i + 1 < rest.length) {
|
|
23
|
+
editor = rest[i + 1];
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new Error("--editor flag requires a value");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
else if (arg?.startsWith("--editor=")) {
|
|
31
|
+
editor = arg.slice("--editor=".length);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return { command, editor };
|
|
35
|
+
}
|
|
36
|
+
async function main() {
|
|
37
|
+
const { command, editor } = parseArgs(process.argv.slice(2));
|
|
38
|
+
if (!command ||
|
|
39
|
+
command === "help" ||
|
|
40
|
+
command === "--help" ||
|
|
41
|
+
command === "-h") {
|
|
42
|
+
process.stdout.write(HELP_TEXT);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (command === "init") {
|
|
46
|
+
await runInit({ editor });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
process.stderr.write(`Unknown command: ${command}\n\n${HELP_TEXT}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
main().catch((err) => {
|
|
53
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
54
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
package/dist/init.d.ts
ADDED
package/dist/init.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import readline from "node:readline/promises";
|
|
3
|
+
import { runClaudeInit } from "./claude.js";
|
|
4
|
+
import { runCodexInit } from "./codex.js";
|
|
5
|
+
import { runCursorInit } from "./cursor.js";
|
|
6
|
+
const EDITORS = ["claude", "codex", "cursor"];
|
|
7
|
+
const EDITOR_LABEL = {
|
|
8
|
+
claude: "Claude Code",
|
|
9
|
+
codex: "Codex",
|
|
10
|
+
cursor: "Cursor",
|
|
11
|
+
};
|
|
12
|
+
function detectInstalledEditors() {
|
|
13
|
+
return EDITORS.filter((e) => isOnPath(e));
|
|
14
|
+
}
|
|
15
|
+
function isOnPath(binary) {
|
|
16
|
+
try {
|
|
17
|
+
const cmd = process.platform === "win32" ? "where" : "command -v";
|
|
18
|
+
execSync(`${cmd} ${binary}`, { stdio: "ignore" });
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function pickEditor(detected) {
|
|
26
|
+
if (detected.length === 1) {
|
|
27
|
+
return detected[0];
|
|
28
|
+
}
|
|
29
|
+
const rl = readline.createInterface({
|
|
30
|
+
input: process.stdin,
|
|
31
|
+
output: process.stdout,
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
process.stdout.write("\nMultiple editors detected:\n");
|
|
35
|
+
detected.forEach((e, i) => {
|
|
36
|
+
process.stdout.write(` ${i + 1}. ${EDITOR_LABEL[e]}\n`);
|
|
37
|
+
});
|
|
38
|
+
while (true) {
|
|
39
|
+
const answer = (await rl.question(`\nSet up which? [1-${detected.length}, default 1]: `)).trim();
|
|
40
|
+
if (answer === "") {
|
|
41
|
+
return detected[0];
|
|
42
|
+
}
|
|
43
|
+
const num = Number.parseInt(answer, 10);
|
|
44
|
+
if (Number.isInteger(num) && num >= 1 && num <= detected.length) {
|
|
45
|
+
return detected[num - 1];
|
|
46
|
+
}
|
|
47
|
+
const byName = detected.find((e) => e === answer.toLowerCase());
|
|
48
|
+
if (byName) {
|
|
49
|
+
return byName;
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write(`Invalid choice: ${answer}\n`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
rl.close();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function runInit({ editor }) {
|
|
59
|
+
const detected = detectInstalledEditors();
|
|
60
|
+
if (detected.length === 0) {
|
|
61
|
+
throw new Error("No supported editor found on PATH. Install Claude Code, Codex, or Cursor and try again.");
|
|
62
|
+
}
|
|
63
|
+
let chosen;
|
|
64
|
+
if (editor) {
|
|
65
|
+
if (!EDITORS.includes(editor)) {
|
|
66
|
+
throw new Error(`Unknown editor: ${editor}. Expected one of: ${EDITORS.join(", ")}`);
|
|
67
|
+
}
|
|
68
|
+
if (!detected.includes(editor)) {
|
|
69
|
+
throw new Error(`${EDITOR_LABEL[editor]} is not installed (binary "${editor}" not found on PATH).`);
|
|
70
|
+
}
|
|
71
|
+
chosen = editor;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
chosen = await pickEditor(detected);
|
|
75
|
+
}
|
|
76
|
+
process.stdout.write(`\nSetting up Bitfab in ${EDITOR_LABEL[chosen]}...\n\n`);
|
|
77
|
+
if (chosen === "claude") {
|
|
78
|
+
await runClaudeInit();
|
|
79
|
+
}
|
|
80
|
+
else if (chosen === "codex") {
|
|
81
|
+
await runCodexInit();
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
await runCursorInit();
|
|
85
|
+
}
|
|
86
|
+
const others = detected.filter((e) => e !== chosen);
|
|
87
|
+
if (others.length > 0) {
|
|
88
|
+
const next = others[0];
|
|
89
|
+
process.stdout.write(`\n To set up another editor: bitfab-cli init --editor ${next}\n`);
|
|
90
|
+
}
|
|
91
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bitfab-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Install and configure the Bitfab plugin in Claude Code, Codex, or Cursor.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"bitfab-cli": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"!dist/**/*.test.*",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/Project-White-Rabbit/ai-assistant",
|
|
19
|
+
"directory": "bitfab-cli"
|
|
20
|
+
},
|
|
21
|
+
"homepage": "https://bitfab.ai",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"bitfab",
|
|
24
|
+
"cli",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"codex",
|
|
27
|
+
"cursor",
|
|
28
|
+
"plugin"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc",
|
|
32
|
+
"dev": "tsc --watch",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"lint": "biome check",
|
|
35
|
+
"lint:fix": "biome check --write",
|
|
36
|
+
"tsc": "tsc --noEmit",
|
|
37
|
+
"knip": "knip",
|
|
38
|
+
"madge": "madge --ts-config ./tsconfig.json --extensions ts --circular ./src",
|
|
39
|
+
"validate": "pnpm lint && pnpm tsc && pnpm knip && pnpm madge && pnpm build"
|
|
40
|
+
},
|
|
41
|
+
"author": "Bitfab",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/node": "^20.0.0",
|
|
48
|
+
"typescript": "^5.0.0",
|
|
49
|
+
"vitest": "^4.1.0"
|
|
50
|
+
}
|
|
51
|
+
}
|