bitcompass 0.2.4 → 0.2.5

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,5 @@
1
1
  import { type SupabaseClient } from '@supabase/supabase-js';
2
- import type { Rule, RuleInsert } from '../types.js';
2
+ import type { ActivityLog, ActivityLogInsert, Rule, RuleInsert } from '../types.js';
3
3
  /** Shown when MCP is used before logging in; instructs user to login and restart MCP. */
4
4
  export declare const AUTH_REQUIRED_MSG = "BitCompass needs authentication. Run `bitcompass login`, then restart the MCP server in your editor.";
5
5
  /** Shown when Supabase URL/key are not set; instructs config then login then restart MCP. */
@@ -17,3 +17,4 @@ export declare const getRuleById: (id: string) => Promise<Rule | null>;
17
17
  export declare const insertRule: (rule: RuleInsert) => Promise<Rule>;
18
18
  export declare const updateRule: (id: string, updates: Partial<RuleInsert>) => Promise<Rule>;
19
19
  export declare const deleteRule: (id: string) => Promise<void>;
20
+ export declare const insertActivityLog: (payload: ActivityLogInsert) => Promise<ActivityLog>;
@@ -122,3 +122,16 @@ export const deleteRule = async (id) => {
122
122
  if (error)
123
123
  throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
124
124
  };
125
+ export const insertActivityLog = async (payload) => {
126
+ const client = getSupabaseClient();
127
+ if (!client)
128
+ throw new Error(NOT_CONFIGURED_MSG);
129
+ const { data, error } = await client
130
+ .from('activity_logs')
131
+ .insert(payload)
132
+ .select()
133
+ .single();
134
+ if (error)
135
+ throw new Error(isAuthError(error) ? AUTH_REQUIRED_MSG : error.message);
136
+ return data;
137
+ };
@@ -0,0 +1,9 @@
1
+ import type { TimeFrame } from '../lib/git-analysis.js';
2
+ /**
3
+ * Shared logic: resolve repo, compute period, gather summary + git analysis, insert log.
4
+ * Used by both CLI and MCP. Returns the created log id or throws.
5
+ */
6
+ export declare const buildAndPushActivityLog: (timeFrame: TimeFrame, cwd: string) => Promise<{
7
+ id: string;
8
+ }>;
9
+ export declare const runLog: () => Promise<void>;
@@ -0,0 +1,54 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import { insertActivityLog } from '../api/client.js';
4
+ import { loadCredentials } from '../auth/config.js';
5
+ import { getRepoRoot, getRepoSummary, getGitAnalysis, getPeriodForTimeFrame, } from '../lib/git-analysis.js';
6
+ /**
7
+ * Shared logic: resolve repo, compute period, gather summary + git analysis, insert log.
8
+ * Used by both CLI and MCP. Returns the created log id or throws.
9
+ */
10
+ export const buildAndPushActivityLog = async (timeFrame, cwd) => {
11
+ const repoRoot = getRepoRoot(cwd);
12
+ if (!repoRoot) {
13
+ throw new Error('Not a git repository. Run from a project with git or pass a valid repo path.');
14
+ }
15
+ const period = getPeriodForTimeFrame(timeFrame);
16
+ const repo_summary = getRepoSummary(repoRoot);
17
+ const git_analysis = getGitAnalysis(repoRoot, period.since);
18
+ const payload = {
19
+ time_frame: timeFrame,
20
+ period_start: period.period_start,
21
+ period_end: period.period_end,
22
+ repo_summary: repo_summary,
23
+ git_analysis: git_analysis,
24
+ };
25
+ const created = await insertActivityLog(payload);
26
+ return { id: created.id };
27
+ };
28
+ export const runLog = async () => {
29
+ if (!loadCredentials()) {
30
+ console.error(chalk.red('Not logged in. Run bitcompass login.'));
31
+ process.exit(1);
32
+ }
33
+ const cwd = process.cwd();
34
+ const repoRoot = getRepoRoot(cwd);
35
+ if (!repoRoot) {
36
+ console.error(chalk.red('Not a git repository. Run this command from a project with git.'));
37
+ process.exit(1);
38
+ }
39
+ const choice = await inquirer.prompt([
40
+ {
41
+ name: 'time_frame',
42
+ message: 'Time frame',
43
+ type: 'list',
44
+ choices: [
45
+ { name: 'Day', value: 'day' },
46
+ { name: 'Week', value: 'week' },
47
+ { name: 'Month', value: 'month' },
48
+ ],
49
+ },
50
+ ]);
51
+ const timeFrame = choice.time_frame;
52
+ const result = await buildAndPushActivityLog(timeFrame, cwd);
53
+ console.log(chalk.green('Log saved.'), chalk.dim(result.id));
54
+ };
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { runConfigGet, runConfigList, runConfigSet } from './commands/config-cmd
6
6
  import { runLogin } from './commands/login.js';
