@theaux/clawdbot 2026.1.15 → 2026.1.16

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.
@@ -6,8 +6,6 @@
6
6
  */
7
7
  import { createHash, randomBytes } from "node:crypto";
8
8
  import { readFileSync } from "node:fs";
9
- import { stdin, stdout } from "node:process";
10
- import { createInterface } from "node:readline/promises";
11
9
  import { loginAntigravity } from "@mariozechner/pi-ai";
12
10
  // OAuth constants - decoded from pi-ai's base64 encoded values to stay in sync
13
11
  const decode = (s) => Buffer.from(s, "base64").toString();
@@ -247,28 +245,16 @@ async function fetchProjectId(accessToken) {
247
245
  // Use fallback project ID
248
246
  return DEFAULT_PROJECT_ID;
249
247
  }
250
- /**
251
- * Prompt user for input via readline.
252
- */
253
- async function promptInput(message) {
254
- const rl = createInterface({ input: stdin, output: stdout });
255
- try {
256
- return (await rl.question(message)).trim();
257
- }
258
- finally {
259
- rl.close();
260
- }
261
- }
262
248
  /**
263
249
  * VPS-aware Antigravity OAuth login.
264
250
  *
265
251
  * On local machines: Uses the standard pi-ai flow with automatic localhost callback.
266
252
  * On VPS/SSH: Shows URL and prompts user to paste the callback URL manually.
267
253
  */
