bitcompass 0.2.8 → 0.3.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 +20 -1
- package/dist/api/client.d.ts +5 -0
- package/dist/api/client.js +28 -0
- package/dist/commands/init.js +152 -3
- package/dist/commands/rules.d.ts +3 -1
- package/dist/commands/rules.js +15 -3
- package/dist/commands/solutions.d.ts +3 -1
- package/dist/commands/solutions.js +15 -3
- package/dist/index.js +10 -2
- package/dist/lib/rule-file-ops.d.ts +11 -0
- package/dist/lib/rule-file-ops.js +39 -0
- package/dist/mcp/server.js +259 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -72,7 +72,26 @@ cd packages/bitcompass-cli && npm run build && bitcompass login
|
|
|
72
72
|
|
|
73
73
|
**Manual (local path):** Settings → MCP → stdio, Command **node**, Args **path/to/packages/bitcompass-cli/dist/index.js** **mcp** **start**.
|
|
74
74
|
|
|
75
|
-
|
|
75
|
+
### MCP Tools
|
|
76
|
+
|
|
77
|
+
**Rules & Solutions:**
|
|
78
|
+
- `search-rules` - Search rules by query (with optional kind filter)
|
|
79
|
+
- `search-solutions` - Search solutions by query
|
|
80
|
+
- `get-rule` - Get full rule/solution details by ID
|
|
81
|
+
- `list-rules` - List all rules/solutions (with optional kind filter and limit)
|
|
82
|
+
- `post-rules` - Create/publish a new rule or solution
|
|
83
|
+
- `update-rule` - Update an existing rule or solution
|
|
84
|
+
- `delete-rule` - Delete a rule or solution by ID
|
|
85
|
+
- `pull-rule` - Pull a rule/solution to a file in the project rules directory
|
|
86
|
+
|
|
87
|
+
**Activity Logs:**
|
|
88
|
+
- `create-activity-log` - Create activity log from git repo (day/week/month)
|
|
89
|
+
- `list-activity-logs` - List user's activity logs (with optional filters)
|
|
90
|
+
- `get-activity-log` - Get activity log details by ID
|
|
91
|
+
|
|
92
|
+
**Prompts:**
|
|
93
|
+
- `share_new_rule` - Guide to collect and publish a reusable rule
|
|
94
|
+
- `share_problem_solution` - Guide to collect and publish a problem solution
|
|
76
95
|
|
|
77
96
|
## Publish (maintainers)
|
|
78
97
|
|
package/dist/api/client.d.ts
CHANGED
|
@@ -18,3 +18,8 @@ export declare const insertRule: (rule: RuleInsert) => Promise<Rule>;
|
|
|
18
18
|
export declare const updateRule: (id: string, updates: Partial<RuleInsert>) => Promise<Rule>;
|
|
19
19
|
export declare const deleteRule: (id: string) => Promise<void>;
|
|
20
20
|
export declare const insertActivityLog: (payload: ActivityLogInsert) => Promise<ActivityLog>;
|
|
21
|
+
export declare const fetchActivityLogs: (options?: {
|
|
22
|
+
limit?: number;
|
|
23
|
+
time_frame?: "day" | "week" | "month";
|
|
24
|
+
}) => Promise<ActivityLog[]>;
|
|
25
|
+
export declare const getActivityLogById: (id: string) => Promise<ActivityLog | null>;
|
package/dist/api/client.js
CHANGED
|
@@ -135,3 +135,31 @@ export const insertActivityLog = async (payload) => {
|
|
|
135
135
|
throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
|
|
136
136
|
return data;
|
|
137
137
|
};
|
|
138
|
+
export const fetchActivityLogs = async (options) => {
|
|
139
|
+
const client = getSupabaseClient();
|
|
140
|
+
if (!client)
|
|
141
|
+
throw new Error(NOT_CONFIGURED_MSG);
|
|
142
|
+
let query = client.from('activity_logs').select('*').order('created_at', { ascending: false });
|
|
143
|
+
if (options?.time_frame) {
|
|
144
|
+
query = query.eq('time_frame', options.time_frame);
|
|
145
|
+
}
|
|
146
|
+
if (options?.limit) {
|
|
147
|
+
query = query.limit(options.limit);
|
|
148
|
+
}
|
|
149
|
+
const { data, error } = await query;
|
|
150
|
+
if (error)
|
|
151
|
+
throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
|
|
152
|
+
return (data ?? []);
|
|
153
|
+
};
|
|
154
|
+
export const getActivityLogById = async (id) => {
|
|
155
|
+
const client = getSupabaseClient();
|
|
156
|
+
if (!client)
|
|
157
|
+
throw new Error(NOT_CONFIGURED_MSG);
|
|
158
|
+
const { data, error } = await client.from('activity_logs').select('*').eq('id', id).single();
|
|
159
|
+
if (error) {
|
|
160
|
+
if (error.code === 'PGRST116')
|
|
161
|
+
return null;
|
|
162
|
+
throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
|
|
163
|
+
}
|
|
164
|
+
return data;
|
|
165
|
+
};
|
package/dist/commands/init.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import inquirer from 'inquirer';
|
|
2
1
|
import chalk from 'chalk';
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
4
|
import { join } from 'path';
|
|
5
|
-
import { getEditorDefaultPath, loadProjectConfig, saveProjectConfig,
|
|
5
|
+
import { getEditorDefaultPath, getProjectConfigDir, loadProjectConfig, saveProjectConfig, } from '../auth/project-config.js';
|
|
6
6
|
const EDITOR_CHOICES = [
|
|
7
7
|
{ name: 'VSCode', value: 'vscode' },
|
|
8
8
|
{ name: 'Cursor', value: 'cursor' },
|
|
@@ -48,9 +48,158 @@ export const runInit = async () => {
|
|
|
48
48
|
};
|
|
49
49
|
saveProjectConfig(config);
|
|
50
50
|
ensureGitignoreEntry();
|
|
51
|
+
// Create rule file about BitCompass MCP and CLI usage
|
|
52
|
+
const outputDir = join(process.cwd(), config.outputPath);
|
|
53
|
+
mkdirSync(outputDir, { recursive: true });
|
|
54
|
+
const rulePath = join(outputDir, 'rule-bitcompass-mcp-and-cli-usage.md');
|
|
55
|
+
const ruleContent = `# BitCompass MCP and CLI Usage
|
|
56
|
+
|
|
57
|
+
This project uses BitCompass for managing rules, solutions, and activity logs. LLMs should use the available MCP tools and CLI commands when appropriate.
|
|
58
|
+
|
|
59
|
+
## MCP Tools (Available in AI Editor)
|
|
60
|
+
|
|
61
|
+
Use these tools directly from the AI editor when available:
|
|
62
|
+
|
|
63
|
+
### search-rules
|
|
64
|
+
Search BitCompass rules by query.
|
|
65
|
+
|
|
66
|
+
**When to use:** When you need to find existing rules or patterns that might help solve a problem.
|
|
67
|
+
|
|
68
|
+
**Parameters:**
|
|
69
|
+
- \`query\` (required): Search query string
|
|
70
|
+
- \`kind\` (optional): Filter by 'rule' or 'solution'
|
|
71
|
+
- \`limit\` (optional): Maximum number of results (default: 20)
|
|
72
|
+
|
|
73
|
+
**Example:** Search for rules about "authentication" or "error handling"
|
|
74
|
+
|
|
75
|
+
### search-solutions
|
|
76
|
+
Search BitCompass solutions by query.
|
|
77
|
+
|
|
78
|
+
**When to use:** When you need to find solutions to specific problems.
|
|
79
|
+
|
|
80
|
+
**Parameters:**
|
|
81
|
+
- \`query\` (required): Search query string
|
|
82
|
+
- \`limit\` (optional): Maximum number of results (default: 20)
|
|
83
|
+
|
|
84
|
+
**Example:** Search for solutions about "database connection issues"
|
|
85
|
+
|
|
86
|
+
### post-rules
|
|
87
|
+
Publish a new rule or solution to BitCompass.
|
|
88
|
+
|
|
89
|
+
**When to use:** When you've created a reusable pattern, best practice, or solution that should be shared with the team.
|
|
90
|
+
|
|
91
|
+
**Parameters:**
|
|
92
|
+
- \`kind\` (required): 'rule' or 'solution'
|
|
93
|
+
- \`title\` (required): Title of the rule/solution
|
|
94
|
+
- \`body\` (required): Full content of the rule/solution
|
|
95
|
+
- \`description\` (optional): Short description
|
|
96
|
+
- \`context\` (optional): Additional context
|
|
97
|
+
- \`examples\` (optional): Array of example strings
|
|
98
|
+
- \`technologies\` (optional): Array of technology tags
|
|
99
|
+
|
|
100
|
+
**Example:** After implementing a new pattern, publish it as a rule so others can reuse it.
|
|
101
|
+
|
|
102
|
+
### create-activity-log
|
|
103
|
+
Collect repository summary and git activity, then push to activity logs.
|
|
104
|
+
|
|
105
|
+
**When to use:** When the user asks to log their work or track activity for a specific time period.
|
|
106
|
+
|
|
107
|
+
**Parameters:**
|
|
108
|
+
- \`time_frame\` (required): 'day', 'week', or 'month'
|
|
109
|
+
- \`repo_path\` (optional): Path to git repo (defaults to current directory)
|
|
110
|
+
|
|
111
|
+
**Example:** User asks "log my work for this week" → call with time_frame: 'week'
|
|
112
|
+
|
|
113
|
+
## CLI Commands (Run via Terminal)
|
|
114
|
+
|
|
115
|
+
Use these commands when you need to interact with BitCompass from the terminal:
|
|
116
|
+
|
|
117
|
+
### Authentication
|
|
118
|
+
- \`bitcompass login\` - Log in with Google (opens browser)
|
|
119
|
+
- \`bitcompass logout\` - Remove stored credentials
|
|
120
|
+
- \`bitcompass whoami\` - Show current user (email)
|
|
121
|
+
|
|
122
|
+
**When to use:** When authentication is required or to verify login status.
|
|
123
|
+
|
|
124
|
+
### Project Configuration
|
|
125
|
+
- \`bitcompass init\` - Configure project: editor/AI provider and output folder
|
|
126
|
+
|
|
127
|
+
**When to use:** Run once per project to set up BitCompass configuration.
|
|
128
|
+
|
|
129
|
+
### Rules Management
|
|
130
|
+
- \`bitcompass rules search [query]\` - Search rules interactively
|
|
131
|
+
- \`bitcompass rules list\` - List all available rules
|
|
132
|
+
- \`bitcompass rules pull [id]\` - Pull a rule by ID (saves to project rules folder)
|
|
133
|
+
- Use \`--global\` flag to install globally to ~/.cursor/rules/
|
|
134
|
+
- \`bitcompass rules push [file]\` - Push a rule file to BitCompass
|
|
135
|
+
|
|
136
|
+
**When to use:**
|
|
137
|
+
- \`pull\`: When you want to download and use a specific rule in your project
|
|
138
|
+
- \`push\`: When you've created a rule file and want to share it
|
|
139
|
+
- \`search\`/\`list\`: When browsing available rules
|
|
140
|
+
|
|
141
|
+
### Solutions Management
|
|
142
|
+
- \`bitcompass solutions search [query]\` - Search solutions interactively
|
|
143
|
+
- \`bitcompass solutions pull [id]\` - Pull a solution by ID
|
|
144
|
+
- Use \`--global\` flag to install globally
|
|
145
|
+
- \`bitcompass solutions push [file]\` - Push a solution file to BitCompass
|
|
146
|
+
|
|
147
|
+
**When to use:** Similar to rules, but for problem-solution pairs.
|
|
148
|
+
|
|
149
|
+
### Activity Logs
|
|
150
|
+
- \`bitcompass log\` - Collect repo summary and git activity, push to activity logs
|
|
151
|
+
- \`bitcompass log YYYY-MM-DD\` - Log activity for a specific date
|
|
152
|
+
- \`bitcompass log YYYY-MM-DD YYYY-MM-DD\` - Log activity for a date range
|
|
153
|
+
|
|
154
|
+
**When to use:** When the user wants to track or log their development activity.
|
|
155
|
+
|
|
156
|
+
### Configuration
|
|
157
|
+
- \`bitcompass config list\` - List all config values
|
|
158
|
+
- \`bitcompass config get <key>\` - Get a specific config value
|
|
159
|
+
- \`bitcompass config set <key> <value>\` - Set config value (supabaseUrl, supabaseAnonKey, apiUrl)
|
|
160
|
+
|
|
161
|
+
**When to use:** When you need to check or modify BitCompass configuration.
|
|
162
|
+
|
|
163
|
+
### MCP Server
|
|
164
|
+
- \`bitcompass mcp start\` - Start MCP server (stdio mode for AI editors)
|
|
165
|
+
- \`bitcompass mcp status\` - Show MCP server status
|
|
166
|
+
|
|
167
|
+
**When to use:** Usually configured automatically by the AI editor. Use \`status\` to verify MCP is working.
|
|
168
|
+
|
|
169
|
+
## Decision Guide
|
|
170
|
+
|
|
171
|
+
**Use MCP tools when:**
|
|
172
|
+
- You're working in the AI editor and need to search or publish rules/solutions
|
|
173
|
+
- The user asks you to search for existing patterns or solutions
|
|
174
|
+
- You've created something reusable and want to share it immediately
|
|
175
|
+
- The user wants to log their activity
|
|
176
|
+
|
|
177
|
+
**Use CLI commands when:**
|
|
178
|
+
- You need to authenticate or verify authentication
|
|
179
|
+
- The user explicitly asks to run a CLI command
|
|
180
|
+
- You need to pull rules/solutions to the project (for version control)
|
|
181
|
+
- You need to check or modify configuration
|
|
182
|
+
- The MCP tool is not available or returns an authentication error
|
|
183
|
+
|
|
184
|
+
## Authentication Notes
|
|
185
|
+
|
|
186
|
+
- Most MCP tools require authentication (except search-rules and search-solutions)
|
|
187
|
+
- If you get an authentication error, instruct the user to run \`bitcompass login\` and restart the MCP server
|
|
188
|
+
- CLI commands that require auth will prompt the user to login
|
|
189
|
+
|
|
190
|
+
## Best Practices
|
|
191
|
+
|
|
192
|
+
1. **Search before creating:** Before publishing a new rule, search to see if something similar exists
|
|
193
|
+
2. **Use descriptive titles:** When posting rules/solutions, use clear, searchable titles
|
|
194
|
+
3. **Include context:** Add context, examples, and technologies when posting to make rules more discoverable
|
|
195
|
+
4. **Pull for version control:** Use \`rules pull\` or \`solutions pull\` to add rules to your project's rules folder (tracked in git)
|
|
196
|
+
5. **Global vs project:** Use \`--global\` flag when you want a rule available across all projects, otherwise use project-specific rules
|
|
197
|
+
`;
|
|
198
|
+
writeFileSync(rulePath, ruleContent, 'utf-8');
|
|
51
199
|
console.log(chalk.green('Project configured.'));
|
|
52
200
|
console.log(chalk.dim('Config:'), join(getProjectConfigDir(), 'config.json'));
|
|
53
201
|
console.log(chalk.dim('Editor:'), config.editor);
|
|
54
202
|
console.log(chalk.dim('Output path:'), config.outputPath);
|
|
55
203
|
console.log(chalk.dim('.gitignore:'), GITIGNORE_ENTRY, 'added or already present.');
|
|
204
|
+
console.log(chalk.green('Rule created:'), rulePath);
|
|
56
205
|
};
|
package/dist/commands/rules.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const runRulesSearch: (query?: string) => Promise<void>;
|
|
2
2
|
export declare const runRulesList: () => Promise<void>;
|
|
3
|
-
export declare const runRulesPull: (id?: string
|
|
3
|
+
export declare const runRulesPull: (id?: string, options?: {
|
|
4
|
+
global?: boolean;
|
|
5
|
+
}) => Promise<void>;
|
|
4
6
|
export declare const runRulesPush: (file?: string) => Promise<void>;
|
package/dist/commands/rules.js
CHANGED
|
@@ -3,6 +3,7 @@ import ora from 'ora';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
6
7
|
import { loadCredentials } from '../auth/config.js';
|
|
7
8
|
import { getProjectConfig } from '../auth/project-config.js';
|
|
8
9
|
import { searchRules, fetchRules, getRuleById, insertRule } from '../api/client.js';
|
|
@@ -46,7 +47,7 @@ export const runRulesList = async () => {
|
|
|
46
47
|
if (list.length === 0)
|
|
47
48
|
console.log(chalk.yellow('No rules yet.'));
|
|
48
49
|
};
|
|
49
|
-
export const runRulesPull = async (id) => {
|
|
50
|
+
export const runRulesPull = async (id, options) => {
|
|
50
51
|
if (!loadCredentials()) {
|
|
51
52
|
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
52
53
|
process.exit(1);
|
|
@@ -70,13 +71,24 @@ export const runRulesPull = async (id) => {
|
|
|
70
71
|
console.error(chalk.red('Rule not found.'));
|
|
71
72
|
process.exit(1);
|
|
72
73
|
}
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
let outDir;
|
|
75
|
+
if (options?.global) {
|
|
76
|
+
// Use global location: ~/.cursor/rules/
|
|
77
|
+
outDir = join(homedir(), '.cursor', 'rules');
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Use project config (default behavior)
|
|
81
|
+
const { outputPath } = getProjectConfig({ warnIfMissing: true });
|
|
82
|
+
outDir = join(process.cwd(), outputPath);
|
|
83
|
+
}
|
|
75
84
|
mkdirSync(outDir, { recursive: true });
|
|
76
85
|
const filename = join(outDir, ruleFilename(rule.title, rule.id));
|
|
77
86
|
const content = `# ${rule.title}\n\n${rule.description}\n\n${rule.body}\n`;
|
|
78
87
|
writeFileSync(filename, content);
|
|
79
88
|
console.log(chalk.green('Wrote'), filename);
|
|
89
|
+
if (options?.global) {
|
|
90
|
+
console.log(chalk.dim('Installed globally for all projects'));
|
|
91
|
+
}
|
|
80
92
|
};
|
|
81
93
|
export const runRulesPush = async (file) => {
|
|
82
94
|
if (!loadCredentials()) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export declare const runSolutionsSearch: (query?: string) => Promise<void>;
|
|
2
|
-
export declare const runSolutionsPull: (id?: string
|
|
2
|
+
export declare const runSolutionsPull: (id?: string, options?: {
|
|
3
|
+
global?: boolean;
|
|
4
|
+
}) => Promise<void>;
|
|
3
5
|
export declare const runSolutionsPush: (file?: string) => Promise<void>;
|
|
@@ -3,6 +3,7 @@ import ora from 'ora';
|
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { join } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
6
7
|
import { loadCredentials } from '../auth/config.js';
|
|
7
8
|
import { getProjectConfig } from '../auth/project-config.js';
|
|
8
9
|
import { searchRules, fetchRules, getRuleById, insertRule } from '../api/client.js';
|
|
@@ -34,7 +35,7 @@ export const runSolutionsSearch = async (query) => {
|
|
|
34
35
|
console.log(rule.body);
|
|
35
36
|
}
|
|
36
37
|
};
|
|
37
|
-
export const runSolutionsPull = async (id) => {
|
|
38
|
+
export const runSolutionsPull = async (id, options) => {
|
|
38
39
|
if (!loadCredentials()) {
|
|
39
40
|
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
40
41
|
process.exit(1);
|
|
@@ -58,13 +59,24 @@ export const runSolutionsPull = async (id) => {
|
|
|
58
59
|
console.error(chalk.red('Solution not found.'));
|
|
59
60
|
process.exit(1);
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
let outDir;
|
|
63
|
+
if (options?.global) {
|
|
64
|
+
// Use global location: ~/.cursor/rules/
|
|
65
|
+
outDir = join(homedir(), '.cursor', 'rules');
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Use project config (default behavior)
|
|
69
|
+
const { outputPath } = getProjectConfig({ warnIfMissing: true });
|
|
70
|
+
outDir = join(process.cwd(), outputPath);
|
|
71
|
+
}
|
|
63
72
|
mkdirSync(outDir, { recursive: true });
|
|
64
73
|
const filename = join(outDir, solutionFilename(rule.title, rule.id));
|
|
65
74
|
const content = `# ${rule.title}\n\n${rule.description}\n\n## Solution\n\n${rule.body}\n`;
|
|
66
75
|
writeFileSync(filename, content);
|
|
67
76
|
console.log(chalk.green('Wrote'), filename);
|
|
77
|
+
if (options?.global) {
|
|
78
|
+
console.log(chalk.dim('Installed globally for all projects'));
|
|
79
|
+
}
|
|
68
80
|
};
|
|
69
81
|
export const runSolutionsPush = async (file) => {
|
|
70
82
|
if (!loadCredentials()) {
|
package/dist/index.js
CHANGED
|
@@ -45,12 +45,20 @@ configCmd.command('get <key>').description('Get a config value').action((key) =>
|
|
|
45
45
|
const rules = program.command('rules').description('Manage rules');
|
|
46
46
|
rules.command('search [query]').description('Search rules').action((query) => runRulesSearch(query).catch(handleErr));
|
|
47
47
|
rules.command('list').description('List rules').action(() => runRulesList().catch(handleErr));
|
|
48
|
-
rules
|
|
48
|
+
rules
|
|
49
|
+
.command('pull [id]')
|
|
50
|
+
.description('Pull a rule by ID or choose from list')
|
|
51
|
+
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
52
|
+
.action((id, options) => runRulesPull(id, options).catch(handleErr));
|
|
49
53
|
rules.command('push [file]').description('Push a rule (file or interactive)').action((file) => runRulesPush(file).catch(handleErr));
|
|
50
54
|
// solutions
|
|
51
55
|
const solutions = program.command('solutions').description('Manage solutions');
|
|
52
56
|
solutions.command('search [query]').description('Search solutions').action((query) => runSolutionsSearch(query).catch(handleErr));
|
|
53
|
-
solutions
|
|
57
|
+
solutions
|
|
58
|
+
.command('pull [id]')
|
|
59
|
+
.description('Pull a solution by ID or choose from list')
|
|
60
|
+
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
61
|
+
.action((id, options) => runSolutionsPull(id, options).catch(handleErr));
|
|
54
62
|
solutions.command('push [file]').description('Push a solution (file or interactive)').action((file) => runSolutionsPush(file).catch(handleErr));
|
|
55
63
|
// mcp
|
|
56
64
|
const mcp = program.command('mcp').description('MCP server');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface PullRuleOptions {
|
|
2
|
+
/** Install globally to ~/.cursor/rules/ for all projects */
|
|
3
|
+
global?: boolean;
|
|
4
|
+
/** Custom output path (overrides project config and global) */
|
|
5
|
+
outputPath?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Pulls a rule or solution to a file. Returns the file path where it was written.
|
|
9
|
+
* Throws if rule not found or if authentication is required.
|
|
10
|
+
*/
|
|
11
|
+
export declare const pullRuleToFile: (id: string, options?: PullRuleOptions) => Promise<string>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { getRuleById } from '../api/client.js';
|
|
5
|
+
import { getProjectConfig } from '../auth/project-config.js';
|
|
6
|
+
import { ruleFilename, solutionFilename } from './slug.js';
|
|
7
|
+
/**
|
|
8
|
+
* Pulls a rule or solution to a file. Returns the file path where it was written.
|
|
9
|
+
* Throws if rule not found or if authentication is required.
|
|
10
|
+
*/
|
|
11
|
+
export const pullRuleToFile = async (id, options = {}) => {
|
|
12
|
+
const rule = await getRuleById(id);
|
|
13
|
+
if (!rule) {
|
|
14
|
+
throw new Error(`Rule or solution with ID ${id} not found.`);
|
|
15
|
+
}
|
|
16
|
+
let outDir;
|
|
17
|
+
if (options.outputPath) {
|
|
18
|
+
// Custom output path takes precedence
|
|
19
|
+
outDir = options.outputPath.startsWith('/') ? options.outputPath : join(process.cwd(), options.outputPath);
|
|
20
|
+
}
|
|
21
|
+
else if (options.global) {
|
|
22
|
+
// Use global location: ~/.cursor/rules/
|
|
23
|
+
outDir = join(homedir(), '.cursor', 'rules');
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// Use project config (default behavior)
|
|
27
|
+
const { outputPath } = getProjectConfig({ warnIfMissing: true });
|
|
28
|
+
outDir = join(process.cwd(), outputPath);
|
|
29
|
+
}
|
|
30
|
+
mkdirSync(outDir, { recursive: true });
|
|
31
|
+
const filename = rule.kind === 'solution'
|
|
32
|
+
? join(outDir, solutionFilename(rule.title, rule.id))
|
|
33
|
+
: join(outDir, ruleFilename(rule.title, rule.id));
|
|
34
|
+
const content = rule.kind === 'solution'
|
|
35
|
+
? `# ${rule.title}\n\n${rule.description}\n\n## Solution\n\n${rule.body}\n`
|
|
36
|
+
: `# ${rule.title}\n\n${rule.description}\n\n${rule.body}\n`;
|
|
37
|
+
writeFileSync(filename, content);
|
|
38
|
+
return filename;
|
|
39
|
+
};
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { AUTH_REQUIRED_MSG, insertRule, searchRules } from '../api/client.js';
|
|
1
|
+
import { AUTH_REQUIRED_MSG, insertRule, searchRules, getRuleById, fetchRules, updateRule, deleteRule, fetchActivityLogs, getActivityLogById, } from '../api/client.js';
|
|
2
2
|
import { buildAndPushActivityLog } from '../commands/log.js';
|
|
3
3
|
import { loadCredentials } from '../auth/config.js';
|
|
4
4
|
import { getProjectConfig } from '../auth/project-config.js';
|
|
5
|
+
import { pullRuleToFile } from '../lib/rule-file-ops.js';
|
|
5
6
|
/** When token is missing, we fail initialize so Cursor shows "Needs authentication" (yellow) instead of success (green). */
|
|
6
7
|
const NEEDS_AUTH_ERROR_MESSAGE = 'Needs authentication';
|
|
7
8
|
const NEEDS_AUTH_ERROR_CODE = -32001; // Server error: auth required
|
|
@@ -101,6 +102,92 @@ function createStdioServer() {
|
|
|
101
102
|
required: ['time_frame'],
|
|
102
103
|
},
|
|
103
104
|
},
|
|
105
|
+
{
|
|
106
|
+
name: 'get-rule',
|
|
107
|
+
description: 'Get full details of a rule or solution by ID',
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
id: { type: 'string', description: 'Rule/solution ID' },
|
|
112
|
+
kind: { type: 'string', enum: ['rule', 'solution'], description: 'Optional: filter by kind' },
|
|
113
|
+
},
|
|
114
|
+
required: ['id'],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'list-rules',
|
|
119
|
+
description: 'List all rules and solutions (with optional filtering by kind)',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
kind: { type: 'string', enum: ['rule', 'solution'], description: 'Optional: filter by kind' },
|
|
124
|
+
limit: { type: 'number', description: 'Optional: maximum number of results (default: 50)' },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'update-rule',
|
|
130
|
+
description: 'Update an existing rule or solution',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
id: { type: 'string', description: 'Rule/solution ID to update' },
|
|
135
|
+
title: { type: 'string', description: 'Updated title' },
|
|
136
|
+
description: { type: 'string', description: 'Updated description' },
|
|
137
|
+
body: { type: 'string', description: 'Updated body content' },
|
|
138
|
+
context: { type: 'string', description: 'Updated context' },
|
|
139
|
+
examples: { type: 'array', items: { type: 'string' }, description: 'Updated examples array' },
|
|
140
|
+
technologies: { type: 'array', items: { type: 'string' }, description: 'Updated technologies array' },
|
|
141
|
+
},
|
|
142
|
+
required: ['id'],
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'delete-rule',
|
|
147
|
+
description: 'Delete a rule or solution by ID',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
id: { type: 'string', description: 'Rule/solution ID to delete' },
|
|
152
|
+
},
|
|
153
|
+
required: ['id'],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'pull-rule',
|
|
158
|
+
description: 'Pull a rule or solution to a file in the project rules directory',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: 'object',
|
|
161
|
+
properties: {
|
|
162
|
+
id: { type: 'string', description: 'Rule/solution ID to pull' },
|
|
163
|
+
output_path: { type: 'string', description: 'Optional: custom output path' },
|
|
164
|
+
global: { type: 'boolean', description: 'Install globally to ~/.cursor/rules/' },
|
|
165
|
+
},
|
|
166
|
+
required: ['id'],
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: 'list-activity-logs',
|
|
171
|
+
description: "List user's activity logs",
|
|
172
|
+
inputSchema: {
|
|
173
|
+
type: 'object',
|
|
174
|
+
properties: {
|
|
175
|
+
limit: { type: 'number', description: 'Optional: maximum number of results (default: 20)' },
|
|
176
|
+
time_frame: { type: 'string', enum: ['day', 'week', 'month'], description: 'Optional: filter by time frame' },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'get-activity-log',
|
|
182
|
+
description: 'Get activity log details by ID',
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {
|
|
186
|
+
id: { type: 'string', description: 'Activity log ID' },
|
|
187
|
+
},
|
|
188
|
+
required: ['id'],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
104
191
|
],
|
|
105
192
|
},
|
|
106
193
|
});
|
|
@@ -270,6 +357,177 @@ function createStdioServer() {
|
|
|
270
357
|
return { error: msg };
|
|
271
358
|
}
|
|
272
359
|
});
|
|
360
|
+
handlers.set('get-rule', async (args) => {
|
|
361
|
+
const id = args.id;
|
|
362
|
+
if (!id)
|
|
363
|
+
return { error: 'id is required' };
|
|
364
|
+
const kind = args.kind;
|
|
365
|
+
try {
|
|
366
|
+
const rule = await getRuleById(id);
|
|
367
|
+
if (!rule) {
|
|
368
|
+
return { error: `Rule or solution with ID ${id} not found.` };
|
|
369
|
+
}
|
|
370
|
+
if (kind && rule.kind !== kind) {
|
|
371
|
+
return { error: `Rule with ID ${id} is a ${rule.kind}, not a ${kind}.` };
|
|
372
|
+
}
|
|
373
|
+
return {
|
|
374
|
+
id: rule.id,
|
|
375
|
+
kind: rule.kind,
|
|
376
|
+
title: rule.title,
|
|
377
|
+
description: rule.description,
|
|
378
|
+
body: rule.body,
|
|
379
|
+
context: rule.context ?? null,
|
|
380
|
+
examples: rule.examples ?? [],
|
|
381
|
+
technologies: rule.technologies ?? [],
|
|
382
|
+
author: rule.author_display_name ?? null,
|
|
383
|
+
created_at: rule.created_at,
|
|
384
|
+
updated_at: rule.updated_at,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
const msg = e instanceof Error ? e.message : 'Failed to get rule.';
|
|
389
|
+
return { error: msg };
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
handlers.set('list-rules', async (args) => {
|
|
393
|
+
const kind = args.kind;
|
|
394
|
+
const limit = args.limit ?? 50;
|
|
395
|
+
try {
|
|
396
|
+
const list = await fetchRules(kind);
|
|
397
|
+
const limited = list.slice(0, limit);
|
|
398
|
+
return {
|
|
399
|
+
rules: limited.map((r) => ({
|
|
400
|
+
id: r.id,
|
|
401
|
+
title: r.title,
|
|
402
|
+
kind: r.kind,
|
|
403
|
+
description: r.description,
|
|
404
|
+
author: r.author_display_name ?? null,
|
|
405
|
+
snippet: r.body.slice(0, 200),
|
|
406
|
+
created_at: r.created_at,
|
|
407
|
+
})),
|
|
408
|
+
total: list.length,
|
|
409
|
+
returned: limited.length,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
const msg = e instanceof Error ? e.message : 'Failed to list rules.';
|
|
414
|
+
return { error: msg };
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
handlers.set('update-rule', async (args) => {
|
|
418
|
+
if (!loadCredentials()?.access_token)
|
|
419
|
+
return { error: AUTH_REQUIRED_MSG };
|
|
420
|
+
const id = args.id;
|
|
421
|
+
if (!id)
|
|
422
|
+
return { error: 'id is required' };
|
|
423
|
+
const updates = {};
|
|
424
|
+
if (args.title !== undefined)
|
|
425
|
+
updates.title = args.title;
|
|
426
|
+
if (args.description !== undefined)
|
|
427
|
+
updates.description = args.description;
|
|
428
|
+
if (args.body !== undefined)
|
|
429
|
+
updates.body = args.body;
|
|
430
|
+
if (args.context !== undefined)
|
|
431
|
+
updates.context = args.context || undefined;
|
|
432
|
+
if (Array.isArray(args.examples))
|
|
433
|
+
updates.examples = args.examples;
|
|
434
|
+
if (Array.isArray(args.technologies))
|
|
435
|
+
updates.technologies = args.technologies;
|
|
436
|
+
try {
|
|
437
|
+
const updated = await updateRule(id, updates);
|
|
438
|
+
return {
|
|
439
|
+
id: updated.id,
|
|
440
|
+
title: updated.title,
|
|
441
|
+
kind: updated.kind,
|
|
442
|
+
success: true,
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
catch (e) {
|
|
446
|
+
const msg = e instanceof Error ? e.message : 'Failed to update rule.';
|
|
447
|
+
return { error: msg };
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
handlers.set('delete-rule', async (args) => {
|
|
451
|
+
if (!loadCredentials()?.access_token)
|
|
452
|
+
return { error: AUTH_REQUIRED_MSG };
|
|
453
|
+
const id = args.id;
|
|
454
|
+
if (!id)
|
|
455
|
+
return { error: 'id is required' };
|
|
456
|
+
try {
|
|
457
|
+
await deleteRule(id);
|
|
458
|
+
return { success: true, id };
|
|
459
|
+
}
|
|
460
|
+
catch (e) {
|
|
461
|
+
const msg = e instanceof Error ? e.message : 'Failed to delete rule.';
|
|
462
|
+
return { error: msg };
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
handlers.set('pull-rule', async (args) => {
|
|
466
|
+
if (!loadCredentials()?.access_token)
|
|
467
|
+
return { error: AUTH_REQUIRED_MSG };
|
|
468
|
+
const id = args.id;
|
|
469
|
+
if (!id)
|
|
470
|
+
return { error: 'id is required' };
|
|
471
|
+
const global = Boolean(args.global);
|
|
472
|
+
const outputPath = typeof args.output_path === 'string' ? args.output_path : undefined;
|
|
473
|
+
try {
|
|
474
|
+
const filePath = await pullRuleToFile(id, { global, outputPath });
|
|
475
|
+
return { success: true, file_path: filePath, id };
|
|
476
|
+
}
|
|
477
|
+
catch (e) {
|
|
478
|
+
const msg = e instanceof Error ? e.message : 'Failed to pull rule.';
|
|
479
|
+
return { error: msg };
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
handlers.set('list-activity-logs', async (args) => {
|
|
483
|
+
if (!loadCredentials()?.access_token)
|
|
484
|
+
return { error: AUTH_REQUIRED_MSG };
|
|
485
|
+
const limit = args.limit ?? 20;
|
|
486
|
+
const timeFrame = args.time_frame;
|
|
487
|
+
try {
|
|
488
|
+
const logs = await fetchActivityLogs({ limit, time_frame: timeFrame });
|
|
489
|
+
return {
|
|
490
|
+
logs: logs.map((log) => ({
|
|
491
|
+
id: log.id,
|
|
492
|
+
time_frame: log.time_frame,
|
|
493
|
+
period_start: log.period_start,
|
|
494
|
+
period_end: log.period_end,
|
|
495
|
+
created_at: log.created_at,
|
|
496
|
+
})),
|
|
497
|
+
total: logs.length,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
catch (e) {
|
|
501
|
+
const msg = e instanceof Error ? e.message : 'Failed to list activity logs.';
|
|
502
|
+
return { error: msg };
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
handlers.set('get-activity-log', async (args) => {
|
|
506
|
+
if (!loadCredentials()?.access_token)
|
|
507
|
+
return { error: AUTH_REQUIRED_MSG };
|
|
508
|
+
const id = args.id;
|
|
509
|
+
if (!id)
|
|
510
|
+
return { error: 'id is required' };
|
|
511
|
+
try {
|
|
512
|
+
const log = await getActivityLogById(id);
|
|
513
|
+
if (!log) {
|
|
514
|
+
return { error: `Activity log with ID ${id} not found.` };
|
|
515
|
+
}
|
|
516
|
+
return {
|
|
517
|
+
id: log.id,
|
|
518
|
+
time_frame: log.time_frame,
|
|
519
|
+
period_start: log.period_start,
|
|
520
|
+
period_end: log.period_end,
|
|
521
|
+
repo_summary: log.repo_summary,
|
|
522
|
+
git_analysis: log.git_analysis,
|
|
523
|
+
created_at: log.created_at,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
catch (e) {
|
|
527
|
+
const msg = e instanceof Error ? e.message : 'Failed to get activity log.';
|
|
528
|
+
return { error: msg };
|
|
529
|
+
}
|
|
530
|
+
});
|
|
273
531
|
return {
|
|
274
532
|
async connect() {
|
|
275
533
|
// Stdio listener already attached; keep process alive
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bitcompass",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "BitCompass CLI - rules, solutions, and MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"dev": "tsc --watch",
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
|
+
"publish": "npm publish --access public",
|
|
13
14
|
"prepare": "npm run build",
|
|
14
15
|
"postinstall": "node scripts/postinstall.js"
|
|
15
16
|
},
|