@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
package/README.md CHANGED
@@ -1,64 +1,255 @@
1
1
  # DevUtils CLI
2
2
 
3
- > **Rebuild in Progress (v0.0.19)** This project is being restructured from the ground up. The previous version (v0.0.18) tried to do too much too fast. The new approach is config-driven, user-driven, and machine-aware. If you're looking for the old code, it's been preserved in the `_rebuild/` directory.
3
+ > **Work in Progress** -- This project is under active development and has not been fully tested in real-world environments. Commands may change, break, or behave unexpectedly. Use at your own risk. The stable, production-ready release will ship as **v1.0**. Until then, versions in the `0.x` range should be considered pre-release.
4
4
 
5
- ## What Happened
5
+ A config-driven CLI toolkit for bootstrapping and managing development environments across any machine.
6
6
 
7
- The original DevUtils CLI was built as a rigid, opinionated toolkit. It assumed a specific workflow, shipped dozens of global scripts, and tried to be everything to everyone out of the gate. After real-world usage and feedback, it became clear that this approach wouldn't hold up. It was too prescriptive, too fragile across environments, and too hard for users to adapt to their own needs.
7
+ DevUtils replaces scattered dotfiles, setup scripts, and manual configuration with a single `dev` command. You tell it what you need, and it handles the rest -- whether you're setting up a new laptop, syncing git identities across machines, or managing tool installations.
8
8
 
9
- ## What's Changing
9
+ ## Installation
10
10
 
11
- The new version takes a fundamentally different approach:
11
+ ```bash
12
+ npm install -g @fredlackey/devutils
13
+ ```
12
14
 
13
- ### Config-Based, Not Opinion-Based
15
+ Requires Node.js 18 or later.
14
16
 
15
- Instead of shipping a fixed set of behaviors, DevUtils will be driven by user configuration. You tell it what matters to you, and it adapts.
17
+ ## Quick Start
16
18
 
17
- ### User Onboarding
19
+ Once installed, the `dev` command is available globally:
18
20
 
19
- The first run walks you through a lightweight onboarding process. It learns who you are, what tools you care about, and how your machine is set up. No assumptions.
21
+ ```bash
22
+ dev help # Show all available services and commands
23
+ dev version # Print the current version
24
+ dev config init # Walk through first-time setup
25
+ dev status # Check the health of your environment
26
+ dev ignore node # Add Node.js patterns to .gitignore
27
+ dev tools list # See what tools are installed
28
+ dev identity list # List your configured git identities
29
+ dev util list # Browse available utility functions
30
+ ```
20
31
 
21
- ### Machine-Aware Profiles
32
+ ## Command Reference
22
33
 
23
- Configuration is scoped to the machine you're on. A laptop, a server, and a VM can each have their own rules without conflicting.
34
+ DevUtils organizes commands by service. The general pattern is:
24
35
 
25
- ### Rule-Based Behavior
36
+ ```
37
+ dev <service> <method> [arguments] [flags]
38
+ ```
26
39
 
27
- Users define rules for folders, tools, and workflows. DevUtils enforces those rules rather than imposing its own. For example:
28
- - "This folder always uses this git identity"
29
- - "This machine should have these tools installed"
30
- - "Run these checks when I open a new terminal"
40
+ ### Services
31
41
 
32
- ### Temporary Workspace
42
+ #### config -- User configuration and onboarding
33
43
 
34
- DevUtils sets up a managed workspace inside the user's home directory for staging, caching, and intermediate state. Nothing touches system-level paths unless you ask for it.
44
+ | Method | Description |
45
+ |----------|----------------------------------------------|
46
+ | `init` | Run first-time setup and create config files |
47
+ | `show` | Display the current configuration |
48
+ | `get` | Read a specific config value |
49
+ | `set` | Update a specific config value |
50
+ | `reset` | Reset configuration to defaults |
51
+ | `export` | Export config to a file |
52
+ | `import` | Import config from a file |
35
53
 
