forge-remote 0.1.23 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-remote",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
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/cli.js CHANGED
@@ -210,8 +210,10 @@ program
210
210
  if (!sdkConfig) {
211
211
  console.error("\nCould not load Firebase SDK config.");
212
212
  console.error(
213
- "Run `forge-remote init` first, or ensure the Firebase CLI is installed.\n",
213
+ "Run `npx forge-remote init` first, or ensure the Firebase CLI is installed.",
214
214
  );
215
+ console.error(" Install Firebase CLI: npm install -g firebase-tools");
216
+ console.error(` Setup guide: https://forgeremote.com/#how-it-works\n`);
215
217
  process.exit(1);
216
218
  }
217
219
 
package/src/init.js CHANGED
@@ -200,21 +200,74 @@ export async function runInit({ projectId: overrideProjectId } = {}) {
200
200
  console.log(
201
201
  chalk.dim(" a QR code to pair with the mobile app. Fully automated.\n"),
202
202
  );
203
+ console.log(
204
+ chalk.dim(
205
+ ` Full setup guide: ${chalk.cyan("https://forgeremote.com/#how-it-works")}`,
206
+ ),
207
+ );
203
208
 
204
- // ── Step 1: Check Firebase CLI ──────────────────────────────────────────
209
+ // ── Prerequisites check ──────────────────────────────────────────────────
205
210
 
206
- console.log(chalk.bold("\n📋 Step 1/11 — Checking Firebase CLI...\n"));
211
+ console.log(chalk.bold("\n📋 Prerequisites Check\n"));
207
212
 
213
+ const nodeVersion = run(["node", "--version"], { silent: true });
214
+ const npmVersion = run(["npm", "--version"], { silent: true });
208
215
  const firebaseVersion = run(["firebase", "--version"], { silent: true });
216
+
217
+ console.log(
218
+ nodeVersion
219
+ ? chalk.green(` ✓ Node.js ${nodeVersion}`)
220
+ : chalk.red(" ✗ Node.js — not found"),
221
+ );
222
+ console.log(
223
+ npmVersion
224
+ ? chalk.green(` ✓ npm ${npmVersion}`)
225
+ : chalk.red(" ✗ npm — not found"),
226
+ );
227
+ console.log(
228
+ firebaseVersion
229
+ ? chalk.green(` ✓ Firebase CLI ${firebaseVersion}`)
230
+ : chalk.red(" ✗ Firebase CLI — not found"),
231
+ );
232
+
233
+ if (!nodeVersion) {
234
+ console.error(chalk.red("\n Node.js is required (v18 or later).\n"));
235
+ console.error(chalk.bold(" Install it from:\n"));
236
+ console.error(chalk.cyan(" https://nodejs.org\n"));
237
+ process.exit(1);
238
+ }
239
+
209
240
  if (!firebaseVersion) {
210
- console.error(chalk.red(" ✗ Firebase CLI not found.\n"));
241
+ console.error(
242
+ chalk.red("\n Firebase CLI is required but not installed.\n"),
243
+ );
244
+ console.error(
245
+ chalk.dim(
246
+ " The Firebase CLI is a free command-line tool from Google that lets",
247
+ ),
248
+ );
249
+ console.error(
250
+ chalk.dim(
251
+ " Forge Remote create and manage your Firebase project automatically.\n",
252
+ ),
253
+ );
211
254
  console.error(chalk.bold(" Install it with:\n"));
212
255
  console.error(chalk.cyan(" npm install -g firebase-tools\n"));
256
+ console.error(chalk.dim(" Then run this command again:\n"));
257
+ console.error(chalk.cyan(" npx forge-remote init\n"));
213
258
  console.error(
214
- chalk.dim(" Then run this command again: forge-remote init\n"),
259
+ chalk.dim(
260
+ ` For more info: ${chalk.cyan("https://forgeremote.com/#how-it-works")}\n`,
261
+ ),
215
262
  );
216
263
  process.exit(1);
217
264
  }
265
+
266
+ console.log(chalk.green("\n All prerequisites met!\n"));
267
+
268
+ // ── Step 1: Check Firebase CLI ──────────────────────────────────────────
269
+
270
+ console.log(chalk.bold("\n📋 Step 1/11 — Checking Firebase CLI...\n"));
218
271
  console.log(chalk.green(` ✓ Firebase CLI ${firebaseVersion}`));
219
272
 
220
273
  // ── Step 2: Firebase login ──────────────────────────────────────────────
package/src/logger.js CHANGED
@@ -134,6 +134,7 @@ export function banner(hostname, platform, desktopId, projectCount) {
134
134
  console.log(` ${BOLD}Projects:${RESET} ${projectCount} found`);
135
135
  console.log(`${BOLD}${CYAN}${line}${RESET}`);
136
136
  console.log(` ${GREEN}Listening for commands...${RESET}`);
137
+ console.log(` ${DIM}Docs: https://forgeremote.com${RESET}`);
137
138
  console.log(` ${DIM}Press Ctrl+C to stop${RESET}\n`);
138
139
  }
139
140
 
@@ -570,6 +570,9 @@ export async function startNewSession(desktopId, payload) {
570
570
  startTime: Date.now(),
571
571
  messageCount: 0,
572
572
  toolCallCount: 0,
573
+ turnToolCalls: 0, // Tool calls this turn (reset on each prompt)
574
+ turnTokensInput: 0, // Input tokens this turn
575
+ turnTokensOutput: 0, // Output tokens this turn
573
576
  isFirstPrompt: true,
574
577
  lastToolCall: null, // Last tool_use block (for permission requests)
575
578
  permissionNeeded: false, // True when Claude reports permission denial
@@ -690,10 +693,13 @@ async function runClaudeProcess(sessionId, prompt) {
690
693
  const session = activeSessions.get(sessionId);
691
694
  if (!session) return;
692
695
 
693
- // Reset permission state for this new turn.
696
+ // Reset permission state and per-turn stats for this new turn.
694
697
  session.permissionNeeded = false;
695
698
  session.deniedToolCall = null;
696
699
  session.lastToolCall = null;
700
+ session.turnToolCalls = 0;
701
+ session.turnTokensInput = 0;
702
+ session.turnTokensOutput = 0;
697
703
 
698
704
  const db = getDb();
699
705
  const sessionRef = db.collection("sessions").doc(sessionId);
@@ -1031,6 +1037,17 @@ async function runClaudeProcess(sessionId, prompt) {
1031
1037
  timestamp: FieldValue.serverTimestamp(),
1032
1038
  });
1033
1039
 
1040
+ // Award XP for completed turn.
1041
+ awardSessionXp(sessionId, {
1042
+ toolCalls: sess?.turnToolCalls || 0,
1043
+ tokensUsed:
1044
+ (sess?.turnTokensInput || 0) + (sess?.turnTokensOutput || 0),
1045
+ }).catch((e) =>
1046
+ log.warn(
1047
+ `Failed to award XP for ${sessionId.slice(0, 8)}: ${e.message}`,
1048
+ ),
1049
+ );
1050
+
1034
1051
  // Push notification for idle.
1035
1052
  notifySessionIdle(sess.desktopId, sessionId, {
1036
1053
  projectName: sess.projectName || "Unknown",
@@ -1310,7 +1327,12 @@ async function handleStreamEvent(sessionId, sessionRef, event) {
1310
1327
  lastActivity: FieldValue.serverTimestamp(),
1311
1328
  });
1312
1329
 
1313
- if (session) session.tokenUsage = tokenUsage;
1330
+ if (session) {
1331
+ session.tokenUsage = tokenUsage;
1332
+ // Track per-turn tokens for XP calculation.
1333
+ session.turnTokensInput = tokenUsage.input;
1334
+ session.turnTokensOutput = tokenUsage.output;
1335
+ }
1314
1336
 
1315
1337
  log.session(
1316
1338
  sessionId,
@@ -1509,7 +1531,10 @@ const toolCallDocIds = new Map();
1509
1531
  async function storeToolCall(sessionId, block) {
1510
1532
  const db = getDb();
1511
1533
  const session = activeSessions.get(sessionId);
1512
- if (session) session.toolCallCount = (session.toolCallCount || 0) + 1;
1534
+ if (session) {
1535
+ session.toolCallCount = (session.toolCallCount || 0) + 1;
1536
+ session.turnToolCalls = (session.turnToolCalls || 0) + 1;
1537
+ }
1513
1538
 
1514
1539
  const toolName = block.name || "unknown";
1515
1540
  const toolInput =