setclaw 1.2.0 → 1.3.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 (3) hide show
  1. package/README.md +12 -73
  2. package/bin/setclaw.js +229 -106
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,7 +9,7 @@ Connect [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Facto
9
9
  [![npm version](https://img.shields.io/npm/v/setclaw?color=cb3837&label=npm&logo=npm&logoColor=white)](https://www.npmjs.com/package/setclaw)
10
10
  [![node](https://img.shields.io/node/v/setclaw?color=339933&logo=node.js&logoColor=white)](https://nodejs.org)
11
11
  [![license](https://img.shields.io/npm/l/setclaw?color=blue)](./LICENSE)
12
- [![platform](https://img.shields.io/badge/platform-windows%20%7C%20macos%20%7C%20linux-lightgrey?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwSj0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPjxyZWN0IHg9IjIiIHk9IjMiIHdpZHRoPSIyMCIgaGVpZ2h0PSIxNCIgcng9IjIiLz48bGluZSB4MT0iOCIgeTE9IjIxIiB4Mj0iMTYiIHkyPSIyMSIvPjxsaW5lIHgxPSIxMiIgeTE9IjE3IiB4Mj0iMTIiIHkyPSIyMSIvPjwvc3ZnPg==)](https://www.npmjs.com/package/setclaw)
12
+ [![platform](https://img.shields.io/badge/platform-windows%20%7C%20macos%20%7C%20linux-lightgrey)](https://www.npmjs.com/package/setclaw)
13
13
 
14
14
  </div>
15
15
 
@@ -35,7 +35,7 @@ Connect [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Facto
35
35
  |:-:|:-----|:----|
36
36
  | 1 | **Node.js 18+** | [nodejs.org](https://nodejs.org) |
37
37
  | 2 | **Quatarly API key** | Format: `qua_trail_...` or `qua_...` |
38
- | 3 | **Factory AI** *(optional)* | See [Install Factory](#install-factory-ai-optional) below |
38
+ | 3 | **Factory AI** *(optional)* | See [Install Factory AI](#install-factory-ai-optional) below |
39
39
 
40
40
  ### Install & Configure
41
41
 
@@ -43,47 +43,26 @@ Connect [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and [Facto
43
43
  npx setclaw@latest
44
44
  ```
45
45
 
46
- That's it. It will prompt for your API key and configure everything in one shot.
47
-
48
- Want the `setclaw` command available globally for future use?
49
-
50
- ```bash
51
- npm i -g setclaw
52
- ```
53
-
54
- That's it. When you run `setclaw`, it will:
46
+ That's it. It will prompt for your API key and configure everything in one shot:
55
47
 
56
48
  ```
57
49
  setclaw — BOA & Quatarly setup for Claude Code & Factory
58
50
  ─────────────────────────────────────────────────
59
51
 
60
- > Adding models to Factory...
61
- ✔ Factory: 11 models added, 0 updated (11 total)
62
- ✔ Backup saved to ~/.factory/settings.json.backup
63
- > Setting Claude Code environment variables...
64
- ✔ Env vars written to Windows registry (HKCU\Environment).
65
-
66
- Environment variables set:
67
- ANTHROPIC_BASE_URL = https://api.quatarly.cloud/
68
- ANTHROPIC_AUTH_TOKEN = qua_trai...
69
- ANTHROPIC_DEFAULT_HAIKU_MODEL = claude-haiku-4-5-20251001
70
- ANTHROPIC_DEFAULT_SONNET_MODEL = claude-sonnet-4-6-20250929
71
- ANTHROPIC_DEFAULT_OPUS_MODEL = claude-opus-4-6-thinking
72
-
73
- ✔ All done! Restart your terminal, then launch Claude Code.
52
+ Enter your Quatarly API key: █
74
53
  ```
75
54
 
76
- You can also run without arguments to get an interactive prompt:
55
+ Or pass the key directly:
77
56
 
78
57
  ```bash
79
- setclaw
80
- # Enter your Quatarly API key: █
58
+ npx setclaw@latest <your-quatarly-api-key>
81
59
  ```
82
60
 
83
- ### Non-Interactive (CI / Scripts)
61
+ Want the `setclaw` command available globally for future use?
84
62
 
85
63
  ```bash
86
- QUATARLY_API_KEY=qua_trail_your-key setclaw
64
+ npm i -g setclaw
65
+ setclaw <your-quatarly-api-key>
87
66
  ```
88
67
 
89
68
  ---
@@ -128,13 +107,7 @@ irm https://app.factory.ai/cli/windows | iex
128
107
  curl -fsSL https://app.factory.ai/cli | sh
129
108
  ```
130
109
 
131
- Then create an account at [app.factory.ai](https://app.factory.ai), run `droid` once to generate `~/.factory/settings.json`, and re-run:
132
-
133
- ```bash
134
- npm i -g setclaw
135
- ```
136
-
137
- `setclaw` will detect the Factory config and inject all 11 models automatically.
110
+ Then create an account at [app.factory.ai](https://app.factory.ai), run `droid` once to generate `~/.factory/settings.json`, then run `setclaw` — it will detect the Factory config and inject all 11 models automatically.
138
111
 
139
112
  ---
140
113
 
@@ -179,46 +152,12 @@ npm i -g setclaw
179
152
 
180
153
  ---
181
154
 
182
- ## Manual Setup (Without setclaw)
183
-
184
- <details>
185
- <summary><b>macOS / Linux</b></summary>
186
-
187
- ```bash
188
- export ANTHROPIC_BASE_URL="https://api.quatarly.cloud/"
189
- export ANTHROPIC_AUTH_TOKEN="qua_trail_your-key-here"
190
- export ANTHROPIC_DEFAULT_HAIKU_MODEL="claude-haiku-4-5-20251001"
191
- export ANTHROPIC_DEFAULT_SONNET_MODEL="claude-sonnet-4-6-20250929"
192
- export ANTHROPIC_DEFAULT_OPUS_MODEL="claude-opus-4-6-thinking"
193
- ```
194
-
195
- Add these to `~/.zshrc` or `~/.bashrc` to persist.
196
-
197
- </details>
198
-
199
- <details>
200
- <summary><b>Windows (PowerShell)</b></summary>
201
-
202
- ```powershell
203
- $env:ANTHROPIC_BASE_URL = "https://api.quatarly.cloud/"
204
- $env:ANTHROPIC_AUTH_TOKEN = "qua_trail_your-key-here"
205
- $env:ANTHROPIC_DEFAULT_HAIKU_MODEL = "claude-haiku-4-5-20251001"
206
- $env:ANTHROPIC_DEFAULT_SONNET_MODEL = "claude-sonnet-4-6-20250929"
207
- $env:ANTHROPIC_DEFAULT_OPUS_MODEL = "claude-opus-4-6-thinking"
208
- ```
209
-
210
- To persist, use `[System.Environment]::SetEnvironmentVariable("VAR", "value", "User")` for each.
211
-
212
- </details>
213
-
214
- ---
215
-
216
155
  ## FAQ
217
156
 
218
157
  <details>
219
158
  <summary><b>Can I run it again with a different API key?</b></summary>
220
159
 
221
- Yes. Just run `setclaw <new-key>` again — it updates existing models and env vars without creating duplicates. A backup of your Factory settings is saved before any changes.
160
+ Yes. Just run `npx setclaw@latest <new-key>` again — it updates existing models and env vars without creating duplicates. A backup of your Factory settings is saved before any changes.
222
161
 
223
162
  </details>
224
163
 
@@ -243,7 +182,7 @@ No. Claude Code routes through Quatarly — you only need a Quatarly API key.
243
182
  npm uninstall -g setclaw
244
183
  ```
245
184
 
246
- This removes the package. To also remove the env vars:
185
+ To also remove the env vars:
247
186
  - **Windows:** Delete from `HKCU\Environment` via System Properties > Environment Variables
248
187
  - **macOS/Linux:** Remove the `# --- Quatarly / Claude Code env ---` block from `~/.bashrc` / `~/.zshrc`
249
188
 
package/bin/setclaw.js CHANGED
@@ -4,9 +4,9 @@
4
4
  * setclaw — BOA & Quatarly setup for Claude Code & Factory
5
5
  *
6
6
  * Usage:
7
- * setclaw <API_KEY>
8
- * QUATARLY_API_KEY=<key> setclaw
9
- * setclaw (prompts interactively)
7
+ * setclaw <API_KEY> — setup with key
8
+ * setclaw — interactive prompt
9
+ * setclaw --restore — restore original env vars & Factory config
10
10
  */
11
11
 
12
12
  import { readFileSync, writeFileSync, copyFileSync, appendFileSync, existsSync, rmSync, mkdirSync } from "fs";
@@ -20,29 +20,30 @@ import { createInterface } from "readline";
20
20
  function ask(question) {
21
21
  const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });
22
22
  return new Promise((resolve) =>
23
- rl.question(question, (answer) => {
24
- rl.close();
25
- resolve(answer.trim());
26
- })
23
+ rl.question(question, (answer) => { rl.close(); resolve(answer.trim()); })
27
24
  );
28
25
  }
29
26
 
30
- const out = (msg) => process.stdout.write(msg + "\n");
31
- const err = (msg) => process.stderr.write(msg + "\n");
32
-
33
- function log(msg) { err(`\x1b[36m>\x1b[0m ${msg}`); }
34
- function success(msg) { err(`\x1b[32m✔\x1b[0m ${msg}`); }
35
- function warn(msg) { err(`\x1b[33m!\x1b[0m ${msg}`); }
36
- function fail(msg) { err(`\x1b[31m✖\x1b[0m ${msg}`); }
37
-
38
- // ─── First-run detection ──────────────────────────────────────────────
39
- // If run via npx (npm_execpath contains 'npx' or no global install marker),
40
- // skip the first-run gate and always go interactive.
41
-
42
- const markerFile = join(homedir(), ".setclaw", "pending");
43
- const isNpx = (process.env.npm_execpath || "").includes("npx") ||
44
- process.argv[1].includes("_npx");
45
- const isFirstRun = !isNpx && existsSync(markerFile);
27
+ const out = (msg = "") => process.stdout.write(msg + "\n");
28
+ const err = (msg = "") => process.stderr.write(msg + "\n");
29
+ const log = (msg) => err(`\x1b[36m>\x1b[0m ${msg}`);
30
+ const ok = (msg) => err(`\x1b[32m✔\x1b[0m ${msg}`);
31
+ const warn = (msg) => err(`\x1b[33m!\x1b[0m ${msg}`);
32
+ const fail = (msg) => err(`\x1b[31m✖\x1b[0m ${msg}`);
33
+
34
+ const OS = platform();
35
+ const HOME = homedir();
36
+ const BACKUP_DIR = join(HOME, ".setclaw");
37
+ const MARKER_FILE = join(BACKUP_DIR, "pending");
38
+ const BACKUP_FILE = join(BACKUP_DIR, "backup.json");
39
+
40
+ const VAR_KEYS = [
41
+ "ANTHROPIC_BASE_URL",
42
+ "ANTHROPIC_AUTH_TOKEN",
43
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL",
44
+ "ANTHROPIC_DEFAULT_SONNET_MODEL",
45
+ "ANTHROPIC_DEFAULT_OPUS_MODEL",
46
+ ];
46
47
 
47
48
  // ─── Banner ──────────────────────────────────────────────────────────
48
49
 
@@ -51,11 +52,90 @@ out("\x1b[1m setclaw\x1b[0m — BOA & Quatarly setup for Claude Code & Factory"
51
52
  out(" \x1b[2m─────────────────────────────────────────────────\x1b[0m");
52
53
  out("");
53
54
 
54
- // ─── Get API key ─────────────────────────────────────────────────────
55
+ // ─── Arg parsing ─────────────────────────────────────────────────────
56
+
57
+ const args = process.argv.slice(2);
58
+ const restore = args.includes("--restore");
59
+ const apiKey = !restore ? (process.env.QUATARLY_API_KEY || args.find(a => !a.startsWith("--"))) : null;
60
+
61
+ // ─── First-run detection ──────────────────────────────────────────────
62
+
63
+ const isNpx = (process.env.npm_execpath || "").includes("npx") || process.argv[1].includes("_npx");
64
+ const isFirstRun = !isNpx && existsSync(MARKER_FILE);
65
+
66
+ // ════════════════════════════════════════════════════════════════════
67
+ // RESTORE MODE
68
+ // ════════════════════════════════════════════════════════════════════
69
+
70
+ if (restore) {
71
+ if (!existsSync(BACKUP_FILE)) {
72
+ fail("No backup found. Nothing to restore.");
73
+ process.exit(1);
74
+ }
75
+
76
+ const backup = JSON.parse(readFileSync(BACKUP_FILE, "utf-8"));
77
+ log("Restoring original environment variables...");
78
+
79
+ if (OS === "win32") {
80
+ for (const key of VAR_KEYS) {
81
+ if (backup.env[key] !== undefined) {
82
+ // Restore to both user and system
83
+ try {
84
+ execSync(`reg add "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" /v "${key}" /t REG_SZ /d "${backup.env[key]}" /f`, { stdio: "ignore" });
85
+ } catch {}
86
+ execSync(`reg add "HKCU\\Environment" /v "${key}" /t REG_SZ /d "${backup.env[key]}" /f`, { stdio: "ignore" });
87
+ ok(`Restored ${key} = ${backup.env[key] || "(empty)"}`);
88
+ } else {
89
+ // Was not set before — delete it
90
+ try {
91
+ execSync(`reg delete "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" /v "${key}" /f`, { stdio: "ignore" });
92
+ } catch {}
93
+ try {
94
+ execSync(`reg delete "HKCU\\Environment" /v "${key}" /f`, { stdio: "ignore" });
95
+ } catch {}
96
+ ok(`Removed ${key} (was not set before)`);
97
+ }
98
+ }
99
+ } else {
100
+ // Remove setclaw block from all rc files
101
+ const marker = "# --- Quatarly / Claude Code env ---";
102
+ const endMarker = "# --- end Quatarly ---";
103
+ const rcFiles = ["/etc/environment", "/etc/zshenv", "/etc/bash.bashrc",
104
+ join(HOME, ".bashrc"), join(HOME, ".zshrc")];
105
+ for (const rc of rcFiles) {
106
+ if (!existsSync(rc)) continue;
107
+ const content = readFileSync(rc, "utf-8");
108
+ if (!content.includes(marker)) continue;
109
+ const regex = new RegExp(
110
+ `\\n?${marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g"
111
+ );
112
+ writeFileSync(rc, content.replace(regex, ""), "utf-8");
113
+ ok(`Cleaned ${rc}`);
114
+ }
115
+ }
116
+
117
+ // Restore Factory settings
118
+ const factoryPath = join(HOME, ".factory", "settings.json");
119
+ if (backup.factory && existsSync(factoryPath)) {
120
+ log("Restoring Factory settings...");
121
+ writeFileSync(factoryPath, JSON.stringify(backup.factory, null, 2), "utf-8");
122
+ ok("Factory settings restored.");
123
+ }
55
124
 
56
- let apiKey = process.env.QUATARLY_API_KEY || process.argv[2];
125
+ // Clean up backup
126
+ try { rmSync(BACKUP_FILE); } catch {}
57
127
 
58
- // If no key and this is first run (just installed), show clear instructions
128
+ out("");
129
+ ok("Restore complete! Open a new terminal to apply changes.");
130
+ out("");
131
+ process.exit(0);
132
+ }
133
+
134
+ // ════════════════════════════════════════════════════════════════════
135
+ // SETUP MODE
136
+ // ════════════════════════════════════════════════════════════════════
137
+
138
+ // First-run gate
59
139
  if (!apiKey && isFirstRun) {
60
140
  out(" \x1b[33m⚡ Setup required!\x1b[0m Run with your Quatarly API key:");
61
141
  out("");
@@ -66,32 +146,59 @@ if (!apiKey && isFirstRun) {
66
146
  process.exit(0);
67
147
  }
68
148
 
69
- if (!apiKey) {
70
- try {
71
- apiKey = await ask(" Enter your Quatarly API key: ");
72
- } catch (err) {
73
- fail(err.message);
74
- process.exit(1);
149
+ let key = apiKey;
150
+ if (!key) {
151
+ try { key = await ask(" Enter your Quatarly API key: "); }
152
+ catch (e) { fail(e.message); process.exit(1); }
153
+ }
154
+ if (!key) { fail("API key cannot be empty."); process.exit(1); }
155
+
156
+ err("");
157
+
158
+ // ─── Backup current state ─────────────────────────────────────────────
159
+
160
+ mkdirSync(BACKUP_DIR, { recursive: true });
161
+
162
+ const backup = { env: {}, factory: null };
163
+
164
+ if (OS === "win32") {
165
+ for (const k of VAR_KEYS) {
166
+ try {
167
+ const val = execSync(`reg query "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" /v "${k}" 2>nul`, { encoding: "utf-8" });
168
+ const match = val.match(/REG_SZ\s+(.+)/);
169
+ backup.env[k] = match ? match[1].trim() : "";
170
+ } catch {
171
+ // Also try HKCU
172
+ try {
173
+ const val = execSync(`reg query "HKCU\\Environment" /v "${k}" 2>nul`, { encoding: "utf-8" });
174
+ const match = val.match(/REG_SZ\s+(.+)/);
175
+ backup.env[k] = match ? match[1].trim() : "";
176
+ } catch {
177
+ backup.env[k] = undefined; // Was not set
178
+ }
179
+ }
75
180
  }
76
181
  }
77
182
 
78
- if (!apiKey) {
79
- fail("API key cannot be empty.");
80
- process.exit(1);
183
+ // Backup Factory settings
184
+ const factoryPath = join(HOME, ".factory", "settings.json");
185
+ if (existsSync(factoryPath)) {
186
+ let raw = readFileSync(factoryPath, "utf-8");
187
+ if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
188
+ backup.factory = JSON.parse(raw.trim() || "{}");
81
189
  }
82
190
 
83
- process.stderr.write("\n");
191
+ writeFileSync(BACKUP_FILE, JSON.stringify(backup, null, 2), "utf-8");
192
+ ok(`Backup saved to ${BACKUP_FILE}`);
84
193
 
85
194
  // ─── Step 1: Add models to Factory ──────────────────────────────────
86
195
 
87
- const settingsPath = join(homedir(), ".factory", "settings.json");
88
-
89
- if (existsSync(settingsPath)) {
196
+ if (existsSync(factoryPath)) {
90
197
  log("Adding models to Factory...");
91
198
 
92
- copyFileSync(settingsPath, `${settingsPath}.backup`);
199
+ copyFileSync(factoryPath, `${factoryPath}.backup`);
93
200
 
94
- let raw = readFileSync(settingsPath, "utf-8");
201
+ let raw = readFileSync(factoryPath, "utf-8");
95
202
  if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
96
203
  const settings = JSON.parse(raw.trim() || "{}");
97
204
 
@@ -109,120 +216,136 @@ if (existsSync(settingsPath)) {
109
216
  { model: "gpt-5.3-codex", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
110
217
  ];
111
218
 
112
- const existing = settings.customModels || [];
219
+ const existing = settings.customModels || [];
113
220
  const existingNames = new Set(existing.map((m) => m.model));
114
- let maxIndex = existing.reduce((max, m) => Math.max(max, m.index ?? -1), -1);
115
- let nextIndex = maxIndex + 1;
116
-
117
- let added = 0;
118
- let updated = 0;
221
+ let maxIndex = existing.reduce((max, m) => Math.max(max, m.index ?? -1), -1);
222
+ let nextIndex = maxIndex + 1;
223
+ let added = 0, updated = 0;
119
224
 
120
225
  for (const m of newModels) {
121
226
  if (existingNames.has(m.model)) {
122
227
  for (const e of existing) {
123
- if (e.model === m.model) {
124
- e.apiKey = apiKey;
125
- e.baseUrl = m.baseUrl;
126
- e.provider = m.provider;
127
- }
228
+ if (e.model === m.model) { e.apiKey = key; e.baseUrl = m.baseUrl; e.provider = m.provider; }
128
229
  }
129
230
  updated++;
130
231
  } else {
131
- existing.push({
132
- model: m.model,
133
- id: `custom:${m.model}-${nextIndex}`,
134
- index: nextIndex,
135
- baseUrl: m.baseUrl,
136
- apiKey,
137
- displayName: m.model,
138
- noImageSupport: false,
139
- provider: m.provider,
140
- });
141
- nextIndex++;
142
- added++;
232
+ existing.push({ model: m.model, id: `custom:${m.model}-${nextIndex}`, index: nextIndex,
233
+ baseUrl: m.baseUrl, apiKey: key, displayName: m.model, noImageSupport: false, provider: m.provider });
234
+ nextIndex++; added++;
143
235
  }
144
236
  }
145
237
 
146
238
  settings.customModels = existing;
147
- writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
148
-
149
- success(`Factory: ${added} models added, ${updated} updated (${existing.length} total)`);
150
- success(`Backup saved to ${settingsPath}.backup`);
239
+ writeFileSync(factoryPath, JSON.stringify(settings, null, 2), "utf-8");
240
+ ok(`Factory: ${added} added, ${updated} updated (${existing.length} total)`);
151
241
  } else {
152
- warn(`Factory settings not found at ${settingsPath} — skipped.`);
242
+ warn("Factory settings not found — skipped.");
153
243
  }
154
244
 
155
- // ─── Step 2: Set Claude Code env vars ────────────────────────────────
245
+ // ─── Step 2: Set env vars (system-wide, fallback to user) ─────────────
156
246
 
157
- log("Setting Claude Code environment variables...");
247
+ log("Setting environment variables system-wide...");
158
248
 
159
249
  const vars = {
160
250
  ANTHROPIC_BASE_URL: "https://api.quatarly.cloud/",
161
- ANTHROPIC_AUTH_TOKEN: apiKey,
251
+ ANTHROPIC_AUTH_TOKEN: key,
162
252
  ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-haiku-4-5-20251001",
163
253
  ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-sonnet-4-6-20250929",
164
254
  ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-4-6-thinking",
165
255
  };
166
256
 
167
- const os = platform();
257
+ if (OS === "win32") {
258
+ let usedSystem = false;
168
259
 
169
- if (os === "win32") {
170
- for (const [key, value] of Object.entries(vars)) {
171
- execSync(`reg add "HKCU\\Environment" /v "${key}" /t REG_SZ /d "${value}" /f`, {
172
- stdio: "ignore",
173
- });
260
+ // Try HKLM (system-wide, requires admin)
261
+ try {
262
+ for (const [k, v] of Object.entries(vars)) {
263
+ execSync(
264
+ `reg add "HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment" /v "${k}" /t REG_SZ /d "${v}" /f`,
265
+ { stdio: "ignore" }
266
+ );
267
+ }
268
+ usedSystem = true;
269
+ ok("Env vars written system-wide (HKLM — all users).");
270
+ } catch {
271
+ // No admin — fall back to HKCU
272
+ warn("No admin rights — setting for current user only (HKCU).");
273
+ for (const [k, v] of Object.entries(vars)) {
274
+ execSync(`reg add "HKCU\\Environment" /v "${k}" /t REG_SZ /d "${v}" /f`, { stdio: "ignore" });
275
+ }
276
+ ok("Env vars written to HKCU\\Environment.");
174
277
  }
175
- success("Env vars written to Windows registry (HKCU\\Environment).");
278
+
279
+ // Broadcast WM_SETTINGCHANGE so open apps pick up the change without reboot
280
+ try {
281
+ execSync(
282
+ `powershell -NoProfile -Command "[System.Environment]::SetEnvironmentVariable('_setclaw_refresh', $null, 'Machine')" 2>nul`,
283
+ { stdio: "ignore" }
284
+ );
285
+ } catch {}
286
+
176
287
  } else {
177
- const marker = "# --- Quatarly / Claude Code env ---";
288
+ const marker = "# --- Quatarly / Claude Code env ---";
178
289
  const endMarker = "# --- end Quatarly ---";
179
- const block = [
180
- "",
181
- marker,
182
- ...Object.entries(vars).map(([k, v]) => `export ${k}="${v}"`),
183
- endMarker,
184
- "",
185
- ].join("\n");
290
+ const block = ["", marker, ...Object.entries(vars).map(([k, v]) => `export ${k}="${v}"`), endMarker, ""].join("\n");
186
291
 
187
- const rcFiles = [join(homedir(), ".bashrc"), join(homedir(), ".zshrc")];
188
- const written = [];
292
+ // Try system-wide files first
293
+ const systemFiles = OS === "darwin"
294
+ ? ["/etc/zshenv", "/etc/bashrc"]
295
+ : ["/etc/environment", "/etc/bash.bashrc", "/etc/zshenv"];
189
296
 
190
- for (const rc of rcFiles) {
191
- if (!existsSync(rc)) continue;
297
+ const userFiles = [join(HOME, ".bashrc"), join(HOME, ".zshrc")];
298
+ let written = [];
192
299
 
300
+ const writeToRc = (rc) => {
301
+ if (!existsSync(rc)) return false;
193
302
  const content = readFileSync(rc, "utf-8");
194
303
  if (content.includes(marker)) {
195
304
  const regex = new RegExp(
196
- `\\n?${marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
197
- "g"
305
+ `\\n?${marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g"
198
306
  );
199
307
  writeFileSync(rc, content.replace(regex, block), "utf-8");
200
308
  } else {
201
309
  appendFileSync(rc, block, "utf-8");
202
310
  }
203
- written.push(rc);
311
+ return true;
312
+ };
313
+
314
+ // Try system-wide
315
+ let systemOk = false;
316
+ for (const rc of systemFiles) {
317
+ try {
318
+ if (writeToRc(rc)) { written.push(rc); systemOk = true; }
319
+ } catch {}
204
320
  }
205
321
 
206
- if (written.length === 0) {
207
- const fallback = join(homedir(), ".bashrc");
208
- appendFileSync(fallback, block, "utf-8");
209
- written.push(fallback);
322
+ if (systemOk) {
323
+ ok(`Env vars written system-wide: ${written.join(", ")}`);
324
+ } else {
325
+ warn("No write access to system files — writing to user profile.");
326
+ for (const rc of userFiles) {
327
+ if (writeToRc(rc)) written.push(rc);
328
+ }
329
+ if (written.length === 0) {
330
+ appendFileSync(join(HOME, ".bashrc"), block, "utf-8");
331
+ written.push(join(HOME, ".bashrc"));
332
+ }
333
+ ok(`Env vars written to: ${written.join(", ")}`);
210
334
  }
211
-
212
- success(`Env vars written to: ${written.join(", ")}`);
213
335
  }
214
336
 
215
337
  // ─── Done ────────────────────────────────────────────────────────────
216
338
 
217
- // Clear the first-run marker
218
- try { rmSync(markerFile); } catch {}
339
+ try { rmSync(MARKER_FILE); } catch {}
219
340
 
220
341
  out("");
221
342
  out(" \x1b[1mEnvironment variables set:\x1b[0m");
222
- for (const [key, value] of Object.entries(vars)) {
223
- const display = key === "ANTHROPIC_AUTH_TOKEN" ? value.slice(0, 8) + "..." : value;
224
- out(` ${key} = ${display}`);
343
+ for (const [k, v] of Object.entries(vars)) {
344
+ const display = k === "ANTHROPIC_AUTH_TOKEN" ? v.slice(0, 8) + "..." : v;
345
+ out(` ${k} = ${display}`);
225
346
  }
226
347
  out("");
227
- success("All done! Restart your terminal, then launch Claude Code.");
348
+ ok("All done! Open a new terminal, then launch Claude Code.");
349
+ out("");
350
+ out(" \x1b[2mTo restore original settings: setclaw --restore\x1b[0m");
228
351
  out("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "setclaw",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "BOA & Quatarly setup for Claude Code and Factory",
5
5
  "type": "module",
6
6
  "bin": {