@vucinatim/agentic-devtools 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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/SECURITY.md +47 -0
  4. package/adapters/claude/namecheap/README.md +13 -0
  5. package/adapters/claude/npm/README.md +11 -0
  6. package/adapters/claude/railway/README.md +11 -0
  7. package/adapters/codex/namecheap/.codex-plugin/plugin.json +40 -0
  8. package/adapters/codex/namecheap/.mcp.json +21 -0
  9. package/adapters/codex/namecheap/SKILL.md +40 -0
  10. package/adapters/codex/npm/.codex-plugin/plugin.json +41 -0
  11. package/adapters/codex/npm/.mcp.json +18 -0
  12. package/adapters/codex/npm/SKILL.md +54 -0
  13. package/adapters/codex/railway/.codex-plugin/plugin.json +39 -0
  14. package/adapters/codex/railway/.mcp.json +20 -0
  15. package/adapters/codex/railway/SKILL.md +44 -0
  16. package/docs/README.md +14 -0
  17. package/docs/architecture.md +208 -0
  18. package/docs/auth-and-setup-guidelines.md +261 -0
  19. package/docs/migration-plan.md +55 -0
  20. package/docs/open-source-readiness.md +119 -0
  21. package/docs/publishing.md +211 -0
  22. package/docs/testing.md +61 -0
  23. package/docs/usage.md +144 -0
  24. package/package.json +78 -0
  25. package/src/cli.mjs +158 -0
  26. package/src/core/config-store.mjs +106 -0
  27. package/src/core/result.mjs +13 -0
  28. package/src/core/tool-registry.mjs +29 -0
  29. package/src/index.mjs +47 -0
  30. package/src/tools/namecheap/auth.mjs +429 -0
  31. package/src/tools/namecheap/client.mjs +655 -0
  32. package/src/tools/namecheap/mcp.mjs +298 -0
  33. package/src/tools/npm/auth.mjs +367 -0
  34. package/src/tools/npm/client.mjs +317 -0
  35. package/src/tools/npm/mcp.mjs +343 -0
  36. package/src/tools/railway/auth.mjs +402 -0
  37. package/src/tools/railway/client.mjs +388 -0
  38. package/src/tools/railway/mcp.mjs +282 -0
