dxai-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 d3v
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/nandha-kumar-hajari/dxai/main/docs/public/og.png" alt="dxai — make your repo ready for every AI coding agent" width="640">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <strong>One command to make your repo ready for every AI coding agent.</strong>
7
+ </p>
8
+
9
+ `dxai` is an interactive CLI that configures **MCP servers**, **agent skills**, **Cursor rules and commands**, **project instruction files** (`CLAUDE.md`, `GEMINI.md`, `AGENTS.md`), and **AI-friendly project scaffolding** across **Cursor, Claude Code, VS Code / Copilot, OpenAI Codex, Gemini CLI, Google Antigravity, and Devin Desktop (formerly Windsurf)** — simultaneously and in the right format for each.
10
+
11
+ 📚 **Full documentation:** <https://nandha-kumar-hajari.github.io/dxai/>
12
+
13
+ ---
14
+
15
+ ## Quick Start
16
+
17
+ ```bash
18
+ npx dxai-cli # interactive menu
19
+ npx dxai-cli system # global IDE configs only
20
+ npx dxai-cli project # repo-local scaffolding only
21
+ npx dxai-cli --yes # accept defaults; non-interactive
22
+ npx dxai-cli --help # full usage
23
+ ```
24
+
25
+ ## Headline features
26
+
27
+ - **One config, every tool.** Pick an MCP server once and dxai writes the right format for every selected agent (JSON for Cursor / VS Code / Gemini / Antigravity / Devin, TOML for Codex, `claude mcp add` for Claude Code). The full per-agent map lives in [Supported Agents](https://nandha-kumar-hajari.github.io/dxai/reference/agents/).
28
+ - **Profiles for teams.** `dxai save-profile --here` writes `./.dxai/profile.json`. Teammates run `dxai apply` to get the same setup.
29
+ - **Drift-aware introspection.** `dxai list` / `status` / `doctor` read a manifest of installs, so cleanup is precise and PR review can spot config drift.
30
+ - **CI-ready.** Set `CI=true` (or pass `--yes`) for unattended runs. JSON output mode pipes into the rest of your tooling.
31
+ - **Backed by the official MCP Registry.** Catalog entries link to their record on [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io); a weekly sync keeps the bundled catalog current and `dxai update` re-resolves live. Any registry server can be added by name: `dxai add io.github.owner/server`.
32
+ - **Bundled + remote registry.** `dxai update` refreshes the MCP / skill catalog from a remote URL; bundled JSON is the offline fallback. Override the source with `DXAI_REGISTRY_URL`. Network calls (registry refresh, skill downloads) use native `fetch` with per-attempt timeouts and retry/backoff — no `curl` dependency.
33
+ - **Reversible.** Every write is backed up as `<file>.bak.<ts>`; `dxai rollback` restores the most recent snapshot (and snapshots the current file first, so it's undoable).
34
+
35
+ ## Commands
36
+
37
+ | Command | What it does |
38
+ |---|---|
39
+ | `dxai system` | Global IDE configs, MCP servers, agent skills |
40
+ | `dxai project` | Repo-local AI scaffolding (rules, CLAUDE.md, AGENTS.md, etc.) |
41
+ | `dxai both` | System + project setup in one go |
42
+ | `dxai init` | Alias for `dxai project` (first-time project setup) |
43
+ | `dxai add <mcp...>` | Add MCP server(s) to detected agents — fast path, no wizard |
44
+ | `dxai remove <mcp...>` (alias `rm`) | Remove MCP server(s) from detected agents |
45
+ | `dxai apply [name]` | Run setup using a saved profile |
46
+ | `dxai save-profile [name]` | Save selections as a reusable profile |
47
+ | `dxai profiles` | List discoverable profiles |
48
+ | `dxai list` | Show what dxai installed (from manifest) |
49
+ | `dxai status` | Compare manifest with live config (drift detection) |
50
+ | `dxai doctor` | Validate dxai installation |
51
+ | `dxai update` | Refresh registry cache from remote |
52
+ | `dxai rollback` | Restore dxai-managed files from their most recent `.bak.<ts>` backup |
53
+ | `dxai cleanup [scope]` (alias `reset`) | Remove dxai-managed configs and files (`system`/`project`/`both`; supports `-y`, `--json`, `--dry-run`, `--backups`) |
54
+
55
+ Per-command pages with full options tables: [Reference / Commands](https://nandha-kumar-hajari.github.io/dxai/reference/commands/).
56
+
57
+ ## Non-interactive / CI
58
+
59
+ ```bash
60
+ dxai system --yes \
61
+ --agents cursor,claude-code \
62
+ --mcp github,playwright,context7
63
+
64
+ dxai project --yes \
65
+ --stack react,node \
66
+ --features cursor-rules,agents-md,editorconfig
67
+
68
+ CI=true dxai system --agents cursor --dry-run --json | jq .
69
+ ```
70
+
71
+ Full flag table: [Reference / Flags](https://nandha-kumar-hajari.github.io/dxai/reference/flags/).
72
+
73
+ ## Requirements
74
+
75
+ - **Node.js 18+** — verified in CI on Node 18, 20, 22.
76
+ - **npm** / **npx** (ships with Node).
77
+ - **Git** — recommended.
78
+
79
+ dxai is verified on **macOS, Linux, and Windows** in the GitHub Actions matrix.
80
+
81
+ ---
82
+
83
+ ## Setup, tests, and CI
84
+
85
+ ```bash
86
+ git clone https://github.com/nandha-kumar-hajari/dxai.git
87
+ cd dxai
88
+ npm install
89
+ npm test # 143 tests, ~0.5s (node:test, no extra framework)
90
+ npm run smoke # quick --version + --help check
91
+ npm link # optional: simulate `npx dxai-cli`
92
+ ```
93
+
94
+ CI matrix runs on every push and PR across **Ubuntu, macOS, Windows × Node 18, 20, 22**:
95
+
96
+ 1. `node --check` for every `.js` in `src/` and `bin/`
97
+ 2. `npm test`
98
+ 3. Smoke run of `dxai --version` and `dxai --help`
99
+ 4. `npm run docs:check` (drift guard — fails if `bin/cli.js` flags or registry JSON change without regenerating docs)
100
+
101
+ ## Documentation site
102
+
103
+ Built with **Astro + Starlight**. Sources live under `docs/`. Most pages are hand-written; registry tables, CLI reference, schema docs, and the changelog are auto-generated from source code.
104
+
105
+ ```bash
106
+ npm run docs:install # one-time: install docs deps
107
+ npm run docs:dev # dev server at http://localhost:4321
108
+ npm run docs:generate # run all generators (idempotent)
109
+ npm run docs:build # generators → astro build → docs/dist/
110
+ npm run docs:check # drift guard (CI uses this)
111
+ npm run docs:preview # preview built site
112
+ ```
113
+
114
+ Auto-generated pages (regenerated by the generators in `scripts/docs/`):
115
+
116
+ | Page | Source |
117
+ |---|---|
118
+ | [Registry / MCP servers](https://nandha-kumar-hajari.github.io/dxai/registry/mcp-servers/) | `src/registry/data/mcp-servers.json` |
119
+ | [Registry / Skills](https://nandha-kumar-hajari.github.io/dxai/registry/skills/) | `src/registry/data/skills.json` |
120
+ | [Registry / Stacks](https://nandha-kumar-hajari.github.io/dxai/registry/stacks/) | `src/registry/stacks.js` (`TECH_STACKS`, `CURSOR_RULES`) |
121
+ | [Registry / Cursor commands](https://nandha-kumar-hajari.github.io/dxai/registry/cursor-commands/) | `src/registry/stacks.js` (`CURSOR_COMMANDS`) |
122
+ | [Reference / Commands](https://nandha-kumar-hajari.github.io/dxai/reference/commands/) | `bin/cli.js` (`buildProgram()`) |
123
+ | [Reference / Flags](https://nandha-kumar-hajari.github.io/dxai/reference/flags/) | `bin/cli.js` (`buildProgram()`) |
124
+ | [Reference / Profile schema](https://nandha-kumar-hajari.github.io/dxai/reference/profile-schema/) | `src/profile.js` (`PROFILE_KEYS`) |
125
+ | [Reference / Manifest schema](https://nandha-kumar-hajari.github.io/dxai/reference/manifest-schema/) | `src/manifest.js` (`emptyManifest`) |
126
+ | [Changelog](https://nandha-kumar-hajari.github.io/dxai/changelog/) | `git log` (conventional commits) |
127
+
128
+ The site also publishes `llms.txt` / `llms-full.txt` for AI-agent consumption (via `starlight-llms-txt`).
129
+
130
+ ## Project structure
131
+
132
+ ```
133
+ bin/cli.js # commander entry, exports buildProgram()
134
+ src/
135
+ index.js # run / apply / saveProfileCmd / listProfilesCmd
136
+ runtime.js # option normalization
137
+ profile.js # profile load / merge / save / discovery
138
+ manifest.js # manifest read / write / record helpers
139
+ inspect.js # list / status / doctor
140
+ update.js # update (remote registry refresh)
141
+ auto-update.js # periodic TTL-based catalog refresh
142
+ cleanup.js # cleanup / reset (manifest-aware)
143
+ rollback.js # restore files from .bak.<ts> snapshots
144
+ mcp-cmd.js # fast-path add / remove MCP commands
145
+ config-writer.js # file writers + dry-run previews
146
+ config-remover.js # scan + remove helpers
147
+ net.js # fetch with timeout + retry/backoff
148
+ fs-atomic.js # atomic writes (temp + rename)
149
+ detect.js # OS, prerequisite, agent detection
150
+ detect-project.js # stack / tooling / git inference
151
+ branding.js # banner / colors / message helpers
152
+ registry/
153
+ mcp-servers.js # re-exports loaded via loader
154
+ skills.js # re-exports loaded via loader
155
+ stacks.js # tech stacks, Cursor rules, commands, templates
156
+ loader.js # cache > bundled JSON resolution; remote fetch
157
+ data/{mcp-servers,skills}.json # bundled catalogs
158
+
159
+ test/ # node:test suite (143 tests)
160
+ docs/ # Astro + Starlight site
161
+ scripts/docs/ # doc generators (gen-*.mjs + lib/render.mjs)
162
+ .github/workflows/ # ci.yml + docs.yml
163
+ ROADMAP.md # tracked gaps and future work
164
+ ```
165
+
166
+ ## Roadmap
167
+
168
+ See [ROADMAP.md](./ROADMAP.md). The Top 5 focus areas — bug fixes, non-interactive mode, profiles, manifest + introspection, remote registry + tests — are complete. Documentation site (this section) shipped on top of that.
169
+
170
+ ## License
171
+
172
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command, Option } from 'commander';
4
+ import { createRequire } from 'node:module';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { realpathSync } from 'node:fs';
7
+ import { run, apply, saveProfileCmd, listProfilesCmd } from '../src/index.js';
8
+ import { cleanup } from '../src/cleanup.js';
9
+ import { addMcp, removeMcp } from '../src/mcp-cmd.js';
10
+ import { rollbackCmd } from '../src/rollback.js';
11
+ import { listCmd, statusCmd, doctorCmd } from '../src/inspect.js';
12
+ import { updateCmd } from '../src/update.js';
13
+
14
+ const require = createRequire(import.meta.url);
15
+ const pkg = require('../package.json');
16
+
17
+ // Comma-separated list parser for repeatable selection flags.
18
+ const csv = (v) => v.split(',').map((s) => s.trim()).filter(Boolean);
19
+
20
+ // Build the configured commander program. Exported so doc generators (and any
21
+ // other tooling) can introspect subcommands and options as data.
22
+ export function buildProgram() {
23
+ const program = new Command();
24
+
25
+ program
26
+ .name('dxai')
27
+ .description('Interactive CLI to bootstrap your AI-powered dev environment.')
28
+ .version(pkg.version, '-v, --version', 'output the version number')
29
+ .showHelpAfterError('(run with --help for usage)');
30
+
31
+ // Shared options applied to setup subcommands.
32
+ const sharedSetupOptions = (cmd) =>
33
+ cmd
34
+ .addOption(new Option('-y, --yes', 'skip prompts; use defaults / values from flags'))
35
+ .addOption(new Option('--agents <list>', 'comma-separated agent IDs (cursor,claude-code,vscode,vscode-insiders,codex,gemini,antigravity,antigravity-ide,antigravity-cli,devin-desktop,devin-cli; former ids like windsurf still work)').argParser(csv))
36
+ .addOption(new Option('--mcp <list>', 'comma-separated MCP server IDs').argParser(csv))
37
+ .addOption(new Option('--skills <list>', 'comma-separated skill IDs').argParser(csv))
38
+ .addOption(new Option('--tools <list>', 'comma-separated automation tool IDs (agent-browser,agent-device)').argParser(csv))
39
+ .addOption(new Option('--features <list>', 'comma-separated project feature IDs').argParser(csv))
40
+ .addOption(new Option('--stack <list>', 'comma-separated tech stack IDs').argParser(csv))
41
+ .addOption(new Option('--profile <nameOrPath>', 'load a saved profile (name or path)'))
42
+ .addOption(new Option('--no-profile', 'skip auto-discovery of project/user profiles'))
43
+ .addOption(new Option('--json', 'emit machine-readable JSON output (no colors, no spinners)'))
44
+ .addOption(new Option('--dry-run', 'preview changes without writing'))
45
+ .addOption(new Option('--no-update', 'skip the periodic catalog refresh check'));
46
+
47
+ sharedSetupOptions(
48
+ program
49
+ .command('system')
50
+ .description('System-level setup (global IDE configs, MCP servers, skills)')
51
+ .action(async (opts) => {
52
+ await run('system', opts);
53
+ })
54
+ );
55
+
56
+ sharedSetupOptions(
57
+ program
58
+ .command('project')
59
+ .alias('init')
60
+ .description('Project-level setup (stack, rules, CLAUDE.md, .editorconfig, etc.)')
61
+ .action(async (opts) => {
62
+ await run('project', opts);
63
+ })
64
+ );
65
+
66
+ sharedSetupOptions(
67
+ program
68
+ .command('both')
69
+ .description('Run system + project setup in one go')
70
+ .action(async (opts) => {
71
+ await run('both', opts);
72
+ })
73
+ );
74
+
75
+ program
76
+ .command('cleanup [scope]')
77
+ .alias('reset')
78
+ .description('Remove dxai-managed configs, files, and skills (scope: system | project | both)')
79
+ .addOption(new Option('-y, --yes', 'non-interactive; remove everything dxai-managed (custom-edit-prone files and backups are kept)'))
80
+ .addOption(new Option('--backups', 'with --yes/--json: also delete .bak.<ts> backup files'))
81
+ .addOption(new Option('--json', 'emit machine-readable JSON output (implies non-interactive)'))
82
+ .addOption(new Option('--dry-run', 'preview what would be removed without deleting anything'))
83
+ .action(async (scope, opts) => {
84
+ await cleanup(scope, opts);
85
+ });
86
+
87
+ // dxai add <id...> — fast path: add MCP server(s) without the wizard
88
+ program
89
+ .command('add <mcp...>')
90
+ .description('Add MCP server(s) to detected agents (fast path, no wizard). Takes catalogue ids or official MCP Registry names like io.github.owner/server')
91
+ .addOption(new Option('--agents <list>', 'comma-separated agent IDs to target (default: detected)').argParser(csv))
92
+ .addOption(new Option('--project', 'write to project-level config instead of global'))
93
+ .addOption(new Option('-y, --yes', 'non-interactive; use defaults for any required inputs'))
94
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
95
+ .addOption(new Option('--dry-run', 'preview what would be added without writing'))
96
+ .action(async (mcp, opts) => {
97
+ await addMcp(mcp, opts);
98
+ });
99
+
100
+ // dxai remove <id...> — fast path: remove MCP server(s) without the wizard
101
+ program
102
+ .command('remove <mcp...>')
103
+ .alias('rm')
104
+ .description('Remove MCP server(s) from detected agents (fast path, no wizard)')
105
+ .addOption(new Option('--agents <list>', 'comma-separated agent IDs to target (default: detected)').argParser(csv))
106
+ .addOption(new Option('--project', 'remove from project-level config instead of global'))
107
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
108
+ .addOption(new Option('--dry-run', 'preview what would be removed without writing'))
109
+ .action(async (mcp, opts) => {
110
+ await removeMcp(mcp, opts);
111
+ });
112
+
113
+ // dxai rollback — restore config/project files from their latest .bak.<ts>
114
+ program
115
+ .command('rollback')
116
+ .description('Restore dxai-managed files from their most recent .bak.<ts> backup')
117
+ .addOption(new Option('--list', 'list restorable backups without changing anything'))
118
+ .addOption(new Option('-y, --yes', 'restore the latest backup for every file without prompting'))
119
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
120
+ .addOption(new Option('--dry-run', 'preview what would be restored without writing'))
121
+ .action(async (opts) => {
122
+ await rollbackCmd(opts);
123
+ });
124
+
125
+ // dxai apply [name|path] — non-interactive run from a saved profile
126
+ program
127
+ .command('apply [nameOrPath]')
128
+ .description('Run setup using a saved profile (auto-loads ./.dxai/profile.json if no name given)')
129
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
130
+ .addOption(new Option('--dry-run', 'preview changes without writing'))
131
+ .action(async (nameOrPath, opts) => {
132
+ await apply(nameOrPath, opts);
133
+ });
134
+
135
+ // dxai save-profile <name> — capture flag values into a profile file
136
+ program
137
+ .command('save-profile [name]')
138
+ .description('Save selections as a profile. Defaults to ~/.dxai/profiles/<name>.json')
139
+ .addOption(new Option('--mode <mode>', 'mode to record: system | project | both'))
140
+ .addOption(new Option('--agents <list>', 'comma-separated agent IDs').argParser(csv))
141
+ .addOption(new Option('--mcp <list>', 'comma-separated MCP server IDs').argParser(csv))
142
+ .addOption(new Option('--skills <list>', 'comma-separated skill IDs').argParser(csv))
143
+ .addOption(new Option('--features <list>', 'comma-separated feature IDs').argParser(csv))
144
+ .addOption(new Option('--stack <list>', 'comma-separated stack IDs').argParser(csv))
145
+ .addOption(new Option('--here', 'save as project-local ./.dxai/profile.json instead'))
146
+ .addOption(new Option('--path <path>', 'save to an explicit file path'))
147
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
148
+ .action(async (name, opts) => {
149
+ await saveProfileCmd(name, opts);
150
+ });
151
+
152
+ // dxai profiles — list discoverable profiles
153
+ program
154
+ .command('profiles')
155
+ .description('List discoverable profiles (~/.dxai/profiles + ./.dxai)')
156
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
157
+ .action(async (opts) => {
158
+ await listProfilesCmd(opts);
159
+ });
160
+
161
+ // dxai list — show what dxai installed (from manifest)
162
+ program
163
+ .command('list')
164
+ .description('Show dxai-managed installs from the manifest (system + project)')
165
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
166
+ .action(async (opts) => {
167
+ await listCmd(opts);
168
+ });
169
+
170
+ // dxai status — diff manifest vs actual config files
171
+ program
172
+ .command('status')
173
+ .description('Compare manifest with live config files; surface drift')
174
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
175
+ .action(async (opts) => {
176
+ await statusCmd(opts);
177
+ });
178
+
179
+ // dxai doctor — validate environment + configs
180
+ program
181
+ .command('doctor')
182
+ .description('Validate dxai installation: configs parse, env vars set, tools on PATH')
183
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
184
+ .addOption(new Option('--handshake', 'spawn each installed MCP server and verify it speaks JSON-RPC over stdio'))
185
+ .action(async (opts) => {
186
+ await doctorCmd(opts);
187
+ });
188
+
189
+ // dxai update — refresh registry cache (MCP servers, skills) from remote
190
+ program
191
+ .command('update')
192
+ .description('Fetch the latest registry (MCP servers, skills) and cache it locally')
193
+ .addOption(new Option('--json', 'emit machine-readable JSON output'))
194
+ .addOption(new Option('--registry-version <ref>', 'registry tag/branch to fetch (default: main)'))
195
+ .addOption(new Option('--registry-url <url>', 'registry base URL (overrides DXAI_REGISTRY_URL)'))
196
+ .addOption(new Option('--no-resolve', 'skip re-resolving registry-linked MCP servers live from the official MCP Registry'))
197
+ .action(async (opts) => {
198
+ await updateCmd(opts);
199
+ });
200
+
201
+ // Default action when no subcommand is given — preserve interactive menu.
202
+ sharedSetupOptions(
203
+ program
204
+ .command('start', { isDefault: true, hidden: true })
205
+ .description('Interactive menu (default when no subcommand is given)')
206
+ .action(async (opts) => {
207
+ await run(undefined, opts);
208
+ })
209
+ );
210
+
211
+ // Examples block.
212
+ program.addHelpText(
213
+ 'after',
214
+ `
215
+ Examples:
216
+ $ dxai # interactive menu
217
+ $ dxai system # interactive system setup
218
+ $ dxai project --yes # accept defaults, no prompts
219
+ $ dxai system -y --agents cursor,claude-code --mcp github,playwright
220
+ $ dxai project -y --stack react,node --features cursor-rules,agents-md
221
+ $ CI=true dxai system --agents cursor # non-interactive (CI mode)
222
+ $ dxai save-profile myteam --agents cursor --mcp github,playwright
223
+ $ dxai add github playwright # add MCP servers to detected agents
224
+ $ dxai add context7 --agents cursor,codex # target specific agents
225
+ $ dxai add io.github.upstash/context7 # any server from the official MCP Registry
226
+ $ dxai remove github --dry-run # preview an MCP removal
227
+ $ dxai apply myteam # run setup from a saved profile
228
+ $ dxai apply --dry-run # auto-load ./.dxai/profile.json
229
+ $ dxai profiles # list saved profiles
230
+ $ dxai cleanup # remove dxai-managed configs
231
+ $ dxai rollback --list # show restorable .bak backups
232
+ $ dxai rollback --dry-run # preview a restore, write nothing
233
+
234
+ Profiles are auto-loaded from (in order):
235
+ ./.dxai/profile.json (project-local)
236
+ ~/.dxai/config.json (user-level)
237
+ ~/.dxairc(.json)
238
+ Pass --no-profile to skip auto-discovery, or --profile <nameOrPath> to override.
239
+
240
+ Environment:
241
+ CI=true Force non-interactive output (plain text, no spinners)
242
+ NO_COLOR=1 Disable colored output
243
+ DXAI_DRY_RUN=1 Equivalent to --dry-run
244
+ DXAI_NO_AUTO_UPDATE=1 Disable the periodic catalog refresh (same as --no-update)
245
+ DXAI_UPDATE_TTL_DAYS=N Days between catalog refresh checks (default 7)
246
+ DXAI_UPDATE_TIMEOUT_MS=N Network timeout for the background refresh (default 4000)
247
+ DXAI_REGISTRY_URL=<url> Override the registry base URL (host a fork)
248
+ DXAI_MCP_REGISTRY_URL=<url> Override the official MCP Registry base URL
249
+ `
250
+ );
251
+
252
+ return program;
253
+ }
254
+
255
+ // Only parse argv when invoked as the entry script (not when imported).
256
+ const invokedDirectly = (() => {
257
+ try {
258
+ if (!process.argv[1]) return false;
259
+ const selfPath = realpathSync(fileURLToPath(import.meta.url));
260
+ const argvPath = realpathSync(process.argv[1]);
261
+ return selfPath === argvPath;
262
+ } catch {
263
+ return false;
264
+ }
265
+ })();
266
+
267
+ if (invokedDirectly) {
268
+ buildProgram().parseAsync(process.argv).catch((err) => {
269
+ console.error(err.stack || err.message || err);
270
+ process.exit(1);
271
+ });
272
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "dxai-cli",
3
+ "version": "1.0.0",
4
+ "description": "Interactive CLI to bootstrap your AI-powered dev environment — MCP servers, agent skills, cursor rules & more across Cursor, Claude Code, VS Code / Copilot, Codex, Gemini CLI, Windsurf, and Antigravity.",
5
+ "keywords": [
6
+ "ai",
7
+ "cursor",
8
+ "claude-code",
9
+ "codex",
10
+ "gemini-cli",
11
+ "mcp",
12
+ "agent-skills",
13
+ "developer-tools",
14
+ "setup",
15
+ "cli"
16
+ ],
17
+ "author": "d3v",
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/nandha-kumar-hajari/dxai.git"
22
+ },
23
+ "homepage": "https://github.com/nandha-kumar-hajari/dxai#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/nandha-kumar-hajari/dxai/issues"
26
+ },
27
+ "type": "module",
28
+ "bin": {
29
+ "dxai": "bin/cli.js"
30
+ },
31
+ "main": "src/index.js",
32
+ "scripts": {
33
+ "test": "node scripts/run-tests.mjs",
34
+ "lint": "eslint .",
35
+ "smoke": "node bin/cli.js --version && node bin/cli.js --help > /dev/null",
36
+ "docs:install": "npm --prefix docs install",
37
+ "docs:dev": "npm --prefix docs run dev",
38
+ "docs:build": "npm run docs:generate && npm --prefix docs run build",
39
+ "docs:preview": "npm --prefix docs run preview",
40
+ "docs:generate": "node scripts/docs/generate-all.mjs",
41
+ "docs:check": "node scripts/docs/check-drift.mjs"
42
+ },
43
+ "files": [
44
+ "bin/",
45
+ "src/",
46
+ "README.md",
47
+ "LICENSE"
48
+ ],
49
+ "engines": {
50
+ "node": ">=18.0.0"
51
+ },
52
+ "dependencies": {
53
+ "chalk": "^5.3.0",
54
+ "commander": "^14.0.3",
55
+ "fs-extra": "^11.2.0",
56
+ "inquirer": "^9.2.23",
57
+ "ora": "^8.0.1"
58
+ },
59
+ "devDependencies": {
60
+ "@eslint/js": "^10.0.1",
61
+ "eslint": "^10.7.0",
62
+ "globals": "^17.7.0"
63
+ }
64
+ }
@@ -0,0 +1,106 @@
1
+ // Periodic, opt-out catalog auto-refresh.
2
+ //
3
+ // The catalog (MCP servers, skills, automation tools) is decoupled from CLI releases:
4
+ // `dxai update` fetches it from the remote and caches it under ~/.dxai/cache/. This module
5
+ // makes that refresh happen on its own, on a TTL, so catalog improvements reach users who
6
+ // never run `dxai update` manually.
7
+ //
8
+ // IMPORTANT: the registry modules load their data at import time, so a refresh during a run
9
+ // updates the on-disk cache for the *next* run — it does not hot-swap the in-memory catalog.
10
+ // We refresh the cache and (optionally) nudge; we never pretend it changed the current run.
11
+
12
+ import fs from 'fs-extra';
13
+ import path from 'path';
14
+ import { CACHE_DIR, registryBaseFor } from './registry/loader.js';
15
+ import { refreshRegistry } from './update.js';
16
+ import { infoMsg } from './branding.js';
17
+ import { writeJsonAtomic } from './fs-atomic.js';
18
+
19
+ const LAST_CHECK_PATH = path.join(CACHE_DIR, '.last-check.json');
20
+ const DEFAULT_TTL_DAYS = 7;
21
+ const DEFAULT_TIMEOUT_MS = 4000;
22
+
23
+ export function ttlMs(env = process.env) {
24
+ const days = Number(env.DXAI_UPDATE_TTL_DAYS) || DEFAULT_TTL_DAYS;
25
+ return days * 24 * 60 * 60 * 1000;
26
+ }
27
+
28
+ function timeoutMs(env = process.env) {
29
+ return Number(env.DXAI_UPDATE_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS;
30
+ }
31
+
32
+ // Due for a refresh when we have never recorded a check, or the last one is older than the TTL.
33
+ export function isDue(lastCheck, now, ttl) {
34
+ if (!lastCheck || !lastCheck.checkedAt) return true;
35
+ const last = Date.parse(lastCheck.checkedAt);
36
+ if (Number.isNaN(last)) return true;
37
+ return now - last > ttl;
38
+ }
39
+
40
+ // Suppress the check for machine output, CI, explicit opt-out, or --no-update.
41
+ export function shouldSkip(runtime = {}, env = process.env) {
42
+ return !!(runtime.json || runtime.ci || env.DXAI_NO_AUTO_UPDATE || runtime.update === false);
43
+ }
44
+
45
+ export function readLastCheck(file = LAST_CHECK_PATH) {
46
+ if (!fs.existsSync(file)) return null;
47
+ try {
48
+ return fs.readJsonSync(file);
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ export function recordCheck(now, file = LAST_CHECK_PATH) {
55
+ // Best-effort: an unwritable cache dir must not propagate out of the
56
+ // background refresh and abort the user's setup.
57
+ try {
58
+ writeJsonAtomic(file, { checkedAt: new Date(now).toISOString() }, { spaces: 2 });
59
+ } catch { /* ignore — we'll just re-check next run */ }
60
+ }
61
+
62
+ // Orchestrator. Dependencies are injectable so the whole flow is testable without a network.
63
+ export async function maybeRefreshCatalog(runtime = {}, deps = {}) {
64
+ const {
65
+ now = Date.now(),
66
+ env = process.env,
67
+ refresh = refreshRegistry,
68
+ lastCheckPath = LAST_CHECK_PATH,
69
+ log = infoMsg,
70
+ } = deps;
71
+
72
+ if (shouldSkip(runtime, env)) return { skipped: true };
73
+
74
+ const record = readLastCheck(lastCheckPath);
75
+ if (!isDue(record, now, ttlMs(env))) return { due: false };
76
+
77
+ // Record immediately so an offline failure doesn't retry every single run.
78
+ recordCheck(now, lastCheckPath);
79
+
80
+ // First ever check: just baseline the timer. The freshly-installed package already
81
+ // bundles a recent snapshot, so there's no need to hit the network on the very first run.
82
+ if (!record) return { firstRun: true };
83
+
84
+ try {
85
+ // Background check: fail fast. Retrying an offline host would stall the
86
+ // user's run for seconds; the interactive `dxai update` still gets retries.
87
+ // One request per file, no live re-resolution: the bot-maintained snapshot
88
+ // is what a background refresh picks up.
89
+ const results = await refresh({ base: registryBaseFor({}), timeoutMs: timeoutMs(env), retries: 0, resolve: false });
90
+ // refreshRegistry captures per-file failures rather than throwing; if every file
91
+ // failed (offline / registry down) treat the whole refresh as a miss.
92
+ if (results.length && results.every((r) => !r.ok)) {
93
+ if (!runtime.json) log('Couldn’t refresh catalog (offline?) — using cached data.');
94
+ return { refreshed: false, results };
95
+ }
96
+ const added = results.reduce((n, r) => n + (r.added?.length || 0), 0);
97
+ const removed = results.reduce((n, r) => n + (r.removed?.length || 0), 0);
98
+ if ((added || removed) && !runtime.json) {
99
+ log(`Catalog refreshed — ${added} new / ${removed} removed (applies on your next run).`);
100
+ }
101
+ return { refreshed: true, results, added, removed };
102
+ } catch (err) {
103
+ if (!runtime.json) log('Couldn’t refresh catalog (offline?) — using cached data.');
104
+ return { refreshed: false, error: err.message };
105
+ }
106
+ }