bitcompass 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,4 +6,19 @@ import type { TimeFrame } from '../lib/git-analysis.js';
6
6
  export declare const buildAndPushActivityLog: (timeFrame: TimeFrame, cwd: string) => Promise<{
7
7
  id: string;
8
8
  }>;
9
- export declare const runLog: () => Promise<void>;
9
+ /**
10
+ * Push an activity log for a custom date or date range. timeFrame is used for display (day/week/month).
11
+ */
12
+ export declare const buildAndPushActivityLogWithPeriod: (period: {
13
+ period_start: string;
14
+ period_end: string;
15
+ since: string;
16
+ }, timeFrame: TimeFrame, cwd: string) => Promise<{
17
+ id: string;
18
+ }>;
19
+ /** Parse argv for log: [start] or [start, end] or [start, '-', end]. Returns { start, end } or null for interactive. */
20
+ export declare const parseLogArgs: (args: string[]) => {
21
+ start: string;
22
+ end?: string;
23
+ } | null;
24
+ export declare const runLog: (args?: string[]) => Promise<void>;
@@ -2,7 +2,7 @@ import inquirer from 'inquirer';
2
2
  import chalk from 'chalk';
3
3
  import { insertActivityLog } from '../api/client.js';
4
4
  import { loadCredentials } from '../auth/config.js';