268
- export async function loginAntigravityVpsAware(onUrl, onProgress) {
254
+ export async function loginAntigravityVpsAware(prompter, onUrl, onProgress) {
269
255
  // Check if we're in a remote environment
270
256
  if (shouldUseManualOAuthFlow()) {
271
- return loginAntigravityManual(onUrl, onProgress);
257
+ return loginAntigravityManual(prompter, onUrl, onProgress);
272
258
  }
273
259
  // Use the standard pi-ai flow for local environments
274
260
  try {
@@ -284,7 +270,7 @@ export async function loginAntigravityVpsAware(onUrl, onProgress) {
284
270
  err.message.includes("port") ||
285
271
  err.message.includes("listen"))) {
286
272
  onProgress?.("Local callback server failed. Switching to manual mode...");
287
- return loginAntigravityManual(onUrl, onProgress);
273
+ return loginAntigravityManual(prompter, onUrl, onProgress);
288
274
  }
289
275
  throw err;
290
276
  }
@@ -292,26 +278,32 @@ export async function loginAntigravityVpsAware(onUrl, onProgress) {
292
278
  /**
293
279
  * Manual Antigravity OAuth login for VPS/headless environments.
294
280
  *
295
- * Shows the OAuth URL and prompts user to paste the callback URL.
281
+ * Shows the OAuth URL and prompts user to paste the callback URL using prompter.
296
282
  */
297
- export async function loginAntigravityManual(onUrl, onProgress) {
283
+ export async function loginAntigravityManual(prompter, onUrl, onProgress) {
298
284
  const { verifier, challenge } = generatePKCESync();
299
285
  const authUrl = buildAuthUrl(challenge, verifier);
300
286
  // Show the URL to the user
301
287
  await onUrl(authUrl);
302
- onProgress?.("Waiting for you to paste the callback URL...");
303
- console.log("\n");
304
- console.log("=".repeat(60));
305
- console.log("VPS/Remote Mode - Manual OAuth");
306
- console.log("=".repeat(60));
307
- console.log("\n1. Open the URL above in your LOCAL browser");
308
- console.log("2. Complete the Google sign-in");
309
- console.log("3. Your browser will redirect to a localhost URL that won't load");
310
- console.log("4. Copy the ENTIRE URL from your browser's address bar");
311
- console.log("5. Paste it below\n");
312
- console.log("The URL will look like:");
313
- console.log("http://localhost:51121/oauth-callback?code=xxx&state=yyy\n");
314
- const callbackInput = await promptInput("Paste the redirect URL here: ");
288
+ // Stop the spinner completely before prompting for input
289
+ // This prevents the continuous update loop that interferes with input
290
+ onProgress?.("");
291
+ // Show instructions using prompter.note for consistent UI
292
+ await prompter.note([
293
+ "1. Open the URL above in your LOCAL browser",
294
+ "2. Complete the Google sign-in",
295
+ "3. Your browser will redirect to a localhost URL that won't load",
296
+ "4. Copy the ENTIRE URL from your browser's address bar",
297
+ "5. Paste it below and press Enter",
298
+ "",
299
+ "The URL will look like:",
300
+ "http://localhost:51121/oauth-callback?code=xxx&state=yyy",
301
+ ].join("\n"), "VPS/Remote Mode - Manual OAuth");
302
+ // Use prompter.text() for clean input (same pattern as Telegram bot token)
303
+ const callbackInput = String(await prompter.text({
304
+ message: "Paste the redirect URL here",
305
+ validate: (value) => (value?.trim() ? undefined : "Required"),
306
+ })).trim();
315
307
  const parsed = parseCallbackInput(callbackInput, verifier);
316
308
  if ("error" in parsed) {
317
309
  throw new Error(parsed.error);
@@ -7,6 +7,12 @@ const AUTH_CHOICE_GROUP_DEFS = [
7
7
  hint: "Codex OAuth + API key",
8
8
  choices: ["codex-cli", "openai-codex", "openai-api-key"],
9
9
  },
10
+ {
11
+ value: "custom-openai",
12
+ label: "Custom OpenAI",
13
+ hint: "OpenAI-compatible API",
14
+ choices: ["custom-openai-api-key"],
15
+ },
10
16
  {
11
17
  value: "anthropic",
12
18
  label: "Anthropic",
@@ -123,6 +129,10 @@ export function buildAuthChoiceOptions(params) {
123
129
  });
124
130
  options.push({ value: "chutes", label: "Chutes (OAuth)" });
125
131
  options.push({ value: "openai-api-key", label: "OpenAI API key" });
132
+ options.push({
133
+ value: "custom-openai-api-key",
134
+ label: "Custom OpenAI (OpenAI-compatible API)"
135
+ });
126
136
  options.push({ value: "openrouter-api-key", label: "OpenRouter API key" });
127
137
  options.push({ value: "moonshot-api-key", label: "Moonshot AI API key" });
128
138
  options.push({ value: "synthetic-api-key", label: "Synthetic API key" });
@@ -0,0 +1,116 @@
1
+ import { applyAuthProfileConfig } from "./onboard-auth.js";
2
+ export async function applyAuthChoiceCustomOpenAI(params) {
3
+ if (params.authChoice !== "custom-openai-api-key") {
4
+ return null;
5
+ }
6
+ let nextConfig = params.config;
7
+ // Prompt for custom endpoint URL
8
+ const endpointUrl = await params.prompter.text({
9
+ message: "Enter OpenAI-compatible API endpoint URL",
10
+ placeholder: "https://api.example.com/v1",
11
+ validate: (value) => {
12
+ if (!value?.trim())
13
+ return "URL is required";
14
+ try {
15
+ new URL(value);
16
+ return undefined;
17
+ }
18
+ catch {
19
+ return "Invalid URL format";
20
+ }
21
+ },
22
+ });
23
+ const baseUrl = String(endpointUrl ?? "").trim();
24
+ if (!baseUrl) {
25
+ throw new Error("Custom endpoint URL is required");
26
+ }
27
+ // Prompt for API key
28
+ const apiKeyInput = await params.prompter.text({
29
+ message: "Enter API key for custom endpoint",
30
+ });
31
+ const apiKey = String(apiKeyInput ?? "").trim();
32
+ if (!apiKey) {
33
+ throw new Error("API key is required");
34
+ }
35
+ // Apply to OpenAI provider
36
+ nextConfig = applyCustomOpenAIConfig(nextConfig, { baseUrl, apiKey });
37
+ // Set auth profile
38
+ nextConfig = applyAuthProfileConfig(nextConfig, {
39
+ profileId: "openai:default",
40
+ provider: "openai",
41
+ mode: "api_key",
42
+ });
43
+ return { config: nextConfig };
44
+ }
45
+ function applyCustomOpenAIConfig(cfg, params) {
46
+ const providers = { ...cfg.models?.providers };
47
+ // Configure OpenAI provider with custom endpoint
48
+ providers.openai = {
49
+ ...providers.openai,
50
+ api: "openai-completions",
51
+ baseUrl: params.baseUrl,
52
+ apiKey: params.apiKey,
53
+ models: [
54
+ {
55
+ id: "gpt-4",
56
+ name: "GPT-4",
57
+ reasoning: false,
58
+ input: ["text"],
59
+ contextWindow: 8192,
60
+ maxTokens: 4096,
61
+ cost: {
62
+ input: 30,
63
+ output: 60,
64
+ cacheRead: 3,
65
+ cacheWrite: 37.5,
66
+ },
67
+ },
68
+ {
69
+ id: "gpt-3.5-turbo",
70
+ name: "GPT-3.5 Turbo",
71
+ reasoning: false,
72
+ input: ["text"],
73
+ contextWindow: 4096,
74
+ maxTokens: 4096,
75
+ cost: {
76
+ input: 0.5,
77
+ output: 1.5,
78
+ cacheRead: 0.05,
79
+ cacheWrite: 0.625,
80
+ },
81
+ },
82
+ ],
83
+ };
84
+ // Add model allowlist entries
85
+ const models = { ...cfg.agents?.defaults?.models };
86
+ models["openai/gpt-4"] = {};
87
+ models["openai/gpt-3.5-turbo"] = {};
88
+ return {
89
+ ...cfg,
90
+ models: {
91
+ ...cfg.models,
92
+ providers,
93
+ },
94
+ agents: {
95
+ ...cfg.agents,
96
+ defaults: {
97
+ ...cfg.agents?.defaults,
98
+ models,
99
+ model: {
100
+ ...(cfg.agents?.defaults?.model &&
101
+ "fallbacks" in cfg.agents.defaults.model
102
+ ? {
103
+ fallbacks: cfg.agents.defaults.model.fallbacks,
104
+ }
105
+ : undefined),
106
+ primary: "openai/gpt-4",
107
+ },
108
+ },
109
+ },
110
+ env: {
111
+ ...cfg.env,
112
+ OPENAI_API_KEY: params.apiKey,
113
+ OPENAI_BASE_URL: params.baseUrl,
114
+ },
115
+ };
116
+ }
@@ -1,11 +1,13 @@
1
1
  import { applyAuthChoiceAnthropic } from "./auth-choice.apply.anthropic.js";
2
2
  import { applyAuthChoiceApiProviders } from "./auth-choice.apply.api-providers.js";
3
+ import { applyAuthChoiceCustomOpenAI } from "./auth-choice.apply.custom-openai.js";
3
4
  import { applyAuthChoiceGitHubCopilot } from "./auth-choice.apply.github-copilot.js";
4
5
  import { applyAuthChoiceMiniMax } from "./auth-choice.apply.minimax.js";
5
6
  import { applyAuthChoiceOAuth } from "./auth-choice.apply.oauth.js";
6
7
  import { applyAuthChoiceOpenAI } from "./auth-choice.apply.openai.js";
7
8
  export async function applyAuthChoice(params) {
8
9
  const handlers = [
10
+ applyAuthChoiceCustomOpenAI,
9
11
  applyAuthChoiceAnthropic,
10
12
  applyAuthChoiceOpenAI,
11
13
  applyAuthChoiceOAuth,
@@ -97,7 +97,7 @@ export async function applyAuthChoiceOAuth(params) {
97
97
  const spin = params.prompter.progress("Starting OAuth flow…");
98
98
  let oauthCreds = null;
99
99
  try {
100
- oauthCreds = await loginAntigravityVpsAware(async (url) => {
100
+ oauthCreds = await loginAntigravityVpsAware(params.prompter, async (url) => {
101
101
  if (isRemote) {
102
102
  spin.stop("OAuth URL ready");
103
103
  params.runtime.log(`\nOpen this URL in your LOCAL browser:\n\n${url}\n`);
@@ -107,7 +107,15 @@ export async function applyAuthChoiceOAuth(params) {
107
107
  await openUrl(url);
108
108
  params.runtime.log(`Open: ${url}`);
109
109
  }
110
- }, (msg) => spin.update(msg));
110
+ }, (msg) => {
111
+ // Stop the spinner when signaled (empty message means stop)
112
+ if (msg === "") {
113
+ spin.stop("Waiting for callback URL...");
114
+ }
115
+ else {
116
+ spin.update(msg);
117
+ }
118
+ });
111
119
  spin.stop("Antigravity OAuth complete");
112
120
  if (oauthCreds) {
113
121
  await writeOAuthCredentials("google-antigravity", oauthCreds, params.agentDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theaux/clawdbot",
3
- "version": "2026.1.15",
3
+ "version": "2026.1.16",
4
4
  "description": "WhatsApp gateway CLI (Baileys web) with Pi RPC agent",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",