Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .claude/skills/verify/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Verify: run Synara locally for runtime verification
# Verify: run Scient locally for runtime verification

How to launch an isolated Synara instance (server + web) to observe UI changes, without touching `~/.synara` or the default dev ports.
How to launch an isolated Scient instance (server + web) to observe UI changes, without touching `~/.scient` or the default dev ports. The `SYNARA_*` names below are inherited runtime compatibility variables; `SCIENT_HOME` is the canonical state-home variable.

## Launch

```bash
# 1. Server (from the directory you want as the workspace/project cwd):
SYNARA_HOME=<scratch>/synara-home \
SCIENT_HOME=<scratch>/scient-home \
SYNARA_PORT=3899 SYNARA_MODE=web SYNARA_NO_BROWSER=1 \
VITE_DEV_SERVER_URL=http://localhost:5899 \
bun <repo>/apps/server/src/index.ts &
Expand Down
2 changes: 1 addition & 1 deletion .docs/architecture.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Architecture

Synara runs as a **Node.js WebSocket server** that wraps `codex app-server` (JSON-RPC over stdio) and serves a React web app.
Scient runs as a **Node.js WebSocket server** that wraps `codex app-server` (JSON-RPC over stdio) and serves a React web app.

```
┌─────────────────────────────────┐
Expand Down
4 changes: 2 additions & 2 deletions .docs/codex-prerequisites.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Codex prerequisites

- Install Codex CLI so `codex` is on your PATH.
- Authenticate Codex before running Synara (for example via API key or ChatGPT auth supported by Codex).
- Synara starts the server via `codex app-server` per session.
- Authenticate Codex before running Scient (for example via API key or ChatGPT auth supported by Codex).
- Scient starts the server via `codex app-server` per session.
2 changes: 1 addition & 1 deletion .docs/encyclopedia.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Encyclopedia

This is a living glossary for Synara. It explains what common terms mean in this codebase.
This is a living glossary for Scient. It explains what common terms mean in this codebase.

## Table of contents

Expand Down
5 changes: 3 additions & 2 deletions .docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ bun run dev
bun run dev:desktop

# Desktop development on an isolated port set
# `SYNARA_DEV_INSTANCE` is an inherited compatibility variable.
SYNARA_DEV_INSTANCE=feature-xyz bun run dev:desktop

# Production
Expand All @@ -17,6 +18,6 @@ bun run start
# Build a shareable macOS .dmg (arm64 by default)
bun run dist:desktop:dmg

# Or from any project directory after publishing:
npx synara
# Or from any project directory after publishing @scientfactory/cli:
npx --package @scientfactory/cli scient
```
2 changes: 1 addition & 1 deletion .docs/runtime-modes.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Runtime modes

Synara has a global runtime mode switch in the chat toolbar:
Scient has a global runtime mode switch in the chat toolbar:

- **Full access** (default): starts sessions with `approvalPolicy: never` and `sandboxMode: danger-full-access`.
- **Supervised**: starts sessions with `approvalPolicy: on-request` and `sandboxMode: workspace-write`, then prompts in-app for command/file approvals.
14 changes: 7 additions & 7 deletions .docs/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
- `bun run dev` — Starts contracts, server, and web in `turbo watch` mode.
- `bun run dev:server` — Starts just the WebSocket server (uses Bun TypeScript execution).
- `bun run dev:web` — Starts just the Vite dev server for the web app.
- Dev commands default `SYNARA_HOME` to `~/.synara`, which keeps dev state under `~/.synara/dev`.
- Dev commands default `SCIENT_HOME` to `~/.scient` and mirror that value into the inherited `SYNARA_HOME` compatibility variable.
- Override server CLI-equivalent flags from root dev commands with `--`, for example:
`bun run dev -- --home-dir ~/.synara-2`
`bun run dev -- --home-dir ~/.scient-2`
- `bun run start` — Runs the production server (serves built web app as static files).
- `bun run build` — Builds contracts, web app, and server through Turbo.
- `bun run typecheck` — Strict TypeScript checks for all packages.
Expand All @@ -19,9 +19,9 @@
## Desktop `.dmg` packaging notes

- Default build is unsigned/not notarized for local sharing.
- The DMG build uses `assets/macos-icon-1024.png` as the production app icon source.
- Desktop production windows load the bundled UI from `synara://app/index.html` (not a `127.0.0.1` document URL).
- Desktop packaging includes `apps/server/dist` (the `synara` backend) and starts it on loopback with an auth token for WebSocket/API traffic.
- The DMG build uses `assets/prod/scient-macos-1024.png` as the production app icon source.
- Desktop production windows load the bundled UI from `scient://app/index.html` (not a `127.0.0.1` document URL).
- Desktop packaging includes `apps/server/dist` (the Scient backend) and starts it on loopback with an auth token for WebSocket/API traffic.
- Your tester can still open it on macOS by right-clicking the app and choosing **Open** on first launch.
- To keep staging files for debugging package contents, run: `bun run dist:desktop:dmg -- --keep-stage`
- To allow code-signing/notarization when configured in CI/secrets, add: `--signed`.
Expand All @@ -33,10 +33,10 @@

