bitcompass 0.3.5 → 0.3.7
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 +6 -2
- package/dist/commands/commands.js +15 -6
- 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 +6 -2
- package/dist/commands/skills.js +15 -6
- package/dist/commands/solutions.d.ts +6 -1
- package/dist/commands/solutions.js +24 -3
- package/dist/index.js +81 -11
- 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/mcp/server.js +45 -17
- package/glossary.md +19 -0
- package/package.json +3 -2
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export declare const runCommandsSearch: (query?: string
|
|
2
|
-
|
|
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>;
|
|
3
7
|
export declare const runCommandsPull: (id?: string, options?: {
|
|
4
8
|
global?: boolean;
|
|
5
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 runCommandsSearch = 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 runCommandsSearch = async (query) => {
|
|
|
17
18
|
console.log(chalk.yellow('No commands 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 runCommandsSearch = async (query) => {
|
|
|
31
36
|
console.log(rule.body);
|
|
32
37
|
}
|
|
33
38
|
};
|
|
34
|
-
export const runCommandsList = async () => {
|
|
39
|
+
export const runCommandsList = 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 runCommandsList = async () => {
|
|
|
39
44
|
const spinner = ora('Loading commands…').start();
|
|
40
45
|
const list = await fetchRules('command');
|
|
41
46
|
spinner.stop();
|
|
42
|
-
list.
|
|
43
|
-
if (list.length === 0)
|
|
47
|
+
if (list.length === 0) {
|
|
44
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 });
|
|
45
53
|
};
|
|
46
54
|
export const runCommandsPull = async (id, options) => {
|
|
47
55
|
if (!loadCredentials()) {
|
|
@@ -82,8 +90,9 @@ export const runCommandsPull = async (id, options) => {
|
|
|
82
90
|
}
|
|
83
91
|
catch (error) {
|
|
84
92
|
spinner.fail(chalk.red('Failed to pull command'));
|
|
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 runCommandsPush = async (file) => {
|
|
@@ -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) => {
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
export declare const runSkillsSearch: (query?: string
|
|
2
|
-
|
|
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>;
|
|
3
7
|
export declare const runSkillsPull: (id?: string, options?: {
|
|
4
8
|
global?: boolean;
|
|
5
9
|
copy?: boolean;
|
package/dist/commands/skills.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 runSkillsSearch = 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 runSkillsSearch = async (query) => {
|
|
|
17
18
|
console.log(chalk.yellow('No skills 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 runSkillsSearch = async (query) => {
|
|
|
31
36
|
console.log(rule.body);
|
|
32
37
|
}
|
|
33
38
|
};
|
|
34
|
-
export const runSkillsList = async () => {
|
|
39
|
+
export const runSkillsList = 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 runSkillsList = async () => {
|
|
|
39
44
|
const spinner = ora('Loading skills…').start();
|
|
40
45
|
const list = await fetchRules('skill');
|
|
41
46
|
spinner.stop();
|
|
42
|
-
list.
|
|
43
|
-
if (list.length === 0)
|
|
47
|
+
if (list.length === 0) {
|
|
44
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 });
|
|
45
53
|
};
|
|
46
54
|
export const runSkillsPull = async (id, options) => {
|
|
47
55
|
if (!loadCredentials()) {
|
|
@@ -82,8 +90,9 @@ export const runSkillsPull = async (id, options) => {
|
|
|
82
90
|
}
|
|
83
91
|
catch (error) {
|
|
84
92
|
spinner.fail(chalk.red('Failed to pull skill'));
|
|
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 runSkillsPush = async (file) => {
|
|
@@ -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,12 +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
16
|
import { runSkillsList, runSkillsPull, runSkillsPush, runSkillsSearch } from './commands/skills.js';
|
|
17
17
|
import { runCommandsList, runCommandsPull, runCommandsPush, runCommandsSearch } from './commands/commands.js';
|
|
18
|
+
import { runGlossary } from './commands/glossary.js';
|
|
18
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
|
+
}
|
|
19
24
|
// Read version from package.json
|
|
20
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
26
|
const packageJsonPath = join(__dirname, '..', 'package.json');
|
|
@@ -25,7 +30,8 @@ const program = new Command();
|
|
|
25
30
|
program
|
|
26
31
|
.name('bitcompass')
|
|
27
32
|
.description('BitCompass CLI - rules, solutions, and MCP server')
|
|
28
|
-
.version(version, '-v, -V, --version', 'display version number')
|
|
33
|
+
.version(version, '-v, -V, --version', 'display version number')
|
|
34
|
+
.option('--no-color', 'Disable colored output');
|
|
29
35
|
program
|
|
30
36
|
.command('login')
|
|
31
37
|
.description('Log in with Google (opens browser)')
|
|
@@ -38,6 +44,10 @@ program
|
|
|
38
44
|
.command('whoami')
|
|
39
45
|
.description('Show current user (email)')
|
|
40
46
|
.action(runWhoami);
|
|
47
|
+
program
|
|
48
|
+
.command('glossary')
|
|
49
|
+
.description('Show glossary (rules, solutions, skills, commands)')
|
|
50
|
+
.action(runGlossary);
|
|
41
51
|
program
|
|
42
52
|
.command('init')
|
|
43
53
|
.description('Configure project: editor/AI provider and output folder for rules/docs/commands')
|
|
@@ -45,7 +55,19 @@ program
|
|
|
45
55
|
program
|
|
46
56
|
.command('log [dates...]')
|
|
47
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')
|
|
48
|
-
.
|
|
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
|
+
}));
|
|
49
71
|
const configCmd = program.command('config').description('Show or set config');
|
|
50
72
|
configCmd.action(runConfigList);
|
|
51
73
|
configCmd.command('list').description('List config values').action(runConfigList);
|
|
@@ -53,45 +75,86 @@ configCmd.command('set <key> <value>').description('Set supabaseUrl, supabaseAno
|
|
|
53
75
|
configCmd.command('get <key>').description('Get a config value').action((key) => runConfigGet(key));
|
|
54
76
|
// rules
|
|
55
77
|
const rules = program.command('rules').description('Manage rules');
|
|
56
|
-
rules
|
|
57
|
-
|
|
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));
|
|
58
89
|
rules
|
|
59
90
|
.command('pull [id]')
|
|
60
91
|
.description('Pull a rule by ID or choose from list (creates symbolic link by default)')
|
|
61
92
|
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
62
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')
|
|
63
95
|
.action((id, options) => runRulesPull(id, options).catch(handleErr));
|
|
64
96
|
rules.command('push [file]').description('Push a rule (file or interactive)').action((file) => runRulesPush(file).catch(handleErr));
|
|
65
97
|
// solutions
|
|
66
98
|
const solutions = program.command('solutions').description('Manage solutions');
|
|
67
|
-
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));
|
|
68
110
|
solutions
|
|
69
111
|
.command('pull [id]')
|
|
70
112
|
.description('Pull a solution by ID or choose from list (creates symbolic link by default)')
|
|
71
113
|
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
72
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')
|
|
73
116
|
.action((id, options) => runSolutionsPull(id, options).catch(handleErr));
|
|
74
117
|
solutions.command('push [file]').description('Push a solution (file or interactive)').action((file) => runSolutionsPush(file).catch(handleErr));
|
|
75
118
|
// skills
|
|
76
119
|
const skills = program.command('skills').description('Manage skills');
|
|
77
|
-
skills
|
|
78
|
-
|
|
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));
|
|
79
131
|
skills
|
|
80
132
|
.command('pull [id]')
|
|
81
133
|
.description('Pull a skill by ID or choose from list (creates symbolic link by default)')
|
|
82
134
|
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
83
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')
|
|
84
137
|
.action((id, options) => runSkillsPull(id, options).catch(handleErr));
|
|
85
138
|
skills.command('push [file]').description('Push a skill (file or interactive)').action((file) => runSkillsPush(file).catch(handleErr));
|
|
86
139
|
// commands
|
|
87
140
|
const commands = program.command('commands').description('Manage commands');
|
|
88
|
-
commands
|
|
89
|
-
|
|
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));
|
|
90
152
|
commands
|
|
91
153
|
.command('pull [id]')
|
|
92
154
|
.description('Pull a command by ID or choose from list (creates symbolic link by default)')
|
|
93
155
|
.option('-g, --global', 'Install globally to ~/.cursor/rules/ for all projects')
|
|
94
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')
|
|
95
158
|
.action((id, options) => runCommandsPull(id, options).catch(handleErr));
|
|
96
159
|
commands.command('push [file]').description('Push a command (file or interactive)').action((file) => runCommandsPush(file).catch(handleErr));
|
|
97
160
|
// mcp
|
|
@@ -102,4 +165,11 @@ function handleErr(err) {
|
|
|
102
165
|
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
103
166
|
process.exit(1);
|
|
104
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
|
+
}
|
|
105
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);
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
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
|
+
// From dist/mcp/server.js (or src/mcp/server.ts when run via Bun), package.json is at package root
|
|
11
|
+
const packageJsonPath = join(__dirname, '..', '..', 'package.json');
|
|
12
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
13
|
+
const VERSION = packageJson.version ?? '0.0.0';
|
|
6
14
|
/** When token is missing, we fail initialize so Cursor shows "Needs authentication" (yellow) instead of success (green). */
|
|
7
15
|
const NEEDS_AUTH_ERROR_MESSAGE = 'Needs authentication';
|
|
8
16
|
const NEEDS_AUTH_ERROR_CODE = -32001; // Server error: auth required
|
|
@@ -44,7 +52,7 @@ function createStdioServer() {
|
|
|
44
52
|
result: {
|
|
45
53
|
protocolVersion: '2024-11-05',
|
|
46
54
|
capabilities: { tools: {}, prompts: {} },
|
|
47
|
-
serverInfo: { name: 'bitcompass', version:
|
|
55
|
+
serverInfo: { name: 'bitcompass', version: VERSION },
|
|
48
56
|
},
|
|
49
57
|
});
|
|
50
58
|
return;
|
|
@@ -57,29 +65,33 @@ function createStdioServer() {
|
|
|
57
65
|
tools: [
|
|
58
66
|
{
|
|
59
67
|
name: 'search-rules',
|
|
60
|
-
description: '
|
|
68
|
+
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
69
|
inputSchema: {
|
|
62
70
|
type: 'object',
|
|
63
|
-
properties: {
|
|
71
|
+
properties: {
|
|
72
|
+
query: { type: 'string', description: 'Search query or keywords' },
|
|
73
|
+
kind: { type: 'string', enum: ['rule', 'solution', 'skill', 'command'], description: 'Optional: restrict to one kind' },
|
|
74
|
+
limit: { type: 'number', description: 'Optional: max results (default 20)' },
|
|
75
|
+
},
|
|
64
76
|
required: ['query'],
|
|
65
77
|
},
|
|
66
78
|
},
|
|
67
79
|
{
|
|
68
80
|
name: 'search-solutions',
|
|
69
|
-
description: '
|
|
81
|
+
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
82
|
inputSchema: {
|
|
71
83
|
type: 'object',
|
|
72
|
-
properties: { query: { type: 'string' }, limit: { type: 'number' } },
|
|
84
|
+
properties: { query: { type: 'string', description: 'Search query' }, limit: { type: 'number', description: 'Optional: max results' } },
|
|
73
85
|
required: ['query'],
|
|
74
86
|
},
|
|
75
87
|
},
|
|
76
88
|
{
|
|
77
89
|
name: 'post-rules',
|
|
78
|
-
description: '
|
|
90
|
+
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
91
|
inputSchema: {
|
|
80
92
|
type: 'object',
|
|
81
93
|
properties: {
|
|
82
|
-
kind: { type: 'string', enum: ['rule', 'solution', 'skill', 'command'] },
|
|
94
|
+
kind: { type: 'string', enum: ['rule', 'solution', 'skill', 'command'], description: 'Type of entry to publish' },
|
|
83
95
|
title: { type: 'string' },
|
|
84
96
|
description: { type: 'string' },
|
|
85
97
|
body: { type: 'string' },
|
|
@@ -92,7 +104,7 @@ function createStdioServer() {
|
|
|
92
104
|
},
|
|
93
105
|
{
|
|
94
106
|
name: 'create-activity-log',
|
|
95
|
-
description: "
|
|
107
|
+
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
108
|
inputSchema: {
|
|
97
109
|
type: 'object',
|
|
98
110
|
properties: {
|
|
@@ -104,7 +116,7 @@ function createStdioServer() {
|
|
|
104
116
|
},
|
|
105
117
|
{
|
|
106
118
|
name: 'get-rule',
|
|
107
|
-
description: '
|
|
119
|
+
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
120
|
inputSchema: {
|
|
109
121
|
type: 'object',
|
|
110
122
|
properties: {
|
|
@@ -116,7 +128,7 @@ function createStdioServer() {
|
|
|
116
128
|
},
|
|
117
129
|
{
|
|
118
130
|
name: 'list-rules',
|
|
119
|
-
description: '
|
|
131
|
+
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
132
|
inputSchema: {
|
|
121
133
|
type: 'object',
|
|
122
134
|
properties: {
|
|
@@ -127,7 +139,7 @@ function createStdioServer() {
|
|
|
127
139
|
},
|
|
128
140
|
{
|
|
129
141
|
name: 'update-rule',
|
|
130
|
-
description: '
|
|
142
|
+
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
143
|
inputSchema: {
|
|
132
144
|
type: 'object',
|
|
133
145
|
properties: {
|
|
@@ -144,7 +156,7 @@ function createStdioServer() {
|
|
|
144
156
|
},
|
|
145
157
|
{
|
|
146
158
|
name: 'delete-rule',
|
|
147
|
-
description: '
|
|
159
|
+
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
160
|
inputSchema: {
|
|
149
161
|
type: 'object',
|
|
150
162
|
properties: {
|
|
@@ -155,7 +167,7 @@ function createStdioServer() {
|
|
|
155
167
|
},
|
|
156
168
|
{
|
|
157
169
|
name: 'pull-rule',
|
|
158
|
-
description: '
|
|
170
|
+
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
171
|
inputSchema: {
|
|
160
172
|
type: 'object',
|
|
161
173
|
properties: {
|
|
@@ -168,7 +180,7 @@ function createStdioServer() {
|
|
|
168
180
|
},
|
|
169
181
|
{
|
|
170
182
|
name: 'list-activity-logs',
|
|
171
|
-
description: "
|
|
183
|
+
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
184
|
inputSchema: {
|
|
173
185
|
type: 'object',
|
|
174
186
|
properties: {
|
|
@@ -179,7 +191,7 @@ function createStdioServer() {
|
|
|
179
191
|
},
|
|
180
192
|
{
|
|
181
193
|
name: 'get-activity-log',
|
|
182
|
-
description: '
|
|
194
|
+
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
195
|
inputSchema: {
|
|
184
196
|
type: 'object',
|
|
185
197
|
properties: {
|
|
@@ -299,7 +311,11 @@ function createStdioServer() {
|
|
|
299
311
|
const limit = args.limit ?? 20;
|
|
300
312
|
try {
|
|
301
313
|
const list = await searchRules(query, { kind, limit });
|
|
302
|
-
|
|
314
|
+
const summary = list.length === 0 ? 'No rules found.' : `Found ${list.length} rule(s).`;
|
|
315
|
+
return {
|
|
316
|
+
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) })),
|
|
317
|
+
summary,
|
|
318
|
+
};
|
|
303
319
|
}
|
|
304
320
|
catch (e) {
|
|
305
321
|
const msg = e instanceof Error ? e.message : 'Search failed.';
|
|
@@ -311,7 +327,11 @@ function createStdioServer() {
|
|
|
311
327
|
const limit = args.limit ?? 20;
|
|
312
328
|
try {
|
|
313
329
|
const list = await searchRules(query, { kind: 'solution', limit });
|
|
314
|
-
|
|
330
|
+
const summary = list.length === 0 ? 'No solutions found.' : `Found ${list.length} solution(s).`;
|
|
331
|
+
return {
|
|
332
|
+
solutions: list.map((r) => ({ id: r.id, title: r.title, author: r.author_display_name ?? null, snippet: r.body.slice(0, 200) })),
|
|
333
|
+
summary,
|
|
334
|
+
};
|
|
315
335
|
}
|
|
316
336
|
catch (e) {
|
|
317
337
|
const msg = e instanceof Error ? e.message : 'Search failed.';
|
|
@@ -395,6 +415,11 @@ function createStdioServer() {
|
|
|
395
415
|
try {
|
|
396
416
|
const list = await fetchRules(kind);
|
|
397
417
|
const limited = list.slice(0, limit);
|
|
418
|
+
const summary = list.length === 0
|
|
419
|
+
? 'No rules found.'
|
|
420
|
+
: limited.length < list.length
|
|
421
|
+
? `Listed ${limited.length} of ${list.length} rule(s).`
|
|
422
|
+
: `Found ${list.length} rule(s).`;
|
|
398
423
|
return {
|
|
399
424
|
rules: limited.map((r) => ({
|
|
400
425
|
id: r.id,
|
|
@@ -407,6 +432,7 @@ function createStdioServer() {
|
|
|
407
432
|
})),
|
|
408
433
|
total: list.length,
|
|
409
434
|
returned: limited.length,
|
|
435
|
+
summary,
|
|
410
436
|
};
|
|
411
437
|
}
|
|
412
438
|
catch (e) {
|
|
@@ -486,6 +512,7 @@ function createStdioServer() {
|
|
|
486
512
|
const timeFrame = args.time_frame;
|
|
487
513
|
try {
|
|
488
514
|
const logs = await fetchActivityLogs({ limit, time_frame: timeFrame });
|
|
515
|
+
const summary = logs.length === 0 ? 'No activity logs found.' : `Found ${logs.length} activity log(s).`;
|
|
489
516
|
return {
|
|
490
517
|
logs: logs.map((log) => ({
|
|
491
518
|
id: log.id,
|
|
@@ -495,6 +522,7 @@ function createStdioServer() {
|
|
|
495
522
|
created_at: log.created_at,
|
|
496
523
|
})),
|
|
497
524
|
total: logs.length,
|
|
525
|
+
summary,
|
|
498
526
|
};
|
|
499
527
|
}
|
|
500
528
|
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.7",
|
|
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
|
}
|