36
- ## Installation
54
+ #### machine -- Machine profiles and detection
55
+
56
+ | Method | Description |
57
+ |----------|-------------------------------------|
58
+ | `detect` | Auto-detect the current machine |
59
+ | `show` | Show the active machine profile |
60
+ | `set` | Set a machine profile value |
61
+ | `list` | List all known machine profiles |
62
+
63
+ #### identity -- Git identities, SSH keys, GPG signing
64
+
65
+ | Method | Description |
66
+ |----------|---------------------------------------------|
67
+ | `add` | Register a new git identity |
68
+ | `remove` | Remove a registered identity |
69
+ | `list` | List all identities |
70
+ | `show` | Show details for a specific identity |
71
+ | `link` | Link an identity to a folder or repo |
72
+ | `unlink` | Remove a folder/repo identity link |
73
+ | `sync` | Sync identity configs to git and SSH |
74
+
75
+ #### tools -- Tool installation and management
76
+
77
+ | Method | Description |
78
+ |-----------|------------------------------------------|
79
+ | `install` | Install a tool using the platform's package manager |
80
+ | `check` | Check if a tool is installed |
81
+ | `list` | List available or installed tools |
82
+ | `search` | Search for tools by name |
83
+
84
+ #### ignore -- .gitignore pattern management
85
+
86
+ | Method | Description |
87
+ |----------|----------------------------------------------|
88
+ | `add` | Add technology patterns to .gitignore |
89
+ | `remove` | Remove a technology's patterns |
90
+ | `list` | List available technologies |
91
+ | `show` | Show patterns for a specific technology |
92
+
93
+ #### util -- Utility functions
94
+
95
+ | Method | Description |
96
+ |----------|---------------------------------------|
97
+ | `add` | Register a custom utility |
98
+ | `remove` | Unregister a utility |
99
+ | `list` | List all available utilities |
100
+ | `show` | Show details for a specific utility |
101
+ | `run` | Execute a utility |
102
+
103
+ #### alias -- Shorthand bin entries
104
+
105
+ | Method | Description |
106
+ |----------|------------------------------------------|
107
+ | `add` | Create a new alias |
108
+ | `remove` | Delete an alias |
109
+ | `list` | List all registered aliases |
110
+ | `sync` | Regenerate alias wrapper scripts |
111
+
112
+ #### auth -- OAuth and credential management
113
+
114
+ | Method | Description |
115
+ |-----------|--------------------------------------|
116
+ | `login` | Authenticate with a service |
117
+ | `logout` | Revoke credentials for a service |
118
+ | `list` | List authenticated services |
119
+ | `status` | Show current auth status |
120
+ | `refresh` | Refresh an expired token |
121
+
122
+ #### api -- API plugin system
123
+
124
+ | Method | Description |
125
+ |-----------|-------------------------------------|
126
+ | `list` | List installed API plugins |
127
+ | `enable` | Enable a plugin |
128
+ | `disable` | Disable a plugin |
129
+ | `update` | Update a plugin to latest version |
130
+
131
+ #### ai -- AI coding assistant launcher
132
+
133
+ | Method | Description |
134
+ |------------|-----------------------------------------|
135
+ | `launch` | Start an AI coding session |
136
+ | `resume` | Resume a previous session |
137
+ | `list` | List configured AI tools |
138
+ | `sessions` | List past sessions |
139
+ | `show` | Show details for a session or AI tool |
140
+ | `set` | Update AI tool settings |
141
+
142
+ #### search -- Markdown search
143
+
144
+ | Method | Description |
145
+ |---------------|------------------------------------------|
146
+ | `query` | Run a general search |
147
+ | `keyword` | Search by keyword |
148
+ | `semantic` | Search by meaning (requires AI plugin) |
149
+ | `get` | Retrieve a specific search result |
150
+ | `collections` | List indexed collections |
151
+ | `index` | Build or rebuild the search index |
152
+ | `status` | Show indexing status |
153
+
154
+ ### Top-Level Commands
155
+
156
+ These commands don't belong to a service and are called directly:
157
+
158
+ | Command | Description |
159
+ |-----------|------------------------------------|
160
+ | `status` | Overall health check |
161
+ | `version` | Show current version |
162
+ | `help` | Show the help message |
163
+ | `schema` | Introspect available commands |
164
+ | `update` | Update DevUtils to latest version |
165
+
166
+ ### Global Flags
167
+
168
+ These flags work with any command:
169
+
170
+ | Flag | Description |
171
+ |-----------------------------------|------------------------------------|
172
+ | `--format <json\|table\|yaml\|csv>` | Set the output format |
173
+ | `--dry-run` | Show what would happen without doing it |
174
+ | `--verbose` | Increase output detail |
175
+ | `--quiet` | Suppress non-essential output |
176
+ | `--json <data>` | Pass structured input as JSON |
177
+ | `--help`, `-h` | Show help |
178
+ | `--version`, `-v` | Show version |
179
+
180
+ ## API Plugin System
181
+
182
+ DevUtils doesn't bundle API integrations directly. Instead, API wrappers are installed as separate plugin packages and managed through the `dev api` service.
183
+
184
+ Plugins live in `~/.devutils/plugins/` and are registered in `~/.devutils/plugins.json`. Each plugin is a standard npm package that exports a defined interface. This keeps the core CLI small and lets you add only the integrations you actually use.
37
185
 
