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.
- package/README.md +12 -73
- package/bin/setclaw.js +229 -106
- 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
|
[](https://www.npmjs.com/package/setclaw)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](./LICENSE)
|
|
12
|
-
[](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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
+
Want the `setclaw` command available globally for future use?
|
|
84
62
|
|
|
85
63
|
```bash
|
|
86
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
9
|
-
* setclaw
|
|
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
|
|
31
|
-
const err
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
// ───
|
|
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
|
-
|
|
125
|
+
// Clean up backup
|
|
126
|
+
try { rmSync(BACKUP_FILE); } catch {}
|
|
57
127
|
|
|
58
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
if (existsSync(settingsPath)) {
|
|
196
|
+
if (existsSync(factoryPath)) {
|
|
90
197
|
log("Adding models to Factory...");
|
|
91
198
|
|
|
92
|
-
copyFileSync(
|
|
199
|
+
copyFileSync(factoryPath, `${factoryPath}.backup`);
|
|
93
200
|
|
|
94
|
-
let raw = readFileSync(
|
|
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
|
|
219
|
+
const existing = settings.customModels || [];
|
|
113
220
|
const existingNames = new Set(existing.map((m) => m.model));
|
|
114
|
-
let maxIndex
|
|
115
|
-
let nextIndex
|
|
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
|
-
|
|
133
|
-
|
|
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(
|
|
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(
|
|
242
|
+
warn("Factory settings not found — skipped.");
|
|
153
243
|
}
|
|
154
244
|
|
|
155
|
-
// ─── Step 2: Set
|
|
245
|
+
// ─── Step 2: Set env vars (system-wide, fallback to user) ─────────────
|
|
156
246
|
|
|
157
|
-
log("Setting
|
|
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:
|
|
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
|
-
|
|
257
|
+
if (OS === "win32") {
|
|
258
|
+
let usedSystem = false;
|
|
168
259
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
188
|
-
const
|
|
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
|
-
|
|
191
|
-
|
|
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
|
-
|
|
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 (
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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 [
|
|
223
|
-
const display =
|
|
224
|
-
out(` ${
|
|
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
|
-
|
|
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("");
|