setclaw 1.0.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/bin/setup.mjs +224 -0
- package/package.json +25 -0
package/bin/setup.mjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* setclaw postinstall — runs automatically during `npm i -g setclaw`
|
|
5
|
+
*
|
|
6
|
+
* npm suppresses stdin during lifecycle scripts, so we open the
|
|
7
|
+
* terminal directly (/dev/tty on unix, CON on windows) to prompt.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync, writeFileSync, copyFileSync, appendFileSync, existsSync, openSync, createReadStream } from "fs";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
import { homedir, platform } from "os";
|
|
13
|
+
import { execSync } from "child_process";
|
|
14
|
+
import { createInterface } from "readline";
|
|
15
|
+
|
|
16
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function openTTY() {
|
|
19
|
+
const ttyPath = platform() === "win32" ? "CON" : "/dev/tty";
|
|
20
|
+
return createReadStream(ttyPath, { encoding: "utf-8" });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ask(question) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let ttyIn;
|
|
26
|
+
try {
|
|
27
|
+
ttyIn = openTTY();
|
|
28
|
+
} catch {
|
|
29
|
+
reject(new Error("Cannot open terminal for input. Pass the key via: QUATARLY_API_KEY=<key> npm i -g setclaw"));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const rl = createInterface({ input: ttyIn, output: process.stderr, terminal: true });
|
|
33
|
+
rl.question(question, (answer) => {
|
|
34
|
+
rl.close();
|
|
35
|
+
ttyIn.destroy();
|
|
36
|
+
resolve(answer.trim());
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function log(msg) {
|
|
42
|
+
process.stderr.write(`\x1b[36m>\x1b[0m ${msg}\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function success(msg) {
|
|
46
|
+
process.stderr.write(`\x1b[32m✔\x1b[0m ${msg}\n`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function warn(msg) {
|
|
50
|
+
process.stderr.write(`\x1b[33m!\x1b[0m ${msg}\n`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function fail(msg) {
|
|
54
|
+
process.stderr.write(`\x1b[31m✖\x1b[0m ${msg}\n`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ─── Banner ──────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
process.stderr.write("\n");
|
|
60
|
+
process.stderr.write("\x1b[1m setclaw\x1b[0m — Quatarly setup for Claude Code & Factory\n");
|
|
61
|
+
process.stderr.write(" ─────────────────────────────────────────────────\n");
|
|
62
|
+
process.stderr.write("\n");
|
|
63
|
+
|
|
64
|
+
// ─── Get API key ─────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
let apiKey = process.env.QUATARLY_API_KEY;
|
|
67
|
+
|
|
68
|
+
if (!apiKey) {
|
|
69
|
+
try {
|
|
70
|
+
apiKey = await ask(" Enter your Quatarly API key: ");
|
|
71
|
+
} catch (err) {
|
|
72
|
+
fail(err.message);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!apiKey) {
|
|
78
|
+
fail("API key cannot be empty.");
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
process.stderr.write("\n");
|
|
83
|
+
|
|
84
|
+
// ─── Step 1: Add models to Factory ──────────────────────────────────
|
|
85
|
+
|
|
86
|
+
const settingsPath = join(homedir(), ".factory", "settings.json");
|
|
87
|
+
|
|
88
|
+
if (existsSync(settingsPath)) {
|
|
89
|
+
log("Adding models to Factory...");
|
|
90
|
+
|
|
91
|
+
copyFileSync(settingsPath, `${settingsPath}.backup`);
|
|
92
|
+
|
|
93
|
+
let raw = readFileSync(settingsPath, "utf-8");
|
|
94
|
+
if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1);
|
|
95
|
+
const settings = JSON.parse(raw.trim() || "{}");
|
|
96
|
+
|
|
97
|
+
const newModels = [
|
|
98
|
+
{ model: "claude-sonnet-4-6-20250929", baseUrl: "https://api.quatarly.cloud/", provider: "anthropic" },
|
|
99
|
+
{ model: "claude-opus-4-6-thinking", baseUrl: "https://api.quatarly.cloud/", provider: "anthropic" },
|
|
100
|
+
{ model: "claude-haiku-4-5-20251001", baseUrl: "https://api.quatarly.cloud/", provider: "anthropic" },
|
|
101
|
+
{ model: "gemini-3.1-pro", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
102
|
+
{ model: "gemini-3-flash", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
103
|
+
{ model: "gpt-5.1", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
104
|
+
{ model: "gpt-5.1-codex", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
105
|
+
{ model: "gpt-5.1-codex-max", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
106
|
+
{ model: "gpt-5.2", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
107
|
+
{ model: "gpt-5.2-codex", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
108
|
+
{ model: "gpt-5.3-codex", baseUrl: "https://api.quatarly.cloud/v1", provider: "openai" },
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const existing = settings.customModels || [];
|
|
112
|
+
const existingNames = new Set(existing.map((m) => m.model));
|
|
113
|
+
let maxIndex = existing.reduce((max, m) => Math.max(max, m.index ?? -1), -1);
|
|
114
|
+
let nextIndex = maxIndex + 1;
|
|
115
|
+
|
|
116
|
+
let added = 0;
|
|
117
|
+
let updated = 0;
|
|
118
|
+
|
|
119
|
+
for (const m of newModels) {
|
|
120
|
+
if (existingNames.has(m.model)) {
|
|
121
|
+
for (const e of existing) {
|
|
122
|
+
if (e.model === m.model) {
|
|
123
|
+
e.apiKey = apiKey;
|
|
124
|
+
e.baseUrl = m.baseUrl;
|
|
125
|
+
e.provider = m.provider;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
updated++;
|
|
129
|
+
} else {
|
|
130
|
+
existing.push({
|
|
131
|
+
model: m.model,
|
|
132
|
+
id: `custom:${m.model}-${nextIndex}`,
|
|
133
|
+
index: nextIndex,
|
|
134
|
+
baseUrl: m.baseUrl,
|
|
135
|
+
apiKey,
|
|
136
|
+
displayName: m.model,
|
|
137
|
+
noImageSupport: false,
|
|
138
|
+
provider: m.provider,
|
|
139
|
+
});
|
|
140
|
+
nextIndex++;
|
|
141
|
+
added++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
settings.customModels = existing;
|
|
146
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
147
|
+
|
|
148
|
+
success(`Factory: ${added} models added, ${updated} updated (${existing.length} total)`);
|
|
149
|
+
success(`Backup saved to ${settingsPath}.backup`);
|
|
150
|
+
} else {
|
|
151
|
+
warn(`Factory settings not found at ${settingsPath} — skipped.`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Step 2: Set Claude Code env vars ────────────────────────────────
|
|
155
|
+
|
|
156
|
+
log("Setting Claude Code environment variables...");
|
|
157
|
+
|
|
158
|
+
const vars = {
|
|
159
|
+
ANTHROPIC_BASE_URL: "https://api.quatarly.cloud/",
|
|
160
|
+
ANTHROPIC_AUTH_TOKEN: apiKey,
|
|
161
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: "claude-haiku-4-5-20251001",
|
|
162
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: "claude-sonnet-4-6-20250929",
|
|
163
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: "claude-opus-4-6-thinking",
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const os = platform();
|
|
167
|
+
|
|
168
|
+
if (os === "win32") {
|
|
169
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
170
|
+
execSync(`reg add "HKCU\\Environment" /v "${key}" /t REG_SZ /d "${value}" /f`, {
|
|
171
|
+
stdio: "ignore",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
success("Env vars written to Windows registry (HKCU\\Environment).");
|
|
175
|
+
} else {
|
|
176
|
+
const marker = "# --- Quatarly / Claude Code env ---";
|
|
177
|
+
const endMarker = "# --- end Quatarly ---";
|
|
178
|
+
const block = [
|
|
179
|
+
"",
|
|
180
|
+
marker,
|
|
181
|
+
...Object.entries(vars).map(([k, v]) => `export ${k}="${v}"`),
|
|
182
|
+
endMarker,
|
|
183
|
+
"",
|
|
184
|
+
].join("\n");
|
|
185
|
+
|
|
186
|
+
const rcFiles = [join(homedir(), ".bashrc"), join(homedir(), ".zshrc")];
|
|
187
|
+
const written = [];
|
|
188
|
+
|
|
189
|
+
for (const rc of rcFiles) {
|
|
190
|
+
if (!existsSync(rc)) continue;
|
|
191
|
+
|
|
192
|
+
const content = readFileSync(rc, "utf-8");
|
|
193
|
+
if (content.includes(marker)) {
|
|
194
|
+
const regex = new RegExp(
|
|
195
|
+
`\\n?${marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s\\S]*?${endMarker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`,
|
|
196
|
+
"g"
|
|
197
|
+
);
|
|
198
|
+
writeFileSync(rc, content.replace(regex, block), "utf-8");
|
|
199
|
+
} else {
|
|
200
|
+
appendFileSync(rc, block, "utf-8");
|
|
201
|
+
}
|
|
202
|
+
written.push(rc);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (written.length === 0) {
|
|
206
|
+
const fallback = join(homedir(), ".bashrc");
|
|
207
|
+
appendFileSync(fallback, block, "utf-8");
|
|
208
|
+
written.push(fallback);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
success(`Env vars written to: ${written.join(", ")}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ─── Done ────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
process.stderr.write("\n");
|
|
217
|
+
process.stderr.write(" \x1b[1mEnvironment variables set:\x1b[0m\n");
|
|
218
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
219
|
+
const display = key === "ANTHROPIC_AUTH_TOKEN" ? value.slice(0, 8) + "..." : value;
|
|
220
|
+
process.stderr.write(` ${key} = ${display}\n`);
|
|
221
|
+
}
|
|
222
|
+
process.stderr.write("\n");
|
|
223
|
+
success("All done! Restart your terminal, then launch Claude Code.");
|
|
224
|
+
process.stderr.write("\n");
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "setclaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "One-command setup for Quatarly API models in Claude Code and Factory",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"postinstall": "node bin/setup.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude",
|
|
14
|
+
"claude-code",
|
|
15
|
+
"quatarly",
|
|
16
|
+
"factory",
|
|
17
|
+
"ai",
|
|
18
|
+
"llm",
|
|
19
|
+
"setup"
|
|
20
|
+
],
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
}
|
|
25
|
+
}
|