@fibukiapp/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/bin/fibuki.mjs +53 -0
- package/lib/auth.mjs +154 -0
- package/package.json +28 -0
package/bin/fibuki.mjs
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FiBuKI CLI
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx @fibukiapp/cli auth Save API key to ~/.fibuki/config.json
|
|
8
|
+
* npx @fibukiapp/cli auth --format mcp Print Claude Desktop MCP config
|
|
9
|
+
* npx @fibukiapp/cli auth --format env Print FIBUKI_API_KEY=fk_xxx
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { deviceAuth } from "../lib/auth.mjs";
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const command = args[0];
|
|
16
|
+
|
|
17
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
18
|
+
console.log(`
|
|
19
|
+
FiBuKI CLI — authenticate and configure AI integrations
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
fibuki auth Authenticate via browser and save API key
|
|
23
|
+
fibuki auth --format mcp Print Claude Desktop MCP config snippet
|
|
24
|
+
fibuki auth --format env Print FIBUKI_API_KEY export
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--format <type> Output format: json (default), mcp, env
|
|
28
|
+
--base-url <url> Override API base URL (for development)
|
|
29
|
+
--help Show this help message
|
|
30
|
+
`);
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (command === "auth") {
|
|
35
|
+
const formatIdx = args.indexOf("--format");
|
|
36
|
+
const format = formatIdx !== -1 ? args[formatIdx + 1] : "json";
|
|
37
|
+
|
|
38
|
+
const baseUrlIdx = args.indexOf("--base-url");
|
|
39
|
+
const baseUrl = baseUrlIdx !== -1 ? args[baseUrlIdx + 1] : "https://fibuki.com";
|
|
40
|
+
|
|
41
|
+
if (!["json", "mcp", "env"].includes(format)) {
|
|
42
|
+
console.error(`Unknown format: ${format}. Use json, mcp, or env.`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
deviceAuth({ format, baseUrl }).catch((err) => {
|
|
47
|
+
console.error("\nError:", err.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
} else {
|
|
51
|
+
console.error(`Unknown command: ${command}. Run 'fibuki --help' for usage.`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
package/lib/auth.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Device Authorization Flow
|
|
3
|
+
*
|
|
4
|
+
* 1. POST /api/auth/device/code → get device_code + user_code
|
|
5
|
+
* 2. Open browser to /auth/device?code=XXXX-XXXX
|
|
6
|
+
* 3. Poll /api/auth/device/token until approved
|
|
7
|
+
* 4. Save or print API key
|
|
8
|
+
*
|
|
9
|
+
* Zero dependencies — uses only Node 18+ built-ins.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { exec } from "node:child_process";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Open a URL in the default browser (platform-aware).
|
|
19
|
+
*/
|
|
20
|
+
function openBrowser(url) {
|
|
21
|
+
const platform = process.platform;
|
|
22
|
+
const cmd =
|
|
23
|
+
platform === "darwin" ? "open" :
|
|
24
|
+
platform === "win32" ? "start" :
|
|
25
|
+
"xdg-open";
|
|
26
|
+
|
|
27
|
+
exec(`${cmd} "${url}"`, (err) => {
|
|
28
|
+
if (err) {
|
|
29
|
+
// Silently fail — user can open the URL manually
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Sleep for ms milliseconds.
|
|
36
|
+
*/
|
|
37
|
+
function sleep(ms) {
|
|
38
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Run the device auth flow.
|
|
43
|
+
*/
|
|
44
|
+
export async function deviceAuth({ format = "json", baseUrl = "https://fibuki.com" }) {
|
|
45
|
+
console.log("\n FiBuKI — Device Authorization\n");
|
|
46
|
+
|
|
47
|
+
// Step 1: Request device code
|
|
48
|
+
const codeRes = await fetch(`${baseUrl}/api/auth/device/code`, {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!codeRes.ok) {
|
|
54
|
+
const err = await codeRes.json().catch(() => ({}));
|
|
55
|
+
throw new Error(err.error || `Failed to start auth flow (HTTP ${codeRes.status})`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const { device_code, user_code, verification_uri, expires_in, interval } = await codeRes.json();
|
|
59
|
+
|
|
60
|
+
// Step 2: Show instructions and open browser
|
|
61
|
+
const browserUrl = `${verification_uri}?code=${encodeURIComponent(user_code)}`;
|
|
62
|
+
|
|
63
|
+
console.log(` Your code: ${user_code}\n`);
|
|
64
|
+
console.log(` Opening browser to: ${browserUrl}`);
|
|
65
|
+
console.log(` (If the browser doesn't open, visit the URL above manually)\n`);
|
|
66
|
+
console.log(` Waiting for approval...`);
|
|
67
|
+
|
|
68
|
+
openBrowser(browserUrl);
|
|
69
|
+
|
|
70
|
+
// Step 3: Poll for approval
|
|
71
|
+
let pollInterval = (interval || 5) * 1000;
|
|
72
|
+
const deadline = Date.now() + expires_in * 1000;
|
|
73
|
+
|
|
74
|
+
while (Date.now() < deadline) {
|
|
75
|
+
await sleep(pollInterval);
|
|
76
|
+
|
|
77
|
+
const tokenRes = await fetch(`${baseUrl}/api/auth/device/token`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify({
|
|
81
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
82
|
+
device_code,
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const tokenData = await tokenRes.json();
|
|
87
|
+
|
|
88
|
+
if (tokenRes.ok && tokenData.access_token) {
|
|
89
|
+
// Success!
|
|
90
|
+
console.log(`\n Authorized! Key "${tokenData.key_name}" created.\n`);
|
|
91
|
+
outputResult({ format, apiKey: tokenData.access_token, keyName: tokenData.key_name, baseUrl });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (tokenData.error === "authorization_pending") {
|
|
96
|
+
process.stdout.write(".");
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (tokenData.error === "slow_down") {
|
|
101
|
+
pollInterval += 2000;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (tokenData.error === "expired_token") {
|
|
106
|
+
throw new Error("Code expired. Please run the command again.");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw new Error(tokenData.error_description || tokenData.error || "Unknown error");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
throw new Error("Timed out waiting for approval. Please run the command again.");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Output the API key in the requested format.
|
|
117
|
+
*/
|
|
118
|
+
function outputResult({ format, apiKey, keyName, baseUrl }) {
|
|
119
|
+
if (format === "env") {
|
|
120
|
+
console.log(` FIBUKI_API_KEY=${apiKey}\n`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (format === "mcp") {
|
|
125
|
+
const mcpUrl = baseUrl.endsWith("/") ? `${baseUrl}api/mcp/sse` : `${baseUrl}/api/mcp/sse`;
|
|
126
|
+
const config = {
|
|
127
|
+
mcpServers: {
|
|
128
|
+
fibuki: {
|
|
129
|
+
url: mcpUrl,
|
|
130
|
+
headers: {
|
|
131
|
+
Authorization: `Bearer ${apiKey}`,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
console.log(" Add this to your Claude Desktop config:\n");
|
|
137
|
+
console.log(JSON.stringify(config, null, 2));
|
|
138
|
+
console.log("");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Default: save to ~/.fibuki/config.json
|
|
143
|
+
const configDir = join(homedir(), ".fibuki");
|
|
144
|
+
const configPath = join(configDir, "config.json");
|
|
145
|
+
|
|
146
|
+
if (!existsSync(configDir)) {
|
|
147
|
+
mkdirSync(configDir, { recursive: true });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const config = { apiKey, keyName };
|
|
151
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
152
|
+
|
|
153
|
+
console.log(` Saved to ${configPath}\n`);
|
|
154
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fibukiapp/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "FiBuKI CLI — authenticate and configure AI integrations",
|
|
5
|
+
"bin": {
|
|
6
|
+
"fibuki": "./bin/fibuki.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/"
|
|
12
|
+
],
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/fibukiapp/fibuki"
|
|
19
|
+
},
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"fibuki",
|
|
23
|
+
"tax",
|
|
24
|
+
"bookkeeping",
|
|
25
|
+
"mcp",
|
|
26
|
+
"cli"
|
|
27
|
+
]
|
|
28
|
+
}
|