## Running multiple dev instances

Set `SYNARA_DEV_INSTANCE` to any value to deterministically shift all dev ports together.
Set the inherited `SYNARA_DEV_INSTANCE` compatibility variable to any value to deterministically shift all dev ports together.

- Default ports: server `3773`, web `5733`
- Shifted ports: `base + offset` (offset is hashed from `SYNARA_DEV_INSTANCE`)
- Example: `SYNARA_DEV_INSTANCE=branch-a bun run dev:desktop`

If you want full control instead of hashing, set `SYNARA_PORT_OFFSET` to a numeric offset.
If you want full control instead of hashing, set the inherited `SYNARA_PORT_OFFSET` compatibility variable to a numeric offset.
2 changes: 1 addition & 1 deletion .docs/workspace-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

- `/apps/server`: Node.js WebSocket server. Wraps Codex app-server, serves the built web app, and opens the browser on start.
- `/apps/web`: React + Vite UI. Session control, conversation, and provider event rendering. Connects to the server via WebSocket.
- `/apps/desktop`: Electron shell. Spawns a desktop-scoped `synara` backend process and loads the shared web app.
- `/apps/desktop`: Electron shell. Spawns the desktop-scoped `scient` backend process and loads the shared web app.
- `/packages/contracts`: Shared effect/Schema schemas and TypeScript contracts for provider events, WebSocket protocol, and model/session types.
- `/packages/shared`: Shared runtime utilities consumed by both server and web. Uses explicit subpath exports (e.g. `@synara/shared/git`, `@synara/shared/DrainableWorker`) — no barrel index.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ jobs:
if: runner.os == 'Linux'
run: node scripts/node-pty-smoke.mjs

- name: Verify Synara identity
- name: Verify Scient identity
run: bun run brand:check

- name: Format
Expand Down Expand Up @@ -148,7 +148,7 @@ jobs:
- name: Install dependencies
run: bun install --frozen-lockfile --ignore-scripts

- name: Verify Synara identity
- name: Verify Scient identity
run: bun run brand:check

