codeant-cli 0.2.5 → 0.2.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.
- package/dist/178.index.js +1 -1
- package/dist/239.index.js +1 -1
- package/dist/701.index.js +1 -1
- package/dist/880.index.js +1 -1
- package/dist/935.index.js +1 -1
- package/dist/966.index.js +1 -1
- package/dist/index.js +11 -4
- package/package.json +2 -1
- package/src/commands/review.js +20 -1
- package/src/index.js +21 -0
- package/src/reviewHeadless.js +22 -1
- package/src/utils/analytics.js +45 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeant-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "Code review CLI tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"ink": "^5.0.1",
|
|
42
42
|
"minimatch": "^10.1.1",
|
|
43
43
|
"open": "^10.1.0",
|
|
44
|
+
"posthog-node": "^5.28.5",
|
|
44
45
|
"react": "^18.3.1"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
package/src/commands/review.js
CHANGED
|
@@ -6,6 +6,7 @@ import { fetchApi } from '../utils/fetchApi.js';
|
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { executeTool } from '../tools/executeTool.js';
|
|
9
|
+
import { track, shutdown as analyticsShutdown } from '../utils/analytics.js';
|
|
9
10
|
import {
|
|
10
11
|
renderInitializing,
|
|
11
12
|
renderFetchingDiff,
|
|
@@ -51,6 +52,9 @@ export default function Review({ scanType = 'all', lastNCommits = 1, failOn = 'C
|
|
|
51
52
|
let cancelled = false;
|
|
52
53
|
|
|
53
54
|
async function runReview() {
|
|
55
|
+
const reviewStartTime = Date.now();
|
|
56
|
+
track('review_triggered', { scan_type: scanType, source: 'cli_interactive' });
|
|
57
|
+
|
|
54
58
|
try {
|
|
55
59
|
// ── Fetch diff ─────────────────────────────────────────────────────
|
|
56
60
|
setStatus('fetching_diff');
|
|
@@ -140,9 +144,24 @@ export default function Review({ scanType = 'all', lastNCommits = 1, failOn = 'C
|
|
|
140
144
|
setSummary(null);
|
|
141
145
|
setStatus('done');
|
|
142
146
|
|
|
147
|
+
const labelCounts = {};
|
|
148
|
+
for (const i of reviewIssues) { labelCounts[i.label] = (labelCounts[i.label] || 0) + 1; }
|
|
149
|
+
track('review_completed', {
|
|
150
|
+
scan_type: scanType,
|
|
151
|
+
source: 'cli_interactive',
|
|
152
|
+
issue_count: reviewIssues.length,
|
|
153
|
+
file_count: perFileRequests.length,
|
|
154
|
+
duration_ms: Date.now() - reviewStartTime,
|
|
155
|
+
label_counts: labelCounts,
|
|
156
|
+
});
|
|
157
|
+
await analyticsShutdown();
|
|
158
|
+
|
|
143
159
|
} catch (err) {
|
|
144
160
|
if (!cancelled) {
|
|
145
|
-
|
|
161
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
162
|
+
track('review_error', { scan_type: scanType, source: 'cli_interactive', error: errMsg });
|
|
163
|
+
await analyticsShutdown();
|
|
164
|
+
setError(errMsg);
|
|
146
165
|
setStatus('error');
|
|
147
166
|
}
|
|
148
167
|
}
|
package/src/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import Review from './commands/review.js';
|
|
|
13
13
|
import Welcome from './components/Welcome.js';
|
|
14
14
|
import * as scm from './scm/index.js';
|
|
15
15
|
import { setConfigValue } from './utils/config.js';
|
|
16
|
+
import { track, shutdown as analyticsShutdown } from './utils/analytics.js';
|
|
16
17
|
|
|
17
18
|
// Read version from package.json — createRequire works with ncc bundling
|
|
18
19
|
const require = createRequire(import.meta.url);
|
|
@@ -324,5 +325,25 @@ program
|
|
|
324
325
|
}));
|
|
325
326
|
});
|
|
326
327
|
|
|
328
|
+
// ─── Analytics tracking (for external callers like Claude Code skills) ───
|
|
329
|
+
program
|
|
330
|
+
.command('track')
|
|
331
|
+
.description('Send an analytics event')
|
|
332
|
+
.requiredOption('--event <name>', 'Event name (e.g. skill_invoked, suggestions_applied)')
|
|
333
|
+
.option('--props <json>', 'JSON string of event properties', '{}')
|
|
334
|
+
.action(async (options) => {
|
|
335
|
+
try {
|
|
336
|
+
const props = JSON.parse(options.props);
|
|
337
|
+
if (typeof props !== 'object' || props === null || Array.isArray(props)) {
|
|
338
|
+
process.stderr.write('--props must be a JSON object\n');
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
track(options.event, props);
|
|
342
|
+
await analyticsShutdown();
|
|
343
|
+
} catch (err) {
|
|
344
|
+
process.stderr.write(`Invalid --props JSON: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
345
|
+
}
|
|
346
|
+
});
|
|
347
|
+
|
|
327
348
|
program.parse();
|
|
328
349
|
}
|
package/src/reviewHeadless.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import ReviewApiHelper from './utils/reviewApiHelper.js';
|
|
2
2
|
import { fetchApi } from './utils/fetchApi.js';
|
|
3
3
|
import { executeTool } from './tools/executeTool.js';
|
|
4
|
+
import { getConfigValue, setConfigValue } from './utils/config.js';
|
|
5
|
+
import { track } from './utils/analytics.js';
|
|
4
6
|
|
|
5
7
|
const MAX_TURNS = 5;
|
|
6
8
|
|
|
@@ -84,6 +86,11 @@ export async function runReviewHeadless(options = {}) {
|
|
|
84
86
|
onFilesReady = () => {},
|
|
85
87
|
} = options;
|
|
86
88
|
|
|
89
|
+
// If the CLI config doesn't have an apiKey but the extension passed one, persist it
|
|
90
|
+
if (apiKey && !getConfigValue('apiKey')) {
|
|
91
|
+
setConfigValue('apiKey', apiKey);
|
|
92
|
+
}
|
|
93
|
+
|
|
87
94
|
// Temporarily set env vars so fetchApi picks them up
|
|
88
95
|
const prevToken = process.env.CODEANT_API_TOKEN;
|
|
89
96
|
const prevUrl = process.env.CODEANT_API_URL;
|
|
@@ -91,6 +98,9 @@ export async function runReviewHeadless(options = {}) {
|
|
|
91
98
|
if (apiKey) process.env.CODEANT_API_TOKEN = apiKey;
|
|
92
99
|
if (baseUrl) process.env.CODEANT_API_URL = baseUrl;
|
|
93
100
|
|
|
101
|
+
const reviewStartTime = Date.now();
|
|
102
|
+
track('review_triggered', { scan_type: scanType, source: 'headless' });
|
|
103
|
+
|
|
94
104
|
try {
|
|
95
105
|
// ── Fetch diff ──────────────────────────────────────────────────────
|
|
96
106
|
onProgress('Fetching diff...');
|
|
@@ -107,6 +117,7 @@ export async function runReviewHeadless(options = {}) {
|
|
|
107
117
|
onFilesReady(meta?.reviewed_files || [], meta);
|
|
108
118
|
|
|
109
119
|
if (!requestBody?.diff_content?.length) {
|
|
120
|
+
track('review_completed', { scan_type: scanType, source: 'headless', issue_count: 0, file_count: 0, duration_ms: Date.now() - reviewStartTime, no_files: true });
|
|
110
121
|
return { issues: [], meta, error: null, noFiles: true };
|
|
111
122
|
}
|
|
112
123
|
|
|
@@ -157,9 +168,19 @@ export async function runReviewHeadless(options = {}) {
|
|
|
157
168
|
label: issue.label || 'Code Quality',
|
|
158
169
|
}));
|
|
159
170
|
|
|
171
|
+
const labelCounts = {};
|
|
172
|
+
for (const i of issues) { labelCounts[i.label] = (labelCounts[i.label] || 0) + 1; }
|
|
173
|
+
track('review_completed', {
|
|
174
|
+
scan_type: scanType, source: 'headless', issue_count: issues.length,
|
|
175
|
+
file_count: perFileRequests.length, duration_ms: Date.now() - reviewStartTime,
|
|
176
|
+
label_counts: labelCounts,
|
|
177
|
+
});
|
|
178
|
+
|
|
160
179
|
return { issues, meta, error: null, noFiles: false };
|
|
161
180
|
} catch (err) {
|
|
162
|
-
|
|
181
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
182
|
+
track('review_error', { scan_type: scanType, source: 'headless', error: errMsg });
|
|
183
|
+
return { issues: [], meta: null, error: errMsg, noFiles: false };
|
|
163
184
|
} finally {
|
|
164
185
|
// Restore env vars
|
|
165
186
|
if (prevToken !== undefined) process.env.CODEANT_API_TOKEN = prevToken;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { PostHog } from 'posthog-node';
|
|
2
|
+
import { getConfigValue } from './config.js';
|
|
3
|
+
|
|
4
|
+
const POSTHOG_API_KEY = 'phc_TpsLE5AMwHYsrSQIYgI7uqvorzlDMsin3lDvTEv2DxO';
|
|
5
|
+
const POSTHOG_HOST = 'https://r.codeant.ai';
|
|
6
|
+
|
|
7
|
+
let client = null;
|
|
8
|
+
|
|
9
|
+
function getClient() {
|
|
10
|
+
if (!client) {
|
|
11
|
+
client = new PostHog(POSTHOG_API_KEY, { host: POSTHOG_HOST, flushAt: 1, flushInterval: 0 });
|
|
12
|
+
}
|
|
13
|
+
return client;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Use the API key directly as distinct ID */
|
|
17
|
+
function getDistinctId() {
|
|
18
|
+
return process.env.CODEANT_API_TOKEN || getConfigValue('apiKey') || 'anonymous';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Track an analytics event. Fire-and-forget — never throws or blocks the CLI.
|
|
23
|
+
* @param {string} event - Event name (e.g. 'review_triggered')
|
|
24
|
+
* @param {Object} [properties={}] - Event properties
|
|
25
|
+
*/
|
|
26
|
+
export function track(event, properties = {}) {
|
|
27
|
+
try {
|
|
28
|
+
getClient().capture({
|
|
29
|
+
distinctId: getDistinctId(),
|
|
30
|
+
event,
|
|
31
|
+
properties: { source: 'cli', cli_version: process.env.npm_package_version || 'unknown', ...properties },
|
|
32
|
+
});
|
|
33
|
+
} catch (_) {
|
|
34
|
+
// Analytics should never break the CLI
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Flush pending events. Call before process exits. */
|
|
39
|
+
export async function shutdown() {
|
|
40
|
+
try {
|
|
41
|
+
if (client) await client.shutdown();
|
|
42
|
+
} catch (_) {
|
|
43
|
+
// noop
|
|
44
|
+
}
|
|
45
|
+
}
|