@code-insights/cli 1.2.0 → 2.1.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/CHANGELOG.md +26 -0
- package/README.md +101 -9
- package/dist/commands/config.d.ts +3 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +130 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +7 -1
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +52 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install-hook.d.ts.map +1 -1
- package/dist/commands/install-hook.js +16 -3
- package/dist/commands/install-hook.js.map +1 -1
- package/dist/commands/open.d.ts +9 -0
- package/dist/commands/open.d.ts.map +1 -0
- package/dist/commands/open.js +59 -0
- package/dist/commands/open.js.map +1 -0
- package/dist/commands/reset.d.ts.map +1 -1
- package/dist/commands/reset.js +14 -1
- package/dist/commands/reset.js.map +1 -1
- package/dist/commands/stats/actions/cost.d.ts +3 -0
- package/dist/commands/stats/actions/cost.d.ts.map +1 -0
- package/dist/commands/stats/actions/cost.js +139 -0
- package/dist/commands/stats/actions/cost.js.map +1 -0
- package/dist/commands/stats/actions/error-handler.d.ts +6 -0
- package/dist/commands/stats/actions/error-handler.d.ts.map +1 -0
- package/dist/commands/stats/actions/error-handler.js +42 -0
- package/dist/commands/stats/actions/error-handler.js.map +1 -0
- package/dist/commands/stats/actions/models.d.ts +3 -0
- package/dist/commands/stats/actions/models.d.ts.map +1 -0
- package/dist/commands/stats/actions/models.js +101 -0
- package/dist/commands/stats/actions/models.js.map +1 -0
- package/dist/commands/stats/actions/overview.d.ts +3 -0
- package/dist/commands/stats/actions/overview.d.ts.map +1 -0
- package/dist/commands/stats/actions/overview.js +147 -0
- package/dist/commands/stats/actions/overview.js.map +1 -0
- package/dist/commands/stats/actions/projects.d.ts +3 -0
- package/dist/commands/stats/actions/projects.d.ts.map +1 -0
- package/dist/commands/stats/actions/projects.js +90 -0
- package/dist/commands/stats/actions/projects.js.map +1 -0
- package/dist/commands/stats/actions/today.d.ts +3 -0
- package/dist/commands/stats/actions/today.d.ts.map +1 -0
- package/dist/commands/stats/actions/today.js +118 -0
- package/dist/commands/stats/actions/today.js.map +1 -0
- package/dist/commands/stats/data/aggregation.d.ts +60 -0
- package/dist/commands/stats/data/aggregation.d.ts.map +1 -0
- package/dist/commands/stats/data/aggregation.js +614 -0
- package/dist/commands/stats/data/aggregation.js.map +1 -0
- package/dist/commands/stats/data/cache.d.ts +29 -0
- package/dist/commands/stats/data/cache.d.ts.map +1 -0
- package/dist/commands/stats/data/cache.js +197 -0
- package/dist/commands/stats/data/cache.js.map +1 -0
- package/dist/commands/stats/data/firestore.d.ts +13 -0
- package/dist/commands/stats/data/firestore.d.ts.map +1 -0
- package/dist/commands/stats/data/firestore.js +170 -0
- package/dist/commands/stats/data/firestore.js.map +1 -0
- package/dist/commands/stats/data/fuzzy-match.d.ts +5 -0
- package/dist/commands/stats/data/fuzzy-match.d.ts.map +1 -0
- package/dist/commands/stats/data/fuzzy-match.js +36 -0
- package/dist/commands/stats/data/fuzzy-match.js.map +1 -0
- package/dist/commands/stats/data/local.d.ts +12 -0
- package/dist/commands/stats/data/local.d.ts.map +1 -0
- package/dist/commands/stats/data/local.js +81 -0
- package/dist/commands/stats/data/local.js.map +1 -0
- package/dist/commands/stats/data/source.d.ts +13 -0
- package/dist/commands/stats/data/source.d.ts.map +1 -0
- package/dist/commands/stats/data/source.js +41 -0
- package/dist/commands/stats/data/source.js.map +1 -0
- package/dist/commands/stats/data/types.d.ts +229 -0
- package/dist/commands/stats/data/types.d.ts.map +1 -0
- package/dist/commands/stats/data/types.js +50 -0
- package/dist/commands/stats/data/types.js.map +1 -0
- package/dist/commands/stats/index.d.ts +3 -0
- package/dist/commands/stats/index.d.ts.map +1 -0
- package/dist/commands/stats/index.js +41 -0
- package/dist/commands/stats/index.js.map +1 -0
- package/dist/commands/stats/render/charts.d.ts +9 -0
- package/dist/commands/stats/render/charts.d.ts.map +1 -0
- package/dist/commands/stats/render/charts.js +45 -0
- package/dist/commands/stats/render/charts.js.map +1 -0
- package/dist/commands/stats/render/colors.d.ts +21 -0
- package/dist/commands/stats/render/colors.d.ts.map +1 -0
- package/dist/commands/stats/render/colors.js +47 -0
- package/dist/commands/stats/render/colors.js.map +1 -0
- package/dist/commands/stats/render/format.d.ts +10 -0
- package/dist/commands/stats/render/format.d.ts.map +1 -0
- package/dist/commands/stats/render/format.js +57 -0
- package/dist/commands/stats/render/format.js.map +1 -0
- package/dist/commands/stats/render/layout.d.ts +10 -0
- package/dist/commands/stats/render/layout.d.ts.map +1 -0
- package/dist/commands/stats/render/layout.js +48 -0
- package/dist/commands/stats/render/layout.js.map +1 -0
- package/dist/commands/stats/shared.d.ts +6 -0
- package/dist/commands/stats/shared.d.ts.map +1 -0
- package/dist/commands/stats/shared.js +26 -0
- package/dist/commands/stats/shared.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +50 -37
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/sync.d.ts +16 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +196 -79
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/telemetry.d.ts +3 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +106 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/firebase/client.d.ts +5 -0
- package/dist/firebase/client.d.ts.map +1 -1
- package/dist/firebase/client.js +5 -2
- package/dist/firebase/client.js.map +1 -1
- package/dist/index.js +21 -4
- package/dist/index.js.map +1 -1
- package/dist/parser/jsonl.js +3 -2
- package/dist/parser/jsonl.js.map +1 -1
- package/dist/providers/claude-code.d.ts.map +1 -1
- package/dist/providers/claude-code.js +5 -1
- package/dist/providers/claude-code.js.map +1 -1
- package/dist/providers/codex.d.ts +14 -0
- package/dist/providers/codex.d.ts.map +1 -0
- package/dist/providers/codex.js +364 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/copilot-cli.d.ts +14 -0
- package/dist/providers/copilot-cli.d.ts.map +1 -0
- package/dist/providers/copilot-cli.js +363 -0
- package/dist/providers/copilot-cli.js.map +1 -0
- package/dist/providers/cursor.d.ts +27 -0
- package/dist/providers/cursor.d.ts.map +1 -0
- package/dist/providers/cursor.js +446 -0
- package/dist/providers/cursor.js.map +1 -0
- package/dist/providers/registry.d.ts +0 -4
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +9 -6
- package/dist/providers/registry.js.map +1 -1
- package/dist/types.d.ts +5 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config.d.ts +10 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +21 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +16 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/telemetry.d.ts +52 -0
- package/dist/utils/telemetry.d.ts.map +1 -0
- package/dist/utils/telemetry.js +304 -0
- package/dist/utils/telemetry.js.map +1 -0
- package/dist/utils/tips.d.ts +11 -0
- package/dist/utils/tips.d.ts.map +1 -0
- package/dist/utils/tips.js +106 -0
- package/dist/utils/tips.js.map +1 -0
- package/dist/utils/welcome.d.ts +11 -0
- package/dist/utils/welcome.d.ts.map +1 -0
- package/dist/utils/welcome.js +72 -0
- package/dist/utils/welcome.js.map +1 -0
- package/package.json +9 -2
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract real filesystem path and optional session fragment from a virtual path.
|
|
3
|
+
* Virtual paths use '#' delimiter: "/path/to/state.vscdb#composerId"
|
|
4
|
+
* Regular paths pass through unchanged.
|
|
5
|
+
*/
|
|
6
|
+
export declare function splitVirtualPath(filePath: string): {
|
|
7
|
+
realPath: string;
|
|
8
|
+
sessionFragment: string | null;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CASvG"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract real filesystem path and optional session fragment from a virtual path.
|
|
3
|
+
* Virtual paths use '#' delimiter: "/path/to/state.vscdb#composerId"
|
|
4
|
+
* Regular paths pass through unchanged.
|
|
5
|
+
*/
|
|
6
|
+
export function splitVirtualPath(filePath) {
|
|
7
|
+
const hashIndex = filePath.lastIndexOf('#');
|
|
8
|
+
if (hashIndex > 0) {
|
|
9
|
+
return {
|
|
10
|
+
realPath: filePath.slice(0, hashIndex),
|
|
11
|
+
sessionFragment: filePath.slice(hashIndex + 1),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
return { realPath: filePath, sessionFragment: null };
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAgB;IAC/C,MAAM,SAAS,GAAG,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAC5C,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,OAAO;YACL,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC;YACtC,eAAe,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC;SAC/C,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface TelemetryEvent {
|
|
2
|
+
machineId: string;
|
|
3
|
+
command: string;
|
|
4
|
+
subcommand?: string;
|
|
5
|
+
success: boolean;
|
|
6
|
+
cliVersion: string;
|
|
7
|
+
nodeVersion: string;
|
|
8
|
+
os: string;
|
|
9
|
+
arch: string;
|
|
10
|
+
providers: string[];
|
|
11
|
+
sessionCountBucket: string;
|
|
12
|
+
dataSource: string;
|
|
13
|
+
hasHook: boolean;
|
|
14
|
+
timestamp: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Check if telemetry is enabled.
|
|
18
|
+
*
|
|
19
|
+
* Check order (first match wins):
|
|
20
|
+
* 1. CODE_INSIGHTS_TELEMETRY_DISABLED=1 env var — respects CI/automation opt-out
|
|
21
|
+
* 2. DO_NOT_TRACK=1 env var — respects the community standard
|
|
22
|
+
* 3. config.telemetry field — user's explicit preference
|
|
23
|
+
* 4. Default: true (opt-out model)
|
|
24
|
+
*/
|
|
25
|
+
export declare function isTelemetryEnabled(): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Show the one-time telemetry disclosure notice if it hasn't been shown yet.
|
|
28
|
+
*
|
|
29
|
+
* Uses a touch file at ~/.code-insights/.telemetry-notice-shown to track state.
|
|
30
|
+
* Only displays if telemetry is currently enabled — no point disclosing disabled telemetry.
|
|
31
|
+
*
|
|
32
|
+
* Returns true if the notice was shown, false if it was already shown or telemetry is off.
|
|
33
|
+
*/
|
|
34
|
+
export declare function showTelemetryNoticeIfNeeded(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Fire-and-forget telemetry event. Does NOT block the calling command.
|
|
37
|
+
*
|
|
38
|
+
* Design principles:
|
|
39
|
+
* - Never awaited: the caller doesn't wait for this
|
|
40
|
+
* - 2s AbortController timeout: we don't hang if the endpoint is slow
|
|
41
|
+
* - Swallows ALL errors: network failures, JSON errors, anything — telemetry
|
|
42
|
+
* must never cause a CLI command to fail
|
|
43
|
+
* - No retries: if it fails, it fails. Reliability of individual events
|
|
44
|
+
* matters less than not disrupting the user's workflow.
|
|
45
|
+
*/
|
|
46
|
+
export declare function trackEvent(command: string, success: boolean, subcommand?: string): void;
|
|
47
|
+
/**
|
|
48
|
+
* Build the event payload that would be sent for the given command.
|
|
49
|
+
* Exported for use by `telemetry status` to show a preview without sending.
|
|
50
|
+
*/
|
|
51
|
+
export declare function buildEventPreview(command: string): TelemetryEvent;
|
|
52
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/utils/telemetry.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAY5C;AAED;;;;;;;GAOG;AACH,wBAAgB,2BAA2B,IAAI,OAAO,CA4BrD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAevF;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,CAEjE"}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import * as crypto from 'crypto';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { loadConfig, getConfigDir, getClaudeDir } from './config.js';
|
|
7
|
+
const TELEMETRY_ENDPOINT = 'https://xrbkoqjfolxiyfxubiom.supabase.co/functions/v1/cli-telemetry';
|
|
8
|
+
// Touch file path that tracks whether the one-time disclosure has been shown
|
|
9
|
+
const NOTICE_FILE = path.join(getConfigDir(), '.telemetry-notice-shown');
|
|
10
|
+
/**
|
|
11
|
+
* Check if telemetry is enabled.
|
|
12
|
+
*
|
|
13
|
+
* Check order (first match wins):
|
|
14
|
+
* 1. CODE_INSIGHTS_TELEMETRY_DISABLED=1 env var — respects CI/automation opt-out
|
|
15
|
+
* 2. DO_NOT_TRACK=1 env var — respects the community standard
|
|
16
|
+
* 3. config.telemetry field — user's explicit preference
|
|
17
|
+
* 4. Default: true (opt-out model)
|
|
18
|
+
*/
|
|
19
|
+
export function isTelemetryEnabled() {
|
|
20
|
+
if (process.env.CODE_INSIGHTS_TELEMETRY_DISABLED === '1')
|
|
21
|
+
return false;
|
|
22
|
+
if (process.env.DO_NOT_TRACK === '1')
|
|
23
|
+
return false;
|
|
24
|
+
const config = loadConfig();
|
|
25
|
+
// If explicitly set in config, respect it
|
|
26
|
+
if (config !== null && typeof config.telemetry === 'boolean') {
|
|
27
|
+
return config.telemetry;
|
|
28
|
+
}
|
|
29
|
+
// Default: enabled (opt-out model)
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Show the one-time telemetry disclosure notice if it hasn't been shown yet.
|
|
34
|
+
*
|
|
35
|
+
* Uses a touch file at ~/.code-insights/.telemetry-notice-shown to track state.
|
|
36
|
+
* Only displays if telemetry is currently enabled — no point disclosing disabled telemetry.
|
|
37
|
+
*
|
|
38
|
+
* Returns true if the notice was shown, false if it was already shown or telemetry is off.
|
|
39
|
+
*/
|
|
40
|
+
export function showTelemetryNoticeIfNeeded() {
|
|
41
|
+
if (!isTelemetryEnabled())
|
|
42
|
+
return false;
|
|
43
|
+
if (fs.existsSync(NOTICE_FILE))
|
|
44
|
+
return false;
|
|
45
|
+
// Show the disclosure banner
|
|
46
|
+
console.log('');
|
|
47
|
+
console.log(' Code Insights collects anonymous usage data to improve the CLI.');
|
|
48
|
+
console.log(' Includes: command name, OS, CLI version, AI tool types.');
|
|
49
|
+
console.log(' Never includes: file paths, project names, session content, or personal data.');
|
|
50
|
+
console.log('');
|
|
51
|
+
console.log(' Disable: code-insights telemetry disable');
|
|
52
|
+
console.log(' Details: code-insights telemetry status');
|
|
53
|
+
console.log('');
|
|
54
|
+
// Mark notice as shown — best-effort write, ignore failures
|
|
55
|
+
try {
|
|
56
|
+
// Ensure config dir exists before writing touch file
|
|
57
|
+
const configDir = getConfigDir();
|
|
58
|
+
if (!fs.existsSync(configDir)) {
|
|
59
|
+
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
60
|
+
}
|
|
61
|
+
fs.writeFileSync(NOTICE_FILE, '', { encoding: 'utf-8', mode: 0o600 });
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
// Non-fatal — if we can't write the touch file, we'll show the notice again
|
|
65
|
+
// next time. That's acceptable; we don't want to break the CLI over this.
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Fire-and-forget telemetry event. Does NOT block the calling command.
|
|
71
|
+
*
|
|
72
|
+
* Design principles:
|
|
73
|
+
* - Never awaited: the caller doesn't wait for this
|
|
74
|
+
* - 2s AbortController timeout: we don't hang if the endpoint is slow
|
|
75
|
+
* - Swallows ALL errors: network failures, JSON errors, anything — telemetry
|
|
76
|
+
* must never cause a CLI command to fail
|
|
77
|
+
* - No retries: if it fails, it fails. Reliability of individual events
|
|
78
|
+
* matters less than not disrupting the user's workflow.
|
|
79
|
+
*/
|
|
80
|
+
export function trackEvent(command, success, subcommand) {
|
|
81
|
+
if (!isTelemetryEnabled())
|
|
82
|
+
return;
|
|
83
|
+
// Build the event synchronously — filesystem reads happen here
|
|
84
|
+
let event;
|
|
85
|
+
try {
|
|
86
|
+
event = buildEvent(command, success, subcommand);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// If building the event fails (e.g., filesystem read error), silently skip
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Fire-and-forget: intentionally not awaited
|
|
93
|
+
// The void cast suppresses the "floating promise" lint warning
|
|
94
|
+
void sendEvent(event);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build the event payload that would be sent for the given command.
|
|
98
|
+
* Exported for use by `telemetry status` to show a preview without sending.
|
|
99
|
+
*/
|
|
100
|
+
export function buildEventPreview(command) {
|
|
101
|
+
return buildEvent(command, true);
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Internal helpers
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
/**
|
|
107
|
+
* Build a TelemetryEvent with all fields populated.
|
|
108
|
+
* Separated from sendEvent so buildEventPreview can reuse it without sending.
|
|
109
|
+
*/
|
|
110
|
+
function buildEvent(command, success, subcommand) {
|
|
111
|
+
return {
|
|
112
|
+
machineId: getMachineId(),
|
|
113
|
+
command,
|
|
114
|
+
subcommand,
|
|
115
|
+
success,
|
|
116
|
+
cliVersion: getCliVersion(),
|
|
117
|
+
nodeVersion: process.version.replace('v', ''),
|
|
118
|
+
os: process.platform,
|
|
119
|
+
arch: process.arch,
|
|
120
|
+
providers: detectProviders(),
|
|
121
|
+
sessionCountBucket: getSessionCountBucket(),
|
|
122
|
+
dataSource: getDataSource(),
|
|
123
|
+
hasHook: detectHook(),
|
|
124
|
+
// Day precision only — avoids time-of-day behavioral fingerprinting
|
|
125
|
+
timestamp: new Date().toISOString().slice(0, 10),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Anonymous machine ID, rotated monthly.
|
|
130
|
+
*
|
|
131
|
+
* Format: SHA-256(hostname:username:code-insights-YYYY-MM).slice(0, 16)
|
|
132
|
+
*
|
|
133
|
+
* Monthly rotation ensures:
|
|
134
|
+
* - Long-term tracking is not possible across months
|
|
135
|
+
* - Events within a month can be correlated for "unique users" metrics
|
|
136
|
+
* - No PII: hostname and username are never sent, only their hash
|
|
137
|
+
*/
|
|
138
|
+
function getMachineId() {
|
|
139
|
+
const now = new Date();
|
|
140
|
+
// YYYY-MM format for monthly rotation
|
|
141
|
+
const monthSalt = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
|
|
142
|
+
let username;
|
|
143
|
+
try {
|
|
144
|
+
username = os.userInfo().username;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// os.userInfo() throws in Docker/CI when UID has no /etc/passwd entry
|
|
148
|
+
username = `uid-${process.getuid?.() ?? 'unknown'}`;
|
|
149
|
+
}
|
|
150
|
+
const input = [os.hostname(), username, `code-insights-${monthSalt}`].join(':');
|
|
151
|
+
return crypto.createHash('sha256').update(input).digest('hex').slice(0, 16);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Read CLI version from package.json.
|
|
155
|
+
* Uses createRequire for JSON imports since this is ESM and JSON imports
|
|
156
|
+
* have inconsistent support across Node versions and bundlers.
|
|
157
|
+
*/
|
|
158
|
+
function getCliVersion() {
|
|
159
|
+
try {
|
|
160
|
+
const require = createRequire(import.meta.url);
|
|
161
|
+
// package.json is two levels up from src/utils/ -> src/ -> cli/
|
|
162
|
+
const pkg = require('../../package.json');
|
|
163
|
+
return pkg.version;
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return 'unknown';
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Detect which AI coding tool data directories exist on this machine.
|
|
171
|
+
* We check for the existence of known directories — we never read their contents.
|
|
172
|
+
*
|
|
173
|
+
* Returns only the tools that are actually present. This tells us which tools
|
|
174
|
+
* users are running alongside code-insights, helping us prioritize provider support.
|
|
175
|
+
*/
|
|
176
|
+
function detectProviders() {
|
|
177
|
+
const home = os.homedir();
|
|
178
|
+
const detected = [];
|
|
179
|
+
// Claude Code: ~/.claude/projects/
|
|
180
|
+
if (fs.existsSync(path.join(home, '.claude', 'projects'))) {
|
|
181
|
+
detected.push('claude-code');
|
|
182
|
+
}
|
|
183
|
+
// Cursor: workspace storage directory (cross-platform path)
|
|
184
|
+
const cursorStoragePaths = [
|
|
185
|
+
// macOS
|
|
186
|
+
path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'workspaceStorage'),
|
|
187
|
+
// Linux
|
|
188
|
+
path.join(home, '.config', 'Cursor', 'User', 'workspaceStorage'),
|
|
189
|
+
// Windows
|
|
190
|
+
path.join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'workspaceStorage'),
|
|
191
|
+
];
|
|
192
|
+
if (cursorStoragePaths.some((p) => fs.existsSync(p))) {
|
|
193
|
+
detected.push('cursor');
|
|
194
|
+
}
|
|
195
|
+
// Codex CLI: ~/.codex/sessions
|
|
196
|
+
if (fs.existsSync(path.join(home, '.codex', 'sessions'))) {
|
|
197
|
+
detected.push('codex-cli');
|
|
198
|
+
}
|
|
199
|
+
// GitHub Copilot CLI: ~/.copilot/session-state
|
|
200
|
+
if (fs.existsSync(path.join(home, '.copilot', 'session-state'))) {
|
|
201
|
+
detected.push('copilot-cli');
|
|
202
|
+
}
|
|
203
|
+
return detected;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Count .jsonl files under ~/.claude/projects/ and bucket the count.
|
|
207
|
+
*
|
|
208
|
+
* Buckets are intentionally coarse — we want to understand "heavy vs light"
|
|
209
|
+
* usage without counting exact files, which could vary wildly and feels more
|
|
210
|
+
* private than a range.
|
|
211
|
+
*/
|
|
212
|
+
function getSessionCountBucket() {
|
|
213
|
+
try {
|
|
214
|
+
const claudeDir = getClaudeDir();
|
|
215
|
+
if (!fs.existsSync(claudeDir))
|
|
216
|
+
return '0';
|
|
217
|
+
// Count all .jsonl files recursively under the Claude projects directory
|
|
218
|
+
let count = 0;
|
|
219
|
+
const walk = (dir) => {
|
|
220
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
221
|
+
for (const entry of entries) {
|
|
222
|
+
if (entry.isDirectory()) {
|
|
223
|
+
walk(path.join(dir, entry.name));
|
|
224
|
+
}
|
|
225
|
+
else if (entry.name.endsWith('.jsonl')) {
|
|
226
|
+
count++;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
walk(claudeDir);
|
|
231
|
+
// Coarse buckets for privacy — exact session counts are never sent
|
|
232
|
+
if (count === 0)
|
|
233
|
+
return '0';
|
|
234
|
+
if (count <= 10)
|
|
235
|
+
return '1-10';
|
|
236
|
+
if (count <= 50)
|
|
237
|
+
return '11-50';
|
|
238
|
+
if (count <= 200)
|
|
239
|
+
return '51-200';
|
|
240
|
+
return '200+';
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return 'unknown';
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Determine the configured data source preference.
|
|
248
|
+
* Returns 'none' if no config exists at all.
|
|
249
|
+
*/
|
|
250
|
+
function getDataSource() {
|
|
251
|
+
const config = loadConfig();
|
|
252
|
+
if (!config)
|
|
253
|
+
return 'none';
|
|
254
|
+
if (config.dataSource)
|
|
255
|
+
return config.dataSource;
|
|
256
|
+
// Infer from credentials: if Firebase is configured, they're likely using it
|
|
257
|
+
if (config.firebase?.projectId)
|
|
258
|
+
return 'firebase';
|
|
259
|
+
return 'local';
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Check if code-insights is registered as a Claude Code hook.
|
|
263
|
+
*
|
|
264
|
+
* Reads ~/.claude/settings.json and looks for 'code-insights' anywhere in the
|
|
265
|
+
* file content. A hook registration means the user has automated sync on session end.
|
|
266
|
+
*/
|
|
267
|
+
function detectHook() {
|
|
268
|
+
try {
|
|
269
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
270
|
+
if (!fs.existsSync(settingsPath))
|
|
271
|
+
return false;
|
|
272
|
+
const content = fs.readFileSync(settingsPath, 'utf-8');
|
|
273
|
+
return content.includes('code-insights');
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Internal: Send the event to the telemetry endpoint.
|
|
281
|
+
* AbortController ensures we don't hang longer than 2 seconds.
|
|
282
|
+
* All errors are swallowed — telemetry failures must never propagate.
|
|
283
|
+
*/
|
|
284
|
+
async function sendEvent(event) {
|
|
285
|
+
const controller = new AbortController();
|
|
286
|
+
// 2s timeout — enough for a healthy network, short enough to not delay anything
|
|
287
|
+
const timer = setTimeout(() => controller.abort(), 2000);
|
|
288
|
+
try {
|
|
289
|
+
await fetch(TELEMETRY_ENDPOINT, {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
headers: { 'Content-Type': 'application/json' },
|
|
292
|
+
body: JSON.stringify(event),
|
|
293
|
+
signal: controller.signal,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
// Swallow everything: network errors, AbortError, JSON serialization errors.
|
|
298
|
+
// Telemetry failures are silent — the CLI command already completed.
|
|
299
|
+
}
|
|
300
|
+
finally {
|
|
301
|
+
clearTimeout(timer);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../src/utils/telemetry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show a rotating contextual tip after a CLI command.
|
|
3
|
+
*
|
|
4
|
+
* Tips are suppressed once a command has accumulated MAX_TIPS_PER_COMMAND
|
|
5
|
+
* displays, so they phase out naturally after the user's first few runs.
|
|
6
|
+
* All state is stored in ~/.code-insights/.tips-state.json.
|
|
7
|
+
*
|
|
8
|
+
* Returns the tip string if one was printed, or null if suppressed.
|
|
9
|
+
*/
|
|
10
|
+
export declare function showTip(command: string): string | null;
|
|
11
|
+
//# sourceMappingURL=tips.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tips.d.ts","sourceRoot":"","sources":["../../src/utils/tips.ts"],"names":[],"mappings":"AA8EA;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA+BtD"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { ensureConfigDir, getConfigDir } from './config.js';
|
|
5
|
+
// Tips stop showing after this many displays per command.
|
|
6
|
+
// Five is enough to surface the tip on early uses without becoming annoying.
|
|
7
|
+
const MAX_TIPS_PER_COMMAND = 5;
|
|
8
|
+
const TIPS_STATE_FILE = '.tips-state.json';
|
|
9
|
+
/**
|
|
10
|
+
* Per-command tip arrays. Tips cycle with modulo so they don't repeat
|
|
11
|
+
* in strict sequence once the list wraps — they're educational hints,
|
|
12
|
+
* not a tutorial that must be seen in order.
|
|
13
|
+
*/
|
|
14
|
+
const TIPS = {
|
|
15
|
+
stats: [
|
|
16
|
+
'Try `code-insights stats cost` for a cost and token breakdown by session',
|
|
17
|
+
'Try `code-insights stats today` to see only today\'s activity',
|
|
18
|
+
'Try `code-insights stats models` to compare usage across AI models',
|
|
19
|
+
'Try `code-insights stats projects` to see which projects you\'ve worked on most',
|
|
20
|
+
'Run `code-insights init` to sync sessions to Firebase and unlock the web dashboard',
|
|
21
|
+
],
|
|
22
|
+
'stats cost': [
|
|
23
|
+
'Use `--period 30d` to see cost over the last 30 days (default is 7d)',
|
|
24
|
+
'Try `code-insights stats models` to break down cost by model',
|
|
25
|
+
'Try `code-insights stats` for a full activity overview',
|
|
26
|
+
],
|
|
27
|
+
'stats today': [
|
|
28
|
+
'Try `code-insights stats` for a full activity overview across all time',
|
|
29
|
+
'Try `code-insights stats cost` to see what today\'s sessions cost',
|
|
30
|
+
],
|
|
31
|
+
'stats projects': [
|
|
32
|
+
'Use `--project <name>` to filter sessions to a specific project',
|
|
33
|
+
'Try `code-insights stats cost` to see how spend is distributed across projects',
|
|
34
|
+
],
|
|
35
|
+
'stats models': [
|
|
36
|
+
'Try `code-insights stats cost` to see per-session cost alongside model usage',
|
|
37
|
+
'Try `code-insights stats` for the full overview including all activity',
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Load tips state from ~/.code-insights/.tips-state.json.
|
|
42
|
+
* Returns an empty state on any I/O or parse error — tips are non-critical.
|
|
43
|
+
*/
|
|
44
|
+
function loadTipsState() {
|
|
45
|
+
try {
|
|
46
|
+
const file = path.join(getConfigDir(), TIPS_STATE_FILE);
|
|
47
|
+
if (!fs.existsSync(file)) {
|
|
48
|
+
return { shown: {} };
|
|
49
|
+
}
|
|
50
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
51
|
+
return JSON.parse(content);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { shown: {} };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Persist tips state. Silently swallows errors — a failed write just
|
|
59
|
+
* means the same tip might show again next run, which is acceptable.
|
|
60
|
+
*/
|
|
61
|
+
function saveTipsState(state) {
|
|
62
|
+
try {
|
|
63
|
+
ensureConfigDir();
|
|
64
|
+
const file = path.join(getConfigDir(), TIPS_STATE_FILE);
|
|
65
|
+
fs.writeFileSync(file, JSON.stringify(state, null, 2), { mode: 0o600 });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Non-critical — swallow silently
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Show a rotating contextual tip after a CLI command.
|
|
73
|
+
*
|
|
74
|
+
* Tips are suppressed once a command has accumulated MAX_TIPS_PER_COMMAND
|
|
75
|
+
* displays, so they phase out naturally after the user's first few runs.
|
|
76
|
+
* All state is stored in ~/.code-insights/.tips-state.json.
|
|
77
|
+
*
|
|
78
|
+
* Returns the tip string if one was printed, or null if suppressed.
|
|
79
|
+
*/
|
|
80
|
+
export function showTip(command) {
|
|
81
|
+
try {
|
|
82
|
+
const tips = TIPS[command];
|
|
83
|
+
// No tips defined for this command — nothing to show
|
|
84
|
+
if (!tips || tips.length === 0) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const state = loadTipsState();
|
|
88
|
+
const count = state.shown[command] ?? 0;
|
|
89
|
+
// User has seen enough tips for this command — stop showing them
|
|
90
|
+
if (count >= MAX_TIPS_PER_COMMAND) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// Cycle through tips so we don't always repeat the first one
|
|
94
|
+
const tip = tips[count % tips.length];
|
|
95
|
+
const formatted = chalk.gray(`\n Tip: ${tip}`);
|
|
96
|
+
console.log(formatted);
|
|
97
|
+
state.shown[command] = count + 1;
|
|
98
|
+
saveTipsState(state);
|
|
99
|
+
return formatted;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Tips are non-critical — swallow all errors silently
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=tips.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tips.js","sourceRoot":"","sources":["../../src/utils/tips.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Show a one-time welcome banner for first-time users.
|
|
3
|
+
*
|
|
4
|
+
* Only fires when no ~/.code-insights/.welcome-shown marker exists.
|
|
5
|
+
* Intentionally non-critical — all file I/O is wrapped so errors
|
|
6
|
+
* are swallowed silently rather than interrupting the user's command.
|
|
7
|
+
*
|
|
8
|
+
* Returns true if the banner was printed, false if already shown.
|
|
9
|
+
*/
|
|
10
|
+
export declare function showWelcomeIfFirstRun(): Promise<boolean>;
|
|
11
|
+
//# sourceMappingURL=welcome.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"welcome.d.ts","sourceRoot":"","sources":["../../src/utils/welcome.ts"],"names":[],"mappings":"AAQA;;;;;;;;GAQG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAmC9D"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { ensureConfigDir, getConfigDir } from './config.js';
|
|
5
|
+
import { getAllProviders } from '../providers/registry.js';
|
|
6
|
+
const WELCOME_MARKER = '.welcome-shown';
|
|
7
|
+
/**
|
|
8
|
+
* Show a one-time welcome banner for first-time users.
|
|
9
|
+
*
|
|
10
|
+
* Only fires when no ~/.code-insights/.welcome-shown marker exists.
|
|
11
|
+
* Intentionally non-critical — all file I/O is wrapped so errors
|
|
12
|
+
* are swallowed silently rather than interrupting the user's command.
|
|
13
|
+
*
|
|
14
|
+
* Returns true if the banner was printed, false if already shown.
|
|
15
|
+
*/
|
|
16
|
+
export async function showWelcomeIfFirstRun() {
|
|
17
|
+
try {
|
|
18
|
+
const markerPath = path.join(getConfigDir(), WELCOME_MARKER);
|
|
19
|
+
// Already greeted this user — bail out fast
|
|
20
|
+
if (fs.existsSync(markerPath)) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const sessionCount = await countAllSessions();
|
|
24
|
+
console.log('');
|
|
25
|
+
console.log(chalk.bold.cyan(' Welcome to Code Insights!'));
|
|
26
|
+
console.log('');
|
|
27
|
+
if (sessionCount > 0) {
|
|
28
|
+
console.log(chalk.dim(' Found ') +
|
|
29
|
+
chalk.white.bold(sessionCount) +
|
|
30
|
+
chalk.dim(` session${sessionCount === 1 ? '' : 's'} across your dev tools`));
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
console.log(chalk.dim(' No sessions found yet across your dev tools'));
|
|
34
|
+
}
|
|
35
|
+
console.log('');
|
|
36
|
+
// Touch the marker so we never show this again
|
|
37
|
+
touchWelcomeMarker(markerPath);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Welcome is non-critical — swallow all errors silently
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Count total sessions across all registered providers.
|
|
47
|
+
* Uses discover() which is fast (file/DB scan, no parsing).
|
|
48
|
+
*/
|
|
49
|
+
async function countAllSessions() {
|
|
50
|
+
const providers = getAllProviders();
|
|
51
|
+
let total = 0;
|
|
52
|
+
await Promise.allSettled(providers.map(async (provider) => {
|
|
53
|
+
try {
|
|
54
|
+
const paths = await provider.discover();
|
|
55
|
+
total += paths.length;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Provider unavailable or errored — skip it, keep counting
|
|
59
|
+
}
|
|
60
|
+
}));
|
|
61
|
+
return total;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create the welcome-shown marker file.
|
|
65
|
+
* Ensures the config directory exists first (handles brand-new installs
|
|
66
|
+
* where no config has been written yet).
|
|
67
|
+
*/
|
|
68
|
+
function touchWelcomeMarker(markerPath) {
|
|
69
|
+
ensureConfigDir();
|
|
70
|
+
fs.writeFileSync(markerPath, '', { mode: 0o600 });
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=welcome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"welcome.js","sourceRoot":"","sources":["../../src/utils/welcome.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,IAAI,CAAC;QACH,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,cAAc,CAAC,CAAC;QAE7D,4CAA4C;QAC5C,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACf,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,gBAAgB,EAAE,CAAC;QAE9C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,IAAI,YAAY,GAAG,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,GAAG,CACT,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC;gBACrB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC;gBAC9B,KAAK,CAAC,GAAG,CAAC,WAAW,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,wBAAwB,CAAC,CAC5E,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,+CAA+C,CAAC,CAAC,CAAC;QAC1E,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,+CAA+C;QAC/C,kBAAkB,CAAC,UAAU,CAAC,CAAC;QAE/B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,wDAAwD;QACxD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,gBAAgB;IAC7B,MAAM,SAAS,GAAG,eAAe,EAAE,CAAC;IACpC,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,MAAM,OAAO,CAAC,UAAU,CACtB,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,CAAC;YACxC,KAAK,IAAI,KAAK,CAAC,MAAM,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,2DAA2D;QAC7D,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,UAAkB;IAC5C,eAAe,EAAE,CAAC;IAClB,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACpD,CAAC"}
|