@@ -0,0 +1,211 @@
1
+ # Publishing
2
+
3
+ Agentic Devtools should publish to npm from GitHub Actions using npm Trusted
4
+ Publishing.
5
+
6
+ The package should be published primarily for `npx` MCP execution:
7
+
8
+ ```bash
9
+ npx -y @vucinatim/agentic-devtools mcp railway
10
+ ```
11
+
12
+ Library imports are supported, but they are not the main user onboarding path.
13
+
14
+ ## Recommended Flow
15
+
16
+ Use:
17
+
18
+ - GitHub public repo: `vucinatim/agentic-devtools`
19
+ - npm package: `@vucinatim/agentic-devtools`
20
+ - GitHub Actions CI
21
+ - npm Trusted Publishing through OIDC
22
+ - provenance attestations
23
+
24
+ Do not use long-lived npm automation tokens unless Trusted Publishing is blocked.
25
+
26
+ ## Why Trusted Publishing
27
+
28
+ npm Trusted Publishing creates a trust relationship between npm and the CI
29
+ provider through OIDC. For GitHub Actions, the workflow needs `id-token: write`
30
+ permission so npm can verify the workflow identity.
31
+
32
+ npm also publishes provenance attestations automatically when Trusted Publishing
33
+ is used from supported CI.
34
+
35
+ References:
36
+
37
+ - <https://docs.npmjs.com/trusted-publishers>
38
+ - <https://docs.npmjs.com/generating-provenance-statements>
39
+ - <https://docs.github.com/actions/automating-your-workflow-with-github-actions/publishing-nodejs-packages>
40
+
41
+ ## Release Management Recommendation
42
+
43
+ Start simple.
44
+
45
+ Phase 1:
46
+
47
+ - manually bump `package.json` version in a PR
48
+ - merge after CI passes
49
+ - publish from a manual GitHub Actions workflow
50
+ - create a GitHub release for the version tag
51
+
52
+ This avoids heavy release machinery while the package API is still settling.
53
+
54
+ Phase 2:
55
+
56
+ - add Changesets once releases become frequent
57
+ - require a changeset for user-visible package changes
58
+ - let the release PR update version and changelog
59
+ - publish after the release PR lands
60
+
61
+ Changesets is a good fit later because it makes the changelog user-facing rather
62
+ than deriving release notes only from commit messages.
63
+
64
+ Reference:
65
+
66
+ - <https://github.com/changesets/action>
67
+
68
+ ## Air Jam Reference
69
+
70
+ Air Jam has a more advanced public package release system:
71
+
72
+ - `release:public`
73
+ - `release:public:next`
74
+ - tag-triggered publishing
75
+ - selected package publishing
76
+ - release validation gates
77
+ - GitHub release creation
78
+ - `pnpm publish --provenance`
79
+
80
+ That is a strong setup for a multi-package repo. Agentic Devtools should borrow
81
+ the quality bar and provenance posture, not the full custom release machinery
82
+ yet.
83
+
84
+ For this repo, one package means a smaller workflow is cleaner.
85
+
86
+ ## Initial CI
87
+
88
+ CI should run on pull requests and pushes to `main`:
89
+
90
+ ```yaml
91
+ name: CI
92
+
93
+ on:
94
+ pull_request:
95
+ push:
96
+ branches:
97
+ - main
98
+
99
+ jobs:
100
+ checks:
101
+ runs-on: ubuntu-latest
102
+ steps:
103
+ - uses: actions/checkout@v5
104
+ - uses: actions/setup-node@v5
105
+ with:
106
+ node-version: "24"
107
+ cache: "npm"
108
+ - run: npm ci
109
+ - run: npm run check
110
+ ```
111
+
112
+ This repo currently uses npm because it is a single package. If it later moves
113
+ to pnpm, mirror Air Jam's `corepack` pattern.
114
+
115
+ ## Initial Publish Workflow
116
+
117
+ The first publish workflow should be manual:
118
+
119
+ ```yaml
120
+ name: Publish
121
+
122
+ on:
123
+ workflow_dispatch:
124
+ inputs:
125
+ tag:
126
+ description: "npm dist-tag"
127
+ required: true
128
+ default: "latest"
129
+ type: choice
130
+ options:
131
+ - latest
132
+ - next
133
+
134
+ permissions:
135
+ contents: write
136
+ id-token: write
137
+
138
+ jobs:
139
+ publish:
140
+ runs-on: ubuntu-latest
141
+ steps:
142
+ - uses: actions/checkout@v5
143
+ - uses: actions/setup-node@v5
144
+ with:
145
+ node-version: "24"
146
+ registry-url: "https://registry.npmjs.org"
147
+ cache: "npm"
148
+ - run: npm ci
149
+ - run: npm run check
150
+ - run: npm publish --access public --tag "${{ github.event.inputs.tag }}"
151
+ ```
152
+
153
+ After npm Trusted Publishing is configured for this exact repo and workflow,
154
+ `npm publish` should authenticate through OIDC without an npm token.
155
+
156
+ ## npm Trusted Publisher Setup
157
+
158
+ In npm package settings, configure a trusted publisher for:
159
+
160
+ - provider: GitHub Actions
161
+ - organization/user: `vucinatim`
162
+ - repository: `agentic-devtools`
163
+ - workflow file: `.github/workflows/publish.yml`
164
+ - environment: only if the workflow uses one
165
+
166
+ The workflow must keep:
167
+
168
+ ```yaml
169
+ permissions:
170
+ id-token: write
171
+ ```
172
+
173
+ ## Package Metadata
174
+
175
+ The public package should include:
176
+
177
+ ```json
178
+ {
179
+ "name": "@vucinatim/agentic-devtools",
180
+ "version": "0.1.0",
181
+ "private": false,
182
+ "description": "MCP-first devtools for AI agents.",
183
+ "type": "module",
184
+ "license": "MIT",
185
+ "repository": {
186
+ "type": "git",
187
+ "url": "git+https://github.com/vucinatim/agentic-devtools.git"
188
+ },
189
+ "homepage": "https://github.com/vucinatim/agentic-devtools#readme",
190
+ "bugs": {
191
+ "url": "https://github.com/vucinatim/agentic-devtools/issues"
192
+ },
193
+ "publishConfig": {
194
+ "access": "public"
195
+ }
196
+ }
197
+ ```
198
+
199
+ ## Pre-Publish Checks
200
+
201
+ Before every release:
202
+
203
+ - run tests
204
+ - run typecheck
205
+ - run build
206
+ - inspect `npm pack --dry-run`
207
+ - verify no credential files are included
208
+ - verify CLI entrypoint works from packed output
209
+ - verify README examples are current
210
+
211
+ `npm pack --dry-run` is mandatory before first publish.
@@ -0,0 +1,61 @@
1
+ # Testing
2
+
3
+ Agentic Devtools uses Vitest for package tests.
4
+
5
+ The test suite should validate three layers:
6
+
7
+ - service clients and auth resolution
8
+ - shared core helpers and registry behavior
9
+ - public package surfaces such as MCP-first CLI commands and exports
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ npm test
15
+ npm run test:namecheap
16
+ npm run test:railway
17
+ npm run coverage
18
+ npm run check
19
+ ```
20
+
21
+ `npm run check` is the CI gate. It runs:
22
+
23
+ 1. syntax validation for JavaScript source and test files
24
+ 2. Vitest with coverage thresholds
25
+ 3. production dependency audit
26
+ 4. package dry-run validation
27
+
28
+ ## Coverage Scope
29
+
30
+ Coverage measures library code under `src/`, excluding:
31
+
32
+ - `src/cli.mjs`
33
+ - `src/tools/*/mcp.mjs`
34
+
35
+ Those files are integration entrypoints. Their behavior should be covered by
36
+ CLI smoke tests and MCP-level tests, not line-by-line unit coverage.
37
+
38
+ Current minimum thresholds:
39
+
40
+ - statements: 65%
41
+ - branches: 50%
42
+ - functions: 65%
43
+ - lines: 65%
44
+
45
+ Raise these as the browser auth flow and MCP entrypoints get more direct test
46
+ coverage.
47
+
48
+ ## Package Validation
49
+
50
+ `npm run validate:package` runs `npm pack --dry-run --json` and verifies that
51
+ the public tarball:
52
+
53
+ - includes required runtime files
54
+ - excludes tests
55
+ - excludes GitHub workflow files
56
+ - excludes local auth files
57
+ - excludes environment files
58
+ - excludes generated tarballs
59
+
60
+ This is mandatory for a public package because package contents are part of the
61
+ security boundary.
package/docs/usage.md ADDED
@@ -0,0 +1,144 @@
1
+ # Usage
2
+
3
+ Agentic Devtools is primarily an MCP server package.
4
+
5
+ The default user should configure their agent host to run a specific tool with
6
+ `npx`. They do not need to add Agentic Devtools to their application
7
+ dependencies.
8
+
9
+ Run guided setup once if you want credentials stored locally instead of inline
10
+ in MCP host config:
11
+
12
+ ```bash
13
+ npx -y @vucinatim/agentic-devtools connect railway
14
+ npx -y @vucinatim/agentic-devtools connect namecheap
15
+ npx -y @vucinatim/agentic-devtools connect npm
16
+ ```
17
+
18
+ ## Primary: MCP Host With `npx`
19
+
20
+ Use this for Codex, Claude, or any MCP-compatible host.
21
+
22
+ Railway:
23
+
24
+ ```json
25
+ {
26
+ "mcpServers": {
27
+ "railway": {
28
+ "command": "npx",
29
+ "args": ["-y", "@vucinatim/agentic-devtools", "mcp", "railway"],
30
+ "env": {
31
+ "RAILWAY_API_TOKEN": "..."
32
+ }
33
+ }
34
+ }
35
+ }
36
+ ```
37
+
38
+ If `connect railway` has already saved a token locally, omit the `env` block.
39
+
40
+ Namecheap:
41
+
42
+ ```json
43
+ {
44
+ "mcpServers": {
45
+ "namecheap": {
46
+ "command": "npx",
47
+ "args": ["-y", "@vucinatim/agentic-devtools", "mcp", "namecheap"],
48
+ "env": {
49
+ "NAMECHEAP_API_USER": "...",
50
+ "NAMECHEAP_API_KEY": "...",
51
+ "NAMECHEAP_USERNAME": "...",
52
+ "NAMECHEAP_CLIENT_IP": "..."
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ If `connect namecheap` has already saved credentials locally, omit the `env`
60
+ block.
61
+
62
+ npm:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "npm": {
68
+ "command": "npx",
69
+ "args": ["-y", "@vucinatim/agentic-devtools", "mcp", "npm"]
70
+ }
71
+ }
72
+ }
73
+ ```
74
+
75
+ Use `connect npm` for guided token setup when package inspection or token
76
+ operations require authentication.
77
+
78
+ ## Secondary: Global CLI
79
+
80
+ Install globally only if you want repeated terminal access without `npx`:
81
+
82
+ ```bash
83
+ npm install -g @vucinatim/agentic-devtools
84
+ agentic-devtools tools
85
+ agentic-devtools connect railway
86
+ agentic-devtools connect npm
87
+ agentic-devtools auth-status railway
88
+ agentic-devtools test-connection railway
89
+ ```
90
+
91
+ ## Tertiary: Project Dependency
92
+
93
+ Install into a project only when you want to build custom automation using the
94
+ service clients:
95
+
96
+ ```bash
97
+ npm install @vucinatim/agentic-devtools
98
+ ```
99
+
100
+ ```js
101
+ import { createRailwayClient } from "@vucinatim/agentic-devtools";
102
+ import { createNamecheapClient } from "@vucinatim/agentic-devtools";
103
+ import { createNpmClient } from "@vucinatim/agentic-devtools";
104
+ ```
105
+
106
+ ## Auth
107
+
108
+ Railway supports:
109
+
110
+ - guided local setup through `agentic-devtools connect railway`
111
+ - `RAILWAY_PROJECT_TOKEN` for project-scoped inspection
112
+ - `RAILWAY_API_TOKEN` or `RAILWAY_TOKEN` for account-scoped inspection
113
+ - `RAILWAY_PROJECT_ID` as an optional default project id
114
+
115
+ Namecheap supports:
116
+
117
+ - guided local setup through `agentic-devtools connect namecheap`
118
+ - `NAMECHEAP_API_USER`
119
+ - `NAMECHEAP_API_KEY`
120
+ - `NAMECHEAP_USERNAME`
121
+ - `NAMECHEAP_CLIENT_IP`
122
+ - `NAMECHEAP_API_SANDBOX=1` for sandbox usage
123
+ - `NAMECHEAP_API_BASE_URL` for an explicit endpoint override
124
+
125
+ npm supports:
126
+
127
+ - guided local setup through `agentic-devtools connect npm`
128
+ - `NPM_TOKEN`
129
+ - `NODE_AUTH_TOKEN`
130
+ - `.npmrc` auth token resolution
131
+ - `NPM_CONFIG_REGISTRY` for registry override
132
+
133
+ For publishing, prefer GitHub Actions Trusted Publishing. Local publishing is
134
+ available through the npm MCP client but defaults to dry-run and requires an
135
+ explicit confirmation string for real publishes.
136
+
137
+ ## Usage Philosophy
138
+
139
+ This package should feel like a focused tool belt for agents:
140
+
141
+ - MCP execution via `npx` is the normal path
142
+ - global CLI is a convenience path
143
+ - project dependency usage is for custom builders
144
+ - host adapters should stay thin and point back to the same package runtime
package/package.json ADDED
@@ -0,0 +1,78 @@
1
+ {
2
+ "name": "@vucinatim/agentic-devtools",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "MCP-first devtools for AI agents.",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "author": {
9
+ "name": "Tim Vucina",
10
+ "email": "tim@vucina.com",
11
+ "url": "https://github.com/vucinatim"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/vucinatim/agentic-devtools.git"
16
+ },
17
+ "homepage": "https://github.com/vucinatim/agentic-devtools#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/vucinatim/agentic-devtools/issues"
20
+ },
21
+ "keywords": [
22
+ "agentic-devtools",
23
+ "mcp",
24
+ "mcp-server",
25
+ "agents",
26
+ "ai-agents",
27
+ "namecheap",
28
+ "railway",
29
+ "npm",
30
+ "developer-tools"
31
+ ],
32
+ "bin": {
33
+ "agentic-devtools": "src/cli.mjs"
34
+ },
35
+ "exports": {
36
+ ".": "./src/index.mjs",
37
+ "./namecheap": "./src/tools/namecheap/client.mjs",
38
+ "./railway": "./src/tools/railway/client.mjs",
39
+ "./npm": "./src/tools/npm/client.mjs"
40
+ },
41
+ "files": [
42
+ "src",
43
+ "adapters",
44
+ "docs",
45
+ "README.md",
46
+ "LICENSE",
47
+ "SECURITY.md"
48
+ ],
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "engines": {
53
+ "node": ">=20"
54
+ },
55
+ "scripts": {
56
+ "build": "node scripts/check-syntax.mjs",
57
+ "check": "npm run build && npm run coverage && npm run audit && npm run validate:package",
58
+ "coverage": "vitest run --coverage",
59
+ "audit": "npm audit --omit=dev",
60
+ "pack:dry": "npm pack --dry-run",
61
+ "test": "vitest run",
62
+ "test:watch": "vitest",
63
+ "test:namecheap": "vitest run tests/tools/namecheap",
64
+ "test:railway": "vitest run tests/tools/railway",
65
+ "validate:package": "node scripts/validate-package.mjs"
66
+ },
67
+ "dependencies": {
68
+ "@modelcontextprotocol/sdk": "1.29.0",
69
+ "fast-xml-parser": "^5.8.0",
70
+ "zod": "^4.2.1"
71
+ },
72
+ "devDependencies": {
73
+ "@emnapi/core": "^1.10.0",
74
+ "@emnapi/runtime": "^1.10.0",
75
+ "@vitest/coverage-v8": "^4.1.6",
76
+ "vitest": "^4.1.6"
77
+ }
78
+ }
package/src/cli.mjs ADDED
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { printJson } from "./core/result.mjs";
4
+ import { getTool, listTools } from "./core/tool-registry.mjs";
5
+
6
+ const usage = () => `Usage:
7
+ agentic-devtools tools
8
+ agentic-devtools mcp <namecheap|railway|npm>
9
+ agentic-devtools connect <namecheap|railway|npm>
10
+ agentic-devtools disconnect <namecheap|railway|npm>
11
+ agentic-devtools auth-status <namecheap|railway|npm>
12
+ agentic-devtools test-connection <namecheap|railway|npm>
13
+
14
+ Environment:
15
+ Namecheap: NAMECHEAP_API_USER, NAMECHEAP_API_KEY, NAMECHEAP_USERNAME, NAMECHEAP_CLIENT_IP
16
+ Railway: RAILWAY_PROJECT_TOKEN or RAILWAY_API_TOKEN / RAILWAY_TOKEN
17
+ npm: NPM_TOKEN or NODE_AUTH_TOKEN
18
+ `;
19
+
20
+ const args = process.argv.slice(2);
21
+
22
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
23
+ process.stdout.write(usage());
24
+ process.exit(0);
25
+ }
26
+
27
+ if (args[0] === "tools") {
28
+ printJson(listTools());
29
+ process.exit(0);
30
+ }
31
+
32
+ if (args[0] === "mcp") {
33
+ const toolName = args[1];
34
+ if (!toolName) {
35
+ throw new Error("Missing tool name for mcp command.");
36
+ }
37
+ const tool = getTool(toolName);
38
+ await import(tool.mcpModule);
39
+ process.exit(0);
40
+ }
41
+
42
+ if (args[0] === "auth-status") {
43
+ const toolName = args[1];
44
+ if (toolName === "namecheap") {
45
+ const { getAuthStatus } = await import("./tools/namecheap/auth.mjs");
46
+ printJson(await getAuthStatus());
47
+ process.exit(0);
48
+ }
49
+ if (toolName === "railway") {
50
+ const { getRailwayAuthStatus } = await import("./tools/railway/client.mjs");
51
+ printJson(getRailwayAuthStatus());
52
+ process.exit(0);
53
+ }
54
+ if (toolName === "npm") {
55
+ const { getNpmAuthStatus } = await import("./tools/npm/auth.mjs");
56
+ printJson(getNpmAuthStatus());
57
+ process.exit(0);
58
+ }
59
+ throw new Error("auth-status expects one of: namecheap, railway, npm");
60
+ }
61
+
62
+ if (args[0] === "connect") {
63
+ const toolName = args[1];
64
+ if (toolName === "namecheap") {
65
+ const { runBrowserAuthFlow } = await import("./tools/namecheap/auth.mjs");
66
+ process.stderr.write("Opening Namecheap browser setup flow...\n");
67
+ printJson(await runBrowserAuthFlow());
68
+ process.exit(0);
69
+ }
70
+ if (toolName === "railway") {
71
+ const { runRailwayBrowserAuthFlow } = await import(
72
+ "./tools/railway/auth.mjs"
73
+ );
74
+ process.stderr.write("Opening Railway browser setup flow...\n");
75
+ printJson(await runRailwayBrowserAuthFlow());
76
+ process.exit(0);
77
+ }
78
+ if (toolName === "npm") {
79
+ const { runNpmBrowserAuthFlow } = await import("./tools/npm/auth.mjs");
80
+ process.stderr.write("Opening npm browser setup flow...\n");
81
+ printJson(
82
+ await runNpmBrowserAuthFlow({
83
+ onReady: ({ url }) => {
84
+ process.stderr.write(`npm setup URL: ${url}\n`);
85
+ },
86
+ }),
87
+ );
88
+ process.exit(0);
89
+ }
90
+ throw new Error("connect expects one of: namecheap, railway, npm");
91
+ }
92
+
93
+ if (args[0] === "disconnect") {
94
+ const toolName = args[1];
95
+ if (toolName === "namecheap") {
96
+ const { disconnectNamecheap } = await import("./tools/namecheap/auth.mjs");
97
+ printJson(await disconnectNamecheap());
98
+ process.exit(0);
99
+ }
100
+ if (toolName === "railway") {
101
+ const { disconnectRailway } = await import("./tools/railway/auth.mjs");
102
+ printJson(await disconnectRailway());
103
+ process.exit(0);
104
+ }
105
+ if (toolName === "npm") {
106
+ const { disconnectNpm } = await import("./tools/npm/auth.mjs");
107
+ printJson(await disconnectNpm());
108
+ process.exit(0);
109
+ }
110
+ throw new Error("disconnect expects one of: namecheap, railway, npm");
111
+ }
112
+
113
+ if (args[0] === "test-connection") {
114
+ const toolName = args[1];
115
+ if (toolName === "namecheap") {
116
+ const { createResolvedNamecheapClient } = await import(
117
+ "./tools/namecheap/client.mjs"
118
+ );
119
+ const client = await createResolvedNamecheapClient();
120
+ const result = await client.listDomains({ page: 1, pageSize: 1 });
121
+ printJson({
122
+ ok: true,
123
+ domainCount: result.paging.totalItems,
124
+ sampleDomains: result.domains.slice(0, 1).map((domain) => domain.name),
125
+ baseUrl: client.baseUrl,
126
+ });
127
+ process.exit(0);
128
+ }
129
+ if (toolName === "railway") {
130
+ const { createRailwayClient } = await import("./tools/railway/client.mjs");
131
+ const client = createRailwayClient();
132
+ const result =
133
+ client.auth.kind === "project"
134
+ ? await client.getProjectTokenContext()
135
+ : await client.getCurrentViewer();
136
+ printJson({
137
+ ok: true,
138
+ tokenSource: client.auth.source,
139
+ tokenKind: client.auth.kind,
140
+ result,
141
+ });
142
+ process.exit(0);
143
+ }
144
+ if (toolName === "npm") {
145
+ const { createNpmClient } = await import("./tools/npm/client.mjs");
146
+ const client = createNpmClient();
147
+ printJson({
148
+ ok: true,
149
+ tokenSource: client.auth.source,
150
+ registry: client.registry,
151
+ user: await client.getCurrentUser(),
152
+ });
153
+ process.exit(0);
154
+ }
155
+ throw new Error("test-connection expects one of: namecheap, railway, npm");
156
+ }
157
+
158
+ throw new Error(`Unknown command "${args[0]}".\n\n${usage()}`);