stagent 0.9.0 → 0.9.2

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": "stagent",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "AI Business Operating System — run your business with AI agents. Local-first, multi-provider, governed.",
5
5
  "keywords": [
6
6
  "ai",
@@ -3,10 +3,7 @@ import { createClient } from "@supabase/supabase-js";
3
3
  import { licenseManager } from "@/lib/license/manager";
4
4
  import { validateLicenseWithCloud } from "@/lib/license/cloud-validation";
5
5
  import { sendUpgradeConfirmation } from "@/lib/billing/email";
6
-
7
- const DEFAULT_SUPABASE_URL = "https://yznantjbmacbllhcyzwc.supabase.co";
8
- const DEFAULT_SUPABASE_ANON_KEY =
9
- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl6bmFudGpibWFjYmxsaGN5endjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1MDg1ODMsImV4cCI6MjA4ODA4NDU4M30.i-P7MXpR1_emBjhUkzbFeSX7fgjgPDv90_wkqF7sW3Y";
6
+ import { getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
10
7
 
11
8
  /**
12
9
  * GET /auth/callback
@@ -30,10 +27,7 @@ export async function GET(req: NextRequest) {
30
27
  return NextResponse.redirect(new URL("/settings?auth=error", req.url));
31
28
  }
32
29
 
33
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
34
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
35
-
36
- const supabase = createClient(url, anonKey, {
30
+ const supabase = createClient(getSupabaseUrl(), getSupabaseAnonKey(), {
37
31
  auth: { persistSession: false },
38
32
  });
39
33
 
@@ -116,7 +116,7 @@ export function AnalyticsDashboard({
116
116
  <CartesianGrid strokeDasharray="3 3" className="stroke-border" />
117
117
  <XAxis dataKey="date" tick={{ fontSize: 10 }} tickFormatter={(v) => v.slice(5)} />
118
118
  <YAxis tick={{ fontSize: 10 }} tickFormatter={(v) => `$${(v / 1_000_000).toFixed(2)}`} />
119
- <Tooltip formatter={(v: number) => [`$${(v / 1_000_000).toFixed(4)}`, "Avg Cost"]} />
119
+ <Tooltip formatter={(v) => [`$${(Number(v) / 1_000_000).toFixed(4)}`, "Avg Cost"]} />
120
120
  <Line type="monotone" dataKey="avgCostMicros" stroke="oklch(0.6 0.2 250)" strokeWidth={2} dot={false} />
121
121
  </LineChart>
122
122
  </ResponsiveContainer>
@@ -1,27 +1,31 @@
1
1
  export async function register() {
2
2
  // Only start background services on the server (not during build or edge)
3
3
  if (process.env.NEXT_RUNTIME === "nodejs") {
4
- // License manager — initialize from DB (creates default row if needed)
5
- const { licenseManager } = await import("@/lib/license/manager");
6
- licenseManager.initialize();
7
- licenseManager.startValidationTimer();
8
-
9
- const { startScheduler } = await import("@/lib/schedules/scheduler");
10
- startScheduler();
11
-
12
- const { startChannelPoller } = await import("@/lib/channels/poller");
13
- startChannelPoller();
14
-
15
- const { startAutoBackup } = await import("@/lib/snapshots/auto-backup");
16
- startAutoBackup();
17
-
18
- // History retention cleanup — prunes old agent_logs and usage_ledger
19
- // based on tier retention limit (Community: 30 days)
20
- startHistoryCleanup(licenseManager);
21
-
22
- // Telemetry batch flush (opt-in, every 5 minutes)
23
- const { startTelemetryFlush } = await import("@/lib/telemetry/queue");
24
- startTelemetryFlush();
4
+ try {
5
+ // License manager initialize from DB (creates default row if needed)
6
+ const { licenseManager } = await import("@/lib/license/manager");
7
+ licenseManager.initialize();
8
+ licenseManager.startValidationTimer();
9
+
10
+ const { startScheduler } = await import("@/lib/schedules/scheduler");
11
+ startScheduler();
12
+
13
+ const { startChannelPoller } = await import("@/lib/channels/poller");
14
+ startChannelPoller();
15
+
16
+ const { startAutoBackup } = await import("@/lib/snapshots/auto-backup");
17
+ startAutoBackup();
18
+
19
+ // History retention cleanup prunes old agent_logs and usage_ledger
20
+ // based on tier retention limit (Community: 30 days)
21
+ startHistoryCleanup(licenseManager);
22
+
23
+ // Telemetry batch flush (opt-in, every 5 minutes)
24
+ const { startTelemetryFlush } = await import("@/lib/telemetry/queue");
25
+ startTelemetryFlush();
26
+ } catch (err) {
27
+ console.error("Instrumentation startup failed:", err);
28
+ }
25
29
  }
26
30
  }
27
31
 
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs";
2
+ import { homedir } from "node:os";
2
3
  import path from "node:path";
3
4
  import yaml from "js-yaml";
4
5
  import { ProfileConfigSchema } from "@/lib/validators/profile";
@@ -30,7 +31,7 @@ function getBuiltinsDir(): string {
30
31
  }
31
32
 
32
33
  const SKILLS_DIR = path.join(
33
- process.env.HOME ?? process.env.USERPROFILE ?? ".",
34
+ process.env.HOME ?? process.env.USERPROFILE ?? homedir(),
34
35
  ".claude",
35
36
  "skills"
36
37
  );
@@ -6,7 +6,7 @@
6
6
  * the cloud backend is not configured.
7
7
  */
8
8
 
9
- import { isCloudConfigured } from "@/lib/cloud/supabase-client";
9
+ import { isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
10
10
 
11
11
  async function sendEmail(
12
12
  template: string,
@@ -15,8 +15,8 @@ async function sendEmail(
15
15
  ): Promise<void> {
16
16
  if (!isCloudConfigured()) return;
17
17
 
18
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
19
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
18
+ const supabaseUrl = getSupabaseUrl();
19
+ const anonKey = getSupabaseAnonKey();
20
20
 
21
21
  try {
22
22
  await fetch(`${supabaseUrl}/functions/v1/send-email`, {
@@ -6,7 +6,7 @@
6
6
  * Edge Functions to create Checkout Sessions and Portal Sessions.
7
7
  */
8
8
 
9
- import { isCloudConfigured } from "@/lib/cloud/supabase-client";
9
+ import { isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
10
10
  import { getProductForTier } from "./products";
11
11
  import type { LicenseTier } from "@/lib/license/tier-limits";
12
12
  import type { BillingInterval } from "./products";
@@ -30,8 +30,8 @@ export async function createCheckoutSession(
30
30
  }
31
31
 
32
32
  const priceId = product.prices[billingPeriod].id;
33
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
34
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
33
+ const supabaseUrl = getSupabaseUrl();
34
+ const anonKey = getSupabaseAnonKey();
35
35
 
36
36
  try {
37
37
  const response = await fetch(
@@ -72,8 +72,8 @@ export async function createPortalSession(
72
72
  return { error: "Cloud backend not configured" };
73
73
  }
74
74
 
75
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
76
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
75
+ const supabaseUrl = getSupabaseUrl();
76
+ const anonKey = getSupabaseAnonKey();
77
77
 
78
78
  try {
79
79
  const response = await fetch(
@@ -9,10 +9,7 @@
9
9
  */
10
10
 
11
11
  import { createClient, type SupabaseClient } from "@supabase/supabase-js";
12
-
13
- const DEFAULT_SUPABASE_URL = "https://yznantjbmacbllhcyzwc.supabase.co";
14
- const DEFAULT_SUPABASE_ANON_KEY =
15
- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl6bmFudGpibWFjYmxsaGN5endjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1MDg1ODMsImV4cCI6MjA4ODA4NDU4M30.i-P7MXpR1_emBjhUkzbFeSX7fgjgPDv90_wkqF7sW3Y";
12
+ import { getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
16
13
 
17
14
  let browserClient: SupabaseClient | null = null;
18
15
 
@@ -23,10 +20,7 @@ let browserClient: SupabaseClient | null = null;
23
20
  export function getSupabaseBrowserClient(): SupabaseClient {
24
21
  if (browserClient) return browserClient;
25
22
 
26
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
27
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
28
-
29
- browserClient = createClient(url, anonKey, {
23
+ browserClient = createClient(getSupabaseUrl(), getSupabaseAnonKey(), {
30
24
  auth: {
31
25
  autoRefreshToken: true,
32
26
  persistSession: true,
@@ -15,6 +15,16 @@ const DEFAULT_SUPABASE_URL = "https://yznantjbmacbllhcyzwc.supabase.co";
15
15
  const DEFAULT_SUPABASE_ANON_KEY =
16
16
  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl6bmFudGpibWFjYmxsaGN5endjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1MDg1ODMsImV4cCI6MjA4ODA4NDU4M30.i-P7MXpR1_emBjhUkzbFeSX7fgjgPDv90_wkqF7sW3Y";
17
17
 
18
+ /** Resolved Supabase URL (env override or production default) */
19
+ export function getSupabaseUrl(): string {
20
+ return process.env.NEXT_PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
21
+ }
22
+
23
+ /** Resolved Supabase anon key (env override or production default) */
24
+ export function getSupabaseAnonKey(): string {
25
+ return process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
26
+ }
27
+
18
28
  let client: SupabaseClient | null = null;
19
29
  let initialized = false;
20
30
 
@@ -27,10 +37,7 @@ export function getSupabaseClient(): SupabaseClient | null {
27
37
  if (initialized) return client;
28
38
  initialized = true;
29
39
 
30
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
31
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
32
-
33
- client = createClient(url, anonKey, {
40
+ client = createClient(getSupabaseUrl(), getSupabaseAnonKey(), {
34
41
  auth: {
35
42
  autoRefreshToken: true,
36
43
  persistSession: false, // Server-side — no browser session
@@ -6,13 +6,9 @@
6
6
  * defaults as supabase-client.ts (production backend by default).
7
7
  */
8
8
 
9
- import { isCloudConfigured } from "@/lib/cloud/supabase-client";
9
+ import { isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
10
10
  import type { LicenseTier } from "./tier-limits";
11
11
 
12
- const DEFAULT_SUPABASE_URL = "https://yznantjbmacbllhcyzwc.supabase.co";
13
- const DEFAULT_SUPABASE_ANON_KEY =
14
- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl6bmFudGpibWFjYmxsaGN5endjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1MDg1ODMsImV4cCI6MjA4ODA4NDU4M30.i-P7MXpR1_emBjhUkzbFeSX7fgjgPDv90_wkqF7sW3Y";
15
-
16
12
  export interface CloudValidationResult {
17
13
  valid: boolean;
18
14
  tier: LicenseTier;
@@ -31,8 +27,8 @@ export async function validateLicenseWithCloud(
31
27
  return { valid: false, tier: "community", error: "Cloud disabled or no email" };
32
28
  }
33
29
 
34
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || DEFAULT_SUPABASE_URL;
35
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || DEFAULT_SUPABASE_ANON_KEY;
30
+ const supabaseUrl = getSupabaseUrl();
31
+ const anonKey = getSupabaseAnonKey();
36
32
 
37
33
  try {
38
34
  const response = await fetch(`${supabaseUrl}/functions/v1/validate-license`, {
@@ -4,7 +4,7 @@
4
4
  * Writes go directly to Supabase with RLS.
5
5
  */
6
6
 
7
- import { getSupabaseClient, isCloudConfigured } from "@/lib/cloud/supabase-client";
7
+ import { getSupabaseClient, isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
8
8
 
9
9
  export interface MarketplaceBlueprint {
10
10
  id: string;
@@ -36,8 +36,8 @@ export async function browseBlueprints(
36
36
  return { blueprints: [], total: 0, page, limit: 20 };
37
37
  }
38
38
 
39
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
40
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
39
+ const supabaseUrl = getSupabaseUrl();
40
+ const anonKey = getSupabaseAnonKey();
41
41
 
42
42
  const params = new URLSearchParams({ page: String(page) });
43
43
  if (category) params.set("category", category);
@@ -12,7 +12,7 @@ import { readFileSync, writeFileSync, existsSync, copyFileSync } from "fs";
12
12
  import { join } from "path";
13
13
  import { tmpdir } from "os";
14
14
  import { getStagentDataDir } from "@/lib/utils/stagent-paths";
15
- import { getSupabaseClient } from "@/lib/cloud/supabase-client";
15
+ import { getSupabaseClient, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
16
16
  import { sqlite } from "@/lib/db";
17
17
 
18
18
  const SYNC_VERSION = Buffer.from([0, 0, 0, 1]); // Version 1
@@ -89,9 +89,7 @@ export async function exportAndUpload(
89
89
  // If an access token is provided, create an authenticated client for Storage RLS
90
90
  if (accessToken) {
91
91
  const { createClient } = await import("@supabase/supabase-js");
92
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL || "https://yznantjbmacbllhcyzwc.supabase.co";
93
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inl6bmFudGpibWFjYmxsaGN5endjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI1MDg1ODMsImV4cCI6MjA4ODA4NDU4M30.i-P7MXpR1_emBjhUkzbFeSX7fgjgPDv90_wkqF7sW3Y";
94
- supabase = createClient(url, anonKey, {
92
+ supabase = createClient(getSupabaseUrl(), getSupabaseAnonKey(), {
95
93
  global: { headers: { Authorization: `Bearer ${accessToken}` } },
96
94
  auth: { persistSession: false },
97
95
  });
@@ -9,7 +9,7 @@
9
9
  * No-op if Supabase is not configured.
10
10
  */
11
11
 
12
- import { isCloudConfigured } from "@/lib/cloud/supabase-client";
12
+ import { isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
13
13
  import { getSettingSync, setSetting } from "@/lib/settings/helpers";
14
14
 
15
15
  const SESSION_KEY = "conversion.sessionId";
@@ -49,10 +49,8 @@ export function trackConversionEvent(
49
49
  if (!isCloudConfigured()) return;
50
50
 
51
51
  const sessionId = getSessionId();
52
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
53
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
54
-
55
- if (!supabaseUrl || !anonKey) return;
52
+ const supabaseUrl = getSupabaseUrl();
53
+ const anonKey = getSupabaseAnonKey();
56
54
 
57
55
  // Fire-and-forget — don't await, don't block
58
56
  fetch(`${supabaseUrl}/functions/v1/conversion-ingest`, {
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { getSettingSync, setSetting } from "@/lib/settings/helpers";
13
13
  import { SETTINGS_KEYS } from "@/lib/constants/settings";
14
- import { isCloudConfigured } from "@/lib/cloud/supabase-client";
14
+ import { isCloudConfigured, getSupabaseUrl, getSupabaseAnonKey } from "@/lib/cloud/supabase-client";
15
15
 
16
16
  const MAX_BATCH_SIZE = 200;
17
17
  const FLUSH_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
@@ -75,8 +75,8 @@ export async function flushTelemetryBatch(): Promise<void> {
75
75
  const batch = loadBatch();
76
76
  if (batch.length === 0) return;
77
77
 
78
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
79
- const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
78
+ const supabaseUrl = getSupabaseUrl();
79
+ const anonKey = getSupabaseAnonKey();
80
80
 
81
81
  try {
82
82
  const res = await fetch(`${supabaseUrl}/functions/v1/telemetry-ingest`, {