@0dai-dev/cli 1.2.1
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/README.md +39 -0
- package/bin/0dai.js +274 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# 0dai
|
|
2
|
+
|
|
3
|
+
One config layer for 5 AI agent CLIs — Claude Code, Codex, OpenCode, Gemini, Aider.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @0dai-dev/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd your-project
|
|
15
|
+
0dai init # detect stack, generate ai/ layer + native configs
|
|
16
|
+
0dai sync # update after changes
|
|
17
|
+
0dai detect # show detected stack
|
|
18
|
+
0dai doctor # check health
|
|
19
|
+
0dai status # maturity, swarm tasks, session
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## What it does
|
|
23
|
+
|
|
24
|
+
`0dai init` sends your project metadata (file names + package.json) to the API and generates:
|
|
25
|
+
|
|
26
|
+
- `ai/` — manifests, personas, skills, playbooks, delegation policy
|
|
27
|
+
- `.claude/` — settings, agents, hooks, rules
|
|
28
|
+
- `.codex/` — config, agents
|
|
29
|
+
- `.gemini/` — settings, agents
|
|
30
|
+
- `.aider/` — config, agents
|
|
31
|
+
- `AGENTS.md`, `.mcp.json`
|
|
32
|
+
|
|
33
|
+
Your source code is never sent. Only file names and package manifests.
|
|
34
|
+
|
|
35
|
+
## Links
|
|
36
|
+
|
|
37
|
+
- Website: https://0dai.dev
|
|
38
|
+
- Docs: https://docs.0dai.dev
|
|
39
|
+
- API: https://api.0dai.dev
|
package/bin/0dai.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const https = require("https");
|
|
5
|
+
const http = require("http");
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
|
|
10
|
+
const VERSION = "1.2.1";
|
|
11
|
+
const API_URL = process.env.ODAI_API_URL || "https://api.0dai.dev";
|
|
12
|
+
const CONFIG_DIR = path.join(os.homedir(), ".0dai");
|
|
13
|
+
const AUTH_FILE = path.join(CONFIG_DIR, "auth.json");
|
|
14
|
+
|
|
15
|
+
const MANIFEST_FILES = [
|
|
16
|
+
"package.json", "go.mod", "pyproject.toml", "requirements.txt",
|
|
17
|
+
"pubspec.yaml", "Cargo.toml", "next.config.js", "next.config.mjs",
|
|
18
|
+
"next.config.ts", "tsconfig.json", "Makefile", "docker-compose.yml",
|
|
19
|
+
"Dockerfile", "pom.xml", "build.gradle",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const PROBE_DIRS = [
|
|
23
|
+
"src", "lib", "app", "apps", "packages", "services",
|
|
24
|
+
"cmd", "internal", "web", "frontend", "backend",
|
|
25
|
+
"tests", "test", "spec", "__tests__",
|
|
26
|
+
"infra", "deploy", "docker", ".github",
|
|
27
|
+
"android", "ios", "linux", "macos", "windows",
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
function apiCall(endpoint, data) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const url = new URL(endpoint, API_URL);
|
|
33
|
+
const mod = url.protocol === "https:" ? https : http;
|
|
34
|
+
const body = data ? JSON.stringify(data) : null;
|
|
35
|
+
const headers = { "Content-Type": "application/json" };
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const auth = JSON.parse(fs.readFileSync(AUTH_FILE, "utf8"));
|
|
39
|
+
const token = auth.api_key || auth.token;
|
|
40
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
41
|
+
} catch {}
|
|
42
|
+
|
|
43
|
+
const opts = {
|
|
44
|
+
hostname: url.hostname,
|
|
45
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
46
|
+
path: url.pathname,
|
|
47
|
+
method: body ? "POST" : "GET",
|
|
48
|
+
headers,
|
|
49
|
+
timeout: 30000,
|
|
50
|
+
};
|
|
51
|
+
if (body) opts.headers["Content-Length"] = Buffer.byteLength(body);
|
|
52
|
+
|
|
53
|
+
const req = mod.request(opts, (res) => {
|
|
54
|
+
let chunks = [];
|
|
55
|
+
res.on("data", (c) => chunks.push(c));
|
|
56
|
+
res.on("end", () => {
|
|
57
|
+
try {
|
|
58
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString()));
|
|
59
|
+
} catch {
|
|
60
|
+
resolve({ error: `HTTP ${res.statusCode}` });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
req.on("error", (e) => resolve({ error: `${e.message}. Is ${API_URL} reachable?` }));
|
|
65
|
+
req.on("timeout", () => { req.destroy(); resolve({ error: "timeout" }); });
|
|
66
|
+
if (body) req.write(body);
|
|
67
|
+
req.end();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function collectMetadata(target) {
|
|
72
|
+
const projectFiles = [];
|
|
73
|
+
const fileContents = {};
|
|
74
|
+
|
|
75
|
+
for (const d of PROBE_DIRS) {
|
|
76
|
+
try { if (fs.statSync(path.join(target, d)).isDirectory()) projectFiles.push(d + "/"); } catch {}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const f of MANIFEST_FILES) {
|
|
80
|
+
const p = path.join(target, f);
|
|
81
|
+
try {
|
|
82
|
+
const stat = fs.statSync(p);
|
|
83
|
+
if (stat.isFile()) {
|
|
84
|
+
projectFiles.push(f);
|
|
85
|
+
const content = fs.readFileSync(p, "utf8");
|
|
86
|
+
if (content.length < 10000) fileContents[f] = content;
|
|
87
|
+
}
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const clis = [];
|
|
92
|
+
for (const cli of ["claude", "codex", "opencode", "gemini", "aider"]) {
|
|
93
|
+
try {
|
|
94
|
+
const { execSync } = require("child_process");
|
|
95
|
+
execSync(`which ${cli}`, { stdio: "ignore" });
|
|
96
|
+
clis.push(cli);
|
|
97
|
+
} catch {}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return { projectFiles, fileContents, clis };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function writeFiles(target, files) {
|
|
104
|
+
let created = 0, updated = 0, unchanged = 0;
|
|
105
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
106
|
+
const p = path.join(target, rel);
|
|
107
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
108
|
+
try {
|
|
109
|
+
if (fs.existsSync(p) && fs.readFileSync(p, "utf8") === content) {
|
|
110
|
+
unchanged++;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (fs.existsSync(p)) updated++;
|
|
114
|
+
else created++;
|
|
115
|
+
} catch { created++; }
|
|
116
|
+
fs.writeFileSync(p, content, "utf8");
|
|
117
|
+
}
|
|
118
|
+
console.log(`[0dai] ${created} created, ${updated} updated, ${unchanged} unchanged`);
|
|
119
|
+
return created + updated;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function cmdInit(target) {
|
|
123
|
+
if (fs.existsSync(path.join(target, "ai", "VERSION"))) {
|
|
124
|
+
const v = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim();
|
|
125
|
+
console.log(`[0dai] ai/ layer already exists (v${v}). Run '0dai sync' to update.`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
console.log("[0dai] collecting project metadata...");
|
|
130
|
+
const { projectFiles, fileContents, clis } = collectMetadata(target);
|
|
131
|
+
|
|
132
|
+
console.log(`[0dai] sending to API (${projectFiles.length} files, ${clis.length} CLIs)...`);
|
|
133
|
+
const result = await apiCall("/v1/init", {
|
|
134
|
+
project_files: projectFiles,
|
|
135
|
+
file_contents: fileContents,
|
|
136
|
+
available_clis: clis,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (result.error) { console.error(`[0dai] error: ${result.error}`); process.exit(1); }
|
|
140
|
+
|
|
141
|
+
console.log(`[0dai] detected: ${result.stack || "?"}`);
|
|
142
|
+
writeFiles(target, result.files || {});
|
|
143
|
+
|
|
144
|
+
// Add to .gitignore
|
|
145
|
+
const gi = path.join(target, ".gitignore");
|
|
146
|
+
try {
|
|
147
|
+
const text = fs.existsSync(gi) ? fs.readFileSync(gi, "utf8") : "";
|
|
148
|
+
if (!text.includes(".0dai")) fs.appendFileSync(gi, "\n.0dai/\n");
|
|
149
|
+
} catch {}
|
|
150
|
+
|
|
151
|
+
console.log(`[0dai] initialized (${result.file_count || "?"} files)`);
|
|
152
|
+
console.log(" skills: /build /review /status /feedback /bugfix /delegate");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function cmdSync(target) {
|
|
156
|
+
const { projectFiles, fileContents, clis } = collectMetadata(target);
|
|
157
|
+
|
|
158
|
+
let version = "unknown", stack = "generic", agents = [];
|
|
159
|
+
try { version = fs.readFileSync(path.join(target, "ai", "VERSION"), "utf8").trim(); } catch {}
|
|
160
|
+
try {
|
|
161
|
+
const d = JSON.parse(fs.readFileSync(path.join(target, "ai", "manifest", "discovery.json"), "utf8"));
|
|
162
|
+
stack = d.stack || "generic";
|
|
163
|
+
agents = d.selected_agents || [];
|
|
164
|
+
} catch {}
|
|
165
|
+
|
|
166
|
+
// Collect current ai/ files
|
|
167
|
+
const currentFiles = {};
|
|
168
|
+
const aiDir = path.join(target, "ai");
|
|
169
|
+
if (fs.existsSync(aiDir)) {
|
|
170
|
+
const walk = (dir) => {
|
|
171
|
+
for (const f of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
172
|
+
const p = path.join(dir, f.name);
|
|
173
|
+
if (f.isDirectory()) walk(p);
|
|
174
|
+
else {
|
|
175
|
+
try {
|
|
176
|
+
const stat = fs.statSync(p);
|
|
177
|
+
if (stat.size < 10000) currentFiles[path.relative(target, p)] = fs.readFileSync(p, "utf8");
|
|
178
|
+
} catch {}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
walk(aiDir);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const result = await apiCall("/v1/sync", {
|
|
186
|
+
ai_version: version, stack, agents: agents.length ? agents : clis,
|
|
187
|
+
current_files: currentFiles, file_contents: fileContents,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
if (result.error) { console.error(`[0dai] error: ${result.error}`); process.exit(1); }
|
|
191
|
+
|
|
192
|
+
const updated = result.files_updated || {};
|
|
193
|
+
if (Object.keys(updated).length) writeFiles(target, updated);
|
|
194
|
+
else console.log("[0dai] already up to date");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function cmdDetect(target) {
|
|
198
|
+
const { projectFiles } = collectMetadata(target);
|
|
199
|
+
const result = await apiCall("/v1/detect", { files: projectFiles });
|
|
200
|
+
if (result.error) { console.error(`[0dai] error: ${result.error}`); return; }
|
|
201
|
+
console.log(`stack: ${result.stack || "?"}`);
|
|
202
|
+
console.log(`clis: ${(result.available_clis || []).join(",")}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function cmdDoctor(target) {
|
|
206
|
+
const ai = path.join(target, "ai");
|
|
207
|
+
if (!fs.existsSync(ai)) { console.log("[0dai] no ai/ layer. Run '0dai init' first."); return; }
|
|
208
|
+
let v = "?";
|
|
209
|
+
try { v = fs.readFileSync(path.join(ai, "VERSION"), "utf8").trim(); } catch {}
|
|
210
|
+
const checks = {
|
|
211
|
+
"ai/VERSION": fs.existsSync(path.join(ai, "VERSION")),
|
|
212
|
+
"ai/manifest/project.yaml": fs.existsSync(path.join(ai, "manifest", "project.yaml")),
|
|
213
|
+
"ai/manifest/commands.yaml": fs.existsSync(path.join(ai, "manifest", "commands.yaml")),
|
|
214
|
+
"ai/manifest/discovery.json": fs.existsSync(path.join(ai, "manifest", "discovery.json")),
|
|
215
|
+
".claude/settings.json": fs.existsSync(path.join(target, ".claude", "settings.json")),
|
|
216
|
+
"AGENTS.md": fs.existsSync(path.join(target, "AGENTS.md")),
|
|
217
|
+
};
|
|
218
|
+
const ok = Object.values(checks).every(Boolean);
|
|
219
|
+
console.log(`[0dai] v${v} — ${ok ? "healthy" : "issues found"}`);
|
|
220
|
+
for (const [name, exists] of Object.entries(checks)) console.log(` ${exists ? "ok" : "MISSING"}: ${name}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function cmdStatus(target) {
|
|
224
|
+
const ai = path.join(target, "ai");
|
|
225
|
+
let v = "?", stack = "?";
|
|
226
|
+
try { v = fs.readFileSync(path.join(ai, "VERSION"), "utf8").trim(); } catch {}
|
|
227
|
+
try { stack = JSON.parse(fs.readFileSync(path.join(ai, "manifest", "discovery.json"), "utf8")).stack || "?"; } catch {}
|
|
228
|
+
console.log(`[0dai] v${v} | stack: ${stack}`);
|
|
229
|
+
|
|
230
|
+
const count = (dir) => { try { return fs.readdirSync(dir).filter(f => f.endsWith(".json")).length; } catch { return 0; } };
|
|
231
|
+
const q = count(path.join(ai, "swarm", "queue"));
|
|
232
|
+
const a = count(path.join(ai, "swarm", "active"));
|
|
233
|
+
const d = count(path.join(ai, "swarm", "done"));
|
|
234
|
+
if (q || a || d) console.log(` swarm: ${q} queued, ${a} active, ${d} done`);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const s = JSON.parse(fs.readFileSync(path.join(ai, "sessions", "active.json"), "utf8"));
|
|
238
|
+
console.log(` session: ${(s.task || {}).goal || "?"} (agent: ${s.current_agent || "?"})`);
|
|
239
|
+
} catch {}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async function main() {
|
|
243
|
+
const args = process.argv.slice(2);
|
|
244
|
+
let target = process.cwd();
|
|
245
|
+
const ti = args.indexOf("--target");
|
|
246
|
+
if (ti >= 0 && args[ti + 1]) { target = path.resolve(args[ti + 1]); args.splice(ti, 2); }
|
|
247
|
+
|
|
248
|
+
const cmd = args[0] || "help";
|
|
249
|
+
|
|
250
|
+
switch (cmd) {
|
|
251
|
+
case "init": await cmdInit(target); break;
|
|
252
|
+
case "sync": await cmdSync(target); break;
|
|
253
|
+
case "detect": await cmdDetect(target); break;
|
|
254
|
+
case "doctor": cmdDoctor(target); break;
|
|
255
|
+
case "status": cmdStatus(target); break;
|
|
256
|
+
case "--version": console.log(`0dai ${VERSION}`); break;
|
|
257
|
+
case "help": case "--help": case "-h":
|
|
258
|
+
console.log(`0dai v${VERSION} — One config for 5 AI agent CLIs\n`);
|
|
259
|
+
console.log("Commands:");
|
|
260
|
+
console.log(" init Initialize ai/ layer (via API)");
|
|
261
|
+
console.log(" sync Update ai/ layer (via API)");
|
|
262
|
+
console.log(" detect Show detected stack");
|
|
263
|
+
console.log(" doctor Check health");
|
|
264
|
+
console.log(" status Show maturity, swarm, session");
|
|
265
|
+
console.log(" --version\n");
|
|
266
|
+
console.log("https://0dai.dev");
|
|
267
|
+
break;
|
|
268
|
+
default:
|
|
269
|
+
console.error(`[0dai] unknown command: ${cmd}. Run '0dai --help'`);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
main().catch((e) => { console.error(`[0dai] ${e.message}`); process.exit(1); });
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@0dai-dev/cli",
|
|
3
|
+
"version": "1.2.1",
|
|
4
|
+
"description": "One config layer for 5 AI agent CLIs — Claude Code, Codex, OpenCode, Gemini, Aider",
|
|
5
|
+
"bin": {
|
|
6
|
+
"0dai": "./bin/0dai.js"
|
|
7
|
+
},
|
|
8
|
+
"keywords": ["ai", "agents", "claude", "codex", "gemini", "aider", "developer-tools", "mcp"],
|
|
9
|
+
"author": "0dai-dev <dev@0dai.dev>",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/0dai-dev/0dai"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://0dai.dev",
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=16"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"bin/",
|
|
21
|
+
"lib/",
|
|
22
|
+
"README.md"
|
|
23
|
+
]
|
|
24
|
+
}
|