bitcompass 0.1.0 → 0.2.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/README.md CHANGED
@@ -54,16 +54,13 @@ Add the `bitcompass` entry under `mcpServers`:
54
54
  "bitcompass": {
55
55
  "type": "stdio",
56
56
  "command": "bitcompass",
57
- "args": ["mcp", "start"],
58
- "env": {
59
- "BITCOMPASS_CONFIG_DIR": "~/.bitcompass"
60
- }
57
+ "args": ["mcp", "start"]
61
58
  }
62
59
  }
63
60
  }
64
61
  ```
65
62
 
66
- Replace `~` with your full home path if your environment doesn’t expand it. Run `bitcompass login` before using MCP.
63
+ Run `bitcompass login` before using MCP. If you added the MCP before logging in, restart the MCP server in Cursor after logging in.
67
64
 
68
65
  ### Development (this repo)
69
66
 
@@ -73,7 +70,7 @@ This repo includes **`.cursor/mcp.json`** so Cursor points at the local CLI when
73
70
  cd packages/bitcompass-cli && npm run build && bitcompass login
74
71
  ```
75
72
 
76
- **Manual (local path):** Settings → MCP → stdio, Command **node**, Args **path/to/packages/bitcompass-cli/dist/index.js** **mcp** **start**. Optionally set env or envFile to the CLI `.env`.
73
+ **Manual (local path):** Settings → MCP → stdio, Command **node**, Args **path/to/packages/bitcompass-cli/dist/index.js** **mcp** **start**.
77
74
 
78
75
  Tools: `search-rules`, `search-solutions`, `post-rules`. Prompts: `share_new_rule`, `share_problem_solution`.
79
76
 
@@ -1,6 +1,13 @@
1
1
  import { type SupabaseClient } from '@supabase/supabase-js';
2
2
  import type { Rule, RuleInsert } from '../types.js';
3
+ /** Shown when MCP is used before logging in; instructs user to login and restart MCP. */
4
+ export declare const AUTH_REQUIRED_MSG = "BitCompass needs authentication. Run `bitcompass login`, then restart the MCP server in your editor.";
5
+ /** Shown when Supabase URL/key are not set; instructs config then login then restart MCP. */
6
+ export declare const NOT_CONFIGURED_MSG = "BitCompass is not configured. Run `bitcompass config set supabaseUrl` and `bitcompass config set supabaseAnonKey`, then `bitcompass login`, then restart the MCP server in your editor.";
7
+ /** Client for writes and authenticated reads (uses token when available). */
3
8
  export declare const getSupabaseClient: () => SupabaseClient | null;
9
+ /** Client for public read-only (rules/solutions). Works without login when RLS allows public select. */
10
+ export declare const getSupabaseClientForRead: () => SupabaseClient | null;
4
11
  export declare const fetchRules: (kind?: "rule" | "solution") => Promise<Rule[]>;
