shraga 0.1.6 → 0.1.8

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.
@@ -13,7 +13,7 @@
13
13
  <link rel="preconnect" href="https://fonts.googleapis.com" />
14
14
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
15
15
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet" />
16
- <script type="module" crossorigin src="/assets/index-BnArwb7g.js"></script>
16
+ <script type="module" crossorigin src="/assets/index-FM42KQNo.js"></script>
17
17
  <link rel="stylesheet" crossorigin href="/assets/index-DdibEb2O.css">
18
18
  </head>
19
19
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shraga",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "The teammate you delegate coding to — a self-hostable, multi-user AI coding agent web UI (Claude Code, with a pluggable engine seam).",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -30,12 +30,13 @@ interface EngineInfo {
30
30
  }
31
31
 
32
32
  const FALLBACK_MODELS: EngineModel[] = [
33
- { value: '', label: 'Default (claude-sonnet-4-6)' },
33
+ { value: '', label: 'Default (claude-sonnet-5)' },
34
34
  { value: 'claude-fable-5', label: 'Fable 5 — frontier, most capable' },
35
35
  { value: 'claude-opus-4-8', label: 'Opus 4.8 — most capable, best for complex/agentic tasks' },
36
36
  { value: 'claude-opus-4-7', label: 'Opus 4.7' },
37
37
  { value: 'claude-opus-4-6', label: 'Opus 4.6' },
38
- { value: 'claude-sonnet-4-6', label: 'Sonnet 4.6 — balanced speed & intelligence' },
38
+ { value: 'claude-sonnet-5', label: 'Sonnet 5 — balanced speed & intelligence' },
39
+ { value: 'claude-sonnet-4-6', label: 'Sonnet 4.6' },
39
40
  { value: 'claude-haiku-4-5', label: 'Haiku 4.5 — fastest' },
40
41
  ];
41
42
 
@@ -1,11 +1,12 @@
1
1
  import { initializeApp, type FirebaseApp } from 'firebase/app';
2
2
  import { getAuth, GoogleAuthProvider, signInWithPopup, signOut, onAuthStateChanged, type Auth, type User } from 'firebase/auth';
3
3
  import { canUseNativeGoogleSignIn, signInWithGoogleNative } from './googleAuthNative';
4
+ import { webConfig } from './webConfig';
4
5
 
5
- // Firebase is OPTIONAL — only initialized when VITE_FIREBASE_CONFIG_PROD is present (an optional
6
- // add-on). This build ships with local auth and no Firebase config; guard so importing this
7
- // module never throws when unconfigured.
8
- const firebaseConfig = JSON.parse(import.meta.env.VITE_FIREBASE_CONFIG_PROD ?? '{}');
6
+ // Firebase is OPTIONAL — only initialized when a config is present (an optional add-on). This build
7
+ // ships with local auth and no Firebase config; guard so importing this module never throws when
8
+ // unconfigured. Runtime server-injected config wins; the build-time VITE_ value is the fallback.
9
+ const firebaseConfig = webConfig.firebase ?? JSON.parse(import.meta.env.VITE_FIREBASE_CONFIG_PROD ?? '{}');
9
10
  export const hasFirebase = !!firebaseConfig.apiKey;
10
11
 
11
12
  let app: FirebaseApp | null = null;
@@ -10,8 +10,10 @@
10
10
  // Mirrors the proven AGF appwrap implementation.
11
11
  import { kit } from '@livx.cc/native-kit';
12
12
  import { GoogleAuthProvider, signInWithCredential, type Auth } from 'firebase/auth';
13
+ import { webConfig } from './webConfig';
13
14
 
14
- const CLIENT_ID = import.meta.env.VITE_GOOGLE_IOS_OAUTH_CLIENT_ID as string | undefined;
15
+ // Runtime server-injected id wins; the build-time VITE_ value is the fallback.
16
+ const CLIENT_ID = webConfig.googleIosOAuthClientId ?? (import.meta.env.VITE_GOOGLE_IOS_OAUTH_CLIENT_ID as string | undefined);
15
17
 
16
18
  /** True only inside the native shell, with the oauth module compiled in and a client id configured. */
