codelibrium-cli 1.1.0 → 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.
Files changed (2) hide show
  1. package/index.js +170 -9
  2. package/package.json +9 -4
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 rulesets into your project.
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 install <slug> [tool]
69
+ npx codelibrium-cli <command> [options]
28
70
 
29
71
  Commands:
30
- install <slug> [tool] Fetch a published ruleset and write it to the right file.
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 ${SUPABASE_ANON_KEY}`,
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
  }
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "codelibrium-cli",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Install AI rulesets, skills and prompts from Codelibrium",
5
5
  "main": "index.js",
6
6
  "bin": {
7
- "codelibrium": "./index.js",
8
- "codelibrium-cli": "./index.js"
7
+ "codelibrium": "index.js",
8
+ "codelibrium-cli": "index.js"
9
9
  },
10
- "keywords": ["ai", "cursor", "rules", "codelibrium"],
10
+ "keywords": [
11
+ "ai",
12
+ "cursor",
13
+ "rules",
14
+ "codelibrium"
15
+ ],
11
16
  "author": "Codelibrium",
12
17
  "license": "MIT",
13
18
  "scripts": {