@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.
Files changed (122) hide show
  1. package/README.md +223 -32
  2. package/package.json +7 -5
  3. package/src/api/loader.js +229 -0
  4. package/src/api/registry.json +62 -0
  5. package/src/cli.js +305 -0
  6. package/src/commands/ai/index.js +16 -0
  7. package/src/commands/ai/launch.js +112 -0
  8. package/src/commands/ai/list.js +54 -0
  9. package/src/commands/ai/resume.js +70 -0
  10. package/src/commands/ai/sessions.js +121 -0
  11. package/src/commands/ai/set.js +131 -0
  12. package/src/commands/ai/show.js +74 -0
  13. package/src/commands/ai/tools.js +46 -0
  14. package/src/commands/alias/add.js +93 -0
  15. package/src/commands/alias/helpers.js +107 -0
  16. package/src/commands/alias/index.js +14 -0
  17. package/src/commands/alias/list.js +55 -0
  18. package/src/commands/alias/remove.js +62 -0
  19. package/src/commands/alias/sync.js +109 -0
  20. package/src/commands/api/disable.js +73 -0
  21. package/src/commands/api/enable.js +148 -0
  22. package/src/commands/api/index.js +15 -0
  23. package/src/commands/api/list.js +66 -0
  24. package/src/commands/api/update.js +87 -0
  25. package/src/commands/auth/index.js +15 -0
  26. package/src/commands/auth/list.js +49 -0
  27. package/src/commands/auth/login.js +384 -0
  28. package/src/commands/auth/logout.js +111 -0
  29. package/src/commands/auth/refresh.js +184 -0
  30. package/src/commands/auth/services.js +169 -0
  31. package/src/commands/auth/status.js +104 -0
  32. package/src/commands/config/export.js +224 -0
  33. package/src/commands/config/get.js +52 -0
  34. package/src/commands/config/import.js +308 -0
  35. package/src/commands/config/index.js +17 -0
  36. package/src/commands/config/init.js +143 -0
  37. package/src/commands/config/reset.js +57 -0
  38. package/src/commands/config/set.js +93 -0
  39. package/src/commands/config/show.js +35 -0
  40. package/src/commands/help.js +338 -0
  41. package/src/commands/identity/add.js +133 -0
  42. package/src/commands/identity/index.js +17 -0
  43. package/src/commands/identity/link.js +76 -0
  44. package/src/commands/identity/list.js +48 -0
  45. package/src/commands/identity/remove.js +72 -0
  46. package/src/commands/identity/show.js +65 -0
  47. package/src/commands/identity/sync.js +172 -0
  48. package/src/commands/identity/unlink.js +57 -0
  49. package/src/commands/ignore/add.js +165 -0
  50. package/src/commands/ignore/index.js +14 -0
  51. package/src/commands/ignore/list.js +89 -0
  52. package/src/commands/ignore/markers.js +43 -0
  53. package/src/commands/ignore/remove.js +164 -0
  54. package/src/commands/ignore/show.js +169 -0
  55. package/src/commands/machine/detect.js +122 -0
  56. package/src/commands/machine/index.js +14 -0
  57. package/src/commands/machine/list.js +74 -0
  58. package/src/commands/machine/set.js +106 -0
  59. package/src/commands/machine/show.js +35 -0
  60. package/src/commands/schema.js +152 -0
  61. package/src/commands/search/collections.js +134 -0
  62. package/src/commands/search/get.js +71 -0
  63. package/src/commands/search/index-cmd.js +54 -0
  64. package/src/commands/search/index.js +21 -0
  65. package/src/commands/search/keyword.js +60 -0
  66. package/src/commands/search/qmd.js +70 -0
  67. package/src/commands/search/query.js +64 -0
  68. package/src/commands/search/semantic.js +62 -0
  69. package/src/commands/search/status.js +46 -0
  70. package/src/commands/status.js +276 -0
  71. package/src/commands/tools/check.js +79 -0
  72. package/src/commands/tools/index.js +14 -0
  73. package/src/commands/tools/install.js +110 -0
  74. package/src/commands/tools/list.js +91 -0
  75. package/src/commands/tools/search.js +60 -0
  76. package/src/commands/update.js +113 -0
  77. package/src/commands/util/add.js +151 -0
  78. package/src/commands/util/index.js +15 -0
  79. package/src/commands/util/list.js +97 -0
  80. package/src/commands/util/remove.js +76 -0
  81. package/src/commands/util/run.js +79 -0
  82. package/src/commands/util/show.js +67 -0
  83. package/src/commands/version.js +33 -0
  84. package/src/installers/_template.js +104 -0
  85. package/src/installers/git.js +150 -0
  86. package/src/installers/homebrew.js +190 -0
  87. package/src/installers/node.js +223 -0
  88. package/src/installers/registry.json +29 -0
  89. package/src/lib/config.js +125 -0
  90. package/src/lib/detect.js +74 -0
  91. package/src/lib/errors.js +114 -0
  92. package/src/lib/github.js +315 -0
  93. package/src/lib/installer.js +225 -0
  94. package/src/lib/output.js +239 -0
  95. package/src/lib/platform.js +112 -0
  96. package/src/lib/platforms/amazon-linux.js +41 -0
  97. package/src/lib/platforms/gitbash.js +46 -0
  98. package/src/lib/platforms/macos.js +45 -0
  99. package/src/lib/platforms/raspbian.js +41 -0
  100. package/src/lib/platforms/ubuntu.js +39 -0
  101. package/src/lib/platforms/windows.js +45 -0
  102. package/src/lib/prompt.js +161 -0
  103. package/src/lib/schema.js +211 -0
  104. package/src/lib/shell.js +75 -0
  105. package/src/patterns/gitignore/claude-code.txt +25 -0
  106. package/src/patterns/gitignore/docker.txt +15 -0
  107. package/src/patterns/gitignore/go.txt +24 -0
  108. package/src/patterns/gitignore/java.txt +38 -0
  109. package/src/patterns/gitignore/jetbrains.txt +26 -0
  110. package/src/patterns/gitignore/linux.txt +18 -0
  111. package/src/patterns/gitignore/macos.txt +27 -0
  112. package/src/patterns/gitignore/node.txt +51 -0
  113. package/src/patterns/gitignore/python.txt +55 -0
  114. package/src/patterns/gitignore/rust.txt +14 -0
  115. package/src/patterns/gitignore/terraform.txt +30 -0
  116. package/src/patterns/gitignore/vscode.txt +15 -0
  117. package/src/patterns/gitignore/windows.txt +25 -0
  118. package/src/utils/clone/index.js +165 -0
  119. package/src/utils/git-push/index.js +230 -0
  120. package/src/utils/git-status/index.js +116 -0
  121. package/src/utils/git-status/unix.sh +75 -0
  122. package/src/utils/registry.json +41 -0
