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 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 { spawnSync } = require("node:child_process");
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 result = spawnSync(binaryPath, process.argv.slice(2), {
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.2",
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": "./bin/clawreform.js"
26
+ "clawreform": "bin/clawreform.js"
27
27
  },
28
28
  "scripts": {
29
29
  "postinstall": "node ./scripts/postinstall.js"