5
12
  export declare const searchRules: (queryText: string, options?: {
6
13
  kind?: "rule" | "solution";
@@ -1,40 +1,75 @@
1
1
  import { createClient } from '@supabase/supabase-js';
2
2
  import { loadConfig, loadCredentials } from '../auth/config.js';
3
- export const getSupabaseClient = () => {
3
+ import { DEFAULT_SUPABASE_ANON_KEY, DEFAULT_SUPABASE_URL } from '../auth/defaults.js';
4
+ /** Shown when MCP is used before logging in; instructs user to login and restart MCP. */
5
+ export const AUTH_REQUIRED_MSG = 'BitCompass needs authentication. Run `bitcompass login`, then restart the MCP server in your editor.';
6
+ /** Shown when Supabase URL/key are not set; instructs config then login then restart MCP. */
7
+ export const NOT_CONFIGURED_MSG = 'BitCompass is not configured. Run `bitcompass config set supabaseUrl` and `bitcompass config set supabaseAnonKey`, then `bitcompass login`, then restart the MCP server in your editor.';
8
+ const isAuthError = (err) => {
9
+ const m = (err.message ?? '').toLowerCase();
10
+ const c = err.code ?? '';
11
+ return (c === 'PGRST301' ||
12
+ c === '401' ||
13
+ m.includes('jwt') ||
14
+ m.includes('row-level security') ||
15
+ m.includes('permission denied') ||
16
+ m.includes('not authenticated'));
17
+ };
18
+ const getSupabaseUrlAndKey = () => {
4
19
  const config = loadConfig();
20
+ const url = config.supabaseUrl ??
21
+ process.env.BITCOMPASS_SUPABASE_URL ??
22
+ DEFAULT_SUPABASE_URL;
23
+ const key = config.supabaseAnonKey ??
24
+ process.env.BITCOMPASS_SUPABASE_ANON_KEY ??
25
+ DEFAULT_SUPABASE_ANON_KEY;
26
+ if (!url || !key)
27
+ return null;
28
+ return { url, key };
29
+ };
30
+ /** Client for writes and authenticated reads (uses token when available). */
31
+ export const getSupabaseClient = () => {
32
+ const pair = getSupabaseUrlAndKey();
33
+ if (!pair)
34
+ return null;
5
35
  const creds = loadCredentials();
6
- const url = config.supabaseUrl ?? process.env.BITCOMPASS_SUPABASE_URL;
7
- const key = config.supabaseAnonKey ?? process.env.BITCOMPASS_SUPABASE_ANON_KEY;
8
- if (!url || !key) {
36
+ const accessToken = creds?.access_token;
37
+ return createClient(pair.url, pair.key, {
38
+ global: accessToken
39
+ ? { headers: { Authorization: `Bearer ${accessToken}` } }
40
+ : undefined,
41
+ });
42
+ };
43
+ /** Client for public read-only (rules/solutions). Works without login when RLS allows public select. */
44
+ export const getSupabaseClientForRead = () => {
45
+ const pair = getSupabaseUrlAndKey();
46
+ if (!pair)
9
47
  return null;
10
- }
48
+ const creds = loadCredentials();
11
49
  const accessToken = creds?.access_token;
12
- const client = createClient(url, key, {
50
+ return createClient(pair.url, pair.key, {
13
51
  global: accessToken
14
52
  ? { headers: { Authorization: `Bearer ${accessToken}` } }
15
53
  : undefined,
16
54
  });
17
- return client;
18
55
  };
19
56
  export const fetchRules = async (kind) => {
20
- const client = getSupabaseClient();
21
- if (!client) {
22
- throw new Error('Supabase not configured. Set BITCOMPASS_SUPABASE_URL and BITCOMPASS_SUPABASE_ANON_KEY or run bitcompass config.');
23
- }
57
+ const client = getSupabaseClientForRead();
58
+ if (!client)
59
+ throw new Error(NOT_CONFIGURED_MSG);
24
60
  let query = client.from('rules').select('*').order('created_at', { ascending: false });
25
61
  if (kind) {
26
62
  query = query.eq('kind', kind);
27
63
  }
28
64
  const { data, error } = await query;
29
65
  if (error)
30
- throw new Error(error.message);
66
+ throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
31
67
  return (data ?? []);
32
68
  };
33
69
  export const searchRules = async (queryText, options = {}) => {
34
- const client = getSupabaseClient();
35
- if (!client) {
36
- throw new Error('Supabase not configured.');
37
- }
70
+ const client = getSupabaseClientForRead();
71
+ if (!client)
72
+ throw new Error(NOT_CONFIGURED_MSG);
38
73
  let query = client
39
74
  .from('rules')
40
75
  .select('*')
@@ -46,44 +81,44 @@ export const searchRules = async (queryText, options = {}) => {
46
81
  }
47
82
  const { data, error } = await query;
48
83
  if (error)
49
- throw new Error(error.message);
84
+ throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
50
85
  return (data ?? []);
51
86
  };
52
87
  export const getRuleById = async (id) => {
53
- const client = getSupabaseClient();
88
+ const client = getSupabaseClientForRead();
54
89
  if (!client)
55
- throw new Error('Supabase not configured.');
90
+ throw new Error(NOT_CONFIGURED_MSG);
56
91
  const { data, error } = await client.from('rules').select('*').eq('id', id).single();
57
92
  if (error) {
58
93
  if (error.code === 'PGRST116')
59
94
  return null;
60
- throw new Error(error.message);
95
+ throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
61
96
  }
62
97
  return data;
63
98
  };
64
99
  export const insertRule = async (rule) => {
65
100
  const client = getSupabaseClient();
66
101
  if (!client)
67
- throw new Error('Supabase not configured.');
102
+ throw new Error(NOT_CONFIGURED_MSG);
68
103
  const { data, error } = await client.from('rules').insert(rule).select().single();
69
104
  if (error)
70
- throw new Error(error.message);
105
+ throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
71
106
  return data;
72
107
  };
73
108
  export const updateRule = async (id, updates) => {
74
109
  const client = getSupabaseClient();
75
110
  if (!client)
76
- throw new Error('Supabase not configured.');
111
+ throw new Error(NOT_CONFIGURED_MSG);
77
112
  const { data, error } = await client.from('rules').update(updates).eq('id', id).select().single();
78
113
  if (error)
79
- throw new Error(error.message);
114
+ throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
80
115
  return data;
81
116
  };
82
117
  export const deleteRule = async (id) => {
83
118
  const client = getSupabaseClient();
84
119
  if (!client)
85
- throw new Error('Supabase not configured.');
120
+ throw new Error(NOT_CONFIGURED_MSG);
86
121
  const { error } = await client.from('rules').delete().eq('id', id);
87
122
  if (error)
88
- throw new Error(error.message);
123
+ throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
89
124
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default Supabase connection for the Company Compass BitCompass CLI.
3
+ * Used when config file and env vars are not set.
4
+ */
5
+ export declare const DEFAULT_SUPABASE_URL = "https://uikzqfzqgqegatmfhhsz.supabase.co";
6
+ export declare const DEFAULT_SUPABASE_ANON_KEY = "sb_publishable_Q_68F2rTNKd8h_2j-UcLOQ_Gq7pTjQC";
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Default Supabase connection for the Company Compass BitCompass CLI.
3
+ * Used when config file and env vars are not set.
4
+ */
5
+ export const DEFAULT_SUPABASE_URL = 'https://uikzqfzqgqegatmfhhsz.supabase.co';
6
+ export const DEFAULT_SUPABASE_ANON_KEY = 'sb_publishable_Q_68F2rTNKd8h_2j-UcLOQ_Gq7pTjQC';
@@ -4,6 +4,7 @@ import { createServer } from 'http';
4
4
  import open from 'open';
5
5
  import ora from 'ora';
6
6
  import { getTokenFilePath, loadConfig, saveCredentials } from '../auth/config.js';
7
+ import { DEFAULT_SUPABASE_ANON_KEY, DEFAULT_SUPABASE_URL } from '../auth/defaults.js';
7
8
  const CALLBACK_PORT = 38473;
8
9
  const createInMemoryStorage = () => {
9
10
  const store = new Map();
@@ -19,8 +20,12 @@ const createInMemoryStorage = () => {
19
20
  };
20
21
  export const runLogin = async () => {
21
22
  const config = loadConfig();
22
- const url = config.supabaseUrl ?? process.env.BITCOMPASS_SUPABASE_URL;
23
- const anonKey = config.supabaseAnonKey ?? process.env.BITCOMPASS_SUPABASE_ANON_KEY;
23
+ const url = config.supabaseUrl ??
24
+ process.env.BITCOMPASS_SUPABASE_URL ??
25
+ DEFAULT_SUPABASE_URL;
26
+ const anonKey = config.supabaseAnonKey ??
27
+ process.env.BITCOMPASS_SUPABASE_ANON_KEY ??
28
+ DEFAULT_SUPABASE_ANON_KEY;
24
29
  if (!url || !anonKey) {
25
30
  console.error(chalk.red('Supabase not configured. Set supabaseUrl and supabaseAnonKey:\n bitcompass config set supabaseUrl https://YOUR_PROJECT.supabase.co\n bitcompass config set supabaseAnonKey YOUR_ANON_KEY\nOr set BITCOMPASS_SUPABASE_URL and BITCOMPASS_SUPABASE_ANON_KEY.'));
26
31
  process.exit(1);
@@ -1,4 +1,4 @@
1
- import { insertRule, searchRules } from '../api/client.js';
1
+ import { AUTH_REQUIRED_MSG, insertRule, searchRules } from '../api/client.js';
2
2
  import { loadCredentials } from '../auth/config.js';
3
3
  function createStdioServer() {
4
4
  const handlers = new Map();
@@ -172,30 +172,36 @@ function createStdioServer() {
172
172
  }
173
173
  };
174
174
  process.stdin.on('data', onData);
175
- // Register tool implementations
175
+ // Register tool implementations (search is public; post requires login)
176
176
  handlers.set('search-rules', async (args) => {
177
- const creds = loadCredentials();
178
- if (!creds?.access_token)
179
- return { error: 'Run bitcompass login first.' };
180
177
  const query = args.query ?? '';
181
178
  const kind = args.kind;
182
179
  const limit = args.limit ?? 20;
183
- const list = await searchRules(query, { kind, limit });
184
- return { rules: list.map((r) => ({ id: r.id, title: r.title, kind: r.kind, snippet: r.body.slice(0, 200) })) };
180
+ try {
181
+ const list = await searchRules(query, { kind, limit });
182
+ return { rules: list.map((r) => ({ id: r.id, title: r.title, kind: r.kind, author: r.author_display_name ?? null, snippet: r.body.slice(0, 200) })) };
183
+ }
184
+ catch (e) {
185
+ const msg = e instanceof Error ? e.message : 'Search failed.';
186
+ return { error: msg };
187
+ }
185
188
  });
186
189
  handlers.set('search-solutions', async (args) => {
187
- const creds = loadCredentials();
188
- if (!creds?.access_token)
189
- return { error: 'Run bitcompass login first.' };
190
190
  const query = args.query ?? '';
191
191
  const limit = args.limit ?? 20;
192
- const list = await searchRules(query, { kind: 'solution', limit });
193
- return { solutions: list.map((r) => ({ id: r.id, title: r.title, snippet: r.body.slice(0, 200) })) };
192
+ try {
193
+ const list = await searchRules(query, { kind: 'solution', limit });
194
+ return { solutions: list.map((r) => ({ id: r.id, title: r.title, author: r.author_display_name ?? null, snippet: r.body.slice(0, 200) })) };
195
+ }
196
+ catch (e) {
197
+ const msg = e instanceof Error ? e.message : 'Search failed.';
198
+ return { error: msg };
199
+ }
194
200
  });
195
201
  handlers.set('post-rules', async (args) => {
196
202
  const creds = loadCredentials();
197
203
  if (!creds?.access_token)
198
- return { error: 'Run bitcompass login first.' };
204
+ return { error: AUTH_REQUIRED_MSG };
199
205
  const payload = {
200
206
  kind: args.kind ?? 'rule',
201
207
  title: args.title ?? 'Untitled',
@@ -205,8 +211,14 @@ function createStdioServer() {
205
211
  examples: Array.isArray(args.examples) ? args.examples : undefined,
206
212
  technologies: Array.isArray(args.technologies) ? args.technologies : undefined,
207
213
  };
208
- const created = await insertRule(payload);
209
- return { id: created.id, title: created.title, success: true };
214
+ try {
215
+ const created = await insertRule(payload);
216
+ return { id: created.id, title: created.title, success: true };
217
+ }
218
+ catch (e) {
219
+ const msg = e instanceof Error ? e.message : 'Failed to publish rule.';
220
+ return { error: msg };
221
+ }
210
222
  });
211
223
  return {
212
224
  async connect() {
@@ -215,11 +227,8 @@ function createStdioServer() {
215
227
  };
216
228
  }
217
229
  export const startMcpServer = async () => {
218
- const creds = loadCredentials();
219
- if (!creds?.access_token) {
220
- process.stderr.write('Not logged in. Run bitcompass login first.\n');
221
- process.exit(1);
222
- }
223
230
  const server = createStdioServer();
224
231
  await server.connect();
232
+ // Do not exit when not logged in: Cursor needs the process alive to complete
233
+ // the MCP handshake. Tools return an auth message (run bitcompass login, then restart MCP) when needed.
225
234
  };
package/dist/types.d.ts CHANGED
@@ -9,6 +9,7 @@ export interface Rule {
9
9
  examples?: string[];
10
10
  technologies?: string[];
11
11
  user_id: string;
12
+ author_display_name?: string | null;
12
13
  created_at: string;
13
14
  updated_at: string;
14
15
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitcompass",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "BitCompass CLI - rules, solutions, and MCP server",
5
5
  "type": "module",
6
6
  "bin": { "bitcompass": "./dist/index.js" },