@fredlackey/devutils 0.0.19 → 0.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/README.md +223 -32
- package/package.json +7 -5
- package/src/api/loader.js +229 -0
- package/src/api/registry.json +62 -0
- package/src/cli.js +305 -0
- package/src/commands/ai/index.js +16 -0
- package/src/commands/ai/launch.js +112 -0
- package/src/commands/ai/list.js +54 -0
- package/src/commands/ai/resume.js +70 -0
- package/src/commands/ai/sessions.js +121 -0
- package/src/commands/ai/set.js +131 -0
- package/src/commands/ai/show.js +74 -0
- package/src/commands/ai/tools.js +46 -0
- package/src/commands/alias/add.js +93 -0
- package/src/commands/alias/helpers.js +107 -0
- package/src/commands/alias/index.js +14 -0
- package/src/commands/alias/list.js +55 -0
- package/src/commands/alias/remove.js +62 -0
- package/src/commands/alias/sync.js +109 -0
- package/src/commands/api/disable.js +73 -0
- package/src/commands/api/enable.js +148 -0
- package/src/commands/api/index.js +15 -0
- package/src/commands/api/list.js +66 -0
- package/src/commands/api/update.js +87 -0
- package/src/commands/auth/index.js +15 -0
- package/src/commands/auth/list.js +49 -0
- package/src/commands/auth/login.js +384 -0
- package/src/commands/auth/logout.js +111 -0
- package/src/commands/auth/refresh.js +184 -0
- package/src/commands/auth/services.js +169 -0
- package/src/commands/auth/status.js +104 -0
- package/src/commands/config/export.js +224 -0
- package/src/commands/config/get.js +52 -0
- package/src/commands/config/import.js +308 -0
- package/src/commands/config/index.js +17 -0
- package/src/commands/config/init.js +143 -0
- package/src/commands/config/reset.js +57 -0
- package/src/commands/config/set.js +93 -0
- package/src/commands/config/show.js +35 -0
- package/src/commands/help.js +338 -0
- package/src/commands/identity/add.js +133 -0
- package/src/commands/identity/index.js +17 -0
- package/src/commands/identity/link.js +76 -0
- package/src/commands/identity/list.js +48 -0
- package/src/commands/identity/remove.js +72 -0
- package/src/commands/identity/show.js +65 -0
- package/src/commands/identity/sync.js +172 -0
- package/src/commands/identity/unlink.js +57 -0
- package/src/commands/ignore/add.js +165 -0
- package/src/commands/ignore/index.js +14 -0
- package/src/commands/ignore/list.js +89 -0
- package/src/commands/ignore/markers.js +43 -0
- package/src/commands/ignore/remove.js +164 -0
- package/src/commands/ignore/show.js +169 -0
- package/src/commands/machine/detect.js +122 -0
- package/src/commands/machine/index.js +14 -0
- package/src/commands/machine/list.js +74 -0
- package/src/commands/machine/set.js +106 -0
- package/src/commands/machine/show.js +35 -0
- package/src/commands/schema.js +152 -0
- package/src/commands/search/collections.js +134 -0
- package/src/commands/search/get.js +71 -0
- package/src/commands/search/index-cmd.js +54 -0
- package/src/commands/search/index.js +21 -0
- package/src/commands/search/keyword.js +60 -0
- package/src/commands/search/qmd.js +70 -0
- package/src/commands/search/query.js +64 -0
- package/src/commands/search/semantic.js +62 -0
- package/src/commands/search/status.js +46 -0
- package/src/commands/status.js +276 -0
- package/src/commands/tools/check.js +79 -0
- package/src/commands/tools/index.js +14 -0
- package/src/commands/tools/install.js +110 -0
- package/src/commands/tools/list.js +91 -0
- package/src/commands/tools/search.js +60 -0
- package/src/commands/update.js +113 -0
- package/src/commands/util/add.js +151 -0
- package/src/commands/util/index.js +15 -0
- package/src/commands/util/list.js +97 -0
- package/src/commands/util/remove.js +76 -0
- package/src/commands/util/run.js +79 -0
- package/src/commands/util/show.js +67 -0
- package/src/commands/version.js +33 -0
- package/src/installers/_template.js +104 -0
- package/src/installers/git.js +150 -0
- package/src/installers/homebrew.js +190 -0
- package/src/installers/node.js +223 -0
- package/src/installers/registry.json +29 -0
- package/src/lib/config.js +125 -0
- package/src/lib/detect.js +74 -0
- package/src/lib/errors.js +114 -0
- package/src/lib/github.js +315 -0
- package/src/lib/installer.js +225 -0
- package/src/lib/output.js +239 -0
- package/src/lib/platform.js +112 -0
- package/src/lib/platforms/amazon-linux.js +41 -0
- package/src/lib/platforms/gitbash.js +46 -0
- package/src/lib/platforms/macos.js +45 -0
- package/src/lib/platforms/raspbian.js +41 -0
- package/src/lib/platforms/ubuntu.js +39 -0
- package/src/lib/platforms/windows.js +45 -0
- package/src/lib/prompt.js +161 -0
- package/src/lib/schema.js +211 -0
- package/src/lib/shell.js +75 -0
- package/src/patterns/gitignore/claude-code.txt +25 -0
- package/src/patterns/gitignore/docker.txt +15 -0
- package/src/patterns/gitignore/go.txt +24 -0
- package/src/patterns/gitignore/java.txt +38 -0
- package/src/patterns/gitignore/jetbrains.txt +26 -0
- package/src/patterns/gitignore/linux.txt +18 -0
- package/src/patterns/gitignore/macos.txt +27 -0
- package/src/patterns/gitignore/node.txt +51 -0
- package/src/patterns/gitignore/python.txt +55 -0
- package/src/patterns/gitignore/rust.txt +14 -0
- package/src/patterns/gitignore/terraform.txt +30 -0
- package/src/patterns/gitignore/vscode.txt +15 -0
- package/src/patterns/gitignore/windows.txt +25 -0
- package/src/utils/clone/index.js +165 -0
- package/src/utils/git-push/index.js +230 -0
- package/src/utils/git-status/index.js +116 -0
- package/src/utils/git-status/unix.sh +75 -0
- package/src/utils/registry.json +41 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const shell = require('../../lib/shell');
|
|
4
|
+
const { checkQmd, parseSearchResults } = require('./qmd');
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
description: 'Show search index health, collection count, and document count',
|
|
8
|
+
arguments: [],
|
|
9
|
+
flags: []
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Shows the health and status of the QMD search index.
|
|
14
|
+
* Displays collection count, document count, last update time, and health.
|
|
15
|
+
*
|
|
16
|
+
* @param {object} args - Parsed CLI arguments { positional, flags }.
|
|
17
|
+
* @param {object} context - CLI context { output, errors }.
|
|
18
|
+
*/
|
|
19
|
+
async function run(args, context) {
|
|
20
|
+
// Check for QMD availability first
|
|
21
|
+
const qmd = checkQmd();
|
|
22
|
+
if (!qmd.available) {
|
|
23
|
+
context.errors.throwError(1, qmd.message, 'search');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Run qmd status and capture the output
|
|
28
|
+
const result = await shell.exec('qmd status');
|
|
29
|
+
|
|
30
|
+
if (result.exitCode !== 0) {
|
|
31
|
+
context.errors.throwError(1, result.stderr || 'Failed to get search index status.', 'search');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Try to parse the output as JSON for structured data
|
|
36
|
+
const output = result.stdout;
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(output);
|
|
39
|
+
context.output.out(parsed);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// QMD returned non-JSON output, pass it through as-is
|
|
42
|
+
context.output.info(output || 'No status information available.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* status command.
|
|
5
|
+
* An overall health check that shows the current state of the DevUtils
|
|
6
|
+
* installation in a single-screen dashboard. Checks config, machine
|
|
7
|
+
* profile, auth services, aliases, API plugins, and sync status.
|
|
8
|
+
*
|
|
9
|
+
* This command is read-only and never modifies any files. It handles
|
|
10
|
+
* missing files gracefully, showing helpful placeholder messages
|
|
11
|
+
* instead of crashing.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const os = require('os');
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
description: 'Overall health check',
|
|
20
|
+
arguments: [],
|
|
21
|
+
flags: []
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Checks the config.json status. Reports whether the file exists
|
|
26
|
+
* and whether it contains valid JSON.
|
|
27
|
+
*
|
|
28
|
+
* @param {object} config - The context.config module.
|
|
29
|
+
* @returns {{ exists: boolean, valid: boolean }}
|
|
30
|
+
*/
|
|
31
|
+
function checkConfig(config) {
|
|
32
|
+
if (!config.exists('config.json')) {
|
|
33
|
+
return { exists: false, valid: false };
|
|
34
|
+
}
|
|
35
|
+
const data = config.read('config.json');
|
|
36
|
+
if (data === null) {
|
|
37
|
+
return { exists: true, valid: false };
|
|
38
|
+
}
|
|
39
|
+
return { exists: true, valid: true };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Checks the machine profile. Reads machines/current.json and pulls
|
|
44
|
+
* out key fields like type, arch, hostname, and packageManager.
|
|
45
|
+
*
|
|
46
|
+
* @param {object} config - The context.config module.
|
|
47
|
+
* @returns {{ detected: boolean, type?: string, arch?: string, hostname?: string, packageManager?: string }}
|
|
48
|
+
*/
|
|
49
|
+
function checkMachine(config) {
|
|
50
|
+
const profile = config.read('machines/current.json');
|
|
51
|
+
if (!profile) {
|
|
52
|
+
return { detected: false };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
detected: true,
|
|
57
|
+
type: profile.os ? profile.os.type : (profile.type || 'unknown'),
|
|
58
|
+
arch: profile.arch || 'unknown',
|
|
59
|
+
hostname: profile.hostname || 'unknown',
|
|
60
|
+
packageManager: profile.packageManagers
|
|
61
|
+
? profile.packageManagers[0] || null
|
|
62
|
+
: (profile.packageManager || null),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Checks the auth directory for connected services.
|
|
68
|
+
* Each JSON file (excluding subdirectories) represents a connected service.
|
|
69
|
+
*
|
|
70
|
+
* @param {object} config - The context.config module.
|
|
71
|
+
* @returns {{ services: string[], count: number }}
|
|
72
|
+
*/
|
|
73
|
+
function checkAuth(config) {
|
|
74
|
+
const authDir = config.getPath('auth');
|
|
75
|
+
if (!fs.existsSync(authDir)) {
|
|
76
|
+
return { services: [], count: 0 };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
const entries = fs.readdirSync(authDir);
|
|
81
|
+
const services = entries
|
|
82
|
+
.filter(entry => {
|
|
83
|
+
const fullPath = path.join(authDir, entry);
|
|
84
|
+
return fs.statSync(fullPath).isFile() && entry.endsWith('.json');
|
|
85
|
+
})
|
|
86
|
+
.map(file => path.basename(file, '.json'));
|
|
87
|
+
|
|
88
|
+
return { services, count: services.length };
|
|
89
|
+
} catch {
|
|
90
|
+
return { services: [], count: 0 };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Checks the aliases.json file for registered aliases.
|
|
96
|
+
*
|
|
97
|
+
* @param {object} config - The context.config module.
|
|
98
|
+
* @returns {{ count: number }}
|
|
99
|
+
*/
|
|
100
|
+
function checkAliases(config) {
|
|
101
|
+
const data = config.read('aliases.json');
|
|
102
|
+
if (!data) {
|
|
103
|
+
return { count: 0 };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Handle both array and object formats
|
|
107
|
+
if (Array.isArray(data)) {
|
|
108
|
+
return { count: data.length };
|
|
109
|
+
}
|
|
110
|
+
return { count: Object.keys(data).length };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Checks the plugins.json file for installed API plugins.
|
|
115
|
+
*
|
|
116
|
+
* @param {object} config - The context.config module.
|
|
117
|
+
* @returns {{ count: number, plugins: string[] }}
|
|
118
|
+
*/
|
|
119
|
+
function checkPlugins(config) {
|
|
120
|
+
const data = config.read('plugins.json');
|
|
121
|
+
if (!data) {
|
|
122
|
+
return { count: 0, plugins: [] };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Handle both array and object formats
|
|
126
|
+
if (Array.isArray(data)) {
|
|
127
|
+
return { count: data.length, plugins: data };
|
|
128
|
+
}
|
|
129
|
+
const plugins = Object.keys(data);
|
|
130
|
+
return { count: plugins.length, plugins };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Checks the sync.json file for the last backup timestamp.
|
|
135
|
+
*
|
|
136
|
+
* @param {object} config - The context.config module.
|
|
137
|
+
* @returns {{ lastBackup: string|null }}
|
|
138
|
+
*/
|
|
139
|
+
function checkSync(config) {
|
|
140
|
+
const data = config.read('sync.json');
|
|
141
|
+
if (!data || !data.lastBackup) {
|
|
142
|
+
return { lastBackup: null };
|
|
143
|
+
}
|
|
144
|
+
return { lastBackup: data.lastBackup };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Formats a human-readable summary line for the config section.
|
|
149
|
+
*
|
|
150
|
+
* @param {{ exists: boolean, valid: boolean }} configStatus - The config check result.
|
|
151
|
+
* @returns {string}
|
|
152
|
+
*/
|
|
153
|
+
function formatConfig(configStatus) {
|
|
154
|
+
if (!configStatus.exists) {
|
|
155
|
+
return 'Not found. Run \'dev config init\' to get started.';
|
|
156
|
+
}
|
|
157
|
+
if (!configStatus.valid) {
|
|
158
|
+
return 'Invalid JSON. Check ~/.devutils/config.json for syntax errors.';
|
|
159
|
+
}
|
|
160
|
+
return 'OK';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Formats a human-readable summary line for the machine section.
|
|
165
|
+
*
|
|
166
|
+
* @param {{ detected: boolean, type?: string, arch?: string, packageManager?: string }} machineStatus - The machine check result.
|
|
167
|
+
* @returns {string}
|
|
168
|
+
*/
|
|
169
|
+
function formatMachine(machineStatus) {
|
|
170
|
+
if (!machineStatus.detected) {
|
|
171
|
+
return 'Not detected. Run \'dev machine detect\' to set up.';
|
|
172
|
+
}
|
|
173
|
+
const pm = machineStatus.packageManager ? ` (${machineStatus.packageManager})` : '';
|
|
174
|
+
return `${machineStatus.type} ${machineStatus.arch}${pm}`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Formats a human-readable summary line for the auth section.
|
|
179
|
+
*
|
|
180
|
+
* @param {{ services: string[], count: number }} authStatus - The auth check result.
|
|
181
|
+
* @returns {string}
|
|
182
|
+
*/
|
|
183
|
+
function formatAuth(authStatus) {
|
|
184
|
+
if (authStatus.count === 0) {
|
|
185
|
+
return 'No services connected.';
|
|
186
|
+
}
|
|
187
|
+
return `${authStatus.count} services (${authStatus.services.join(', ')})`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Formats a human-readable summary line for the aliases section.
|
|
192
|
+
*
|
|
193
|
+
* @param {{ count: number }} aliasStatus - The alias check result.
|
|
194
|
+
* @returns {string}
|
|
195
|
+
*/
|
|
196
|
+
function formatAliases(aliasStatus) {
|
|
197
|
+
if (aliasStatus.count === 0) {
|
|
198
|
+
return 'None registered.';
|
|
199
|
+
}
|
|
200
|
+
return `${aliasStatus.count} registered`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Formats a human-readable summary line for the plugins section.
|
|
205
|
+
*
|
|
206
|
+
* @param {{ count: number, plugins: string[] }} pluginStatus - The plugin check result.
|
|
207
|
+
* @returns {string}
|
|
208
|
+
*/
|
|
209
|
+
function formatPlugins(pluginStatus) {
|
|
210
|
+
if (pluginStatus.count === 0) {
|
|
211
|
+
return 'None installed.';
|
|
212
|
+
}
|
|
213
|
+
return `${pluginStatus.count} installed (${pluginStatus.plugins.join(', ')})`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Formats a human-readable summary line for the sync section.
|
|
218
|
+
*
|
|
219
|
+
* @param {{ lastBackup: string|null }} syncStatus - The sync check result.
|
|
220
|
+
* @returns {string}
|
|
221
|
+
*/
|
|
222
|
+
function formatSync(syncStatus) {
|
|
223
|
+
if (!syncStatus.lastBackup) {
|
|
224
|
+
return 'Never.';
|
|
225
|
+
}
|
|
226
|
+
// Format the ISO timestamp into something more readable
|
|
227
|
+
try {
|
|
228
|
+
const date = new Date(syncStatus.lastBackup);
|
|
229
|
+
return date.toLocaleString();
|
|
230
|
+
} catch {
|
|
231
|
+
return syncStatus.lastBackup;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Runs the status health check. Gathers information from config files,
|
|
237
|
+
* machine profile, auth directory, aliases, plugins, and sync state.
|
|
238
|
+
* Outputs a compact summary for humans or a structured object for JSON.
|
|
239
|
+
*
|
|
240
|
+
* @param {object} args - Parsed command arguments (none expected).
|
|
241
|
+
* @param {object} context - The CLI context object with config, platform, shell, output, flags.
|
|
242
|
+
*/
|
|
243
|
+
async function run(args, context) {
|
|
244
|
+
const configStatus = checkConfig(context.config);
|
|
245
|
+
const machineStatus = checkMachine(context.config);
|
|
246
|
+
const authStatus = checkAuth(context.config);
|
|
247
|
+
const aliasStatus = checkAliases(context.config);
|
|
248
|
+
const pluginStatus = checkPlugins(context.config);
|
|
249
|
+
const syncStatus = checkSync(context.config);
|
|
250
|
+
|
|
251
|
+
const result = {
|
|
252
|
+
config: configStatus,
|
|
253
|
+
machine: machineStatus,
|
|
254
|
+
auth: authStatus,
|
|
255
|
+
aliases: aliasStatus,
|
|
256
|
+
plugins: pluginStatus,
|
|
257
|
+
sync: syncStatus,
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (context.flags.format === 'json') {
|
|
261
|
+
context.output.out(result);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Human-friendly output
|
|
266
|
+
context.output.info('DevUtils Status');
|
|
267
|
+
context.output.info('---------------');
|
|
268
|
+
context.output.info(`Config: ${formatConfig(configStatus)}`);
|
|
269
|
+
context.output.info(`Machine: ${formatMachine(machineStatus)}`);
|
|
270
|
+
context.output.info(`Auth: ${formatAuth(authStatus)}`);
|
|
271
|
+
context.output.info(`Aliases: ${formatAliases(aliasStatus)}`);
|
|
272
|
+
context.output.info(`API Plugins: ${formatPlugins(pluginStatus)}`);
|
|
273
|
+
context.output.info(`Last Sync: ${formatSync(syncStatus)}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Command: dev tools check <tool-name>
|
|
5
|
+
*
|
|
6
|
+
* Checks if a tool is installed and shows its version.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @type {{ description: string, arguments: Array, flags: Array }}
|
|
11
|
+
*/
|
|
12
|
+
const meta = {
|
|
13
|
+
description: 'Check if a tool is installed and show its version',
|
|
14
|
+
arguments: [
|
|
15
|
+
{ name: 'tool', description: 'Name of the tool to check', required: true }
|
|
16
|
+
],
|
|
17
|
+
flags: []
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Runs the check command.
|
|
22
|
+
* @param {{ positional: string[], flags: object }} args - Parsed command arguments.
|
|
23
|
+
* @param {object} context - The CLI context object.
|
|
24
|
+
*/
|
|
25
|
+
async function run(args, context) {
|
|
26
|
+
const toolName = args.positional[0];
|
|
27
|
+
|
|
28
|
+
if (!toolName) {
|
|
29
|
+
context.errors.throwError(400, 'Missing required argument: tool. Usage: dev tools check <tool-name>', 'tools');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const installer = require('../../lib/installer');
|
|
34
|
+
|
|
35
|
+
// Look up the tool in the registry
|
|
36
|
+
const tool = installer.findTool(toolName);
|
|
37
|
+
if (!tool) {
|
|
38
|
+
context.output.error(`Tool '${toolName}' not found in registry. Run "dev tools search ${toolName}" to search.`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Load the installer and check if the tool is installed
|
|
44
|
+
const mod = installer.loadInstaller(tool);
|
|
45
|
+
const installed = await mod.isInstalled(context);
|
|
46
|
+
|
|
47
|
+
if (installed) {
|
|
48
|
+
// Try to get the version
|
|
49
|
+
let version = 'version unknown';
|
|
50
|
+
if (typeof mod.getVersion === 'function') {
|
|
51
|
+
const ver = await mod.getVersion(context);
|
|
52
|
+
if (ver) {
|
|
53
|
+
version = `version ${ver}`;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
// Fall back to --version
|
|
57
|
+
try {
|
|
58
|
+
const result = await context.shell.exec(`${tool.name} --version`);
|
|
59
|
+
if (result.exitCode === 0 && result.stdout) {
|
|
60
|
+
const match = result.stdout.trim().match(/(\d+\.\d+[\.\d]*)/);
|
|
61
|
+
if (match) {
|
|
62
|
+
version = `version ${match[1]}`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// version remains "version unknown"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
context.output.out({ name: tool.name, installed: true, version });
|
|
71
|
+
} else {
|
|
72
|
+
context.output.out({ name: tool.name, installed: false, version: null });
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
context.output.error(err.message);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tools service registration.
|
|
3
|
+
* Tool installation and management.
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {
|
|
6
|
+
name: 'tools',
|
|
7
|
+
description: 'Tool installation and management',
|
|
8
|
+
commands: {
|
|
9
|
+
install: () => require('./install'),
|
|
10
|
+
check: () => require('./check'),
|
|
11
|
+
list: () => require('./list'),
|
|
12
|
+
search: () => require('./search'),
|
|
13
|
+
}
|
|
14
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Command: dev tools install <tool-name>
|
|
5
|
+
*
|
|
6
|
+
* Installs a development tool using the platform-appropriate method.
|
|
7
|
+
* Resolves and installs dependencies automatically.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @type {{ description: string, arguments: Array, flags: Array }}
|
|
12
|
+
*/
|
|
13
|
+
const meta = {
|
|
14
|
+
description: 'Install a development tool',
|
|
15
|
+
arguments: [
|
|
16
|
+
{ name: 'tool', description: 'Name of the tool to install', required: true }
|
|
17
|
+
],
|
|
18
|
+
flags: [
|
|
19
|
+
{ name: '--dry-run', description: 'Show what would be installed without doing it' },
|
|
20
|
+
{ name: '--skip-deps', description: 'Skip dependency installation' }
|
|
21
|
+
]
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Runs the install command.
|
|
26
|
+
* @param {{ positional: string[], flags: object }} args - Parsed command arguments.
|
|
27
|
+
* @param {object} context - The CLI context object.
|
|
28
|
+
*/
|
|
29
|
+
async function run(args, context) {
|
|
30
|
+
const toolName = args.positional[0];
|
|
31
|
+
|
|
32
|
+
if (!toolName) {
|
|
33
|
+
context.errors.throwError(400, 'Missing required argument: tool. Usage: dev tools install <tool-name>', 'tools');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const installer = require('../../lib/installer');
|
|
38
|
+
|
|
39
|
+
// Look up the tool in the registry
|
|
40
|
+
const tool = installer.findTool(toolName);
|
|
41
|
+
if (!tool) {
|
|
42
|
+
context.output.error(`Tool '${toolName}' not found in registry. Run "dev tools search ${toolName}" to search.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Check platform support
|
|
47
|
+
const platformType = context.platform.detect().type;
|
|
48
|
+
if (!tool.platforms.includes(platformType)) {
|
|
49
|
+
context.output.error(
|
|
50
|
+
`Tool '${tool.name}' is not supported on ${platformType}. ` +
|
|
51
|
+
`Supported platforms: ${tool.platforms.join(', ')}`
|
|
52
|
+
);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Dry run: show what would be installed without doing it
|
|
57
|
+
if (context.flags.dryRun || args.flags['dry-run']) {
|
|
58
|
+
try {
|
|
59
|
+
const chain = installer.resolveDependencies(tool.name);
|
|
60
|
+
|
|
61
|
+
// Check which tools are already installed
|
|
62
|
+
const statuses = [];
|
|
63
|
+
for (const name of chain) {
|
|
64
|
+
const isAlready = await installer.checkInstalled(name, context);
|
|
65
|
+
statuses.push({ name, installed: isAlready });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const needsInstall = statuses.filter(s => !s.installed);
|
|
69
|
+
const alreadyInstalled = statuses.filter(s => s.installed);
|
|
70
|
+
|
|
71
|
+
if (needsInstall.length === 0) {
|
|
72
|
+
context.output.info(`${tool.name} is already installed (and all dependencies).`);
|
|
73
|
+
} else {
|
|
74
|
+
context.output.info('Would install (in order):');
|
|
75
|
+
for (const s of needsInstall) {
|
|
76
|
+
context.output.info(` - ${s.name}`);
|
|
77
|
+
}
|
|
78
|
+
if (alreadyInstalled.length > 0) {
|
|
79
|
+
context.output.info('Already installed:');
|
|
80
|
+
for (const s of alreadyInstalled) {
|
|
81
|
+
context.output.info(` - ${s.name}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} catch (err) {
|
|
86
|
+
context.output.error(err.message);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Actually install
|
|
92
|
+
try {
|
|
93
|
+
const result = await installer.installTool(tool.name, context);
|
|
94
|
+
|
|
95
|
+
if (result.alreadyInstalled) {
|
|
96
|
+
context.output.info(`${tool.name} is already installed.`);
|
|
97
|
+
} else {
|
|
98
|
+
if (result.dependenciesInstalled.length > 0) {
|
|
99
|
+
context.output.info(`Dependencies installed: ${result.dependenciesInstalled.join(', ')}`);
|
|
100
|
+
}
|
|
101
|
+
if (result.installed) {
|
|
102
|
+
context.output.info(`${tool.name} installed successfully.`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
context.output.error(err.message);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Command: dev tools list
|
|
5
|
+
*
|
|
6
|
+
* Lists all available tools from the registry, with their install status.
|
|
7
|
+
* Can filter by --installed, --available, or --platform.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @type {{ description: string, arguments: Array, flags: Array }}
|
|
12
|
+
*/
|
|
13
|
+
const meta = {
|
|
14
|
+
description: 'List available tools',
|
|
15
|
+
arguments: [],
|
|
16
|
+
flags: [
|
|
17
|
+
{ name: '--installed', description: 'Show only installed tools' },
|
|
18
|
+
{ name: '--available', description: 'Show only tools not yet installed' },
|
|
19
|
+
{ name: '--platform', description: 'Filter by platform (default: current)' }
|
|
20
|
+
]
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Runs the list command.
|
|
25
|
+
* @param {{ positional: string[], flags: object }} args - Parsed command arguments.
|
|
26
|
+
* @param {object} context - The CLI context object.
|
|
27
|
+
*/
|
|
28
|
+
async function run(args, context) {
|
|
29
|
+
const installer = require('../../lib/installer');
|
|
30
|
+
|
|
31
|
+
// Load the full registry
|
|
32
|
+
const tools = installer.loadRegistry();
|
|
33
|
+
|
|
34
|
+
// Determine platform filter
|
|
35
|
+
const platformFilter = args.flags.platform || context.platform.detect().type;
|
|
36
|
+
|
|
37
|
+
// Filter to tools that support the target platform
|
|
38
|
+
let filtered = tools.filter(t => t.platforms.includes(platformFilter));
|
|
39
|
+
|
|
40
|
+
// If --installed or --available, check each tool's install status
|
|
41
|
+
const showInstalled = args.flags.installed === true;
|
|
42
|
+
const showAvailable = args.flags.available === true;
|
|
43
|
+
|
|
44
|
+
if (showInstalled || showAvailable) {
|
|
45
|
+
const statusResults = [];
|
|
46
|
+
for (const tool of filtered) {
|
|
47
|
+
try {
|
|
48
|
+
const mod = installer.loadInstaller(tool);
|
|
49
|
+
const isInst = await mod.isInstalled(context);
|
|
50
|
+
statusResults.push({ tool, installed: isInst });
|
|
51
|
+
} catch {
|
|
52
|
+
// If the installer can't be loaded, treat as not installed
|
|
53
|
+
statusResults.push({ tool, installed: false });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (showInstalled) {
|
|
58
|
+
filtered = statusResults.filter(r => r.installed).map(r => r.tool);
|
|
59
|
+
} else {
|
|
60
|
+
filtered = statusResults.filter(r => !r.installed).map(r => r.tool);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (filtered.length === 0) {
|
|
65
|
+
context.output.info('No tools match the current filters.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Build output rows with install status
|
|
70
|
+
const rows = [];
|
|
71
|
+
for (const tool of filtered) {
|
|
72
|
+
let status = '-';
|
|
73
|
+
try {
|
|
74
|
+
const mod = installer.loadInstaller(tool);
|
|
75
|
+
const isInst = await mod.isInstalled(context);
|
|
76
|
+
status = isInst ? 'yes' : 'no';
|
|
77
|
+
} catch {
|
|
78
|
+
status = '?';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
rows.push({
|
|
82
|
+
Name: tool.name,
|
|
83
|
+
Description: tool.description,
|
|
84
|
+
Installed: status,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
context.output.out(rows);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { meta, run };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Command: dev tools search <query>
|
|
5
|
+
*
|
|
6
|
+
* Searches for tools by name or keyword in the registry.
|
|
7
|
+
* Matches against tool name and description (case-insensitive).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @type {{ description: string, arguments: Array, flags: Array }}
|
|
12
|
+
*/
|
|
13
|
+
const meta = {
|
|
14
|
+
description: 'Search for tools by name or keyword',
|
|
15
|
+
arguments: [
|
|
16
|
+
{ name: 'query', description: 'Search term (matches name and description)', required: true }
|
|
17
|
+
],
|
|
18
|
+
flags: []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Runs the search command.
|
|
23
|
+
* @param {{ positional: string[], flags: object }} args - Parsed command arguments.
|
|
24
|
+
* @param {object} context - The CLI context object.
|
|
25
|
+
*/
|
|
26
|
+
async function run(args, context) {
|
|
27
|
+
const query = args.positional[0];
|
|
28
|
+
|
|
29
|
+
if (!query) {
|
|
30
|
+
context.errors.throwError(400, 'Missing required argument: query. Usage: dev tools search <query>', 'tools');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const installer = require('../../lib/installer');
|
|
35
|
+
|
|
36
|
+
// Load the registry and search
|
|
37
|
+
const tools = installer.loadRegistry();
|
|
38
|
+
const lower = query.toLowerCase();
|
|
39
|
+
|
|
40
|
+
const matches = tools.filter(t =>
|
|
41
|
+
t.name.toLowerCase().includes(lower) ||
|
|
42
|
+
t.description.toLowerCase().includes(lower)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (matches.length === 0) {
|
|
46
|
+
context.output.info(`No tools found matching '${query}'.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Format as table with name, description, and platforms
|
|
51
|
+
const rows = matches.map(t => ({
|
|
52
|
+
Name: t.name,
|
|
53
|
+
Description: t.description,
|
|
54
|
+
Platforms: t.platforms.join(', '),
|
|
55
|
+
}));
|
|
56
|
+
|
|
57
|
+
context.output.out(rows);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { meta, run };
|