38
186
  ```bash
39
- npm install -g @fredlackey/devutils
187
+ dev api list # See what's installed
188
+ dev api enable <plugin> # Enable a plugin
189
+ dev api disable <plugin> # Disable a plugin
190
+ dev api update <plugin> # Update to latest version
40
191
  ```
41
192
 
42
- > The CLI is published but actively being rebuilt. Expect breaking changes until v0.1.0.
193
+ ## Configuration
194
+
195
+ All user data lives in `~/.devutils/`, created during `dev config init`:
196
+
197
+ - `config.json` -- User preferences, profile name, backup location
198
+ - `aliases.json` -- Registered alias mappings
199
+ - `ai.json` -- AI tool configurations
200
+ - `plugins.json` -- Installed API plugin registry
201
+ - `machines/` -- Machine profiles
202
+ - `auth/` -- OAuth tokens and API credentials
203
+ - `plugins/` -- Installed API plugin packages
204
+ - `utils/` -- User-added custom utilities
205
+ - `bin/` -- Generated alias wrapper scripts (added to PATH)
206
+ - `cache/` -- Temporary data
207
+
208
+ ## Supported Platforms
209
+
210
+ | Platform | Package Manager |
211
+ |-------------------|---------------------|
212
+ | macOS | Homebrew |
213
+ | Ubuntu | APT, Snap |
214
+ | Raspberry Pi OS | APT, Snap |
215
+ | Amazon Linux | DNF, YUM |
216
+ | Windows | Chocolatey, winget |
217
+ | Git Bash | Manual / Portable |
218
+
219
+ ## Current Status
43
220
 
44
- ## Previous Version
221
+ DevUtils is in **pre-release** (`0.1.x`). The core framework, command routing, and service structure are in place. Basic smoke tests pass on Ubuntu 24.04 in Docker, but deeper integration testing -- real Git operations, SSH key workflows, GitHub auth, tool installation, and interactive prompts -- has not been completed yet.
45
222
 
46
- The v0.0.18 codebase (commands, scripts, installers, utilities) has been moved to `_rebuild/` and is preserved for reference. Useful patterns and utilities will be pulled forward into the new architecture as needed.
223
+ What's working:
224
+ - Command routing and service discovery across all 11 services
225
+ - Config init, show, get, set, reset, file-based export/import
226
+ - Machine detection and profile management
227
+ - Gitignore pattern management (add, remove, list, show)
228
+ - Tool check, list, search, and dry-run install
229
+ - Identity CRUD (add, list, show, remove)
230
+ - Alias management and wrapper generation
231
+ - AI tool configuration
232
+ - Schema introspection
233
+ - Platform detection (macOS, Ubuntu, Raspberry Pi OS, Amazon Linux, Windows, Git Bash)
47
234
 
48
- ## Roadmap
235
+ What still needs real-world testing:
236
+ - SSH key generation and GitHub integration
237
+ - Git identity sync to actual repositories
238
+ - Config backup/restore via remote Git repo
239
+ - OAuth login flows
240
+ - Tool installation on each supported platform
241
+ - API plugin installation and lifecycle
242
+ - AI session launch and resume
243
+ - QMD search indexing and queries
49
244
 
50
- - [ ] User onboarding flow
51
- - [ ] Config file schema (`~/.devutils/config.json`)
52
- - [ ] Machine profile detection and storage
53
- - [ ] Rule engine for folder and tool behaviors
54
- - [ ] Migrate useful scripts and installers from `_rebuild/`
245
+ Patch versions (`0.1.1`, `0.1.2`, etc.) will ship as issues are found and fixed during hands-on use. Minor version bumps (`0.2.0`) are reserved for breaking changes. The first stable release will be **v1.0.0**.
55
246
 
