@snapcommit/cli 3.9.21 → 3.11.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/commands/autopilot.js +443 -0
- package/dist/commands/cursor-style.js +208 -334
- package/dist/commands/natural.js +1 -1
- package/dist/commands/onboard.js +1 -0
- package/dist/commands/telemetry.js +44 -0
- package/dist/index.js +38 -0
- package/dist/repl.js +91 -5
- package/dist/types/prompt.js +2 -0
- package/dist/types/workflow.js +2 -0
- package/dist/utils/memory.js +80 -0
- package/dist/utils/prompt-helpers.js +62 -0
- package/dist/utils/prompt.js +78 -0
- package/dist/utils/settings.js +58 -0
- package/dist/utils/telemetry.js +81 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,7 +22,9 @@ const natural_1 = require("./commands/natural");
|
|
|
22
22
|
const conflict_1 = require("./commands/conflict");
|
|
23
23
|
const uninstall_1 = require("./commands/uninstall");
|
|
24
24
|
const github_connect_1 = require("./commands/github-connect");
|
|
25
|
+
const telemetry_1 = require("./commands/telemetry");
|
|
25
26
|
const repl_1 = require("./repl");
|
|
27
|
+
const autopilot_1 = require("./commands/autopilot");
|
|
26
28
|
// Load environment variables from root .env
|
|
27
29
|
(0, dotenv_1.config)({ path: path_1.default.join(__dirname, '../../.env') });
|
|
28
30
|
// Read version from package.json
|
|
@@ -127,6 +129,20 @@ program
|
|
|
127
129
|
.action(async () => {
|
|
128
130
|
await (0, conflict_1.conflictCommand)();
|
|
129
131
|
});
|
|
132
|
+
// Command: snapcommit autopilot
|
|
133
|
+
program
|
|
134
|
+
.command('autopilot [workflowId]')
|
|
135
|
+
.description('Run multi-step AI-assisted workflows (merge recovery, release prep, etc.)')
|
|
136
|
+
.option('--auto', 'Run without confirmations where possible')
|
|
137
|
+
.option('--plan', 'Show the workflow plan without executing steps')
|
|
138
|
+
.action(async (workflowId, cmd) => {
|
|
139
|
+
const opts = cmd.opts();
|
|
140
|
+
await (0, autopilot_1.autopilotCommand)(workflowId, {
|
|
141
|
+
workflowId,
|
|
142
|
+
auto: Boolean(opts.auto),
|
|
143
|
+
planOnly: Boolean(opts.plan),
|
|
144
|
+
});
|
|
145
|
+
});
|
|
130
146
|
// Command: snapcommit github
|
|
131
147
|
const githubCmd = program
|
|
132
148
|
.command('github')
|
|
@@ -149,6 +165,28 @@ githubCmd
|
|
|
149
165
|
.action(async () => {
|
|
150
166
|
await (0, github_connect_1.githubDisconnectCommand)();
|
|
151
167
|
});
|
|
168
|
+
// Telemetry management
|
|
169
|
+
const telemetryCmd = program
|
|
170
|
+
.command('telemetry')
|
|
171
|
+
.description('Manage anonymous telemetry');
|
|
172
|
+
telemetryCmd
|
|
173
|
+
.command('enable')
|
|
174
|
+
.description('Enable anonymous telemetry diagnostics')
|
|
175
|
+
.action(async () => {
|
|
176
|
+
await (0, telemetry_1.telemetryEnableCommand)();
|
|
177
|
+
});
|
|
178
|
+
telemetryCmd
|
|
179
|
+
.command('disable')
|
|
180
|
+
.description('Disable anonymous telemetry diagnostics')
|
|
181
|
+
.action(async () => {
|
|
182
|
+
await (0, telemetry_1.telemetryDisableCommand)();
|
|
183
|
+
});
|
|
184
|
+
telemetryCmd
|
|
185
|
+
.command('status')
|
|
186
|
+
.description('Show telemetry status')
|
|
187
|
+
.action(async () => {
|
|
188
|
+
await (0, telemetry_1.telemetryStatusCommand)();
|
|
189
|
+
});
|
|
152
190
|
// Command: snapcommit uninstall
|
|
153
191
|
program
|
|
154
192
|
.command('uninstall')
|
package/dist/repl.js
CHANGED
|
@@ -43,6 +43,9 @@ const auth_1 = require("./lib/auth");
|
|
|
43
43
|
const natural_1 = require("./commands/natural");
|
|
44
44
|
const github_connect_1 = require("./commands/github-connect");
|
|
45
45
|
const version_1 = require("./utils/version");
|
|
46
|
+
const telemetry_1 = require("./utils/telemetry");
|
|
47
|
+
const settings_1 = require("./utils/settings");
|
|
48
|
+
const TELEMETRY_REPROMPT_INTERVAL_MS = 1000 * 60 * 60 * 24 * 14;
|
|
46
49
|
/**
|
|
47
50
|
* Start SnapCommit REPL (Read-Eval-Print-Loop)
|
|
48
51
|
* Interactive mode for natural language Git commands
|
|
@@ -101,13 +104,64 @@ async function startREPL() {
|
|
|
101
104
|
}
|
|
102
105
|
console.log(chalk_1.default.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
|
|
103
106
|
console.log(chalk_1.default.bold.yellow('🎯 Try it now! Type what you want to do:\n'));
|
|
107
|
+
const promptState = {
|
|
108
|
+
resolver: null,
|
|
109
|
+
};
|
|
110
|
+
const promptController = {
|
|
111
|
+
ask: (question, options = {}) => new Promise((resolve) => {
|
|
112
|
+
if (promptState.resolver) {
|
|
113
|
+
console.log(chalk_1.default.red('\n❌ Internal prompt error: previous question still active.\n'));
|
|
114
|
+
resolve(options.defaultValue ?? '');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const { defaultValue, suffix } = options;
|
|
118
|
+
const segments = [`\n${question}`];
|
|
119
|
+
if (suffix)
|
|
120
|
+
segments.push(` ${suffix}`);
|
|
121
|
+
if (defaultValue !== undefined) {
|
|
122
|
+
segments.push(` ${chalk_1.default.gray(`[default: ${defaultValue}]`)}`);
|
|
123
|
+
}
|
|
124
|
+
process.stdout.write(segments.join('') + '\n' + chalk_1.default.cyan('› '));
|
|
125
|
+
promptState.resolver = (value) => {
|
|
126
|
+
const trimmed = value.trim();
|
|
127
|
+
if (defaultValue !== undefined && trimmed === '') {
|
|
128
|
+
resolve(defaultValue);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
resolve(trimmed);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}),
|
|
135
|
+
confirm: async (question, options = {}) => {
|
|
136
|
+
const defaultYes = options.defaultValue ?? false;
|
|
137
|
+
const hint = defaultYes ? chalk_1.default.gray('[Y/n]') : chalk_1.default.gray('[y/N]');
|
|
138
|
+
while (true) {
|
|
139
|
+
const response = (await promptController.ask(`${question} ${hint}`)).toLowerCase();
|
|
140
|
+
if (!response) {
|
|
141
|
+
return defaultYes;
|
|
142
|
+
}
|
|
143
|
+
if (response === 'y' || response === 'yes') {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
if (response === 'n' || response === 'no') {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
console.log(chalk_1.default.yellow('Please answer with yes or no.'));
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
};
|
|
104
153
|
const rl = readline_1.default.createInterface({
|
|
105
154
|
input: process.stdin,
|
|
106
155
|
output: process.stdout,
|
|
107
156
|
prompt: chalk_1.default.cyan('snap> '),
|
|
108
157
|
});
|
|
109
|
-
rl.prompt();
|
|
110
158
|
rl.on('line', async (input) => {
|
|
159
|
+
if (promptState.resolver) {
|
|
160
|
+
const resolver = promptState.resolver;
|
|
161
|
+
promptState.resolver = null;
|
|
162
|
+
resolver(input);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
111
165
|
const line = input.trim();
|
|
112
166
|
if (!line) {
|
|
113
167
|
rl.prompt();
|
|
@@ -315,18 +369,50 @@ async function startREPL() {
|
|
|
315
369
|
}
|
|
316
370
|
// Everything else is natural language
|
|
317
371
|
try {
|
|
318
|
-
|
|
319
|
-
await (0, natural_1.naturalCommand)(line);
|
|
320
|
-
rl.resume(); // Resume REPL readline
|
|
372
|
+
await (0, natural_1.naturalCommand)(line, promptController);
|
|
321
373
|
}
|
|
322
374
|
catch (error) {
|
|
323
375
|
console.log(chalk_1.default.red(`\n❌ Error: ${error.message}\n`));
|
|
324
|
-
rl.resume(); // Make sure to resume even on error
|
|
325
376
|
}
|
|
326
377
|
rl.prompt();
|
|
327
378
|
});
|
|
379
|
+
await showTelemetryNotice(promptController);
|
|
380
|
+
rl.prompt();
|
|
328
381
|
rl.on('close', () => {
|
|
329
382
|
console.log(chalk_1.default.gray('\n👋 See you later! Happy coding!\n'));
|
|
330
383
|
process.exit(0);
|
|
331
384
|
});
|
|
332
385
|
}
|
|
386
|
+
async function showTelemetryNotice(prompt) {
|
|
387
|
+
const settings = (0, settings_1.getSettings)();
|
|
388
|
+
const telemetryOn = (0, telemetry_1.isTelemetryEnabled)();
|
|
389
|
+
const lastPrompt = settings.telemetryPromptedAt ?? 0;
|
|
390
|
+
const shouldPrompt = !telemetryOn &&
|
|
391
|
+
(lastPrompt === 0 || Date.now() - lastPrompt > TELEMETRY_REPROMPT_INTERVAL_MS);
|
|
392
|
+
console.log(chalk_1.default.gray(`📊 Telemetry: ${telemetryOn ? chalk_1.default.green('enabled') : chalk_1.default.red('disabled')} • Manage anytime with ` +
|
|
393
|
+
chalk_1.default.cyan('snap telemetry status | enable | disable')));
|
|
394
|
+
if (!shouldPrompt) {
|
|
395
|
+
if (!telemetryOn) {
|
|
396
|
+
console.log(chalk_1.default.gray(' Help improve SnapCommit by enabling anonymous diagnostics when you are ready.'));
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
console.log(chalk_1.default.gray(' Thanks for helping us prioritise new features!'));
|
|
400
|
+
}
|
|
401
|
+
console.log();
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
console.log();
|
|
405
|
+
console.log(chalk_1.default.yellow.bold('Help us keep SnapCommit fast for everyone!'));
|
|
406
|
+
console.log(chalk_1.default.gray('We collect anonymous usage diagnostics (never your code, repo names, or secrets) to guide feature development.'));
|
|
407
|
+
console.log();
|
|
408
|
+
const enable = await prompt.confirm('Enable anonymous telemetry?', { defaultValue: true });
|
|
409
|
+
(0, telemetry_1.markTelemetryPrompted)();
|
|
410
|
+
if (enable) {
|
|
411
|
+
(0, telemetry_1.setTelemetryEnabled)(true);
|
|
412
|
+
(0, telemetry_1.recordTelemetry)('telemetry_opt_in');
|
|
413
|
+
console.log(chalk_1.default.green('\n✅ Telemetry enabled. Manage anytime with `snap telemetry disable`.\n'));
|
|
414
|
+
}
|
|
415
|
+
else {
|
|
416
|
+
console.log(chalk_1.default.gray('\nNo problem. Enable later with `snap telemetry enable` if you change your mind.\n'));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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.loadMemory = loadMemory;
|
|
7
|
+
exports.saveMemory = saveMemory;
|
|
8
|
+
exports.updateMemory = updateMemory;
|
|
9
|
+
exports.rememberPreference = rememberPreference;
|
|
10
|
+
exports.recallPreference = recallPreference;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const MEMORY_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit');
|
|
15
|
+
const MEMORY_FILE = path_1.default.join(MEMORY_DIR, 'memory.json');
|
|
16
|
+
const DEFAULT_MEMORY = {
|
|
17
|
+
version: 1,
|
|
18
|
+
workflows: {},
|
|
19
|
+
preferences: {},
|
|
20
|
+
};
|
|
21
|
+
function loadMemory() {
|
|
22
|
+
try {
|
|
23
|
+
if (!fs_1.default.existsSync(MEMORY_FILE)) {
|
|
24
|
+
ensureMemoryFile();
|
|
25
|
+
return { ...DEFAULT_MEMORY };
|
|
26
|
+
}
|
|
27
|
+
const raw = fs_1.default.readFileSync(MEMORY_FILE, 'utf-8');
|
|
28
|
+
const parsed = JSON.parse(raw);
|
|
29
|
+
return {
|
|
30
|
+
...DEFAULT_MEMORY,
|
|
31
|
+
...parsed,
|
|
32
|
+
workflows: { ...DEFAULT_MEMORY.workflows, ...parsed.workflows },
|
|
33
|
+
preferences: { ...DEFAULT_MEMORY.preferences, ...parsed.preferences },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return { ...DEFAULT_MEMORY };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveMemory(memory) {
|
|
41
|
+
ensureMemoryFile();
|
|
42
|
+
fs_1.default.writeFileSync(MEMORY_FILE, JSON.stringify(memory, null, 2), 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
function updateMemory(mutator) {
|
|
45
|
+
const current = loadMemory();
|
|
46
|
+
const next = mutator({ ...current });
|
|
47
|
+
saveMemory(next);
|
|
48
|
+
return next;
|
|
49
|
+
}
|
|
50
|
+
function rememberPreference(namespace, key, value) {
|
|
51
|
+
updateMemory((memory) => {
|
|
52
|
+
const preferences = {
|
|
53
|
+
...(memory.preferences || {}),
|
|
54
|
+
[namespace]: {
|
|
55
|
+
...(typeof memory.preferences?.[namespace] === 'object'
|
|
56
|
+
? memory.preferences?.[namespace]
|
|
57
|
+
: {}),
|
|
58
|
+
[key]: value,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
return { ...memory, preferences };
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function recallPreference(namespace, key) {
|
|
65
|
+
const memory = loadMemory();
|
|
66
|
+
const namespacePrefs = memory.preferences?.[namespace];
|
|
67
|
+
if (!namespacePrefs || typeof namespacePrefs !== 'object') {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const record = namespacePrefs;
|
|
71
|
+
return record[key];
|
|
72
|
+
}
|
|
73
|
+
function ensureMemoryFile() {
|
|
74
|
+
if (!fs_1.default.existsSync(MEMORY_DIR)) {
|
|
75
|
+
fs_1.default.mkdirSync(MEMORY_DIR, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
if (!fs_1.default.existsSync(MEMORY_FILE)) {
|
|
78
|
+
fs_1.default.writeFileSync(MEMORY_FILE, JSON.stringify(DEFAULT_MEMORY, null, 2), 'utf-8');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
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.createPromptHelpers = createPromptHelpers;
|
|
7
|
+
const readline_1 = __importDefault(require("readline"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
function createPromptHelpers(prompt) {
|
|
10
|
+
return {
|
|
11
|
+
ask: (question, options) => askWithFallback(question, options, prompt),
|
|
12
|
+
confirm: (question, options) => confirmWithFallback(question, options, prompt),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
async function askWithFallback(question, options = {}, prompt) {
|
|
16
|
+
if (prompt) {
|
|
17
|
+
return prompt.ask(question, options);
|
|
18
|
+
}
|
|
19
|
+
const rl = readline_1.default.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout,
|
|
22
|
+
});
|
|
23
|
+
const { defaultValue, suffix } = options;
|
|
24
|
+
const segments = [question];
|
|
25
|
+
if (suffix)
|
|
26
|
+
segments.push(` ${suffix}`);
|
|
27
|
+
if (defaultValue !== undefined) {
|
|
28
|
+
segments.push(` ${chalk_1.default.gray(`[default: ${defaultValue}]`)}`);
|
|
29
|
+
}
|
|
30
|
+
return await new Promise((resolve) => {
|
|
31
|
+
rl.question(`${segments.join('')}` + '\n› ', (answer) => {
|
|
32
|
+
rl.close();
|
|
33
|
+
const trimmed = answer.trim();
|
|
34
|
+
if (defaultValue !== undefined && trimmed === '') {
|
|
35
|
+
resolve(defaultValue);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
resolve(trimmed);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async function confirmWithFallback(question, options = {}, prompt) {
|
|
44
|
+
if (prompt) {
|
|
45
|
+
return prompt.confirm(question, options);
|
|
46
|
+
}
|
|
47
|
+
const defaultYes = options.defaultValue ?? false;
|
|
48
|
+
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
49
|
+
while (true) {
|
|
50
|
+
const answer = (await askWithFallback(`${question} ${hint}`, {}, undefined)).toLowerCase();
|
|
51
|
+
if (!answer) {
|
|
52
|
+
return defaultYes;
|
|
53
|
+
}
|
|
54
|
+
if (answer === 'y' || answer === 'yes') {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (answer === 'n' || answer === 'no') {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
console.log(chalk_1.default.yellow('Please answer with yes or no.'));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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.promptConfirm = promptConfirm;
|
|
7
|
+
exports.promptSelect = promptSelect;
|
|
8
|
+
exports.promptInput = promptInput;
|
|
9
|
+
const readline_1 = __importDefault(require("readline"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
function createInterface() {
|
|
12
|
+
return readline_1.default.createInterface({
|
|
13
|
+
input: process.stdin,
|
|
14
|
+
output: process.stdout,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
async function promptConfirm(question, defaultValue = true) {
|
|
18
|
+
const rl = createInterface();
|
|
19
|
+
const hint = defaultValue ? '[Y/n]' : '[y/N]';
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
rl.question(`${chalk_1.default.cyan(question)} ${chalk_1.default.gray(hint)} `, (answer) => {
|
|
22
|
+
rl.close();
|
|
23
|
+
const normalized = answer.trim().toLowerCase();
|
|
24
|
+
if (!normalized) {
|
|
25
|
+
resolve(defaultValue);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (['y', 'yes'].includes(normalized)) {
|
|
29
|
+
resolve(true);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (['n', 'no'].includes(normalized)) {
|
|
33
|
+
resolve(false);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
resolve(defaultValue);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async function promptSelect(question, choices, defaultIndex = 0) {
|
|
41
|
+
const rl = createInterface();
|
|
42
|
+
const safeIndex = Math.min(Math.max(defaultIndex, 0), choices.length - 1);
|
|
43
|
+
console.log(chalk_1.default.cyan(question));
|
|
44
|
+
choices.forEach((choice, index) => {
|
|
45
|
+
const prefix = index === safeIndex ? chalk_1.default.green('➤') : chalk_1.default.gray('•');
|
|
46
|
+
const label = `${choice.label}${choice.hint ? chalk_1.default.gray(` — ${choice.hint}`) : ''}`;
|
|
47
|
+
console.log(`${prefix} [${index + 1}] ${label}`);
|
|
48
|
+
});
|
|
49
|
+
console.log();
|
|
50
|
+
return new Promise((resolve) => {
|
|
51
|
+
rl.question(chalk_1.default.cyan(`Select an option [1-${choices.length}] (default: ${safeIndex + 1}): `), (answer) => {
|
|
52
|
+
rl.close();
|
|
53
|
+
const trimmed = answer.trim();
|
|
54
|
+
if (!trimmed) {
|
|
55
|
+
resolve(choices[safeIndex].value);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const idx = parseInt(trimmed, 10);
|
|
59
|
+
if (!Number.isNaN(idx) && idx >= 1 && idx <= choices.length) {
|
|
60
|
+
resolve(choices[idx - 1].value);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
resolve(choices[safeIndex].value);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function promptInput(question, defaultValue = '') {
|
|
69
|
+
const rl = createInterface();
|
|
70
|
+
const suffix = defaultValue ? chalk_1.default.gray(` (default: ${defaultValue})`) : '';
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
rl.question(chalk_1.default.cyan(`${question}${suffix}: `), (answer) => {
|
|
73
|
+
rl.close();
|
|
74
|
+
const trimmed = answer.trim();
|
|
75
|
+
resolve(trimmed || defaultValue);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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.getSettings = getSettings;
|
|
7
|
+
exports.saveSettings = saveSettings;
|
|
8
|
+
exports.updateSettings = updateSettings;
|
|
9
|
+
exports.getOrCreateDeviceId = getOrCreateDeviceId;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
14
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit');
|
|
15
|
+
const SETTINGS_FILE = path_1.default.join(CONFIG_DIR, 'settings.json');
|
|
16
|
+
const defaultSettings = {
|
|
17
|
+
telemetryEnabled: false,
|
|
18
|
+
telemetryPromptedAt: 0,
|
|
19
|
+
};
|
|
20
|
+
function ensureConfigDir() {
|
|
21
|
+
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
22
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function getSettings() {
|
|
26
|
+
try {
|
|
27
|
+
ensureConfigDir();
|
|
28
|
+
if (!fs_1.default.existsSync(SETTINGS_FILE)) {
|
|
29
|
+
return { ...defaultSettings };
|
|
30
|
+
}
|
|
31
|
+
const raw = fs_1.default.readFileSync(SETTINGS_FILE, 'utf-8');
|
|
32
|
+
const parsed = JSON.parse(raw);
|
|
33
|
+
return { ...defaultSettings, ...parsed };
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return { ...defaultSettings };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function saveSettings(settings) {
|
|
40
|
+
ensureConfigDir();
|
|
41
|
+
const merged = { ...defaultSettings, ...settings };
|
|
42
|
+
fs_1.default.writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2));
|
|
43
|
+
return merged;
|
|
44
|
+
}
|
|
45
|
+
function updateSettings(partial) {
|
|
46
|
+
const current = getSettings();
|
|
47
|
+
const merged = { ...current, ...partial };
|
|
48
|
+
return saveSettings(merged);
|
|
49
|
+
}
|
|
50
|
+
function getOrCreateDeviceId() {
|
|
51
|
+
const settings = getSettings();
|
|
52
|
+
if (settings.deviceId) {
|
|
53
|
+
return settings.deviceId;
|
|
54
|
+
}
|
|
55
|
+
const deviceId = crypto_1.default.randomUUID();
|
|
56
|
+
updateSettings({ deviceId });
|
|
57
|
+
return deviceId;
|
|
58
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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.isTelemetryEnabled = isTelemetryEnabled;
|
|
7
|
+
exports.setTelemetryEnabled = setTelemetryEnabled;
|
|
8
|
+
exports.markTelemetryPrompted = markTelemetryPrompted;
|
|
9
|
+
exports.recordTelemetry = recordTelemetry;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const settings_1 = require("./settings");
|
|
14
|
+
const LOG_DIR = path_1.default.join(os_1.default.homedir(), '.snapcommit', 'logs');
|
|
15
|
+
const LOG_FILE = path_1.default.join(LOG_DIR, 'telemetry.log');
|
|
16
|
+
const TELEMETRY_ENDPOINT = process.env.SNAPCOMMIT_TELEMETRY_URL || 'https://www.snapcommit.dev/api/telemetry';
|
|
17
|
+
let packageVersion = '0.0.0';
|
|
18
|
+
try {
|
|
19
|
+
const pkgPath = path_1.default.join(__dirname, '../../package.json');
|
|
20
|
+
const raw = fs_1.default.readFileSync(pkgPath, 'utf-8');
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
packageVersion = parsed.version || packageVersion;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// ignore
|
|
26
|
+
}
|
|
27
|
+
function ensureLogDir() {
|
|
28
|
+
if (!fs_1.default.existsSync(LOG_DIR)) {
|
|
29
|
+
fs_1.default.mkdirSync(LOG_DIR, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function isTelemetryEnabled() {
|
|
33
|
+
return Boolean((0, settings_1.getSettings)().telemetryEnabled);
|
|
34
|
+
}
|
|
35
|
+
function setTelemetryEnabled(enabled) {
|
|
36
|
+
(0, settings_1.updateSettings)({ telemetryEnabled: enabled });
|
|
37
|
+
}
|
|
38
|
+
function markTelemetryPrompted() {
|
|
39
|
+
(0, settings_1.updateSettings)({ telemetryPromptedAt: Date.now() });
|
|
40
|
+
}
|
|
41
|
+
function appendLocalLog(entry) {
|
|
42
|
+
try {
|
|
43
|
+
ensureLogDir();
|
|
44
|
+
fs_1.default.appendFileSync(LOG_FILE, JSON.stringify(entry) + '\n');
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// ignore logging failures
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function recordTelemetry(event, payload = {}) {
|
|
51
|
+
if (!isTelemetryEnabled()) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
const deviceId = (0, settings_1.getOrCreateDeviceId)();
|
|
56
|
+
const body = {
|
|
57
|
+
event,
|
|
58
|
+
payload,
|
|
59
|
+
timestamp,
|
|
60
|
+
version: packageVersion,
|
|
61
|
+
deviceId,
|
|
62
|
+
};
|
|
63
|
+
// Fire and forget
|
|
64
|
+
void (async () => {
|
|
65
|
+
try {
|
|
66
|
+
const response = await fetch(TELEMETRY_ENDPOINT, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
});
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
appendLocalLog({ ...body, status: response.status, error: await response.text().catch(() => undefined) });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
appendLocalLog({ ...body, error: error?.message || 'network-error' });
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
}
|