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/LICENSE +21 -0
- package/README.md +200 -0
- package/dist/db.d.ts +15 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +229 -0
- package/dist/db.js.map +1 -0
- package/dist/formatters.d.ts +9 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +121 -0
- package/dist/formatters.js.map +1 -0
- package/dist/git.d.ts +7 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +47 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +168 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +46 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/watcher.d.ts +3 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +48 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +46 -0
- package/src/db.ts +239 -0
- package/src/formatters.ts +152 -0
- package/src/git.ts +44 -0
- package/src/index.ts +206 -0
- package/src/types.ts +49 -0
- package/src/watcher.ts +52 -0
- package/test.ts +4 -0
- package/tsconfig.json +20 -0
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
|
package/dist/git.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|
package/dist/types.d.ts
ADDED
|
@@ -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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
@@ -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"}
|
package/dist/watcher.js
ADDED
|
@@ -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
|
+
}
|