5
- import { getRepoRoot, getRepoSummary, getGitAnalysis, getPeriodForTimeFrame, } from '../lib/git-analysis.js';
5
+ import { getRepoRoot, getRepoSummary, getGitAnalysis, getPeriodForTimeFrame, getPeriodForCustomDates, } from '../lib/git-analysis.js';
6
6
  /**
7
7
  * Shared logic: resolve repo, compute period, gather summary + git analysis, insert log.
8
8
  * Used by both CLI and MCP. Returns the created log id or throws.
@@ -25,7 +25,55 @@ export const buildAndPushActivityLog = async (timeFrame, cwd) => {
25
25
  const created = await insertActivityLog(payload);
26
26
  return { id: created.id };
27
27
  };
28
- export const runLog = async () => {
28
+ /**
29
+ * Push an activity log for a custom date or date range. timeFrame is used for display (day/week/month).
30
+ */
31
+ export const buildAndPushActivityLogWithPeriod = async (period, timeFrame, cwd) => {
32
+ const repoRoot = getRepoRoot(cwd);
33
+ if (!repoRoot) {
34
+ throw new Error('Not a git repository. Run from a project with git or pass a valid repo path.');
35
+ }
36
+ const repo_summary = getRepoSummary(repoRoot);
37
+ const git_analysis = getGitAnalysis(repoRoot, period.since, period.period_end);
38
+ const payload = {
39
+ time_frame: timeFrame,
40
+ period_start: period.period_start,
41
+ period_end: period.period_end,
42
+ repo_summary: repo_summary,
43
+ git_analysis: git_analysis,
44
+ };
45
+ const created = await insertActivityLog(payload);
46
+ return { id: created.id };
47
+ };
48
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
49
+ const isDateArg = (s) => ISO_DATE.test(s.trim());
50
+ /** Parse argv for log: [start] or [start, end] or [start, '-', end]. Returns { start, end } or null for interactive. */
51
+ export const parseLogArgs = (args) => {
52
+ const trimmed = args.map((a) => a.trim()).filter(Boolean);
53
+ if (trimmed.length === 0)
54
+ return null;
55
+ if (trimmed.length === 1 && isDateArg(trimmed[0]))
56
+ return { start: trimmed[0] };
57
+ if (trimmed.length === 2 && isDateArg(trimmed[0]) && isDateArg(trimmed[1])) {
58
+ return { start: trimmed[0], end: trimmed[1] };
59
+ }
60
+ if (trimmed.length === 3 && isDateArg(trimmed[0]) && trimmed[1] === '-' && isDateArg(trimmed[2])) {
61
+ return { start: trimmed[0], end: trimmed[2] };
62
+ }
63
+ 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
+ };
65
+ /** Choose time_frame for a custom range by span (day ≤ 1, week ≤ 7, else month). */
66
+ const timeFrameForRange = (start, end) => {
67
+ const a = new Date(start);
68
+ const b = new Date(end);
69
+ const days = Math.round((b.getTime() - a.getTime()) / (24 * 60 * 60 * 1000)) + 1;
70
+ if (days <= 1)
71
+ return 'day';
72
+ if (days <= 7)
73
+ return 'week';
74
+ return 'month';
75
+ };
76
+ export const runLog = async (args = []) => {
29
77
  if (!loadCredentials()) {
30
78
  console.error(chalk.red('Not logged in. Run bitcompass login.'));
31
79
  process.exit(1);
@@ -36,6 +84,14 @@ export const runLog = async () => {
36
84
  console.error(chalk.red('Not a git repository. Run this command from a project with git.'));
37
85
  process.exit(1);
38
86
  }
87
+ const parsed = parseLogArgs(args);
88
+ if (parsed) {
89
+ const period = getPeriodForCustomDates(parsed.start, parsed.end);
90
+ 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));
93
+ return;
94
+ }
39
95
  const choice = await inquirer.prompt([
40
96
  {
41
97
  name: 'time_frame',
package/dist/index.js CHANGED
@@ -28,9 +28,9 @@ program
28
28
  .description('Show current user (email)')
29
29
  .action(runWhoami);
30
30
  program
31
- .command('log')
32
- .description('Collect repo summary and git activity, then push to your activity logs')
33
- .action(() => runLog().catch(handleErr));
31
+ .command('log [dates...]')
32
+ .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')
33
+ .action((dates) => runLog(dates ?? []).catch(handleErr));
34
34
  const configCmd = program.command('config').description('Show or set config');
35
35
  configCmd.action(runConfigList);
36
36
  configCmd.command('list').description('List config values').action(runConfigList);
@@ -36,7 +36,13 @@ export declare const getRepoSummary: (repoRoot: string) => RepoSummary;
36
36
  * period_end is now; period_start and since are the start of the window.
37
37
  */
38
38
  export declare const getPeriodForTimeFrame: (timeFrame: TimeFrame) => PeriodBounds;
39
+ /**
40
+ * Compute period for custom date(s). Single date = that day; two dates = range (inclusive).
41
+ * period_end is end of last day (23:59:59.999).
42
+ */
43
+ export declare const getPeriodForCustomDates: (startDateStr: string, endDateStr?: string) => PeriodBounds;
39
44
  /**
40
45
  * Run git log for the given period and return structured analysis.
46
+ * If until is provided, commits are limited to the range [since, until].
41
47
  */
42
- export declare const getGitAnalysis: (repoRoot: string, since: string) => GitAnalysisResult;
48
+ export declare const getGitAnalysis: (repoRoot: string, since: string, until?: string) => GitAnalysisResult;
@@ -72,12 +72,61 @@ export const getPeriodForTimeFrame = (timeFrame) => {
72
72
  since,
73
73
  };
74
74
  };
75
+ /**
76
+ * Parse an ISO date string (YYYY-MM-DD) to Date at start of day (UTC).
77
+ */
78
+ const parseDate = (s) => {
79
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s.trim());
80
+ if (!match)
81
+ return null;
82
+ const y = parseInt(match[1], 10);
83
+ const m = parseInt(match[2], 10) - 1;
84
+ const d = parseInt(match[3], 10);
85
+ const date = new Date(Date.UTC(y, m, d, 0, 0, 0, 0));
86
+ if (date.getUTCFullYear() !== y || date.getUTCMonth() !== m || date.getUTCDate() !== d)
87
+ return null;
88
+ return date;
89
+ };
90
+ /**
91
+ * Compute period for custom date(s). Single date = that day; two dates = range (inclusive).
92
+ * period_end is end of last day (23:59:59.999).
93
+ */
94
+ export const getPeriodForCustomDates = (startDateStr, endDateStr) => {
95
+ const startDate = parseDate(startDateStr);
96
+ if (!startDate) {
97
+ throw new Error(`Invalid start date: ${startDateStr}. Use YYYY-MM-DD.`);
98
+ }
99
+ let periodEnd;
100
+ if (endDateStr !== undefined && endDateStr.trim() !== '') {
101
+ const endDate = parseDate(endDateStr);
102
+ if (!endDate)
103
+ throw new Error(`Invalid end date: ${endDateStr}. Use YYYY-MM-DD.`);
104
+ if (endDate < startDate)
105
+ throw new Error('End date must be on or after start date.');
106
+ periodEnd = new Date(endDate);
107
+ periodEnd.setUTCHours(23, 59, 59, 999);
108
+ }
109
+ else {
110
+ periodEnd = new Date(startDate);
111
+ periodEnd.setUTCHours(23, 59, 59, 999);
112
+ }
113
+ const period_start = new Date(startDate);
114
+ period_start.setUTCHours(0, 0, 0, 0);
115
+ const since = period_start.toISOString();
116
+ return {
117
+ period_start: period_start.toISOString(),
118
+ period_end: periodEnd.toISOString(),
119
+ since,
120
+ };
121
+ };
75
122
  /**
76
123
  * Run git log for the given period and return structured analysis.
124
+ * If until is provided, commits are limited to the range [since, until].
77
125
  */
78
- export const getGitAnalysis = (repoRoot, since) => {
126
+ export const getGitAnalysis = (repoRoot, since, until) => {
79
127
  const format = '%H%x00%s%x00%ci';
80
- const logOut = exec(`git log --since="${since}" --format=${format}`, repoRoot);
128
+ const untilArg = until ? ` --until="${until}"` : '';
129
+ const logOut = exec(`git log --since="${since}"${untilArg} --format=${format}`, repoRoot);
81
130
  const commits = [];
82
131
  if (logOut) {
83
132
  for (const line of logOut.split('\n')) {
@@ -91,7 +140,7 @@ export const getGitAnalysis = (repoRoot, since) => {
91
140
  }
92
141
  }
93
142
  }
94
- const shortstatOut = exec(`git log --since="${since}" --shortstat --format=`, repoRoot);
143
+ const shortstatOut = exec(`git log --since="${since}"${untilArg} --shortstat --format=`, repoRoot);
95
144
  let insertions = 0;
96
145
  let deletions = 0;
97
146
  if (shortstatOut) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bitcompass",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "BitCompass CLI - rules, solutions, and MCP server",
5
5
  "type": "module",
6
6
  "bin": {