codelibrium-cli 1.1.1 → 1.1.2
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/index.js +170 -9
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const https = require('https');
|
|
4
|
+
const http = require('http');
|
|
4
5
|
const fs = require('fs');
|
|
5
6
|
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const crypto = require('crypto');
|
|
9
|
+
const { exec } = require('child_process');
|
|
6
10
|
|
|
7
11
|
const SUPABASE_HOST = 'dlrwytcguskuolvvwhbn.supabase.co';
|
|
8
12
|
const SUPABASE_ANON_KEY =
|
|
@@ -20,15 +24,58 @@ const TOOL_TO_FILENAME = {
|
|
|
20
24
|
|
|
21
25
|
const VALID_TOOLS = Object.keys(TOOL_TO_FILENAME);
|
|
22
26
|
|
|
27
|
+
function configDirPath() {
|
|
28
|
+
return path.join(os.homedir(), '.codelibrium');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function configFilePath() {
|
|
32
|
+
return path.join(configDirPath(), 'config.json');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readConfig() {
|
|
36
|
+
const p = configFilePath();
|
|
37
|
+
try {
|
|
38
|
+
const raw = fs.readFileSync(p, 'utf8');
|
|
39
|
+
const data = JSON.parse(raw);
|
|
40
|
+
if (data && typeof data.token === 'string' && data.token.length > 0) return data;
|
|
41
|
+
} catch {
|
|
42
|
+
/* missing or invalid */
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function saveConfig(obj) {
|
|
48
|
+
const dir = configDirPath();
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
fs.writeFileSync(configFilePath(), JSON.stringify(obj, null, 2), 'utf8');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function openBrowser(targetUrl) {
|
|
54
|
+
const q =
|
|
55
|
+
process.platform === 'darwin'
|
|
56
|
+
? `open ${JSON.stringify(targetUrl)}`
|
|
57
|
+
: process.platform === 'win32'
|
|
58
|
+
? `start "" ${JSON.stringify(targetUrl)}`
|
|
59
|
+
: `xdg-open ${JSON.stringify(targetUrl)}`;
|
|
60
|
+
exec(q, (err) => {
|
|
61
|
+
if (err) console.error('Could not open browser automatically. Open this URL:', targetUrl);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
23
65
|
function printHelp() {
|
|
24
|
-
console.log(`Codelibrium CLI — install published
|
|
66
|
+
console.log(`Codelibrium CLI — install rulesets from Codelibrium (published or your own when logged in).
|
|
25
67
|
|
|
26
68
|
Usage:
|
|
27
|
-
npx codelibrium-cli
|
|
69
|
+
npx codelibrium-cli <command> [options]
|
|
28
70
|
|
|
29
71
|
Commands:
|
|
30
|
-
install <slug> [tool] Fetch a
|
|
72
|
+
install <slug> [tool] Fetch a ruleset and write it to the right file.
|
|
31
73
|
[tool] defaults to "cursor" if omitted.
|
|
74
|
+
Log in for access to your private rulesets (RLS).
|
|
75
|
+
|
|
76
|
+
login Log in to your Codelibrium account
|
|
77
|
+
logout Log out
|
|
78
|
+
whoami Show current logged in user
|
|
32
79
|
|
|
33
80
|
Valid tools:
|
|
34
81
|
${VALID_TOOLS.join(', ')}
|
|
@@ -43,6 +90,7 @@ Tool → file:
|
|
|
43
90
|
Examples:
|
|
44
91
|
npx codelibrium-cli install my-ruleset
|
|
45
92
|
npx codelibrium-cli install my-ruleset claude
|
|
93
|
+
npx codelibrium-cli login
|
|
46
94
|
|
|
47
95
|
Options:
|
|
48
96
|
-h, --help Show this message
|
|
@@ -54,7 +102,8 @@ function fail(message) {
|
|
|
54
102
|
process.exit(1);
|
|
55
103
|
}
|
|
56
104
|
|
|
57
|
-
function httpsGetJson(urlPath) {
|
|
105
|
+
function httpsGetJson(urlPath, authorizationBearer) {
|
|
106
|
+
const bearer = authorizationBearer != null ? authorizationBearer : SUPABASE_ANON_KEY;
|
|
58
107
|
return new Promise((resolve, reject) => {
|
|
59
108
|
const opts = {
|
|
60
109
|
hostname: SUPABASE_HOST,
|
|
@@ -62,7 +111,7 @@ function httpsGetJson(urlPath) {
|
|
|
62
111
|
method: 'GET',
|
|
63
112
|
headers: {
|
|
64
113
|
apikey: SUPABASE_ANON_KEY,
|
|
65
|
-
Authorization: `Bearer ${
|
|
114
|
+
Authorization: `Bearer ${bearer}`,
|
|
66
115
|
Accept: 'application/json',
|
|
67
116
|
},
|
|
68
117
|
};
|
|
@@ -97,6 +146,105 @@ function httpsGetJson(urlPath) {
|
|
|
97
146
|
});
|
|
98
147
|
}
|
|
99
148
|
|
|
149
|
+
function cmdLogout() {
|
|
150
|
+
const p = configFilePath();
|
|
151
|
+
try {
|
|
152
|
+
fs.unlinkSync(p);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
if (e.code !== 'ENOENT') throw e;
|
|
155
|
+
}
|
|
156
|
+
console.log('✓ Logged out');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function cmdWhoami() {
|
|
160
|
+
const cfg = readConfig();
|
|
161
|
+
if (!cfg) {
|
|
162
|
+
console.log('Not logged in. Run: npx codelibrium-cli login');
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let rows;
|
|
167
|
+
try {
|
|
168
|
+
rows = await httpsGetJson('/rest/v1/user_profiles?select=email,full_name', cfg.token);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
fail(e.message || String(e));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
174
|
+
fail('Could not load your profile. Your session may have expired — run login again.');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const row = rows[0];
|
|
178
|
+
const name = row.full_name != null && String(row.full_name).trim() ? String(row.full_name).trim() : '(no name)';
|
|
179
|
+
const email = row.email != null && String(row.email).trim() ? String(row.email).trim() : '(no email)';
|
|
180
|
+
console.log(`${name} <${email}>`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function cmdLogin() {
|
|
184
|
+
const state = crypto.randomBytes(16).toString('hex');
|
|
185
|
+
const authUrl = `https://codelibrium.com/cli-auth?state=${encodeURIComponent(state)}`;
|
|
186
|
+
|
|
187
|
+
const server = http.createServer((req, res) => {
|
|
188
|
+
let u;
|
|
189
|
+
try {
|
|
190
|
+
u = new URL(req.url || '/', 'http://127.0.0.1:9876');
|
|
191
|
+
} catch {
|
|
192
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
193
|
+
res.end('Bad request');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (u.pathname !== '/callback') {
|
|
198
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
199
|
+
res.end('Not found');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const token = u.searchParams.get('token');
|
|
204
|
+
const stateParam = u.searchParams.get('state');
|
|
205
|
+
|
|
206
|
+
if (stateParam !== state) {
|
|
207
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
208
|
+
res.end('<!DOCTYPE html><html><body><p>Invalid state. Close this window and try again from the CLI.</p></body></html>');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!token || typeof token !== 'string') {
|
|
213
|
+
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
214
|
+
res.end('<!DOCTYPE html><html><body><p>Missing token. Close this window and try again.</p></body></html>');
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
saveConfig({
|
|
219
|
+
token: String(token),
|
|
220
|
+
savedAt: new Date().toISOString(),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
224
|
+
res.end(
|
|
225
|
+
'<!DOCTYPE html><html><body><p>Logged in. You can close this window.</p></body></html>',
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
server.close(() => {
|
|
229
|
+
console.log('✓ Logged in to Codelibrium');
|
|
230
|
+
process.exit(0);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
server.on('error', (err) => {
|
|
235
|
+
if (err.code === 'EADDRINUSE') {
|
|
236
|
+
fail('Port 9876 is already in use. Close the other process and try again.');
|
|
237
|
+
}
|
|
238
|
+
fail(err.message || String(err));
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
server.listen(9876, '127.0.0.1', () => {
|
|
242
|
+
console.log('Opening browser to log in to Codelibrium...');
|
|
243
|
+
openBrowser(authUrl);
|
|
244
|
+
console.log('Waiting for authentication...');
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
100
248
|
async function cmdInstall(slug, tool) {
|
|
101
249
|
if (!slug || slug.startsWith('-')) {
|
|
102
250
|
fail('Missing <slug>.\n\nUsage: npx codelibrium-cli install <slug> [tool]\nRun with --help for more.');
|
|
@@ -115,14 +263,15 @@ async function cmdInstall(slug, tool) {
|
|
|
115
263
|
|
|
116
264
|
console.log(`Fetching "${slug}" from Codelibrium...`);
|
|
117
265
|
|
|
266
|
+
const cfg = readConfig();
|
|
267
|
+
const bearer = cfg && cfg.token ? cfg.token : null;
|
|
268
|
+
|
|
118
269
|
const query =
|
|
119
|
-
'/rest/v1/rulesets?slug=eq.' +
|
|
120
|
-
encodeURIComponent(slug) +
|
|
121
|
-
'&select=content,tool,name&is_published=eq.true';
|
|
270
|
+
'/rest/v1/rulesets?slug=eq.' + encodeURIComponent(slug) + '&select=content,tool,name';
|
|
122
271
|
|
|
123
272
|
let rows;
|
|
124
273
|
try {
|
|
125
|
-
rows = await httpsGetJson(query);
|
|
274
|
+
rows = await httpsGetJson(query, bearer);
|
|
126
275
|
} catch (e) {
|
|
127
276
|
fail(e.message || String(e));
|
|
128
277
|
}
|
|
@@ -160,6 +309,18 @@ async function main() {
|
|
|
160
309
|
await cmdInstall(argv[1], argv[2]);
|
|
161
310
|
return;
|
|
162
311
|
}
|
|
312
|
+
if (cmd === 'login') {
|
|
313
|
+
cmdLogin();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (cmd === 'logout') {
|
|
317
|
+
cmdLogout();
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (cmd === 'whoami') {
|
|
321
|
+
await cmdWhoami();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
163
324
|
|
|
164
325
|
fail(`Unknown command "${cmd}".\nRun with --help for usage.`);
|
|
165
326
|
}
|