56
247
  ## Contact
57
248
 
58
249
  **Fred Lackey**
59
- - Email: [fred.lackey@gmail.com](mailto:fred.lackey@gmail.com)
60
- - Website: [fredlackey.com](https://fredlackey.com)
61
- - GitHub: [@FredLackey](https://github.com/FredLackey)
250
+ - Email: [fred.lackey@gmail.com](mailto:fred.lackey@gmail.com)
251
+ - Website: [fredlackey.com](https://fredlackey.com)
252
+ - GitHub: [@FredLackey](https://github.com/FredLackey)
62
253
 
63
254
  ## License
64
255
 
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "@fredlackey/devutils",
3
- "version": "0.0.19",
3
+ "version": "0.1.0",
4
4
  "description": "A config-driven CLI toolkit for bootstrapping and managing development environments across any machine.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
- "dev": "./bin/dev.js"
7
+ "dev": "./src/cli.js"
8
8
  },
9
9
  "files": [
10
- "bin/",
11
- "src/"
10
+ "src/",
11
+ "package.json",
12
+ "README.md",
13
+ "LICENSE"
12
14
  ],
13
15
  "scripts": {
14
- "test": "echo \"Error: no test specified\" && exit 1"
16
+ "test": "node test/index.js"
15
17
  },
16
18
  "dependencies": {
17
19
  "commander": "^12.0.0"
@@ -0,0 +1,229 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const DEVUTILS_DIR = path.join(os.homedir(), '.devutils');
8
+ const PLUGINS_FILE = path.join(DEVUTILS_DIR, 'plugins.json');
9
+ const PLUGINS_DIR = path.join(DEVUTILS_DIR, 'plugins');
10
+
11
+ /**
12
+ * Reads and parses ~/.devutils/plugins.json.
13
+ * Returns an empty object if the file does not exist or is unreadable.
14
+ * Logs a warning to stderr if the file exists but contains invalid JSON.
15
+ *
16
+ * @returns {object} A map of plugin names to their entries, or {}.
17
+ */
18
+ function readPluginsJson() {
19
+ try {
20
+ const raw = fs.readFileSync(PLUGINS_FILE, 'utf8');
21
+ return JSON.parse(raw);
22
+ } catch (err) {
23
+ // If the file exists but JSON is invalid, warn the user
24
+ if (err instanceof SyntaxError && fs.existsSync(PLUGINS_FILE)) {
25
+ process.stderr.write(`Warning: ${PLUGINS_FILE} contains invalid JSON. Treating as empty.\n`);
26
+ }
27
+ return {};
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Validates that a plugin module exports the required contract fields.
33
+ * Returns null if valid, or a structured error object if something is missing.
34
+ *
35
+ * @param {object} pluginModule - The required plugin module.
36
+ * @param {string} pluginName - The plugin name (for error messages).
37
+ * @returns {object|null} Null if valid, or { error, code, message } if invalid.
38
+ */
39
+ function validateContract(pluginModule, pluginName) {
40
+ const required = ['name', 'description', 'version', 'auth', 'resources'];
41
+ const missing = required.filter(field => !pluginModule[field]);
42
+
43
+ if (missing.length > 0) {
44
+ return {
45
+ error: true,
46
+ code: 'INVALID_CONTRACT',
47
+ message: `Plugin "${pluginName}" is missing required fields: ${missing.join(', ')}.\nThe plugin may be outdated or incorrectly built.`
48
+ };
49
+ }
50
+
51
+ if (typeof pluginModule.resources !== 'object') {
52
+ return {
53
+ error: true,
54
+ code: 'INVALID_CONTRACT',
55
+ message: `Plugin "${pluginName}" has an invalid resources export. Expected an object.`
56
+ };
57
+ }
58
+
59
+ return null;
60
+ }
61
+
62
+ /**
63
+ * Loads a plugin by name from the installed plugins directory.
64
+ * Reads plugins.json, requires the package, and validates the contract.
65
+ *
66
+ * @param {string} pluginName - The short plugin name (e.g., 'gmail').
67
+ * @returns {object} { error: false, plugin } on success, or { error: true, code, message } on failure.
68
+ */
69
+ function loadPlugin(pluginName) {
70
+ const plugins = readPluginsJson();
71
+ const entry = plugins[pluginName];
72
+
73
+ if (!entry) {
74
+ return {
75
+ error: true,
76
+ code: 'NOT_INSTALLED',
77
+ message: `API plugin "${pluginName}" is not installed.\nRun "dev api enable ${pluginName}" to install it.`
78
+ };
79
+ }
80
+
81
+ const packagePath = path.join(PLUGINS_DIR, 'node_modules', entry.package);
82
+
83
+ let pluginModule;
84
+ try {
85
+ pluginModule = require(packagePath);
86
+ } catch (err) {
87
+ return {
88
+ error: true,
89
+ code: 'LOAD_FAILED',
90
+ message: `Failed to load plugin "${pluginName}" from ${packagePath}.\nThe package may be corrupted. Try "dev api disable ${pluginName}" then "dev api enable ${pluginName}".`
91
+ };
92
+ }
93
+
94
+ // Validate the plugin contract
95
+ const validation = validateContract(pluginModule, pluginName);
96
+ if (validation) {
97
+ return validation;
98
+ }
99
+
100
+ return { error: false, plugin: pluginModule };
101
+ }
102
+
103
+ /**
104
+ * Resolves a specific command from a plugin's resource tree.
105
+ * Given a plugin name, resource name, and command name, loads the plugin
106
+ * and walks the resource/command tree to find the command module.
107
+ *
108
+ * @param {string} pluginName - The plugin name (e.g., 'gmail').
109
+ * @param {string} resourceName - The resource name (e.g., 'messages').
110
+ * @param {string} commandName - The command name (e.g., 'list').
111
+ * @returns {object} { error: false, command, plugin } on success, or { error: true, code, message } on failure.
112
+ */
113
+ function resolveCommand(pluginName, resourceName, commandName) {
114
+ const result = loadPlugin(pluginName);
115
+ if (result.error) return result;
116
+
117
+ const plugin = result.plugin;
118
+ const resource = plugin.resources[resourceName];
119
+
120
+ if (!resource) {
121
+ const available = Object.keys(plugin.resources).join(', ');
122
+ return {
123
+ error: true,
124
+ code: 'UNKNOWN_RESOURCE',
125
+ message: `Plugin "${pluginName}" has no resource "${resourceName}".\nAvailable resources: ${available}`
126
+ };
127
+ }
128
+
129
+ const commandLoader = resource.commands[commandName];
130
+ if (!commandLoader) {
131
+ const available = Object.keys(resource.commands).join(', ');
132
+ return {
133
+ error: true,
134
+ code: 'UNKNOWN_COMMAND',
135
+ message: `Resource "${resourceName}" in plugin "${pluginName}" has no command "${commandName}".\nAvailable commands: ${available}`
136
+ };
137
+ }
138
+
139
+ let commandModule;
140
+ try {
141
+ commandModule = typeof commandLoader === 'function' ? commandLoader() : commandLoader;
142
+ } catch (err) {
143
+ return {
144
+ error: true,
145
+ code: 'COMMAND_LOAD_FAILED',
146
+ message: `Failed to load command "${commandName}" from plugin "${pluginName}": ${err.message}`
147
+ };
148
+ }
149
+
150
+ return {
151
+ error: false,
152
+ command: commandModule,
153
+ plugin: plugin
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Builds the context object that plugin commands receive.
159
+ * Reads the auth credential for the plugin's declared auth service
160
+ * and unwraps the credential envelope so plugins get flat access.
161
+ *
162
+ * @param {object} plugin - The plugin's contract object (must have an auth field).
163
+ * @param {object} coreContext - The core CLI context (output, errors, config, shell, platform).
164
+ * @returns {object} The enriched plugin context with auth, output, errors, config, shell, platform.
165
+ */
166
+ function buildPluginContext(plugin, coreContext) {
167
+ const authService = plugin.auth;
168
+
169
+ // Read the auth credential for this plugin's declared service
170
+ const authFilePath = path.join(DEVUTILS_DIR, 'auth', `${authService}.json`);
171
+ let authCredential = null;
172
+ try {
173
+ const raw = fs.readFileSync(authFilePath, 'utf8');
174
+ const credentialFile = JSON.parse(raw);
175
+
176
+ // Unwrap the credential envelope. The auth system stores files as:
177
+ // { service, type, credentials: { ... } }
178
+ // Plugins expect flat access (e.g., context.auth.accessKeyId), so we
179
+ // pass credentialFile.credentials directly instead of the full envelope.
180
+ authCredential = credentialFile.credentials || credentialFile;
181
+ } catch (err) {
182
+ // Auth not available - plugin commands will get null
183
+ }
184
+
185
+ return {
186
+ auth: authCredential,
187
+ output: coreContext.output,
188
+ errors: coreContext.errors,
189
+ config: coreContext.config,
190
+ shell: coreContext.shell,
191
+ platform: coreContext.platform
192
+ };
193
+ }
194
+
195
+ /**
196
+ * Returns all installed plugins from plugins.json.
197
+ * Returns an empty object if no plugins are installed.
198
+ *
199
+ * @returns {object} A map of plugin names to their entries.
200
+ */
201
+ function getInstalledPlugins() {
202
+ return readPluginsJson();
203
+ }
204
+
205
+ /**
206
+ * Returns the list of available plugins from the bundled registry.
207
+ * Returns an empty array if the registry file is missing or unreadable.
208
+ *
209
+ * @returns {Array<object>} An array of registry plugin entries.
210
+ */
211
+ function getRegistryPlugins() {
212
+ try {
213
+ return require('./registry.json');
214
+ } catch (err) {
215
+ return [];
216
+ }
217
+ }
218
+
219
+ module.exports = {
220
+ resolveCommand,
221
+ loadPlugin,
222
+ buildPluginContext,
223
+ validateContract,
224
+ getInstalledPlugins,
225
+ getRegistryPlugins,
226
+ readPluginsJson,
227
+ PLUGINS_FILE,
228
+ PLUGINS_DIR
229
+ };
@@ -0,0 +1,62 @@
1
+ [
2
+ {
3
+ "name": "gmail",
4
+ "package": "@fredlackey/devutils-api-gmail",
5
+ "description": "Google Gmail (messages, labels, drafts, threads)",
6
+ "auth": "google"
7
+ },
8
+ {
9
+ "name": "drive",
10
+ "package": "@fredlackey/devutils-api-drive",
11
+ "description": "Google Drive (files, folders, permissions)",
12
+ "auth": "google"
13
+ },
14
+ {
15
+ "name": "sheets",
16
+ "package": "@fredlackey/devutils-api-sheets",
17
+ "description": "Google Sheets (spreadsheets, values, sheets)",
18
+ "auth": "google"
19
+ },
20
+ {
21
+ "name": "docs",
22
+ "package": "@fredlackey/devutils-api-docs",
23
+ "description": "Google Docs (documents)",
24
+ "auth": "google"
25
+ },
26
+ {
27
+ "name": "aws",
28
+ "package": "@fredlackey/devutils-api-aws",
29
+ "description": "Amazon Web Services (compute, storage, functions, groups)",
30
+ "auth": "aws"
31
+ },
32
+ {
33
+ "name": "cloudflare",
34
+ "package": "@fredlackey/devutils-api-cloudflare",
35
+ "description": "Cloudflare (zones, DNS records, API tokens)",
36
+ "auth": "cloudflare"
37
+ },
38
+ {
39
+ "name": "dokploy",
40
+ "package": "@fredlackey/devutils-api-dokploy",
41
+ "description": "Dokploy (applications, projects, domains, servers)",
42
+ "auth": "dokploy"
43
+ },
44
+ {
45
+ "name": "namecheap",
46
+ "package": "@fredlackey/devutils-api-namecheap",
47
+ "description": "Namecheap (domains, DNS, SSL certificates)",
48
+ "auth": "namecheap"
49
+ },
50
+ {
51
+ "name": "flowroute",
52
+ "package": "@fredlackey/devutils-api-flowroute",
53
+ "description": "Flowroute (SMS, MMS, phone numbers)",
54
+ "auth": "flowroute"
55
+ },
56
+ {
57
+ "name": "mailu",
58
+ "package": "@fredlackey/devutils-api-mailu",
59
+ "description": "Mailu (email users, aliases, domains)",
60
+ "auth": "mailu"
61
+ }
62
+ ]