@creativeintelligence/abbie 0.1.2 → 0.1.3

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.
@@ -553,51 +553,93 @@ export default function abbieExtension(pi: ExtensionAPI) {
553
553
  } catch { return []; }
554
554
  }
555
555
 
556
- // On session start: check if any providers are connected
556
+ // On session start: check if any providers are connected, try syncing from Convex
557
557
  pi.on("session_start", async (_event, ctx) => {
558
- const providers = await getConnectedProviders();
559
- if (providers.length === 0 && ctx.hasUI) {
560
- ctx.ui.notify(
561
- "No AI provider connected. Use /login to connect Anthropic, OpenAI, Google, or GitHub Copilot.",
562
- "warning",
563
- );
558
+ const localProviders = await getConnectedProviders();
559
+
560
+ // If no local providers, try to pull credentials from Convex (for fresh installs)
561
+ if (localProviders.length === 0) {
562
+ await syncProvidersFromConvex();
563
+ const afterSync = await getConnectedProviders();
564
+ if (afterSync.length === 0 && ctx.hasUI) {
565
+ ctx.ui.notify(
566
+ "No AI provider connected. Connect one at Settings → Providers in the web app, or use /login here.",
567
+ "warning",
568
+ );
569
+ }
564
570
  }
565
571
  });
566
572
 
567
- // Sync provider status to Convex after bridge is up
568
- pi.on("agent_end", async (_event, ctx) => {
569
- // After first agent interaction, sync provider info to Convex
570
- // This runs once per session via a simple flag
573
+ // Sync provider credentials to Convex after first agent interaction
574
+ pi.on("agent_end", async (_event, _ctx) => {
571
575
  if ((pi as any).__providersSynced) return;
572
576
  (pi as any).__providersSynced = true;
573
577
 
574
578
  const providers = await getConnectedProviders();
575
579
  if (providers.length === 0) return;
576
580
 
577
- // Sync via bridge if configured
581
+ // Also sync credentials to Convex for sandbox injection
578
582
  try {
579
583
  const { existsSync, readFileSync } = await import("node:fs");
580
584
  const { join } = await import("node:path");
581
585
  const { homedir } = await import("node:os");
586
+ const authPath = join(homedir(), ".pi", "agent", "auth.json");
582
587
  const configPath = join(homedir(), ".abbie", "config.json");
583
- if (!existsSync(configPath)) return;
588
+ if (!existsSync(configPath) || !existsSync(authPath)) return;
584
589
  const config = JSON.parse(readFileSync(configPath, "utf8"));
585
590
  const convexUrl = config.convexUrl || process.env.NEXT_PUBLIC_CONVEX_URL;
586
591
  const bridgeSecret = config.bridgeSecret || process.env.ABBIE_BRIDGE_SECRET;
587
592
  if (!convexUrl || !bridgeSecret) return;
588
593
 
589
- // POST provider list to bridge (no credentials, just IDs)
590
- await fetch(`${convexUrl.replace('.convex.cloud', '.convex.site')}/bridge/providers/sync`, {
591
- method: "POST",
592
- headers: {
593
- "Content-Type": "application/json",
594
- "Authorization": `Bearer ${bridgeSecret}`,
595
- },
596
- body: JSON.stringify({ providers }),
597
- }).catch(() => {}); // best-effort
594
+ const siteUrl = convexUrl.replace('.convex.cloud', '.convex.site');
595
+ const authData = JSON.parse(readFileSync(authPath, "utf8"));
596
+
597
+ // Sync each provider's credentials
598
+ for (const providerId of providers) {
599
+ const cred = authData[providerId];
600
+ if (!cred?.access || !cred?.refresh) continue;
601
+
602
+ await fetch(`${siteUrl}/bridge/providers/complete`, {
603
+ method: "POST",
604
+ headers: {
605
+ "Content-Type": "application/json",
606
+ "x-bridge-secret": bridgeSecret,
607
+ },
608
+ body: JSON.stringify({
609
+ // Use a synthetic request ID (the endpoint handles upserts)
610
+ requestId: null,
611
+ accessToken: cred.access,
612
+ refreshToken: cred.refresh,
613
+ expiresAt: cred.expires || Date.now() + 3600_000,
614
+ provider: providerId,
615
+ }),
616
+ }).catch(() => {}); // best-effort
617
+ }
598
618
  } catch { /* non-fatal */ }
599
619
  });
600
620
 
621
+ /**
622
+ * Pull provider credentials from Convex and write to local auth.json.
623
+ * Used on fresh installs where user connected via web but auth.json is empty.
624
+ */
625
+ async function syncProvidersFromConvex(): Promise<void> {
626
+ try {
627
+ const { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } = await import("node:fs");
628
+ const { join } = await import("node:path");
629
+ const { homedir } = await import("node:os");
630
+
631
+ const configPath = join(homedir(), ".abbie", "config.json");
632
+ if (!existsSync(configPath)) return;
633
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
634
+ const convexUrl = config.convexUrl || process.env.NEXT_PUBLIC_CONVEX_URL;
635
+ const bridgeSecret = config.bridgeSecret || process.env.ABBIE_BRIDGE_SECRET;
636
+ if (!convexUrl || !bridgeSecret) return;
637
+
638
+ // TODO: Add a bridge endpoint that returns stored credentials
639
+ // For now, the bridge handles credential sync via the poll loop
640
+ } catch { /* non-fatal */ }
641
+ }
642
+
601
643
  // ===========================================================================
602
644
  // Auto-bridge: start bridge daemon on Pi startup
603
645
  // ===========================================================================
@@ -11672,5 +11672,5 @@
11672
11672
  "summary": "Watch workspace windows (tmux panes) and stream updates"
11673
11673
  }
11674
11674
  },
11675
- "version": "0.1.2"
11675
+ "version": "0.1.3"
11676
11676
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@creativeintelligence/abbie",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Abbie — agent orchestration CLI",