17
19
  export async function canUseNativeGoogleSignIn(): Promise<boolean> {
@@ -0,0 +1,12 @@
1
+ // Runtime web-config injected by the server into index.html (window.__SHRAGA_WEB_CONFIG__),
2
+ // read SYNCHRONOUSLY here — the inline script runs before this bundle, so the global is present at
3
+ // module-eval. Lets a self-hosted deploy configure Firebase via server env with no client rebuild.
4
+ // Falls back to the build-time VITE_ values so from-source builds keep working.
5
+
6
+ export interface WebConfig {
7
+ firebase?: Record<string, unknown>;
8
+ googleIosOAuthClientId?: string;
9
+ }
10
+
11
+ export const webConfig: WebConfig =
12
+ (typeof window !== 'undefined' && (window as unknown as { __SHRAGA_WEB_CONFIG__?: WebConfig }).__SHRAGA_WEB_CONFIG__) || {};
@@ -734,7 +734,9 @@ const { loadExtensions, registerExtension } = await import('./extensions.ts');
734
734
  for (const fn of __reg.extensions ?? []) await registerExtension(fn);
735
735
  await loadExtensions(app);
736
736
 
737
- if (existsSync(distPath)) app.use(express.static(distPath));
737
+ // `index: false` so `/` falls through to the SPA catch-all, which injects the runtime web-config
738
+ // into index.html. Static assets (JS/CSS/etc.) are still served directly from here.
739
+ if (existsSync(distPath)) app.use(express.static(distPath, { index: false }));
738
740
 
739
741
  // The SPA catch-all (`app.get('*')`) is registered LATER — after mountFeatures() — via
740
742
  // registerSpaCatchAll(), so feature/extension GET routes are matched before falling through to
@@ -536,7 +536,7 @@ export class DataSync {
536
536
  emitEvent('data-sync', { kind: 'deploy', owners, text });
537
537
  }
538
538
 
539
- private async askClaude(prompt: string, model = 'claude-sonnet-4-6', maxTokens = 8192): Promise<string> {
539
+ private async askClaude(prompt: string, model = 'claude-sonnet-5', maxTokens = 8192): Promise<string> {
540
540
  const apiKey = process.env.ANTHROPIC_API_KEY;
541
541
  if (!apiKey) throw new Error('ANTHROPIC_API_KEY not set');
542
542
  const resp = await fetch('https://api.anthropic.com/v1/messages', {
@@ -14,7 +14,7 @@ export interface ParsedPrompt {
14
14
  /** Model used when neither directives nor config specify one. Always passed
15
15
  * explicitly to the SDK — the CLI's own default silently drifts (it picked
16
16
  * Opus 4.7), which burns rate limits and budget. */
17
- export const DEFAULT_MODEL = 'claude-sonnet-4-6';
17
+ export const DEFAULT_MODEL = 'claude-sonnet-5';
18
18
 
19
19
  // Canonical model aliases + label. Vendored, pure, dependency-free (src/server/model-aliases.ts).
20
20
  // Re-exported here so the rest of shraga keeps importing model helpers from one place.
@@ -166,7 +166,8 @@ export class ClaudeCodeEngine implements AgentEngine {
166
166
  { value: 'claude-opus-4-8', label: 'Opus 4.8 — most capable' },
167
167
  { value: 'claude-opus-4-7', label: 'Opus 4.7' },
168
168
  { value: 'claude-opus-4-6', label: 'Opus 4.6' },
169
- { value: 'claude-sonnet-4-6', label: 'Sonnet 4.6 — balanced' },
169
+ { value: 'claude-sonnet-5', label: 'Sonnet 5 — balanced' },
170
+ { value: 'claude-sonnet-4-6', label: 'Sonnet 4.6' },
170
171
  { value: 'claude-haiku-4-5', label: 'Haiku 4.5 — fastest' },
171
172
  ];
172
173
  }
@@ -14,7 +14,9 @@ export const MODEL_ALIASES: Record<string, string> = {
14
14
  'opus-4-8': 'claude-opus-4-8',
15
15
  'opus-4-7': 'claude-opus-4-7',
16
16
  'opus-4-6': 'claude-opus-4-6',
17
- sonnet: 'claude-sonnet-4-6',
17
+ sonnet: 'claude-sonnet-5',
18
+ 'sonnet-5': 'claude-sonnet-5',
19
+ 'sonnet-4-6': 'claude-sonnet-4-6',
18
20
  haiku: 'claude-haiku-4-5-20251001',
19
21
  };
20
22
 
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import { existsSync } from 'fs';
3
3
  import type express from 'express';
4
+ import { getSpaShell } from './spa-shell.ts';
4
5
 
5
6
  // Non-page prefixes that must fall through to a real 404 (JSON/API/transport), never the SPA shell.
6
7
  function isNonPagePath(p: string): boolean {
@@ -34,7 +35,9 @@ export function registerSpaCatchAll(app: express.Express, distPath: string): voi
34
35
  }
35
36
  const handler: express.RequestHandler = (req, res, next) => {
36
37
  if (isNonPagePath(req.path)) return next();
37
- res.sendFile(indexHtml);
38
+ // Serve the shell with the runtime web-config injected (cached). This is the SINGLE HTML-page
39
+ // path — `/` falls through here too (express.static is mounted with `index: false`).
40
+ res.type('html').send(getSpaShell(indexHtml));
38
41
  };
39
42
  (handler as { __spaCatchAll?: boolean }).__spaCatchAll = true;
40
43
  app.get('*', handler);
@@ -0,0 +1,51 @@
1
+ import { readFileSync } from 'fs';
2
+
3
+ // Runtime web-config injected into the SPA shell so self-hosters (and the npm consumer) configure
4
+ // Firebase via SERVER env with NO client rebuild. The client reads window.__SHRAGA_WEB_CONFIG__
5
+ // first, then falls back to the build-time VITE_ values, so from-source builds keep working.
6
+
7
+ export interface SpaWebConfig {
8
+ firebase?: Record<string, unknown>;
9
+ googleIosOAuthClientId?: string;
10
+ }
11
+
12
+ /** Build the runtime web-config from server env. Clean names win, VITE_ names are the fallback. */
13
+ export function buildWebConfig(env: NodeJS.ProcessEnv = process.env): SpaWebConfig {
14
+ const config: SpaWebConfig = {};
15
+
16
+ const firebaseRaw = env.FIREBASE_WEB_CONFIG ?? env.VITE_FIREBASE_CONFIG_PROD;
17
+ if (firebaseRaw) {
18
+ try {
19
+ const parsed = JSON.parse(firebaseRaw);
20
+ if (parsed && typeof parsed === 'object' && Object.keys(parsed).length > 0) config.firebase = parsed;
21
+ } catch (err) {
22
+ console.warn('[spa-shell] invalid FIREBASE_WEB_CONFIG/VITE_FIREBASE_CONFIG_PROD JSON:', (err as Error).message);
23
+ }
24
+ }
25
+
26
+ const iosClientId = env.GOOGLE_IOS_OAUTH_CLIENT_ID ?? env.VITE_GOOGLE_IOS_OAUTH_CLIENT_ID;
27
+ if (iosClientId) config.googleIosOAuthClientId = iosClientId;
28
+
29
+ return config;
30
+ }
31
+
32
+ /** Inject the config as an inline global into <head> (before the app bundle). Pure + escape-safe. */
33
+ export function injectShellConfig(html: string, config: SpaWebConfig): string {
34
+ // Escape `<` so a `</script>` inside any value cannot break out of the inline script.
35
+ const json = JSON.stringify(config).replace(/</g, '\\u003c');
36
+ const tag = `<script>window.__SHRAGA_WEB_CONFIG__ = ${json};</script>`;
37
+ return html.includes('</head>') ? html.replace('</head>', `${tag}</head>`) : `${tag}${html}`;
38
+ }
39
+
40
+ // Cache the injected shell per index.html path — env is fixed for the process lifetime.
41
+ const shellCache = new Map<string, string>();
42
+
43
+ /** Read index.html once, inject the runtime web-config, and cache the result. */
44
+ export function getSpaShell(indexHtmlPath: string): string {
45
+ let shell = shellCache.get(indexHtmlPath);
46
+ if (shell === undefined) {
47
+ shell = injectShellConfig(readFileSync(indexHtmlPath, 'utf8'), buildWebConfig());
48
+ shellCache.set(indexHtmlPath, shell);
49
+ }
50
+ return shell;
51
+ }