diff --git a/README.ar.md b/README.ar.md
index 7cc71aefe..a0e4a08c2 100644
--- a/README.ar.md
+++ b/README.ar.md
@@ -800,7 +800,7 @@ Issues و PRs و skills جديدة وأنظمة تصميم جديدة، كلّه
شكراً لكلّ من ساعد في دفع Open Design للأمام — بكود، بوثائق، بملاحظات، بـ skills جديدة، بأنظمة تصميم جديدة، أو حتى بـ issue حادّة. كلّ مساهمة حقيقية تهمّ، والجدار أدناه أسهل طريقة لقول ذلك علناً.
-
+
إن شحنت أوّل PR — مرحباً. تصنيف [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) هو نقطة الدخول.
@@ -817,9 +817,9 @@ Issues و PRs و skills جديدة وأنظمة تصميم جديدة، كلّه
-
-
-
+
+
+
diff --git a/README.de.md b/README.de.md
index 83f6aac0e..d34706ae7 100644
--- a/README.de.md
+++ b/README.de.md
@@ -726,7 +726,7 @@ Vollständiger Walkthrough, Merge-Messlatte, Code Style und was wir nicht annehm
Danke an alle, die Open Design vorangebracht haben: durch Code, Docs, Feedback, neue Skills, neue Design Systems oder auch ein scharfes Issue. Jeder echte Beitrag zählt, und die Wand unten ist die einfachste Art, das laut zu sagen.
-
+
Wenn Sie Ihren ersten PR gemergt haben: willkommen. Das Label [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) ist der Einstiegspunkt.
@@ -743,9 +743,9 @@ Das SVG oben wird täglich von [`.github/workflows/metrics.yml`](.github/workflo
-
-
-
+
+
+
diff --git a/README.es.md b/README.es.md
index 36a526a3a..a5ef84401 100644
--- a/README.es.md
+++ b/README.es.md
@@ -787,7 +787,7 @@ Walkthrough completo, estándar de merge, code style y lo que no aceptamos → [
Gracias a todas las personas que han ayudado a mover Open Design hacia adelante: con código, docs, feedback, nuevas skills, nuevos design systems o incluso un issue preciso. Toda contribución real cuenta, y el muro de abajo es la forma más simple de decirlo en voz alta.
-
+
Si ya enviaste tu primer PR, bienvenido. La etiqueta [`good-first-issue`](https://github.com/nexu-io/open-design/labels/good-first-issue) es el punto de entrada.
@@ -804,9 +804,9 @@ El SVG anterior se regenera diariamente mediante [`.github/workflows/metrics.yml
-
-
-
+
+
+
diff --git a/README.fr.md b/README.fr.md
index 24fad9eae..6da7b27e2 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -733,7 +733,7 @@ Guide complet, critères de merge, style de code et refus fréquents → [`CONTR
Merci à toutes les personnes qui font avancer Open Design : code, docs, retours, nouveaux Skills, nouveaux Design Systems ou issues bien ciblées. Chaque vraie contribution compte.
-
+
Si vous avez livré votre première PR, bienvenue. Le label [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) est le point d’entrée.
@@ -750,9 +750,9 @@ Le SVG ci-dessus est régénéré chaque jour par [`.github/workflows/metrics.ym
-
-
-
+
+
+
diff --git a/README.ja-JP.md b/README.ja-JP.md
index 94bf9ada9..abc90bc39 100644
--- a/README.ja-JP.md
+++ b/README.ja-JP.md
@@ -723,7 +723,7 @@ Issue、PR、新 Skill、新 Design System を歓迎します。最も効果の
コード、ドキュメント、フィードバック、新 Skill、新 Design System、あるいは鋭い Issue — あらゆる形で Open Design を前進させてくださったすべての方に感謝します。すべての実質的なコントリビューションは大切であり、以下のウォールは最もシンプルな感謝の表明です。
-
+
初めての PR を送った方 — ようこそ。[`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) ラベルがエントリポイントです。
@@ -740,9 +740,9 @@ Issue、PR、新 Skill、新 Design System を歓迎します。最も効果の
-
-
-
+
+
+
diff --git a/README.ko.md b/README.ko.md
index 07575e56b..51f91743d 100644
--- a/README.ko.md
+++ b/README.ko.md
@@ -726,7 +726,7 @@ daemon 부팅 시 `PATH`에서 자동 감지됩니다. 설정 필요 없음. 스
Open Design을 앞으로 나아가게 도와준 모든 분께 감사드립니다 — 코드, 문서, 피드백, 새 skill, 새 디자인 시스템, 또는 날카로운 이슈 하나라도. 모든 진짜 기여가 의미 있고, 아래의 벽이 가장 직접적인 "감사합니다"입니다.
-
+
첫 PR을 보냈다면 — 환영합니다. [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) 레이블이 시작점입니다.
@@ -743,9 +743,9 @@ Open Design을 앞으로 나아가게 도와준 모든 분께 감사드립니다
-
-
-
+
+
+
diff --git a/README.md b/README.md
index 8bc3cb1ed..58093bde0 100644
--- a/README.md
+++ b/README.md
@@ -1040,7 +1040,7 @@ Full walkthrough, bar-for-merging, code style, and what we don't accept → [`CO
Thanks to everyone who has helped move Open Design forward — through code, docs, feedback, new skills, new design systems, or even a sharp issue. Every real contribution counts, and the wall below is the easiest way to say so out loud.
-
+
If you've shipped your first PR — welcome. The [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) label is the entry point.
@@ -1057,9 +1057,9 @@ The SVG above is regenerated daily by [`.github/workflows/metrics.yml`](.github/
-
-
-
+
+
+
diff --git a/README.pt-BR.md b/README.pt-BR.md
index 680e5e389..a3e954615 100644
--- a/README.pt-BR.md
+++ b/README.pt-BR.md
@@ -730,7 +730,7 @@ Walkthrough completo, barra para mergear, estilo de código e o que não aceitam
Obrigado a todas as pessoas que ajudaram a empurrar o Open Design pra frente — via código, docs, feedback, novas skills, novos design systems ou até uma issue afiada. Toda contribuição real conta, e a parede abaixo é a forma mais simples de dizer isso em voz alta.
-
+
Se você acabou de mandar seu primeiro PR — bem-vindo. A label [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) é o ponto de entrada.
@@ -747,9 +747,9 @@ O SVG acima é regenerado diariamente por [`.github/workflows/metrics.yml`](.git
-
-
-
+
+
+
diff --git a/README.ru.md b/README.ru.md
index 3d757c990..42955c1ab 100644
--- a/README.ru.md
+++ b/README.ru.md
@@ -729,7 +729,7 @@ Issues, PR, новые skills и новые design systems приветству
Спасибо всем, кто помогает двигать Open Design вперёд — кодом, документацией, обратной связью, новыми skills, новыми design systems или просто точным issue. Вклад любого реального масштаба здесь важен, а стена ниже — самый простой способ сказать это вслух.
-
+
Если вы только что отправили свой первый PR — добро пожаловать. Метка [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) — хорошая точка входа.
@@ -746,9 +746,9 @@ SVG выше ежедневно пересобирается workflow [`.github/
-
-
-
+
+
+
diff --git a/README.tr.md b/README.tr.md
index d733d087d..3958abe59 100644
--- a/README.tr.md
+++ b/README.tr.md
@@ -887,7 +887,7 @@ Tam walkthrough, merge çıtası, code style ve kabul etmediklerimiz → [`CONTR
Open Design'ı kod, doküman, feedback, yeni skill, yeni design system veya keskin bir issue ile ileri taşıyan herkese teşekkürler. Her gerçek katkı önemlidir; aşağıdaki wall bunu yüksek sesle söylemenin en kolay yolu.
-
+
İlk PR'ını gönderdiysen hoş geldin. [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) label'ı giriş noktasıdır.
@@ -904,9 +904,9 @@ Yukarıdaki SVG [`.github/workflows/metrics.yml`](.github/workflows/metrics.yml)
-
-
-
+
+
+
diff --git a/README.uk.md b/README.uk.md
index b45e7eaa4..2cbbc9daa 100644
--- a/README.uk.md
+++ b/README.uk.md
@@ -729,7 +729,7 @@ OD не зупиняється на коді. Та сама поверхня ч
Дякуємо всім, хто допоміг просувати Open Design — через код, документацію, зворотний зв'язок, нові навички, нові системи дизайну або навіть гостре питання. Кожен реальний внесок рахується, а стіна нижче — найпростіший спосіб сказати це вголос.
-
+
Якщо ви злили свій перший PR — ласкаво просимо. Мітка [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) — це точка входу.
@@ -746,9 +746,9 @@ SVG вище перегенерується щодня [`.github/workflows/metri
-
-
-
+
+
+
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 4735ca902..a5dbd86ae 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -722,7 +722,7 @@ Daemon 启动时从 `PATH` 自动检测,无需配置。流式分发逻辑在 [
感谢每一位让 Open Design 变得更好的朋友 —— 无论是写代码、修文档、提 issue、加 skill 还是加 design system,每一次真实贡献都会被记住。下面这面墙是最直观的「Thank you」。
-
+
第一次提 PR?欢迎从 [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) 标签起步。
@@ -739,9 +739,9 @@ Daemon 启动时从 `PATH` 自动检测,无需配置。流式分发逻辑在 [
-
-
-
+
+
+
diff --git a/README.zh-TW.md b/README.zh-TW.md
index 8b7b2c612..092a54683 100644
--- a/README.zh-TW.md
+++ b/README.zh-TW.md
@@ -1006,7 +1006,7 @@ Daemon 啟動時從 `PATH` 自動檢測,無需配置。流式分發邏輯在 [
感謝每一位讓 Open Design 變得更好的朋友 —— 無論是寫程式碼、修文檔、提 issue、加 skill 還是加 design system,每一次真實貢獻都會被記住。下面這面牆是最直觀的「Thank you」。
-
+
第一次提 PR?歡迎從 [`good-first-issue`/`help-wanted`](https://github.com/nexu-io/open-design/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22%2C%22help+wanted%22) 標籤起步。
@@ -1023,9 +1023,9 @@ Daemon 啟動時從 `PATH` 自動檢測,無需配置。流式分發邏輯在 [
-
-
-
+
+
+
diff --git a/apps/daemon/src/import-export-routes.ts b/apps/daemon/src/import-export-routes.ts
index 15f9c6c90..5e615f570 100644
--- a/apps/daemon/src/import-export-routes.ts
+++ b/apps/daemon/src/import-export-routes.ts
@@ -6,6 +6,7 @@ import {
inlineRelativeAssets,
type InlineAssetReader,
} from './inline-assets.js';
+import { isSandboxModeEnabled } from './sandbox-mode.js';
export interface RegisterImportRoutesDeps extends RouteDeps<'db' | 'http' | 'uploads' | 'node' | 'ids' | 'paths' | 'imports' | 'auth' | 'projectStore' | 'conversations' | 'projectFiles' | 'validation'> {}
@@ -28,6 +29,11 @@ export function registerImportRoutes(app: Express, ctx: RegisterImportRoutesDeps
const { insertConversation } = ctx.conversations;
const { setTabs } = ctx.projectFiles;
const { validateProjectDesignSystemId } = ctx.validation;
+ const rejectSandboxFolderImport = () =>
+ isSandboxModeEnabled(process.env)
+ ? 'folder imports are disabled when OD_SANDBOX_MODE is enabled'
+ : null;
+
app.post(
'/api/import/claude-design',
importUpload.single('file'),
@@ -107,6 +113,10 @@ export function registerImportRoutes(app: Express, ctx: RegisterImportRoutesDeps
if (typeof baseDir !== 'string' || !baseDir.trim()) {
return sendApiError(res, 400, 'BAD_REQUEST', 'baseDir required');
}
+ const sandboxReason = rejectSandboxFolderImport();
+ if (sandboxReason) {
+ return sendApiError(res, 400, 'BAD_REQUEST', sandboxReason);
+ }
let trustedPickerImport = false;
if (isDesktopAuthGateActive()) {
const secret = desktopAuthSecret();
@@ -204,6 +214,10 @@ export function registerImportRoutes(app: Express, ctx: RegisterImportRoutesDeps
if (typeof baseDir !== 'string' || !baseDir.trim()) {
return sendApiError(res, 400, 'BAD_REQUEST', 'baseDir required');
}
+ const sandboxReason = rejectSandboxFolderImport();
+ if (sandboxReason) {
+ return sendApiError(res, 400, 'BAD_REQUEST', sandboxReason);
+ }
let trustedPickerImport = false;
if (isDesktopAuthGateActive()) {
const secret = desktopAuthSecret();
diff --git a/apps/daemon/src/media-config.ts b/apps/daemon/src/media-config.ts
index cb35381b2..466f77b99 100644
--- a/apps/daemon/src/media-config.ts
+++ b/apps/daemon/src/media-config.ts
@@ -41,6 +41,7 @@ import path from 'node:path';
import { MEDIA_PROVIDERS } from './media-models.js';
import { expandHomePrefix } from './home-expansion.js';
import { resolveXAIBearer } from './xai-credentials.js';
+import { isSandboxModeEnabled } from './sandbox-mode.js';
const PROVIDER_IDS = MEDIA_PROVIDERS.map((p) => p.id);
type ProviderEntry = { apiKey?: string; baseUrl?: string; model?: string };
@@ -291,6 +292,7 @@ function apiKeyFromCodexAuth(data: unknown): string {
}
async function resolveOpenAIAuthFileCredential(): Promise {
+ if (isSandboxModeEnabled(process.env)) return null;
const home = os.homedir();
const codexAuth = await readJsonIfPresent(
path.join(home, '.codex', 'auth.json'),
@@ -318,6 +320,8 @@ async function resolveXAIOAuthCredential(
};
}
+ if (isSandboxModeEnabled(process.env)) return null;
+
// 2. Borrow the xAI OAuth token Hermes wrote to ~/.hermes/auth.json
// when the user ran `hermes auth add xai-oauth`. A user who has already authorized
// Hermes doesn't have to run a second OAuth dance inside OD.
diff --git a/apps/daemon/src/project-root.ts b/apps/daemon/src/project-root.ts
new file mode 100644
index 000000000..26693047e
--- /dev/null
+++ b/apps/daemon/src/project-root.ts
@@ -0,0 +1,23 @@
+import path from 'node:path';
+
+export function resolveProjectRoot(moduleDir: string): string {
+ const base = path.basename(moduleDir);
+ const daemonDir =
+ base === 'dist' || base === 'src' ? path.dirname(moduleDir) : moduleDir;
+ return path.resolve(daemonDir, '../..');
+}
+
+export function resolveProjectRootFromNestedModule(moduleDir: string): string {
+ let current = path.resolve(moduleDir);
+ while (true) {
+ const base = path.basename(current);
+ if (base === 'dist' || base === 'src') {
+ return resolveProjectRoot(current);
+ }
+ const parent = path.dirname(current);
+ if (parent === current) {
+ return resolveProjectRoot(moduleDir);
+ }
+ current = parent;
+ }
+}
diff --git a/apps/daemon/src/project-routes.ts b/apps/daemon/src/project-routes.ts
index 6977d6cb2..941c2e8fa 100644
--- a/apps/daemon/src/project-routes.ts
+++ b/apps/daemon/src/project-routes.ts
@@ -1,4 +1,5 @@
import type { Express } from 'express';
+import path from 'node:path';
import {
defaultScenarioPluginIdForProjectMetadata,
type PluginManifest,
@@ -171,6 +172,25 @@ function buildSrcdocTransportShell(): string {