@@ -0,0 +1,113 @@
1
+ 'use strict';
2
+
3
+ const shell = require('../lib/shell');
4
+
5
+ const meta = {
6
+ description: 'Self-update to the latest published version.',
7
+ arguments: [],
8
+ flags: [
9
+ { name: 'check', description: 'Check for updates without installing' },
10
+ ],
11
+ };
12
+
13
+ /**
14
+ * Get the current installed version from package.json.
15
+ * @returns {string}
16
+ */
17
+ function getCurrentVersion() {
18
+ const pkg = require('../../package.json');
19
+ return pkg.version;
20
+ }
21
+
22
+ /**
23
+ * Get the latest version from the npm registry.
24
+ * @returns {Promise<string|null>}
25
+ */
26
+ async function getLatestVersion() {
27
+ const result = await shell.exec('npm view @fredlackey/devutils version');
28
+ if (result.exitCode !== 0) return null;
29
+ return result.stdout.trim() || null;
30
+ }
31
+
32
+ /**
33
+ * Compare two semver strings. Returns true if latest is newer than current.
34
+ * @param {string} latest
35
+ * @param {string} current
36
+ * @returns {boolean}
37
+ */
38
+ function isNewer(latest, current) {
39
+ const l = latest.split('.').map(Number);
40
+ const c = current.split('.').map(Number);
41
+ for (let i = 0; i < Math.max(l.length, c.length); i++) {
42
+ const lv = l[i] || 0;
43
+ const cv = c[i] || 0;
44
+ if (lv > cv) return true;
45
+ if (lv < cv) return false;
46
+ }
47
+ return false;
48
+ }
49
+
50
+ async function run(args, context) {
51
+ if (!shell.commandExists('npm')) {
52
+ context.errors.throwError(500, 'npm is not available on this system. Install Node.js and npm first.', 'update');
53
+ return;
54
+ }
55
+
56
+ const current = getCurrentVersion();
57
+ context.output.info(`Current version: ${current}`);
58
+ context.output.info('Checking for updates...');
59
+
60
+ const latest = await getLatestVersion();
61
+ if (!latest) {
62
+ context.errors.throwError(500, 'Could not reach the npm registry. Check your network connection.', 'update');
63
+ return;
64
+ }
65
+
66
+ if (!isNewer(latest, current)) {
67
+ const result = {
68
+ current,
69
+ latest,
70
+ updateAvailable: false,
71
+ message: `Already on the latest version (${current}).`,
72
+ };
73
+ context.output.out(result);
74
+ return;
75
+ }
76
+
77
+ // Update available
78
+ if (args.flags.check) {
79
+ const result = {
80
+ current,
81
+ latest,
82
+ updateAvailable: true,
83
+ message: `Update available: ${current} -> ${latest}. Run "dev update" to install.`,
84
+ };
85
+ context.output.out(result);
86
+ return;
87
+ }
88
+
89
+ // Perform the update
90
+ context.output.info(`Updating from ${current} to ${latest}...`);
91
+
92
+ const installResult = await shell.exec('npm install -g @fredlackey/devutils@latest');
93
+
94
+ if (installResult.exitCode !== 0) {
95
+ const msg = installResult.stderr || '';
96
+ if (msg.includes('EACCES') || msg.includes('permission')) {
97
+ context.errors.throwError(403, 'Permission denied. Try running with sudo: sudo dev update', 'update');
98
+ return;
99
+ }
100
+ context.errors.throwError(500, `Update failed: ${msg}`, 'update');
101
+ return;
102
+ }
103
+
104
+ const result = {
105
+ previous: current,
106
+ current: latest,
107
+ updateAvailable: false,
108
+ message: `Updated to ${latest}. Restart your terminal to use the new version.`,
109
+ };
110
+ context.output.out(result);
111
+ }
112
+
113
+ module.exports = { meta, run };
@@ -0,0 +1,151 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const meta = {
8
+ description: 'Register a custom utility by name and source path. Copies the script into ~/.devutils/utils/.',
9
+ arguments: [
10
+ { name: 'name', required: true, description: 'Name for the utility (used in dev util run <name>)' },
11
+ { name: 'path', required: true, description: 'Path to the script file or directory to register' },
12
+ ],
13
+ flags: [
14
+ { name: 'link', description: 'Create a symlink instead of copying (for active development)' },
15
+ ],
16
+ };
17
+
18
+ /**
19
+ * Recursively copy a directory.
20
+ * @param {string} src - Source directory path.
21
+ * @param {string} dest - Destination directory path.
22
+ */
23
+ function copyDirectorySync(src, dest) {
24
+ fs.mkdirSync(dest, { recursive: true });
25
+ const entries = fs.readdirSync(src, { withFileTypes: true });
26
+ for (const entry of entries) {
27
+ const srcPath = path.join(src, entry.name);
28
+ const destPath = path.join(dest, entry.name);
29
+ if (entry.isDirectory()) {
30
+ copyDirectorySync(srcPath, destPath);
31
+ } else {
32
+ fs.copyFileSync(srcPath, destPath);
33
+ }
34
+ }
35
+ }
36
+
37
+ async function run(args, context) {
38
+ const name = args.positional[0];
39
+ const sourcePath = args.positional[1];
40
+
41
+ if (!name || !sourcePath) {
42
+ context.errors.throwError(400, 'Usage: dev util add <name> <path>', 'util');
43
+ return;
44
+ }
45
+
46
+ if (!/^[a-z0-9][a-z0-9-]*$/.test(name)) {
47
+ context.output.error('Utility names must be lowercase letters, numbers, and hyphens only.');
48
+ context.output.info('Example: my-script, git-helper, deploy-2');
49
+ return;
50
+ }
51
+
52
+ // Check for name conflicts with built-in utilities
53
+ const builtInRegistry = require('../../utils/registry.json');
54
+ const isBuiltIn = (builtInRegistry.utilities || []).some(u => u.name === name);
55
+ if (isBuiltIn) {
56
+ context.output.error(`"${name}" is a built-in utility and cannot be overridden.`);
57
+ return;
58
+ }
59
+
60
+ const customDir = path.join(os.homedir(), '.devutils', 'utils');
61
+ const existingFolder = path.join(customDir, name);
62
+ const existingFile = path.join(customDir, name + '.js');
63
+ if (fs.existsSync(existingFolder) || fs.existsSync(existingFile)) {
64
+ context.output.error(`A custom utility named "${name}" already exists.`);
65
+ context.output.info('Remove it first with: dev util remove ' + name);
66
+ return;
67
+ }
68
+
69
+ // Validate the source path
70
+ const resolvedSource = path.resolve(sourcePath);
71
+ if (!fs.existsSync(resolvedSource)) {
72
+ context.output.error(`Source path not found: ${resolvedSource}`);
73
+ return;
74
+ }
75
+
76
+ const stat = fs.statSync(resolvedSource);
77
+ let entryPoint;
78
+
79
+ if (stat.isDirectory()) {
80
+ entryPoint = path.join(resolvedSource, 'index.js');
81
+ if (!fs.existsSync(entryPoint)) {
82
+ context.output.error('Directory does not contain an index.js file.');
83
+ return;
84
+ }
85
+ } else if (stat.isFile() && resolvedSource.endsWith('.js')) {
86
+ entryPoint = resolvedSource;
87
+ } else {
88
+ context.output.error('Source must be a .js file or a directory containing index.js.');
89
+ return;
90
+ }
91
+
92
+ // Verify it exports a run function
93
+ try {
94
+ const mod = require(entryPoint);
95
+ if (typeof mod.run !== 'function') {
96
+ context.output.error('The source file must export a run() function.');
97
+ return;
98
+ }
99
+ } catch (err) {
100
+ context.output.error(`Failed to load source file: ${err.message}`);
101
+ return;
102
+ }
103
+
104
+ // Copy or link the utility
105
+ fs.mkdirSync(customDir, { recursive: true });
106
+
107
+ if (args.flags.link) {
108
+ const linkTarget = stat.isDirectory()
109
+ ? path.join(customDir, name)
110
+ : path.join(customDir, name + '.js');
111
+ fs.symlinkSync(resolvedSource, linkTarget);
112
+ } else {
113
+ if (stat.isDirectory()) {
114
+ copyDirectorySync(resolvedSource, path.join(customDir, name));
115
+ } else {
116
+ fs.copyFileSync(resolvedSource, path.join(customDir, name + '.js'));
117
+ }
118
+ }
119
+
120
+ // Update the custom registry
121
+ const registryPath = path.join(customDir, 'registry.json');
122
+ let registry = { utilities: [] };
123
+ if (fs.existsSync(registryPath)) {
124
+ try {
125
+ registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
126
+ } catch {
127
+ registry = { utilities: [] };
128
+ }
129
+ }
130
+
131
+ const mod = require(entryPoint);
132
+ const utilMeta = mod.meta || {};
133
+
134
+ registry.utilities.push({
135
+ name: name,
136
+ description: utilMeta.description || '',
137
+ type: 'custom',
138
+ platforms: utilMeta.platforms || [],
139
+ arguments: utilMeta.arguments || [],
140
+ flags: utilMeta.flags || [],
141
+ source: resolvedSource,
142
+ linked: !!args.flags.link,
143
+ });
144
+
145
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n');
146
+
147
+ context.output.info(`Utility "${name}" registered successfully.`);
148
+ context.output.info(`Run it with: dev util run ${name}`);
149
+ }
150
+
151
+ module.exports = { meta, run };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Util service registration.
3
+ * Complex utility functions (platform-aware).
4
+ */
5
+ module.exports = {
6
+ name: 'util',
7
+ description: 'Complex utility functions (platform-aware)',
8
+ commands: {
9
+ run: () => require('./run'),
10
+ list: () => require('./list'),
11
+ show: () => require('./show'),
12
+ add: () => require('./add'),
13
+ remove: () => require('./remove'),
14
+ }
15
+ };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const meta = {
8
+ description: 'List available utilities. Use flags to filter by source.',
9
+ arguments: [],
10
+ flags: [
11
+ { name: 'built-in', description: 'Show only built-in utilities' },
12
+ { name: 'custom', description: 'Show only user-added utilities' },
13
+ ],
14
+ };
15
+
16
+ /**
17
+ * Load custom utilities from ~/.devutils/utils/.
18
+ * Checks for a registry.json first; falls back to scanning directories.
19
+ *
20
+ * @returns {Array<object>} Array of utility metadata objects.
21
+ */
22
+ function loadCustomUtilities() {
23
+ const customDir = path.join(os.homedir(), '.devutils', 'utils');
24
+ if (!fs.existsSync(customDir)) {
25
+ return [];
26
+ }
27
+
28
+ const registryPath = path.join(customDir, 'registry.json');
29
+ if (fs.existsSync(registryPath)) {
30
+ try {
31
+ const data = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
32
+ return (data.utilities || []).map(u => ({ ...u, type: 'custom' }));
33
+ } catch {
34
+ // Fall through to directory scan
35
+ }
36
+ }
37
+
38
+ // Fallback: scan directories for index.js with meta exports
39
+ const entries = fs.readdirSync(customDir, { withFileTypes: true });
40
+ const utilities = [];
41
+
42
+ for (const entry of entries) {
43
+ if (entry.name === 'registry.json') continue;
44
+
45
+ let indexPath;
46
+ if (entry.isDirectory()) {
47
+ indexPath = path.join(customDir, entry.name, 'index.js');
48
+ } else if (entry.isFile() && entry.name.endsWith('.js')) {
49
+ indexPath = path.join(customDir, entry.name);
50
+ }
51
+
52
+ if (indexPath && fs.existsSync(indexPath)) {
53
+ try {
54
+ const mod = require(indexPath);
55
+ if (mod.meta) {
56
+ utilities.push({
57
+ name: mod.meta.name || entry.name.replace(/\.js$/, ''),
58
+ description: mod.meta.description || '',
59
+ type: 'custom',
60
+ });
61
+ }
62
+ } catch {
63
+ // Skip utilities that fail to load
64
+ }
65
+ }
66
+ }
67
+
68
+ return utilities;
69
+ }
70
+
71
+ async function run(args, context) {
72
+ const builtInRegistry = require('../../utils/registry.json');
73
+ const results = [];
74
+
75
+ if (!args.flags.custom) {
76
+ for (const util of builtInRegistry.utilities || []) {
77
+ results.push({ Name: util.name, Description: util.description, Type: 'built-in' });
78
+ }
79
+ }
80
+
81
+ if (!args.flags['built-in']) {
82
+ const custom = loadCustomUtilities();
83
+ for (const util of custom) {
84
+ results.push({ Name: util.name, Description: util.description || '', Type: 'custom' });
85
+ }
86
+ }
87
+
88
+ if (results.length === 0) {
89
+ context.output.info('No utilities found.');
90
+ return;
91
+ }
92
+
93
+ context.output.out(results);
94
+ context.output.info(`\n${results.length} ${results.length === 1 ? 'utility' : 'utilities'} available.`);
95
+ }
96
+
97
+ module.exports = { meta, run };
@@ -0,0 +1,76 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const meta = {
8
+ description: 'Unregister a custom utility. Built-in utilities cannot be removed.',
9
+ arguments: [
10
+ { name: 'name', required: true, description: 'Name of the custom utility to remove' },
11
+ ],
12
+ flags: [
13
+ { name: 'confirm', description: 'Skip the confirmation prompt' },
14
+ ],
15
+ };
16
+
17
+ async function run(args, context) {
18
+ const name = args.positional[0];
19
+ if (!name) {
20
+ context.errors.throwError(400, 'Usage: dev util remove <name>', 'util');
21
+ return;
22
+ }
23
+
24
+ // Reject removal of built-in utilities
25
+ const builtInRegistry = require('../../utils/registry.json');
26
+ const isBuiltIn = (builtInRegistry.utilities || []).some(u => u.name === name);
27
+ if (isBuiltIn) {
28
+ context.output.error(`"${name}" is a built-in utility and cannot be removed.`);
29
+ return;
30
+ }
31
+
32
+ const customDir = path.join(os.homedir(), '.devutils', 'utils');
33
+ const folderPath = path.join(customDir, name);
34
+ const filePath = path.join(customDir, name + '.js');
35
+
36
+ const exists = fs.existsSync(folderPath) || fs.existsSync(filePath);
37
+ if (!exists) {
38
+ context.output.error(`Custom utility "${name}" not found.`);
39
+ return;
40
+ }
41
+
42
+ // Ask for confirmation unless --confirm is set
43
+ if (!args.flags.confirm) {
44
+ const ok = await context.prompt.confirm(
45
+ `Remove custom utility "${name}"? This will delete the files.`,
46
+ false
47
+ );
48
+ if (!ok) {
49
+ context.output.info('Cancelled.');
50
+ return;
51
+ }
52
+ }
53
+
54
+ // Delete the files
55
+ if (fs.existsSync(folderPath)) {
56
+ fs.rmSync(folderPath, { recursive: true, force: true });
57
+ } else if (fs.existsSync(filePath)) {
58
+ fs.unlinkSync(filePath);
59
+ }
60
+
61
+ // Update the custom registry
62
+ const registryPath = path.join(customDir, 'registry.json');
63
+ if (fs.existsSync(registryPath)) {
64
+ try {
65
+ const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
66
+ registry.utilities = (registry.utilities || []).filter(u => u.name !== name);
67
+ fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n');
68
+ } catch {
69
+ // Registry corrupted, skip update
70
+ }
71
+ }
72
+
73
+ context.output.info(`Utility "${name}" removed.`);
74
+ }
75
+
76
+ module.exports = { meta, run };
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const builtInDir = path.join(__dirname, '..', '..', 'utils');
8
+ const customDir = path.join(os.homedir(), '.devutils', 'utils');
9
+
10
+ const meta = {
11
+ description: 'Execute a utility by name. Extra arguments are passed through to the utility.',
12
+ arguments: [
13
+ { name: 'name', required: true, description: 'Name of the utility to run' },
14
+ ],
15
+ flags: [],
16
+ };
17
+
18
+ /**
19
+ * Find a utility by name, checking built-in first, then custom.
20
+ * Supports both folder-based (name/index.js) and single-file (name.js) utilities.
21
+ *
22
+ * @param {string} name - The utility name.
23
+ * @returns {{ path: string, type: string }|null} The utility location, or null if not found.
24
+ */
25
+ function findUtility(name) {
26
+ // Check built-in: folder with index.js
27
+ const builtInFolder = path.join(builtInDir, name, 'index.js');
28
+ if (fs.existsSync(builtInFolder)) {
29
+ return { path: builtInFolder, type: 'built-in' };
30
+ }
31
+
32
+ // Check built-in: single file
33
+ const builtInFile = path.join(builtInDir, name + '.js');
34
+ if (fs.existsSync(builtInFile)) {
35
+ return { path: builtInFile, type: 'built-in' };
36
+ }
37
+
38
+ // Check custom: folder with index.js
39
+ const customFolder = path.join(customDir, name, 'index.js');
40
+ if (fs.existsSync(customFolder)) {
41
+ return { path: customFolder, type: 'custom' };
42
+ }
43
+
44
+ // Check custom: single file
45
+ const customFile = path.join(customDir, name + '.js');
46
+ if (fs.existsSync(customFile)) {
47
+ return { path: customFile, type: 'custom' };
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ async function run(args, context) {
54
+ const name = args.positional[0];
55
+ if (!name) {
56
+ context.errors.throwError(400, 'Usage: dev util run <name> [args...]', 'util');
57
+ return;
58
+ }
59
+
60
+ const utilArgs = args.positional.slice(1);
61
+
62
+ const util = findUtility(name);
63
+ if (!util) {
64
+ context.output.error(`Utility "${name}" not found.`);
65
+ context.output.info('Run "dev util list" to see available utilities.');
66
+ return;
67
+ }
68
+
69
+ const utilModule = require(util.path);
70
+
71
+ if (typeof utilModule.run !== 'function') {
72
+ context.output.error(`Utility "${name}" does not export a run() function.`);
73
+ return;
74
+ }
75
+
76
+ await utilModule.run(utilArgs, context);
77
+ }
78
+
79
+ module.exports = { meta, run, findUtility };
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ const meta = {
4
+ description: 'Show details, supported platforms, and accepted arguments for a utility.',
5
+ arguments: [
6
+ { name: 'name', required: true, description: 'Name of the utility to inspect' },
7
+ ],
8
+ flags: [],
9
+ };
10
+
11
+ async function run(args, context) {
12
+ const name = args.positional[0];
13
+ if (!name) {
14
+ context.errors.throwError(400, 'Usage: dev util show <name>', 'util');
15
+ return;
16
+ }
17
+
18
+ const { findUtility } = require('./run');
19
+ const util = findUtility(name);
20
+
21
+ if (!util) {
22
+ context.errors.throwError(404, `Utility "${name}" not found. Run "dev util list" to see available utilities.`, 'util');
23
+ return;
24
+ }
25
+
26
+ const utilModule = require(util.path);
27
+ const utilMeta = utilModule.meta || {};
28
+
29
+ const info = {
30
+ name: utilMeta.name || name,
31
+ description: utilMeta.description || '(no description)',
32
+ type: util.type,
33
+ platforms: utilMeta.platforms || [],
34
+ arguments: utilMeta.arguments || [],
35
+ flags: utilMeta.flags || [],
36
+ };
37
+
38
+ if (context.flags.format === 'json') {
39
+ context.output.out(info);
40
+ return;
41
+ }
42
+
43
+ context.output.info(`Utility: ${info.name}`);
44
+ context.output.info('');
45
+ context.output.info(` Description: ${info.description}`);
46
+ context.output.info(` Type: ${info.type}`);
47
+ context.output.info(` Platforms: ${info.platforms.length > 0 ? info.platforms.join(', ') : '(all)'}`);
48
+
49
+ if (info.arguments.length > 0) {
50
+ context.output.info('');
51
+ context.output.info(' Arguments:');
52
+ for (const arg of info.arguments) {
53
+ const req = arg.required ? '(required)' : '(optional)';
54
+ context.output.info(` ${arg.name} ${req} - ${arg.description || ''}`);
55
+ }
56
+ }
57
+
58
+ if (info.flags.length > 0) {
59
+ context.output.info('');
60
+ context.output.info(' Flags:');
61
+ for (const flag of info.flags) {
62
+ context.output.info(` --${flag.name} - ${flag.description || ''}`);
63
+ }
64
+ }
65
+ }
66
+
67
+ module.exports = { meta, run };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * version command.
5
+ * Reads the version string from package.json and prints it.
6
+ * When format is json, outputs { version: "x.y.z" }.
7
+ * When table/tty, outputs just the version string.
8
+ */
9
+
10
+ const meta = {
11
+ description: 'Show the current installed version',
12
+ arguments: [],
13
+ flags: []
14
+ };
15
+
16
+ /**
17
+ * Prints the current DevUtils CLI version.
18
+ *
19
+ * @param {object} args - Parsed command arguments (none expected).
20
+ * @param {object} context - The CLI context object with output, flags, etc.
21
+ */
22
+ async function run(args, context) {
23
+ const pkg = require('../../package.json');
24
+ const version = pkg.version;
25
+
26
+ if (context.flags.format === 'json') {
27
+ context.output.out({ version });
28
+ } else {
29
+ context.output.info(version);
30
+ }
31
+ }
32
+
33
+ module.exports = { meta, run };