setclaw 1.0.4 → 1.0.6

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