forge-remote 0.1.9 → 0.1.11

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/init.js +38 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-remote",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "Desktop relay for Forge Remote — monitor and control Claude Code sessions from your phone",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/src/init.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright (c) 2025-2026 Iron Forge Apps
3
3
  // Created by Daniel Wendel, CEO/Founder of Iron Forge Apps
4
4
 
5
- import { execSync, execFileSync } from "child_process";
5
+ import { execSync, execFileSync, spawn } from "child_process";
6
6
  import { createInterface } from "readline";
7
7
  import { hostname, platform, homedir } from "os";
8
8
  import { existsSync, mkdirSync, writeFileSync, readFileSync, rmSync } from "fs";
@@ -119,14 +119,24 @@ function waitForEnter(message) {
119
119
 
120
120
  /**
121
121
  * Open a URL in the default browser (best-effort, no throw).
122
+ * Uses spawn + detach to avoid blocking or suspending the terminal.
122
123
  */
123
124
  function openBrowser(url) {
124
125
  const p = platform();
125
126
  try {
126
- if (p === "darwin") execFileSync("open", [url], { stdio: "pipe" });
127
- else if (p === "win32")
128
- execFileSync("cmd", ["/c", "start", "", url], { stdio: "pipe" });
129
- else execFileSync("xdg-open", [url], { stdio: "pipe" });
127
+ let cmd, args;
128
+ if (p === "darwin") {
129
+ cmd = "open";
130
+ args = [url];
131
+ } else if (p === "win32") {
132
+ cmd = "cmd";
133
+ args = ["/c", "start", "", url];
134
+ } else {
135
+ cmd = "xdg-open";
136
+ args = [url];
137
+ }
138
+ const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
139
+ child.unref();
130
140
  } catch {
131
141
  // Silently fail — URL is always printed for manual opening.
132
142
  }
@@ -576,7 +586,8 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
576
586
  console.log(chalk.yellow(" ⚠ Firestore already enabled — continuing."));
577
587
  results.firestoreEnabled = true;
578
588
  } else {
579
- const maxFirestoreRetries = 2;
589
+ const maxFirestoreRetries = 4;
590
+ let billingPromptShown = false;
580
591
 
581
592
  for (let attempt = 0; attempt < maxFirestoreRetries; attempt++) {
582
593
  try {
@@ -614,13 +625,31 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
614
625
  break;
615
626
  }
616
627
 
628
+ // Detect API propagation delay — retry automatically with a wait.
629
+ if (
630
+ fullOutput.includes("has not been used in project") ||
631
+ fullOutput.includes("it is disabled")
632
+ ) {
633
+ if (attempt < maxFirestoreRetries - 1) {
634
+ console.log(
635
+ chalk.yellow(
636
+ ` ⚠ Firestore API still propagating (attempt ${attempt + 1}/${maxFirestoreRetries}). Waiting 15s...`,
637
+ ),
638
+ );
639
+ await new Promise((r) => setTimeout(r, 15000));
640
+ continue;
641
+ }
642
+ }
643
+
617
644
  if (
645
+ !billingPromptShown &&
618
646
  (fullOutput.includes("billing") ||
619
647
  fullOutput.includes("FAILED_PRECONDITION") ||
620
- fullOutput.includes("403")) &&
621
- attempt === 0
648
+ (fullOutput.includes("403") &&
649
+ !fullOutput.includes("has not been used")))
622
650
  ) {
623
- // First attempt failed due to billing — guide the user through upgrading.
651
+ // Failed due to billing — guide the user through upgrading.
652
+ billingPromptShown = true;
624
653
  console.log(
625
654
  chalk.yellow(
626
655
  "\n Firestore requires the Blaze (pay-as-you-go) plan.",