@dirxai/cli 0.1.0 → 0.2.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 +21 -0
- package/README.md +56 -68
- package/dist/index.js +960 -0
- package/package.json +45 -38
- package/install.js +0 -92
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DirX AI
|
|
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
CHANGED
|
@@ -1,101 +1,89 @@
|
|
|
1
1
|
# DirX — Unified Gateway & CLI for Agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@dirxai/cli)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
dirx ls / # discover registered services
|
|
7
|
-
dirx cat /net/api.github.com # read service info
|
|
8
|
-
dirx grep "issues" api.github.com # search endpoints
|
|
9
|
-
dirx write /net/api.github.com/owner/repo/issues --json '{"title":"Bug"}'
|
|
10
|
-
dirx bash 'cat /net | cat /net/api.github.com'
|
|
11
|
-
```
|
|
6
|
+
DirX gives AI agents a unified, file-system-like interface to discover and interact with internet APIs — with built-in governance, access control, and auditing.
|
|
12
7
|
|
|
13
8
|
## Install
|
|
14
9
|
|
|
15
|
-
### npm (recommended)
|
|
16
|
-
|
|
17
10
|
```bash
|
|
18
11
|
npm install -g @dirxai/cli
|
|
19
12
|
```
|
|
20
13
|
|
|
21
|
-
|
|
14
|
+
## Quick Start
|
|
22
15
|
|
|
23
16
|
```bash
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```
|
|
17
|
+
# Authenticate with the gateway
|
|
18
|
+
dirx auth
|
|
27
19
|
|
|
28
|
-
|
|
20
|
+
# Browse the API directory
|
|
21
|
+
dirx ls /
|
|
22
|
+
dirx ls /net/
|
|
29
23
|
|
|
30
|
-
|
|
24
|
+
# Read service descriptions
|
|
25
|
+
dirx cat /net/api.github.com/DIR.md
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
| macOS ARM64 | `dirx-aarch64-apple-darwin.tar.gz` |
|
|
35
|
-
| macOS x86_64 | `dirx-x86_64-apple-darwin.tar.gz` |
|
|
36
|
-
| Linux x86_64 | `dirx-x86_64-unknown-linux-gnu.tar.gz` |
|
|
27
|
+
# Search across services
|
|
28
|
+
dirx grep "weather" /net/
|
|
37
29
|
|
|
38
|
-
|
|
30
|
+
# Write data
|
|
31
|
+
dirx write /net/api.example.com/data -d '{"key": "value"}'
|
|
39
32
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
sudo mv dirx /usr/local/bin/
|
|
33
|
+
# Manage API keys (BYOK)
|
|
34
|
+
dirx keys set api.github.com --token ghp_xxxx --sync
|
|
43
35
|
```
|
|
44
36
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
# 1. Authenticate
|
|
49
|
-
dirx auth --server https://api.dirx.ai --json '{}'
|
|
37
|
+
## Commands
|
|
50
38
|
|
|
51
|
-
|
|
52
|
-
dirx ls /
|
|
53
|
-
dirx ls /net
|
|
39
|
+
### Agent Commands
|
|
54
40
|
|
|
55
|
-
|
|
56
|
-
|
|
41
|
+
| Command | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| `dirx ls <path>` | List directory contents |
|
|
44
|
+
| `dirx cat <path>` | Read file contents |
|
|
45
|
+
| `dirx write <path>` | Write data |
|
|
46
|
+
| `dirx edit <path>` | Partial update |
|
|
47
|
+
| `dirx grep <pattern> <path>` | Search across services |
|
|
48
|
+
| `dirx bash <pipeline>` | Execute multi-step pipeline |
|
|
57
49
|
|
|
58
|
-
|
|
59
|
-
dirx grep "pull requests"
|
|
50
|
+
### Developer Tools
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
dirx
|
|
52
|
+
| Command | Description |
|
|
53
|
+
|---------|-------------|
|
|
54
|
+
| `dirx init [dir]` | Initialize DirX in a project |
|
|
55
|
+
| `dirx generate [dir]` | Scan and detect route definitions |
|
|
56
|
+
| `dirx claim <domain>` | Claim domain via DNS verification |
|
|
57
|
+
| `dirx register` | Register DIR.md with the gateway |
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
dirx bash 'cat /net/api.github.com | write /log'
|
|
67
|
-
```
|
|
59
|
+
### Configuration
|
|
68
60
|
|
|
69
|
-
|
|
61
|
+
| Command | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `dirx auth` | Authenticate with the gateway |
|
|
64
|
+
| `dirx keys set <domain>` | Set an API key |
|
|
65
|
+
| `dirx keys list` | List stored keys |
|
|
66
|
+
| `dirx keys remove <domain>` | Remove a key |
|
|
67
|
+
| `dirx status` | Show CLI config and auth status |
|
|
70
68
|
|
|
71
|
-
|
|
69
|
+
## Environment Variables
|
|
72
70
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```
|
|
71
|
+
| Variable | Description | Default |
|
|
72
|
+
|----------|-------------|---------|
|
|
73
|
+
| `DIRX_GATEWAY_URL` | Gateway URL | `https://api.dirx.ai` |
|
|
74
|
+
| `DIRX_TOKEN` | Agent token | — |
|
|
75
|
+
| `DIRX_HOME` | Config directory | `~/.dirx` |
|
|
79
76
|
|
|
80
|
-
##
|
|
77
|
+
## Development
|
|
81
78
|
|
|
79
|
+
```bash
|
|
80
|
+
git clone https://github.com/dirxai/dirx.git
|
|
81
|
+
cd dirx
|
|
82
|
+
npm install
|
|
83
|
+
npm run build
|
|
84
|
+
npm run lint
|
|
82
85
|
```
|
|
83
|
-
CLI (Rust) ──HTTP──▶ DirX Server (Go) ──▶ Adapters ──▶ External APIs
|
|
84
|
-
│ Registry │ GitHub
|
|
85
|
-
│ Router │ OpenAI
|
|
86
|
-
│ Auth (JWKS) │ Custom...
|
|
87
|
-
│ Policy
|
|
88
|
-
│ Audit
|
|
89
|
-
│ BYOK
|
|
90
|
-
│ Search
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Documentation
|
|
94
|
-
|
|
95
|
-
- [dirx.ai](https://dirx.ai) — official site
|
|
96
|
-
- [API Reference](https://dirx.ai/docs/api)
|
|
97
|
-
- [Developer Guide](https://dirx.ai/docs/developers)
|
|
98
86
|
|
|
99
87
|
## License
|
|
100
88
|
|
|
101
|
-
MIT
|
|
89
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,960 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync, statSync, chmodSync } from 'fs';
|
|
3
|
+
import { dirname, resolve, join, extname, relative } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { readFile } from 'fs/promises';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { Command } from 'commander';
|
|
8
|
+
|
|
9
|
+
var __defProp = Object.defineProperty;
|
|
10
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
+
var __esm = (fn, res) => function __init() {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
};
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/credentials.ts
|
|
20
|
+
var credentials_exports = {};
|
|
21
|
+
__export(credentials_exports, {
|
|
22
|
+
getAgentToken: () => getAgentToken,
|
|
23
|
+
getToken: () => getToken,
|
|
24
|
+
saveAgentToken: () => saveAgentToken,
|
|
25
|
+
saveToken: () => saveToken
|
|
26
|
+
});
|
|
27
|
+
function credentialsPath() {
|
|
28
|
+
const dirxHome = process.env.DIRX_HOME ?? join(homedir(), ".dirx");
|
|
29
|
+
return join(dirxHome, "credentials.json");
|
|
30
|
+
}
|
|
31
|
+
function loadStore() {
|
|
32
|
+
try {
|
|
33
|
+
const text = readFileSync(credentialsPath(), "utf-8");
|
|
34
|
+
return JSON.parse(text);
|
|
35
|
+
} catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function saveStore(store) {
|
|
40
|
+
const filePath = credentialsPath();
|
|
41
|
+
const dir = join(filePath, "..");
|
|
42
|
+
mkdirSync(dir, { recursive: true });
|
|
43
|
+
writeFileSync(filePath, JSON.stringify(store, null, 2));
|
|
44
|
+
try {
|
|
45
|
+
chmodSync(filePath, 384);
|
|
46
|
+
} catch {
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function decodeJwtPayload(token) {
|
|
50
|
+
try {
|
|
51
|
+
const parts = token.split(".");
|
|
52
|
+
if (parts.length !== 3) return {};
|
|
53
|
+
const payload = JSON.parse(
|
|
54
|
+
Buffer.from(parts[1], "base64url").toString("utf-8")
|
|
55
|
+
);
|
|
56
|
+
return { iat: payload.iat, exp: payload.exp };
|
|
57
|
+
} catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function saveAgentToken(gatewayUrl, token) {
|
|
62
|
+
const store = loadStore();
|
|
63
|
+
const { iat, exp } = decodeJwtPayload(token);
|
|
64
|
+
store.agentToken = {
|
|
65
|
+
gatewayUrl,
|
|
66
|
+
token,
|
|
67
|
+
issuedAt: iat,
|
|
68
|
+
expiresAt: exp
|
|
69
|
+
};
|
|
70
|
+
saveStore(store);
|
|
71
|
+
}
|
|
72
|
+
async function getAgentToken() {
|
|
73
|
+
const store = loadStore();
|
|
74
|
+
if (!store.agentToken) return null;
|
|
75
|
+
if (store.agentToken.expiresAt) {
|
|
76
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
77
|
+
if (now >= store.agentToken.expiresAt) return null;
|
|
78
|
+
}
|
|
79
|
+
return store.agentToken;
|
|
80
|
+
}
|
|
81
|
+
async function saveToken(domain, token) {
|
|
82
|
+
const store = loadStore();
|
|
83
|
+
const { iat, exp } = decodeJwtPayload(token);
|
|
84
|
+
if (!store.tokens) store.tokens = {};
|
|
85
|
+
store.tokens[domain] = { token, issuedAt: iat, expiresAt: exp };
|
|
86
|
+
saveStore(store);
|
|
87
|
+
}
|
|
88
|
+
async function getToken(domain) {
|
|
89
|
+
const store = loadStore();
|
|
90
|
+
const entry = store.tokens?.[domain];
|
|
91
|
+
if (!entry) return null;
|
|
92
|
+
if (entry.expiresAt) {
|
|
93
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
94
|
+
if (now >= entry.expiresAt) return null;
|
|
95
|
+
}
|
|
96
|
+
return entry.token;
|
|
97
|
+
}
|
|
98
|
+
var init_credentials = __esm({
|
|
99
|
+
"src/credentials.ts"() {
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// src/commands/auth.ts
|
|
104
|
+
var auth_exports = {};
|
|
105
|
+
__export(auth_exports, {
|
|
106
|
+
runAuth: () => runAuth
|
|
107
|
+
});
|
|
108
|
+
async function runAuth(opts) {
|
|
109
|
+
const gatewayUrl = opts.gatewayUrl ?? process.env.DIRX_GATEWAY_URL ?? "https://api.dirx.ai";
|
|
110
|
+
const sub = `agent-${Date.now()}`;
|
|
111
|
+
const res = await fetch(`${gatewayUrl.replace(/\/$/, "")}/auth/token`, {
|
|
112
|
+
method: "POST",
|
|
113
|
+
headers: { "Content-Type": "application/json" },
|
|
114
|
+
body: JSON.stringify({
|
|
115
|
+
sub,
|
|
116
|
+
svc: "gateway",
|
|
117
|
+
roles: ["agent"],
|
|
118
|
+
expiresIn: "24h"
|
|
119
|
+
})
|
|
120
|
+
});
|
|
121
|
+
if (!res.ok) {
|
|
122
|
+
const body = await res.text();
|
|
123
|
+
throw new Error(`Auth failed (${res.status}): ${body}`);
|
|
124
|
+
}
|
|
125
|
+
const { token } = await res.json();
|
|
126
|
+
await saveAgentToken(gatewayUrl, token);
|
|
127
|
+
console.log("Authenticated with gateway");
|
|
128
|
+
console.log(` Token saved to ~/.dirx/credentials.json`);
|
|
129
|
+
console.log(` Gateway: ${gatewayUrl}`);
|
|
130
|
+
}
|
|
131
|
+
var init_auth = __esm({
|
|
132
|
+
"src/commands/auth.ts"() {
|
|
133
|
+
init_credentials();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// src/commands/init.ts
|
|
138
|
+
var init_exports = {};
|
|
139
|
+
__export(init_exports, {
|
|
140
|
+
runInit: () => runInit
|
|
141
|
+
});
|
|
142
|
+
async function runInit(dir) {
|
|
143
|
+
const target = dir === "." ? process.cwd() : dir;
|
|
144
|
+
const dirMdPath = join(target, "DIR.md");
|
|
145
|
+
const dirJsonPath = join(target, "dir.json");
|
|
146
|
+
if (existsSync(dirMdPath)) {
|
|
147
|
+
console.log(`Already initialized: ${dirMdPath} exists`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const { lang, framework } = detectFramework(target);
|
|
151
|
+
mkdirSync(target, { recursive: true });
|
|
152
|
+
const mdContent = [
|
|
153
|
+
"# My Service",
|
|
154
|
+
"",
|
|
155
|
+
"Describe your service here.",
|
|
156
|
+
"",
|
|
157
|
+
"## Stack",
|
|
158
|
+
"",
|
|
159
|
+
`- Language: ${lang}`,
|
|
160
|
+
`- Framework: ${framework}`,
|
|
161
|
+
"",
|
|
162
|
+
"## Endpoints",
|
|
163
|
+
"",
|
|
164
|
+
"- `GET /` \u2014 health check",
|
|
165
|
+
""
|
|
166
|
+
].join("\n");
|
|
167
|
+
const jsonContent = {
|
|
168
|
+
title: "My Service",
|
|
169
|
+
description: "A DirX-compatible service",
|
|
170
|
+
base_url: "https://api.example.com",
|
|
171
|
+
actions: ["ls", "read"],
|
|
172
|
+
endpoints: [
|
|
173
|
+
{ path: "/", method: "GET", description: "Health check" }
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
writeFileSync(dirMdPath, mdContent);
|
|
177
|
+
writeFileSync(dirJsonPath, JSON.stringify(jsonContent, null, 2));
|
|
178
|
+
console.log(`Initialized DirX project in ${target}`);
|
|
179
|
+
console.log(` Detected: ${lang} / ${framework}`);
|
|
180
|
+
console.log(` Created: DIR.md, dir.json`);
|
|
181
|
+
console.log(`
|
|
182
|
+
Next steps:`);
|
|
183
|
+
console.log(` 1. Edit DIR.md to describe your service`);
|
|
184
|
+
console.log(` 2. Run 'dirx generate' to scan routes`);
|
|
185
|
+
console.log(` 3. Run 'dirx register --domain <your-domain>' to publish`);
|
|
186
|
+
}
|
|
187
|
+
function detectFramework(target) {
|
|
188
|
+
if (existsSync(join(target, "Cargo.toml"))) {
|
|
189
|
+
return { lang: "rust", framework: "axum" };
|
|
190
|
+
}
|
|
191
|
+
if (existsSync(join(target, "go.mod"))) {
|
|
192
|
+
return { lang: "go", framework: "net/http" };
|
|
193
|
+
}
|
|
194
|
+
if (existsSync(join(target, "package.json"))) {
|
|
195
|
+
if (existsSync(join(target, "next.config.js")) || existsSync(join(target, "next.config.mjs"))) {
|
|
196
|
+
return { lang: "node", framework: "nextjs" };
|
|
197
|
+
}
|
|
198
|
+
return { lang: "node", framework: "express" };
|
|
199
|
+
}
|
|
200
|
+
if (existsSync(join(target, "requirements.txt")) || existsSync(join(target, "pyproject.toml"))) {
|
|
201
|
+
return { lang: "python", framework: "fastapi" };
|
|
202
|
+
}
|
|
203
|
+
return { lang: "unknown", framework: "generic" };
|
|
204
|
+
}
|
|
205
|
+
var init_init = __esm({
|
|
206
|
+
"src/commands/init.ts"() {
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// src/commands/generate.ts
|
|
211
|
+
var generate_exports = {};
|
|
212
|
+
__export(generate_exports, {
|
|
213
|
+
runGenerate: () => runGenerate
|
|
214
|
+
});
|
|
215
|
+
async function runGenerate(dir) {
|
|
216
|
+
const target = dir === "." ? process.cwd() : dir;
|
|
217
|
+
const dirJsonPath = join(target, "dir.json");
|
|
218
|
+
if (!existsSync(dirJsonPath)) {
|
|
219
|
+
console.error(
|
|
220
|
+
`dir.json not found in ${target}. Run 'dirx init' first.`
|
|
221
|
+
);
|
|
222
|
+
process.exitCode = 1;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const { framework } = detectFrameworkFromPackage(target);
|
|
226
|
+
const patterns = getRoutePatterns(framework);
|
|
227
|
+
const routes = [];
|
|
228
|
+
if (patterns.length > 0) {
|
|
229
|
+
scanDir(target, target, patterns, framework, routes, 0);
|
|
230
|
+
}
|
|
231
|
+
console.log(`Scanned ${target} (${framework})`);
|
|
232
|
+
if (routes.length > 0) {
|
|
233
|
+
console.log(`
|
|
234
|
+
Found ${routes.length} file(s) with route definitions:
|
|
235
|
+
`);
|
|
236
|
+
for (const r of routes) {
|
|
237
|
+
console.log(` ${r.file} (${r.framework})`);
|
|
238
|
+
}
|
|
239
|
+
} else {
|
|
240
|
+
console.log(`
|
|
241
|
+
No route definitions detected.`);
|
|
242
|
+
}
|
|
243
|
+
console.log(`
|
|
244
|
+
Edit DIR.md and dir.json to describe your API.`);
|
|
245
|
+
}
|
|
246
|
+
function detectFrameworkFromPackage(target) {
|
|
247
|
+
const pkgPath = join(target, "package.json");
|
|
248
|
+
if (existsSync(pkgPath)) {
|
|
249
|
+
try {
|
|
250
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
251
|
+
const deps = {
|
|
252
|
+
...pkg.dependencies,
|
|
253
|
+
...pkg.devDependencies
|
|
254
|
+
};
|
|
255
|
+
if (deps.next) return { framework: "nextjs" };
|
|
256
|
+
if (deps.hono) return { framework: "hono" };
|
|
257
|
+
if (deps.fastify) return { framework: "fastify" };
|
|
258
|
+
if (deps.express) return { framework: "express" };
|
|
259
|
+
} catch {
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (existsSync(join(target, "go.mod")))
|
|
263
|
+
return { framework: "go" };
|
|
264
|
+
if (existsSync(join(target, "Cargo.toml")))
|
|
265
|
+
return { framework: "axum" };
|
|
266
|
+
if (existsSync(join(target, "requirements.txt")) || existsSync(join(target, "pyproject.toml")))
|
|
267
|
+
return { framework: "fastapi" };
|
|
268
|
+
return { framework: "generic" };
|
|
269
|
+
}
|
|
270
|
+
function getRoutePatterns(framework) {
|
|
271
|
+
switch (framework) {
|
|
272
|
+
case "express":
|
|
273
|
+
case "hono":
|
|
274
|
+
case "fastify":
|
|
275
|
+
return [".get(", ".post(", ".put(", ".delete(", ".patch(", "router."];
|
|
276
|
+
case "nextjs":
|
|
277
|
+
return ["export default", "export async function"];
|
|
278
|
+
case "fastapi":
|
|
279
|
+
return ["@app.get", "@app.post", "@app.put", "@app.delete", "@router."];
|
|
280
|
+
case "go":
|
|
281
|
+
return ["HandleFunc(", "Get(", "Post(", "Put(", "Delete(", "r.Route("];
|
|
282
|
+
case "axum":
|
|
283
|
+
return [".route(", ".get(", ".post(", ".put(", ".delete("];
|
|
284
|
+
default:
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function scanDir(root, dir, patterns, framework, routes, depth) {
|
|
289
|
+
if (depth > MAX_SCAN_DEPTH) return;
|
|
290
|
+
let entries;
|
|
291
|
+
try {
|
|
292
|
+
entries = readdirSync(dir);
|
|
293
|
+
} catch {
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
for (const name of entries) {
|
|
297
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
298
|
+
const fullPath = join(dir, name);
|
|
299
|
+
let stat;
|
|
300
|
+
try {
|
|
301
|
+
stat = statSync(fullPath);
|
|
302
|
+
} catch {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (stat.isDirectory()) {
|
|
306
|
+
scanDir(root, fullPath, patterns, framework, routes, depth + 1);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (!stat.isFile()) continue;
|
|
310
|
+
if (!SCAN_EXTENSIONS.has(extname(name))) continue;
|
|
311
|
+
try {
|
|
312
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
313
|
+
if (patterns.some((p) => content.includes(p))) {
|
|
314
|
+
routes.push({
|
|
315
|
+
file: relative(root, fullPath),
|
|
316
|
+
framework
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
} catch {
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
var MAX_SCAN_DEPTH, SKIP_DIRS, SCAN_EXTENSIONS;
|
|
324
|
+
var init_generate = __esm({
|
|
325
|
+
"src/commands/generate.ts"() {
|
|
326
|
+
MAX_SCAN_DEPTH = 8;
|
|
327
|
+
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
328
|
+
"node_modules",
|
|
329
|
+
".git",
|
|
330
|
+
"target",
|
|
331
|
+
"__pycache__",
|
|
332
|
+
"vendor",
|
|
333
|
+
"dist",
|
|
334
|
+
"build",
|
|
335
|
+
".next"
|
|
336
|
+
]);
|
|
337
|
+
SCAN_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
338
|
+
".ts",
|
|
339
|
+
".tsx",
|
|
340
|
+
".js",
|
|
341
|
+
".jsx",
|
|
342
|
+
".py",
|
|
343
|
+
".go",
|
|
344
|
+
".rs"
|
|
345
|
+
]);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
// src/commands/claim.ts
|
|
350
|
+
var claim_exports = {};
|
|
351
|
+
__export(claim_exports, {
|
|
352
|
+
runClaim: () => runClaim
|
|
353
|
+
});
|
|
354
|
+
async function runClaim(options) {
|
|
355
|
+
const { gatewayUrl, domain, verify } = options;
|
|
356
|
+
const baseUrl = gatewayUrl.replace(/\/$/, "");
|
|
357
|
+
if (verify) {
|
|
358
|
+
await verifyClaim(baseUrl, domain);
|
|
359
|
+
} else {
|
|
360
|
+
await requestChallenge(baseUrl, domain);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
async function requestChallenge(baseUrl, domain) {
|
|
364
|
+
const res = await fetch(`${baseUrl}/domains/challenge`, {
|
|
365
|
+
method: "POST",
|
|
366
|
+
headers: { "Content-Type": "application/json" },
|
|
367
|
+
body: JSON.stringify({ domain })
|
|
368
|
+
});
|
|
369
|
+
if (!res.ok) {
|
|
370
|
+
const body = await res.text();
|
|
371
|
+
throw new Error(`Challenge request failed (${res.status}): ${body}`);
|
|
372
|
+
}
|
|
373
|
+
const data = await res.json();
|
|
374
|
+
const expires = new Date(data.expiresAt).toISOString();
|
|
375
|
+
console.log(`
|
|
376
|
+
Domain claim initiated for ${data.domain}
|
|
377
|
+
`);
|
|
378
|
+
console.log(`Add the following DNS TXT record:
|
|
379
|
+
`);
|
|
380
|
+
console.log(` Name: ${data.txtRecord}`);
|
|
381
|
+
console.log(` Value: ${data.txtValue}
|
|
382
|
+
`);
|
|
383
|
+
console.log(`This challenge expires at ${expires}.
|
|
384
|
+
`);
|
|
385
|
+
console.log(`After adding the record, verify with:`);
|
|
386
|
+
console.log(` dirx claim ${domain} --verify
|
|
387
|
+
`);
|
|
388
|
+
console.log(`Tip: You can check propagation with:`);
|
|
389
|
+
console.log(` dig TXT ${data.txtRecord}`);
|
|
390
|
+
}
|
|
391
|
+
async function verifyClaim(baseUrl, domain) {
|
|
392
|
+
const res = await fetch(`${baseUrl}/domains/verify`, {
|
|
393
|
+
method: "POST",
|
|
394
|
+
headers: { "Content-Type": "application/json" },
|
|
395
|
+
body: JSON.stringify({ domain })
|
|
396
|
+
});
|
|
397
|
+
const data = await res.json();
|
|
398
|
+
if (!res.ok) {
|
|
399
|
+
const error = data.error || "Verification failed";
|
|
400
|
+
console.error(`
|
|
401
|
+
Verification failed: ${error}
|
|
402
|
+
`);
|
|
403
|
+
console.log(`Troubleshooting:`);
|
|
404
|
+
console.log(
|
|
405
|
+
` 1. Check the record exists: dig TXT _dirx.${domain}`
|
|
406
|
+
);
|
|
407
|
+
console.log(` 2. DNS propagation can take up to 5 minutes`);
|
|
408
|
+
console.log(
|
|
409
|
+
` 3. Ensure the TXT value matches exactly (including "dirx-verify=" prefix)
|
|
410
|
+
`
|
|
411
|
+
);
|
|
412
|
+
process.exitCode = 1;
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const publishToken = data.publishToken;
|
|
416
|
+
await saveToken(domain, publishToken);
|
|
417
|
+
console.log(`
|
|
418
|
+
Domain ${domain} verified successfully!`);
|
|
419
|
+
console.log(`Publish token saved to ~/.dirx/credentials.json
|
|
420
|
+
`);
|
|
421
|
+
console.log(`You can now register services:`);
|
|
422
|
+
console.log(` dirx register --domain ${domain}`);
|
|
423
|
+
}
|
|
424
|
+
var init_claim = __esm({
|
|
425
|
+
"src/commands/claim.ts"() {
|
|
426
|
+
init_credentials();
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// src/commands/register.ts
|
|
431
|
+
var register_exports = {};
|
|
432
|
+
__export(register_exports, {
|
|
433
|
+
runRegister: () => runRegister
|
|
434
|
+
});
|
|
435
|
+
async function runRegister(options) {
|
|
436
|
+
const projectDir = options.dir ?? process.cwd();
|
|
437
|
+
const gatewayUrl = options.gatewayUrl ?? process.env.DIRX_GATEWAY_URL ?? "https://api.dirx.ai";
|
|
438
|
+
const domain = options.domain ?? process.env.DIRX_DOMAIN;
|
|
439
|
+
if (!domain) {
|
|
440
|
+
throw new Error(
|
|
441
|
+
"Domain is required. Use --domain or DIRX_DOMAIN env var."
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
const token = await resolveToken({
|
|
445
|
+
token: options.token,
|
|
446
|
+
domain,
|
|
447
|
+
gatewayUrl
|
|
448
|
+
});
|
|
449
|
+
const dirMdPath = join(projectDir, "DIR.md");
|
|
450
|
+
let dirMd;
|
|
451
|
+
try {
|
|
452
|
+
dirMd = await readFile(dirMdPath, "utf-8");
|
|
453
|
+
} catch {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`DIR.md not found at ${dirMdPath}. Run 'dirx init' first.`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (!dirMd.trim()) {
|
|
459
|
+
throw new Error(`DIR.md is empty at ${dirMdPath}`);
|
|
460
|
+
}
|
|
461
|
+
const baseUrl = gatewayUrl.replace(/\/$/, "");
|
|
462
|
+
const url = `${baseUrl}/registry/services?domain=${encodeURIComponent(domain)}`;
|
|
463
|
+
const res = await fetch(url, {
|
|
464
|
+
method: "POST",
|
|
465
|
+
headers: {
|
|
466
|
+
Authorization: `Bearer ${token}`,
|
|
467
|
+
"Content-Type": "text/markdown"
|
|
468
|
+
},
|
|
469
|
+
body: dirMd
|
|
470
|
+
});
|
|
471
|
+
if (!res.ok) {
|
|
472
|
+
const body = await res.text();
|
|
473
|
+
throw new Error(`Registration failed (${res.status}): ${body}`);
|
|
474
|
+
}
|
|
475
|
+
const result = await res.json();
|
|
476
|
+
console.log(`Registered ${result.name} as ${result.domain}`);
|
|
477
|
+
}
|
|
478
|
+
async function resolveToken(options) {
|
|
479
|
+
if (options.token) return options.token;
|
|
480
|
+
if (process.env.DIRX_PUBLISH_TOKEN) return process.env.DIRX_PUBLISH_TOKEN;
|
|
481
|
+
if (options.domain) {
|
|
482
|
+
const stored = await getToken(options.domain);
|
|
483
|
+
if (stored) return stored;
|
|
484
|
+
const gw = options.gatewayUrl ?? process.env.DIRX_GATEWAY_URL;
|
|
485
|
+
if (gw) {
|
|
486
|
+
const renewed = await renewToken(gw, options.domain);
|
|
487
|
+
if (renewed) return renewed;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
throw new Error(
|
|
491
|
+
"No auth token found. Use `dirx claim <domain>` to obtain a publish token, or provide --token."
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
async function renewToken(gatewayUrl, domain) {
|
|
495
|
+
const baseUrl = gatewayUrl.replace(/\/$/, "");
|
|
496
|
+
try {
|
|
497
|
+
const res = await fetch(`${baseUrl}/domains/verify`, {
|
|
498
|
+
method: "POST",
|
|
499
|
+
headers: { "Content-Type": "application/json" },
|
|
500
|
+
body: JSON.stringify({ domain })
|
|
501
|
+
});
|
|
502
|
+
if (!res.ok) return null;
|
|
503
|
+
const data = await res.json();
|
|
504
|
+
if (!data.publishToken) return null;
|
|
505
|
+
await saveToken(domain, data.publishToken);
|
|
506
|
+
console.log(`Token renewed for ${domain}`);
|
|
507
|
+
return data.publishToken;
|
|
508
|
+
} catch {
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
var init_register = __esm({
|
|
513
|
+
"src/commands/register.ts"() {
|
|
514
|
+
init_credentials();
|
|
515
|
+
}
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// src/commands/status.ts
|
|
519
|
+
var status_exports = {};
|
|
520
|
+
__export(status_exports, {
|
|
521
|
+
runStatus: () => runStatus
|
|
522
|
+
});
|
|
523
|
+
async function runStatus() {
|
|
524
|
+
const gatewayUrl = process.env.DIRX_GATEWAY_URL ?? "https://api.dirx.ai";
|
|
525
|
+
console.log(`DirX CLI Status
|
|
526
|
+
`);
|
|
527
|
+
console.log(` Gateway: ${gatewayUrl}`);
|
|
528
|
+
const stored = await getAgentToken();
|
|
529
|
+
if (stored) {
|
|
530
|
+
const hint = stored.token.length > 8 ? `${stored.token.slice(0, 4)}...${stored.token.slice(-4)}` : "********";
|
|
531
|
+
console.log(` Token: ${hint}`);
|
|
532
|
+
console.log(` Server: ${stored.gatewayUrl}`);
|
|
533
|
+
if (stored.expiresAt) {
|
|
534
|
+
const expDate = new Date(stored.expiresAt * 1e3);
|
|
535
|
+
const now = /* @__PURE__ */ new Date();
|
|
536
|
+
if (expDate > now) {
|
|
537
|
+
console.log(` Expires: ${expDate.toISOString()}`);
|
|
538
|
+
} else {
|
|
539
|
+
console.log(` Expires: EXPIRED (${expDate.toISOString()})`);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
} else {
|
|
543
|
+
console.log(` Token: not set (run 'dirx auth')`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
var init_status = __esm({
|
|
547
|
+
"src/commands/status.ts"() {
|
|
548
|
+
init_credentials();
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// src/gateway/client.ts
|
|
553
|
+
async function createClient() {
|
|
554
|
+
const { getAgentToken: getAgentToken2 } = await Promise.resolve().then(() => (init_credentials(), credentials_exports));
|
|
555
|
+
const stored = await getAgentToken2();
|
|
556
|
+
const gatewayUrl = process.env.DIRX_GATEWAY_URL ?? stored?.gatewayUrl ?? "https://api.dirx.ai";
|
|
557
|
+
const token = process.env.DIRX_TOKEN ?? stored?.token ?? null;
|
|
558
|
+
if (!token) {
|
|
559
|
+
throw new Error(
|
|
560
|
+
"No token found. Run 'dirx auth' first, or set DIRX_TOKEN."
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
return new GatewayClient(gatewayUrl, token);
|
|
564
|
+
}
|
|
565
|
+
var GatewayClient;
|
|
566
|
+
var init_client = __esm({
|
|
567
|
+
"src/gateway/client.ts"() {
|
|
568
|
+
GatewayClient = class {
|
|
569
|
+
constructor(gatewayUrl, token) {
|
|
570
|
+
this.gatewayUrl = gatewayUrl;
|
|
571
|
+
this.token = token;
|
|
572
|
+
}
|
|
573
|
+
baseUrl() {
|
|
574
|
+
return this.gatewayUrl.replace(/\/$/, "");
|
|
575
|
+
}
|
|
576
|
+
/** Send a command string to the gateway /execute endpoint. */
|
|
577
|
+
async execute(command) {
|
|
578
|
+
const url = `${this.baseUrl()}/execute`;
|
|
579
|
+
const res = await fetch(url, {
|
|
580
|
+
method: "POST",
|
|
581
|
+
headers: {
|
|
582
|
+
Authorization: `Bearer ${this.token}`,
|
|
583
|
+
"Content-Type": "application/json"
|
|
584
|
+
},
|
|
585
|
+
body: JSON.stringify({ command })
|
|
586
|
+
});
|
|
587
|
+
if (!res.ok) {
|
|
588
|
+
const body = await res.text();
|
|
589
|
+
throw new Error(`Gateway error ${res.status}: ${body}`);
|
|
590
|
+
}
|
|
591
|
+
return res.json();
|
|
592
|
+
}
|
|
593
|
+
// --- BYOK methods ---
|
|
594
|
+
async uploadByok(domain, auth) {
|
|
595
|
+
const url = `${this.baseUrl()}/byok/${encodeURIComponent(domain)}`;
|
|
596
|
+
const res = await fetch(url, {
|
|
597
|
+
method: "PUT",
|
|
598
|
+
headers: {
|
|
599
|
+
Authorization: `Bearer ${this.token}`,
|
|
600
|
+
"Content-Type": "application/json"
|
|
601
|
+
},
|
|
602
|
+
body: JSON.stringify({ auth })
|
|
603
|
+
});
|
|
604
|
+
if (!res.ok) {
|
|
605
|
+
const body = await res.text();
|
|
606
|
+
throw new Error(`BYOK upload failed (${res.status}): ${body}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async listByok() {
|
|
610
|
+
const url = `${this.baseUrl()}/byok`;
|
|
611
|
+
const res = await fetch(url, {
|
|
612
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
613
|
+
});
|
|
614
|
+
if (!res.ok) {
|
|
615
|
+
const body = await res.text();
|
|
616
|
+
throw new Error(`BYOK list failed (${res.status}): ${body}`);
|
|
617
|
+
}
|
|
618
|
+
return res.json();
|
|
619
|
+
}
|
|
620
|
+
async deleteByok(domain) {
|
|
621
|
+
const url = `${this.baseUrl()}/byok/${encodeURIComponent(domain)}`;
|
|
622
|
+
const res = await fetch(url, {
|
|
623
|
+
method: "DELETE",
|
|
624
|
+
headers: { Authorization: `Bearer ${this.token}` }
|
|
625
|
+
});
|
|
626
|
+
if (!res.ok) {
|
|
627
|
+
const body = await res.text();
|
|
628
|
+
throw new Error(`BYOK delete failed (${res.status}): ${body}`);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// src/keys/provider-map.ts
|
|
636
|
+
function getAuthHint(domain) {
|
|
637
|
+
return providers[domain] ?? null;
|
|
638
|
+
}
|
|
639
|
+
var providers;
|
|
640
|
+
var init_provider_map = __esm({
|
|
641
|
+
"src/keys/provider-map.ts"() {
|
|
642
|
+
providers = {
|
|
643
|
+
"api.github.com": {
|
|
644
|
+
envVar: "GITHUB_TOKEN",
|
|
645
|
+
guideUrl: "https://github.com/settings/tokens"
|
|
646
|
+
},
|
|
647
|
+
"api.openai.com": {
|
|
648
|
+
envVar: "OPENAI_API_KEY",
|
|
649
|
+
guideUrl: "https://platform.openai.com/api-keys"
|
|
650
|
+
},
|
|
651
|
+
"api.anthropic.com": {
|
|
652
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
653
|
+
guideUrl: "https://console.anthropic.com/settings/keys"
|
|
654
|
+
},
|
|
655
|
+
"generativelanguage.googleapis.com": {
|
|
656
|
+
envVar: "GOOGLE_API_KEY",
|
|
657
|
+
guideUrl: "https://aistudio.google.com/apikey"
|
|
658
|
+
},
|
|
659
|
+
"api.stripe.com": {
|
|
660
|
+
envVar: "STRIPE_SECRET_KEY",
|
|
661
|
+
guideUrl: "https://dashboard.stripe.com/apikeys"
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
// src/commands/fs.ts
|
|
668
|
+
var fs_exports = {};
|
|
669
|
+
__export(fs_exports, {
|
|
670
|
+
registerFsCommands: () => registerFsCommands
|
|
671
|
+
});
|
|
672
|
+
function output(result) {
|
|
673
|
+
console.log(JSON.stringify(result, null, 2));
|
|
674
|
+
}
|
|
675
|
+
function extractDomain(path) {
|
|
676
|
+
const cleaned = path.replace(/^\/+/, "");
|
|
677
|
+
const segments = cleaned.split("/");
|
|
678
|
+
const candidate = segments.length > 1 ? segments[1] : segments[0];
|
|
679
|
+
if (!candidate) return null;
|
|
680
|
+
return candidate.includes(".") ? candidate : null;
|
|
681
|
+
}
|
|
682
|
+
function isAuthError(message) {
|
|
683
|
+
if (/Gateway error (401|403):/.test(message)) return true;
|
|
684
|
+
if (/Gateway error 500:/.test(message) && /\b(Unauthorized|Unauthenticated|authenticate|API key|api[_-]?key)\b/i.test(
|
|
685
|
+
message
|
|
686
|
+
))
|
|
687
|
+
return true;
|
|
688
|
+
return false;
|
|
689
|
+
}
|
|
690
|
+
function handleError(err, cmdPath) {
|
|
691
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
692
|
+
if (isAuthError(message)) {
|
|
693
|
+
const domain = cmdPath && extractDomain(cmdPath) || message.match(/([a-z0-9-]+(?:\.[a-z0-9-]+){1,})/i)?.[1] || null;
|
|
694
|
+
if (domain) {
|
|
695
|
+
const hint = getAuthHint(domain);
|
|
696
|
+
console.error(`Error: Authentication required for ${domain}`);
|
|
697
|
+
if (hint?.guideUrl) {
|
|
698
|
+
console.error(` Get your key: ${hint.guideUrl}`);
|
|
699
|
+
} else {
|
|
700
|
+
console.error(
|
|
701
|
+
` Get your key: https://${domain} (check the developer/API settings)`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
console.error(
|
|
705
|
+
` Set your key: dirx keys set ${domain} --token <YOUR_KEY> --sync`
|
|
706
|
+
);
|
|
707
|
+
console.error(` Then retry your command.`);
|
|
708
|
+
process.exit(1);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
console.error(JSON.stringify({ error: message }));
|
|
712
|
+
process.exit(1);
|
|
713
|
+
}
|
|
714
|
+
function registerFsCommands(program2) {
|
|
715
|
+
program2.command("ls").description("List directory contents in the DirX path space").argument("<path>", "Directory path (e.g. /net/)").action(async (path) => {
|
|
716
|
+
try {
|
|
717
|
+
const client = await createClient();
|
|
718
|
+
const result = await client.execute(`ls ${path}`);
|
|
719
|
+
output(result);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
handleError(err, path);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
program2.command("cat").description("Read file contents from a DirX path").argument("<path>", "File path").action(async (path) => {
|
|
725
|
+
try {
|
|
726
|
+
const client = await createClient();
|
|
727
|
+
const result = await client.execute(`cat ${path}`);
|
|
728
|
+
output(result);
|
|
729
|
+
} catch (err) {
|
|
730
|
+
handleError(err, path);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
program2.command("read").description("Read file contents (alias for cat)").argument("<path>", "File path").action(async (path) => {
|
|
734
|
+
try {
|
|
735
|
+
const client = await createClient();
|
|
736
|
+
const result = await client.execute(`cat ${path}`);
|
|
737
|
+
output(result);
|
|
738
|
+
} catch (err) {
|
|
739
|
+
handleError(err, path);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
program2.command("write").description("Write data to a DirX path").argument("<path>", "File path").option("-d, --data <json>", "JSON data to write").option("-f, --file <file>", "Read data from file").action(
|
|
743
|
+
async (path, opts) => {
|
|
744
|
+
try {
|
|
745
|
+
let payload = opts.data ?? "";
|
|
746
|
+
if (opts.file) {
|
|
747
|
+
const { readFileSync: readFileSync4 } = await import('fs');
|
|
748
|
+
payload = readFileSync4(opts.file, "utf-8");
|
|
749
|
+
}
|
|
750
|
+
const client = await createClient();
|
|
751
|
+
const result = await client.execute(`write ${path} ${payload}`);
|
|
752
|
+
output(result);
|
|
753
|
+
} catch (err) {
|
|
754
|
+
handleError(err, path);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
);
|
|
758
|
+
program2.command("edit").description("Edit (partial update) data at a DirX path").argument("<path>", "File path").option("-d, --data <json>", "JSON data for partial update").option("-f, --file <file>", "Read data from file").action(
|
|
759
|
+
async (path, opts) => {
|
|
760
|
+
try {
|
|
761
|
+
let payload = opts.data ?? "";
|
|
762
|
+
if (opts.file) {
|
|
763
|
+
const { readFileSync: readFileSync4 } = await import('fs');
|
|
764
|
+
payload = readFileSync4(opts.file, "utf-8");
|
|
765
|
+
}
|
|
766
|
+
const client = await createClient();
|
|
767
|
+
const result = await client.execute(`edit ${path} ${payload}`);
|
|
768
|
+
output(result);
|
|
769
|
+
} catch (err) {
|
|
770
|
+
handleError(err, path);
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
);
|
|
774
|
+
program2.command("bash").description("Execute a multi-step pipeline on the gateway").argument("<pipeline>", "Pipeline expression").action(async (pipeline) => {
|
|
775
|
+
try {
|
|
776
|
+
const client = await createClient();
|
|
777
|
+
const result = await client.execute(`bash ${pipeline}`);
|
|
778
|
+
output(result);
|
|
779
|
+
} catch (err) {
|
|
780
|
+
handleError(err);
|
|
781
|
+
}
|
|
782
|
+
});
|
|
783
|
+
program2.command("grep").description("Search across services in the DirX path space").argument("<pattern>", "Search pattern").argument("<path>", "Search scope path").action(async (pattern, path) => {
|
|
784
|
+
try {
|
|
785
|
+
const client = await createClient();
|
|
786
|
+
const result = await client.execute(`grep ${pattern} ${path}`);
|
|
787
|
+
output(result);
|
|
788
|
+
} catch (err) {
|
|
789
|
+
handleError(err, path);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
var init_fs = __esm({
|
|
794
|
+
"src/commands/fs.ts"() {
|
|
795
|
+
init_client();
|
|
796
|
+
init_provider_map();
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
// src/commands/keys.ts
|
|
801
|
+
var keys_exports = {};
|
|
802
|
+
__export(keys_exports, {
|
|
803
|
+
registerKeysCommand: () => registerKeysCommand
|
|
804
|
+
});
|
|
805
|
+
async function loadKeyStore() {
|
|
806
|
+
const { readFileSync: readFileSync4 } = await import('fs');
|
|
807
|
+
const { join: join5 } = await import('path');
|
|
808
|
+
const { homedir: homedir2 } = await import('os');
|
|
809
|
+
const dirxHome = process.env.DIRX_HOME ?? join5(homedir2(), ".dirx");
|
|
810
|
+
const filePath = join5(dirxHome, "keys.json");
|
|
811
|
+
try {
|
|
812
|
+
const text = readFileSync4(filePath, "utf-8");
|
|
813
|
+
return JSON.parse(text);
|
|
814
|
+
} catch {
|
|
815
|
+
return { keys: {} };
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function saveKeyStore(store) {
|
|
819
|
+
const { writeFileSync: writeFileSync3, mkdirSync: mkdirSync3, chmodSync: chmodSync2 } = await import('fs');
|
|
820
|
+
const { join: join5 } = await import('path');
|
|
821
|
+
const { homedir: homedir2 } = await import('os');
|
|
822
|
+
const dirxHome = process.env.DIRX_HOME ?? join5(homedir2(), ".dirx");
|
|
823
|
+
const filePath = join5(dirxHome, "keys.json");
|
|
824
|
+
mkdirSync3(dirxHome, { recursive: true });
|
|
825
|
+
writeFileSync3(filePath, JSON.stringify(store, null, 2));
|
|
826
|
+
try {
|
|
827
|
+
chmodSync2(filePath, 384);
|
|
828
|
+
} catch {
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function registerKeysCommand(program2) {
|
|
832
|
+
const keys = program2.command("keys").description("Manage external API keys (BYOK)");
|
|
833
|
+
keys.command("set").description("Set an API key for a domain").argument("<domain>", "API domain (e.g. api.github.com)").requiredOption("--token <token>", "API token/key").option("--type <type>", "Auth type (bearer, header, query)", "bearer").option("--sync", "Also upload to the gateway").action(
|
|
834
|
+
async (domain, opts) => {
|
|
835
|
+
const store = await loadKeyStore();
|
|
836
|
+
store.keys[domain] = {
|
|
837
|
+
type: opts.type,
|
|
838
|
+
token: opts.token,
|
|
839
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
840
|
+
};
|
|
841
|
+
await saveKeyStore(store);
|
|
842
|
+
console.log(`Key saved for ${domain}`);
|
|
843
|
+
if (opts.sync) {
|
|
844
|
+
try {
|
|
845
|
+
const client = await createClient();
|
|
846
|
+
await client.uploadByok(domain, {
|
|
847
|
+
type: opts.type,
|
|
848
|
+
token: opts.token
|
|
849
|
+
});
|
|
850
|
+
console.log(`Key synced to gateway`);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
853
|
+
console.error(`Sync failed: ${msg}`);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
keys.command("list").description("List stored API keys").option("--remote", "List keys stored on the gateway").action(async (opts) => {
|
|
859
|
+
if (opts.remote) {
|
|
860
|
+
try {
|
|
861
|
+
const client = await createClient();
|
|
862
|
+
const result = await client.listByok();
|
|
863
|
+
console.log(JSON.stringify(result, null, 2));
|
|
864
|
+
} catch (err) {
|
|
865
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
866
|
+
console.error(`Error: ${msg}`);
|
|
867
|
+
process.exit(1);
|
|
868
|
+
}
|
|
869
|
+
} else {
|
|
870
|
+
const store = await loadKeyStore();
|
|
871
|
+
const domains = Object.keys(store.keys);
|
|
872
|
+
if (domains.length === 0) {
|
|
873
|
+
console.log("No keys stored locally.");
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
for (const domain of domains) {
|
|
877
|
+
const entry = store.keys[domain];
|
|
878
|
+
const hint = entry.token ? `${entry.token.slice(0, 4)}...${entry.token.slice(-4)}` : "(set)";
|
|
879
|
+
console.log(` ${domain} ${entry.type} ${hint} ${entry.updatedAt}`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
keys.command("remove").description("Remove an API key for a domain").argument("<domain>", "API domain").option("--remote", "Also delete from the gateway").action(async (domain, opts) => {
|
|
884
|
+
const store = await loadKeyStore();
|
|
885
|
+
if (store.keys[domain]) {
|
|
886
|
+
delete store.keys[domain];
|
|
887
|
+
await saveKeyStore(store);
|
|
888
|
+
console.log(`Key removed for ${domain}`);
|
|
889
|
+
} else {
|
|
890
|
+
console.log(`No key found for ${domain}`);
|
|
891
|
+
}
|
|
892
|
+
if (opts.remote) {
|
|
893
|
+
try {
|
|
894
|
+
const client = await createClient();
|
|
895
|
+
await client.deleteByok(domain);
|
|
896
|
+
console.log(`Key deleted from gateway`);
|
|
897
|
+
} catch (err) {
|
|
898
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
899
|
+
console.error(`Remote delete failed: ${msg}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
var init_keys = __esm({
|
|
905
|
+
"src/commands/keys.ts"() {
|
|
906
|
+
init_client();
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
910
|
+
var version = "0.0.0";
|
|
911
|
+
try {
|
|
912
|
+
const pkg = JSON.parse(
|
|
913
|
+
readFileSync(resolve(__dirname$1, "../package.json"), "utf-8")
|
|
914
|
+
);
|
|
915
|
+
version = pkg.version;
|
|
916
|
+
} catch {
|
|
917
|
+
}
|
|
918
|
+
var program = new Command();
|
|
919
|
+
program.name("dirx").description("DirX \u2014 Unified Gateway & CLI for Agents").version(version);
|
|
920
|
+
program.command("auth").description("Authenticate with the DirX gateway").option("--gateway-url <url>", "Gateway URL (default: https://api.dirx.ai)").action(async (opts) => {
|
|
921
|
+
const { runAuth: runAuth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
922
|
+
await runAuth2({ gatewayUrl: opts.gatewayUrl });
|
|
923
|
+
});
|
|
924
|
+
program.command("init").description("Initialize DirX in the current project").argument("[dir]", "Project directory", ".").action(async (dir) => {
|
|
925
|
+
const { runInit: runInit2 } = await Promise.resolve().then(() => (init_init(), init_exports));
|
|
926
|
+
await runInit2(dir);
|
|
927
|
+
});
|
|
928
|
+
program.command("generate").description("Scan project and detect route definitions").argument("[dir]", "Project directory", ".").action(async (dir) => {
|
|
929
|
+
const { runGenerate: runGenerate2 } = await Promise.resolve().then(() => (init_generate(), generate_exports));
|
|
930
|
+
await runGenerate2(dir);
|
|
931
|
+
});
|
|
932
|
+
program.command("claim").description("Claim domain ownership via DNS verification").argument("<domain>", "Domain to claim").option("--verify", "Verify DNS record and obtain publish token").option("--gateway-url <url>", "Gateway URL").action(
|
|
933
|
+
async (domain, opts) => {
|
|
934
|
+
const { runClaim: runClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
|
|
935
|
+
const gatewayUrl = opts.gatewayUrl ?? process.env.DIRX_GATEWAY_URL ?? "https://api.dirx.ai";
|
|
936
|
+
await runClaim2({
|
|
937
|
+
gatewayUrl,
|
|
938
|
+
domain,
|
|
939
|
+
verify: opts.verify
|
|
940
|
+
});
|
|
941
|
+
}
|
|
942
|
+
);
|
|
943
|
+
program.command("register").description("Register DIR.md with the gateway").option("--gateway-url <url>", "Gateway URL").option("--token <token>", "Auth token (publish token)").option("--domain <domain>", "Domain name for the service").option("--dir <dir>", "Project directory", ".").action(async (opts) => {
|
|
944
|
+
const { runRegister: runRegister2 } = await Promise.resolve().then(() => (init_register(), register_exports));
|
|
945
|
+
await runRegister2({
|
|
946
|
+
gatewayUrl: opts.gatewayUrl,
|
|
947
|
+
token: opts.token,
|
|
948
|
+
domain: opts.domain,
|
|
949
|
+
dir: opts.dir === "." ? process.cwd() : opts.dir
|
|
950
|
+
});
|
|
951
|
+
});
|
|
952
|
+
program.command("status").description("Show current CLI configuration and auth status").action(async () => {
|
|
953
|
+
const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), status_exports));
|
|
954
|
+
await runStatus2();
|
|
955
|
+
});
|
|
956
|
+
var { registerFsCommands: registerFsCommands2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
957
|
+
registerFsCommands2(program);
|
|
958
|
+
var { registerKeysCommand: registerKeysCommand2 } = await Promise.resolve().then(() => (init_keys(), keys_exports));
|
|
959
|
+
registerKeysCommand2(program);
|
|
960
|
+
program.parse();
|
package/package.json
CHANGED
|
@@ -1,40 +1,47 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"
|
|
39
|
-
|
|
2
|
+
"name": "@dirxai/cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "DirX — Unified Gateway & CLI for Agents",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dirx": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsup",
|
|
12
|
+
"dev": "tsup --watch",
|
|
13
|
+
"lint": "tsc --noEmit",
|
|
14
|
+
"test": "vitest run"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"commander": "^13.0.0"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"tsup": "^8.0.0",
|
|
22
|
+
"typescript": "^5.7.0",
|
|
23
|
+
"vitest": "^4.0.0"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/dirxai/dirx.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://dirx.ai",
|
|
30
|
+
"keywords": [
|
|
31
|
+
"dirx",
|
|
32
|
+
"agent",
|
|
33
|
+
"cli",
|
|
34
|
+
"gateway",
|
|
35
|
+
"api",
|
|
36
|
+
"mcp"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
}
|
|
40
47
|
}
|
package/install.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const https = require("https");
|
|
4
|
-
const http = require("http");
|
|
5
|
-
const fs = require("fs");
|
|
6
|
-
const os = require("os");
|
|
7
|
-
const path = require("path");
|
|
8
|
-
const { execSync } = require("child_process");
|
|
9
|
-
|
|
10
|
-
const VERSION = require("./package.json").version;
|
|
11
|
-
const REPO = "dirxai/dirx";
|
|
12
|
-
const BIN_DIR = path.join(__dirname, "bin");
|
|
13
|
-
|
|
14
|
-
const PLATFORM_MAP = {
|
|
15
|
-
darwin: "apple-darwin",
|
|
16
|
-
linux: "unknown-linux-gnu",
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const ARCH_MAP = {
|
|
20
|
-
x64: "x86_64",
|
|
21
|
-
arm64: "aarch64",
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function getArchiveName() {
|
|
25
|
-
const platform = PLATFORM_MAP[process.platform];
|
|
26
|
-
const arch = ARCH_MAP[process.arch];
|
|
27
|
-
if (!platform || !arch) {
|
|
28
|
-
throw new Error(
|
|
29
|
-
`Unsupported platform: ${process.platform}-${process.arch}`
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
return `dirx-${arch}-${platform}.tar.gz`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function getDownloadUrl(archiveName) {
|
|
36
|
-
return `https://github.com/${REPO}/releases/download/v${VERSION}/${archiveName}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function download(url) {
|
|
40
|
-
return new Promise((resolve, reject) => {
|
|
41
|
-
const client = url.startsWith("https") ? https : http;
|
|
42
|
-
client.get(url, { headers: { "User-Agent": "dirx-npm-installer" } }, (res) => {
|
|
43
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
44
|
-
return download(res.headers.location).then(resolve).catch(reject);
|
|
45
|
-
}
|
|
46
|
-
if (res.statusCode !== 200) {
|
|
47
|
-
return reject(new Error(`Download failed: HTTP ${res.statusCode} from ${url}`));
|
|
48
|
-
}
|
|
49
|
-
const chunks = [];
|
|
50
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
51
|
-
res.on("end", () => resolve(Buffer.concat(chunks)));
|
|
52
|
-
res.on("error", reject);
|
|
53
|
-
}).on("error", reject);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async function main() {
|
|
58
|
-
const archiveName = getArchiveName();
|
|
59
|
-
const url = getDownloadUrl(archiveName);
|
|
60
|
-
const archivePath = path.join(os.tmpdir(), archiveName);
|
|
61
|
-
const binPath = path.join(BIN_DIR, "dirx");
|
|
62
|
-
|
|
63
|
-
console.log(`Downloading DirX v${VERSION} for ${process.platform}-${process.arch}...`);
|
|
64
|
-
console.log(` ${url}`);
|
|
65
|
-
|
|
66
|
-
fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const data = await download(url);
|
|
70
|
-
fs.writeFileSync(archivePath, data);
|
|
71
|
-
|
|
72
|
-
execSync(`tar xzf "${archivePath}" -C "${BIN_DIR}"`, { stdio: "inherit" });
|
|
73
|
-
fs.chmodSync(binPath, 0o755);
|
|
74
|
-
fs.unlinkSync(archivePath);
|
|
75
|
-
|
|
76
|
-
console.log(`DirX v${VERSION} installed to ${binPath}`);
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const version = execSync(`"${binPath}" --version`, { encoding: "utf8" }).trim();
|
|
80
|
-
console.log(` ${version}`);
|
|
81
|
-
} catch {
|
|
82
|
-
// binary works but --version might not exist yet
|
|
83
|
-
}
|
|
84
|
-
} catch (err) {
|
|
85
|
-
console.error(`Failed to install DirX: ${err.message}`);
|
|
86
|
-
console.error("You can download manually from:");
|
|
87
|
-
console.error(` https://github.com/${REPO}/releases/tag/v${VERSION}`);
|
|
88
|
-
process.exit(1);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
main();
|