7
7
  import { runLogout } from './commands/logout.js';
8
8
  import { runMcpStart, runMcpStatus } from './commands/mcp.js';
9
+ import { runLog } from './commands/log.js';
9
10
  import { runRulesList, runRulesPull, runRulesPush, runRulesSearch } from './commands/rules.js';
10
11
  import { runSolutionsPull, runSolutionsPush, runSolutionsSearch } from './commands/solutions.js';
11
12
  import { runWhoami } from './commands/whoami.js';
@@ -26,6 +27,10 @@ program
26
27
  .command('whoami')
27
28
  .description('Show current user (email)')
28
29
  .action(runWhoami);
30
+ program
31
+ .command('log')
32
+ .description('Collect repo summary and git activity, then push to your activity logs')
33
+ .action(() => runLog().catch(handleErr));
29
34
  const configCmd = program.command('config').description('Show or set config');
30
35
  configCmd.action(runConfigList);
31
36
  configCmd.command('list').description('List config values').action(runConfigList);
@@ -0,0 +1,42 @@
1
+ export type TimeFrame = 'day' | 'week' | 'month';
2
+ export interface RepoSummary {
3
+ remote_url: string;
4
+ branch: string;
5
+ repo_path: string;
6
+ }
7
+ export interface GitCommitInfo {
8
+ hash: string;
9
+ subject: string;
10
+ date: string;
11
+ }
12
+ export interface GitAnalysisResult {
13
+ commit_count: number;
14
+ commits: GitCommitInfo[];
15
+ files_changed: {
16
+ insertions: number;
17
+ deletions: number;
18
+ };
19
+ }
20
+ export interface PeriodBounds {
21
+ period_start: string;
22
+ period_end: string;
23
+ since: string;
24
+ }
25
+ /**
26
+ * Resolve the git repository root from the given directory.
27
+ * Returns null if not inside a git work tree.
28
+ */
29
+ export declare const getRepoRoot: (cwd: string) => string | null;
30
+ /**
31
+ * Get a short summary of the repo: remote URL, current branch, path.
32
+ */
33
+ export declare const getRepoSummary: (repoRoot: string) => RepoSummary;
34
+ /**
35
+ * Compute period_start, period_end, and a git --since string for the given time frame.
36
+ * period_end is now; period_start and since are the start of the window.
37
+ */
38
+ export declare const getPeriodForTimeFrame: (timeFrame: TimeFrame) => PeriodBounds;
39
+ /**
40
+ * Run git log for the given period and return structured analysis.
41
+ */
42
+ export declare const getGitAnalysis: (repoRoot: string, since: string) => GitAnalysisResult;
@@ -0,0 +1,110 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync } from 'fs';
3
+ const exec = (cmd, cwd) => {
4
+ try {
5
+ return execSync(cmd, { cwd, encoding: 'utf-8', maxBuffer: 2 * 1024 * 1024 }).trim();
6
+ }
7
+ catch {
8
+ return '';
9
+ }
10
+ };
11
+ /**
12
+ * Resolve the git repository root from the given directory.
13
+ * Returns null if not inside a git work tree.
14
+ */
15
+ export const getRepoRoot = (cwd) => {
16
+ try {
17
+ const root = exec('git rev-parse --show-toplevel', cwd);
18
+ if (!root || !existsSync(root))
19
+ return null;
20
+ return root;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ };
26
+ /**
27
+ * Get a short summary of the repo: remote URL, current branch, path.
28
+ */
29
+ export const getRepoSummary = (repoRoot) => {
30
+ const remoteUrl = exec('git remote get-url origin', repoRoot) || '';
31
+ const branch = exec('git rev-parse --abbrev-ref HEAD', repoRoot) || 'HEAD';
32
+ return {
33
+ remote_url: remoteUrl,
34
+ branch,
35
+ repo_path: repoRoot,
36
+ };
37
+ };
38
+ /**
39
+ * Compute period_start, period_end, and a git --since string for the given time frame.
40
+ * period_end is now; period_start and since are the start of the window.
41
+ */
42
+ export const getPeriodForTimeFrame = (timeFrame) => {
43
+ const now = new Date();
44
+ const period_end = now.toISOString();
45
+ let period_start;
46
+ switch (timeFrame) {
47
+ case 'day': {
48
+ const start = new Date(now);
49
+ start.setHours(0, 0, 0, 0);
50
+ period_start = start;
51
+ break;
52
+ }
53
+ case 'week': {
54
+ const start = new Date(now);
55
+ start.setDate(start.getDate() - 7);
56
+ period_start = start;
57
+ break;
58
+ }
59
+ case 'month': {
60
+ const start = new Date(now);
61
+ start.setMonth(start.getMonth() - 1);
62
+ period_start = start;
63
+ break;
64
+ }
65
+ default:
66
+ period_start = new Date(now.getTime() - 24 * 60 * 60 * 1000);
67
+ }
68
+ const since = period_start.toISOString();
69
+ return {
70
+ period_start: period_start.toISOString(),
71
+ period_end,
72
+ since,
73
+ };
74
+ };
75
+ /**
76
+ * Run git log for the given period and return structured analysis.
77
+ */
78
+ export const getGitAnalysis = (repoRoot, since) => {
79
+ const format = '%H%x00%s%x00%ci';
80
+ const logOut = exec(`git log --since="${since}" --format=${format}`, repoRoot);
81
+ const commits = [];
82
+ if (logOut) {
83
+ for (const line of logOut.split('\n')) {
84
+ const parts = line.split('\0');
85
+ if (parts.length >= 3) {
86
+ commits.push({
87
+ hash: parts[0].slice(0, 7),
88
+ subject: parts[1] || '',
89
+ date: parts[2] || '',
90
+ });
91
+ }
92
+ }
93
+ }
94
+ const shortstatOut = exec(`git log --since="${since}" --shortstat --format=`, repoRoot);
95
+ let insertions = 0;
96
+ let deletions = 0;
97
+ if (shortstatOut) {
98
+ for (const m of shortstatOut.matchAll(/(\d+) insertion[s]?/g)) {
99
+ insertions += parseInt(m[1], 10) || 0;
100
+ }
101
+ for (const m of shortstatOut.matchAll(/(\d+) deletion[s]?/g)) {
102
+ deletions += parseInt(m[1], 10) || 0;
103
+ }
104
+ }
105
+ return {
106
+ commit_count: commits.length,
107
+ commits,
108
+ files_changed: { insertions, deletions },
109
+ };
110
+ };
@@ -1,4 +1,5 @@
1
1
  import { AUTH_REQUIRED_MSG, insertRule, searchRules } from '../api/client.js';
2
+ import { buildAndPushActivityLog } from '../commands/log.js';
2
3
  import { loadCredentials } from '../auth/config.js';
3
4
  /** When token is missing, we fail initialize so Cursor shows "Needs authentication" (yellow) instead of success (green). */
4
5
  const NEEDS_AUTH_ERROR_MESSAGE = 'Needs authentication';
@@ -87,6 +88,18 @@ function createStdioServer() {
87
88
  required: ['kind', 'title', 'body'],
88
89
  },
89
90
  },
