@crystalplatforms/adm 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +154 -161
- package/bin/adm +18 -21
- package/package.json +9 -22
- package/scripts/completion/adm.bash +12 -55
- package/scripts/completion/adm.zsh +21 -58
- package/scripts/install.js +8 -8
- package/src/commands/config.js +66 -0
- package/src/commands/dotfiles.js +49 -32
- package/src/commands/feedback.js +12 -0
- package/src/commands/help.js +33 -0
- package/src/commands/uninstall.js +73 -54
- package/src/commands/version.js +7 -0
- package/src/commands.js +26 -0
- package/src/config/index.js +32 -32
- package/src/data/extensions.json +358 -358
- package/src/engines.js +38 -0
- package/src/install/executor.js +263 -0
- package/src/install/hints.js +94 -0
- package/src/install/npm-prefix.js +110 -0
- package/src/install/plan.js +75 -0
- package/src/install/report.js +46 -0
- package/src/install/run.js +88 -0
- package/src/install/shell-paths.js +129 -0
- package/src/legacy-cleanup.js +96 -0
- package/src/pkg/builders.js +220 -0
- package/src/pkg/managers.js +59 -0
- package/src/pkg/package-names.js +229 -0
- package/src/pkg/script-urls.js +100 -0
- package/src/platform/index.js +129 -0
- package/src/platform/os-release.js +27 -0
- package/src/platform/shell.js +111 -0
- package/src/reports.js +37 -0
- package/src/router.js +157 -0
- package/src/setup/catalog.js +13 -13
- package/src/setup/git-step.js +128 -0
- package/src/setup/loader.js +108 -0
- package/src/setup/prompts.js +259 -0
- package/src/setup/registry.js +48 -0
- package/src/setup/spinner.js +67 -0
- package/src/setup/wizard.js +319 -195
- package/src/upgrade/index.js +499 -0
- package/src/utils/closest-match.js +36 -0
- package/src/utils/dotfiles-sync.js +89 -89
- package/src/utils/file-conflict-handler.js +30 -30
- package/bin/adm-tui +0 -13
- package/src/ai/knowledge.js +0 -61
- package/src/commands/connect.js +0 -76
- package/src/commands/issue.js +0 -80
- package/src/commands/pr.js +0 -55
- package/src/commands/theme.js +0 -103
- package/src/commands/upgrade.js +0 -114
- package/src/installer/git-config.js +0 -34
- package/src/installer/node-installer.js +0 -50
- package/src/installer/package-manager-installer.js +0 -31
- package/src/installer/ssh-setup.js +0 -31
- package/src/installer/system-packages.js +0 -49
- package/src/installers/index.js +0 -66
- package/src/integrations/ai-backend.js +0 -60
- package/src/integrations/ai-providers/registry.js +0 -261
- package/src/integrations/github.js +0 -107
- package/src/integrations/gitlab.js +0 -121
- package/src/plugins/context.js +0 -64
- package/src/plugins/loader.js +0 -50
- package/src/plugins/runner.js +0 -25
- package/src/setup/installer.js +0 -192
- package/src/shell/detector.js +0 -10
- package/src/tui/app-state.js +0 -374
- package/src/tui/app.js +0 -442
- package/src/tui/autocomplete.js +0 -57
- package/src/tui/commands/registry.js +0 -823
- package/src/tui/components/SetupScreen.js +0 -292
- package/src/ui/animations.js +0 -24
- package/src/ui/spinner.js +0 -34
- package/src/ui/theme/cyberpunk.js +0 -14
- package/src/ui/theme/dark.js +0 -14
- package/src/ui/theme/forest.js +0 -14
- package/src/ui/theme/light.js +0 -14
- package/src/ui/theme/monokai.js +0 -14
- package/src/ui/theme/nord.js +0 -14
- package/src/ui/theme.js +0 -49
- package/src/utils/command-history.js +0 -43
- package/src/utils/error-handler.js +0 -69
- package/src/utils/fuzzy-search.js +0 -20
- package/src/utils/github-error-handler.js +0 -26
- package/src/utils/keychain.js +0 -97
- package/src/utils/open-url.js +0 -15
- package/src/utils/terminal-detection.js +0 -26
- package/src/utils/token-encryption.js +0 -96
|
@@ -1,292 +0,0 @@
|
|
|
1
|
-
const React = require('react');
|
|
2
|
-
const { loadCatalog, getToolsByCategories } = require('../../setup/catalog');
|
|
3
|
-
const { installTools, installToolAsync, getInstallCommand, isToolInstalled } = require('../../setup/installer');
|
|
4
|
-
|
|
5
|
-
const PAGE_SIZE = 10;
|
|
6
|
-
|
|
7
|
-
function createSetupState(opts = {}) {
|
|
8
|
-
const categories = loadCatalog();
|
|
9
|
-
const onExit = opts.onExit || (() => {});
|
|
10
|
-
const dryRun = opts.dryRun || false;
|
|
11
|
-
const execSync = opts.execSync;
|
|
12
|
-
|
|
13
|
-
const state = {
|
|
14
|
-
categories,
|
|
15
|
-
step: 'categories',
|
|
16
|
-
selectedCategories: [],
|
|
17
|
-
selectedTools: [],
|
|
18
|
-
summary: null,
|
|
19
|
-
cursor: 0,
|
|
20
|
-
page: 0,
|
|
21
|
-
installProgress: null,
|
|
22
|
-
|
|
23
|
-
toggleCategory(id) {
|
|
24
|
-
const idx = state.selectedCategories.indexOf(id);
|
|
25
|
-
if (idx === -1) state.selectedCategories.push(id);
|
|
26
|
-
else state.selectedCategories.splice(idx, 1);
|
|
27
|
-
},
|
|
28
|
-
|
|
29
|
-
toggleTool(id) {
|
|
30
|
-
const idx = state.selectedTools.indexOf(id);
|
|
31
|
-
if (idx === -1) state.selectedTools.push(id);
|
|
32
|
-
else state.selectedTools.splice(idx, 1);
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
getToolsForSelectedCategories() {
|
|
36
|
-
return getToolsByCategories(state.selectedCategories);
|
|
37
|
-
},
|
|
38
|
-
|
|
39
|
-
goToTools() {
|
|
40
|
-
state.step = 'tools';
|
|
41
|
-
state.cursor = 0;
|
|
42
|
-
state.page = 0;
|
|
43
|
-
},
|
|
44
|
-
|
|
45
|
-
goToInstall() {
|
|
46
|
-
state.step = 'install';
|
|
47
|
-
state.cursor = 0;
|
|
48
|
-
state.page = 0;
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
goToSummary(summary, results) {
|
|
52
|
-
state.summary = summary;
|
|
53
|
-
state.installResults = results || [];
|
|
54
|
-
state.step = 'summary';
|
|
55
|
-
state.page = 0;
|
|
56
|
-
},
|
|
57
|
-
|
|
58
|
-
getVisibleItems() {
|
|
59
|
-
if (state.step === 'categories') return state.categories;
|
|
60
|
-
if (state.step === 'tools') return state.getToolsForSelectedCategories();
|
|
61
|
-
if (state.step === 'summary') return state.installResults || [];
|
|
62
|
-
return [];
|
|
63
|
-
},
|
|
64
|
-
|
|
65
|
-
handleArrow(direction) {
|
|
66
|
-
const totalPages = state.getTotalPages();
|
|
67
|
-
if (direction === 'right' && state.page < totalPages - 1) {
|
|
68
|
-
state.page++;
|
|
69
|
-
} else if (direction === 'left' && state.page > 0) {
|
|
70
|
-
state.page--;
|
|
71
|
-
}
|
|
72
|
-
},
|
|
73
|
-
|
|
74
|
-
getTotalPages() {
|
|
75
|
-
return Math.max(1, Math.ceil(state.getVisibleItems().length / PAGE_SIZE));
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
cancel() {
|
|
79
|
-
onExit();
|
|
80
|
-
},
|
|
81
|
-
|
|
82
|
-
async runInstall(platform, onProgress) {
|
|
83
|
-
if (dryRun) {
|
|
84
|
-
const allTools = state.getToolsForSelectedCategories();
|
|
85
|
-
const selected = allTools.filter(t => state.selectedTools.includes(t.id));
|
|
86
|
-
const plan = selected.map(t => ({
|
|
87
|
-
id: t.id,
|
|
88
|
-
name: t.name,
|
|
89
|
-
command: getInstallCommand(t, platform),
|
|
90
|
-
}));
|
|
91
|
-
return { dryRun: true, plan };
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const allTools = state.getToolsForSelectedCategories();
|
|
95
|
-
const selected = allTools.filter(t => state.selectedTools.includes(t.id));
|
|
96
|
-
const results = [];
|
|
97
|
-
|
|
98
|
-
for (const tool of selected) {
|
|
99
|
-
state.installProgress = { current: tool.name, done: results.length, total: selected.length };
|
|
100
|
-
if (onProgress) onProgress();
|
|
101
|
-
|
|
102
|
-
const result = await installToolAsync(tool, platform);
|
|
103
|
-
results.push(result);
|
|
104
|
-
if (onProgress) onProgress();
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
state.installProgress = null;
|
|
108
|
-
|
|
109
|
-
const summary = {
|
|
110
|
-
installed: results.filter(r => r.status === 'installed').length,
|
|
111
|
-
skipped: results.filter(r => r.status === 'skipped').length,
|
|
112
|
-
failed: results.filter(r => r.status === 'failed').length,
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
return { results, summary };
|
|
116
|
-
},
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
return state;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function createSetupScreen(ink) {
|
|
123
|
-
const { Box, Text, useInput, useApp } = ink;
|
|
124
|
-
|
|
125
|
-
return function SetupScreen({ onExit, dryRun, platform }) {
|
|
126
|
-
const stateRef = React.useRef(createSetupState({ onExit, dryRun }));
|
|
127
|
-
const [renderKey, setRenderKey] = React.useState(0);
|
|
128
|
-
const s = stateRef.current;
|
|
129
|
-
|
|
130
|
-
const triggerRender = () => setRenderKey(k => k + 1);
|
|
131
|
-
|
|
132
|
-
const allItems = s.getVisibleItems();
|
|
133
|
-
|
|
134
|
-
const totalPages = s.getTotalPages();
|
|
135
|
-
const pageStart = s.page * PAGE_SIZE;
|
|
136
|
-
const pageItems = allItems.slice(pageStart, pageStart + PAGE_SIZE);
|
|
137
|
-
|
|
138
|
-
// Clamp cursor to current page
|
|
139
|
-
if (s.cursor < pageStart || s.cursor >= pageStart + pageItems.length) {
|
|
140
|
-
s.cursor = pageStart;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
useInput((ch, key) => {
|
|
144
|
-
if (key.escape) {
|
|
145
|
-
s.cancel();
|
|
146
|
-
return;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Navigate up/down within current page
|
|
150
|
-
if (key.upArrow) {
|
|
151
|
-
s.cursor = Math.max(pageStart, s.cursor - 1);
|
|
152
|
-
triggerRender();
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
if (key.downArrow) {
|
|
156
|
-
s.cursor = Math.min(pageStart + pageItems.length - 1, s.cursor + 1);
|
|
157
|
-
triggerRender();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Page left/right
|
|
162
|
-
if (key.leftArrow) {
|
|
163
|
-
s.handleArrow('left');
|
|
164
|
-
s.cursor = s.page * PAGE_SIZE;
|
|
165
|
-
triggerRender();
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
if (key.rightArrow) {
|
|
169
|
-
s.handleArrow('right');
|
|
170
|
-
s.cursor = s.page * PAGE_SIZE;
|
|
171
|
-
triggerRender();
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Space — toggle selection
|
|
176
|
-
if (ch === ' ') {
|
|
177
|
-
const item = allItems[s.cursor];
|
|
178
|
-
if (!item) return;
|
|
179
|
-
if (s.step === 'categories') {
|
|
180
|
-
s.toggleCategory(item.id);
|
|
181
|
-
} else if (s.step === 'tools') {
|
|
182
|
-
s.toggleTool(item.id);
|
|
183
|
-
}
|
|
184
|
-
triggerRender();
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Enter — confirm step
|
|
189
|
-
if (key.return) {
|
|
190
|
-
if (s.step === 'categories' && s.selectedCategories.length > 0) {
|
|
191
|
-
s.goToTools();
|
|
192
|
-
triggerRender();
|
|
193
|
-
} else if (s.step === 'tools' && s.selectedTools.length > 0) {
|
|
194
|
-
s.goToInstall();
|
|
195
|
-
triggerRender();
|
|
196
|
-
s.runInstall(platform || 'darwin', triggerRender).then(result => {
|
|
197
|
-
if (result.summary) {
|
|
198
|
-
s.goToSummary(result.summary, result.results || []);
|
|
199
|
-
}
|
|
200
|
-
triggerRender();
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
const lines = [];
|
|
207
|
-
const selCount = s.step === 'categories' ? s.selectedCategories.length : s.selectedTools.length;
|
|
208
|
-
const pagerText = totalPages > 1 ? ` [${s.page + 1}/${totalPages}] ←→ = change page` : '';
|
|
209
|
-
|
|
210
|
-
if (s.step === 'categories') {
|
|
211
|
-
lines.push({ text: `Select categories (${selCount} selected) Space=toggle Enter=confirm Esc=cancel`, highlight: false });
|
|
212
|
-
lines.push({ text: pagerText, highlight: false });
|
|
213
|
-
|
|
214
|
-
for (let i = 0; i < pageItems.length; i++) {
|
|
215
|
-
const cat = pageItems[i];
|
|
216
|
-
const globalIdx = pageStart + i;
|
|
217
|
-
const marker = s.selectedCategories.includes(cat.id) ? '[x]' : '[ ]';
|
|
218
|
-
const active = globalIdx === s.cursor ? ' > ' : ' ';
|
|
219
|
-
lines.push({ text: `${active}${marker} ${cat.icon} ${cat.name}`, highlight: globalIdx === s.cursor });
|
|
220
|
-
}
|
|
221
|
-
} else if (s.step === 'tools') {
|
|
222
|
-
lines.push({ text: `Select tools (${selCount} selected) Space=toggle Enter=confirm Esc=cancel`, highlight: false });
|
|
223
|
-
lines.push({ text: pagerText, highlight: false });
|
|
224
|
-
|
|
225
|
-
for (let i = 0; i < pageItems.length; i++) {
|
|
226
|
-
const tool = pageItems[i];
|
|
227
|
-
const globalIdx = pageStart + i;
|
|
228
|
-
const marker = s.selectedTools.includes(tool.id) ? '[x]' : '[ ]';
|
|
229
|
-
const active = globalIdx === s.cursor ? ' > ' : ' ';
|
|
230
|
-
lines.push({ text: `${active}${marker} ${tool.name} (${tool.installMethod})`, highlight: globalIdx === s.cursor });
|
|
231
|
-
}
|
|
232
|
-
} else if (s.step === 'install') {
|
|
233
|
-
const p = s.installProgress;
|
|
234
|
-
if (p) {
|
|
235
|
-
lines.push({ text: `Installing (${p.done + 1}/${p.total}): ${p.current}...`, highlight: false });
|
|
236
|
-
} else {
|
|
237
|
-
lines.push({ text: 'Installing selected tools...', highlight: false });
|
|
238
|
-
}
|
|
239
|
-
} else if (s.step === 'summary') {
|
|
240
|
-
const sum = s.summary || {};
|
|
241
|
-
const results = s.installResults || [];
|
|
242
|
-
|
|
243
|
-
lines.push({ text: ' Setup complete!', highlight: false });
|
|
244
|
-
lines.push({ text: ` Installed: ${sum.installed || 0} Skipped: ${sum.skipped || 0} Failed: ${sum.failed || 0}`, highlight: false });
|
|
245
|
-
lines.push({ text: '', highlight: false });
|
|
246
|
-
|
|
247
|
-
// Show per-tool results (paginated)
|
|
248
|
-
const resPageStart = s.page * PAGE_SIZE;
|
|
249
|
-
const resPage = results.slice(resPageStart, resPageStart + PAGE_SIZE);
|
|
250
|
-
const resTotalPages = Math.max(1, Math.ceil(results.length / PAGE_SIZE));
|
|
251
|
-
|
|
252
|
-
for (const r of resPage) {
|
|
253
|
-
const icon = r.status === 'installed' ? '+' : r.status === 'skipped' ? '-' : 'x';
|
|
254
|
-
const color = r.status === 'installed' ? 'green' : r.status === 'skipped' ? 'yellow' : 'red';
|
|
255
|
-
const detail = r.status === 'failed' ? ` — ${r.error || 'unknown error'}` : '';
|
|
256
|
-
lines.push({ text: ` [${icon}] ${r.name} ${r.status}${detail}`, color, highlight: false });
|
|
257
|
-
if (r.hint) {
|
|
258
|
-
lines.push({ text: ` ${r.hint}`, color: 'gray', highlight: false });
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (resTotalPages > 1) {
|
|
263
|
-
lines.push({ text: ` [${s.page + 1}/${resTotalPages}] ←→ = more results`, highlight: false });
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
lines.push({ text: '\n Press Esc to return to ADM.', highlight: false });
|
|
267
|
-
|
|
268
|
-
if (sum.installed > 0) {
|
|
269
|
-
lines.push({ text: ' Tip: restart terminal (or run: source ~/.zshrc) for new tools', color: 'yellow', highlight: false });
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (dryRun) {
|
|
273
|
-
lines.push({ text: ' (dry-run mode — no tools were actually installed)', highlight: false });
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return React.createElement(
|
|
278
|
-
Box,
|
|
279
|
-
{ flexDirection: 'column', flexGrow: 1, paddingX: 1 },
|
|
280
|
-
...lines.map((line, i) =>
|
|
281
|
-
React.createElement(Text, {
|
|
282
|
-
key: i,
|
|
283
|
-
wrap: 'wrap',
|
|
284
|
-
color: line.color || (line.highlight ? 'cyan' : undefined),
|
|
285
|
-
bold: line.highlight,
|
|
286
|
-
}, line.text)
|
|
287
|
-
),
|
|
288
|
-
);
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
module.exports = { createSetupScreen, createSetupState };
|
package/src/ui/animations.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
function createAnimation(renderFn, options = {}) {
|
|
2
|
-
const interval = options.interval || 5000;
|
|
3
|
-
let timer = null;
|
|
4
|
-
|
|
5
|
-
return {
|
|
6
|
-
start() {
|
|
7
|
-
if (timer) return this;
|
|
8
|
-
timer = setInterval(renderFn, interval);
|
|
9
|
-
return this;
|
|
10
|
-
},
|
|
11
|
-
stop() {
|
|
12
|
-
if (timer) {
|
|
13
|
-
clearInterval(timer);
|
|
14
|
-
timer = null;
|
|
15
|
-
}
|
|
16
|
-
return this;
|
|
17
|
-
},
|
|
18
|
-
isRunning() {
|
|
19
|
-
return timer !== null;
|
|
20
|
-
},
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
module.exports = { createAnimation };
|
package/src/ui/spinner.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
const ora = require('ora');
|
|
2
|
-
const { resolveTheme } = require('./theme');
|
|
3
|
-
|
|
4
|
-
const NAMED_COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'gray'];
|
|
5
|
-
|
|
6
|
-
function toOraColor(color) {
|
|
7
|
-
if (NAMED_COLORS.includes(color)) return color;
|
|
8
|
-
return 'cyan';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function createSpinner(text, options = {}) {
|
|
12
|
-
const theme = resolveTheme({});
|
|
13
|
-
const rawColor = options.noColor ? 'white' : (options.color || theme.colors.primary);
|
|
14
|
-
const spinnerColor = toOraColor(rawColor);
|
|
15
|
-
const spinnerOpts = {
|
|
16
|
-
text,
|
|
17
|
-
color: spinnerColor,
|
|
18
|
-
isEnabled: options.isEnabled !== undefined ? options.isEnabled : true,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const spinner = ora(spinnerOpts);
|
|
22
|
-
|
|
23
|
-
return {
|
|
24
|
-
get text() { return spinner.text; },
|
|
25
|
-
set text(val) { spinner.text = val; },
|
|
26
|
-
start() { spinner.start(); return spinner; },
|
|
27
|
-
stop() { spinner.stop(); return spinner; },
|
|
28
|
-
succeed(msg) { spinner.succeed(msg || text); return spinner; },
|
|
29
|
-
fail(msg) { spinner.fail(msg || text); return spinner; },
|
|
30
|
-
_ora: spinner,
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
module.exports = { createSpinner };
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
name: 'cyberpunk',
|
|
3
|
-
colors: {
|
|
4
|
-
primary: '#f706cf',
|
|
5
|
-
secondary: '#ff2a6d',
|
|
6
|
-
accent: '#05d9e8',
|
|
7
|
-
text: '#d1f7ff',
|
|
8
|
-
bg: '#01012b',
|
|
9
|
-
muted: '#7b2d8e',
|
|
10
|
-
error: '#ff2a6d',
|
|
11
|
-
success: '#01ff89',
|
|
12
|
-
warning: '#ff6c11',
|
|
13
|
-
},
|
|
14
|
-
};
|
package/src/ui/theme/dark.js
DELETED
package/src/ui/theme/forest.js
DELETED
package/src/ui/theme/light.js
DELETED
package/src/ui/theme/monokai.js
DELETED
package/src/ui/theme/nord.js
DELETED
package/src/ui/theme.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
const darkTheme = require('./theme/dark');
|
|
2
|
-
const lightTheme = require('./theme/light');
|
|
3
|
-
const cyberpunkTheme = require('./theme/cyberpunk');
|
|
4
|
-
const nordTheme = require('./theme/nord');
|
|
5
|
-
const forestTheme = require('./theme/forest');
|
|
6
|
-
const monokaiTheme = require('./theme/monokai');
|
|
7
|
-
const { isDarkMode } = require('../utils/terminal-detection');
|
|
8
|
-
|
|
9
|
-
const presets = {
|
|
10
|
-
dark: darkTheme,
|
|
11
|
-
light: lightTheme,
|
|
12
|
-
cyberpunk: cyberpunkTheme,
|
|
13
|
-
nord: nordTheme,
|
|
14
|
-
forest: forestTheme,
|
|
15
|
-
monokai: monokaiTheme,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
function getTheme(name) {
|
|
19
|
-
const theme = presets[name];
|
|
20
|
-
if (!theme) throw new Error(`Unknown theme: "${name}". Available: ${Object.keys(presets).join(', ')}`);
|
|
21
|
-
return theme;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function listThemes() {
|
|
25
|
-
return Object.values(presets);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function resolveTheme(config = {}) {
|
|
29
|
-
const { theme } = config;
|
|
30
|
-
|
|
31
|
-
if (!theme || theme === 'auto') {
|
|
32
|
-
return isDarkMode() ? darkTheme : lightTheme;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (typeof theme === 'string') {
|
|
36
|
-
return getTheme(theme);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (typeof theme === 'object') {
|
|
40
|
-
return {
|
|
41
|
-
name: 'custom',
|
|
42
|
-
colors: { ...darkTheme.colors, ...theme },
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return darkTheme;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
module.exports = { resolveTheme, getTheme, listThemes };
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
const fs = require('fs').promises;
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { configDir } = require('../config/index');
|
|
4
|
-
|
|
5
|
-
const MAX_ENTRIES = 1000;
|
|
6
|
-
|
|
7
|
-
function historyFile() {
|
|
8
|
-
return path.join(configDir(), 'history');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
async function loadHistory() {
|
|
12
|
-
try {
|
|
13
|
-
const data = await fs.readFile(historyFile(), 'utf8');
|
|
14
|
-
return data.split('\n').filter(Boolean);
|
|
15
|
-
} catch (err) {
|
|
16
|
-
if (err.code === 'ENOENT') return [];
|
|
17
|
-
throw err;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function saveCommand(command) {
|
|
22
|
-
const trimmed = command.trim();
|
|
23
|
-
if (!trimmed) return;
|
|
24
|
-
|
|
25
|
-
const dir = configDir();
|
|
26
|
-
await fs.mkdir(dir, { recursive: true });
|
|
27
|
-
|
|
28
|
-
let history = await loadHistory();
|
|
29
|
-
history.push(trimmed);
|
|
30
|
-
if (history.length > MAX_ENTRIES) {
|
|
31
|
-
history = history.slice(history.length - MAX_ENTRIES);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await fs.writeFile(historyFile(), history.join('\n'), 'utf8');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async function searchHistory(query) {
|
|
38
|
-
const history = await loadHistory();
|
|
39
|
-
const lower = query.toLowerCase();
|
|
40
|
-
return history.filter(entry => entry.toLowerCase().includes(lower));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
module.exports = { saveCommand, loadHistory, searchHistory };
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
class AdmError extends Error {
|
|
2
|
-
constructor(type, message, exitCode = 1, remediation = null) {
|
|
3
|
-
super(message);
|
|
4
|
-
this.name = 'AdmError';
|
|
5
|
-
this.type = type;
|
|
6
|
-
this.exitCode = exitCode;
|
|
7
|
-
this.remediation = remediation;
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function stripSensitive(msg) {
|
|
12
|
-
if (typeof msg !== 'string') return String(msg ?? '');
|
|
13
|
-
return msg
|
|
14
|
-
.replace(/ghp_[A-Za-z0-9]{20,}/g, '<TOKEN>')
|
|
15
|
-
.replace(/glpat-[A-Za-z0-9\-]{20,}/g, '<TOKEN>')
|
|
16
|
-
.replace(/Bearer\s+\S+/gi, 'Bearer <TOKEN>');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function handleAdmError(err) {
|
|
20
|
-
// String error
|
|
21
|
-
if (typeof err === 'string') {
|
|
22
|
-
return new AdmError('unknown', err, 1, 'Type /help for available commands.');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Network errors
|
|
26
|
-
if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
|
|
27
|
-
return new AdmError('network', 'Network error. Check your internet connection.', 1, 'Check your internet connection and try again.');
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Auth errors (401/403 without rate limit)
|
|
31
|
-
if (err.status === 401) {
|
|
32
|
-
return new AdmError('auth', stripSensitive('Authentication failed. Token may be expired or invalid.'), 1, 'Run /connect to reconnect.');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (err.status === 403) {
|
|
36
|
-
if (err.headers && err.headers['x-ratelimit-remaining'] === '0') {
|
|
37
|
-
return new AdmError('rate_limit', 'GitHub API rate limit exceeded.', 1, 'Wait a few minutes and try again.');
|
|
38
|
-
}
|
|
39
|
-
return new AdmError('auth', stripSensitive('Authentication failed. Token may be expired or invalid.'), 1, 'Run /connect to reconnect.');
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Not found
|
|
43
|
-
if (err.status === 404) {
|
|
44
|
-
return new AdmError('not_found', 'Resource not found.', 1, 'Check the repository/PR number and your permissions.');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// User errors — validation, missing arguments
|
|
48
|
-
const msg = stripSensitive(err.message || 'An unexpected error occurred.');
|
|
49
|
-
if (/missing required argument|argument.*required|must provide/i.test(msg)) {
|
|
50
|
-
return new AdmError('user_error', msg, 2, 'Type /help for available commands.');
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Unknown / fallback
|
|
54
|
-
return new AdmError('unknown', msg, 1, 'If this persists, file an issue at https://github.com/adm-cli/adm/issues.');
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function formatError(err) {
|
|
58
|
-
if (err instanceof AdmError) {
|
|
59
|
-
let msg = `Error: ${err.message}`;
|
|
60
|
-
if (err.remediation) {
|
|
61
|
-
msg += `\n → ${err.remediation}`;
|
|
62
|
-
}
|
|
63
|
-
return msg;
|
|
64
|
-
}
|
|
65
|
-
// Plain Error or anything else
|
|
66
|
-
return `Error: ${err.message || String(err)}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
module.exports = { AdmError, handleAdmError, formatError };
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
function matchCommands(query, commands) {
|
|
2
|
-
if (!query) return commands;
|
|
3
|
-
|
|
4
|
-
const lower = query.toLowerCase();
|
|
5
|
-
|
|
6
|
-
const prefixMatches = commands.filter(cmd => cmd.name.startsWith(lower));
|
|
7
|
-
if (prefixMatches.length > 0) return prefixMatches;
|
|
8
|
-
|
|
9
|
-
const fuzzyMatches = commands.filter(cmd => {
|
|
10
|
-
const name = cmd.name.toLowerCase();
|
|
11
|
-
let qi = 0;
|
|
12
|
-
for (let ci = 0; ci < name.length && qi < lower.length; ci++) {
|
|
13
|
-
if (name[ci] === lower[qi]) qi++;
|
|
14
|
-
}
|
|
15
|
-
return qi === lower.length;
|
|
16
|
-
});
|
|
17
|
-
return fuzzyMatches;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
module.exports = { matchCommands };
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
class GitHubError extends Error {
|
|
2
|
-
constructor(type, message, originalError) {
|
|
3
|
-
super(message);
|
|
4
|
-
this.name = 'GitHubError';
|
|
5
|
-
this.type = type;
|
|
6
|
-
this.originalError = originalError;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function handleGitHubError(err) {
|
|
11
|
-
if (err.status === 401 || err.status === 403) {
|
|
12
|
-
return new GitHubError('auth', 'Authentication failed. Token may be expired or invalid. Run /connect to reconnect.', err);
|
|
13
|
-
}
|
|
14
|
-
if (err.status === 403 && err.headers && err.headers['x-ratelimit-remaining'] === '0') {
|
|
15
|
-
return new GitHubError('rate_limit', 'GitHub API rate limit exceeded. Wait a few minutes and try again.', err);
|
|
16
|
-
}
|
|
17
|
-
if (err.status === 404) {
|
|
18
|
-
return new GitHubError('not_found', 'Resource not found. Check the repository/PR number and permissions.', err);
|
|
19
|
-
}
|
|
20
|
-
if (err.code === 'ENOTFOUND' || err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
|
|
21
|
-
return new GitHubError('network', 'Network error. Check your internet connection and try again.', err);
|
|
22
|
-
}
|
|
23
|
-
return new GitHubError('unknown', err.message || 'An unexpected error occurred.', err);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
module.exports = { GitHubError, handleGitHubError };
|