- name: Exercise release-only workflow steps
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ release/
/.synara-*/
/.synara/
/.synara-*/
/.scient/electron-dev
/.scient-*/
.idea/
apps/web/.playwright
apps/web/playwright-report
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Reference usage: opening/closing a project and the sidebar sections in `apps/web
## Local Dev Instance Isolation

- Never start the default `bun run dev` while another Scient instance is running unless the user explicitly wants shared ports/state.
- Use an isolated home dir and non-default ports when running alongside the user's own Synara instance, for example: `env -u SYNARA_AUTH_TOKEN SYNARA_PORT_OFFSET=3158 SYNARA_NO_BROWSER=1 bun run dev -- --home-dir ./.scient-pr84 --port 58090`.
- Use an isolated home dir and non-default ports when running alongside another desktop instance, for example: `env -u SYNARA_AUTH_TOKEN SYNARA_PORT_OFFSET=3158 SYNARA_NO_BROWSER=1 bun run dev -- --home-dir ./.scient-pr84 --port 58090`.
- Always dry-run first when avoiding conflicts: `env -u SYNARA_AUTH_TOKEN SYNARA_PORT_OFFSET=3158 bun run dev -- --home-dir ./.scient-pr84 --port 58090 --dry-run`.
- Unset `SYNARA_AUTH_TOKEN` for browser dev instances unless the web app is also configured to connect with that token. If auth is accidentally inherited, the browser WebSocket can be rejected and the UI will show no threads even though SQLite has projects/threads.
- Check both server and web ports with `lsof -nP -iTCP:<port> -sTCP:LISTEN`. A desktop app can bind `127.0.0.1:<port>` while the dev server binds IPv6 `*:<port>`, and `localhost` may still hit the wrong process.
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ Scient is a local-first desktop workspace for working with the AI agents and sub

It brings chats, terminals, browser previews, diffs, branches, provider sessions, and handoffs into one focused workspace so you can run agent work without juggling a dozen windows.

![Scient app showing parallel agent threads, terminal output, and project navigation](assets/prod/readme-screenshot.jpeg)

## What it does

- Use the AI accounts you already pay for: Claude Code, Codex, Antigravity, OpenCode, Cursor, Grok, Kilo Code, and Pi.
Expand Down
Binary file removed apps/marketing/public/screenshot.jpeg
Binary file not shown.
50 changes: 0 additions & 50 deletions apps/marketing/src/pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,6 @@ import { RELEASES_URL } from "../lib/releases";

<a href="/download" class="other-platforms">Other platforms</a>

<div class="screenshot-wrap">
<img src="/screenshot.jpeg" alt="Scient" class="screenshot" />
</div>
</Layout>

<script>
Expand Down Expand Up @@ -176,50 +173,6 @@ import { RELEASES_URL } from "../lib/releases";
text-decoration-color: var(--fg-muted);
}

/* ── Screenshot ── */

.screenshot-wrap {
margin: 0 auto;
width: min(95vw, 1400px);
opacity: 0;
transform: scale(0.98);
animation: screenshot-in 1s ease-out 0.15s forwards;
}

.screenshot {
width: 100%;
display: block;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.06);
mask-image: linear-gradient(to bottom, black 80%, transparent 100%);
-webkit-mask-image: linear-gradient(to bottom, black 80%, transparent 100%);
}

.screenshot-wrap::after {
content: "";
display: block;
width: 100%;
height: 120px;
background: inherit;
margin-top: -1px;
opacity: 0.04;
transform: scaleY(-1);
mask-image: linear-gradient(to bottom, black 0%, transparent 60%);
-webkit-mask-image: linear-gradient(to bottom, black 0%, transparent 60%);
pointer-events: none;
}

@keyframes screenshot-in {
from {
opacity: 0;
transform: scale(0.98);
}
to {
opacity: 1;
transform: scale(1);
}
}

/* ── Mobile ── */

@media (max-width: 640px) {
Expand All @@ -231,8 +184,5 @@ import { RELEASES_URL } from "../lib/releases";
margin-bottom: 4vh;
}

.screenshot-wrap {
width: 95vw;
}
}
</style>
5 changes: 2 additions & 3 deletions apps/server/scripts/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import { resolveCatalogDependencies } from "../../../scripts/lib/resolve-catalog.ts";
import rootPackageJson from "../../../package.json" with { type: "json" };
import serverPackageJson from "../package.json" with { type: "json" };
import { replaceBundledWebClient } from "./replace-bundled-web-client.ts";

class CliError extends Data.TaggedError("CliError")<{
readonly message: string;
Expand Down Expand Up @@ -149,7 +150,6 @@ const buildCmd = Command.make(
(config) =>
Effect.gen(function* () {
const path = yield* Path.Path;
const fs = yield* FileSystem.FileSystem;
const repoRoot = yield* RepoRoot;
const serverDir = path.join(repoRoot, "apps/server");

Expand All @@ -167,8 +167,7 @@ const buildCmd = Command.make(
const webDist = path.join(repoRoot, "apps/web/dist");
const clientTarget = path.join(serverDir, "dist/client");

if (yield* fs.exists(webDist)) {
yield* fs.copy(webDist, clientTarget);
if (yield* replaceBundledWebClient(webDist, clientTarget)) {
yield* applyDevelopmentIconOverrides(repoRoot, serverDir);
yield* Effect.log("[cli] Bundled web app into dist/client");
} else {
Expand Down
42 changes: 42 additions & 0 deletions apps/server/scripts/replace-bundled-web-client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as NodeServices from "@effect/platform-node/NodeServices";
import { Effect, FileSystem, Path } from "effect";
import { describe, expect, it } from "vitest";

import { replaceBundledWebClient } from "./replace-bundled-web-client.ts";

describe("replaceBundledWebClient", () => {
it("removes stale client assets before copying the current web build", async () => {
await Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const root = yield* fs.makeTempDirectoryScoped({ prefix: "scient-web-client-" });
const webDist = path.join(root, "web-dist");
const clientTarget = path.join(root, "server-dist", "client");

yield* fs.makeDirectory(webDist, { recursive: true });
yield* fs.makeDirectory(clientTarget, { recursive: true });
yield* fs.writeFileString(path.join(webDist, "index.html"), "current");
yield* fs.writeFileString(path.join(clientTarget, "stale-logo.svg"), "obsolete");

expect(yield* replaceBundledWebClient(webDist, clientTarget)).toBe(true);
expect(yield* fs.readFileString(path.join(clientTarget, "index.html"))).toBe("current");
expect(yield* fs.exists(path.join(clientTarget, "stale-logo.svg"))).toBe(false);
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer), Effect.runPromise);
});

it("leaves the existing client untouched when the web build is absent", async () => {
await Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const root = yield* fs.makeTempDirectoryScoped({ prefix: "scient-web-client-" });
const clientTarget = path.join(root, "server-dist", "client");
const existingAsset = path.join(clientTarget, "index.html");

yield* fs.makeDirectory(clientTarget, { recursive: true });
yield* fs.writeFileString(existingAsset, "existing");

expect(yield* replaceBundledWebClient(path.join(root, "missing"), clientTarget)).toBe(false);
expect(yield* fs.readFileString(existingAsset)).toBe("existing");
}).pipe(Effect.scoped, Effect.provide(NodeServices.layer), Effect.runPromise);
});
});
18 changes: 18 additions & 0 deletions apps/server/scripts/replace-bundled-web-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// FILE: replace-bundled-web-client.ts
// Purpose: Replaces the bundled web client without retaining files from older builds.

import { Effect, FileSystem } from "effect";

export const replaceBundledWebClient = Effect.fn("replaceBundledWebClient")(function* (
webDist: string,
clientTarget: string,
) {
const fs = yield* FileSystem.FileSystem;
if (!(yield* fs.exists(webDist))) {
return false;
}

yield* fs.remove(clientTarget, { force: true, recursive: true });
yield* fs.copy(webDist, clientTarget);
return true;
});
4 changes: 2 additions & 2 deletions apps/server/src/git/Layers/GitCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ it.layer(TestLayer)("git integration", (it) => {

const stashList = yield* git(tmp, ["stash", "list"]);
expect(stashList).toContain("pre-existing stash");
expect(stashList).not.toContain("synara: stash before switching to feature");
expect(stashList).not.toContain("scient: stash before switching to feature");
expect(yield* readTextFile(path.join(tmp, "README.md"))).toBe("dirty changes\n");
}),
);
Expand Down Expand Up @@ -859,7 +859,7 @@ it.layer(TestLayer)("git integration", (it) => {
expect(yield* readTextFile(path.join(tmp, "README.md"))).toBe("conflicting content\n");
expect((yield* git(tmp, ["status", "--short"])).trim()).toBe("");
expect(yield* git(tmp, ["stash", "list"])).toContain(
"synara: stash before switching to conflicting",
"scient: stash before switching to conflicting",
);
}),
);
Expand Down
4 changes: 2 additions & 2 deletions apps/server/src/git/Layers/GitCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const EMPTY_TREE_OBJECT_ID = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
const WORKING_TREE_DIFF_TIMEOUT_MS = 15_000;
const MAX_UNTRACKED_DIFF_CONCURRENCY = 4;
const MOVE_AWARE_WORKING_TREE_STATUS_TIMEOUT_MS = 15_000;
const AUTO_DETACHED_WORKTREE_DIRNAME = "synara";
const AUTO_DETACHED_WORKTREE_DIRNAME = "scient";
const NON_REPOSITORY_STATUS_DETAILS = Object.freeze({
isRepo: false,
hasOriginRemote: false,
Expand Down Expand Up @@ -2445,7 +2445,7 @@ export const makeGitCore = (options?: { executeOverride?: GitCoreShape["execute"
yield* executeGit(
"GitCore.stashAndCheckout.stashPush",
input.cwd,
["stash", "push", "-u", "-m", `synara: stash before switching to ${input.branch}`],
["stash", "push", "-u", "-m", `scient: stash before switching to ${input.branch}`],
{
timeoutMs: 30_000,
fallbackErrorMessage: "git stash failed",
Expand Down
6 changes: 3 additions & 3 deletions apps/server/src/git/Layers/GitManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2323,7 +2323,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
mode: "worktree",
});

expect(result.branch).toBe("synara/pr-91/main");
expect(result.branch).toBe("scient/pr-91/main");
expect(result.worktreePath).not.toBeNull();
expect((yield* runGit(repoDir, ["branch", "--show-current"])).stdout.trim()).toBe("main");
expect((yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim()).toBe(mainBefore);
Expand All @@ -2332,7 +2332,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
"branch",
"--show-current",
])).stdout.trim(),
).toBe("synara/pr-91/main");
).toBe("scient/pr-91/main");
}),
);

Expand Down Expand Up @@ -2384,7 +2384,7 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
mode: "worktree",
});

expect(result.branch).toBe("synara/pr-92/main");
expect(result.branch).toBe("scient/pr-92/main");
expect((yield* runGit(repoDir, ["rev-parse", "main"])).stdout.trim()).toBe(localMainBefore);
expect(
(yield* runGit(result.worktreePath as string, [
Expand Down
Loading
Loading