91
+ {
92
+ name: 'create-activity-log',
93
+ 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.",
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: {
97
+ time_frame: { type: 'string', enum: ['day', 'week', 'month'], description: 'Time period for the activity log' },
98
+ repo_path: { type: 'string', description: 'Path to the git repo (e.g. workspace root). If omitted, uses current working directory.' },
99
+ },
100
+ required: ['time_frame'],
101
+ },
102
+ },
90
103
  ],
91
104
  },
92
105
  });
@@ -239,6 +252,23 @@ function createStdioServer() {
239
252
  return { error: msg };
240
253
  }
241
254
  });
255
+ handlers.set('create-activity-log', async (args) => {
256
+ if (!loadCredentials()?.access_token)
257
+ return { error: AUTH_REQUIRED_MSG };
258
+ const timeFrame = args.time_frame ?? 'day';
259
+ if (timeFrame !== 'day' && timeFrame !== 'week' && timeFrame !== 'month') {
260
+ return { error: 'time_frame must be day, week, or month.' };
261
+ }
262
+ const repoPath = typeof args.repo_path === 'string' ? args.repo_path : process.cwd();
263
+ try {
264
+ const result = await buildAndPushActivityLog(timeFrame, repoPath);
265
+ return { success: true, id: result.id };
266
+ }
267
+ catch (e) {
268
+ const msg = e instanceof Error ? e.message : 'Failed to create activity log.';
269
+ return { error: msg };
270
+ }
271
+ });
242
272
  return {
243
273
  async connect() {
244
274
  // Stdio listener already attached; keep process alive
package/dist/types.d.ts CHANGED
@@ -35,3 +35,21 @@ export interface BitcompassConfig {
35
35
  supabaseUrl?: string;
36
36
  supabaseAnonKey?: string;
37
37
  }
38
+ export type ActivityLogTimeFrame = 'day' | 'week' | 'month';
39
+ export interface ActivityLogInsert {
40
+ time_frame: ActivityLogTimeFrame;
41
+ period_start: string;
42
+ period_end: string;
43
+ repo_summary: Record<string, unknown>;
44
+ git_analysis: Record<string, unknown>;
45
+ }
46
+ export interface ActivityLog {
47
+ id: string;
48
+ user_id: string;
49
+ time_frame: ActivityLogTimeFrame;
50
+ period_start: string;
51
+ period_end: string;
52
+ repo_summary: Record<string, unknown>;
53
+ git_analysis: Record<string, unknown>;
54
+ created_at: string;
55
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitcompass",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "BitCompass CLI - rules, solutions, and MCP server",
5
5
  "type": "module",
6
6
  "bin": {