codesession-cli 1.0.0

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/git.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initGit = initGit;
4
+ exports.checkForNewCommits = checkForNewCommits;
5
+ exports.getGitInfo = getGitInfo;
6
+ const simple_git_1 = require("simple-git");
7
+ const db_1 = require("./db");
8
+ let git;
9
+ let lastCommitHash = null;
10
+ function initGit(cwd) {
11
+ git = (0, simple_git_1.simpleGit)(cwd);
12
+ }
13
+ async function checkForNewCommits(sessionId) {
14
+ if (!git)
15
+ return;
16
+ try {
17
+ const log = await git.log({ maxCount: 1 });
18
+ if (log.latest && log.latest.hash !== lastCommitHash) {
19
+ lastCommitHash = log.latest.hash;
20
+ (0, db_1.addCommit)({
21
+ sessionId,
22
+ hash: log.latest.hash.substring(0, 7),
23
+ message: log.latest.message,
24
+ timestamp: new Date().toISOString(),
25
+ });
26
+ }
27
+ }
28
+ catch (error) {
29
+ // Not a git repo or no commits yet
30
+ }
31
+ }
32
+ async function getGitInfo() {
33
+ if (!git)
34
+ return null;
35
+ try {
36
+ const branch = await git.revparse(['--abbrev-ref', 'HEAD']);
37
+ const status = await git.status();
38
+ return {
39
+ branch: branch.trim(),
40
+ hasChanges: !status.isClean(),
41
+ };
42
+ }
43
+ catch (error) {
44
+ return null;
45
+ }
46
+ }
47
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../src/git.ts"],"names":[],"mappings":";;AAMA,0BAEC;AAED,gDAkBC;AAED,gCAaC;AA3CD,2CAAkD;AAClD,6BAAiC;AAEjC,IAAI,GAAc,CAAC;AACnB,IAAI,cAAc,GAAkB,IAAI,CAAC;AAEzC,SAAgB,OAAO,CAAC,GAAW;IACjC,GAAG,GAAG,IAAA,sBAAS,EAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAEM,KAAK,UAAU,kBAAkB,CAAC,SAAiB;IACxD,IAAI,CAAC,GAAG;QAAE,OAAO;IAEjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACrD,cAAc,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;YAEjC,IAAA,cAAS,EAAC;gBACR,SAAS;gBACT,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC;gBACrC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO;gBAC3B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;aACpC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,mCAAmC;IACrC,CAAC;AACH,CAAC;AAEM,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IAEtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;QAClC,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE;YACrB,UAAU,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE;SAC9B,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const db_1 = require("./db");
10
+ const git_1 = require("./git");
11
+ const watcher_1 = require("./watcher");
12
+ const formatters_1 = require("./formatters");
13
+ const program = new commander_1.Command();
14
+ program
15
+ .name('devsession')
16
+ .description('Track your AI coding sessions: time, files, commits, AI costs')
17
+ .version('1.0.0');
18
+ // Start command
19
+ program
20
+ .command('start')
21
+ .description('Start a new coding session')
22
+ .argument('<name>', 'Session name')
23
+ .action(async (name) => {
24
+ const active = (0, db_1.getActiveSession)();
25
+ if (active) {
26
+ console.log(chalk_1.default.yellow(`\nSession "${active.name}" is already active.`));
27
+ console.log(chalk_1.default.gray('End it with: ds end\n'));
28
+ return;
29
+ }
30
+ const cwd = process.cwd();
31
+ const sessionId = (0, db_1.createSession)({
32
+ name,
33
+ startTime: new Date().toISOString(),
34
+ workingDirectory: cwd,
35
+ filesChanged: 0,
36
+ commits: 0,
37
+ aiCost: 0,
38
+ aiTokens: 0,
39
+ status: 'active',
40
+ });
41
+ // Initialize git tracking
42
+ (0, git_1.initGit)(cwd);
43
+ // Start file watcher
44
+ (0, watcher_1.startWatcher)(sessionId, cwd);
45
+ // Check for commits every 10 seconds
46
+ const gitInterval = setInterval(async () => {
47
+ await (0, git_1.checkForNewCommits)(sessionId);
48
+ }, 10000);
49
+ // Store interval ID
50
+ global.gitInterval = gitInterval;
51
+ const gitInfo = await (0, git_1.getGitInfo)();
52
+ console.log(chalk_1.default.green(`\n✓ Session started: ${name}`));
53
+ if (gitInfo) {
54
+ console.log(chalk_1.default.gray(` Branch: ${gitInfo.branch}`));
55
+ }
56
+ console.log(chalk_1.default.gray(` Directory: ${cwd}`));
57
+ console.log(chalk_1.default.gray('\n Tracking: files, commits, AI usage'));
58
+ console.log(chalk_1.default.gray(' End with: ds end\n'));
59
+ });
60
+ // End command
61
+ program
62
+ .command('end')
63
+ .description('End the active session')
64
+ .option('-n, --notes <notes>', 'Session notes')
65
+ .action((options) => {
66
+ const session = (0, db_1.getActiveSession)();
67
+ if (!session) {
68
+ console.log(chalk_1.default.yellow('\nNo active session.\n'));
69
+ return;
70
+ }
71
+ // Stop tracking
72
+ (0, watcher_1.stopWatcher)();
73
+ if (global.gitInterval) {
74
+ clearInterval(global.gitInterval);
75
+ }
76
+ (0, db_1.endSession)(session.id, new Date().toISOString(), options.notes);
77
+ const updated = (0, db_1.getSession)(session.id);
78
+ if (updated) {
79
+ console.log(chalk_1.default.green('\n✓ Session ended\n'));
80
+ (0, formatters_1.displaySession)(updated);
81
+ }
82
+ });
83
+ // Show command
84
+ program
85
+ .command('show')
86
+ .description('Show session details')
87
+ .argument('[id]', 'Session ID (defaults to last session)')
88
+ .option('--files', 'Show file changes')
89
+ .option('--commits', 'Show commits')
90
+ .action((id, options) => {
91
+ let session;
92
+ if (id) {
93
+ session = (0, db_1.getSession)(parseInt(id));
94
+ }
95
+ else {
96
+ const sessions = (0, db_1.getSessions)(1);
97
+ session = sessions[0];
98
+ }
99
+ if (!session) {
100
+ console.log(chalk_1.default.yellow('\nSession not found.\n'));
101
+ return;
102
+ }
103
+ (0, formatters_1.displaySession)(session);
104
+ if (options.files) {
105
+ const files = (0, db_1.getFileChanges)(session.id);
106
+ (0, formatters_1.displayFileChanges)(files);
107
+ }
108
+ if (options.commits) {
109
+ const commits = (0, db_1.getCommits)(session.id);
110
+ (0, formatters_1.displayCommits)(commits);
111
+ }
112
+ });
113
+ // List command
114
+ program
115
+ .command('list')
116
+ .alias('ls')
117
+ .description('List recent sessions')
118
+ .option('-l, --limit <number>', 'Number of sessions to show', parseInt, 10)
119
+ .action((options) => {
120
+ const sessions = (0, db_1.getSessions)(options.limit);
121
+ (0, formatters_1.displaySessions)(sessions);
122
+ });
123
+ // Stats command
124
+ program
125
+ .command('stats')
126
+ .description('Show overall statistics')
127
+ .action(() => {
128
+ const stats = (0, db_1.getStats)();
129
+ (0, formatters_1.displayStats)(stats);
130
+ });
131
+ // Log AI usage command
132
+ program
133
+ .command('log-ai')
134
+ .description('Log AI usage for active session')
135
+ .requiredOption('-p, --provider <provider>', 'AI provider')
136
+ .requiredOption('-m, --model <model>', 'Model name')
137
+ .requiredOption('-t, --tokens <tokens>', 'Total tokens', parseInt)
138
+ .requiredOption('-c, --cost <cost>', 'Cost in dollars', parseFloat)
139
+ .action((options) => {
140
+ const session = (0, db_1.getActiveSession)();
141
+ if (!session) {
142
+ console.log(chalk_1.default.yellow('\nNo active session. Start one with: ds start <name>\n'));
143
+ return;
144
+ }
145
+ (0, db_1.addAIUsage)({
146
+ sessionId: session.id,
147
+ provider: options.provider,
148
+ model: options.model,
149
+ tokens: options.tokens,
150
+ cost: options.cost,
151
+ timestamp: new Date().toISOString(),
152
+ });
153
+ console.log(chalk_1.default.green(`\n✓ Logged AI usage: ${options.tokens.toLocaleString()} tokens, $${options.cost.toFixed(2)}\n`));
154
+ });
155
+ // Status command
156
+ program
157
+ .command('status')
158
+ .description('Show active session status')
159
+ .action(() => {
160
+ const session = (0, db_1.getActiveSession)();
161
+ if (!session) {
162
+ console.log(chalk_1.default.yellow('\nNo active session.\n'));
163
+ return;
164
+ }
165
+ (0, formatters_1.displaySession)(session);
166
+ });
167
+ program.parse();
168
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ export interface Session {
2
+ id?: number;
3
+ name: string;
4
+ startTime: string;
5
+ endTime?: string;
6
+ duration?: number;
7
+ workingDirectory: string;
8
+ filesChanged: number;
9
+ commits: number;
10
+ aiCost: number;
11
+ aiTokens: number;
12
+ notes?: string;
13
+ status: 'active' | 'completed';
14
+ }
15
+ export interface FileChange {
16
+ id?: number;
17
+ sessionId: number;
18
+ filePath: string;
19
+ changeType: 'created' | 'modified' | 'deleted';
20
+ timestamp: string;
21
+ }
22
+ export interface Commit {
23
+ id?: number;
24
+ sessionId: number;
25
+ hash: string;
26
+ message: string;
27
+ timestamp: string;
28
+ }
29
+ export interface AIUsage {
30
+ id?: number;
31
+ sessionId: number;
32
+ provider: string;
33
+ model: string;
34
+ tokens: number;
35
+ cost: number;
36
+ timestamp: string;
37
+ }
38
+ export interface SessionStats {
39
+ totalSessions: number;
40
+ totalTime: number;
41
+ totalFiles: number;
42
+ totalCommits: number;
43
+ totalAICost: number;
44
+ avgSessionTime: number;
45
+ }
46
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,QAAQ,GAAG,WAAW,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC/C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ export declare function startWatcher(sessionId: number, cwd: string): void;
2
+ export declare function stopWatcher(): void;
3
+ //# sourceMappingURL=watcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAOA,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAajE;AAED,wBAAgB,WAAW,IAAI,IAAI,CAMlC"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.startWatcher = startWatcher;
7
+ exports.stopWatcher = stopWatcher;
8
+ const chokidar_1 = __importDefault(require("chokidar"));
9
+ const path_1 = require("path");
10
+ const db_1 = require("./db");
11
+ let watcher = null;
12
+ const changedFiles = new Set();
13
+ function startWatcher(sessionId, cwd) {
14
+ if (watcher)
15
+ return;
16
+ watcher = chokidar_1.default.watch(cwd, {
17
+ ignored: /(^|[\/\\])\..|(node_modules|dist|build|\.git)/,
18
+ persistent: true,
19
+ ignoreInitial: true,
20
+ });
21
+ watcher
22
+ .on('add', (path) => handleChange(sessionId, path, cwd, 'created'))
23
+ .on('change', (path) => handleChange(sessionId, path, cwd, 'modified'))
24
+ .on('unlink', (path) => handleChange(sessionId, path, cwd, 'deleted'));
25
+ }
26
+ function stopWatcher() {
27
+ if (watcher) {
28
+ watcher.close();
29
+ watcher = null;
30
+ changedFiles.clear();
31
+ }
32
+ }
33
+ function handleChange(sessionId, path, cwd, changeType) {
34
+ const relativePath = (0, path_1.relative)(cwd, path);
35
+ // Deduplicate rapid changes to same file
36
+ const key = `${relativePath}-${changeType}`;
37
+ if (changedFiles.has(key))
38
+ return;
39
+ changedFiles.add(key);
40
+ setTimeout(() => changedFiles.delete(key), 1000);
41
+ (0, db_1.addFileChange)({
42
+ sessionId,
43
+ filePath: relativePath,
44
+ changeType,
45
+ timestamp: new Date().toISOString(),
46
+ });
47
+ }
48
+ //# sourceMappingURL=watcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":";;;;;AAOA,oCAaC;AAED,kCAMC;AA5BD,wDAAgC;AAChC,+BAAgC;AAChC,6BAAqC;AAErC,IAAI,OAAO,GAA8B,IAAI,CAAC;AAC9C,MAAM,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;AAEvC,SAAgB,YAAY,CAAC,SAAiB,EAAE,GAAW;IACzD,IAAI,OAAO;QAAE,OAAO;IAEpB,OAAO,GAAG,kBAAQ,CAAC,KAAK,CAAC,GAAG,EAAE;QAC5B,OAAO,EAAE,+CAA+C;QACxD,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI;KACpB,CAAC,CAAC;IAEH,OAAO;SACJ,EAAE,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;SAClE,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;SACtE,EAAE,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAgB,WAAW;IACzB,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,KAAK,EAAE,CAAC;QAChB,OAAO,GAAG,IAAI,CAAC;QACf,YAAY,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CACnB,SAAiB,EACjB,IAAY,EACZ,GAAW,EACX,UAA8C;IAE9C,MAAM,YAAY,GAAG,IAAA,eAAQ,EAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAEzC,yCAAyC;IACzC,MAAM,GAAG,GAAG,GAAG,YAAY,IAAI,UAAU,EAAE,CAAC;IAC5C,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO;IAElC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACtB,UAAU,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;IAEjD,IAAA,kBAAa,EAAC;QACZ,SAAS;QACT,QAAQ,EAAE,YAAY;QACtB,UAAU;QACV,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC,CAAC;AACL,CAAC"}
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "codesession-cli",
3
+ "version": "1.0.0",
4
+ "description": "Track your AI coding sessions: time, files, commits, AI costs",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "codesession": "./dist/index.js",
8
+ "cs": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "prepare": "npm run build"
15
+ },
16
+ "keywords": [
17
+ "ai",
18
+ "coding",
19
+ "session",
20
+ "tracker",
21
+ "productivity",
22
+ "git",
23
+ "time-tracking",
24
+ "cli"
25
+ ],
26
+ "author": "Brian Mwirigi",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "chalk": "^4.1.2",
30
+ "commander": "^11.1.0",
31
+ "better-sqlite3": "^9.2.2",
32
+ "cli-table3": "^0.6.3",
33
+ "date-fns": "^3.0.6",
34
+ "chokidar": "^3.5.3",
35
+ "simple-git": "^3.22.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/better-sqlite3": "^7.6.8",
39
+ "@types/node": "^20.10.6",
40
+ "tsx": "^4.7.0",
41
+ "typescript": "^5.3.3"
42
+ },
43
+ "engines": {
44
+ "node": ">=16.0.0"
45
+ }
46
+ }
package/src/db.ts ADDED
@@ -0,0 +1,239 @@
1
+ import Database from 'better-sqlite3';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ import { existsSync, mkdirSync } from 'fs';
5
+ import { Session, FileChange, Commit, AIUsage, SessionStats } from './types';
6
+
7
+ const DB_DIR = join(homedir(), '.devsession');
8
+ const DB_PATH = join(DB_DIR, 'sessions.db');
9
+
10
+ if (!existsSync(DB_DIR)) {
11
+ mkdirSync(DB_DIR, { recursive: true });
12
+ }
13
+
14
+ const db = new Database(DB_PATH);
15
+
16
+ // Initialize database
17
+ db.exec(`
18
+ CREATE TABLE IF NOT EXISTS sessions (
19
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
20
+ name TEXT NOT NULL,
21
+ start_time TEXT NOT NULL,
22
+ end_time TEXT,
23
+ duration INTEGER,
24
+ working_directory TEXT NOT NULL,
25
+ files_changed INTEGER DEFAULT 0,
26
+ commits INTEGER DEFAULT 0,
27
+ ai_cost REAL DEFAULT 0,
28
+ ai_tokens INTEGER DEFAULT 0,
29
+ notes TEXT,
30
+ status TEXT DEFAULT 'active'
31
+ )
32
+ `);
33
+
34
+ db.exec(`
35
+ CREATE TABLE IF NOT EXISTS file_changes (
36
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37
+ session_id INTEGER NOT NULL,
38
+ file_path TEXT NOT NULL,
39
+ change_type TEXT NOT NULL,
40
+ timestamp TEXT NOT NULL,
41
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
42
+ )
43
+ `);
44
+
45
+ db.exec(`
46
+ CREATE TABLE IF NOT EXISTS commits (
47
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ session_id INTEGER NOT NULL,
49
+ hash TEXT NOT NULL,
50
+ message TEXT NOT NULL,
51
+ timestamp TEXT NOT NULL,
52
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
53
+ )
54
+ `);
55
+
56
+ db.exec(`
57
+ CREATE TABLE IF NOT EXISTS ai_usage (
58
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
59
+ session_id INTEGER NOT NULL,
60
+ provider TEXT NOT NULL,
61
+ model TEXT NOT NULL,
62
+ tokens INTEGER NOT NULL,
63
+ cost REAL NOT NULL,
64
+ timestamp TEXT NOT NULL,
65
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
66
+ )
67
+ `);
68
+
69
+ export function createSession(session: Omit<Session, 'id'>): number {
70
+ const stmt = db.prepare(`
71
+ INSERT INTO sessions (name, start_time, working_directory, status)
72
+ VALUES (?, ?, ?, ?)
73
+ `);
74
+ const result = stmt.run(session.name, session.startTime, session.workingDirectory, 'active');
75
+ return result.lastInsertRowid as number;
76
+ }
77
+
78
+ export function getActiveSession(): Session | null {
79
+ const stmt = db.prepare('SELECT * FROM sessions WHERE status = ? ORDER BY id DESC LIMIT 1');
80
+ const row = stmt.get('active') as any;
81
+ if (!row) return null;
82
+ return mapSession(row);
83
+ }
84
+
85
+ export function endSession(sessionId: number, endTime: string, notes?: string): void {
86
+ const session = getSession(sessionId);
87
+ if (!session) return;
88
+
89
+ const duration = Math.floor((new Date(endTime).getTime() - new Date(session.startTime).getTime()) / 1000);
90
+
91
+ const stmt = db.prepare(`
92
+ UPDATE sessions
93
+ SET end_time = ?, duration = ?, status = ?, notes = ?
94
+ WHERE id = ?
95
+ `);
96
+ stmt.run(endTime, duration, 'completed', notes || null, sessionId);
97
+ }
98
+
99
+ export function getSession(sessionId: number): Session | null {
100
+ const stmt = db.prepare('SELECT * FROM sessions WHERE id = ?');
101
+ const row = stmt.get(sessionId) as any;
102
+ return row ? mapSession(row) : null;
103
+ }
104
+
105
+ export function getSessions(limit = 10): Session[] {
106
+ const stmt = db.prepare('SELECT * FROM sessions ORDER BY start_time DESC LIMIT ?');
107
+ const rows = stmt.all(limit) as any[];
108
+ return rows.map(mapSession);
109
+ }
110
+
111
+ export function addFileChange(change: Omit<FileChange, 'id'>): void {
112
+ const stmt = db.prepare(`
113
+ INSERT INTO file_changes (session_id, file_path, change_type, timestamp)
114
+ VALUES (?, ?, ?, ?)
115
+ `);
116
+ stmt.run(change.sessionId, change.filePath, change.changeType, change.timestamp);
117
+
118
+ // Update session files count
119
+ const countStmt = db.prepare('SELECT COUNT(DISTINCT file_path) as count FROM file_changes WHERE session_id = ?');
120
+ const result = countStmt.get(change.sessionId) as any;
121
+
122
+ const updateStmt = db.prepare('UPDATE sessions SET files_changed = ? WHERE id = ?');
123
+ updateStmt.run(result.count, change.sessionId);
124
+ }
125
+
126
+ export function addCommit(commit: Omit<Commit, 'id'>): void {
127
+ const stmt = db.prepare(`
128
+ INSERT INTO commits (session_id, hash, message, timestamp)
129
+ VALUES (?, ?, ?, ?)
130
+ `);
131
+ stmt.run(commit.sessionId, commit.hash, commit.message, commit.timestamp);
132
+
133
+ // Update session commits count
134
+ const countStmt = db.prepare('SELECT COUNT(*) as count FROM commits WHERE session_id = ?');
135
+ const result = countStmt.get(commit.sessionId) as any;
136
+
137
+ const updateStmt = db.prepare('UPDATE sessions SET commits = ? WHERE id = ?');
138
+ updateStmt.run(result.count, commit.sessionId);
139
+ }
140
+
141
+ export function addAIUsage(usage: Omit<AIUsage, 'id'>): void {
142
+ const stmt = db.prepare(`
143
+ INSERT INTO ai_usage (session_id, provider, model, tokens, cost, timestamp)
144
+ VALUES (?, ?, ?, ?, ?, ?)
145
+ `);
146
+ stmt.run(usage.sessionId, usage.provider, usage.model, usage.tokens, usage.cost, usage.timestamp);
147
+
148
+ // Update session AI totals
149
+ const sumStmt = db.prepare(`
150
+ SELECT SUM(cost) as total_cost, SUM(tokens) as total_tokens
151
+ FROM ai_usage WHERE session_id = ?
152
+ `);
153
+ const result = sumStmt.get(usage.sessionId) as any;
154
+
155
+ const updateStmt = db.prepare('UPDATE sessions SET ai_cost = ?, ai_tokens = ? WHERE id = ?');
156
+ updateStmt.run(result.total_cost || 0, result.total_tokens || 0, usage.sessionId);
157
+ }
158
+
159
+ export function getFileChanges(sessionId: number): FileChange[] {
160
+ const stmt = db.prepare('SELECT * FROM file_changes WHERE session_id = ? ORDER BY timestamp');
161
+ const rows = stmt.all(sessionId) as any[];
162
+ return rows.map((row) => ({
163
+ id: row.id,
164
+ sessionId: row.session_id,
165
+ filePath: row.file_path,
166
+ changeType: row.change_type,
167
+ timestamp: row.timestamp,
168
+ }));
169
+ }
170
+
171
+ export function getCommits(sessionId: number): Commit[] {
172
+ const stmt = db.prepare('SELECT * FROM commits WHERE session_id = ? ORDER BY timestamp');
173
+ const rows = stmt.all(sessionId) as any[];
174
+ return rows.map((row) => ({
175
+ id: row.id,
176
+ sessionId: row.session_id,
177
+ hash: row.hash,
178
+ message: row.message,
179
+ timestamp: row.timestamp,
180
+ }));
181
+ }
182
+
183
+ export function getAIUsage(sessionId: number): AIUsage[] {
184
+ const stmt = db.prepare('SELECT * FROM ai_usage WHERE session_id = ? ORDER BY timestamp');
185
+ const rows = stmt.all(sessionId) as any[];
186
+ return rows.map((row) => ({
187
+ id: row.id,
188
+ sessionId: row.session_id,
189
+ provider: row.provider,
190
+ model: row.model,
191
+ tokens: row.tokens,
192
+ cost: row.cost,
193
+ timestamp: row.timestamp,
194
+ }));
195
+ }
196
+
197
+ export function getStats(): SessionStats {
198
+ const stmt = db.prepare(`
199
+ SELECT
200
+ COUNT(*) as total,
201
+ SUM(duration) as total_time,
202
+ SUM(files_changed) as total_files,
203
+ SUM(commits) as total_commits,
204
+ SUM(ai_cost) as total_cost,
205
+ AVG(duration) as avg_time
206
+ FROM sessions WHERE status = 'completed'
207
+ `);
208
+ const result = stmt.get() as any;
209
+
210
+ return {
211
+ totalSessions: result.total || 0,
212
+ totalTime: result.total_time || 0,
213
+ totalFiles: result.total_files || 0,
214
+ totalCommits: result.total_commits || 0,
215
+ totalAICost: result.total_cost || 0,
216
+ avgSessionTime: result.avg_time || 0,
217
+ };
218
+ }
219
+
220
+ function mapSession(row: any): Session {
221
+ return {
222
+ id: row.id,
223
+ name: row.name,
224
+ startTime: row.start_time,
225
+ endTime: row.end_time,
226
+ duration: row.duration,
227
+ workingDirectory: row.working_directory,
228
+ filesChanged: row.files_changed,
229
+ commits: row.commits,
230
+ aiCost: row.ai_cost,
231
+ aiTokens: row.ai_tokens,
232
+ notes: row.notes,
233
+ status: row.status,
234
+ };
235
+ }
236
+
237
+ export function closeDb(): void {
238
+ db.close();
239
+ }