bitcompass 0.3.4 → 0.3.6
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/dist/commands/commands.d.ts +11 -0
- package/dist/commands/commands.js +129 -0
- package/dist/commands/config-cmd.js +2 -2
- package/dist/commands/glossary.d.ts +1 -0
- package/dist/commands/glossary.js +16 -0
- package/dist/commands/log.d.ts +8 -2
- package/dist/commands/log.js +48 -7
- package/dist/commands/login.js +60 -0
- package/dist/commands/rules.d.ts +6 -2
- package/dist/commands/rules.js +15 -6
- package/dist/commands/skills.d.ts +11 -0
- package/dist/commands/skills.js +129 -0
- package/dist/commands/solutions.d.ts +6 -1
- package/dist/commands/solutions.js +24 -3
- package/dist/index.js +101 -7
- package/dist/lib/git-analysis.d.ts +5 -0
- package/dist/lib/git-analysis.js +4 -3
- package/dist/lib/list-format.d.ts +15 -0
- package/dist/lib/list-format.js +34 -0
- package/dist/lib/rule-file-ops.js +33 -7
- package/dist/lib/slug.d.ts +10 -0
- package/dist/lib/slug.js +18 -0
- package/dist/mcp/server.js +44 -17
- package/glossary.md +19 -0
- package/package.json +3 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const runCommandsSearch: (query?: string, options?: {
|
|
2
|
+
listOnly?: boolean;
|
|
3
|
+
}) => Promise<void>;
|
|
4
|
+
export declare const runCommandsList: (options?: {
|
|
5
|
+
table?: boolean;
|
|
6
|
+
}) => Promise<void>;
|
|
7
|
+
export declare const runCommandsPull: (id?: string, options?: {
|
|
8
|
+
global?: boolean;
|
|
9
|
+
copy?: boolean;
|
|
10
|
+
}) => Promise<void>;
|
|
11
|
+
export declare const runCommandsPush: (file?: string) => Promise<void>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadCredentials } from '../auth/config.js';
|
|
5
|
+
import { searchRules, fetchRules, getRuleById, insertRule } from '../api/client.js';
|
|
6
|
+
import { pullRuleToFile } from '../lib/rule-file-ops.js';
|
|
7
|
+
import { formatList, shouldUseTable } from '../lib/list-format.js';
|
|
8
|
+
export const runCommandsSearch = async (query, options) => {
|
|
9
|
+
if (!loadCredentials()) {
|
|
10
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const q = query ?? (await inquirer.prompt([{ name: 'q', message: 'Search query', type: 'input' }])).q;
|
|
14
|
+
const spinner = ora('Searching commands…').start();
|
|
15
|
+
const list = await searchRules(q, { kind: 'command', limit: 20 });
|
|
16
|
+
spinner.stop();
|
|
17
|
+
if (list.length === 0) {
|
|
18
|
+
console.log(chalk.yellow('No commands found.'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (options?.listOnly) {
|
|
22
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable: shouldUseTable(), showKind: true });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const choice = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
name: 'id',
|
|
28
|
+
message: 'Select a command',
|
|
29
|
+
type: 'list',
|
|
30
|
+
choices: list.map((r) => ({ name: `${r.title} (${r.id})`, value: r.id })),
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
const rule = await getRuleById(choice.id);
|
|
34
|
+
if (rule) {
|
|
35
|
+
console.log(chalk.cyan(rule.title));
|
|
36
|
+
console.log(rule.body);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export const runCommandsList = async (options) => {
|
|
40
|
+
if (!loadCredentials()) {
|
|
41
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const spinner = ora('Loading commands…').start();
|
|
45
|
+
const list = await fetchRules('command');
|
|
46
|
+
spinner.stop();
|
|
47
|
+
if (list.length === 0) {
|
|
48
|
+
console.log(chalk.yellow('No commands yet.'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const useTable = shouldUseTable(options?.table);
|
|
52
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable, showKind: false });
|
|
53
|
+
};
|
|
54
|
+
export const runCommandsPull = async (id, options) => {
|
|
55
|
+
if (!loadCredentials()) {
|
|
56
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
let targetId = id;
|
|
60
|
+
if (!targetId) {
|
|
61
|
+
const spinner = ora('Loading commands…').start();
|
|
62
|
+
const list = await fetchRules('command');
|
|
63
|
+
spinner.stop();
|
|
64
|
+
if (list.length === 0) {
|
|
65
|
+
console.log(chalk.yellow('No commands to pull.'));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const choice = await inquirer.prompt([
|
|
69
|
+
{ name: 'id', message: 'Select command', type: 'list', choices: list.map((r) => ({ name: r.title, value: r.id })) },
|
|
70
|
+
]);
|
|
71
|
+
targetId = choice.id;
|
|
72
|
+
}
|
|
73
|
+
const spinner = ora('Pulling command…').start();
|
|
74
|
+
try {
|
|
75
|
+
const filename = await pullRuleToFile(targetId, {
|
|
76
|
+
global: options?.global,
|
|
77
|
+
useSymlink: !options?.copy, // Use symlink unless --copy flag is set
|
|
78
|
+
});
|
|
79
|
+
spinner.succeed(chalk.green('Pulled command'));
|
|
80
|
+
console.log(chalk.dim(filename));
|
|
81
|
+
if (options?.copy) {
|
|
82
|
+
console.log(chalk.dim('Copied as file (not a symlink)'));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(chalk.dim('Created symbolic link to cached command'));
|
|
86
|
+
}
|
|
87
|
+
if (options?.global) {
|
|
88
|
+
console.log(chalk.dim('Installed globally for all projects'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
spinner.fail(chalk.red('Failed to pull command'));
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
console.error(chalk.red(message));
|
|
95
|
+
process.exit(message.includes('not found') ? 2 : 1);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
export const runCommandsPush = async (file) => {
|
|
99
|
+
if (!loadCredentials()) {
|
|
100
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
let payload;
|
|
104
|
+
if (file) {
|
|
105
|
+
const { readFileSync } = await import('fs');
|
|
106
|
+
const raw = readFileSync(file, 'utf-8');
|
|
107
|
+
try {
|
|
108
|
+
payload = JSON.parse(raw);
|
|
109
|
+
payload.kind = 'command';
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
const lines = raw.split('\n');
|
|
113
|
+
const title = lines[0].replace(/^#\s*/, '') || 'Untitled';
|
|
114
|
+
payload = { kind: 'command', title, description: '', body: raw };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const answers = await inquirer.prompt([
|
|
119
|
+
{ name: 'title', message: 'Command title', type: 'input', default: 'Untitled' },
|
|
120
|
+
{ name: 'description', message: 'Description', type: 'input', default: '' },
|
|
121
|
+
{ name: 'body', message: 'Command content', type: 'editor', default: '' },
|
|
122
|
+
]);
|
|
123
|
+
payload = { kind: 'command', title: answers.title, description: answers.description, body: answers.body };
|
|
124
|
+
}
|
|
125
|
+
const spinner = ora('Publishing command…').start();
|
|
126
|
+
const created = await insertRule(payload);
|
|
127
|
+
spinner.succeed(chalk.green('Published command ') + created.id);
|
|
128
|
+
console.log(chalk.dim(created.title));
|
|
129
|
+
};
|
|
@@ -16,7 +16,7 @@ export const runConfigSet = (key, value) => {
|
|
|
16
16
|
const config = loadConfig();
|
|
17
17
|
if (!CONFIG_KEYS.includes(key)) {
|
|
18
18
|
console.error(chalk.red('Unknown key. Use one of:'), CONFIG_KEYS.join(', '));
|
|
19
|
-
process.exit(
|
|
19
|
+
process.exit(2);
|
|
20
20
|
}
|
|
21
21
|
config[key] = value;
|
|
22
22
|
saveConfig(config);
|
|
@@ -26,7 +26,7 @@ export const runConfigGet = (key) => {
|
|
|
26
26
|
const config = loadConfig();
|
|
27
27
|
if (!CONFIG_KEYS.includes(key)) {
|
|
28
28
|
console.error(chalk.red('Unknown key.'));
|
|
29
|
-
process.exit(
|
|
29
|
+
process.exit(2);
|
|
30
30
|
}
|
|
31
31
|
const val = config[key] ?? process.env[`BITCOMPASS_${key.toUpperCase()}`];
|
|
32
32
|
console.log(val ?? '');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const runGlossary: () => void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { readFileSync, existsSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
/** Path to glossary.md in the CLI package (works when run from source or installed). */
|
|
7
|
+
const getGlossaryPath = () => join(__dirname, '..', '..', 'glossary.md');
|
|
8
|
+
export const runGlossary = () => {
|
|
9
|
+
const path = getGlossaryPath();
|
|
10
|
+
if (!existsSync(path)) {
|
|
11
|
+
console.error(chalk.red('Glossary file not found.'));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const content = readFileSync(path, 'utf8');
|
|
15
|
+
console.log(content.trim());
|
|
16
|
+
};
|
package/dist/commands/log.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { TimeFrame } from '../lib/git-analysis.js';
|
|
2
|
+
export type LogProgressStep = 'analyzing' | 'pushing';
|
|
2
3
|
/**
|
|
3
4
|
* Shared logic: resolve repo, compute period, gather summary + git analysis, insert log.
|
|
4
5
|
* Used by both CLI and MCP. Returns the created log id or throws.
|
|
6
|
+
* Optional onProgress callback for CLI to show step-wise spinner (e.g. analyzing → pushing).
|
|
5
7
|
*/
|
|
6
|
-
export declare const buildAndPushActivityLog: (timeFrame: TimeFrame, cwd: string) => Promise<{
|
|
8
|
+
export declare const buildAndPushActivityLog: (timeFrame: TimeFrame, cwd: string, onProgress?: (step: LogProgressStep) => void) => Promise<{
|
|
7
9
|
id: string;
|
|
8
10
|
}>;
|
|
9
11
|
/**
|
|
@@ -13,7 +15,7 @@ export declare const buildAndPushActivityLogWithPeriod: (period: {
|
|
|
13
15
|
period_start: string;
|
|
14
16
|
period_end: string;
|
|
15
17
|
since: string;
|
|
16
|
-
}, timeFrame: TimeFrame, cwd: string) => Promise<{
|
|
18
|
+
}, timeFrame: TimeFrame, cwd: string, onProgress?: (step: LogProgressStep) => void) => Promise<{
|
|
17
19
|
id: string;
|
|
18
20
|
}>;
|
|
19
21
|
/** Parse argv for log: [start] or [start, end] or [start, '-', end]. Returns { start, end } or null for interactive. */
|
|
@@ -21,4 +23,8 @@ export declare const parseLogArgs: (args: string[]) => {
|
|
|
21
23
|
start: string;
|
|
22
24
|
end?: string;
|
|
23
25
|
} | null;
|
|
26
|
+
/** Thrown for invalid date args; CLI should exit with code 2. */
|
|
27
|
+
export declare class ValidationError extends Error {
|
|
28
|
+
constructor(message: string);
|
|
29
|
+
}
|
|
24
30
|
export declare const runLog: (args?: string[]) => Promise<void>;
|
package/dist/commands/log.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
4
|
import { insertActivityLog } from '../api/client.js';
|
|
4
5
|
import { loadCredentials } from '../auth/config.js';
|
|
5
|
-
import { getRepoRoot, getRepoSummary, getGitAnalysis, getPeriodForTimeFrame, getPeriodForCustomDates, } from '../lib/git-analysis.js';
|
|
6
|
+
import { getRepoRoot, getRepoSummary, getGitAnalysis, getPeriodForTimeFrame, getPeriodForCustomDates, parseDate, } from '../lib/git-analysis.js';
|
|
6
7
|
/**
|
|
7
8
|
* Shared logic: resolve repo, compute period, gather summary + git analysis, insert log.
|
|
8
9
|
* Used by both CLI and MCP. Returns the created log id or throws.
|
|
10
|
+
* Optional onProgress callback for CLI to show step-wise spinner (e.g. analyzing → pushing).
|
|
9
11
|
*/
|
|
10
|
-
export const buildAndPushActivityLog = async (timeFrame, cwd) => {
|
|
12
|
+
export const buildAndPushActivityLog = async (timeFrame, cwd, onProgress) => {
|
|
11
13
|
const repoRoot = getRepoRoot(cwd);
|
|
12
14
|
if (!repoRoot) {
|
|
13
15
|
throw new Error('Not a git repository. Run from a project with git or pass a valid repo path.');
|
|
14
16
|
}
|
|
15
17
|
const period = getPeriodForTimeFrame(timeFrame);
|
|
18
|
+
onProgress?.('analyzing');
|
|
16
19
|
const repo_summary = getRepoSummary(repoRoot);
|
|
17
20
|
const git_analysis = getGitAnalysis(repoRoot, period.since);
|
|
18
21
|
const payload = {
|
|
@@ -22,17 +25,19 @@ export const buildAndPushActivityLog = async (timeFrame, cwd) => {
|
|
|
22
25
|
repo_summary: repo_summary,
|
|
23
26
|
git_analysis: git_analysis,
|
|
24
27
|
};
|
|
28
|
+
onProgress?.('pushing');
|
|
25
29
|
const created = await insertActivityLog(payload);
|
|
26
30
|
return { id: created.id };
|
|
27
31
|
};
|
|
28
32
|
/**
|
|
29
33
|
* Push an activity log for a custom date or date range. timeFrame is used for display (day/week/month).
|
|
30
34
|
*/
|
|
31
|
-
export const buildAndPushActivityLogWithPeriod = async (period, timeFrame, cwd) => {
|
|
35
|
+
export const buildAndPushActivityLogWithPeriod = async (period, timeFrame, cwd, onProgress) => {
|
|
32
36
|
const repoRoot = getRepoRoot(cwd);
|
|
33
37
|
if (!repoRoot) {
|
|
34
38
|
throw new Error('Not a git repository. Run from a project with git or pass a valid repo path.');
|
|
35
39
|
}
|
|
40
|
+
onProgress?.('analyzing');
|
|
36
41
|
const repo_summary = getRepoSummary(repoRoot);
|
|
37
42
|
const git_analysis = getGitAnalysis(repoRoot, period.since, period.period_end);
|
|
38
43
|
const payload = {
|
|
@@ -42,6 +47,7 @@ export const buildAndPushActivityLogWithPeriod = async (period, timeFrame, cwd)
|
|
|
42
47
|
repo_summary: repo_summary,
|
|
43
48
|
git_analysis: git_analysis,
|
|
44
49
|
};
|
|
50
|
+
onProgress?.('pushing');
|
|
45
51
|
const created = await insertActivityLog(payload);
|
|
46
52
|
return { id: created.id };
|
|
47
53
|
};
|
|
@@ -62,6 +68,13 @@ export const parseLogArgs = (args) => {
|
|
|
62
68
|
}
|
|
63
69
|
throw new Error('Usage: bitcompass log [YYYY-MM-DD] or bitcompass log [YYYY-MM-DD] [YYYY-MM-DD] or bitcompass log [YYYY-MM-DD] - [YYYY-MM-DD]');
|
|
64
70
|
};
|
|
71
|
+
/** Thrown for invalid date args; CLI should exit with code 2. */
|
|
72
|
+
export class ValidationError extends Error {
|
|
73
|
+
constructor(message) {
|
|
74
|
+
super(message);
|
|
75
|
+
this.name = 'ValidationError';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
65
78
|
/** Choose time_frame for a custom range by span (day ≤ 1, week ≤ 7, else month). */
|
|
66
79
|
const timeFrameForRange = (start, end) => {
|
|
67
80
|
const a = new Date(start);
|
|
@@ -85,13 +98,33 @@ export const runLog = async (args = []) => {
|
|
|
85
98
|
process.exit(1);
|
|
86
99
|
}
|
|
87
100
|
const parsed = parseLogArgs(args);
|
|
101
|
+
const spinner = ora('Analyzing repository…').start();
|
|
102
|
+
const onProgress = (step) => {
|
|
103
|
+
spinner.text = step === 'analyzing' ? 'Analyzing repository…' : 'Pushing activity log…';
|
|
104
|
+
};
|
|
88
105
|
if (parsed) {
|
|
106
|
+
if (!parseDate(parsed.start)) {
|
|
107
|
+
spinner.stop();
|
|
108
|
+
throw new ValidationError(`Invalid date "${parsed.start}". Use YYYY-MM-DD (e.g. 2025-02-06).`);
|
|
109
|
+
}
|
|
110
|
+
if (parsed.end !== undefined && !parseDate(parsed.end)) {
|
|
111
|
+
spinner.stop();
|
|
112
|
+
throw new ValidationError(`Invalid date "${parsed.end}". Use YYYY-MM-DD (e.g. 2025-02-06).`);
|
|
113
|
+
}
|
|
89
114
|
const period = getPeriodForCustomDates(parsed.start, parsed.end);
|
|
90
115
|
const timeFrame = parsed.end ? timeFrameForRange(parsed.start, parsed.end) : 'day';
|
|
91
|
-
|
|
92
|
-
|
|
116
|
+
try {
|
|
117
|
+
const result = await buildAndPushActivityLogWithPeriod(period, timeFrame, cwd, onProgress);
|
|
118
|
+
spinner.succeed(chalk.green('Log saved.'));
|
|
119
|
+
console.log(chalk.dim(result.id));
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
spinner.fail(chalk.red(err instanceof Error ? err.message : 'Failed'));
|
|
123
|
+
throw err;
|
|
124
|
+
}
|
|
93
125
|
return;
|
|
94
126
|
}
|
|
127
|
+
spinner.stop();
|
|
95
128
|
const choice = await inquirer.prompt([
|
|
96
129
|
{
|
|
97
130
|
name: 'time_frame',
|
|
@@ -105,6 +138,14 @@ export const runLog = async (args = []) => {
|
|
|
105
138
|
},
|
|
106
139
|
]);
|
|
107
140
|
const timeFrame = choice.time_frame;
|
|
108
|
-
|
|
109
|
-
|
|
141
|
+
spinner.start('Analyzing repository…');
|
|
142
|
+
try {
|
|
143
|
+
const result = await buildAndPushActivityLog(timeFrame, cwd, onProgress);
|
|
144
|
+
spinner.succeed(chalk.green('Log saved.'));
|
|
145
|
+
console.log(chalk.dim(result.id));
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
spinner.fail(chalk.red(err instanceof Error ? err.message : 'Failed'));
|
|
149
|
+
throw err;
|
|
150
|
+
}
|
|
110
151
|
};
|
package/dist/commands/login.js
CHANGED
|
@@ -83,6 +83,42 @@ const CALLBACK_SUCCESS_HTML = `<!DOCTYPE html>
|
|
|
83
83
|
font-size: 0.8125rem;
|
|
84
84
|
color: ${STYLES.muted};
|
|
85
85
|
}
|
|
86
|
+
.verify-block {
|
|
87
|
+
margin-top: 1rem;
|
|
88
|
+
padding-top: 1rem;
|
|
89
|
+
border-top: 1px solid ${STYLES.border};
|
|
90
|
+
font-size: 0.8125rem;
|
|
91
|
+
color: ${STYLES.muted};
|
|
92
|
+
}
|
|
93
|
+
.cmd-row {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
justify-content: center;
|
|
97
|
+
gap: 0.5rem;
|
|
98
|
+
margin-top: 0.5rem;
|
|
99
|
+
flex-wrap: wrap;
|
|
100
|
+
}
|
|
101
|
+
.cmd {
|
|
102
|
+
font-family: ui-monospace, monospace;
|
|
103
|
+
font-size: 0.8125rem;
|
|
104
|
+
padding: 0.375rem 0.75rem;
|
|
105
|
+
background: ${STYLES.background};
|
|
106
|
+
border: 1px solid ${STYLES.border};
|
|
107
|
+
border-radius: 0.375rem;
|
|
108
|
+
color: ${STYLES.foreground};
|
|
109
|
+
}
|
|
110
|
+
.copy-btn {
|
|
111
|
+
font-size: 0.8125rem;
|
|
112
|
+
padding: 0.375rem 0.75rem;
|
|
113
|
+
background: ${STYLES.primary};
|
|
114
|
+
color: ${STYLES.primaryForeground};
|
|
115
|
+
border: none;
|
|
116
|
+
border-radius: 0.375rem;
|
|
117
|
+
cursor: pointer;
|
|
118
|
+
font-weight: 500;
|
|
119
|
+
}
|
|
120
|
+
.copy-btn:hover { opacity: 0.9; }
|
|
121
|
+
.copy-btn.copied { background: ${STYLES.muted}; cursor: default; }
|
|
86
122
|
</style>
|
|
87
123
|
</head>
|
|
88
124
|
<body>
|
|
@@ -96,7 +132,31 @@ const CALLBACK_SUCCESS_HTML = `<!DOCTYPE html>
|
|
|
96
132
|
<h1>You're all set</h1>
|
|
97
133
|
<p class="muted">You're logged in successfully. You can close this window safely—your credentials are saved and the CLI is ready to use.</p>
|
|
98
134
|
<p class="hint">Return to your terminal to continue.</p>
|
|
135
|
+
<div class="verify-block">
|
|
136
|
+
<p class="muted" style="margin:0">Verify in terminal:</p>
|
|
137
|
+
<div class="cmd-row">
|
|
138
|
+
<code class="cmd" id="whoami-cmd">bitcompass whoami</code>
|
|
139
|
+
<button type="button" class="copy-btn" id="copy-btn" aria-label="Copy command">Copy</button>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
99
142
|
</div>
|
|
143
|
+
<script>
|
|
144
|
+
(function() {
|
|
145
|
+
var btn = document.getElementById('copy-btn');
|
|
146
|
+
var cmd = document.getElementById('whoami-cmd');
|
|
147
|
+
if (!btn || !cmd) return;
|
|
148
|
+
btn.addEventListener('click', function() {
|
|
149
|
+
navigator.clipboard.writeText('bitcompass whoami').then(function() {
|
|
150
|
+
btn.textContent = 'Copied!';
|
|
151
|
+
btn.classList.add('copied');
|
|
152
|
+
setTimeout(function() {
|
|
153
|
+
btn.textContent = 'Copy';
|
|
154
|
+
btn.classList.remove('copied');
|
|
155
|
+
}, 2000);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
})();
|
|
159
|
+
</script>
|
|
100
160
|
</body>
|
|
101
161
|
</html>`;
|
|
102
162
|
const escapeHtml = (s) => s
|
package/dist/commands/rules.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export declare const runRulesSearch: (query?: string
|
|
2
|
-
|
|
1
|
+
export declare const runRulesSearch: (query?: string, options?: {
|
|
2
|
+
listOnly?: boolean;
|
|
3
|
+
}) => Promise<void>;
|
|
4
|
+
export declare const runRulesList: (options?: {
|
|
5
|
+
table?: boolean;
|
|
6
|
+
}) => Promise<void>;
|
|
3
7
|
export declare const runRulesPull: (id?: string, options?: {
|
|
4
8
|
global?: boolean;
|
|
5
9
|
copy?: boolean;
|
package/dist/commands/rules.js
CHANGED
|
@@ -4,7 +4,8 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { loadCredentials } from '../auth/config.js';
|
|
5
5
|
import { searchRules, fetchRules, getRuleById, insertRule } from '../api/client.js';
|
|
6
6
|
import { pullRuleToFile } from '../lib/rule-file-ops.js';
|
|
7
|
-
|
|
7
|
+
import { formatList, shouldUseTable } from '../lib/list-format.js';
|
|
8
|
+
export const runRulesSearch = async (query, options) => {
|
|
8
9
|
if (!loadCredentials()) {
|
|
9
10
|
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
10
11
|
process.exit(1);
|
|
@@ -17,6 +18,10 @@ export const runRulesSearch = async (query) => {
|
|
|
17
18
|
console.log(chalk.yellow('No rules found.'));
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
21
|
+
if (options?.listOnly) {
|
|
22
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable: shouldUseTable(), showKind: true });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
20
25
|
const choice = await inquirer.prompt([
|
|
21
26
|
{
|
|
22
27
|
name: 'id',
|
|
@@ -31,7 +36,7 @@ export const runRulesSearch = async (query) => {
|
|
|
31
36
|
console.log(rule.body);
|
|
32
37
|
}
|
|
33
38
|
};
|
|
34
|
-
export const runRulesList = async () => {
|
|
39
|
+
export const runRulesList = async (options) => {
|
|
35
40
|
if (!loadCredentials()) {
|
|
36
41
|
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
37
42
|
process.exit(1);
|
|
@@ -39,9 +44,12 @@ export const runRulesList = async () => {
|
|
|
39
44
|
const spinner = ora('Loading rules…').start();
|
|
40
45
|
const list = await fetchRules('rule');
|
|
41
46
|
spinner.stop();
|
|
42
|
-
list.
|
|
43
|
-
if (list.length === 0)
|
|
47
|
+
if (list.length === 0) {
|
|
44
48
|
console.log(chalk.yellow('No rules yet.'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const useTable = shouldUseTable(options?.table);
|
|
52
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable, showKind: false });
|
|
45
53
|
};
|
|
46
54
|
export const runRulesPull = async (id, options) => {
|
|
47
55
|
if (!loadCredentials()) {
|
|
@@ -82,8 +90,9 @@ export const runRulesPull = async (id, options) => {
|
|
|
82
90
|
}
|
|
83
91
|
catch (error) {
|
|
84
92
|
spinner.fail(chalk.red('Failed to pull rule'));
|
|
85
|
-
|
|
86
|
-
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
console.error(chalk.red(message));
|
|
95
|
+
process.exit(message.includes('not found') ? 2 : 1);
|
|
87
96
|
}
|
|
88
97
|
};
|
|
89
98
|
export const runRulesPush = async (file) => {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const runSkillsSearch: (query?: string, options?: {
|
|
2
|
+
listOnly?: boolean;
|
|
3
|
+
}) => Promise<void>;
|
|
4
|
+
export declare const runSkillsList: (options?: {
|
|
5
|
+
table?: boolean;
|
|
6
|
+
}) => Promise<void>;
|
|
7
|
+
export declare const runSkillsPull: (id?: string, options?: {
|
|
8
|
+
global?: boolean;
|
|
9
|
+
copy?: boolean;
|
|
10
|
+
}) => Promise<void>;
|
|
11
|
+
export declare const runSkillsPush: (file?: string) => Promise<void>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { loadCredentials } from '../auth/config.js';
|
|
5
|
+
import { searchRules, fetchRules, getRuleById, insertRule } from '../api/client.js';
|
|
6
|
+
import { pullRuleToFile } from '../lib/rule-file-ops.js';
|
|
7
|
+
import { formatList, shouldUseTable } from '../lib/list-format.js';
|
|
8
|
+
export const runSkillsSearch = async (query, options) => {
|
|
9
|
+
if (!loadCredentials()) {
|
|
10
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
const q = query ?? (await inquirer.prompt([{ name: 'q', message: 'Search query', type: 'input' }])).q;
|
|
14
|
+
const spinner = ora('Searching skills…').start();
|
|
15
|
+
const list = await searchRules(q, { kind: 'skill', limit: 20 });
|
|
16
|
+
spinner.stop();
|
|
17
|
+
if (list.length === 0) {
|
|
18
|
+
console.log(chalk.yellow('No skills found.'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (options?.listOnly) {
|
|
22
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable: shouldUseTable(), showKind: true });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const choice = await inquirer.prompt([
|
|
26
|
+
{
|
|
27
|
+
name: 'id',
|
|
28
|
+
message: 'Select a skill',
|
|
29
|
+
type: 'list',
|
|
30
|
+
choices: list.map((r) => ({ name: `${r.title} (${r.id})`, value: r.id })),
|
|
31
|
+
},
|
|
32
|
+
]);
|
|
33
|
+
const rule = await getRuleById(choice.id);
|
|
34
|
+
if (rule) {
|
|
35
|
+
console.log(chalk.cyan(rule.title));
|
|
36
|
+
console.log(rule.body);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
export const runSkillsList = async (options) => {
|
|
40
|
+
if (!loadCredentials()) {
|
|
41
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const spinner = ora('Loading skills…').start();
|
|
45
|
+
const list = await fetchRules('skill');
|
|
46
|
+
spinner.stop();
|
|
47
|
+
if (list.length === 0) {
|
|
48
|
+
console.log(chalk.yellow('No skills yet.'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const useTable = shouldUseTable(options?.table);
|
|
52
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable, showKind: false });
|
|
53
|
+
};
|
|
54
|
+
export const runSkillsPull = async (id, options) => {
|
|
55
|
+
if (!loadCredentials()) {
|
|
56
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
let targetId = id;
|
|
60
|
+
if (!targetId) {
|
|
61
|
+
const spinner = ora('Loading skills…').start();
|
|
62
|
+
const list = await fetchRules('skill');
|
|
63
|
+
spinner.stop();
|
|
64
|
+
if (list.length === 0) {
|
|
65
|
+
console.log(chalk.yellow('No skills to pull.'));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const choice = await inquirer.prompt([
|
|
69
|
+
{ name: 'id', message: 'Select skill', type: 'list', choices: list.map((r) => ({ name: r.title, value: r.id })) },
|
|
70
|
+
]);
|
|
71
|
+
targetId = choice.id;
|
|
72
|
+
}
|
|
73
|
+
const spinner = ora('Pulling skill…').start();
|
|
74
|
+
try {
|
|
75
|
+
const filename = await pullRuleToFile(targetId, {
|
|
76
|
+
global: options?.global,
|
|
77
|
+
useSymlink: !options?.copy, // Use symlink unless --copy flag is set
|
|
78
|
+
});
|
|
79
|
+
spinner.succeed(chalk.green('Pulled skill'));
|
|
80
|
+
console.log(chalk.dim(filename));
|
|
81
|
+
if (options?.copy) {
|
|
82
|
+
console.log(chalk.dim('Copied as file (not a symlink)'));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log(chalk.dim('Created symbolic link to cached skill'));
|
|
86
|
+
}
|
|
87
|
+
if (options?.global) {
|
|
88
|
+
console.log(chalk.dim('Installed globally for all projects'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
spinner.fail(chalk.red('Failed to pull skill'));
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
console.error(chalk.red(message));
|
|
95
|
+
process.exit(message.includes('not found') ? 2 : 1);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
export const runSkillsPush = async (file) => {
|
|
99
|
+
if (!loadCredentials()) {
|
|
100
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
let payload;
|
|
104
|
+
if (file) {
|
|
105
|
+
const { readFileSync } = await import('fs');
|
|
106
|
+
const raw = readFileSync(file, 'utf-8');
|
|
107
|
+
try {
|
|
108
|
+
payload = JSON.parse(raw);
|
|
109
|
+
payload.kind = 'skill';
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
const lines = raw.split('\n');
|
|
113
|
+
const title = lines[0].replace(/^#\s*/, '') || 'Untitled';
|
|
114
|
+
payload = { kind: 'skill', title, description: '', body: raw };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const answers = await inquirer.prompt([
|
|
119
|
+
{ name: 'title', message: 'Skill title', type: 'input', default: 'Untitled' },
|
|
120
|
+
{ name: 'description', message: 'Description', type: 'input', default: '' },
|
|
121
|
+
{ name: 'body', message: 'Skill content', type: 'editor', default: '' },
|
|
122
|
+
]);
|
|
123
|
+
payload = { kind: 'skill', title: answers.title, description: answers.description, body: answers.body };
|
|
124
|
+
}
|
|
125
|
+
const spinner = ora('Publishing skill…').start();
|
|
126
|
+
const created = await insertRule(payload);
|
|
127
|
+
spinner.succeed(chalk.green('Published skill ') + created.id);
|
|
128
|
+
console.log(chalk.dim(created.title));
|
|
129
|
+
};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
export declare const runSolutionsSearch: (query?: string
|
|
1
|
+
export declare const runSolutionsSearch: (query?: string, options?: {
|
|
2
|
+
listOnly?: boolean;
|
|
3
|
+
}) => Promise<void>;
|
|
4
|
+
export declare const runSolutionsList: (options?: {
|
|
5
|
+
table?: boolean;
|
|
6
|
+
}) => Promise<void>;
|
|
2
7
|
export declare const runSolutionsPull: (id?: string, options?: {
|
|
3
8
|
global?: boolean;
|
|
4
9
|
copy?: boolean;
|
|
@@ -4,7 +4,8 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { loadCredentials } from '../auth/config.js';
|
|
5
5
|
import { searchRules, fetchRules, getRuleById, insertRule } from '../api/client.js';
|
|
6
6
|
import { pullRuleToFile } from '../lib/rule-file-ops.js';
|
|
7
|
-
|
|
7
|
+
import { formatList, shouldUseTable } from '../lib/list-format.js';
|
|
8
|
+
export const runSolutionsSearch = async (query, options) => {
|
|
8
9
|
if (!loadCredentials()) {
|
|
9
10
|
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
10
11
|
process.exit(1);
|
|
@@ -17,6 +18,10 @@ export const runSolutionsSearch = async (query) => {
|
|
|
17
18
|
console.log(chalk.yellow('No solutions found.'));
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
21
|
+
if (options?.listOnly) {
|
|
22
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable: shouldUseTable(), showKind: true });
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
20
25
|
const choice = await inquirer.prompt([
|
|
21
26
|
{
|
|
22
27
|
name: 'id',
|
|
@@ -31,6 +36,21 @@ export const runSolutionsSearch = async (query) => {
|
|
|
31
36
|
console.log(rule.body);
|
|
32
37
|
}
|
|
33
38
|
};
|
|
39
|
+
export const runSolutionsList = async (options) => {
|
|
40
|
+
if (!loadCredentials()) {
|
|
41
|
+
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
const spinner = ora('Loading solutions…').start();
|
|
45
|
+
const list = await fetchRules('solution');
|
|
46
|
+
spinner.stop();
|
|
47
|
+
if (list.length === 0) {
|
|
48
|
+
console.log(chalk.yellow('No solutions yet.'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const useTable = shouldUseTable(options?.table);
|
|
52
|
+
formatList(list.map((r) => ({ id: r.id, title: r.title, kind: r.kind })), { useTable, showKind: false });
|
|
53
|
+
};
|
|
34
54
|
export const runSolutionsPull = async (id, options) => {
|
|
35
55
|
if (!loadCredentials()) {
|
|
36
56
|
console.error(chalk.red('Not logged in. Run bitcompass login.'));
|
|
@@ -70,8 +90,9 @@ export const runSolutionsPull = async (id, options) => {
|
|
|
70
90
|
}
|
|
71
91
|
catch (error) {
|
|
72
92
|
spinner.fail(chalk.red('Failed to pull solution'));
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
94
|
+
console.error(chalk.red(message));
|
|
95
|
+
process.exit(message.includes('not found') ? 2 : 1);
|
|
75
96
|
}
|
|
76
97
|
};
|
|
77
98
|
export const runSolutionsPush = async (file) => {
|
package/dist/index.js
CHANGED
|
@@ -10,10 +10,17 @@ import { runInit } from './commands/init.js';
|
|
|
10
10
|
import { runLogin } from './commands/login.js';
|
|
11
11
|
import { runLogout } from './commands/logout.js';
|
|
12
12
|
import { runMcpStart, runMcpStatus } from './commands/mcp.js';
|
|
13
|
-
import { runLog } from './commands/log.js';
|
|
13
|
+
import { runLog, ValidationError } from './commands/log.js';
|
|
14
14
|
import { runRulesList, runRulesPull, runRulesPush, runRulesSearch } from './commands/rules.js';
|
|
15
|
-
import { runSolutionsPull, runSolutionsPush, runSolutionsSearch } from './commands/solutions.js';
|
|
15
|
+
import { runSolutionsList, runSolutionsPull, runSolutionsPush, runSolutionsSearch } from './commands/solutions.js';
|
|
16
|
+
import { runSkillsList, runSkillsPull, runSkillsPush, runSkillsSearch } from './commands/skills.js';
|
|
17
|
+
import { runCommandsList, runCommandsPull, runCommandsPush, runCommandsSearch } from './commands/commands.js';
|
|
18
|
+
import { runGlossary } from './commands/glossary.js';
|
|
16
19
|
import { runWhoami } from './commands/whoami.js';
|
|
20
|
+
// Disable chalk colors when NO_COLOR is set or --no-color is passed (must run before any command)
|
|
21
|
+
if (process.env.NO_COLOR !== undefined || process.argv.includes('--no-color')) {
|
|
22
|
+
chalk.level = 0;
|
|
23
|
+
}
|
|
17
24
|
// Read version from package.json
|
|
18
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
19
26
|
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
@@ -23,7 +30,8 @@ const program = new Command();
|
|
|
23
30
|
program
|
|
24
31
|
.name('bitcompass')
|
|
25
32
|
.description('BitCompass CLI - rules, solutions, and MCP server')
|
|
26
|
-
.version(version, '-v, -V, --version', 'display version number')
|
|
33
|
+
.version(version, '-v, -V, --version', 'display version number')
|
|
34
|
+
.option('--no-color', 'Disable colored output');
|
|
27
35
|
program
|
|
28
36
|
.command('login')
|
|
29
37
|
.description('Log in with Google (opens browser)')
|
|
@@ -36,6 +44,10 @@ program
|
|
|
36
44
|
.command('whoami')
|
|
37
45
|
.description('Show current user (email)')
|
|
38
46
|
.action(runWhoami);
|
|
47
|
+
program
|
|
48
|
+
.command('glossary')
|
|
49
|
+
.description('Show glossary (rules, solutions, skills, commands)')
|
|
50
|
+
.action(runGlossary);
|
|
39
51
|
program
|
|
40
52
|
.command('init')
|
|
41
53
|
.description('Configure project: editor/AI provider and output folder for rules/docs/commands')
|
|
@@ -43,7 +55,19 @@ program
|
|
|
43
55
|
program
|
|
44
56
|
.command('log [dates...]')
|
|
45
57
|
.description('Collect repo summary and git activity, then push to your activity logs. Optional: bitcompass log YYYY-MM-DD or bitcompass log YYYY-MM-DD YYYY-MM-DD')
|
|
46
|
-
.
|
|
58
|
+
.addHelpText('after', `
|
|
59
|
+
Examples:
|
|
60
|
+
bitcompass log
|
|
61
|
+
bitcompass log 2025-02-01
|
|
62
|
+
bitcompass log 2025-02-01 2025-02-05
|
|
63
|
+
`)
|
|
64
|
+
.action((dates) => runLog(dates ?? []).catch((err) => {
|
|
65
|
+
if (err instanceof ValidationError) {
|
|
66
|
+
console.error(chalk.red(err.message));
|
|
67
|
+
process.exit(2);
|
|
68
|
+
}
|
|
69
|
+
handleErr(err);
|
|
70
|
+
}));
|
|
47
71
|
const configCmd = program.command('config').description('Show or set config');
|
|
48
72
|
configCmd.action(runConfigList);
|
|
49
73
|
configCmd.command('list').description('List config values').action(runConfigList);
|
|
@@ -51,25 +75,88 @@ configCmd.command('set <key> <value>').description('Set supabaseUrl, supabaseAno
|
|
|
51
75
|
configCmd.command('get <key>').description('Get a config value').action((key) => runConfigGet(key));
|
|
52
76
|
// rules
|
|
53
77
|
const rules = program.command('rules').description('Manage rules');
|
|
54
|
-
rules
|
|
55
|
-
|
|
78
|
+
rules
|
|
79
|
+
.command('search [query]')
|
|
80
|
+
.description('Search rules')
|
|
81
|
+
.option('-l, --list', 'List results only; do not prompt to select')
|
|
82
|
+
.action((query, cmd) => runRulesSearch(query, { listOnly: cmd?.opts()?.list }).catch(handleErr));
|
|
83
|
+
rules
|
|
84
|
+
.command('list')
|
|
85
|
+
.description('List rules')
|
|
86
|
+
.option('--table', 'Show output in aligned columns (default when TTY)')
|
|
87
|
+
.addHelpText('after', '\nExamples:\n bitcompass rules list\n bitcompass rules list --table\n')
|
|
88
|
+
.action((opts) => runRulesList({ table: opts.table }).catch(handleErr));
|
|
56
89
|
rules
|
|
57
90
|
.command('pull [id]')
|
|
58
91
|
.description('Pull a rule by ID or choose from list (creates symbolic link by default)')
|
|
59
92
|
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
60
93
|
.option('--copy', 'Copy file instead of creating symbolic link')
|
|
94
|
+
.addHelpText('after', '\nExamples:\n bitcompass rules pull <id>\n bitcompass rules pull <id> --global\n bitcompass rules pull <id> --copy\n')
|
|
61
95
|
.action((id, options) => runRulesPull(id, options).catch(handleErr));
|
|
62
96
|
rules.command('push [file]').description('Push a rule (file or interactive)').action((file) => runRulesPush(file).catch(handleErr));
|
|
63
97
|
// solutions
|
|
64
98
|
const solutions = program.command('solutions').description('Manage solutions');
|
|
65
|
-
solutions
|
|
99
|
+
solutions
|
|
100
|
+
.command('search [query]')
|
|
101
|
+
.description('Search solutions')
|
|
102
|
+
.option('-l, --list', 'List results only; do not prompt to select')
|
|
103
|
+
.action((query, cmd) => runSolutionsSearch(query, { listOnly: cmd?.opts()?.list }).catch(handleErr));
|
|
104
|
+
solutions
|
|
105
|
+
.command('list')
|
|
106
|
+
.description('List solutions')
|
|
107
|
+
.option('--table', 'Show output in aligned columns (default when TTY)')
|
|
108
|
+
.addHelpText('after', '\nExamples:\n bitcompass solutions list\n bitcompass solutions list --table\n')
|
|
109
|
+
.action((opts) => runSolutionsList({ table: opts.table }).catch(handleErr));
|
|
66
110
|
solutions
|
|
67
111
|
.command('pull [id]')
|
|
68
112
|
.description('Pull a solution by ID or choose from list (creates symbolic link by default)')
|
|
69
113
|
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
70
114
|
.option('--copy', 'Copy file instead of creating symbolic link')
|
|
115
|
+
.addHelpText('after', '\nExamples:\n bitcompass solutions pull <id>\n bitcompass solutions pull <id> --global\n')
|
|
71
116
|
.action((id, options) => runSolutionsPull(id, options).catch(handleErr));
|
|
72
117
|
solutions.command('push [file]').description('Push a solution (file or interactive)').action((file) => runSolutionsPush(file).catch(handleErr));
|
|
118
|
+
// skills
|
|
119
|
+
const skills = program.command('skills').description('Manage skills');
|
|
120
|
+
skills
|
|
121
|
+
.command('search [query]')
|
|
122
|
+
.description('Search skills')
|
|
123
|
+
.option('-l, --list', 'List results only; do not prompt to select')
|
|
124
|
+
.action((query, cmd) => runSkillsSearch(query, { listOnly: cmd?.opts()?.list }).catch(handleErr));
|
|
125
|
+
skills
|
|
126
|
+
.command('list')
|
|
127
|
+
.description('List skills')
|
|
128
|
+
.option('--table', 'Show output in aligned columns (default when TTY)')
|
|
129
|
+
.addHelpText('after', '\nExamples:\n bitcompass skills list\n bitcompass skills list --table\n')
|
|
130
|
+
.action((opts) => runSkillsList({ table: opts.table }).catch(handleErr));
|
|
131
|
+
skills
|
|
132
|
+
.command('pull [id]')
|
|
133
|
+
.description('Pull a skill by ID or choose from list (creates symbolic link by default)')
|
|
134
|
+
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
135
|
+
.option('--copy', 'Copy file instead of creating symbolic link')
|
|
136
|
+
.addHelpText('after', '\nExamples:\n bitcompass skills pull <id>\n bitcompass skills pull <id> --global\n')
|
|
137
|
+
.action((id, options) => runSkillsPull(id, options).catch(handleErr));
|
|
138
|
+
skills.command('push [file]').description('Push a skill (file or interactive)').action((file) => runSkillsPush(file).catch(handleErr));
|
|
139
|
+
// commands
|
|
140
|
+
const commands = program.command('commands').description('Manage commands');
|
|
141
|
+
commands
|
|
142
|
+
.command('search [query]')
|
|
143
|
+
.description('Search commands')
|
|
144
|
+
.option('-l, --list', 'List results only; do not prompt to select')
|
|
145
|
+
.action((query, cmd) => runCommandsSearch(query, { listOnly: cmd?.opts()?.list }).catch(handleErr));
|
|
146
|
+
commands
|
|
147
|
+
.command('list')
|
|
148
|
+
.description('List commands')
|
|
149
|
+
.option('--table', 'Show output in aligned columns (default when TTY)')
|
|
150
|
+
.addHelpText('after', '\nExamples:\n bitcompass commands list\n bitcompass commands list --table\n')
|
|
151
|
+
.action((opts) => runCommandsList({ table: opts.table }).catch(handleErr));
|
|
152
|
+
commands
|
|
153
|
+
.command('pull [id]')
|
|
154
|
+
.description('Pull a command by ID or choose from list (creates symbolic link by default)')
|
|
155
|
+
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
156
|
+
.option('--copy', 'Copy file instead of creating symbolic link')
|
|
157
|
+
.addHelpText('after', '\nExamples:\n bitcompass commands pull <id>\n bitcompass commands pull <id> --global\n')
|
|
158
|
+
.action((id, options) => runCommandsPull(id, options).catch(handleErr));
|
|
159
|
+
commands.command('push [file]').description('Push a command (file or interactive)').action((file) => runCommandsPush(file).catch(handleErr));
|
|
73
160
|
// mcp
|
|
74
161
|
const mcp = program.command('mcp').description('MCP server');
|
|
75
162
|
mcp.command('start').description('Start MCP server (stdio)').action(() => runMcpStart().catch(handleErr));
|
|
@@ -78,4 +165,11 @@ function handleErr(err) {
|
|
|
78
165
|
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
79
166
|
process.exit(1);
|
|
80
167
|
}
|
|
168
|
+
if (process.argv.slice(2).length === 0) {
|
|
169
|
+
console.log(chalk.cyan('BitCompass') +
|
|
170
|
+
chalk.dim(' – rules, solutions, and MCP server. Run ') +
|
|
171
|
+
chalk.cyan('bitcompass --help') +
|
|
172
|
+
chalk.dim(' for commands.'));
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
81
175
|
program.parse();
|
|
@@ -37,6 +37,11 @@ export declare const getRepoSummary: (repoRoot: string) => RepoSummary;
|
|
|
37
37
|
* period_end is now; period_start and since are the start of the window.
|
|
38
38
|
*/
|
|
39
39
|
export declare const getPeriodForTimeFrame: (timeFrame: TimeFrame) => PeriodBounds;
|
|
40
|
+
/**
|
|
41
|
+
* Parse an ISO date string (YYYY-MM-DD) to Date at start of day (UTC).
|
|
42
|
+
* Returns null for invalid or non-calendar dates (e.g. 2025-02-30).
|
|
43
|
+
*/
|
|
44
|
+
export declare const parseDate: (s: string) => Date | null;
|
|
40
45
|
/**
|
|
41
46
|
* Compute period for custom date(s). Single date = that day; two dates = range (inclusive).
|
|
42
47
|
* period_end is end of last day (23:59:59.999).
|
package/dist/lib/git-analysis.js
CHANGED
|
@@ -74,8 +74,9 @@ export const getPeriodForTimeFrame = (timeFrame) => {
|
|
|
74
74
|
};
|
|
75
75
|
/**
|
|
76
76
|
* Parse an ISO date string (YYYY-MM-DD) to Date at start of day (UTC).
|
|
77
|
+
* Returns null for invalid or non-calendar dates (e.g. 2025-02-30).
|
|
77
78
|
*/
|
|
78
|
-
const parseDate = (s) => {
|
|
79
|
+
export const parseDate = (s) => {
|
|
79
80
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s.trim());
|
|
80
81
|
if (!match)
|
|
81
82
|
return null;
|
|
@@ -94,13 +95,13 @@ const parseDate = (s) => {
|
|
|
94
95
|
export const getPeriodForCustomDates = (startDateStr, endDateStr) => {
|
|
95
96
|
const startDate = parseDate(startDateStr);
|
|
96
97
|
if (!startDate) {
|
|
97
|
-
throw new Error(`Invalid start date: ${startDateStr}. Use YYYY-MM-DD.`);
|
|
98
|
+
throw new Error(`Invalid start date: ${startDateStr}. Use YYYY-MM-DD (e.g. 2025-02-06).`);
|
|
98
99
|
}
|
|
99
100
|
let periodEnd;
|
|
100
101
|
if (endDateStr !== undefined && endDateStr.trim() !== '') {
|
|
101
102
|
const endDate = parseDate(endDateStr);
|
|
102
103
|
if (!endDate)
|
|
103
|
-
throw new Error(`Invalid end date: ${endDateStr}. Use YYYY-MM-DD.`);
|
|
104
|
+
throw new Error(`Invalid end date: ${endDateStr}. Use YYYY-MM-DD (e.g. 2025-02-06).`);
|
|
104
105
|
if (endDate < startDate)
|
|
105
106
|
throw new Error('End date must be on or after start date.');
|
|
106
107
|
periodEnd = new Date(endDate);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ListRow {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
kind?: string;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Format a list of rules/items for terminal output.
|
|
8
|
+
* When useTable is true (TTY or --table), prints aligned columns; otherwise one line per row for scripts/CI.
|
|
9
|
+
*/
|
|
10
|
+
export declare const formatList: (list: ListRow[], options: {
|
|
11
|
+
useTable: boolean;
|
|
12
|
+
showKind?: boolean;
|
|
13
|
+
}) => void;
|
|
14
|
+
/** Use table format when stdout is a TTY or when tableFlag is true. */
|
|
15
|
+
export declare const shouldUseTable: (tableFlag?: boolean) => boolean;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
const ID_COLUMN_WIDTH = 36;
|
|
3
|
+
const TITLE_MAX_WIDTH = 50;
|
|
4
|
+
const KIND_WIDTH = 10;
|
|
5
|
+
/**
|
|
6
|
+
* Format a list of rules/items for terminal output.
|
|
7
|
+
* When useTable is true (TTY or --table), prints aligned columns; otherwise one line per row for scripts/CI.
|
|
8
|
+
*/
|
|
9
|
+
export const formatList = (list, options) => {
|
|
10
|
+
if (list.length === 0)
|
|
11
|
+
return;
|
|
12
|
+
const { useTable, showKind = false } = options;
|
|
13
|
+
if (useTable) {
|
|
14
|
+
const titleWidth = Math.min(TITLE_MAX_WIDTH, Math.max(...list.map((r) => r.title.length), 5));
|
|
15
|
+
const headerKind = showKind ? chalk.dim('Kind'.padEnd(KIND_WIDTH)) + ' ' : '';
|
|
16
|
+
console.log(chalk.bold('ID'.padEnd(ID_COLUMN_WIDTH)) +
|
|
17
|
+
' ' +
|
|
18
|
+
chalk.bold('Title'.padEnd(titleWidth)) +
|
|
19
|
+
(showKind ? ' ' + chalk.bold('Kind') : ''));
|
|
20
|
+
const sep = '-'.repeat(ID_COLUMN_WIDTH + 1 + titleWidth + (showKind ? KIND_WIDTH + 2 : 0));
|
|
21
|
+
console.log(chalk.dim(sep));
|
|
22
|
+
for (const r of list) {
|
|
23
|
+
const id = r.id.length > ID_COLUMN_WIDTH ? r.id.slice(0, ID_COLUMN_WIDTH - 1) + '…' : r.id.padEnd(ID_COLUMN_WIDTH);
|
|
24
|
+
const title = r.title.length > titleWidth ? r.title.slice(0, titleWidth - 1) + '…' : r.title.padEnd(titleWidth);
|
|
25
|
+
const kindPart = showKind ? ' ' + (r.kind ?? '').padEnd(KIND_WIDTH) : '';
|
|
26
|
+
console.log(chalk.dim(id) + ' ' + chalk.cyan(title) + kindPart);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
list.forEach((r) => console.log(`${chalk.cyan(r.title)} ${chalk.dim(r.id)}`));
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/** Use table format when stdout is a TTY or when tableFlag is true. */
|
|
34
|
+
export const shouldUseTable = (tableFlag) => Boolean(tableFlag ?? process.stdout.isTTY);
|
|
@@ -3,7 +3,7 @@ import { join, relative, dirname } from 'path';
|
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { getRuleById } from '../api/client.js';
|
|
5
5
|
import { getProjectConfig } from '../auth/project-config.js';
|
|
6
|
-
import { ruleFilename, solutionFilename } from './slug.js';
|
|
6
|
+
import { ruleFilename, solutionFilename, skillFilename, commandFilename } from './slug.js';
|
|
7
7
|
import { ensureRuleCached } from './rule-cache.js';
|
|
8
8
|
/**
|
|
9
9
|
* Pulls a rule or solution to a file using symbolic links (like Bun init).
|
|
@@ -33,9 +33,22 @@ export const pullRuleToFile = async (id, options = {}) => {
|
|
|
33
33
|
outDir = join(process.cwd(), outputPath);
|
|
34
34
|
}
|
|
35
35
|
mkdirSync(outDir, { recursive: true });
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
:
|
|
36
|
+
let filename;
|
|
37
|
+
switch (rule.kind) {
|
|
38
|
+
case 'solution':
|
|
39
|
+
filename = join(outDir, solutionFilename(rule.title, rule.id));
|
|
40
|
+
break;
|
|
41
|
+
case 'skill':
|
|
42
|
+
filename = join(outDir, skillFilename(rule.title, rule.id));
|
|
43
|
+
break;
|
|
44
|
+
case 'command':
|
|
45
|
+
filename = join(outDir, commandFilename(rule.title, rule.id));
|
|
46
|
+
break;
|
|
47
|
+
case 'rule':
|
|
48
|
+
default:
|
|
49
|
+
filename = join(outDir, ruleFilename(rule.title, rule.id));
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
39
52
|
// Remove existing file/symlink if it exists
|
|
40
53
|
if (existsSync(filename)) {
|
|
41
54
|
try {
|
|
@@ -67,9 +80,22 @@ export const pullRuleToFile = async (id, options = {}) => {
|
|
|
67
80
|
}
|
|
68
81
|
else {
|
|
69
82
|
// Fallback: copy file content (for compatibility or when symlinks aren't desired)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
:
|
|
83
|
+
let content;
|
|
84
|
+
switch (rule.kind) {
|
|
85
|
+
case 'solution':
|
|
86
|
+
content = `# ${rule.title}\n\n${rule.description}\n\n## Solution\n\n${rule.body}\n`;
|
|
87
|
+
break;
|
|
88
|
+
case 'skill':
|
|
89
|
+
content = `# ${rule.title}\n\n${rule.description}\n\n${rule.body}\n`;
|
|
90
|
+
break;
|
|
91
|
+
case 'command':
|
|
92
|
+
content = `# ${rule.title}\n\n${rule.description}\n\n${rule.body}\n`;
|
|
93
|
+
break;
|
|
94
|
+
case 'rule':
|
|
95
|
+
default:
|
|
96
|
+
content = `# ${rule.title}\n\n${rule.description}\n\n${rule.body}\n`;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
73
99
|
writeFileSync(filename, content);
|
|
74
100
|
}
|
|
75
101
|
return filename;
|
package/dist/lib/slug.d.ts
CHANGED
|
@@ -13,3 +13,13 @@ export declare const ruleFilename: (title: string, id: string) => string;
|
|
|
13
13
|
* Falls back to id if slug is empty.
|
|
14
14
|
*/
|
|
15
15
|
export declare const solutionFilename: (title: string, id: string) => string;
|
|
16
|
+
/**
|
|
17
|
+
* Returns the skill filename (e.g. skill-strava-api-authentication-flow.md).
|
|
18
|
+
* Falls back to id if slug is empty.
|
|
19
|
+
*/
|
|
20
|
+
export declare const skillFilename: (title: string, id: string) => string;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the command filename (e.g. command-strava-api-authentication-flow.md).
|
|
23
|
+
* Falls back to id if slug is empty.
|
|
24
|
+
*/
|
|
25
|
+
export declare const commandFilename: (title: string, id: string) => string;
|
package/dist/lib/slug.js
CHANGED
|
@@ -31,3 +31,21 @@ export const solutionFilename = (title, id) => {
|
|
|
31
31
|
const base = slug ? `solution-${slug}` : `solution-${id}`;
|
|
32
32
|
return `${base}.md`;
|
|
33
33
|
};
|
|
34
|
+
/**
|
|
35
|
+
* Returns the skill filename (e.g. skill-strava-api-authentication-flow.md).
|
|
36
|
+
* Falls back to id if slug is empty.
|
|
37
|
+
*/
|
|
38
|
+
export const skillFilename = (title, id) => {
|
|
39
|
+
const slug = titleToSlug(title);
|
|
40
|
+
const base = slug ? `skill-${slug}` : `skill-${id}`;
|
|
41
|
+
return `${base}.md`;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Returns the command filename (e.g. command-strava-api-authentication-flow.md).
|
|
45
|
+
* Falls back to id if slug is empty.
|
|
46
|
+
*/
|
|
47
|
+
export const commandFilename = (title, id) => {
|
|
48
|
+
const slug = titleToSlug(title);
|
|
49
|
+
const base = slug ? `command-${slug}` : `command-${id}`;
|
|
50
|
+
return `${base}.md`;
|
|
51
|
+
};
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
1
4
|
import { AUTH_REQUIRED_MSG, insertRule, searchRules, getRuleById, fetchRules, updateRule, deleteRule, fetchActivityLogs, getActivityLogById, } from '../api/client.js';
|
|
2
5
|
import { buildAndPushActivityLog } from '../commands/log.js';
|
|
3
6
|
import { loadCredentials } from '../auth/config.js';
|
|
4
7
|
import { getProjectConfig } from '../auth/project-config.js';
|
|
5
8
|
import { pullRuleToFile } from '../lib/rule-file-ops.js';
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
11
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
12
|
+
const VERSION = packageJson.version ?? '0.0.0';
|
|
6
13
|
/** When token is missing, we fail initialize so Cursor shows "Needs authentication" (yellow) instead of success (green). */
|
|
7
14
|
const NEEDS_AUTH_ERROR_MESSAGE = 'Needs authentication';
|
|
8
15
|
const NEEDS_AUTH_ERROR_CODE = -32001; // Server error: auth required
|
|
@@ -44,7 +51,7 @@ function createStdioServer() {
|
|
|
44
51
|
result: {
|
|
45
52
|
protocolVersion: '2024-11-05',
|
|
46
53
|
capabilities: { tools: {}, prompts: {} },
|
|
47
|
-
serverInfo: { name: 'bitcompass', version:
|
|
54
|
+
serverInfo: { name: 'bitcompass', version: VERSION },
|
|
48
55
|
},
|
|
49
56
|
});
|
|
50
57
|
return;
|
|
@@ -57,29 +64,33 @@ function createStdioServer() {
|
|
|
57
64
|
tools: [
|
|
58
65
|
{
|
|
59
66
|
name: 'search-rules',
|
|
60
|
-
description: '
|
|
67
|
+
description: 'Use when the user wants to find rules, solutions, skills, or commands by keyword or topic. Returns a list of matching items with id, title, kind, author, and a short body snippet. Optionally filter by kind (rule, solution, skill, command) and limit results.',
|
|
61
68
|
inputSchema: {
|
|
62
69
|
type: 'object',
|
|
63
|
-
properties: {
|
|
70
|
+
properties: {
|
|
71
|
+
query: { type: 'string', description: 'Search query or keywords' },
|
|
72
|
+
kind: { type: 'string', enum: ['rule', 'solution', 'skill', 'command'], description: 'Optional: restrict to one kind' },
|
|
73
|
+
limit: { type: 'number', description: 'Optional: max results (default 20)' },
|
|
74
|
+
},
|
|
64
75
|
required: ['query'],
|
|
65
76
|
},
|
|
66
77
|
},
|
|
67
78
|
{
|
|
68
79
|
name: 'search-solutions',
|
|
69
|
-
description: '
|
|
80
|
+
description: 'Use when the user asks for problem solutions or how-to guides. Returns a list of solutions with id, title, author, and a short snippet. Prefer this over search-rules when the intent is clearly "solutions" or "problem solutions".',
|
|
70
81
|
inputSchema: {
|
|
71
82
|
type: 'object',
|
|
72
|
-
properties: { query: { type: 'string' }, limit: { type: 'number' } },
|
|
83
|
+
properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', description: 'Optional: max results' } },
|
|
73
84
|
required: ['query'],
|
|
74
85
|
},
|
|
75
86
|
},
|
|
76
87
|
{
|
|
77
88
|
name: 'post-rules',
|
|
78
|
-
description: '
|
|
89
|
+
description: 'Use when the user wants to publish or share a new rule, solution, skill, or command to BitCompass. Requires kind, title, and body. Returns the created id and title on success. User must be logged in (bitcompass login).',
|
|
79
90
|
inputSchema: {
|
|
80
91
|
type: 'object',
|
|
81
92
|
properties: {
|
|
82
|
-
kind: { type: 'string', enum: ['rule', 'solution', 'skill', 'command'] },
|
|
93
|
+
kind: { type: 'string', enum: ['rule', 'solution', 'skill', 'command'], description: 'Type of entry to publish' },
|
|
83
94
|
title: { type: 'string' },
|
|
84
95
|
description: { type: 'string' },
|
|
85
96
|
body: { type: 'string' },
|
|
@@ -92,7 +103,7 @@ function createStdioServer() {
|
|
|
92
103
|
},
|
|
93
104
|
{
|
|
94
105
|
name: 'create-activity-log',
|
|
95
|
-
description: "
|
|
106
|
+
description: "Use when the user wants to record their repo activity (commits, files changed) for a time period. Ask for time_frame: day, week, or month. Requires a git repo at repo_path (default: cwd). Returns success and log id, or an error if not a git repo or auth missing.",
|
|
96
107
|
inputSchema: {
|
|
97
108
|
type: 'object',
|
|
98
109
|
properties: {
|
|
@@ -104,7 +115,7 @@ function createStdioServer() {
|
|
|
104
115
|
},
|
|
105
116
|
{
|
|
106
117
|
name: 'get-rule',
|
|
107
|
-
description: '
|
|
118
|
+
description: 'Use when you have a rule/solution ID and need the full content (title, description, body, examples, technologies). Returns the complete rule object or an error if not found. Optional kind filter verifies the entry matches that type.',
|
|
108
119
|
inputSchema: {
|
|
109
120
|
type: 'object',
|
|
110
121
|
properties: {
|
|
@@ -116,7 +127,7 @@ function createStdioServer() {
|
|
|
116
127
|
},
|
|
117
128
|
{
|
|
118
129
|
name: 'list-rules',
|
|
119
|
-
description: '
|
|
130
|
+
description: 'Use when the user wants to browse or list all rules and/or solutions without a search query. Optional kind filter (rule or solution) and limit. Returns an array of items with id, title, kind, description, author, snippet, created_at, plus total/returned counts.',
|
|
120
131
|
inputSchema: {
|
|
121
132
|
type: 'object',
|
|
122
133
|
properties: {
|
|
@@ -127,7 +138,7 @@ function createStdioServer() {
|
|
|
127
138
|
},
|
|
128
139
|
{
|
|
129
140
|
name: 'update-rule',
|
|
130
|
-
description: '
|
|
141
|
+
description: 'Use when the user wants to edit an existing rule or solution they own. Pass id and any fields to update (title, description, body, context, examples, technologies). Returns updated metadata. Requires authentication.',
|
|
131
142
|
inputSchema: {
|
|
132
143
|
type: 'object',
|
|
133
144
|
properties: {
|
|
@@ -144,7 +155,7 @@ function createStdioServer() {
|
|
|
144
155
|
},
|
|
145
156
|
{
|
|
146
157
|
name: 'delete-rule',
|
|
147
|
-
description: '
|
|
158
|
+
description: 'Use when the user wants to remove a rule or solution by ID. Returns success or error. Requires authentication; user can only delete their own entries.',
|
|
148
159
|
inputSchema: {
|
|
149
160
|
type: 'object',
|
|
150
161
|
properties: {
|
|
@@ -155,7 +166,7 @@ function createStdioServer() {
|
|
|
155
166
|
},
|
|
156
167
|
{
|
|
157
168
|
name: 'pull-rule',
|
|
158
|
-
description: '
|
|
169
|
+
description: 'Use when the user wants to install a rule or solution into their project (e.g. "pull this rule to my project"). Writes the rule to the project rules directory or optional output_path; global installs to ~/.cursor/rules/. Returns the file path written or an error.',
|
|
159
170
|
inputSchema: {
|
|
160
171
|
type: 'object',
|
|
161
172
|
properties: {
|
|
@@ -168,7 +179,7 @@ function createStdioServer() {
|
|
|
168
179
|
},
|
|
169
180
|
{
|
|
170
181
|
name: 'list-activity-logs',
|
|
171
|
-
description: "
|
|
182
|
+
description: "Use when the user wants to see their past activity logs (e.g. 'show my logs', 'list activity'). Optional limit and time_frame (day, week, month). Returns an array of logs with id, time_frame, period_start, period_end, created_at.",
|
|
172
183
|
inputSchema: {
|
|
173
184
|
type: 'object',
|
|
174
185
|
properties: {
|
|
@@ -179,7 +190,7 @@ function createStdioServer() {
|
|
|
179
190
|
},
|
|
180
191
|
{
|
|
181
192
|
name: 'get-activity-log',
|
|
182
|
-
description: '
|
|
193
|
+
description: 'Use when the user asks for details of a specific activity log by ID. Returns the full log: time_frame, period_start, period_end, repo_summary, git_analysis, created_at.',
|
|
183
194
|
inputSchema: {
|
|
184
195
|
type: 'object',
|
|
185
196
|
properties: {
|
|
@@ -299,7 +310,11 @@ function createStdioServer() {
|
|
|
299
310
|
const limit = args.limit ?? 20;
|
|
300
311
|
try {
|
|
301
312
|
const list = await searchRules(query, { kind, limit });
|
|
302
|
-
|
|
313
|
+
const summary = list.length === 0 ? 'No rules found.' : `Found ${list.length} rule(s).`;
|
|
314
|
+
return {
|
|
315
|
+
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) })),
|
|
316
|
+
summary,
|
|
317
|
+
};
|
|
303
318
|
}
|
|
304
319
|
catch (e) {
|
|
305
320
|
const msg = e instanceof Error ? e.message : 'Search failed.';
|
|
@@ -311,7 +326,11 @@ function createStdioServer() {
|
|
|
311
326
|
const limit = args.limit ?? 20;
|
|
312
327
|
try {
|
|
313
328
|
const list = await searchRules(query, { kind: 'solution', limit });
|
|
314
|
-
|
|
329
|
+
const summary = list.length === 0 ? 'No solutions found.' : `Found ${list.length} solution(s).`;
|
|
330
|
+
return {
|
|
331
|
+
solutions: list.map((r) => ({ id: r.id, title: r.title, author: r.author_display_name ?? null, snippet: r.body.slice(0, 200) })),
|
|
332
|
+
summary,
|
|
333
|
+
};
|
|
315
334
|
}
|
|
316
335
|
catch (e) {
|
|
317
336
|
const msg = e instanceof Error ? e.message : 'Search failed.';
|
|
@@ -395,6 +414,11 @@ function createStdioServer() {
|
|
|
395
414
|
try {
|
|
396
415
|
const list = await fetchRules(kind);
|
|
397
416
|
const limited = list.slice(0, limit);
|
|
417
|
+
const summary = list.length === 0
|
|
418
|
+
? 'No rules found.'
|
|
419
|
+
: limited.length < list.length
|
|
420
|
+
? `Listed ${limited.length} of ${list.length} rule(s).`
|
|
421
|
+
: `Found ${list.length} rule(s).`;
|
|
398
422
|
return {
|
|
399
423
|
rules: limited.map((r) => ({
|
|
400
424
|
id: r.id,
|
|
@@ -407,6 +431,7 @@ function createStdioServer() {
|
|
|
407
431
|
})),
|
|
408
432
|
total: list.length,
|
|
409
433
|
returned: limited.length,
|
|
434
|
+
summary,
|
|
410
435
|
};
|
|
411
436
|
}
|
|
412
437
|
catch (e) {
|
|
@@ -486,6 +511,7 @@ function createStdioServer() {
|
|
|
486
511
|
const timeFrame = args.time_frame;
|
|
487
512
|
try {
|
|
488
513
|
const logs = await fetchActivityLogs({ limit, time_frame: timeFrame });
|
|
514
|
+
const summary = logs.length === 0 ? 'No activity logs found.' : `Found ${logs.length} activity log(s).`;
|
|
489
515
|
return {
|
|
490
516
|
logs: logs.map((log) => ({
|
|
491
517
|
id: log.id,
|
|
@@ -495,6 +521,7 @@ function createStdioServer() {
|
|
|
495
521
|
created_at: log.created_at,
|
|
496
522
|
})),
|
|
497
523
|
total: logs.length,
|
|
524
|
+
summary,
|
|
498
525
|
};
|
|
499
526
|
}
|
|
500
527
|
catch (e) {
|
package/glossary.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# BitCompass terminology
|
|
2
|
+
|
|
3
|
+
Shared definitions for rules, solutions, skills, and commands.
|
|
4
|
+
|
|
5
|
+
## Rule
|
|
6
|
+
|
|
7
|
+
A **rule** is a reusable guideline or standard (e.g. coding style, when to use a tool) that can be pulled into a project. Rules are typically stored in `.cursor/rules/` or similar and guide AI or tooling behavior.
|
|
8
|
+
|
|
9
|
+
## Solution
|
|
10
|
+
|
|
11
|
+
A **solution** is a step-by-step or narrative answer to a specific problem (e.g. "How to fix X"). Solutions can be pulled like rules and are often used to document fixes or procedures.
|
|
12
|
+
|
|
13
|
+
## Skill
|
|
14
|
+
|
|
15
|
+
A **skill** is an extended capability or workflow for an agent (e.g. "Figma design-to-code"). Skills are installable into an agent environment and extend what the agent can do.
|
|
16
|
+
|
|
17
|
+
## Command
|
|
18
|
+
|
|
19
|
+
A **command** is an executable shortcut or script registered in BitCompass. Commands can be discovered and pulled into a project for reuse.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bitcompass",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "BitCompass CLI - rules, solutions, and MCP server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
"files": [
|
|
51
51
|
"dist",
|
|
52
|
-
"scripts"
|
|
52
|
+
"scripts",
|
|
53
|
+
"glossary.md"
|
|
53
54
|
]
|
|
54
55
|
}
|