@hifullmoon/aicommit 2.0.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/.aicommit.config.example.json +0 -10
- package/CHANGELOG.md +24 -1
- package/README.md +11 -36
- package/README.zh-CN.md +11 -36
- package/SECURITY.md +0 -1
- package/bin/aicommit.js +0 -18
- package/docs/distribution.md +1 -1
- package/docs/privacy.md +18 -20
- package/docs/provider-compatibility.md +13 -25
- package/docs/team-policy.md +2 -2
- package/docs/troubleshooting.md +16 -19
- package/package.json +1 -1
- package/src/api.js +3 -20
- package/src/cli.js +16 -130
- package/src/completion.js +1 -16
- package/src/config-command.js +24 -6
- package/src/config-paths.js +31 -0
- package/src/config.js +22 -42
- package/src/doctor.js +1 -12
- package/src/git.js +1 -1
- package/src/main.js +2 -26
- package/src/provider-presets.js +5 -95
- package/src/providers.js +5 -6
- package/src/setup.js +18 -4
- package/src/split.js +4 -119
- package/src/utils.js +1 -1
- package/docs/examples/extension/aicommit-extension.json +0 -9
- package/docs/examples/extension/index.mjs +0 -32
- package/docs/extensions.md +0 -93
- package/docs/provider-presets.md +0 -113
- package/schemas/aicommit-extension.schema.json +0 -27
- package/src/extension-runner.mjs +0 -36
- package/src/extensions.js +0 -426
- package/src/metrics.js +0 -375
- package/src/preset-command.js +0 -91
package/src/metrics.js
DELETED
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import { appendFile, chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
4
|
-
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
|
|
7
|
-
export const DEFAULT_METRICS = Object.freeze({
|
|
8
|
-
enabled: true,
|
|
9
|
-
path: '',
|
|
10
|
-
maxEntries: 500,
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
let activeSettings = null;
|
|
14
|
-
const METRIC_RESULTS = new Set([
|
|
15
|
-
'committed',
|
|
16
|
-
'dry_run',
|
|
17
|
-
'cancelled',
|
|
18
|
-
'config',
|
|
19
|
-
'git_state',
|
|
20
|
-
'network',
|
|
21
|
-
'provider',
|
|
22
|
-
'response_format',
|
|
23
|
-
'sensitive_data',
|
|
24
|
-
'concurrent_modification',
|
|
25
|
-
'internal',
|
|
26
|
-
'unknown',
|
|
27
|
-
]);
|
|
28
|
-
|
|
29
|
-
export function metricsPath(settings = DEFAULT_METRICS, home = homedir()) {
|
|
30
|
-
return settings.path || join(home, '.aicommit', 'metrics.jsonl');
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function configureMetrics(settings) {
|
|
34
|
-
activeSettings = { ...DEFAULT_METRICS, ...settings };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function safeUsage(usage) {
|
|
38
|
-
if (!usage) return null;
|
|
39
|
-
const safe = {};
|
|
40
|
-
for (const key of ['inputTokens', 'outputTokens', 'totalTokens']) {
|
|
41
|
-
if (typeof usage[key] === 'number' && Number.isFinite(usage[key]) && usage[key] >= 0) {
|
|
42
|
-
safe[key] = usage[key];
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return Object.keys(safe).length ? safe : null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export function minimizeMetric(input = {}) {
|
|
49
|
-
const result = METRIC_RESULTS.has(input.result) ? input.result : 'unknown';
|
|
50
|
-
return {
|
|
51
|
-
durationMs:
|
|
52
|
-
typeof input.durationMs === 'number' && Number.isFinite(input.durationMs)
|
|
53
|
-
? Math.max(0, Math.round(input.durationMs))
|
|
54
|
-
: null,
|
|
55
|
-
usage: safeUsage(input.usage),
|
|
56
|
-
result,
|
|
57
|
-
edited: Boolean(input.edited),
|
|
58
|
-
rewrites:
|
|
59
|
-
Number.isInteger(input.rewrites) && input.rewrites >= 0 ? Math.min(input.rewrites, 1000) : 0,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function trimMetrics(path, maxEntries) {
|
|
64
|
-
const text = await readFile(path, 'utf8');
|
|
65
|
-
const lines = text.split('\n').filter(Boolean);
|
|
66
|
-
if (lines.length <= maxEntries) return;
|
|
67
|
-
|
|
68
|
-
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
69
|
-
try {
|
|
70
|
-
await writeFile(tempPath, lines.slice(-maxEntries).join('\n') + '\n', {
|
|
71
|
-
encoding: 'utf8',
|
|
72
|
-
mode: 0o600,
|
|
73
|
-
});
|
|
74
|
-
await rename(tempPath, path);
|
|
75
|
-
await chmod(path, 0o600).catch(() => {});
|
|
76
|
-
} catch (err) {
|
|
77
|
-
await unlink(tempPath).catch(() => {});
|
|
78
|
-
throw err;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export async function recordMetric(input, settings = activeSettings) {
|
|
83
|
-
if (!settings?.enabled) return false;
|
|
84
|
-
const path = metricsPath(settings);
|
|
85
|
-
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
86
|
-
await appendFile(path, JSON.stringify(minimizeMetric(input)) + '\n', {
|
|
87
|
-
encoding: 'utf8',
|
|
88
|
-
mode: 0o600,
|
|
89
|
-
});
|
|
90
|
-
await chmod(path, 0o600).catch(() => {});
|
|
91
|
-
await trimMetrics(path, settings.maxEntries || DEFAULT_METRICS.maxEntries);
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
async function readUserMetricsConfig(path) {
|
|
96
|
-
try {
|
|
97
|
-
const parsed = JSON.parse(await readFile(path, 'utf8'));
|
|
98
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
99
|
-
throw new Error('expected a JSON object');
|
|
100
|
-
}
|
|
101
|
-
return parsed;
|
|
102
|
-
} catch (err) {
|
|
103
|
-
if (err.code === 'ENOENT') return {};
|
|
104
|
-
throw new Error(`Failed to read user config ${path}: ${err.message}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function writeUserConfig(path, config) {
|
|
109
|
-
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
110
|
-
try {
|
|
111
|
-
await writeFile(tempPath, JSON.stringify(config, null, 2) + '\n', {
|
|
112
|
-
encoding: 'utf8',
|
|
113
|
-
mode: 0o600,
|
|
114
|
-
});
|
|
115
|
-
await rename(tempPath, path);
|
|
116
|
-
await chmod(path, 0o600).catch(() => {});
|
|
117
|
-
} catch (err) {
|
|
118
|
-
await unlink(tempPath).catch(() => {});
|
|
119
|
-
throw err;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
async function metricCount(path) {
|
|
124
|
-
try {
|
|
125
|
-
return (await readFile(path, 'utf8')).split('\n').filter(Boolean).length;
|
|
126
|
-
} catch (err) {
|
|
127
|
-
if (err.code === 'ENOENT') return 0;
|
|
128
|
-
throw err;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export async function readMetricRecords(path) {
|
|
133
|
-
let textValue;
|
|
134
|
-
try {
|
|
135
|
-
textValue = await readFile(path, 'utf8');
|
|
136
|
-
} catch (err) {
|
|
137
|
-
if (err.code === 'ENOENT') return { records: [], invalid: 0 };
|
|
138
|
-
throw err;
|
|
139
|
-
}
|
|
140
|
-
const records = [];
|
|
141
|
-
let invalid = 0;
|
|
142
|
-
for (const line of textValue.split('\n')) {
|
|
143
|
-
if (!line.trim()) continue;
|
|
144
|
-
try {
|
|
145
|
-
const parsed = JSON.parse(line);
|
|
146
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
147
|
-
invalid++;
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
records.push(minimizeMetric(parsed));
|
|
151
|
-
} catch {
|
|
152
|
-
invalid++;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
return { records, invalid };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function average(values) {
|
|
159
|
-
return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function percentile(values, ratio) {
|
|
163
|
-
if (!values.length) return null;
|
|
164
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
165
|
-
return sorted[Math.ceil((sorted.length - 1) * ratio)];
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function percent(part, whole) {
|
|
169
|
-
return whole ? (part / whole) * 100 : null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function relativeChange(previous, recent) {
|
|
173
|
-
if (previous === null || recent === null || previous === 0) return null;
|
|
174
|
-
return ((recent - previous) / previous) * 100;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function tokenTotal(record) {
|
|
178
|
-
const usage = record.usage;
|
|
179
|
-
if (!usage) return null;
|
|
180
|
-
if (typeof usage.totalTokens === 'number') return usage.totalTokens;
|
|
181
|
-
if (typeof usage.inputTokens === 'number' && typeof usage.outputTokens === 'number') {
|
|
182
|
-
return usage.inputTokens + usage.outputTokens;
|
|
183
|
-
}
|
|
184
|
-
return null;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function halves(records) {
|
|
188
|
-
if (records.length < 2) return [[], records];
|
|
189
|
-
const split = Math.floor(records.length / 2);
|
|
190
|
-
return [records.slice(0, split), records.slice(split)];
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function windowQuality(records) {
|
|
194
|
-
const changed = records.filter((record) => record.edited || record.rewrites > 0).length;
|
|
195
|
-
return { runs: records.length, changed, rate: percent(changed, records.length) };
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
export function summarizeMetrics(inputRecords) {
|
|
199
|
-
const records = (inputRecords || []).map((record) => minimizeMetric(record));
|
|
200
|
-
const successful = records.filter((record) => ['committed', 'dry_run'].includes(record.result));
|
|
201
|
-
const failed = records.filter(
|
|
202
|
-
(record) => !['committed', 'dry_run', 'cancelled'].includes(record.result),
|
|
203
|
-
);
|
|
204
|
-
const edited = records.filter((record) => record.edited);
|
|
205
|
-
const rewritten = records.filter((record) => record.rewrites > 0);
|
|
206
|
-
const acceptedFirstPass = successful.filter((record) => !record.edited && record.rewrites === 0);
|
|
207
|
-
const durations = records
|
|
208
|
-
.map((record) => record.durationMs)
|
|
209
|
-
.filter((value) => typeof value === 'number');
|
|
210
|
-
const tokens = records.map(tokenTotal).filter((value) => typeof value === 'number');
|
|
211
|
-
const [previousRecords, recentRecords] = halves(records);
|
|
212
|
-
const previousDurations = previousRecords
|
|
213
|
-
.map((record) => record.durationMs)
|
|
214
|
-
.filter((value) => typeof value === 'number');
|
|
215
|
-
const recentDurations = recentRecords
|
|
216
|
-
.map((record) => record.durationMs)
|
|
217
|
-
.filter((value) => typeof value === 'number');
|
|
218
|
-
const previousTokens = previousRecords
|
|
219
|
-
.map(tokenTotal)
|
|
220
|
-
.filter((value) => typeof value === 'number');
|
|
221
|
-
const recentTokens = recentRecords.map(tokenTotal).filter((value) => typeof value === 'number');
|
|
222
|
-
const [previousSuccess, recentSuccess] = halves(successful);
|
|
223
|
-
const previousQuality = windowQuality(previousSuccess);
|
|
224
|
-
const recentQuality = windowQuality(recentSuccess);
|
|
225
|
-
const qualityImprovement =
|
|
226
|
-
previousQuality.rate === null || recentQuality.rate === null || previousQuality.rate === 0
|
|
227
|
-
? null
|
|
228
|
-
: ((previousQuality.rate - recentQuality.rate) / previousQuality.rate) * 100;
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
runs: records.length,
|
|
232
|
-
outcomes: {
|
|
233
|
-
committed: records.filter((record) => record.result === 'committed').length,
|
|
234
|
-
dryRuns: records.filter((record) => record.result === 'dry_run').length,
|
|
235
|
-
acceptedFirstPass: acceptedFirstPass.length,
|
|
236
|
-
edited: edited.length,
|
|
237
|
-
rewritten: rewritten.length,
|
|
238
|
-
rewriteCount: records.reduce((sum, record) => sum + record.rewrites, 0),
|
|
239
|
-
failed: failed.length,
|
|
240
|
-
cancelled: records.filter((record) => record.result === 'cancelled').length,
|
|
241
|
-
},
|
|
242
|
-
rates: {
|
|
243
|
-
firstPassAcceptance: percent(acceptedFirstPass.length, successful.length),
|
|
244
|
-
editOrRewrite: percent(
|
|
245
|
-
successful.filter((record) => record.edited || record.rewrites > 0).length,
|
|
246
|
-
successful.length,
|
|
247
|
-
),
|
|
248
|
-
failure: percent(failed.length, records.length),
|
|
249
|
-
},
|
|
250
|
-
latency: {
|
|
251
|
-
count: durations.length,
|
|
252
|
-
averageMs: average(durations),
|
|
253
|
-
p50Ms: percentile(durations, 0.5),
|
|
254
|
-
p95Ms: percentile(durations, 0.95),
|
|
255
|
-
previousAverageMs: average(previousDurations),
|
|
256
|
-
recentAverageMs: average(recentDurations),
|
|
257
|
-
relativeChangePercent: relativeChange(average(previousDurations), average(recentDurations)),
|
|
258
|
-
},
|
|
259
|
-
tokens: {
|
|
260
|
-
count: tokens.length,
|
|
261
|
-
total: tokens.reduce((sum, value) => sum + value, 0),
|
|
262
|
-
average: average(tokens),
|
|
263
|
-
previousAverage: average(previousTokens),
|
|
264
|
-
recentAverage: average(recentTokens),
|
|
265
|
-
relativeChangePercent: relativeChange(average(previousTokens), average(recentTokens)),
|
|
266
|
-
},
|
|
267
|
-
qualityTrend: {
|
|
268
|
-
baselineReady: previousSuccess.length >= 5 && recentSuccess.length >= 5,
|
|
269
|
-
previous: previousQuality,
|
|
270
|
-
recent: recentQuality,
|
|
271
|
-
relativeImprovementPercent: qualityImprovement,
|
|
272
|
-
targetMet: qualityImprovement !== null && qualityImprovement >= 20,
|
|
273
|
-
},
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function renderPercent(value) {
|
|
278
|
-
return value === null ? 'n/a' : `${value.toFixed(1)}%`;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function renderDuration(value) {
|
|
282
|
-
if (value === null) return 'n/a';
|
|
283
|
-
return value < 1000 ? `${Math.round(value)}ms` : `${(value / 1000).toFixed(1)}s`;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function renderNumber(value) {
|
|
287
|
-
return value === null ? 'n/a' : Math.round(value).toLocaleString('en-US');
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
export async function runStatsCommand(action = 'show', options = {}) {
|
|
291
|
-
if (['clear', 'enable', 'disable'].includes(action)) {
|
|
292
|
-
return updateMetrics(action, options);
|
|
293
|
-
}
|
|
294
|
-
const home = options.home || homedir();
|
|
295
|
-
const userConfigPath = options.userConfigPath || join(home, '.aicommit.config.json');
|
|
296
|
-
const existing = await readUserMetricsConfig(userConfigPath);
|
|
297
|
-
const settings = { ...DEFAULT_METRICS, ...existing.metrics };
|
|
298
|
-
const path = metricsPath(settings, home);
|
|
299
|
-
const { records, invalid } = await readMetricRecords(path);
|
|
300
|
-
const stats = summarizeMetrics(records);
|
|
301
|
-
|
|
302
|
-
console.log('');
|
|
303
|
-
console.log(' ' + chalk.cyan.bold('Local quality stats'));
|
|
304
|
-
console.log(` Status: ${settings.enabled ? 'enabled' : 'disabled'}`);
|
|
305
|
-
console.log(
|
|
306
|
-
` Runs: ${stats.runs}${invalid ? ` (${invalid} invalid line(s) ignored)` : ''}`,
|
|
307
|
-
);
|
|
308
|
-
console.log(
|
|
309
|
-
` First pass: ${stats.outcomes.acceptedFirstPass} (${renderPercent(stats.rates.firstPassAcceptance)})`,
|
|
310
|
-
);
|
|
311
|
-
console.log(` Edited: ${stats.outcomes.edited}`);
|
|
312
|
-
console.log(
|
|
313
|
-
` Rewritten: ${stats.outcomes.rewritten} run(s), ${stats.outcomes.rewriteCount} rewrite(s)`,
|
|
314
|
-
);
|
|
315
|
-
console.log(` Failed: ${stats.outcomes.failed} (${renderPercent(stats.rates.failure)})`);
|
|
316
|
-
console.log(
|
|
317
|
-
` Latency: P50 ${renderDuration(stats.latency.p50Ms)}, P95 ${renderDuration(stats.latency.p95Ms)}, ` +
|
|
318
|
-
`recent ${renderPercent(stats.latency.relativeChangePercent)}`,
|
|
319
|
-
);
|
|
320
|
-
console.log(
|
|
321
|
-
` Tokens: total ${renderNumber(stats.tokens.total)}, average ${renderNumber(stats.tokens.average)}, ` +
|
|
322
|
-
`recent ${renderPercent(stats.tokens.relativeChangePercent)}`,
|
|
323
|
-
);
|
|
324
|
-
if (stats.qualityTrend.baselineReady) {
|
|
325
|
-
console.log(
|
|
326
|
-
` Edit/rewrite: previous ${renderPercent(stats.qualityTrend.previous.rate)} → ` +
|
|
327
|
-
`recent ${renderPercent(stats.qualityTrend.recent.rate)}; relative improvement ` +
|
|
328
|
-
`${renderPercent(stats.qualityTrend.relativeImprovementPercent)} ` +
|
|
329
|
-
`(20% target ${stats.qualityTrend.targetMet ? 'met' : 'not yet met'})`,
|
|
330
|
-
);
|
|
331
|
-
} else {
|
|
332
|
-
console.log(
|
|
333
|
-
' Edit/rewrite: collect at least 10 successful runs to establish two baseline windows',
|
|
334
|
-
);
|
|
335
|
-
}
|
|
336
|
-
console.log(` Path: ${path}`);
|
|
337
|
-
console.log(
|
|
338
|
-
' Privacy: local only; no messages, diffs, reasoning, file names, providers, or models',
|
|
339
|
-
);
|
|
340
|
-
console.log('');
|
|
341
|
-
return { action, path, enabled: settings.enabled, invalid, stats };
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
async function updateMetrics(action, options = {}) {
|
|
345
|
-
const home = options.home || homedir();
|
|
346
|
-
const userConfigPath = options.userConfigPath || join(home, '.aicommit.config.json');
|
|
347
|
-
const existing = await readUserMetricsConfig(userConfigPath);
|
|
348
|
-
const settings = { ...DEFAULT_METRICS, ...existing.metrics };
|
|
349
|
-
const path = metricsPath(settings, home);
|
|
350
|
-
|
|
351
|
-
if (action === 'clear') {
|
|
352
|
-
const count = await metricCount(path);
|
|
353
|
-
await unlink(path).catch((err) => {
|
|
354
|
-
if (err.code !== 'ENOENT') throw err;
|
|
355
|
-
});
|
|
356
|
-
console.log(
|
|
357
|
-
` ${chalk.green('✓')} Cleared ${count} local metric record(s): ${path} ` +
|
|
358
|
-
chalk.dim('(cannot be recovered)'),
|
|
359
|
-
);
|
|
360
|
-
return { action, count, path, enabled: settings.enabled };
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
if (action === 'enable' || action === 'disable') {
|
|
364
|
-
const enabled = action === 'enable';
|
|
365
|
-
const updated = {
|
|
366
|
-
...existing,
|
|
367
|
-
metrics: { ...existing.metrics, enabled },
|
|
368
|
-
};
|
|
369
|
-
await writeUserConfig(userConfigPath, updated);
|
|
370
|
-
console.log(` ${chalk.green('✓')} Local metrics ${enabled ? 'enabled' : 'disabled'}.`);
|
|
371
|
-
return { action, count: await metricCount(path), path, enabled };
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
throw new Error(`Unsupported stats action: ${action}`);
|
|
375
|
-
}
|
package/src/preset-command.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
|
|
3
|
-
import { ERROR_CATEGORIES, fail } from './errors.js';
|
|
4
|
-
import {
|
|
5
|
-
installProviderPresetManifest,
|
|
6
|
-
loadProviderPresetManifest,
|
|
7
|
-
providerPresetPaths,
|
|
8
|
-
rollbackProviderPresetManifest,
|
|
9
|
-
} from './provider-presets.js';
|
|
10
|
-
import { fileExists } from './utils.js';
|
|
11
|
-
|
|
12
|
-
export async function inspectProviderPresetPaths(home = homedir()) {
|
|
13
|
-
const paths = providerPresetPaths(home);
|
|
14
|
-
return {
|
|
15
|
-
bundled: { path: paths.bundled, exists: await fileExists(paths.bundled) },
|
|
16
|
-
user: { path: paths.user, exists: await fileExists(paths.user) },
|
|
17
|
-
backup: { path: paths.backup, exists: await fileExists(paths.backup) },
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function printPaths(paths) {
|
|
22
|
-
for (const [name, entry] of Object.entries(paths)) {
|
|
23
|
-
console.log(`${name.padEnd(7)} ${entry.path}${entry.exists ? '' : ' (not found)'}`);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export async function runPresetCommand(
|
|
28
|
-
action,
|
|
29
|
-
{ file = null, machineOutput = false, home = homedir() } = {},
|
|
30
|
-
) {
|
|
31
|
-
const paths = await inspectProviderPresetPaths(home);
|
|
32
|
-
if (action === 'path') {
|
|
33
|
-
if (!machineOutput) printPaths(paths);
|
|
34
|
-
return { exitReason: 'preset_path', data: { paths } };
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
if (action === 'install') {
|
|
39
|
-
const installed = await installProviderPresetManifest(file, { home });
|
|
40
|
-
if (!machineOutput) {
|
|
41
|
-
console.log(`Installed provider preset v${installed.manifest.version}: ${installed.path}`);
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
exitReason: 'preset_installed',
|
|
45
|
-
data: {
|
|
46
|
-
version: installed.manifest.version,
|
|
47
|
-
path: installed.path,
|
|
48
|
-
backupPath: installed.backupPath,
|
|
49
|
-
invalidBackupPath: installed.invalidBackupPath,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (action === 'rollback') {
|
|
54
|
-
const installed = await rollbackProviderPresetManifest({ home });
|
|
55
|
-
if (!machineOutput) {
|
|
56
|
-
console.log(`Rolled back to provider preset v${installed.manifest.version}.`);
|
|
57
|
-
}
|
|
58
|
-
return {
|
|
59
|
-
exitReason: 'preset_rolled_back',
|
|
60
|
-
data: {
|
|
61
|
-
version: installed.manifest.version,
|
|
62
|
-
path: installed.path,
|
|
63
|
-
backupPath: installed.backupPath,
|
|
64
|
-
invalidBackupPath: installed.invalidBackupPath,
|
|
65
|
-
},
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const loaded = await loadProviderPresetManifest({ path: file, home });
|
|
70
|
-
const data = {
|
|
71
|
-
valid: true,
|
|
72
|
-
source: loaded.source,
|
|
73
|
-
path: loaded.path,
|
|
74
|
-
version: loaded.manifest.version,
|
|
75
|
-
compatibility: loaded.manifest.compatibility,
|
|
76
|
-
providerCount: loaded.manifest.providers.length,
|
|
77
|
-
...(action === 'show' ? { manifest: loaded.manifest } : {}),
|
|
78
|
-
};
|
|
79
|
-
if (!machineOutput) {
|
|
80
|
-
if (action === 'show') console.log(JSON.stringify(data, null, 2));
|
|
81
|
-
else {
|
|
82
|
-
console.log(
|
|
83
|
-
`Provider preset v${data.version} is compatible (${data.providerCount} providers, ${data.source}).`,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return { exitReason: action === 'show' ? 'preset_show' : 'preset_valid', data };
|
|
88
|
-
} catch (err) {
|
|
89
|
-
throw fail(ERROR_CATEGORIES.CONFIG, err.message, { cause: err });
|
|
90
|
-
}
|
|
91
|
-
}
|