clawreform 0.3.2 → 0.3.4
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 +10 -0
- package/bin/clawreform.js +239 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,6 +8,15 @@ Installs the native `clawreform` CLI from GitHub Releases and exposes it on your
|
|
|
8
8
|
npm install -g clawreform
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Launch
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
clawreform
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
When run with no args in an interactive terminal, this npm launcher opens the web dashboard flow by default.
|
|
18
|
+
On first run, it also performs `clawreform setup --quick` automatically when config is missing.
|
|
19
|
+
|
|
11
20
|
## Verify
|
|
12
21
|
|
|
13
22
|
```bash
|
|
@@ -18,3 +27,4 @@ clawreform --version
|
|
|
18
27
|
|
|
19
28
|
- This package downloads the matching binary for Linux, macOS, and Windows.
|
|
20
29
|
- No third-party runtime dependencies are used in this npm package.
|
|
30
|
+
- You can disable no-arg auto-dashboard behavior with `CLAWREFORM_NO_AUTO_DASHBOARD=1`.
|
package/bin/clawreform.js
CHANGED
|
@@ -1,8 +1,239 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const os = require("node:os");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
const { spawn, spawnSync } = require("node:child_process");
|
|
4
7
|
const { ensureBinaryInstalled } = require("../lib/install");
|
|
5
8
|
|
|
9
|
+
const API_KEY_VARS = [
|
|
10
|
+
"OPENROUTER_API_KEY",
|
|
11
|
+
"GROQ_API_KEY",
|
|
12
|
+
"ANTHROPIC_API_KEY",
|
|
13
|
+
"OPENAI_API_KEY",
|
|
14
|
+
"GEMINI_API_KEY",
|
|
15
|
+
"GOOGLE_API_KEY",
|
|
16
|
+
"DEEPSEEK_API_KEY",
|
|
17
|
+
"MINIMAX_API_KEY",
|
|
18
|
+
"MISTRAL_API_KEY",
|
|
19
|
+
"TOGETHER_API_KEY",
|
|
20
|
+
"FIREWORKS_API_KEY",
|
|
21
|
+
"COHERE_API_KEY",
|
|
22
|
+
"PERPLEXITY_API_KEY",
|
|
23
|
+
"XAI_API_KEY"
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
function sleep(ms) {
|
|
27
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeArgs(argv) {
|
|
31
|
+
const args = argv.slice(2);
|
|
32
|
+
|
|
33
|
+
// UX default for npm installs:
|
|
34
|
+
// In an interactive terminal, no-arg invocation should open the GUI dashboard.
|
|
35
|
+
const noArgs = args.length === 0;
|
|
36
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
37
|
+
const autoDashboardDisabled =
|
|
38
|
+
process.env.CLAWREFORM_NO_AUTO_DASHBOARD === "1" ||
|
|
39
|
+
process.env.CLAWREFORM_NO_AUTO_DASHBOARD === "true";
|
|
40
|
+
|
|
41
|
+
if (noArgs && interactive && !autoDashboardDisabled) {
|
|
42
|
+
process.stderr.write("No command provided. Launching web dashboard.\n");
|
|
43
|
+
return ["dashboard"];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Friendly aliases for non-technical users.
|
|
47
|
+
if (args[0] === "gui" || args[0] === "web") {
|
|
48
|
+
return ["dashboard", ...args.slice(1)];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return args;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function clawreformHome() {
|
|
55
|
+
return path.join(os.homedir(), ".clawreform");
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function configPath() {
|
|
59
|
+
return path.join(clawreformHome(), "config.toml");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function daemonInfoPath() {
|
|
63
|
+
return path.join(clawreformHome(), "daemon.json");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function dotEnvPath() {
|
|
67
|
+
return path.join(clawreformHome(), ".env");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function daemonBaseUrlFromFile() {
|
|
71
|
+
try {
|
|
72
|
+
const raw = fs.readFileSync(daemonInfoPath(), "utf8");
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
if (parsed && typeof parsed.listen_addr === "string" && parsed.listen_addr.trim()) {
|
|
75
|
+
return `http://${parsed.listen_addr.replace("0.0.0.0", "127.0.0.1")}`;
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// ignore
|
|
79
|
+
}
|
|
80
|
+
return `http://127.0.0.1:4332`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasConfiguredApiKey() {
|
|
84
|
+
for (const key of API_KEY_VARS) {
|
|
85
|
+
const value = process.env[key];
|
|
86
|
+
if (typeof value === "string" && value.trim()) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const content = fs.readFileSync(dotEnvPath(), "utf8");
|
|
93
|
+
for (const line of content.split(/\r?\n/)) {
|
|
94
|
+
const trimmed = line.trim();
|
|
95
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const idx = trimmed.indexOf("=");
|
|
99
|
+
if (idx <= 0) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const key = trimmed.slice(0, idx).trim();
|
|
103
|
+
const rawValue = trimmed.slice(idx + 1).trim();
|
|
104
|
+
const value = rawValue.replace(/^['"]|['"]$/g, "").trim();
|
|
105
|
+
if (API_KEY_VARS.includes(key) && value) {
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// ignore missing/unreadable .env
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function daemonHealthy(baseUrl) {
|
|
117
|
+
try {
|
|
118
|
+
const response = await fetch(`${baseUrl}/api/health`, {
|
|
119
|
+
headers: { "User-Agent": "clawreform-npm-launcher" }
|
|
120
|
+
});
|
|
121
|
+
return response.ok;
|
|
122
|
+
} catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function waitForDaemonReady(timeoutMs) {
|
|
128
|
+
const deadline = Date.now() + timeoutMs;
|
|
129
|
+
while (Date.now() < deadline) {
|
|
130
|
+
const baseUrl = daemonBaseUrlFromFile();
|
|
131
|
+
if (await daemonHealthy(baseUrl)) {
|
|
132
|
+
return baseUrl;
|
|
133
|
+
}
|
|
134
|
+
await sleep(500);
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function runQuickSetup(binaryPath) {
|
|
140
|
+
if (fs.existsSync(configPath())) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
process.stderr.write("First run detected. Applying quick setup...\n");
|
|
145
|
+
const result = spawnSync(binaryPath, ["setup", "--quick"], {
|
|
146
|
+
stdio: "inherit",
|
|
147
|
+
env: process.env
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (result.error) {
|
|
151
|
+
process.stderr.write(`Quick setup failed: ${result.error.message}\n`);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return result.status === 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function spawnDaemon(binaryPath, injectPlaceholderKeys) {
|
|
158
|
+
const env = { ...process.env };
|
|
159
|
+
if (injectPlaceholderKeys) {
|
|
160
|
+
for (const key of API_KEY_VARS) {
|
|
161
|
+
if (!env[key] || !String(env[key]).trim()) {
|
|
162
|
+
env[key] = "clawreform-placeholder-key";
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
const child = spawn(binaryPath, ["start"], {
|
|
169
|
+
detached: true,
|
|
170
|
+
stdio: "ignore",
|
|
171
|
+
env,
|
|
172
|
+
windowsHide: true
|
|
173
|
+
});
|
|
174
|
+
child.unref();
|
|
175
|
+
return true;
|
|
176
|
+
} catch {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function openBrowser(url) {
|
|
182
|
+
try {
|
|
183
|
+
if (process.platform === "darwin") {
|
|
184
|
+
return spawnSync("open", [url], { stdio: "ignore" }).status === 0;
|
|
185
|
+
}
|
|
186
|
+
if (process.platform === "win32") {
|
|
187
|
+
return (
|
|
188
|
+
spawnSync("cmd.exe", ["/c", "start", "", url], {
|
|
189
|
+
stdio: "ignore",
|
|
190
|
+
windowsHide: true
|
|
191
|
+
}).status === 0
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return spawnSync("xdg-open", [url], { stdio: "ignore" }).status === 0;
|
|
195
|
+
} catch {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function runDashboardFlow(binaryPath) {
|
|
201
|
+
if (!runQuickSetup(binaryPath)) {
|
|
202
|
+
process.stderr.write("Setup did not complete. Run: clawreform setup --quick\n");
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
let baseUrl = await waitForDaemonReady(2000);
|
|
207
|
+
if (!baseUrl) {
|
|
208
|
+
const injectPlaceholderKeys = !hasConfiguredApiKey();
|
|
209
|
+
if (injectPlaceholderKeys) {
|
|
210
|
+
process.stderr.write(
|
|
211
|
+
"No API key found yet. Starting in setup mode so you can finish setup in the dashboard.\n"
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
process.stderr.write("Starting background daemon...\n");
|
|
215
|
+
if (!spawnDaemon(binaryPath, injectPlaceholderKeys)) {
|
|
216
|
+
process.stderr.write("Could not start daemon process.\n");
|
|
217
|
+
process.stderr.write("Run manually: clawreform start\n");
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
baseUrl = await waitForDaemonReady(45000);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!baseUrl) {
|
|
224
|
+
process.stderr.write("Daemon did not become ready in time.\n");
|
|
225
|
+
process.stderr.write("Run manually: clawreform start\n");
|
|
226
|
+
return 1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const url = `${baseUrl}/`;
|
|
230
|
+
process.stderr.write(`Opening dashboard: ${url}\n`);
|
|
231
|
+
if (!openBrowser(url)) {
|
|
232
|
+
process.stderr.write(`Open this URL manually: ${url}\n`);
|
|
233
|
+
}
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
6
237
|
async function main() {
|
|
7
238
|
let binaryPath;
|
|
8
239
|
try {
|
|
@@ -13,7 +244,13 @@ async function main() {
|
|
|
13
244
|
process.exit(1);
|
|
14
245
|
}
|
|
15
246
|
|
|
16
|
-
const
|
|
247
|
+
const args = normalizeArgs(process.argv);
|
|
248
|
+
|
|
249
|
+
if (args[0] === "dashboard") {
|
|
250
|
+
process.exit(await runDashboardFlow(binaryPath));
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const result = spawnSync(binaryPath, args, {
|
|
17
254
|
stdio: "inherit",
|
|
18
255
|
env: process.env
|
|
19
256
|
});
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawreform",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Cross-platform npm launcher for clawREFORM by aegntic.ai CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "clawREFORM by aegntic.ai Team",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "https://github.com/aegntic/clawreform"
|
|
9
|
+
"url": "git+https://github.com/aegntic/clawreform.git"
|
|
10
10
|
},
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/aegntic/clawreform/issues"
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"node": ">=18.0.0"
|
|
24
24
|
},
|
|
25
25
|
"bin": {
|
|
26
|
-
"clawreform": "
|
|
26
|
+
"clawreform": "bin/clawreform.js"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
29
|
"postinstall": "node ./scripts/postinstall.js"
|