clawfire 0.6.4 → 0.6.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/dist/cli.js CHANGED
@@ -269,7 +269,7 @@ async function runDevServer() {
269
269
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : 3e3;
270
270
  const apiPort = apiPortArg ? parseInt(apiPortArg.split("=")[1], 10) : 3456;
271
271
  const noHotReload = args.includes("--no-hot-reload");
272
- const { startDevServer } = await import("./dev-server-7JCKX7OD.js");
272
+ const { startDevServer } = await import("./dev-server-PQP33VSE.js");
273
273
  await startDevServer({
274
274
  projectDir,
275
275
  port,
@@ -1856,8 +1856,9 @@ function generateDashboardHtml(options) {
1856
1856
  }
1857
1857
 
1858
1858
  btn.disabled = true;
1859
- btn.textContent = 'Setting...';
1860
- status.style.display = 'none';
1859
+ btn.textContent = 'Setting up...';
1860
+ status.textContent = 'Selecting project, detecting web app, auto-filling config...';
1861
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1c;border:1px solid #3b82f6;color:#93c5fd;';
1861
1862
 
1862
1863
  fetch(API + '/__dev/setup/select-project', {
1863
1864
  method: 'POST',
@@ -1867,8 +1868,11 @@ function generateDashboardHtml(options) {
1867
1868
  .then(function(r) { return r.json(); })
1868
1869
  .then(function(data) {
1869
1870
  if (data.success) {
1870
- status.textContent = data.message;
1871
- status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
1871
+ var msg = data.steps ? data.steps.join('\\n') : data.message;
1872
+ status.textContent = msg;
1873
+ status.style.whiteSpace = 'pre-line';
1874
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;white-space:pre-line;';
1875
+ btn.textContent = 'Done';
1872
1876
  setTimeout(refreshSetupStatus, 1000);
1873
1877
  } else {
1874
1878
  status.textContent = data.message;
@@ -2579,7 +2583,7 @@ function generateDashboardHtml(options) {
2579
2583
  import { execFile as execFile2, spawn } from "child_process";
2580
2584
  import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2581
2585
  import { resolve as resolve4, join as join3 } from "path";
2582
- import { tmpdir, platform } from "os";
2586
+ import { tmpdir, platform, homedir } from "os";
2583
2587
  var FirebaseSetup = class {
2584
2588
  projectDir;
2585
2589
  stateFilePath;
@@ -3004,8 +3008,57 @@ var FirebaseSetup = class {
3004
3008
  }
3005
3009
  // ─── Firestore Database Automation ──────────────────────────────────
3006
3010
  /**
3007
- * Enable Firestore API via gcloud CLI.
3008
- * Required before creating a database for the first time.
3011
+ * Read Firebase CLI's stored OAuth token and exchange for access token.
3012
+ * No gcloud CLI needed uses the token from `firebase login`.
3013
+ */
3014
+ async getFirebaseAccessToken() {
3015
+ const home = homedir();
3016
+ const configPath = join3(home, ".config", "configstore", "firebase-tools.json");
3017
+ if (!existsSync5(configPath)) return null;
3018
+ try {
3019
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
3020
+ let refreshToken = null;
3021
+ if (config?.activeAccounts) {
3022
+ const activeEmail = config?.user?.email;
3023
+ if (activeEmail && config.activeAccounts[activeEmail]?.tokens?.refresh_token) {
3024
+ refreshToken = config.activeAccounts[activeEmail].tokens.refresh_token;
3025
+ }
3026
+ if (!refreshToken) {
3027
+ for (const account of Object.values(config.activeAccounts)) {
3028
+ if (account?.tokens?.refresh_token) {
3029
+ refreshToken = account.tokens.refresh_token;
3030
+ break;
3031
+ }
3032
+ }
3033
+ }
3034
+ }
3035
+ if (!refreshToken && config?.tokens?.refresh_token) {
3036
+ refreshToken = config.tokens.refresh_token;
3037
+ }
3038
+ if (!refreshToken) return null;
3039
+ const body = new URLSearchParams({
3040
+ grant_type: "refresh_token",
3041
+ refresh_token: refreshToken,
3042
+ client_id: "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com",
3043
+ client_secret: "j9iVZfS8kkCEFUPaAeJV0sAi"
3044
+ });
3045
+ const res = await fetch("https://oauth2.googleapis.com/token", {
3046
+ method: "POST",
3047
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3048
+ body: body.toString()
3049
+ });
3050
+ if (res.ok) {
3051
+ const data = await res.json();
3052
+ return data.access_token || null;
3053
+ }
3054
+ } catch {
3055
+ }
3056
+ return null;
3057
+ }
3058
+ /**
3059
+ * Enable Firestore API using Google Service Usage REST API.
3060
+ * Uses Firebase CLI's stored auth token — no gcloud CLI needed.
3061
+ * Falls back to gcloud CLI, then manual URL.
3009
3062
  */
3010
3063
  async enableFirestoreApi() {
3011
3064
  const state = this.loadState();
@@ -3023,24 +3076,46 @@ var FirebaseSetup = class {
3023
3076
  if (!projectId) {
3024
3077
  return { success: false, message: "No project ID found. Select a project first." };
3025
3078
  }
3079
+ try {
3080
+ const accessToken = await this.getFirebaseAccessToken();
3081
+ if (accessToken) {
3082
+ const res = await fetch(
3083
+ `https://serviceusage.googleapis.com/v1/projects/${projectId}/services/firestore.googleapis.com:enable`,
3084
+ {
3085
+ method: "POST",
3086
+ headers: {
3087
+ Authorization: `Bearer ${accessToken}`,
3088
+ "Content-Type": "application/json"
3089
+ }
3090
+ }
3091
+ );
3092
+ if (res.ok) {
3093
+ return { success: true, message: "Firestore API enabled via REST API." };
3094
+ }
3095
+ if (res.status === 409) {
3096
+ return { success: true, message: "Firestore API already enabled." };
3097
+ }
3098
+ }
3099
+ } catch {
3100
+ }
3026
3101
  try {
3027
3102
  await this.execTimeout(
3028
3103
  "gcloud",
3029
3104
  ["services", "enable", "firestore.googleapis.com", "--project", projectId],
3030
3105
  6e4
3031
3106
  );
3032
- return { success: true, message: "Firestore API enabled." };
3107
+ return { success: true, message: "Firestore API enabled via gcloud." };
3033
3108
  } catch (err) {
3034
3109
  const msg = err instanceof Error ? err.message : "Unknown error";
3035
- if (msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found")) {
3036
- return {
3037
- success: false,
3038
- message: `gcloud CLI not found. Please enable the Firestore API manually:
3039
- https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
3040
- };
3110
+ if (!(msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found"))) {
3111
+ return { success: false, message: `Failed to enable Firestore API: ${msg}` };
3041
3112
  }
3042
- return { success: false, message: `Failed to enable Firestore API: ${msg}` };
3043
3113
  }
3114
+ return {
3115
+ success: false,
3116
+ message: `Failed to enable Firestore API automatically. Please enable it manually:
3117
+ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
3118
+ };
3044
3119
  }
3045
3120
  /**
3046
3121
  * Create Firestore database via CLI.
@@ -3048,41 +3123,48 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3048
3123
  * Handles "ALREADY_EXISTS" gracefully — returns success.
3049
3124
  */
3050
3125
  async createFirestoreDatabase(location = "nam5") {
3051
- try {
3052
- await this.execTimeout(
3053
- "firebase",
3054
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3055
- 6e4
3056
- );
3057
- return { success: true, message: `Firestore database created (location: ${location}).` };
3058
- } catch (err) {
3059
- const msg = err instanceof Error ? err.message : "Unknown error";
3060
- if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3061
- return { success: true, message: "Firestore database already exists." };
3062
- }
3063
- if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3064
- const enableResult = await this.enableFirestoreApi();
3065
- if (!enableResult.success) {
3066
- return enableResult;
3126
+ const dbArgs = ["firestore:databases:create", "(default)", "--location", location, "--json"];
3127
+ const tryCreate = async () => {
3128
+ try {
3129
+ await this.execTimeout("firebase", dbArgs, 6e4);
3130
+ return { ok: true, alreadyExists: false, needsApi: false, msg: "" };
3131
+ } catch (err) {
3132
+ const msg = err instanceof Error ? err.message : "Unknown error";
3133
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3134
+ return { ok: true, alreadyExists: true, needsApi: false, msg };
3067
3135
  }
3068
- await new Promise((r) => setTimeout(r, 5e3));
3069
- try {
3070
- await this.execTimeout(
3071
- "firebase",
3072
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3073
- 6e4
3074
- );
3075
- return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3076
- } catch (retryErr) {
3077
- const retryMsg = retryErr instanceof Error ? retryErr.message : "Unknown error";
3078
- if (retryMsg.includes("ALREADY_EXISTS") || retryMsg.includes("already exists")) {
3079
- return { success: true, message: "Firestore database already exists." };
3080
- }
3081
- return { success: false, message: `Failed to create Firestore database after enabling API: ${retryMsg}` };
3136
+ if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3137
+ return { ok: false, alreadyExists: false, needsApi: true, msg };
3082
3138
  }
3139
+ return { ok: false, alreadyExists: false, needsApi: false, msg };
3140
+ }
3141
+ };
3142
+ const first = await tryCreate();
3143
+ if (first.ok) {
3144
+ return { success: true, message: first.alreadyExists ? "Firestore database already exists." : `Firestore database created (location: ${location}).` };
3145
+ }
3146
+ if (!first.needsApi) {
3147
+ return { success: false, message: `Failed to create Firestore database: ${first.msg}` };
3148
+ }
3149
+ const enableResult = await this.enableFirestoreApi();
3150
+ if (!enableResult.success) {
3151
+ return enableResult;
3152
+ }
3153
+ const waits = [5e3, 5e3, 1e4, 1e4, 15e3];
3154
+ for (let i = 0; i < waits.length; i++) {
3155
+ await new Promise((r) => setTimeout(r, waits[i]));
3156
+ const retry = await tryCreate();
3157
+ if (retry.ok) {
3158
+ return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3159
+ }
3160
+ if (!retry.needsApi) {
3161
+ return { success: false, message: `Failed to create Firestore database: ${retry.msg}` };
3083
3162
  }
3084
- return { success: false, message: `Failed to create Firestore database: ${msg}` };
3085
3163
  }
3164
+ return {
3165
+ success: false,
3166
+ message: "Firestore API was enabled but database creation timed out waiting for propagation. Please wait a minute and try again."
3167
+ };
3086
3168
  }
3087
3169
  /**
3088
3170
  * Deploy open Firestore security rules for dev testing.
@@ -4102,10 +4184,57 @@ ${liveReloadScript}
4102
4184
  sendJson({ success: false, message: "projectId is required" }, 400);
4103
4185
  return;
4104
4186
  }
4105
- this.firebaseSetup.selectProject(data.projectId).then((result) => {
4187
+ (async () => {
4188
+ const selectResult = await this.firebaseSetup.selectProject(data.projectId);
4189
+ if (!selectResult.success) {
4190
+ sendJson(selectResult);
4191
+ return;
4192
+ }
4193
+ const steps = [selectResult.message];
4194
+ try {
4195
+ const { apps } = await this.firebaseSetup.listWebApps();
4196
+ let webAppId = "";
4197
+ if (apps.length > 0) {
4198
+ webAppId = apps[0].appId;
4199
+ this.firebaseSetup.selectWebApp(apps[0].appId, apps[0].displayName);
4200
+ steps.push(`Web app "${apps[0].displayName}" selected.`);
4201
+ } else {
4202
+ const createResult = await this.firebaseSetup.createWebApp(data.projectId);
4203
+ if (createResult.success && createResult.appId) {
4204
+ webAppId = createResult.appId;
4205
+ steps.push(`Web app "${data.projectId}" created.`);
4206
+ } else {
4207
+ steps.push(`Web app creation skipped: ${createResult.message}`);
4208
+ }
4209
+ }
4210
+ if (webAppId) {
4211
+ try {
4212
+ const sdkConfig = await fetchFirebaseSdkConfig(this.options.projectDir, webAppId);
4213
+ const fields = {};
4214
+ for (const [key, value] of Object.entries(sdkConfig)) {
4215
+ if (value && typeof value === "string") {
4216
+ fields[key] = value;
4217
+ }
4218
+ }
4219
+ if (Object.keys(fields).length > 0) {
4220
+ for (const [key, value] of Object.entries(fields)) {
4221
+ try {
4222
+ this.updateProjectConfig(key, value);
4223
+ } catch {
4224
+ }
4225
+ }
4226
+ steps.push("Config auto-filled in clawfire.config.ts.");
4227
+ }
4228
+ } catch (configErr) {
4229
+ steps.push(`Config auto-fill skipped: ${configErr instanceof Error ? configErr.message : "unknown error"}`);
4230
+ }
4231
+ }
4232
+ } catch (autoFillErr) {
4233
+ steps.push(`Auto-setup partial: ${autoFillErr instanceof Error ? autoFillErr.message : "unknown error"}`);
4234
+ }
4106
4235
  clearFirebaseStatusCache();
4107
- sendJson(result);
4108
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4236
+ sendJson({ success: true, message: steps.join(" "), steps });
4237
+ })().catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4109
4238
  } catch {
4110
4239
  sendJson({ success: false, message: "Invalid JSON body" }, 400);
4111
4240
  }
package/dist/dev.cjs CHANGED
@@ -2268,8 +2268,9 @@ function generateDashboardHtml(options) {
2268
2268
  }
2269
2269
 
2270
2270
  btn.disabled = true;
2271
- btn.textContent = 'Setting...';
2272
- status.style.display = 'none';
2271
+ btn.textContent = 'Setting up...';
2272
+ status.textContent = 'Selecting project, detecting web app, auto-filling config...';
2273
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a0a1c;border:1px solid #3b82f6;color:#93c5fd;';
2273
2274
 
2274
2275
  fetch(API + '/__dev/setup/select-project', {
2275
2276
  method: 'POST',
@@ -2279,8 +2280,11 @@ function generateDashboardHtml(options) {
2279
2280
  .then(function(r) { return r.json(); })
2280
2281
  .then(function(data) {
2281
2282
  if (data.success) {
2282
- status.textContent = data.message;
2283
- status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;';
2283
+ var msg = data.steps ? data.steps.join('\\n') : data.message;
2284
+ status.textContent = msg;
2285
+ status.style.whiteSpace = 'pre-line';
2286
+ status.style.cssText = 'display:block;margin-top:8px;font-size:13px;padding:8px 12px;border-radius:6px;background:#0a1a0a;border:1px solid #22c55e;color:#22c55e;white-space:pre-line;';
2287
+ btn.textContent = 'Done';
2284
2288
  setTimeout(refreshSetupStatus, 1000);
2285
2289
  } else {
2286
2290
  status.textContent = data.message;
@@ -3416,8 +3420,57 @@ var FirebaseSetup = class {
3416
3420
  }
3417
3421
  // ─── Firestore Database Automation ──────────────────────────────────
3418
3422
  /**
3419
- * Enable Firestore API via gcloud CLI.
3420
- * Required before creating a database for the first time.
3423
+ * Read Firebase CLI's stored OAuth token and exchange for access token.
3424
+ * No gcloud CLI needed uses the token from `firebase login`.
3425
+ */
3426
+ async getFirebaseAccessToken() {
3427
+ const home = (0, import_node_os.homedir)();
3428
+ const configPath = (0, import_node_path4.join)(home, ".config", "configstore", "firebase-tools.json");
3429
+ if (!(0, import_node_fs4.existsSync)(configPath)) return null;
3430
+ try {
3431
+ const config = JSON.parse((0, import_node_fs4.readFileSync)(configPath, "utf-8"));
3432
+ let refreshToken = null;
3433
+ if (config?.activeAccounts) {
3434
+ const activeEmail = config?.user?.email;
3435
+ if (activeEmail && config.activeAccounts[activeEmail]?.tokens?.refresh_token) {
3436
+ refreshToken = config.activeAccounts[activeEmail].tokens.refresh_token;
3437
+ }
3438
+ if (!refreshToken) {
3439
+ for (const account of Object.values(config.activeAccounts)) {
3440
+ if (account?.tokens?.refresh_token) {
3441
+ refreshToken = account.tokens.refresh_token;
3442
+ break;
3443
+ }
3444
+ }
3445
+ }
3446
+ }
3447
+ if (!refreshToken && config?.tokens?.refresh_token) {
3448
+ refreshToken = config.tokens.refresh_token;
3449
+ }
3450
+ if (!refreshToken) return null;
3451
+ const body = new URLSearchParams({
3452
+ grant_type: "refresh_token",
3453
+ refresh_token: refreshToken,
3454
+ client_id: "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com",
3455
+ client_secret: "j9iVZfS8kkCEFUPaAeJV0sAi"
3456
+ });
3457
+ const res = await fetch("https://oauth2.googleapis.com/token", {
3458
+ method: "POST",
3459
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3460
+ body: body.toString()
3461
+ });
3462
+ if (res.ok) {
3463
+ const data = await res.json();
3464
+ return data.access_token || null;
3465
+ }
3466
+ } catch {
3467
+ }
3468
+ return null;
3469
+ }
3470
+ /**
3471
+ * Enable Firestore API using Google Service Usage REST API.
3472
+ * Uses Firebase CLI's stored auth token — no gcloud CLI needed.
3473
+ * Falls back to gcloud CLI, then manual URL.
3421
3474
  */
3422
3475
  async enableFirestoreApi() {
3423
3476
  const state = this.loadState();
@@ -3435,24 +3488,46 @@ var FirebaseSetup = class {
3435
3488
  if (!projectId) {
3436
3489
  return { success: false, message: "No project ID found. Select a project first." };
3437
3490
  }
3491
+ try {
3492
+ const accessToken = await this.getFirebaseAccessToken();
3493
+ if (accessToken) {
3494
+ const res = await fetch(
3495
+ `https://serviceusage.googleapis.com/v1/projects/${projectId}/services/firestore.googleapis.com:enable`,
3496
+ {
3497
+ method: "POST",
3498
+ headers: {
3499
+ Authorization: `Bearer ${accessToken}`,
3500
+ "Content-Type": "application/json"
3501
+ }
3502
+ }
3503
+ );
3504
+ if (res.ok) {
3505
+ return { success: true, message: "Firestore API enabled via REST API." };
3506
+ }
3507
+ if (res.status === 409) {
3508
+ return { success: true, message: "Firestore API already enabled." };
3509
+ }
3510
+ }
3511
+ } catch {
3512
+ }
3438
3513
  try {
3439
3514
  await this.execTimeout(
3440
3515
  "gcloud",
3441
3516
  ["services", "enable", "firestore.googleapis.com", "--project", projectId],
3442
3517
  6e4
3443
3518
  );
3444
- return { success: true, message: "Firestore API enabled." };
3519
+ return { success: true, message: "Firestore API enabled via gcloud." };
3445
3520
  } catch (err) {
3446
3521
  const msg = err instanceof Error ? err.message : "Unknown error";
3447
- if (msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found")) {
3448
- return {
3449
- success: false,
3450
- message: `gcloud CLI not found. Please enable the Firestore API manually:
3451
- https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
3452
- };
3522
+ if (!(msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found"))) {
3523
+ return { success: false, message: `Failed to enable Firestore API: ${msg}` };
3453
3524
  }
3454
- return { success: false, message: `Failed to enable Firestore API: ${msg}` };
3455
3525
  }
3526
+ return {
3527
+ success: false,
3528
+ message: `Failed to enable Firestore API automatically. Please enable it manually:
3529
+ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
3530
+ };
3456
3531
  }
3457
3532
  /**
3458
3533
  * Create Firestore database via CLI.
@@ -3460,41 +3535,48 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3460
3535
  * Handles "ALREADY_EXISTS" gracefully — returns success.
3461
3536
  */
3462
3537
  async createFirestoreDatabase(location = "nam5") {
3463
- try {
3464
- await this.execTimeout(
3465
- "firebase",
3466
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3467
- 6e4
3468
- );
3469
- return { success: true, message: `Firestore database created (location: ${location}).` };
3470
- } catch (err) {
3471
- const msg = err instanceof Error ? err.message : "Unknown error";
3472
- if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3473
- return { success: true, message: "Firestore database already exists." };
3474
- }
3475
- if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3476
- const enableResult = await this.enableFirestoreApi();
3477
- if (!enableResult.success) {
3478
- return enableResult;
3538
+ const dbArgs = ["firestore:databases:create", "(default)", "--location", location, "--json"];
3539
+ const tryCreate = async () => {
3540
+ try {
3541
+ await this.execTimeout("firebase", dbArgs, 6e4);
3542
+ return { ok: true, alreadyExists: false, needsApi: false, msg: "" };
3543
+ } catch (err) {
3544
+ const msg = err instanceof Error ? err.message : "Unknown error";
3545
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3546
+ return { ok: true, alreadyExists: true, needsApi: false, msg };
3479
3547
  }
3480
- await new Promise((r) => setTimeout(r, 5e3));
3481
- try {
3482
- await this.execTimeout(
3483
- "firebase",
3484
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3485
- 6e4
3486
- );
3487
- return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3488
- } catch (retryErr) {
3489
- const retryMsg = retryErr instanceof Error ? retryErr.message : "Unknown error";
3490
- if (retryMsg.includes("ALREADY_EXISTS") || retryMsg.includes("already exists")) {
3491
- return { success: true, message: "Firestore database already exists." };
3492
- }
3493
- return { success: false, message: `Failed to create Firestore database after enabling API: ${retryMsg}` };
3548
+ if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3549
+ return { ok: false, alreadyExists: false, needsApi: true, msg };
3494
3550
  }
3551
+ return { ok: false, alreadyExists: false, needsApi: false, msg };
3552
+ }
3553
+ };
3554
+ const first = await tryCreate();
3555
+ if (first.ok) {
3556
+ return { success: true, message: first.alreadyExists ? "Firestore database already exists." : `Firestore database created (location: ${location}).` };
3557
+ }
3558
+ if (!first.needsApi) {
3559
+ return { success: false, message: `Failed to create Firestore database: ${first.msg}` };
3560
+ }
3561
+ const enableResult = await this.enableFirestoreApi();
3562
+ if (!enableResult.success) {
3563
+ return enableResult;
3564
+ }
3565
+ const waits = [5e3, 5e3, 1e4, 1e4, 15e3];
3566
+ for (let i = 0; i < waits.length; i++) {
3567
+ await new Promise((r) => setTimeout(r, waits[i]));
3568
+ const retry = await tryCreate();
3569
+ if (retry.ok) {
3570
+ return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3571
+ }
3572
+ if (!retry.needsApi) {
3573
+ return { success: false, message: `Failed to create Firestore database: ${retry.msg}` };
3495
3574
  }
3496
- return { success: false, message: `Failed to create Firestore database: ${msg}` };
3497
3575
  }
3576
+ return {
3577
+ success: false,
3578
+ message: "Firestore API was enabled but database creation timed out waiting for propagation. Please wait a minute and try again."
3579
+ };
3498
3580
  }
3499
3581
  /**
3500
3582
  * Deploy open Firestore security rules for dev testing.
@@ -4514,10 +4596,57 @@ ${liveReloadScript}
4514
4596
  sendJson({ success: false, message: "projectId is required" }, 400);
4515
4597
  return;
4516
4598
  }
4517
- this.firebaseSetup.selectProject(data.projectId).then((result) => {
4599
+ (async () => {
4600
+ const selectResult = await this.firebaseSetup.selectProject(data.projectId);
4601
+ if (!selectResult.success) {
4602
+ sendJson(selectResult);
4603
+ return;
4604
+ }
4605
+ const steps = [selectResult.message];
4606
+ try {
4607
+ const { apps } = await this.firebaseSetup.listWebApps();
4608
+ let webAppId = "";
4609
+ if (apps.length > 0) {
4610
+ webAppId = apps[0].appId;
4611
+ this.firebaseSetup.selectWebApp(apps[0].appId, apps[0].displayName);
4612
+ steps.push(`Web app "${apps[0].displayName}" selected.`);
4613
+ } else {
4614
+ const createResult = await this.firebaseSetup.createWebApp(data.projectId);
4615
+ if (createResult.success && createResult.appId) {
4616
+ webAppId = createResult.appId;
4617
+ steps.push(`Web app "${data.projectId}" created.`);
4618
+ } else {
4619
+ steps.push(`Web app creation skipped: ${createResult.message}`);
4620
+ }
4621
+ }
4622
+ if (webAppId) {
4623
+ try {
4624
+ const sdkConfig = await fetchFirebaseSdkConfig(this.options.projectDir, webAppId);
4625
+ const fields = {};
4626
+ for (const [key, value] of Object.entries(sdkConfig)) {
4627
+ if (value && typeof value === "string") {
4628
+ fields[key] = value;
4629
+ }
4630
+ }
4631
+ if (Object.keys(fields).length > 0) {
4632
+ for (const [key, value] of Object.entries(fields)) {
4633
+ try {
4634
+ this.updateProjectConfig(key, value);
4635
+ } catch {
4636
+ }
4637
+ }
4638
+ steps.push("Config auto-filled in clawfire.config.ts.");
4639
+ }
4640
+ } catch (configErr) {
4641
+ steps.push(`Config auto-fill skipped: ${configErr instanceof Error ? configErr.message : "unknown error"}`);
4642
+ }
4643
+ }
4644
+ } catch (autoFillErr) {
4645
+ steps.push(`Auto-setup partial: ${autoFillErr instanceof Error ? autoFillErr.message : "unknown error"}`);
4646
+ }
4518
4647
  clearFirebaseStatusCache();
4519
- sendJson(result);
4520
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4648
+ sendJson({ success: true, message: steps.join(" "), steps });
4649
+ })().catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4521
4650
  } catch {
4522
4651
  sendJson({ success: false, message: "Invalid JSON body" }, 400);
4523
4652
  }