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.
@@ -1,5 +1,9 @@
1
- export declare const runCommandsSearch: (query?: string) => Promise<void>;
2
- export declare const runCommandsList: () => Promise<void>;
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
- export const runCommandsSearch = async (query) => {
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.forEach((r) => console.log(`${chalk.cyan(r.title)} ${chalk.dim(r.id)}`));
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
- console.error(chalk.red(error.message));
86
- process.exit(1);
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(1);
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(1);
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
+ };
@@ -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>;
@@ -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
- const result = await buildAndPushActivityLogWithPeriod(period, timeFrame, cwd);
92
- console.log(chalk.green('Log saved.'), chalk.dim(result.id));
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
- const result = await buildAndPushActivityLog(timeFrame, cwd);
109
- console.log(chalk.green('Log saved.'), chalk.dim(result.id));
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
  };
@@ -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
@@ -1,5 +1,9 @@
1
- export declare const runRulesSearch: (query?: string) => Promise<void>;
2
- export declare const runRulesList: () => Promise<void>;
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;
@@ -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
- export const runRulesSearch = async (query) => {
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.forEach((r) => console.log(`${chalk.cyan(r.title)} ${chalk.dim(r.id)}`));
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
- console.error(chalk.red(error.message));
86
- process.exit(1);
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) => Promise<void>;
2
- export declare const runSkillsList: () => Promise<void>;
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;
@@ -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
- export const runSkillsSearch = async (query) => {
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.forEach((r) => console.log(`${chalk.cyan(r.title)} ${chalk.dim(r.id)}`));
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
- console.error(chalk.red(error.message));
86
- process.exit(1);
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) => Promise<void>;
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
- export const runSolutionsSearch = async (query) => {
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
- console.error(chalk.red(error.message));
74
- process.exit(1);
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
- .action((dates) => runLog(dates ?? []).catch(handleErr));
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.command('search [query]').description('Search rules').action((query) => runRulesSearch(query).catch(handleErr));
57
- rules.command('list').description('List rules').action(() => runRulesList().catch(handleErr));
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.command('search [query]').description('Search solutions').action((query) => runSolutionsSearch(query).catch(handleErr));
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.command('search [query]').description('Search skills').action((query) => runSkillsSearch(query).catch(handleErr));
78
- skills.command('list').description('List skills').action(() => runSkillsList().catch(handleErr));
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.command('search [query]').description('Search commands').action((query) => runCommandsSearch(query).catch(handleErr));
89
- commands.command('list').description('List commands').action(() => runCommandsList().catch(handleErr));
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).
@@ -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);
@@ -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: '0.1.0' },
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: 'Search BitCompass rules by query',
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: { query: { type: 'string' }, kind: { type: 'string', enum: ['rule', 'solution', 'skill', 'command'] }, limit: { type: 'number' } },
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: 'Search BitCompass solutions by query',
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: 'Publish a new rule or solution to BitCompass',
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: "Collect a summary of the repository and git activity for the chosen period, then push the log to the user's private activity logs. Requires a git repository; if repo_path is not a git repo, returns an error. Ask the user which time frame they want: day, week, or month.",
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: 'Get full details of a rule or solution by ID',
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: 'List all rules and solutions (with optional filtering by kind)',
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: 'Update an existing rule or solution',
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: 'Delete a rule or solution by ID',
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: 'Pull a rule or solution to a file in the project rules directory',
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: "List user's activity logs",
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: 'Get activity log details by ID',
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
- return { rules: list.map((r) => ({ id: r.id, title: r.title, kind: r.kind, author: r.author_display_name ?? null, snippet: r.body.slice(0, 200) })) };
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
- return { solutions: list.map((r) => ({ id: r.id, title: r.title, author: r.author_display_name ?? null, snippet: r.body.slice(0, 200) })) };
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.5",
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
  }