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/dev.js CHANGED
@@ -2230,8 +2230,9 @@ function generateDashboardHtml(options) {
2230
2230
  }
2231
2231
 
2232
2232
  btn.disabled = true;
2233
- btn.textContent = 'Setting...';
2234
- status.style.display = 'none';
2233
+ btn.textContent = 'Setting up...';
2234
+ status.textContent = 'Selecting project, detecting web app, auto-filling config...';
2235
+ 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;';
2235
2236
 
2236
2237
  fetch(API + '/__dev/setup/select-project', {
2237
2238
  method: 'POST',
@@ -2241,8 +2242,11 @@ function generateDashboardHtml(options) {
2241
2242
  .then(function(r) { return r.json(); })
2242
2243
  .then(function(data) {
2243
2244
  if (data.success) {
2244
- status.textContent = data.message;
2245
- 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;';
2245
+ var msg = data.steps ? data.steps.join('\\n') : data.message;
2246
+ status.textContent = msg;
2247
+ status.style.whiteSpace = 'pre-line';
2248
+ 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;';
2249
+ btn.textContent = 'Done';
2246
2250
  setTimeout(refreshSetupStatus, 1000);
2247
2251
  } else {
2248
2252
  status.textContent = data.message;
@@ -2953,7 +2957,7 @@ function generateDashboardHtml(options) {
2953
2957
  import { execFile as execFile2, spawn } from "child_process";
2954
2958
  import { existsSync as existsSync6, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2955
2959
  import { resolve as resolve5, join as join4 } from "path";
2956
- import { tmpdir, platform } from "os";
2960
+ import { tmpdir, platform, homedir } from "os";
2957
2961
  var FirebaseSetup = class {
2958
2962
  projectDir;
2959
2963
  stateFilePath;
@@ -3378,8 +3382,57 @@ var FirebaseSetup = class {
3378
3382
  }
3379
3383
  // ─── Firestore Database Automation ──────────────────────────────────
3380
3384
  /**
3381
- * Enable Firestore API via gcloud CLI.
3382
- * Required before creating a database for the first time.
3385
+ * Read Firebase CLI's stored OAuth token and exchange for access token.
3386
+ * No gcloud CLI needed uses the token from `firebase login`.
3387
+ */
3388
+ async getFirebaseAccessToken() {
3389
+ const home = homedir();
3390
+ const configPath = join4(home, ".config", "configstore", "firebase-tools.json");
3391
+ if (!existsSync6(configPath)) return null;
3392
+ try {
3393
+ const config = JSON.parse(readFileSync4(configPath, "utf-8"));
3394
+ let refreshToken = null;
3395
+ if (config?.activeAccounts) {
3396
+ const activeEmail = config?.user?.email;
3397
+ if (activeEmail && config.activeAccounts[activeEmail]?.tokens?.refresh_token) {
3398
+ refreshToken = config.activeAccounts[activeEmail].tokens.refresh_token;
3399
+ }
3400
+ if (!refreshToken) {
3401
+ for (const account of Object.values(config.activeAccounts)) {
3402
+ if (account?.tokens?.refresh_token) {
3403
+ refreshToken = account.tokens.refresh_token;
3404
+ break;
3405
+ }
3406
+ }
3407
+ }
3408
+ }
3409
+ if (!refreshToken && config?.tokens?.refresh_token) {
3410
+ refreshToken = config.tokens.refresh_token;
3411
+ }
3412
+ if (!refreshToken) return null;
3413
+ const body = new URLSearchParams({
3414
+ grant_type: "refresh_token",
3415
+ refresh_token: refreshToken,
3416
+ client_id: "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com",
3417
+ client_secret: "j9iVZfS8kkCEFUPaAeJV0sAi"
3418
+ });
3419
+ const res = await fetch("https://oauth2.googleapis.com/token", {
3420
+ method: "POST",
3421
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
3422
+ body: body.toString()
3423
+ });
3424
+ if (res.ok) {
3425
+ const data = await res.json();
3426
+ return data.access_token || null;
3427
+ }
3428
+ } catch {
3429
+ }
3430
+ return null;
3431
+ }
3432
+ /**
3433
+ * Enable Firestore API using Google Service Usage REST API.
3434
+ * Uses Firebase CLI's stored auth token — no gcloud CLI needed.
3435
+ * Falls back to gcloud CLI, then manual URL.
3383
3436
  */
3384
3437
  async enableFirestoreApi() {
3385
3438
  const state = this.loadState();
@@ -3397,24 +3450,46 @@ var FirebaseSetup = class {
3397
3450
  if (!projectId) {
3398
3451
  return { success: false, message: "No project ID found. Select a project first." };
3399
3452
  }
3453
+ try {
3454
+ const accessToken = await this.getFirebaseAccessToken();
3455
+ if (accessToken) {
3456
+ const res = await fetch(
3457
+ `https://serviceusage.googleapis.com/v1/projects/${projectId}/services/firestore.googleapis.com:enable`,
3458
+ {
3459
+ method: "POST",
3460
+ headers: {
3461
+ Authorization: `Bearer ${accessToken}`,
3462
+ "Content-Type": "application/json"
3463
+ }
3464
+ }
3465
+ );
3466
+ if (res.ok) {
3467
+ return { success: true, message: "Firestore API enabled via REST API." };
3468
+ }
3469
+ if (res.status === 409) {
3470
+ return { success: true, message: "Firestore API already enabled." };
3471
+ }
3472
+ }
3473
+ } catch {
3474
+ }
3400
3475
  try {
3401
3476
  await this.execTimeout(
3402
3477
  "gcloud",
3403
3478
  ["services", "enable", "firestore.googleapis.com", "--project", projectId],
3404
3479
  6e4
3405
3480
  );
3406
- return { success: true, message: "Firestore API enabled." };
3481
+ return { success: true, message: "Firestore API enabled via gcloud." };
3407
3482
  } catch (err) {
3408
3483
  const msg = err instanceof Error ? err.message : "Unknown error";
3409
- if (msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found")) {
3410
- return {
3411
- success: false,
3412
- message: `gcloud CLI not found. Please enable the Firestore API manually:
3413
- https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
3414
- };
3484
+ if (!(msg.includes("ENOENT") || msg.includes("not found") || msg.includes("command not found"))) {
3485
+ return { success: false, message: `Failed to enable Firestore API: ${msg}` };
3415
3486
  }
3416
- return { success: false, message: `Failed to enable Firestore API: ${msg}` };
3417
3487
  }
3488
+ return {
3489
+ success: false,
3490
+ message: `Failed to enable Firestore API automatically. Please enable it manually:
3491
+ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview?project=${projectId}`
3492
+ };
3418
3493
  }
3419
3494
  /**
3420
3495
  * Create Firestore database via CLI.
@@ -3422,41 +3497,48 @@ https://console.developers.google.com/apis/api/firestore.googleapis.com/overview
3422
3497
  * Handles "ALREADY_EXISTS" gracefully — returns success.
3423
3498
  */
3424
3499
  async createFirestoreDatabase(location = "nam5") {
3425
- try {
3426
- await this.execTimeout(
3427
- "firebase",
3428
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3429
- 6e4
3430
- );
3431
- return { success: true, message: `Firestore database created (location: ${location}).` };
3432
- } catch (err) {
3433
- const msg = err instanceof Error ? err.message : "Unknown error";
3434
- if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3435
- return { success: true, message: "Firestore database already exists." };
3436
- }
3437
- if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3438
- const enableResult = await this.enableFirestoreApi();
3439
- if (!enableResult.success) {
3440
- return enableResult;
3500
+ const dbArgs = ["firestore:databases:create", "(default)", "--location", location, "--json"];
3501
+ const tryCreate = async () => {
3502
+ try {
3503
+ await this.execTimeout("firebase", dbArgs, 6e4);
3504
+ return { ok: true, alreadyExists: false, needsApi: false, msg: "" };
3505
+ } catch (err) {
3506
+ const msg = err instanceof Error ? err.message : "Unknown error";
3507
+ if (msg.includes("ALREADY_EXISTS") || msg.includes("already exists")) {
3508
+ return { ok: true, alreadyExists: true, needsApi: false, msg };
3441
3509
  }
3442
- await new Promise((r) => setTimeout(r, 5e3));
3443
- try {
3444
- await this.execTimeout(
3445
- "firebase",
3446
- ["firestore:databases:create", "(default)", "--location", location, "--json"],
3447
- 6e4
3448
- );
3449
- return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3450
- } catch (retryErr) {
3451
- const retryMsg = retryErr instanceof Error ? retryErr.message : "Unknown error";
3452
- if (retryMsg.includes("ALREADY_EXISTS") || retryMsg.includes("already exists")) {
3453
- return { success: true, message: "Firestore database already exists." };
3454
- }
3455
- return { success: false, message: `Failed to create Firestore database after enabling API: ${retryMsg}` };
3510
+ if (msg.includes("403") || msg.includes("has not been used") || msg.includes("is disabled")) {
3511
+ return { ok: false, alreadyExists: false, needsApi: true, msg };
3456
3512
  }
3513
+ return { ok: false, alreadyExists: false, needsApi: false, msg };
3514
+ }
3515
+ };
3516
+ const first = await tryCreate();
3517
+ if (first.ok) {
3518
+ return { success: true, message: first.alreadyExists ? "Firestore database already exists." : `Firestore database created (location: ${location}).` };
3519
+ }
3520
+ if (!first.needsApi) {
3521
+ return { success: false, message: `Failed to create Firestore database: ${first.msg}` };
3522
+ }
3523
+ const enableResult = await this.enableFirestoreApi();
3524
+ if (!enableResult.success) {
3525
+ return enableResult;
3526
+ }
3527
+ const waits = [5e3, 5e3, 1e4, 1e4, 15e3];
3528
+ for (let i = 0; i < waits.length; i++) {
3529
+ await new Promise((r) => setTimeout(r, waits[i]));
3530
+ const retry = await tryCreate();
3531
+ if (retry.ok) {
3532
+ return { success: true, message: `Firestore API enabled and database created (location: ${location}).` };
3533
+ }
3534
+ if (!retry.needsApi) {
3535
+ return { success: false, message: `Failed to create Firestore database: ${retry.msg}` };
3457
3536
  }
3458
- return { success: false, message: `Failed to create Firestore database: ${msg}` };
3459
3537
  }
3538
+ return {
3539
+ success: false,
3540
+ message: "Firestore API was enabled but database creation timed out waiting for propagation. Please wait a minute and try again."
3541
+ };
3460
3542
  }
3461
3543
  /**
3462
3544
  * Deploy open Firestore security rules for dev testing.
@@ -4476,10 +4558,57 @@ ${liveReloadScript}
4476
4558
  sendJson({ success: false, message: "projectId is required" }, 400);
4477
4559
  return;
4478
4560
  }
4479
- this.firebaseSetup.selectProject(data.projectId).then((result) => {
4561
+ (async () => {
4562
+ const selectResult = await this.firebaseSetup.selectProject(data.projectId);
4563
+ if (!selectResult.success) {
4564
+ sendJson(selectResult);
4565
+ return;
4566
+ }
4567
+ const steps = [selectResult.message];
4568
+ try {
4569
+ const { apps } = await this.firebaseSetup.listWebApps();
4570
+ let webAppId = "";
4571
+ if (apps.length > 0) {
4572
+ webAppId = apps[0].appId;
4573
+ this.firebaseSetup.selectWebApp(apps[0].appId, apps[0].displayName);
4574
+ steps.push(`Web app "${apps[0].displayName}" selected.`);
4575
+ } else {
4576
+ const createResult = await this.firebaseSetup.createWebApp(data.projectId);
4577
+ if (createResult.success && createResult.appId) {
4578
+ webAppId = createResult.appId;
4579
+ steps.push(`Web app "${data.projectId}" created.`);
4580
+ } else {
4581
+ steps.push(`Web app creation skipped: ${createResult.message}`);
4582
+ }
4583
+ }
4584
+ if (webAppId) {
4585
+ try {
4586
+ const sdkConfig = await fetchFirebaseSdkConfig(this.options.projectDir, webAppId);
4587
+ const fields = {};
4588
+ for (const [key, value] of Object.entries(sdkConfig)) {
4589
+ if (value && typeof value === "string") {
4590
+ fields[key] = value;
4591
+ }
4592
+ }
4593
+ if (Object.keys(fields).length > 0) {
4594
+ for (const [key, value] of Object.entries(fields)) {
4595
+ try {
4596
+ this.updateProjectConfig(key, value);
4597
+ } catch {
4598
+ }
4599
+ }
4600
+ steps.push("Config auto-filled in clawfire.config.ts.");
4601
+ }
4602
+ } catch (configErr) {
4603
+ steps.push(`Config auto-fill skipped: ${configErr instanceof Error ? configErr.message : "unknown error"}`);
4604
+ }
4605
+ }
4606
+ } catch (autoFillErr) {
4607
+ steps.push(`Auto-setup partial: ${autoFillErr instanceof Error ? autoFillErr.message : "unknown error"}`);
4608
+ }
4480
4609
  clearFirebaseStatusCache();
4481
- sendJson(result);
4482
- }).catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4610
+ sendJson({ success: true, message: steps.join(" "), steps });
4611
+ })().catch((err) => sendJson({ success: false, message: err instanceof Error ? err.message : "Failed" }, 500));
4483
4612
  } catch {
4484
4613
  sendJson({ success: false, message: "Invalid JSON body" }, 400);
4485
4614
  }