shraga 0.1.5 → 0.1.7
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/README.md +65 -27
- package/dist/client/assets/{index-BnArwb7g.js → index-Cqpw0NtH.js} +237 -237
- package/dist/client/index.html +1 -1
- package/package.json +4 -2
- package/src/client/lib/firebase.ts +5 -4
- package/src/client/lib/googleAuthNative.ts +3 -1
- package/src/client/lib/webConfig.ts +12 -0
- package/src/server/boot.ts +3 -1
- package/src/server/sessions.ts +2 -2
- package/src/server/spa-catchall.ts +4 -1
- package/src/server/spa-shell.ts +51 -0
package/dist/client/index.html
CHANGED
|
@@ -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-
|
|
16
|
+
<script type="module" crossorigin src="/assets/index-Cqpw0NtH.js"></script>
|
|
17
17
|
<link rel="stylesheet" crossorigin href="/assets/index-DdibEb2O.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shraga",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|
|
7
7
|
"types": "./src/index.ts",
|
|
8
|
+
"//exports.server": "UNSTABLE internal seam (semver-exempt): ./server/* lets a downstream feature (e.g. the Circles Gmail overlay in shraga-circles) import the server-internal modules it already coupled to under the old rsync/overlay model — claude/sessions/contacts/mcp/paths. The stable public surface is '.' (createShraga + feature/engine/webhook/event seams). Depend on these subpaths only if '.' cannot express it, and expect breakage across minors.",
|
|
8
9
|
"exports": {
|
|
9
10
|
".": "./src/index.ts",
|
|
10
|
-
"./package.json": "./package.json"
|
|
11
|
+
"./package.json": "./package.json",
|
|
12
|
+
"./server/*": "./src/server/*.ts"
|
|
11
13
|
},
|
|
12
14
|
"license": "MIT",
|
|
13
15
|
"author": "Elya Livshitz",
|
|
@@ -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
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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
|
-
|
|
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__) || {};
|
package/src/server/boot.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/src/server/sessions.ts
CHANGED
|
@@ -32,7 +32,7 @@ export interface SessionMeta {
|
|
|
32
32
|
/** Emails allowed to see this session (lowercase). Checked in addition to uid/scope. */
|
|
33
33
|
visibleTo?: string[];
|
|
34
34
|
runStatus?: 'running' | 'idle';
|
|
35
|
-
runOrigin?: 'web' | 'slack' | 'scheduler';
|
|
35
|
+
runOrigin?: 'web' | 'slack' | 'scheduler' | 'gmail';
|
|
36
36
|
lastStopReason?: 'max_turns_reached' | 'error' | 'aborted';
|
|
37
37
|
runRetryCount?: number;
|
|
38
38
|
directives?: Directives;
|
|
@@ -263,7 +263,7 @@ export function setAutoApprove(uid: string, value: boolean): void {
|
|
|
263
263
|
let _shuttingDown = false;
|
|
264
264
|
export function setShuttingDown(): void { _shuttingDown = true; }
|
|
265
265
|
|
|
266
|
-
export function setRunStatus(sessionId: string, status: 'running' | 'idle', origin?: 'web' | 'slack' | 'scheduler', stopReason?: SessionMeta['lastStopReason']): void {
|
|
266
|
+
export function setRunStatus(sessionId: string, status: 'running' | 'idle', origin?: 'web' | 'slack' | 'scheduler' | 'gmail', stopReason?: SessionMeta['lastStopReason']): void {
|
|
267
267
|
if (_shuttingDown && status === 'idle') return;
|
|
268
268
|
const sessions = loadIndex();
|
|
269
269
|
const s = sessions.find((x) => x.sessionId === sessionId);
|
|
@@ -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
|
-
|
|
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
|
+
}
|