@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,73 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const shell = require('../../lib/shell');
5
+ const loader = require('../../api/loader');
6
+
7
+ const meta = {
8
+ description: 'Remove an installed API plugin',
9
+ arguments: [
10
+ { name: 'name', description: 'Plugin name to remove', required: true }
11
+ ],
12
+ flags: [
13
+ { name: 'confirm', type: 'boolean', description: 'Skip confirmation prompt' }
14
+ ]
15
+ };
16
+
17
+ /**
18
+ * Removes an installed API plugin.
19
+ * Runs npm uninstall, removes the entry from plugins.json,
20
+ * and asks for confirmation unless --confirm is passed.
21
+ *
22
+ * @param {object} args - Parsed CLI arguments { positional, flags }.
23
+ * @param {object} context - CLI context { output, errors, prompt }.
24
+ */
25
+ async function run(args, context) {
26
+ const pluginName = args.positional[0];
27
+
28
+ if (!pluginName) {
29
+ context.errors.throwError(400, 'Missing required argument: <name>. Example: dev api disable gmail', 'api');
30
+ return;
31
+ }
32
+
33
+ // Check if the plugin is installed
34
+ const plugins = loader.readPluginsJson();
35
+ const entry = plugins[pluginName];
36
+
37
+ if (!entry) {
38
+ context.output.info(`Plugin "${pluginName}" is not installed.`);
39
+ return;
40
+ }
41
+
42
+ // Ask for confirmation unless --confirm is passed
43
+ if (!args.flags.confirm) {
44
+ const ok = await context.prompt.confirm(
45
+ `Remove plugin "${pluginName}" (${entry.package})?`,
46
+ false
47
+ );
48
+ if (!ok) {
49
+ context.output.info('Cancelled.');
50
+ return;
51
+ }
52
+ }
53
+
54
+ // Run npm uninstall
55
+ const result = await shell.exec(`npm uninstall ${entry.package}`, { cwd: loader.PLUGINS_DIR });
56
+
57
+ if (result.exitCode !== 0) {
58
+ context.errors.throwError(
59
+ 500,
60
+ `Failed to uninstall plugin "${pluginName}".\n${result.stderr || result.stdout}`,
61
+ 'api'
62
+ );
63
+ return;
64
+ }
65
+
66
+ // Remove the entry from plugins.json
67
+ delete plugins[pluginName];
68
+ fs.writeFileSync(loader.PLUGINS_FILE, JSON.stringify(plugins, null, 2) + '\n');
69
+
70
+ context.output.info(`Plugin "${pluginName}" removed.`);
71
+ }
72
+
73
+ module.exports = { meta, run };
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const shell = require('../../lib/shell');
6
+ const loader = require('../../api/loader');
7
+
8
+ const meta = {
9
+ description: 'Install an API plugin from npm or a git repository',
10
+ arguments: [
11
+ { name: 'name', description: 'Plugin name or full package name', required: true }
12
+ ],
13
+ flags: [
14
+ { name: 'source', type: 'string', description: 'Install source type: "npm" or "git" (default: npm)' },
15
+ { name: 'url', type: 'string', description: 'Package name or git URL when using a non-registry plugin' }
16
+ ]
17
+ };
18
+
19
+ /**
20
+ * Installs an API plugin from npm or a git repository.
21
+ * Resolves short names (like "gmail") through the registry, or accepts
22
+ * full package names (starting with "@") and git URLs directly.
23
+ *
24
+ * @param {object} args - Parsed CLI arguments { positional, flags }.
25
+ * @param {object} context - CLI context { output, errors, prompt }.
26
+ */
27
+ async function run(args, context) {
28
+ const pluginName = args.positional[0];
29
+
30
+ if (!pluginName) {
31
+ context.errors.throwError(400, 'Missing required argument: <name>. Example: dev api enable gmail', 'api');
32
+ return;
33
+ }
34
+
35
+ // Resolve the package name from the registry or direct input
36
+ const registry = loader.getRegistryPlugins();
37
+ const registryEntry = registry.find(entry => entry.name === pluginName);
38
+ let packageName;
39
+
40
+ if (registryEntry) {
41
+ // Found in registry - use the registry package name
42
+ packageName = registryEntry.package;
43
+ } else if (args.flags.url) {
44
+ // Not in registry but user provided a URL/package name
45
+ packageName = args.flags.url;
46
+ } else if (pluginName.startsWith('@') || pluginName.includes('/')) {
47
+ // Looks like a scoped npm package or git URL - use directly
48
+ packageName = pluginName;
49
+ } else {
50
+ context.errors.throwError(
51
+ 404,
52
+ `Plugin "${pluginName}" not found in the registry. Use --source git --url <git-url> to install from a git repository, or --url <package-name> to install a non-registry npm package.`,
53
+ 'api'
54
+ );
55
+ return;
56
+ }
57
+
58
+ // Check if already installed
59
+ const installed = loader.readPluginsJson();
60
+ if (installed[pluginName]) {
61
+ context.output.info(
62
+ `Plugin "${pluginName}" is already installed (version ${installed[pluginName].version || 'unknown'}). Use "dev api update ${pluginName}" to update.`
63
+ );
64
+ return;
65
+ }
66
+
67
+ // Ensure the plugins directory exists with a package.json
68
+ const pluginsDir = loader.PLUGINS_DIR;
69
+ const pkgJsonPath = path.join(pluginsDir, 'package.json');
70
+
71
+ if (!fs.existsSync(pkgJsonPath)) {
72
+ fs.mkdirSync(pluginsDir, { recursive: true });
73
+ fs.writeFileSync(pkgJsonPath, JSON.stringify({
74
+ name: 'devutils-plugins',
75
+ version: '1.0.0',
76
+ private: true,
77
+ description: 'DevUtils CLI plugin packages'
78
+ }, null, 2) + '\n');
79
+ }
80
+
81
+ // Determine the install target
82
+ const installTarget = args.flags.url || packageName;
83
+
84
+ // Run npm install
85
+ context.output.info(`Installing ${pluginName} (${installTarget})...`);
86
+ const result = await shell.exec(`npm install ${installTarget}`, { cwd: pluginsDir });
87
+
88
+ if (result.exitCode !== 0) {
89
+ context.errors.throwError(
90
+ 500,
91
+ `Failed to install plugin "${pluginName}".\n${result.stderr || result.stdout}`,
92
+ 'api'
93
+ );
94
+ return;
95
+ }
96
+
97
+ // Read the installed version from the plugin's package.json
98
+ let installedVersion = 'unknown';
99
+ try {
100
+ const pluginPkgPath = path.join(pluginsDir, 'node_modules', packageName, 'package.json');
101
+ const pluginPkg = JSON.parse(fs.readFileSync(pluginPkgPath, 'utf8'));
102
+ installedVersion = pluginPkg.version || 'unknown';
103
+ } catch (err) {
104
+ // Could not read version, continue with 'unknown'
105
+ }
106
+
107
+ // Update plugins.json
108
+ const plugins = loader.readPluginsJson();
109
+ const sourceType = args.flags.source || 'npm';
110
+ plugins[pluginName] = {
111
+ package: packageName,
112
+ version: installedVersion,
113
+ source: sourceType,
114
+ installedAt: new Date().toISOString()
115
+ };
116
+
117
+ // Only store the URL if one was explicitly provided
118
+ if (args.flags.url) {
119
+ plugins[pluginName].url = args.flags.url;
120
+ }
121
+
122
+ // Ensure the parent directory exists for plugins.json
123
+ const pluginsFileDir = path.dirname(loader.PLUGINS_FILE);
124
+ if (!fs.existsSync(pluginsFileDir)) {
125
+ fs.mkdirSync(pluginsFileDir, { recursive: true });
126
+ }
127
+
128
+ fs.writeFileSync(loader.PLUGINS_FILE, JSON.stringify(plugins, null, 2) + '\n');
129
+
130
+ // Validate the plugin contract
131
+ const loadResult = loader.loadPlugin(pluginName);
132
+ if (loadResult.error) {
133
+ context.output.info(
134
+ `Warning: Plugin installed but does not follow the expected contract. It may not work correctly.\n${loadResult.message}`
135
+ );
136
+ }
137
+
138
+ // Print success
139
+ context.output.info(`Plugin "${pluginName}" installed (v${installedVersion}).`);
140
+
141
+ // Show auth hint if available from the registry entry
142
+ const authService = registryEntry ? registryEntry.auth : null;
143
+ if (authService) {
144
+ context.output.info(`This plugin requires "${authService}" authentication. Run "dev auth login ${authService}" if you haven't already.`);
145
+ }
146
+ }
147
+
148
+ module.exports = { meta, run };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * API service registration.
3
+ * API plugin system — manages plugin installation, removal, and updates.
4
+ * Plugin commands are loaded at runtime from ~/.devutils/plugins/.
5
+ */
6
+ module.exports = {
7
+ name: 'api',
8
+ description: 'API plugin system',
9
+ commands: {
10
+ list: () => require('./list'),
11
+ enable: () => require('./enable'),
12
+ disable: () => require('./disable'),
13
+ update: () => require('./update'),
14
+ }
15
+ };
@@ -0,0 +1,66 @@
1
+ 'use strict';
2
+
3
+ const loader = require('../../api/loader');
4
+
5
+ const meta = {
6
+ description: 'List installed and available API plugins',
7
+ arguments: [],
8
+ flags: [
9
+ { name: 'installed', type: 'boolean', description: 'Show only installed plugins' },
10
+ { name: 'available', type: 'boolean', description: 'Show only available (not installed) plugins' }
11
+ ]
12
+ };
13
+
14
+ /**
15
+ * Lists installed and/or available API plugins.
16
+ * Shows both by default, or filters with --installed / --available flags.
17
+ *
18
+ * @param {object} args - Parsed CLI arguments { positional, flags }.
19
+ * @param {object} context - CLI context { output, errors }.
20
+ */
21
+ async function run(args, context) {
22
+ const installed = loader.getInstalledPlugins();
23
+ const registry = loader.getRegistryPlugins();
24
+ const installedNames = Object.keys(installed);
25
+
26
+ // Build the installed list
27
+ const installedList = installedNames.map(name => ({
28
+ name,
29
+ package: installed[name].package,
30
+ version: installed[name].version || 'unknown',
31
+ source: installed[name].source || 'npm',
32
+ installedAt: installed[name].installedAt || ''
33
+ }));
34
+
35
+ // Build the available list (registry entries that are not installed)
36
+ const availableList = registry
37
+ .filter(entry => !installed[entry.name])
38
+ .map(entry => ({
39
+ name: entry.name,
40
+ package: entry.package,
41
+ description: entry.description
42
+ }));
43
+
44
+ // Apply filters
45
+ const showInstalled = args.flags.installed || !args.flags.available;
46
+ const showAvailable = args.flags.available || !args.flags.installed;
47
+
48
+ const result = {};
49
+
50
+ if (showInstalled) {
51
+ result.installed = installedList;
52
+ }
53
+ if (showAvailable) {
54
+ result.available = availableList;
55
+ }
56
+
57
+ // If only installed was requested and nothing is installed, give a hint
58
+ if (args.flags.installed && installedList.length === 0) {
59
+ context.output.info('No API plugins installed. Run "dev api enable <name>" to install one.');
60
+ return;
61
+ }
62
+
63
+ context.output.out(result);
64
+ }
65
+
66
+ module.exports = { meta, run };
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const shell = require('../../lib/shell');
6
+ const loader = require('../../api/loader');
7
+
8
+ const meta = {
9
+ description: 'Update an installed API plugin to the latest version',
10
+ arguments: [
11
+ { name: 'name', description: 'Plugin name to update', required: true }
12
+ ],
13
+ flags: []
14
+ };
15
+
16
+ /**
17
+ * Updates an installed API plugin to the latest version.
18
+ * For npm-sourced plugins, runs npm update. For git-sourced plugins,
19
+ * re-runs npm install with the original git URL.
20
+ *
21
+ * @param {object} args - Parsed CLI arguments { positional, flags }.
22
+ * @param {object} context - CLI context { output, errors }.
23
+ */
24
+ async function run(args, context) {
25
+ const pluginName = args.positional[0];
26
+
27
+ if (!pluginName) {
28
+ context.errors.throwError(400, 'Missing required argument: <name>. Example: dev api update gmail', 'api');
29
+ return;
30
+ }
31
+
32
+ // Check if the plugin is installed
33
+ const plugins = loader.readPluginsJson();
34
+ const entry = plugins[pluginName];
35
+
36
+ if (!entry) {
37
+ context.output.info(`Plugin "${pluginName}" is not installed.`);
38
+ return;
39
+ }
40
+
41
+ const previousVersion = entry.version || 'unknown';
42
+ const pluginsDir = loader.PLUGINS_DIR;
43
+
44
+ // Determine update strategy based on source type
45
+ let result;
46
+ if (entry.source === 'git' && entry.url) {
47
+ // Git-sourced plugins: re-install from the original URL
48
+ context.output.info(`Updating ${pluginName} from git...`);
49
+ result = await shell.exec(`npm install ${entry.url}`, { cwd: pluginsDir });
50
+ } else {
51
+ // npm-sourced plugins: use npm update
52
+ context.output.info(`Updating ${pluginName}...`);
53
+ result = await shell.exec(`npm update ${entry.package}`, { cwd: pluginsDir });
54
+ }
55
+
56
+ if (result.exitCode !== 0) {
57
+ context.errors.throwError(
58
+ 500,
59
+ `Failed to update plugin "${pluginName}".\n${result.stderr || result.stdout}`,
60
+ 'api'
61
+ );
62
+ return;
63
+ }
64
+
65
+ // Read the new version from the plugin's package.json
66
+ let newVersion = 'unknown';
67
+ try {
68
+ const pluginPkgPath = path.join(pluginsDir, 'node_modules', entry.package, 'package.json');
69
+ const pluginPkg = JSON.parse(fs.readFileSync(pluginPkgPath, 'utf8'));
70
+ newVersion = pluginPkg.version || 'unknown';
71
+ } catch (err) {
72
+ // Could not read version
73
+ }
74
+
75
+ // Update the version in plugins.json
76
+ plugins[pluginName].version = newVersion;
77
+ fs.writeFileSync(loader.PLUGINS_FILE, JSON.stringify(plugins, null, 2) + '\n');
78
+
79
+ // Report the result
80
+ if (newVersion !== previousVersion) {
81
+ context.output.info(`Updated ${pluginName} from ${previousVersion} to ${newVersion}.`);
82
+ } else {
83
+ context.output.info(`${pluginName} is already at the latest version (${newVersion}).`);
84
+ }
85
+ }
86
+
87
+ module.exports = { meta, run };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Auth service registration.
3
+ * OAuth and credential management.
4
+ */
5
+ module.exports = {
6
+ name: 'auth',
7
+ description: 'OAuth and credential management',
8
+ commands: {
9
+ login: () => require('./login'),
10
+ logout: () => require('./logout'),
11
+ list: () => require('./list'),
12
+ status: () => require('./status'),
13
+ refresh: () => require('./refresh'),
14
+ }
15
+ };
@@ -0,0 +1,49 @@
1
+ 'use strict';
2
+
3
+ const { AUTH_SERVICES, readCredential, getTokenStatus } = require('./services');
4
+
5
+ const meta = {
6
+ description: 'List all connected services and their token status',
7
+ arguments: [],
8
+ flags: []
9
+ };
10
+
11
+ /**
12
+ * Run the auth list command.
13
+ * Scans all known services from AUTH_SERVICES and shows their auth status.
14
+ * Shows 'valid', 'expired', or 'missing' for each service.
15
+ *
16
+ * @param {object} args - Parsed CLI arguments (positional, flags).
17
+ * @param {object} context - CLI context (output, prompt, errors).
18
+ */
19
+ async function run(args, context) {
20
+ const services = Object.keys(AUTH_SERVICES);
21
+ const results = [];
22
+
23
+ for (const service of services) {
24
+ const config = AUTH_SERVICES[service];
25
+ const credential = readCredential(service);
26
+ const status = getTokenStatus(credential);
27
+
28
+ results.push({
29
+ service: service,
30
+ type: config.type,
31
+ status: status,
32
+ authenticatedAt: credential && credential.authenticatedAt ? credential.authenticatedAt : '-'
33
+ });
34
+ }
35
+
36
+ // Check if any services are connected
37
+ const hasConnected = results.some(r => r.status !== 'missing');
38
+
39
+ if (!hasConnected) {
40
+ context.output.info('No services authenticated. Run "dev auth login <service>" to connect.');
41
+ context.output.info('');
42
+ context.output.info('Available services: ' + services.join(', '));
43
+ return;
44
+ }
45
+
46
+ context.output.out(results);
47
+ }
48
+
49
+ module.exports = { meta, run };