diff --git a/CUT3.png b/Rowl.png similarity index 100% rename from CUT3.png rename to Rowl.png diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 51fddf46b8c..93efeadc34d 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -27,5 +27,5 @@ "vitest": "catalog:", "wait-on": "^8.0.2" }, - "productName": "CUT3" + "productName": "Rowl" } diff --git a/apps/desktop/resources/icon.png b/apps/desktop/resources/icon.png index 7deef34a342..762d14ae76c 100644 Binary files a/apps/desktop/resources/icon.png and b/apps/desktop/resources/icon.png differ diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index d37337c3d46..079f2d3241b 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -70,7 +70,7 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state"; const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state"; const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download"; const UPDATE_INSTALL_CHANNEL = "desktop:update-install"; -const DESKTOP_SCHEME = "cut3"; +const DESKTOP_SCHEME = "rowl"; const ROOT_DIR = Path.resolve(__dirname, "../../.."); const isDevelopment = Boolean(process.env.VITE_DEV_SERVER_URL); const appReleaseBranding = resolveAppReleaseBranding({ @@ -78,7 +78,7 @@ const appReleaseBranding = resolveAppReleaseBranding({ isDevelopment, }); const STATE_DIR = - process.env.CUT3_STATE_DIR?.trim() || + process.env.ROWL_STATE_DIR?.trim() || Path.join(OS.homedir(), ".t3", appReleaseBranding.stateDirName); const APP_DISPLAY_NAME = appReleaseBranding.displayName; const APP_USER_MODEL_ID = appReleaseBranding.appId; @@ -530,8 +530,8 @@ function resolveEmbeddedCommitHash(): string | null { try { const raw = FS.readFileSync(packageJsonPath, "utf8"); - const parsed = JSON.parse(raw) as { cut3CommitHash?: unknown }; - return normalizeCommitHash(parsed.cut3CommitHash); + const parsed = JSON.parse(raw) as { rowlCommitHash?: unknown }; + return normalizeCommitHash(parsed.rowlCommitHash); } catch { return null; } @@ -542,7 +542,7 @@ function resolveAboutCommitHash(): string | null { return aboutCommitHashCache; } - const envCommitHash = normalizeCommitHash(process.env.CUT3_COMMIT_HASH); + const envCommitHash = normalizeCommitHash(process.env.ROWL_COMMIT_HASH); if (envCommitHash) { aboutCommitHashCache = envCommitHash; return aboutCommitHashCache; @@ -564,7 +564,7 @@ function resolveBackendEntry(): string { } function resolveBackendCwd(): string { - const override = process.env.CUT3_BACKEND_CWD?.trim(); + const override = process.env.ROWL_BACKEND_CWD?.trim(); if (override) { return override; } @@ -630,7 +630,7 @@ function handleFatalStartupError(stage: string, error: unknown): void { console.error(`[desktop] fatal startup error (${stage})`, error); if (!isQuitting) { isQuitting = true; - dialog.showErrorBox("CUT3 failed to start", `Stage: ${stage}\n${message}${detail}`); + dialog.showErrorBox("Rowl failed to start", `Stage: ${stage}\n${message}${detail}`); } stopBackend(); restoreStdIoCapture?.(); @@ -651,7 +651,7 @@ function updateBackendWsUrl(port: number): void { port, authToken: backendAuthToken, }); - process.env.CUT3_DESKTOP_WS_URL = backendWsUrl; + process.env.ROWL_DESKTOP_WS_URL = backendWsUrl; writeDesktopLogHeader(`backend websocket url updated port=${port}`); broadcastBackendWsUrl(); } @@ -747,7 +747,7 @@ function handleCheckForUpdatesMenuClick(): void { isPackaged: app.isPackaged, platform: process.platform, appImage: process.env.APPIMAGE, - disabledByEnv: process.env.CUT3_DISABLE_AUTO_UPDATE === "1", + disabledByEnv: process.env.ROWL_DISABLE_AUTO_UPDATE === "1", }); if (disabledReason) { console.info("[desktop-updater] Manual update check requested, but updates are disabled."); @@ -774,7 +774,7 @@ async function checkForUpdatesFromMenu(): Promise { void dialog.showMessageBox({ type: "info", title: "You're up to date!", - message: `CUT3 ${updateState.currentVersion} is currently the newest version available.`, + message: `Rowl ${updateState.currentVersion} is currently the newest version available.`, buttons: ["OK"], }); } else if (updateState.status === "error") { @@ -999,7 +999,7 @@ function shouldEnableAutoUpdates(): boolean { isPackaged: app.isPackaged, platform: process.platform, appImage: process.env.APPIMAGE, - disabledByEnv: process.env.CUT3_DISABLE_AUTO_UPDATE === "1", + disabledByEnv: process.env.ROWL_DISABLE_AUTO_UPDATE === "1", }) === null ); } @@ -1084,7 +1084,7 @@ function configureAutoUpdater(): void { updaterConfigured = true; const githubToken = - process.env.CUT3_DESKTOP_UPDATE_GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || ""; + process.env.ROWL_DESKTOP_UPDATE_GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim() || ""; if (githubToken) { // When a token is provided, re-configure the feed with `private: true` so // electron-updater uses the GitHub API (api.github.com) instead of the @@ -1187,11 +1187,11 @@ function configureAutoUpdater(): void { function backendEnv(): NodeJS.ProcessEnv { return { ...process.env, - CUT3_MODE: "desktop", - CUT3_NO_BROWSER: "1", - CUT3_PORT: "0", - CUT3_STATE_DIR: STATE_DIR, - CUT3_AUTH_TOKEN: backendAuthToken, + ROWL_MODE: "desktop", + ROWL_NO_BROWSER: "1", + ROWL_PORT: "0", + ROWL_STATE_DIR: STATE_DIR, + ROWL_AUTH_TOKEN: backendAuthToken, }; } @@ -1598,7 +1598,7 @@ function createWindow(): BrowserWindow { contextIsolation: true, nodeIntegration: false, sandbox: true, - additionalArguments: backendWsUrl ? [`--cut3-desktop-ws-url=${backendWsUrl}`] : [], + additionalArguments: backendWsUrl ? [`--rowl-desktop-ws-url=${backendWsUrl}`] : [], }, }); diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts index ee5790f56e9..176760375b6 100644 --- a/apps/desktop/src/preload.ts +++ b/apps/desktop/src/preload.ts @@ -15,7 +15,7 @@ const UPDATE_STATE_CHANNEL = "desktop:update-state"; const UPDATE_GET_STATE_CHANNEL = "desktop:update-get-state"; const UPDATE_DOWNLOAD_CHANNEL = "desktop:update-download"; const UPDATE_INSTALL_CHANNEL = "desktop:update-install"; -let wsUrl = process.env.CUT3_DESKTOP_WS_URL ?? null; +let wsUrl = process.env.ROWL_DESKTOP_WS_URL ?? null; wsUrl = resolveInitialDesktopWsUrl({ envValue: wsUrl, argv: process.argv }); ipcRenderer.on(BACKEND_WS_URL_UPDATED_CHANNEL, (_event, nextUrl: unknown) => { diff --git a/apps/desktop/src/preloadWsUrl.test.ts b/apps/desktop/src/preloadWsUrl.test.ts index 940f357f6b6..aec43335fe5 100644 --- a/apps/desktop/src/preloadWsUrl.test.ts +++ b/apps/desktop/src/preloadWsUrl.test.ts @@ -7,7 +7,7 @@ describe("resolveInitialDesktopWsUrl", () => { expect( resolveInitialDesktopWsUrl({ envValue: "ws://127.0.0.1:3773/?token=env-token", - argv: ["electron", "app.js", "--cut3-desktop-ws-url=ws://127.0.0.1:4000"], + argv: ["electron", "app.js", "--rowl-desktop-ws-url=ws://127.0.0.1:4000"], }), ).toBe("ws://127.0.0.1:3773/?token=env-token"); }); @@ -16,7 +16,7 @@ describe("resolveInitialDesktopWsUrl", () => { expect( resolveInitialDesktopWsUrl({ envValue: undefined, - argv: ["electron", "app.js", "--cut3-desktop-ws-url=ws://127.0.0.1:4000/?token=test"], + argv: ["electron", "app.js", "--rowl-desktop-ws-url=ws://127.0.0.1:4000/?token=test"], }), ).toBe("ws://127.0.0.1:4000/?token=test"); }); diff --git a/apps/desktop/src/preloadWsUrl.ts b/apps/desktop/src/preloadWsUrl.ts index 66e96965025..7bddd4a31eb 100644 --- a/apps/desktop/src/preloadWsUrl.ts +++ b/apps/desktop/src/preloadWsUrl.ts @@ -1,4 +1,4 @@ -const DESKTOP_WS_URL_ARG_PREFIX = "--cut3-desktop-ws-url="; +const DESKTOP_WS_URL_ARG_PREFIX = "--rowl-desktop-ws-url="; export function resolveInitialDesktopWsUrl(args: { envValue: string | null | undefined; diff --git a/apps/desktop/src/updateState.test.ts b/apps/desktop/src/updateState.test.ts index 8ab628f6cac..7017570a139 100644 --- a/apps/desktop/src/updateState.test.ts +++ b/apps/desktop/src/updateState.test.ts @@ -86,7 +86,7 @@ describe("getAutoUpdateDisabledReason", () => { appImage: undefined, disabledByEnv: true, }), - ).toContain("CUT3_DISABLE_AUTO_UPDATE"); + ).toContain("ROWL_DISABLE_AUTO_UPDATE"); }); it("reports linux non-AppImage builds as disabled", () => { diff --git a/apps/desktop/src/updateState.ts b/apps/desktop/src/updateState.ts index c1ecbe89081..c80508ba3d0 100644 --- a/apps/desktop/src/updateState.ts +++ b/apps/desktop/src/updateState.ts @@ -42,7 +42,7 @@ export function getAutoUpdateDisabledReason(args: { return "Automatic updates are only available in packaged production builds."; } if (args.disabledByEnv) { - return "Automatic updates are disabled by the CUT3_DISABLE_AUTO_UPDATE setting."; + return "Automatic updates are disabled by the ROWL_DISABLE_AUTO_UPDATE setting."; } if (args.platform === "linux" && !args.appImage) { return "Automatic updates on Linux require running the AppImage build."; diff --git a/apps/server/package.json b/apps/server/package.json index 11059746c69..8abc771c3c0 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,5 +1,5 @@ { - "name": "cut3", + "name": "rowl", "version": "1.0.1", "repository": { "type": "git", @@ -7,7 +7,7 @@ "directory": "apps/server" }, "bin": { - "cut3": "./dist/index.mjs" + "rowl": "./dist/index.mjs" }, "files": [ "dist" diff --git a/apps/server/src/codexAppServerManager.ts b/apps/server/src/codexAppServerManager.ts index 910c94392f8..33c6d57e387 100644 --- a/apps/server/src/codexAppServerManager.ts +++ b/apps/server/src/codexAppServerManager.ts @@ -454,8 +454,8 @@ export function normalizeCodexModelSlug( export function buildCodexInitializeParams() { return { clientInfo: { - name: "cut3_desktop", - title: "CUT3 Desktop", + name: "rowl_desktop", + title: "Rowl Desktop", version: "0.1.0", }, capabilities: { @@ -1084,7 +1084,7 @@ export class CodexAppServerManager extends EventEmitter, - env: Record = { CUT3_NO_BROWSER: "true" }, + env: Record = { ROWL_NO_BROWSER: "true" }, ) => { const uniqueStateDir = `/tmp/t3-cli-state-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; return Command.runWith(t3Cli, { version: "0.0.0-test" })(args).pipe( @@ -71,7 +71,7 @@ const runCli = ( ConfigProvider.layer( ConfigProvider.fromEnv({ env: { - CUT3_STATE_DIR: uniqueStateDir, + ROWL_STATE_DIR: uniqueStateDir, ...env, }, }), @@ -134,13 +134,13 @@ it.layer(testLayer)("server CLI command", (it) => { it.effect("uses env fallbacks when flags are not provided", () => Effect.gen(function* () { yield* runCli([], { - CUT3_MODE: "desktop", - CUT3_PORT: "4999", - CUT3_HOST: "100.88.10.4", - CUT3_STATE_DIR: "/tmp/t3-env-state", + ROWL_MODE: "desktop", + ROWL_PORT: "4999", + ROWL_HOST: "100.88.10.4", + ROWL_STATE_DIR: "/tmp/t3-env-state", VITE_DEV_SERVER_URL: "http://localhost:5173", - CUT3_NO_BROWSER: "true", - CUT3_AUTH_TOKEN: "env-token", + ROWL_NO_BROWSER: "true", + ROWL_AUTH_TOKEN: "env-token", }); assert.equal(start.mock.calls.length, 1); @@ -157,12 +157,12 @@ it.layer(testLayer)("server CLI command", (it) => { }), ); - it.effect("prefers --mode over CUT3_MODE", () => + it.effect("prefers --mode over ROWL_MODE", () => Effect.gen(function* () { findAvailablePort.mockImplementation((_preferred: number) => Effect.succeed(4666)); yield* runCli(["--mode", "web"], { - CUT3_MODE: "desktop", - CUT3_NO_BROWSER: "true", + ROWL_MODE: "desktop", + ROWL_NO_BROWSER: "true", }); assert.deepStrictEqual(findAvailablePort.mock.calls, [[3773]]); @@ -173,10 +173,10 @@ it.layer(testLayer)("server CLI command", (it) => { }), ); - it.effect("prefers --no-browser over CUT3_NO_BROWSER", () => + it.effect("prefers --no-browser over ROWL_NO_BROWSER", () => Effect.gen(function* () { yield* runCli(["--no-browser"], { - CUT3_NO_BROWSER: "false", + ROWL_NO_BROWSER: "false", }); assert.equal(start.mock.calls.length, 1); @@ -187,8 +187,8 @@ it.layer(testLayer)("server CLI command", (it) => { it.effect("opens a tokenized browser url when auth is enabled", () => Effect.gen(function* () { yield* runCli([], { - CUT3_NO_BROWSER: "false", - CUT3_AUTH_TOKEN: "env-token", + ROWL_NO_BROWSER: "false", + ROWL_AUTH_TOKEN: "env-token", }); assert.deepStrictEqual(openBrowser.mock.calls, [["http://127.0.0.1:3773/?token=env-token"]]); @@ -211,8 +211,8 @@ it.layer(testLayer)("server CLI command", (it) => { it.effect("uses fixed localhost defaults in desktop mode", () => Effect.gen(function* () { yield* runCli([], { - CUT3_MODE: "desktop", - CUT3_NO_BROWSER: "true", + ROWL_MODE: "desktop", + ROWL_NO_BROWSER: "true", }); assert.equal(findAvailablePort.mock.calls.length, 0); @@ -226,9 +226,9 @@ it.layer(testLayer)("server CLI command", (it) => { it.effect("accepts an ephemeral desktop port from the environment", () => Effect.gen(function* () { yield* runCli([], { - CUT3_MODE: "desktop", - CUT3_PORT: "0", - CUT3_NO_BROWSER: "true", + ROWL_MODE: "desktop", + ROWL_PORT: "0", + ROWL_NO_BROWSER: "true", }); assert.equal(findAvailablePort.mock.calls.length, 0); @@ -242,8 +242,8 @@ it.layer(testLayer)("server CLI command", (it) => { it.effect("allows overriding desktop host with --host", () => Effect.gen(function* () { yield* runCli(["--host", "0.0.0.0"], { - CUT3_MODE: "desktop", - CUT3_NO_BROWSER: "true", + ROWL_MODE: "desktop", + ROWL_NO_BROWSER: "true", }); assert.equal(start.mock.calls.length, 1); @@ -255,10 +255,10 @@ it.layer(testLayer)("server CLI command", (it) => { it.effect("supports CLI and env for bootstrap/log websocket toggles", () => Effect.gen(function* () { yield* runCli(["--auto-bootstrap-project-from-cwd"], { - CUT3_MODE: "desktop", - CUT3_LOG_WS_EVENTS: "false", - CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false", - CUT3_NO_BROWSER: "true", + ROWL_MODE: "desktop", + ROWL_LOG_WS_EVENTS: "false", + ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD: "false", + ROWL_NO_BROWSER: "true", }); assert.equal(start.mock.calls.length, 1); diff --git a/apps/server/src/main.ts b/apps/server/src/main.ts index 9fb1139b7f1..db6cd452b4a 100644 --- a/apps/server/src/main.ts +++ b/apps/server/src/main.ts @@ -71,7 +71,7 @@ export interface CliConfigShape { * CliConfig - Service tag for startup CLI/runtime helpers. */ export class CliConfig extends ServiceMap.Service()( - "cut3/main/CliConfig", + "rowl/main/CliConfig", ) { static readonly layer = Layer.effect( CliConfig, @@ -91,7 +91,7 @@ export class CliConfig extends ServiceMap.Service()( } const CliEnvConfig = Config.all({ - mode: Config.string("CUT3_MODE").pipe( + mode: Config.string("ROWL_MODE").pipe( Config.option, Config.map( Option.match({ @@ -100,26 +100,26 @@ const CliEnvConfig = Config.all({ }), ), ), - port: Config.number("CUT3_PORT").pipe( + port: Config.number("ROWL_PORT").pipe( Config.option, Config.map(Option.match({ onNone: () => undefined, onSome: (value) => value })), ), - host: Config.string("CUT3_HOST").pipe(Config.option, Config.map(Option.getOrUndefined)), - stateDir: Config.string("CUT3_STATE_DIR").pipe(Config.option, Config.map(Option.getOrUndefined)), + host: Config.string("ROWL_HOST").pipe(Config.option, Config.map(Option.getOrUndefined)), + stateDir: Config.string("ROWL_STATE_DIR").pipe(Config.option, Config.map(Option.getOrUndefined)), devUrl: Config.url("VITE_DEV_SERVER_URL").pipe(Config.option, Config.map(Option.getOrUndefined)), - noBrowser: Config.boolean("CUT3_NO_BROWSER").pipe( + noBrowser: Config.boolean("ROWL_NO_BROWSER").pipe( Config.option, Config.map(Option.getOrUndefined), ), - authToken: Config.string("CUT3_AUTH_TOKEN").pipe( + authToken: Config.string("ROWL_AUTH_TOKEN").pipe( Config.option, Config.map(Option.getOrUndefined), ), - autoBootstrapProjectFromCwd: Config.boolean("CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD").pipe( + autoBootstrapProjectFromCwd: Config.boolean("ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD").pipe( Config.option, Config.map(Option.getOrUndefined), ), - logWebSocketEvents: Config.boolean("CUT3_LOG_WS_EVENTS").pipe( + logWebSocketEvents: Config.boolean("ROWL_LOG_WS_EVENTS").pipe( Config.option, Config.map(Option.getOrUndefined), ), @@ -276,7 +276,7 @@ const makeServerProgram = (input: CliInput) => ? `http://${formatHostForUrl(config.host)}:${listeningPort}` : localUrl; const { authToken, devUrl, ...safeConfig } = config; - yield* Effect.logInfo("CUT3 running", { + yield* Effect.logInfo("Rowl running", { ...safeConfig, devUrl: devUrl?.toString(), authEnabled: Boolean(authToken), @@ -315,7 +315,7 @@ const hostFlag = Flag.string("host").pipe( Flag.optional, ); const stateDirFlag = Flag.string("state-dir").pipe( - Flag.withDescription("State directory path (equivalent to CUT3_STATE_DIR)."), + Flag.withDescription("State directory path (equivalent to ROWL_STATE_DIR)."), Flag.optional, ); const devUrlFlag = Flag.string("dev-url").pipe( @@ -340,7 +340,7 @@ const autoBootstrapProjectFromCwdFlag = Flag.boolean("auto-bootstrap-project-fro ); const logWebSocketEventsFlag = Flag.boolean("log-websocket-events").pipe( Flag.withDescription( - "Emit server-side logs for outbound WebSocket push traffic (equivalent to CUT3_LOG_WS_EVENTS).", + "Emit server-side logs for outbound WebSocket push traffic (equivalent to ROWL_LOG_WS_EVENTS).", ), Flag.withAlias("log-ws-events"), Flag.optional, @@ -357,6 +357,6 @@ export const t3Cli = Command.make("t3", { autoBootstrapProjectFromCwd: autoBootstrapProjectFromCwdFlag, logWebSocketEvents: logWebSocketEventsFlag, }).pipe( - Command.withDescription("Run the CUT3 server."), + Command.withDescription("Run the Rowl server."), Command.withHandler((input) => Effect.scoped(makeServerProgram(input))), ); diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index 0e9c8b59f82..a547cfc5ea8 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -1455,4 +1455,105 @@ describe("ProviderCommandReactor", () => { expect(thread?.session?.threadId).toBe("thread-1"); expect(thread?.session?.activeTurnId).toBeNull(); }); + + describe("per-thread concurrency", () => { + it("control events are not serialized behind turn-start events", async () => { + const harness = await createHarness(); + const now = new Date().toISOString(); + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.turn.start", + commandId: CommandId.makeUnsafe("cmd-concurrent-ctrl-turn"), + threadId: ThreadId.makeUnsafe("thread-1"), + message: { + messageId: asMessageId("user-message-ctrl"), + role: "user", + text: "turn with interrupt", + attachments: [], + }, + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, + runtimeMode: "approval-required", + createdAt: now, + }), + ); + + await waitFor(() => harness.sendTurn.mock.calls.length >= 1); + + const interruptCallCountBefore = harness.interruptTurn.mock.calls.length; + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.turn.interrupt", + commandId: CommandId.makeUnsafe("cmd-concurrent-ctrl-interrupt"), + threadId: ThreadId.makeUnsafe("thread-1"), + turnId: undefined, + createdAt: now, + }), + ); + + await waitFor(() => harness.interruptTurn.mock.calls.length > interruptCallCountBefore, 3000); + + expect(harness.interruptTurn.mock.calls.length).toBeGreaterThan(0); + }); + + it("different threads' turn-start events both reach sendTurn", async () => { + const harness = await createHarness(); + const now = new Date().toISOString(); + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.create", + commandId: CommandId.makeUnsafe("cmd-thread-create-2"), + threadId: ThreadId.makeUnsafe("thread-2"), + projectId: asProjectId("project-1"), + title: "Thread 2", + model: "gpt-5-codex", + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, + runtimeMode: "approval-required", + branch: null, + worktreePath: null, + createdAt: now, + }), + ); + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.turn.start", + commandId: CommandId.makeUnsafe("cmd-concurrent-t1"), + threadId: ThreadId.makeUnsafe("thread-1"), + message: { + messageId: asMessageId("user-message-t1"), + role: "user", + text: "turn thread 1", + attachments: [], + }, + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, + runtimeMode: "approval-required", + createdAt: now, + }), + ); + + await Effect.runPromise( + harness.engine.dispatch({ + type: "thread.turn.start", + commandId: CommandId.makeUnsafe("cmd-concurrent-t2"), + threadId: ThreadId.makeUnsafe("thread-2"), + message: { + messageId: asMessageId("user-message-t2"), + role: "user", + text: "turn thread 2", + attachments: [], + }, + interactionMode: DEFAULT_PROVIDER_INTERACTION_MODE, + runtimeMode: "approval-required", + createdAt: now, + }), + ); + + await waitFor(() => harness.sendTurn.mock.calls.length >= 2, 5000); + + expect(harness.sendTurn.mock.calls.length).toBe(2); + }); + }); }); diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts index fe8d0b81e50..7b12cd3fc3d 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts @@ -15,8 +15,20 @@ import { type TurnId, } from "@t3tools/contracts"; import { isCodexOpenRouterModel } from "@t3tools/shared/model"; -import { Cache, Cause, Duration, Effect, Layer, Option, Schema, Stream } from "effect"; -import { makeDrainableWorker } from "@t3tools/shared/DrainableWorker"; +import { + Cache, + Cause, + Duration, + Effect, + Fiber, + Layer, + Option, + Queue, + Ref, + Schema, + Stream, +} from "effect"; +import * as Semaphore from "effect/Semaphore"; import { resolveThreadWorkspaceCwd } from "../../checkpointing/Utils.ts"; import { GitCore } from "../../git/Services/GitCore.ts"; @@ -241,6 +253,38 @@ const make = Effect.gen(function* () { ); const threadProviderOptions = new Map(); + const perThreadSemaphores = new Map(); + + const getSemaphoreForThread = (threadId: ThreadId): Semaphore.Semaphore => { + let sem = perThreadSemaphores.get(threadId); + if (!sem) { + sem = Effect.runSync(Semaphore.make(1)); + perThreadSemaphores.set(threadId, sem); + } + return sem; + }; + + type Lane = { + readonly queue: Queue.Queue; + readonly fiber: Fiber.Fiber; + }; + + const perThreadLanesRef = yield* Ref.make>(new Map()); + + const getOrCreateLane = (threadId: ThreadId) => + Effect.gen(function* () { + const lanes = yield* Ref.get(perThreadLanesRef); + const existing = lanes.get(threadId); + if (existing) return existing; + + const queue = yield* Queue.unbounded(); + const fiber = yield* Effect.forkScoped( + Effect.forever(Queue.take(queue).pipe(Effect.flatMap(processDomainEventSafely))), + ); + const lane: Lane = { queue, fiber }; + yield* Ref.set(perThreadLanesRef, new Map(lanes).set(threadId, lane)); + return lane; + }); const appendProviderFailureActivity = (input: { readonly threadId: ThreadId; @@ -748,63 +792,66 @@ const make = Effect.gen(function* () { .pipe(Effect.catch(() => Effect.void)); } - yield* sendTurnForThread({ - threadId: event.payload.threadId, - messageText: message.text, - ...(message.attachments !== undefined ? { attachments: message.attachments } : {}), - ...(event.payload.provider !== undefined ? { provider: event.payload.provider } : {}), - ...(event.payload.model !== undefined ? { model: event.payload.model } : {}), - ...(event.payload.modelOptions !== undefined - ? { modelOptions: event.payload.modelOptions } - : {}), - ...(providerOptions !== undefined ? { providerOptions } : {}), - ...(event.payload.skills !== undefined ? { skills: event.payload.skills } : {}), - interactionMode: event.payload.interactionMode, - createdAt: event.payload.createdAt, - }).pipe( - Effect.tap(() => - Effect.logInfo("provider command reactor completed turn-start send", { - threadId: event.payload.threadId, - provider: event.payload.provider ?? null, - model: event.payload.model ?? null, - }), - ), - Effect.catchCause((cause) => - Effect.gen(function* () { - const error = Cause.squash(cause); - const detail = toErrorMessage(error); - const sessionProvider = - thread.session?.providerName === "codex" || - thread.session?.providerName === "copilot" || - thread.session?.providerName === "kimi" || - thread.session?.providerName === "opencode" || - thread.session?.providerName === "pi" - ? thread.session.providerName - : null; - const provider = event.payload.provider ?? sessionProvider; - - yield* appendProviderFailureActivity({ - threadId: event.payload.threadId, - kind: "provider.turn.start.failed", - summary: "Provider turn start failed", - detail, - turnId: thread.latestTurn?.turnId ?? null, - createdAt: event.payload.createdAt, - }); - yield* setProviderFailureSession({ + const sem = getSemaphoreForThread(event.payload.threadId); + yield* sem.withPermits(1)( + sendTurnForThread({ + threadId: event.payload.threadId, + messageText: message.text, + ...(message.attachments !== undefined ? { attachments: message.attachments } : {}), + ...(event.payload.provider !== undefined ? { provider: event.payload.provider } : {}), + ...(event.payload.model !== undefined ? { model: event.payload.model } : {}), + ...(event.payload.modelOptions !== undefined + ? { modelOptions: event.payload.modelOptions } + : {}), + ...(providerOptions !== undefined ? { providerOptions } : {}), + ...(event.payload.skills !== undefined ? { skills: event.payload.skills } : {}), + interactionMode: event.payload.interactionMode, + createdAt: event.payload.createdAt, + }).pipe( + Effect.tap(() => + Effect.logInfo("provider command reactor completed turn-start send", { threadId: event.payload.threadId, - provider, - runtimeMode: thread.runtimeMode, - lastError: detail, - createdAt: event.payload.createdAt, - ...(thread.session?.startedAt !== undefined - ? { startedAt: thread.session.startedAt } - : {}), - ...(thread.session?.tokenUsage !== undefined - ? { tokenUsage: thread.session.tokenUsage } - : {}), - }); - }), + provider: event.payload.provider ?? null, + model: event.payload.model ?? null, + }), + ), + Effect.catchCause((cause) => + Effect.gen(function* () { + const error = Cause.squash(cause); + const detail = toErrorMessage(error); + const sessionProvider = + thread.session?.providerName === "codex" || + thread.session?.providerName === "copilot" || + thread.session?.providerName === "kimi" || + thread.session?.providerName === "opencode" || + thread.session?.providerName === "pi" + ? thread.session.providerName + : null; + const provider = event.payload.provider ?? sessionProvider; + + yield* appendProviderFailureActivity({ + threadId: event.payload.threadId, + kind: "provider.turn.start.failed", + summary: "Provider turn start failed", + detail, + turnId: thread.latestTurn?.turnId ?? null, + createdAt: event.payload.createdAt, + }); + yield* setProviderFailureSession({ + threadId: event.payload.threadId, + provider, + runtimeMode: thread.runtimeMode, + lastError: detail, + createdAt: event.payload.createdAt, + ...(thread.session?.startedAt !== undefined + ? { startedAt: thread.session.startedAt } + : {}), + ...(thread.session?.tokenUsage !== undefined + ? { tokenUsage: thread.session.tokenUsage } + : {}), + }); + }), + ), ), ); }); @@ -999,8 +1046,6 @@ const make = Effect.gen(function* () { }), ); - const worker = yield* makeDrainableWorker(processDomainEventSafely); - const start: ProviderCommandReactorShape["start"] = Effect.forkScoped( Stream.runForEach(orchestrationEngine.streamDomainEvents, (event) => { if ( @@ -1014,13 +1059,27 @@ const make = Effect.gen(function* () { return Effect.void; } - return worker.enqueue(event); + if (event.type === "thread.turn-start-requested") { + return getOrCreateLane(event.payload.threadId).pipe( + Effect.flatMap((lane) => Queue.offer(lane.queue, event)), + Effect.asVoid, + ); + } + + return Effect.forkScoped(processDomainEventSafely(event)); }), ).pipe(Effect.asVoid); + const drain = Effect.gen(function* () { + const lanes = yield* Ref.get(perThreadLanesRef); + const laneEntries = Array.from(lanes.entries()); + yield* Effect.forEach(laneEntries, ([, lane]) => Queue.shutdown(lane.queue)); + yield* Effect.forEach(laneEntries, ([, lane]) => Fiber.interrupt(lane.fiber)); + }); + return { start, - drain: worker.drain, + drain, } satisfies ProviderCommandReactorShape; }); diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts index 03555690c03..c54a9c439be 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts @@ -35,7 +35,7 @@ const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_TTL = Duration.minutes(120); const BUFFERED_PROPOSED_PLAN_BY_ID_CACHE_CAPACITY = 10_000; const BUFFERED_PROPOSED_PLAN_BY_ID_TTL = Duration.minutes(120); const MAX_BUFFERED_ASSISTANT_CHARS = 24_000; -const STRICT_PROVIDER_LIFECYCLE_GUARD = process.env.CUT3_STRICT_PROVIDER_LIFECYCLE_GUARD !== "0"; +const STRICT_PROVIDER_LIFECYCLE_GUARD = process.env.ROWL_STRICT_PROVIDER_LIFECYCLE_GUARD !== "0"; type TurnStartRequestedDomainEvent = Extract< OrchestrationEvent, diff --git a/apps/server/src/projectWorkspaceMetadata.ts b/apps/server/src/projectWorkspaceMetadata.ts index e892546f106..754d5efaeb0 100644 --- a/apps/server/src/projectWorkspaceMetadata.ts +++ b/apps/server/src/projectWorkspaceMetadata.ts @@ -21,11 +21,11 @@ import { import { Schema } from "effect"; const AGENTS_FILE_NAME = "AGENTS.md"; -const COMMANDS_DIRECTORY_RELATIVE_PATH = ".cut3/commands"; -const SKILLS_DIRECTORY_RELATIVE_PATH = ".cut3/skills"; +const COMMANDS_DIRECTORY_RELATIVE_PATH = ".rowl/commands"; +const SKILLS_DIRECTORY_RELATIVE_PATH = ".rowl/skills"; const SKILL_FILE_NAME = "SKILL.md"; -const INIT_SECTION_START = ""; -const INIT_SECTION_END = ""; +const INIT_SECTION_START = ""; +const INIT_SECTION_END = ""; function safeReadTextFile(filePath: string): string | null { try { @@ -119,7 +119,7 @@ function buildInitManagedSection(cwd: string): string { const lines: string[] = [ INIT_SECTION_START, - "## CUT3 Init Snapshot", + "## Rowl Init Snapshot", "", `- Workspace root: ${path.basename(cwd)}`, ]; diff --git a/apps/server/src/serverLayers.ts b/apps/server/src/serverLayers.ts index 3e142f2a181..026147cedf0 100644 --- a/apps/server/src/serverLayers.ts +++ b/apps/server/src/serverLayers.ts @@ -42,7 +42,7 @@ import { BunPtyAdapterLive } from "./terminal/Layers/BunPTY"; import { NodePtyAdapterLive } from "./terminal/Layers/NodePTY"; import { AnalyticsService } from "./telemetry/Services/AnalyticsService"; -const ENABLE_PROVIDER_EVENT_LOGS_ENV = "CUT3_ENABLE_PROVIDER_EVENT_LOGS"; +const ENABLE_PROVIDER_EVENT_LOGS_ENV = "ROWL_ENABLE_PROVIDER_EVENT_LOGS"; function shouldEnableProviderEventLogs(): boolean { const raw = process.env[ENABLE_PROVIDER_EVENT_LOGS_ENV]?.trim().toLowerCase(); diff --git a/apps/server/src/telemetry/Layers/AnalyticsService.test.ts b/apps/server/src/telemetry/Layers/AnalyticsService.test.ts index d42f7bec972..02d079eaf5d 100644 --- a/apps/server/src/telemetry/Layers/AnalyticsService.test.ts +++ b/apps/server/src/telemetry/Layers/AnalyticsService.test.ts @@ -48,10 +48,10 @@ it.layer(NodeServices.layer)("AnalyticsService test", (it) => { const telemetryLayer = AnalyticsServiceLayerLive.pipe(Layer.provideMerge(serverConfigLayer)); const configLayer = ConfigProvider.layer( ConfigProvider.fromUnknown({ - CUT3_TELEMETRY_ENABLED: true, - CUT3_POSTHOG_KEY: "phc_test_key", - CUT3_POSTHOG_HOST: "", - CUT3_TELEMETRY_FLUSH_BATCH_SIZE: 20, + ROWL_TELEMETRY_ENABLED: true, + ROWL_POSTHOG_KEY: "phc_test_key", + ROWL_POSTHOG_HOST: "", + ROWL_TELEMETRY_FLUSH_BATCH_SIZE: 20, }), ); const batchServerLayer = HttpServer.serve( diff --git a/apps/server/src/telemetry/Layers/AnalyticsService.ts b/apps/server/src/telemetry/Layers/AnalyticsService.ts index 74939ccfd8a..c8b3150b295 100644 --- a/apps/server/src/telemetry/Layers/AnalyticsService.ts +++ b/apps/server/src/telemetry/Layers/AnalyticsService.ts @@ -22,15 +22,15 @@ interface BufferedAnalyticsEvent { } const TelemetryEnvConfig = Config.all({ - posthogKey: Config.string("CUT3_POSTHOG_KEY").pipe( + posthogKey: Config.string("ROWL_POSTHOG_KEY").pipe( Config.withDefault("phc_XOWci4oZP4VvLiEyrFqkFjP4CZn55mjYYBMREK5Wd6m"), ), - posthogHost: Config.string("CUT3_POSTHOG_HOST").pipe( + posthogHost: Config.string("ROWL_POSTHOG_HOST").pipe( Config.withDefault("https://us.i.posthog.com"), ), - enabled: Config.boolean("CUT3_TELEMETRY_ENABLED").pipe(Config.withDefault(true)), - flushBatchSize: Config.number("CUT3_TELEMETRY_FLUSH_BATCH_SIZE").pipe(Config.withDefault(20)), - maxBufferedEvents: Config.number("CUT3_TELEMETRY_MAX_BUFFERED_EVENTS").pipe( + enabled: Config.boolean("ROWL_TELEMETRY_ENABLED").pipe(Config.withDefault(true)), + flushBatchSize: Config.number("ROWL_TELEMETRY_FLUSH_BATCH_SIZE").pipe(Config.withDefault(20)), + maxBufferedEvents: Config.number("ROWL_TELEMETRY_MAX_BUFFERED_EVENTS").pipe( Config.withDefault(1_000), ), }); diff --git a/apps/server/src/terminal/Layers/Manager.test.ts b/apps/server/src/terminal/Layers/Manager.test.ts index ccdec95c568..7e0d7654b66 100644 --- a/apps/server/src/terminal/Layers/Manager.test.ts +++ b/apps/server/src/terminal/Layers/Manager.test.ts @@ -721,7 +721,7 @@ describe("TerminalManager", () => { }; setEnv("PORT", "5173"); - setEnv("CUT3_PORT", "3773"); + setEnv("ROWL_PORT", "3773"); setEnv("VITE_DEV_SERVER_URL", "http://localhost:5173"); setEnv("TEST_TERMINAL_KEEP", "keep-me"); @@ -733,7 +733,7 @@ describe("TerminalManager", () => { if (!spawnInput) return; expect(spawnInput.env.PORT).toBeUndefined(); - expect(spawnInput.env.CUT3_PORT).toBeUndefined(); + expect(spawnInput.env.ROWL_PORT).toBeUndefined(); expect(spawnInput.env.VITE_DEV_SERVER_URL).toBeUndefined(); expect(spawnInput.env.TEST_TERMINAL_KEEP).toBe("keep-me"); @@ -748,8 +748,8 @@ describe("TerminalManager", () => { await manager.open( openInput({ env: { - CUT3_PROJECT_ROOT: "/repo", - CUT3_WORKTREE_PATH: "/repo/worktree-a", + ROWL_PROJECT_ROOT: "/repo", + ROWL_WORKTREE_PATH: "/repo/worktree-a", CUSTOM_FLAG: "1", }, }), @@ -758,8 +758,8 @@ describe("TerminalManager", () => { expect(spawnInput).toBeDefined(); if (!spawnInput) return; - expect(spawnInput.env.CUT3_PROJECT_ROOT).toBe("/repo"); - expect(spawnInput.env.CUT3_WORKTREE_PATH).toBe("/repo/worktree-a"); + expect(spawnInput.env.ROWL_PROJECT_ROOT).toBe("/repo"); + expect(spawnInput.env.ROWL_WORKTREE_PATH).toBe("/repo/worktree-a"); expect(spawnInput.env.CUSTOM_FLAG).toBe("1"); await manager.dispose(); diff --git a/apps/server/src/terminal/Layers/Manager.ts b/apps/server/src/terminal/Layers/Manager.ts index 23caf2441ce..44d6df5a98d 100644 --- a/apps/server/src/terminal/Layers/Manager.ts +++ b/apps/server/src/terminal/Layers/Manager.ts @@ -274,7 +274,7 @@ function toSessionKey(threadId: string, terminalId: string): string { function shouldExcludeTerminalEnvKey(key: string): boolean { const normalizedKey = key.toUpperCase(); - if (normalizedKey.startsWith("CUT3_")) { + if (normalizedKey.startsWith("ROWL_")) { return true; } if (normalizedKey.startsWith("VITE_")) { diff --git a/apps/server/src/wsServer.ts b/apps/server/src/wsServer.ts index d56820326fe..7a2f51bdcc5 100644 --- a/apps/server/src/wsServer.ts +++ b/apps/server/src/wsServer.ts @@ -151,7 +151,7 @@ export interface ServerShape { /** * Server - Service tag for HTTP/WebSocket lifecycle management. */ -export class Server extends ServiceMap.Service()("cut3/wsServer/Server") {} +export class Server extends ServiceMap.Service()("rowl/wsServer/Server") {} const isServerNotRunningError = (error: Error): boolean => { const maybeCode = (error as NodeJS.ErrnoException).code; diff --git a/apps/web/index.html b/apps/web/index.html index 2e9a56c7023..a592730c8fc 100644 --- a/apps/web/index.html +++ b/apps/web/index.html @@ -15,7 +15,7 @@ (() => { const root = document.documentElement; try { - const raw = localStorage.getItem("cut3:app-settings:v1"); + const raw = localStorage.getItem("rowl:app-settings:v1"); if (!raw) { return; } @@ -29,7 +29,7 @@ } })(); - CUT3 + Rowl
diff --git a/apps/web/public/Rowlappicon.png b/apps/web/public/Rowlappicon.png new file mode 100644 index 00000000000..762d14ae76c Binary files /dev/null and b/apps/web/public/Rowlappicon.png differ diff --git a/apps/web/public/apple-touch-icon.png b/apps/web/public/apple-touch-icon.png index 42e669af355..762d14ae76c 100644 Binary files a/apps/web/public/apple-touch-icon.png and b/apps/web/public/apple-touch-icon.png differ diff --git a/apps/web/public/favicon-16x16.png b/apps/web/public/favicon-16x16.png index 66edce332b9..d0faa59576e 100644 Binary files a/apps/web/public/favicon-16x16.png and b/apps/web/public/favicon-16x16.png differ diff --git a/apps/web/public/favicon-32x32.png b/apps/web/public/favicon-32x32.png index f45be08fe39..ec749bc3a24 100644 Binary files a/apps/web/public/favicon-32x32.png and b/apps/web/public/favicon-32x32.png differ diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico index 8e0add12c2e..762d14ae76c 100644 Binary files a/apps/web/public/favicon.ico and b/apps/web/public/favicon.ico differ diff --git a/apps/web/public/favicon.png b/apps/web/public/favicon.png new file mode 100644 index 00000000000..762d14ae76c Binary files /dev/null and b/apps/web/public/favicon.png differ diff --git a/apps/web/public/icon.png b/apps/web/public/icon.png index 7deef34a342..762d14ae76c 100644 Binary files a/apps/web/public/icon.png and b/apps/web/public/icon.png differ diff --git a/apps/web/public/rowl-logo-dark.png b/apps/web/public/rowl-logo-dark.png new file mode 100644 index 00000000000..3ef8baded84 Binary files /dev/null and b/apps/web/public/rowl-logo-dark.png differ diff --git a/apps/web/public/rowl-logo-light.png b/apps/web/public/rowl-logo-light.png new file mode 100644 index 00000000000..7271d9ab5e9 Binary files /dev/null and b/apps/web/public/rowl-logo-light.png differ diff --git a/apps/web/src/appSettings.ts b/apps/web/src/appSettings.ts index 2a0c9fe8f88..b778293c1a9 100644 --- a/apps/web/src/appSettings.ts +++ b/apps/web/src/appSettings.ts @@ -23,7 +23,7 @@ import { CUSTOM_THEME_IDS } from "./lib/customThemes"; import { normalizeModelPreferenceSlugs } from "./lib/modelPreferences"; import { isOpenRouterGuaranteedFreeSlug } from "./lib/openRouterModels"; -const APP_SETTINGS_STORAGE_KEY = "cut3:app-settings:v1"; +const APP_SETTINGS_STORAGE_KEY = "rowl:app-settings:v1"; const MAX_CUSTOM_MODEL_COUNT = 32; const MAX_FAVORITE_MODEL_COUNT = 32; const MAX_RECENT_MODEL_COUNT = 12; diff --git a/apps/web/src/branding.ts b/apps/web/src/branding.ts index c9b41e6f5c8..07adb1d175f 100644 --- a/apps/web/src/branding.ts +++ b/apps/web/src/branding.ts @@ -1,6 +1,6 @@ import { resolveAppReleaseBranding } from "@t3tools/shared/appRelease"; -export const APP_BASE_NAME = "CUT3"; +export const APP_BASE_NAME = "Rowl"; export const APP_VERSION = import.meta.env.APP_VERSION || "0.0.0"; const appReleaseBranding = resolveAppReleaseBranding({ version: APP_VERSION, diff --git a/apps/web/src/components/AppearanceSettingsSection.tsx b/apps/web/src/components/AppearanceSettingsSection.tsx index e46e0bca32a..a98b00d835b 100644 --- a/apps/web/src/components/AppearanceSettingsSection.tsx +++ b/apps/web/src/components/AppearanceSettingsSection.tsx @@ -71,7 +71,7 @@ function getAppearanceCopy(language: AppLanguage) { return { sectionTitle: "ظاهر", sectionDescription: - "پالت پایه روشن و تیره، تایپوگرافی و کنترل های ظاهری CUT3 را برای وب و Electron تنظیم کنید.", + "پالت پایه روشن و تیره، تایپوگرافی و کنترل های ظاهری Rowl را برای وب و Electron تنظیم کنید.", themeMode: { light: { label: "روشن", description: "از ظاهر روشن استفاده شود." }, dark: { label: "تیره", description: "از ظاهر تیره استفاده شود." }, @@ -107,7 +107,7 @@ function getAppearanceCopy(language: AppLanguage) { pointerCursorsDescription: "به جای فلش پیش فرض، روی دکمه ها و پیوندها از نشانگر دستی استفاده شود.", uiFontSize: "اندازه فونت رابط", - uiFontSizeDescription: "اندازه پایه مورد استفاده در رابط CUT3 را تنظیم می کند.", + uiFontSizeDescription: "اندازه پایه مورد استفاده در رابط Rowl را تنظیم می کند.", uiFontSizeAria: "اندازه فونت رابط بر حسب پیکسل", timestampFormat: "قالب زمان", timestampFormatDescription: @@ -136,7 +136,7 @@ function getAppearanceCopy(language: AppLanguage) { importDialogDescription: "یک شیء JSON با فیلدهای accent، background، foreground، uiFont، codeFont، translucentSidebar و contrast وارد کنید.", importDialogFootnote: (themeName: string) => - `CUT3 فقط مقادیر ${themeName} فعلی را درون ریزی می کند.`, + `Rowl فقط مقادیر ${themeName} فعلی را درون ریزی می کند.`, cancel: "لغو", applyImport: "اعمال درون ریزی", }; @@ -145,7 +145,7 @@ function getAppearanceCopy(language: AppLanguage) { return { sectionTitle: "Appearance", sectionDescription: - "Customize CUT3's base light and dark palettes, typography, and interactive chrome across web and Electron.", + "Customize Rowl's base light and dark palettes, typography, and interactive chrome across web and Electron.", themeMode: { light: { label: "Light", description: "Use the light appearance." }, dark: { label: "Dark", description: "Use the dark appearance." }, @@ -181,7 +181,7 @@ function getAppearanceCopy(language: AppLanguage) { pointerCursorsDescription: "Use hand cursors on buttons and links instead of the default arrow.", uiFontSize: "UI font size", - uiFontSizeDescription: "Adjust the base size used across the shared CUT3 interface.", + uiFontSizeDescription: "Adjust the base size used across the shared Rowl interface.", uiFontSizeAria: "UI font size in pixels", timestampFormat: "Timestamp format", timestampFormatDescription: "System default follows your browser or OS time format.", @@ -207,7 +207,7 @@ function getAppearanceCopy(language: AppLanguage) { importDialogDescription: "Paste a JSON object with accent, background, foreground, uiFont, codeFont, translucentSidebar, and contrast.", importDialogFootnote: (themeName: string) => - `CUT3 only imports the current ${themeName} values.`, + `Rowl only imports the current ${themeName} values.`, cancel: "Cancel", applyImport: "Apply import", }; diff --git a/apps/web/src/components/BranchToolbar.logic.ts b/apps/web/src/components/BranchToolbar.logic.ts index 6e569ce13f5..9a9f0890854 100644 --- a/apps/web/src/components/BranchToolbar.logic.ts +++ b/apps/web/src/components/BranchToolbar.logic.ts @@ -1,7 +1,7 @@ import type { GitBranch } from "@t3tools/contracts"; export type EnvMode = "local" | "worktree"; -const PREFERRED_REMOTE_NAME = "CUT3"; +const PREFERRED_REMOTE_NAME = "Rowl"; export function resolveEffectiveEnvMode(input: { activeWorktreePath: string | null; diff --git a/apps/web/src/components/ChatView.browser.tsx b/apps/web/src/components/ChatView.browser.tsx index faa9dc7d1d7..a2a108796f5 100644 --- a/apps/web/src/components/ChatView.browser.tsx +++ b/apps/web/src/components/ChatView.browser.tsx @@ -38,7 +38,7 @@ const PROJECT_ID = "project-1" as ProjectId; const NOW_ISO = "2026-03-04T12:00:00.000Z"; const BASE_TIME_MS = Date.parse(NOW_ISO); const ATTACHMENT_SVG = ""; -const APP_SETTINGS_STORAGE_KEY = "cut3:app-settings:v1"; +const APP_SETTINGS_STORAGE_KEY = "rowl:app-settings:v1"; const CHAT_BACKGROUND_TEST_DATA_URL = "data:image/svg+xml," + encodeURIComponent( @@ -134,7 +134,7 @@ function isoAt(offsetSeconds: number): string { function createBaseServerConfig(): ServerConfig { return { cwd: "/repo/project", - keybindingsConfigPath: "/repo/project/.cut3-keybindings.json", + keybindingsConfigPath: "/repo/project/.rowl-keybindings.json", keybindings: [], issues: [], providers: [ diff --git a/apps/web/src/components/ChatView.logic.ts b/apps/web/src/components/ChatView.logic.ts index 5ffca53cc3f..675f6311b70 100644 --- a/apps/web/src/components/ChatView.logic.ts +++ b/apps/web/src/components/ChatView.logic.ts @@ -10,8 +10,8 @@ import { getAppModelOptions } from "../appSettings"; import { type ComposerImageAttachment, type DraftThreadState } from "../composerDraftStore"; import { Schema } from "effect"; -export const LAST_INVOKED_SCRIPT_BY_PROJECT_KEY = "cut3:last-invoked-script-by-project"; -const WORKTREE_BRANCH_PREFIX = "cut3"; +export const LAST_INVOKED_SCRIPT_BY_PROJECT_KEY = "rowl:last-invoked-script-by-project"; +const WORKTREE_BRANCH_PREFIX = "rowl"; export const LastInvokedScriptByProjectSchema = Schema.Record(ProjectId, Schema.String); diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index 4a83c0a4f6f..4e63831fe05 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -338,8 +338,8 @@ import { findMatchingApprovalRule, type ApprovalRule } from "../approvalRules"; import { formatTimestamp } from "../timestampFormat"; import { showTurnCompleteNotification } from "../notifications"; -const LAST_EDITOR_KEY = "cut3:last-editor"; -const LAST_INVOKED_SCRIPT_BY_PROJECT_KEY = "cut3:last-invoked-script-by-project"; +const LAST_EDITOR_KEY = "rowl:last-editor"; +const LAST_INVOKED_SCRIPT_BY_PROJECT_KEY = "rowl:last-invoked-script-by-project"; const ATTACHMENT_PREVIEW_HANDOFF_TTL_MS = 5000; const IMAGE_SIZE_LIMIT_LABEL = `${Math.round(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES / (1024 * 1024))}MB`; const IMAGE_ONLY_BOOTSTRAP_PROMPT = @@ -366,7 +366,7 @@ const EMPTY_PENDING_USER_INPUT_ANSWERS: Record Enter your OpenRouter API key - CUT3 needs an OpenRouter API key before it can start Codex sessions that use + Rowl needs an OpenRouter API key before it can start Codex sessions that use OpenRouter-routed models such as openrouter/free or specific{" "} :free model ids. @@ -7923,7 +7923,7 @@ export default function ChatView({ threadId }: ChatViewProps) { Enter your Kimi API key - CUT3 can start Kimi CLI chat with a Kimi Code API key. You can generate one from the + Rowl can start Kimi CLI chat with a Kimi Code API key. You can generate one from the Kimi Code Console, or authenticate in the local CLI with kimi login or /login instead. @@ -8392,8 +8392,8 @@ const ThreadModelRerouteBanner = memo(function ThreadModelRerouteBanner({ : getModelDisplayName(notice.toModel, "codex"); const message = notice.toModel === "openrouter/free" - ? `CUT3 retried this turn through ${toModelLabel} after ${fromModelLabel} could not be served. OpenRouter may answer with a different free model for this turn.` - : `CUT3 retried this turn from ${fromModelLabel} to ${toModelLabel}.`; + ? `Rowl retried this turn through ${toModelLabel} after ${fromModelLabel} could not be served. OpenRouter may answer with a different free model for this turn.` + : `Rowl retried this turn from ${fromModelLabel} to ${toModelLabel}.`; return (
@@ -8828,8 +8828,8 @@ function getChatSurfaceCopy(language: AppLanguage) { "تصاویر را انتخاب، رها، یا پیست کنید؛ حداکثر ۸ تصویر و هر کدام تا ۱۰ مگابایت", followUpQueued: "پیگیری در صف قرار گرفت", steeringCurrentRun: "در حال هدایت نوبت فعلی", - steeringCurrentRunHint: "CUT3 نوبت فعلی را متوقف می کند و این پیگیری را بعدی می فرستد.", - queueCurrentRunHint: "CUT3 این پیگیری را بعد از تمام شدن نوبت فعلی می فرستد.", + steeringCurrentRunHint: "Rowl نوبت فعلی را متوقف می کند و این پیگیری را بعدی می فرستد.", + queueCurrentRunHint: "Rowl این پیگیری را بعد از تمام شدن نوبت فعلی می فرستد.", queuedTurnFailed: "ارسال مورد در صف انجام نشد", retryQueuedFollowUp: "تلاش دوباره", removeQueuedFollowUp: "حذف", @@ -8885,8 +8885,8 @@ function getChatSurfaceCopy(language: AppLanguage) { attachImagesTooltip: "Attach images · drag, paste, or pick up to 8 images (10 MB each)", followUpQueued: "Follow-up queued", steeringCurrentRun: "Steering current run", - steeringCurrentRunHint: "CUT3 will stop the current turn and send this follow-up next.", - queueCurrentRunHint: "CUT3 will send this follow-up after the current turn settles.", + steeringCurrentRunHint: "Rowl will stop the current turn and send this follow-up next.", + queueCurrentRunHint: "Rowl will send this follow-up after the current turn settles.", queuedTurnFailed: "Queued follow-up failed", retryQueuedFollowUp: "Retry", removeQueuedFollowUp: "Remove", @@ -9134,7 +9134,7 @@ export function ProviderSetupDialog(props: { "وضعیت runtime های محلی را بررسی کنید، کلیدها را اضافه کنید، و قدم بعدی هر ارائه دهنده را بدون خروج از چت ببینید.", snapshotTitle: "نمای آماده سازی ارائه دهنده", snapshotDescription: - "CUT3 وضعیت runtime های محلی را می خواند. احراز هویت OpenCode، Codex، Copilot، Kimi، و Pi همچنان در ابزارهای خود آنها مدیریت می شود.", + "Rowl وضعیت runtime های محلی را می خواند. احراز هویت OpenCode، Codex، Copilot، Kimi، و Pi همچنان در ابزارهای خود آنها مدیریت می شود.", ready: "آماده", attention: "نیاز به توجه", unavailable: "ناموجود", @@ -9158,7 +9158,7 @@ export function ProviderSetupDialog(props: { "Check local runtime health, add keys, and see the next step for each provider without leaving chat.", snapshotTitle: "Provider readiness snapshot", snapshotDescription: - "CUT3 inspects your local runtimes here. Authentication for OpenCode, Codex, Copilot, Kimi, and Pi still lives in their own CLIs and config files.", + "Rowl inspects your local runtimes here. Authentication for OpenCode, Codex, Copilot, Kimi, and Pi still lives in their own CLIs and config files.", ready: "Ready", attention: "Needs attention", unavailable: "Unavailable", @@ -9306,7 +9306,7 @@ export function ProviderSetupDialog(props: { ? "Shared OpenRouter key is ready for OpenRouter-routed sessions." : "Add the shared OpenRouter API key to unlock OpenRouter-routed models."; message = - "Used for openrouter/free and any saved OpenRouter :free slugs. CUT3 also forwards the same key to new OpenCode sessions when their config expects OPENROUTER_API_KEY."; + "Used for openrouter/free and any saved OpenRouter :free slugs. Rowl also forwards the same key to new OpenCode sessions when their config expects OPENROUTER_API_KEY."; actions.push(

- CUT3 reads {binaryCommand} auth list, {binaryCommand} mcp list, + Rowl reads {binaryCommand} auth list, {binaryCommand} mcp list, and {binaryCommand} mcp auth list. OpenCode still owns the actual login, logout, and OAuth flows.

@@ -256,7 +256,7 @@ export const OpenCodeCredentialsManager = memo(function OpenCodeCredentialsManag {!mcpSupported ? (

- CUT3 could not inspect OpenCode MCP servers from this runtime snapshot. + Rowl could not inspect OpenCode MCP servers from this runtime snapshot.

) : mcpServers.length === 0 ? (

diff --git a/apps/web/src/components/PiProvider.browser.tsx b/apps/web/src/components/PiProvider.browser.tsx index 4b705eb65a3..938b6fa4fa7 100644 --- a/apps/web/src/components/PiProvider.browser.tsx +++ b/apps/web/src/components/PiProvider.browser.tsx @@ -61,7 +61,7 @@ describe("Pi provider GUI", () => { authStatus: "unauthenticated", checkedAt: "2026-03-24T00:00:00.000Z", message: - "Pi is embedded in CUT3, but no authenticated Pi-backed models are currently available.", + "Pi is embedded in Rowl, but no authenticated Pi-backed models are currently available.", }, ]; @@ -93,13 +93,13 @@ describe("Pi provider GUI", () => { const piCard = await waitForElement( () => Array.from(document.querySelectorAll("div, section")).find((element) => - (element.textContent ?? "").includes("CUT3 embeds Pi through its Node SDK"), + (element.textContent ?? "").includes("Rowl embeds Pi through its Node SDK"), ) ?? null, "Unable to find the Pi guidance card in the provider setup dialog.", ); expect(piCard.textContent).toContain("Pi"); - expect(piCard.textContent).toContain("CUT3 embeds Pi through its Node SDK"); + expect(piCard.textContent).toContain("Rowl embeds Pi through its Node SDK"); expect(piCard.textContent).toContain("bunx pi"); } finally { await screen.unmount(); diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 6938d3926c8..bd4294ee25c 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -42,7 +42,8 @@ import { useLocation, useNavigate, useParams } from "@tanstack/react-router"; import { useAppSettings } from "../appSettings"; import { getAppLanguageDetails, type AppLanguage } from "../appLanguage"; import { isElectron } from "../env"; -import { APP_BASE_NAME, APP_VERSION } from "../branding"; +import { APP_VERSION } from "../branding"; +import { useTheme } from "../hooks/useTheme"; import { resolveServerHttpUrl } from "../lib/serverUrl"; import { cn, isMacPlatform, newCommandId } from "../lib/utils"; import { useStore } from "../store"; @@ -116,7 +117,7 @@ function getSidebarCopy(language: AppLanguage) { failedToRenameThread: "تغییر نام رشته انجام نشد", keepingOrphanedWorktree: "worktree یتیم حفظ شد", orphanedWorktreeKeptForSafety: (path: string) => - `CUT3 نتوانست بررسی کند که ${path} تغییرات ثبت نشده دارد یا نه، بنابراین worktree برای ایمنی حفظ می شود.`, + `Rowl نتوانست بررسی کند که ${path} تغییرات ثبت نشده دارد یا نه، بنابراین worktree برای ایمنی حفظ می شود.`, orphanedWorktreeWithChangesPrompt: (path: string) => [ "این رشته تنها رشته متصل به این worktree است:", @@ -216,7 +217,7 @@ function getSidebarCopy(language: AppLanguage) { failedToRenameThread: "Failed to rename thread", keepingOrphanedWorktree: "Keeping orphaned worktree", orphanedWorktreeKeptForSafety: (path: string) => - `CUT3 could not verify whether ${path} has uncommitted changes, so the worktree will be kept for safety.`, + `Rowl could not verify whether ${path} has uncommitted changes, so the worktree will be kept for safety.`, orphanedWorktreeWithChangesPrompt: (path: string) => [ "This thread is the only one linked to this worktree:", @@ -406,7 +407,9 @@ function localizeThreadStatusLabel(label: string, language: AppLanguage): string } function BrandMark() { - return ; + const { resolvedTheme } = useTheme(); + const src = resolvedTheme === "dark" ? "/rowl-logo-dark.png" : "/rowl-logo-light.png"; + return ; } function ProjectFavicon({ cwd }: { cwd: string }) { @@ -1426,9 +1429,6 @@ export default function Sidebar() { render={

- - {APP_BASE_NAME} -
} /> diff --git a/apps/web/src/components/ThreadNewButton.browser.tsx b/apps/web/src/components/ThreadNewButton.browser.tsx index b3792f78e2f..0dfe94ba434 100644 --- a/apps/web/src/components/ThreadNewButton.browser.tsx +++ b/apps/web/src/components/ThreadNewButton.browser.tsx @@ -41,7 +41,7 @@ const wsLink = ws.link(/ws(s)?:\/\/.*/); function createBaseServerConfig(): ServerConfig { return { cwd: "/repo/project", - keybindingsConfigPath: "/repo/project/.cut3-keybindings.json", + keybindingsConfigPath: "/repo/project/.rowl-keybindings.json", keybindings: [], issues: [], providers: [ diff --git a/apps/web/src/components/chat/ComposerSkillPicker.tsx b/apps/web/src/components/chat/ComposerSkillPicker.tsx index db3317110dd..842f5488465 100644 --- a/apps/web/src/components/chat/ComposerSkillPicker.tsx +++ b/apps/web/src/components/chat/ComposerSkillPicker.tsx @@ -44,7 +44,7 @@ export const ComposerSkillPicker = memo(function ComposerSkillPicker(props: {
{props.skills.length === 0 ? (
- No skills found in .cut3/skills. + No skills found in .rowl/skills.
) : (
diff --git a/apps/web/src/components/chat/EmptyChatOnboarding.tsx b/apps/web/src/components/chat/EmptyChatOnboarding.tsx index 119db1d2928..9c431913317 100644 --- a/apps/web/src/components/chat/EmptyChatOnboarding.tsx +++ b/apps/web/src/components/chat/EmptyChatOnboarding.tsx @@ -13,18 +13,18 @@ function getEmptyChatOnboardingCopy(language: "en" | "fa") { if (language === "fa") { return { loadingTitle: "در حال آماده سازی پروژه ها...", - loadingDescription: "CUT3 در حال همگام سازی فضای کاری محلی شما است.", + loadingDescription: "Rowl در حال همگام سازی فضای کاری محلی شما است.", noProjectsTitle: "اولین پروژه خود را اضافه کنید", noProjectsDescription: - "CUT3 به یک پوشه پروژه نیاز دارد تا بتواند thread ها را ایجاد کند، دستورهای محلی را کشف کند، و AGENTS.md و skill های فضای کاری را بخواند.", + "Rowl به یک پوشه پروژه نیاز دارد تا بتواند thread ها را ایجاد کند، دستورهای محلی را کشف کند، و AGENTS.md و skill های فضای کاری را بخواند.", browse: "مرور پوشه", addProject: "افزودن پروژه", pathLabel: "مسیر پروژه", pathPlaceholder: "/path/to/project", - nextStepHint: "بعد از افزودن پروژه، CUT3 فوراً اولین thread پیش نویس را برای شما باز می کند.", + nextStepHint: "بعد از افزودن پروژه، Rowl فوراً اولین thread پیش نویس را برای شما باز می کند.", existingProjectsTitle: "یک thread جدید شروع کنید", existingProjectsDescription: (count: number) => - `${count} پروژه از قبل در CUT3 موجود است. می توانید یک thread جدید بسازید یا از سایدبار یک thread قبلی را ادامه دهید.`, + `${count} پروژه از قبل در Rowl موجود است. می توانید یک thread جدید بسازید یا از سایدبار یک thread قبلی را ادامه دهید.`, createThread: "ایجاد thread جدید", sidebarHint: "یا یک thread موجود را از سایدبار انتخاب کنید.", }; @@ -32,19 +32,19 @@ function getEmptyChatOnboardingCopy(language: "en" | "fa") { return { loadingTitle: "Preparing your projects...", - loadingDescription: "CUT3 is syncing the local workspace state.", + loadingDescription: "Rowl is syncing the local workspace state.", noProjectsTitle: "Add your first project", noProjectsDescription: - "CUT3 needs a project folder before it can create threads, discover repo-local commands, and read workspace AGENTS.md and skills.", + "Rowl needs a project folder before it can create threads, discover repo-local commands, and read workspace AGENTS.md and skills.", browse: "Browse for folder", addProject: "Add project", pathLabel: "Project path", pathPlaceholder: "/path/to/project", nextStepHint: - "After you add a project, CUT3 immediately opens your first draft thread so you can start working right away.", + "After you add a project, Rowl immediately opens your first draft thread so you can start working right away.", existingProjectsTitle: "Start a new thread", existingProjectsDescription: (count: number) => - `${count} project${count === 1 ? " is" : "s are"} already available in CUT3. Start a new thread or resume one from the sidebar.`, + `${count} project${count === 1 ? " is" : "s are"} already available in Rowl. Start a new thread or resume one from the sidebar.`, createThread: "Create new thread", sidebarHint: "Or pick an existing thread from the sidebar.", }; diff --git a/apps/web/src/components/settings/PermissionPoliciesSection.tsx b/apps/web/src/components/settings/PermissionPoliciesSection.tsx index 42bb94e3fde..28df1f16de6 100644 --- a/apps/web/src/components/settings/PermissionPoliciesSection.tsx +++ b/apps/web/src/components/settings/PermissionPoliciesSection.tsx @@ -136,7 +136,7 @@ export function PermissionPoliciesSection({
Current runtime approvals expose command, file-read, file-change, raw request type, and truncated detail text. If a provider starts emitting a new request type later, you can - target it with Request type terms even before CUT3 adds a dedicated label for it. + target it with Request type terms even before Rowl adds a dedicated label for it.
diff --git a/apps/web/src/composerDraftStore.ts b/apps/web/src/composerDraftStore.ts index b83b3fe1842..f5d604cd364 100644 --- a/apps/web/src/composerDraftStore.ts +++ b/apps/web/src/composerDraftStore.ts @@ -14,7 +14,7 @@ import { Debouncer } from "@tanstack/react-pacer"; import { create } from "zustand"; import { createJSONStorage, persist, type StateStorage } from "zustand/middleware"; -export const COMPOSER_DRAFT_STORAGE_KEY = "cut3:composer-drafts:v1"; +export const COMPOSER_DRAFT_STORAGE_KEY = "rowl:composer-drafts:v1"; export type DraftThreadEnvMode = "local" | "worktree"; const COMPOSER_PERSIST_DEBOUNCE_MS = 300; diff --git a/apps/web/src/editorPreferences.ts b/apps/web/src/editorPreferences.ts index 5054b1c990a..be065a81693 100644 --- a/apps/web/src/editorPreferences.ts +++ b/apps/web/src/editorPreferences.ts @@ -2,7 +2,7 @@ import { EDITORS, EditorId, NativeApi } from "@t3tools/contracts"; import { getLocalStorageItem, setLocalStorageItem, useLocalStorage } from "./hooks/useLocalStorage"; import { useMemo } from "react"; -const LAST_EDITOR_KEY = "cut3:last-editor"; +const LAST_EDITOR_KEY = "rowl:last-editor"; export function usePreferredEditor(availableEditors: ReadonlyArray) { const [lastEditor, setLastEditor] = useLocalStorage(LAST_EDITOR_KEY, null, EditorId); diff --git a/apps/web/src/hooks/useLocalStorage.ts b/apps/web/src/hooks/useLocalStorage.ts index 3c63ed0e375..de230838b18 100644 --- a/apps/web/src/hooks/useLocalStorage.ts +++ b/apps/web/src/hooks/useLocalStorage.ts @@ -39,7 +39,7 @@ export const removeLocalStorageItem = (key: string) => { isomorphicLocalStorage.removeItem(key); }; -const LOCAL_STORAGE_CHANGE_EVENT = "cut3:local_storage_change"; +const LOCAL_STORAGE_CHANGE_EVENT = "rowl:local_storage_change"; interface LocalStorageChangeDetail { key: string; diff --git a/apps/web/src/hooks/useTheme.ts b/apps/web/src/hooks/useTheme.ts index af95459635a..59011d554ee 100644 --- a/apps/web/src/hooks/useTheme.ts +++ b/apps/web/src/hooks/useTheme.ts @@ -20,7 +20,7 @@ type ThemeSnapshot = { customThemeId: CustomThemeId; }; -const STORAGE_KEY = "cut3:theme"; +const STORAGE_KEY = "rowl:theme"; const MEDIA_QUERY = "(prefers-color-scheme: dark)"; let listeners: Array<() => void> = []; diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 6e09e1adc4f..53584d05577 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -414,9 +414,9 @@ --sidebar-accent: var(--accent); --sidebar-accent-foreground: var(--accent-foreground); --sidebar-ring: var(--ring); - --cut3-sidebar-backdrop-filter: none; - --cut3-sidebar-surface: var(--sidebar); - --cut3-sidebar-surface-solid: var(--sidebar); + --rowl-sidebar-backdrop-filter: none; + --rowl-sidebar-surface: var(--sidebar); + --rowl-sidebar-surface-solid: var(--sidebar); @variant dark { color-scheme: dark; @@ -451,9 +451,9 @@ --sidebar-accent: var(--accent); --sidebar-accent-foreground: var(--accent-foreground); --sidebar-ring: var(--ring); - --cut3-sidebar-backdrop-filter: none; - --cut3-sidebar-surface: var(--sidebar); - --cut3-sidebar-surface-solid: var(--sidebar); + --rowl-sidebar-backdrop-filter: none; + --rowl-sidebar-surface: var(--sidebar); + --rowl-sidebar-surface-solid: var(--sidebar); } } @@ -856,12 +856,12 @@ button { [data-slot="sidebar-inner"], [data-slot="sidebar"][data-mobile="true"] { - backdrop-filter: var(--cut3-sidebar-backdrop-filter); + backdrop-filter: var(--rowl-sidebar-backdrop-filter); } html[data-sidebar-translucent="on"] [data-slot="sidebar-inner"], html[data-sidebar-translucent="on"] [data-slot="sidebar"][data-mobile="true"] { - background: var(--cut3-sidebar-surface) !important; + background: var(--rowl-sidebar-surface) !important; } @media (min-width: 48rem) { diff --git a/apps/web/src/lib/appearanceTheme.ts b/apps/web/src/lib/appearanceTheme.ts index 11009f5c631..d65e1784740 100644 --- a/apps/web/src/lib/appearanceTheme.ts +++ b/apps/web/src/lib/appearanceTheme.ts @@ -328,13 +328,13 @@ export function deriveAppearanceCssVariables( "--border": border, "--card": card, "--card-foreground": foreground, - "--cut3-sidebar-backdrop-filter": normalized.translucentSidebar + "--rowl-sidebar-backdrop-filter": normalized.translucentSidebar ? "blur(24px) saturate(1.12)" : "none", - "--cut3-sidebar-surface": normalized.translucentSidebar + "--rowl-sidebar-surface": normalized.translucentSidebar ? withAlpha(sidebarBase, appearance === "light" ? 0.78 : 0.72) : sidebarBase, - "--cut3-sidebar-surface-solid": sidebarBase, + "--rowl-sidebar-surface-solid": sidebarBase, "--foreground": foreground, "--font-code-snippet": normalized.codeFont, "--font-ui": normalized.uiFont, @@ -373,9 +373,9 @@ export function clearAppliedAppearanceCssVariables(): void { "--border", "--card", "--card-foreground", - "--cut3-sidebar-backdrop-filter", - "--cut3-sidebar-surface", - "--cut3-sidebar-surface-solid", + "--rowl-sidebar-backdrop-filter", + "--rowl-sidebar-surface", + "--rowl-sidebar-surface-solid", "--foreground", "--font-code-snippet", "--font-ui", @@ -426,16 +426,16 @@ export function applyGlobalAppearanceSettings(args: { normalizeFontStack(args.themeConfig.codeFont, DEFAULT_CODE_FONT), ); root.style.setProperty( - "--cut3-sidebar-backdrop-filter", + "--rowl-sidebar-backdrop-filter", args.themeConfig.translucentSidebar ? "blur(24px) saturate(1.12)" : "none", ); root.style.setProperty( - "--cut3-sidebar-surface", + "--rowl-sidebar-surface", args.themeConfig.translucentSidebar ? "color-mix(in srgb, var(--sidebar) 76%, transparent)" : "var(--sidebar)", ); - root.style.setProperty("--cut3-sidebar-surface-solid", "var(--sidebar)"); + root.style.setProperty("--rowl-sidebar-surface-solid", "var(--sidebar)"); if (args.customThemeEnabled) { return; diff --git a/apps/web/src/lib/chatBackgroundStorage.ts b/apps/web/src/lib/chatBackgroundStorage.ts index 676cfc3e5e5..3e112853ab8 100644 --- a/apps/web/src/lib/chatBackgroundStorage.ts +++ b/apps/web/src/lib/chatBackgroundStorage.ts @@ -1,4 +1,4 @@ -const DB_NAME = "cut3-assets"; +const DB_NAME = "rowl-assets"; const DB_VERSION = 1; const STORE_NAME = "chat-background-images"; diff --git a/apps/web/src/lib/openRouterModels.ts b/apps/web/src/lib/openRouterModels.ts index 5c6f8c6a8ef..54b15c980e7 100644 --- a/apps/web/src/lib/openRouterModels.ts +++ b/apps/web/src/lib/openRouterModels.ts @@ -30,7 +30,7 @@ export type OpenRouterFreeModelCatalog = readonly models: ReadonlyArray; }; -const OPENROUTER_FREE_MODEL_CACHE_STORAGE_KEY = "cut3:openrouter-free-models-cache:v1"; +const OPENROUTER_FREE_MODEL_CACHE_STORAGE_KEY = "rowl:openrouter-free-models-cache:v1"; export const OPENROUTER_FREE_ROUTER_OPTION: OpenRouterFreeModelOption = { slug: OPENROUTER_FREE_ROUTER_MODEL, diff --git a/apps/web/src/notifications.ts b/apps/web/src/notifications.ts index 71a026f471f..645ad9006ab 100644 --- a/apps/web/src/notifications.ts +++ b/apps/web/src/notifications.ts @@ -12,11 +12,11 @@ const NOTIFICATION_AUTO_CLOSE_MS = 8_000; function getNotificationCopy(language: string) { if (language === "fa") { return { - title: "CUT3 — کار تمام شد", + title: "Rowl — کار تمام شد", }; } return { - title: "CUT3 — Task Complete", + title: "Rowl — Task Complete", }; } diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index f69b5f2a2e9..0ff3e5325b2 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -112,7 +112,7 @@ function getSettingsCopy(language: AppLanguage) { blurDescription: "برای نرم تر شدن والپیپرهای پرجزئیات پشت پیام ها، تاری را بیشتر کنید.", resetImageEffects: "بازنشانی افکت های تصویر", imageStorageNote: (sizeLabel: string) => - `CUT3 این تصویر را در تنظیمات محلی همین دستگاه نگه می دارد. اندازه آن را حداکثر ${sizeLabel} نگه دارید.`, + `Rowl این تصویر را در تنظیمات محلی همین دستگاه نگه می دارد. اندازه آن را حداکثر ${sizeLabel} نگه دارید.`, chooseImageFile: "یک فایل تصویری انتخاب کنید.", imageTooLarge: (sizeLabel: string) => `یک تصویر حداکثر ${sizeLabel} انتخاب کنید تا به صورت محلی ذخیره شود.`, @@ -130,12 +130,12 @@ function getSettingsCopy(language: AppLanguage) { resetCodexOverrides: "بازنشانی بازنویسی های Codex", openRouterTitle: "OpenRouter", openRouterDescription: - "CUT3، OpenRouter را به صورت یک بخش مستقل در رابط نشان می دهد و این نشست ها را از پشت صحنه از طریق Codex اجرا می کند؛ بنابراین می توانید از openrouter/free یا مدل های ذخیره شده :free بدون تغییر پیکربندی عادی Codex استفاده کنید.", + "Rowl، OpenRouter را به صورت یک بخش مستقل در رابط نشان می دهد و این نشست ها را از پشت صحنه از طریق Codex اجرا می کند؛ بنابراین می توانید از openrouter/free یا مدل های ذخیره شده :free بدون تغییر پیکربندی عادی Codex استفاده کنید.", openRouterApiKey: "کلید API OpenRouter", openRouterKeyDescription: (electron: boolean) => electron - ? "فقط برای مدل های Codex که از OpenRouter عبور می کنند لازم است. CUT3 آن را در نشست دسکتاپ نگه می دارد و در صورت وجود ذخیره سازی امن، در مخزن اعتبار سیستم عامل ذخیره می کند. برای مجموعه مدل های رایگان فعلی از openrouter/free استفاده کنید یا در ادامه اسلاگ های :free مشخص را اضافه کنید." - : "فقط برای مدل های Codex که از OpenRouter عبور می کنند لازم است. CUT3 آن را فقط در حافظه نشست فعلی مرورگر نگه می دارد. برای مجموعه مدل های رایگان فعلی از openrouter/free استفاده کنید یا در ادامه اسلاگ های :free مشخص را اضافه کنید.", + ? "فقط برای مدل های Codex که از OpenRouter عبور می کنند لازم است. Rowl آن را در نشست دسکتاپ نگه می دارد و در صورت وجود ذخیره سازی امن، در مخزن اعتبار سیستم عامل ذخیره می کند. برای مجموعه مدل های رایگان فعلی از openrouter/free استفاده کنید یا در ادامه اسلاگ های :free مشخص را اضافه کنید." + : "فقط برای مدل های Codex که از OpenRouter عبور می کنند لازم است. Rowl آن را فقط در حافظه نشست فعلی مرورگر نگه می دارد. برای مجموعه مدل های رایگان فعلی از openrouter/free استفاده کنید یا در ادامه اسلاگ های :free مشخص را اضافه کنید.", openRouterConfigured: "کلید OpenRouter برای نشست های جدید Codex تنظیم شده است.", openRouterMissing: "برای استفاده از مدل های Codex مبتنی بر OpenRouter یک کلید اضافه کنید.", resetOpenRouterKey: "بازنشانی کلید OpenRouter", @@ -147,7 +147,7 @@ function getSettingsCopy(language: AppLanguage) { resetCopilotOverrides: "بازنشانی بازنویسی های Copilot", opencodeTitle: "OpenCode", opencodeDescription: - "برای نشست های جدید OpenCode اعمال می شود و به CUT3 اجازه می دهد از نصب غیرپیش فرض opencode استفاده کند. اعتبارنامه ها را با opencode auth login و opencode auth logout مدیریت کنید، و اگر پیکربندی OpenCode شما به OPENROUTER_API_KEY نیاز دارد کلید OpenRouter را در بخش بالایی CUT3 وارد کنید.", + "برای نشست های جدید OpenCode اعمال می شود و به Rowl اجازه می دهد از نصب غیرپیش فرض opencode استفاده کند. اعتبارنامه ها را با opencode auth login و opencode auth logout مدیریت کنید، و اگر پیکربندی OpenCode شما به OPENROUTER_API_KEY نیاز دارد کلید OpenRouter را در بخش بالایی Rowl وارد کنید.", opencodeBinaryPath: "مسیر باینری", leaveBlankOpencode: "برای استفاده از opencode از PATH این کادر را خالی بگذارید.", resetOpencodeOverrides: "بازنشانی بازنویسی ها", @@ -159,12 +159,12 @@ function getSettingsCopy(language: AppLanguage) { kimiApiKey: "کلید API Kimi", kimiApiDescription: (electron: boolean) => electron - ? "اگر می خواهید CUT3 نشست های Kimi را مستقیم شروع کند، این کلید را از Kimi Code Console بسازید. CUT3 آن را در نشست دسکتاپ نگه می دارد و در صورت وجود ذخیره سازی امن، در مخزن اعتبار سیستم عامل ذخیره می کند. اگر ترجیح می دهید از ورود محلی CLI استفاده کنید، این فیلد را خالی بگذارید و با kimi login یا /login وارد شوید." - : "اگر می خواهید CUT3 نشست های Kimi را مستقیم شروع کند، این کلید را از Kimi Code Console بسازید. CUT3 آن را فقط در حافظه نشست فعلی مرورگر نگه می دارد. اگر ترجیح می دهید از ورود محلی CLI استفاده کنید، این فیلد را خالی بگذارید و با kimi login یا /login وارد شوید.", + ? "اگر می خواهید Rowl نشست های Kimi را مستقیم شروع کند، این کلید را از Kimi Code Console بسازید. Rowl آن را در نشست دسکتاپ نگه می دارد و در صورت وجود ذخیره سازی امن، در مخزن اعتبار سیستم عامل ذخیره می کند. اگر ترجیح می دهید از ورود محلی CLI استفاده کنید، این فیلد را خالی بگذارید و با kimi login یا /login وارد شوید." + : "اگر می خواهید Rowl نشست های Kimi را مستقیم شروع کند، این کلید را از Kimi Code Console بسازید. Rowl آن را فقط در حافظه نشست فعلی مرورگر نگه می دارد. اگر ترجیح می دهید از ورود محلی CLI استفاده کنید، این فیلد را خالی بگذارید و با kimi login یا /login وارد شوید.", resetKimiOverrides: "بازنشانی بازنویسی های Kimi", piTitle: "Pi agent harness", piDescription: - "CUT3، Pi را از طریق SDK نود خودش داخل سرور تعبیه می کند. اعتبارنامه ها و کاتالوگ مدل Pi از ~/.pi/agent خوانده می شوند؛ برای ورود از خود Pi با pi یا bunx pi استفاده کنید و /login را اجرا کنید، یا auth.json / متغیرهای محیطی Pi را پر کنید. CUT3 عمداً پکیج ها، AGENTS، system promptها، افزونه ها، مهارت ها، پرامپت ها و تم های Pi را در این مسیر غیرفعال می کند تا دستورهای فضای کاری فقط از CUT3 بیایند.", + "Rowl، Pi را از طریق SDK نود خودش داخل سرور تعبیه می کند. اعتبارنامه ها و کاتالوگ مدل Pi از ~/.pi/agent خوانده می شوند؛ برای ورود از خود Pi با pi یا bunx pi استفاده کنید و /login را اجرا کنید، یا auth.json / متغیرهای محیطی Pi را پر کنید. Rowl عمداً پکیج ها، AGENTS، system promptها، افزونه ها، مهارت ها، پرامپت ها و تم های Pi را در این مسیر غیرفعال می کند تا دستورهای فضای کاری فقط از Rowl بیایند.", modelsTitle: "مدل ها", modelsDescription: "اسلاگ های مدل اضافی را ذخیره کنید تا در انتخابگر مدل گفتگو و پیشنهادهای دستور /model دیده شوند. مدل های رایگان OpenRouter اکنون بخش مستقل خودشان را دارند.", @@ -185,16 +185,16 @@ function getSettingsCopy(language: AppLanguage) { }, openRouterFreeModelsTitle: "مدل های رایگان OpenRouter", openRouterFreeModelsDescription: (routerSlug: string) => - `CUT3 کاتالوگ زنده OpenRouter را بررسی می کند و مدل هایی را نشان می دهد که همین حالا رایگان هستند. روتر داخلی ${routerSlug} همیشه در دسترس است و می توانید هر مدل رایگان زنده را ذخیره کنید تا در انتخابگر و پیشنهادهای /model ظاهر شود.`, + `Rowl کاتالوگ زنده OpenRouter را بررسی می کند و مدل هایی را نشان می دهد که همین حالا رایگان هستند. روتر داخلی ${routerSlug} همیشه در دسترس است و می توانید هر مدل رایگان زنده را ذخیره کنید تا در انتخابگر و پیشنهادهای /model ظاهر شود.`, refreshList: "نوسازی فهرست", openRouterChecking: "در حال بررسی OpenRouter برای فهرست فعلی مدل های رایگان...", openRouterAvailable: (count: number) => - `${count} مدل رایگان زنده OpenRouter در حال حاضر با مسیر بومی ابزار CUT3 سازگار ${count === 1 ? "است" : "هستند"}، به علاوه روتر داخلی.`, + `${count} مدل رایگان زنده OpenRouter در حال حاضر با مسیر بومی ابزار Rowl سازگار ${count === 1 ? "است" : "هستند"}، به علاوه روتر داخلی.`, openRouterCached: (count: number) => - `CUT3 آخرین کاتالوگ سالم OpenRouter را نشان می دهد (${count} مدل رایگان سازگار به علاوه روتر داخلی) چون واکشی زنده فعلاً در دسترس نیست.`, + `Rowl آخرین کاتالوگ سالم OpenRouter را نشان می دهد (${count} مدل رایگان سازگار به علاوه روتر داخلی) چون واکشی زنده فعلاً در دسترس نیست.`, openRouterUnavailable: "کشف زنده مدل های رایگان OpenRouter در حال حاضر در دسترس نیست.", openRouterFilteringNote: (routerSlug: string) => - `CUT3 فقط انتخاب هایی را نشان می دهد که روی :free یا ${routerSlug} قفل شده باشند و از ابزارها پشتیبانی کنند.`, + `Rowl فقط انتخاب هایی را نشان می دهد که روی :free یا ${routerSlug} قفل شده باشند و از ابزارها پشتیبانی کنند.`, lastCheckedAt: (label: string) => `آخرین بررسی در ${label}.`, builtIn: "داخلی", saved: "ذخیره شده", @@ -257,7 +257,7 @@ function getSettingsCopy(language: AppLanguage) { desktopNotifications: "اعلان‌های دسکتاپ", desktopNotificationsDescription: "وقتی agent کارش تمام شد و پنجره فعال نیست، یک اعلان دسکتاپ نشان بده.", - desktopNotificationsGranted: "اعلان‌های مرورگر برای CUT3 مجاز هستند.", + desktopNotificationsGranted: "اعلان‌های مرورگر برای Rowl مجاز هستند.", desktopNotificationsBlocked: "اعلان‌ها در تنظیمات مرورگر یا سایت مسدود شده‌اند. برای استفاده، دوباره آن‌ها را مجاز کنید.", desktopNotificationsUnsupported: "این محیط از اعلان‌های دسکتاپ پشتیبانی نمی‌کند.", @@ -282,11 +282,11 @@ function getSettingsCopy(language: AppLanguage) { modelTooLong: (maxLength: number) => `اسلاگ مدل باید حداکثر ${maxLength} کاراکتر باشد.`, customModelAlreadySaved: "این مدل سفارشی قبلا ذخیره شده است.", openRouterMustBeFree: - "شناسه های مدل OpenRouter باید از openrouter/free یا یک اسلاگ صریح :free استفاده کنند تا CUT3 ناخواسته به مدل پولی منتقل نشود.", + "شناسه های مدل OpenRouter باید از openrouter/free یا یک اسلاگ صریح :free استفاده کنند تا Rowl ناخواسته به مدل پولی منتقل نشود.", openRouterNotInCatalog: "این مدل OpenRouter در کاتالوگ زنده فعلی رایگان وجود ندارد. فهرست را نوسازی کنید و یک مدل :free فعلی انتخاب کنید.", openRouterNeedsTools: - "CUT3 به مدل های OpenRouter نیاز دارد که هم tools و هم tool_choice را اعلام کنند. یک مدل رایگان دیگر انتخاب کنید یا از openrouter/free استفاده کنید.", + "Rowl به مدل های OpenRouter نیاز دارد که هم tools و هم tool_choice را اعلام کنند. یک مدل رایگان دیگر انتخاب کنید یا از openrouter/free استفاده کنید.", noEditorsFound: "هیچ ویرایشگری در دسترس نیست.", openKeybindingsFailed: "باز کردن فایل کلیدهای میانبر ممکن نشد.", openRouterWarningMissingCatalog: "دیگر در کاتالوگ زنده رایگان فعلی OpenRouter دیده نمی شود.", @@ -315,7 +315,7 @@ function getSettingsCopy(language: AppLanguage) { blurDescription: "Increase blur to soften detailed wallpapers behind message content.", resetImageEffects: "Reset image effects", imageStorageNote: (sizeLabel: string) => - `CUT3 stores this image in local app settings on this device. Keep it at or under ${sizeLabel}.`, + `Rowl stores this image in local app settings on this device. Keep it at or under ${sizeLabel}.`, chooseImageFile: "Choose an image file.", imageTooLarge: (sizeLabel: string) => `Choose an image up to ${sizeLabel} so it can be saved locally.`, @@ -333,12 +333,12 @@ function getSettingsCopy(language: AppLanguage) { resetCodexOverrides: "Reset codex overrides", openRouterTitle: "OpenRouter", openRouterDescription: - "CUT3 exposes OpenRouter as its own top-level UI section and routes those sessions through Codex under the hood, so you can use the built-in openrouter/free router or saved OpenRouter :free model ids without editing your normal Codex config.", + "Rowl exposes OpenRouter as its own top-level UI section and routes those sessions through Codex under the hood, so you can use the built-in openrouter/free router or saved OpenRouter :free model ids without editing your normal Codex config.", openRouterApiKey: "OpenRouter API key", openRouterKeyDescription: (electron: boolean) => electron - ? "Needed only for Codex models routed through OpenRouter. CUT3 keeps it in the desktop session and persists it in your OS credential store when secure storage is available. Use openrouter/free for the current free-model pool, or add specific :free slugs below." - : "Needed only for Codex models routed through OpenRouter. CUT3 keeps it only in memory for the current browser session. Use openrouter/free for the current free-model pool, or add specific :free slugs below.", + ? "Needed only for Codex models routed through OpenRouter. Rowl keeps it in the desktop session and persists it in your OS credential store when secure storage is available. Use openrouter/free for the current free-model pool, or add specific :free slugs below." + : "Needed only for Codex models routed through OpenRouter. Rowl keeps it only in memory for the current browser session. Use openrouter/free for the current free-model pool, or add specific :free slugs below.", openRouterConfigured: "OpenRouter key is configured for new Codex sessions.", openRouterMissing: "Add a key to use OpenRouter-routed Codex models.", resetOpenRouterKey: "Reset OpenRouter key", @@ -350,24 +350,24 @@ function getSettingsCopy(language: AppLanguage) { resetCopilotOverrides: "Reset copilot overrides", opencodeTitle: "OpenCode", opencodeDescription: - "Applies to new OpenCode sessions and lets CUT3 use a non-default `opencode` install. Manage credentials with `opencode auth login` and `opencode auth logout`, and add the top-level OpenRouter key in CUT3 if your OpenCode config expects `OPENROUTER_API_KEY`.", + "Applies to new OpenCode sessions and lets Rowl use a non-default `opencode` install. Manage credentials with `opencode auth login` and `opencode auth logout`, and add the top-level OpenRouter key in Rowl if your OpenCode config expects `OPENROUTER_API_KEY`.", opencodeBinaryPath: "Binary path", leaveBlankOpencode: "Leave blank to use opencode from your PATH.", resetOpencodeOverrides: "Reset overrides", kimiTitle: "Kimi Code CLI", kimiDescription: - "These overrides apply to new Kimi Code sessions. Install with curl -LsSf https://code.kimi.com/install.sh | bash, then authenticate with `kimi login` or the in-shell `/login` flow, or add a Kimi Code API key here to let CUT3 start sessions directly.", + "These overrides apply to new Kimi Code sessions. Install with curl -LsSf https://code.kimi.com/install.sh | bash, then authenticate with `kimi login` or the in-shell `/login` flow, or add a Kimi Code API key here to let Rowl start sessions directly.", kimiBinaryPath: "Kimi binary path", leaveBlankKimi: "Leave blank to use kimi from your PATH.", kimiApiKey: "Kimi API key", kimiApiDescription: (electron: boolean) => electron - ? "Generate this from the Kimi Code Console if you want CUT3 to start Kimi sessions directly. CUT3 keeps it in the desktop session and persists it in your OS credential store when secure storage is available. Leave this blank if you prefer to authenticate in the local CLI with `kimi login` or `/login`." - : "Generate this from the Kimi Code Console if you want CUT3 to start Kimi sessions directly. CUT3 keeps it only in memory for the current browser session. Leave this blank if you prefer to authenticate in the local CLI with `kimi login` or `/login`.", + ? "Generate this from the Kimi Code Console if you want Rowl to start Kimi sessions directly. Rowl keeps it in the desktop session and persists it in your OS credential store when secure storage is available. Leave this blank if you prefer to authenticate in the local CLI with `kimi login` or `/login`." + : "Generate this from the Kimi Code Console if you want Rowl to start Kimi sessions directly. Rowl keeps it only in memory for the current browser session. Leave this blank if you prefer to authenticate in the local CLI with `kimi login` or `/login`.", resetKimiOverrides: "Reset kimi overrides", piTitle: "Pi agent harness", piDescription: - "CUT3 embeds Pi through its Node SDK. Pi credentials and model discovery come from ~/.pi/agent; authenticate Pi through the Pi CLI (`pi` or `bunx pi`) and `/login`, or populate Pi's auth.json / provider environment variables. CUT3 intentionally disables Pi packages, AGENTS files, system prompts, extensions, skills, prompt templates, and themes on this path so workspace instructions still come only from CUT3.", + "Rowl embeds Pi through its Node SDK. Pi credentials and model discovery come from ~/.pi/agent; authenticate Pi through the Pi CLI (`pi` or `bunx pi`) and `/login`, or populate Pi's auth.json / provider environment variables. Rowl intentionally disables Pi packages, AGENTS files, system prompts, extensions, skills, prompt templates, and themes on this path so workspace instructions still come only from Rowl.", modelsTitle: "Models", modelsDescription: "Save additional provider model slugs so they appear in the chat model picker and /model command suggestions. OpenRouter free models now have their own section, while the cards below handle additional provider-specific custom models.", @@ -388,16 +388,16 @@ function getSettingsCopy(language: AppLanguage) { }, openRouterFreeModelsTitle: "OpenRouter Free Models", openRouterFreeModelsDescription: (routerSlug: string) => - `CUT3 checks OpenRouter's live catalog and lists the models that are free right now. The built-in ${routerSlug} router is always available, and you can save any live free model below so it shows up in the picker and /model suggestions.`, + `Rowl checks OpenRouter's live catalog and lists the models that are free right now. The built-in ${routerSlug} router is always available, and you can save any live free model below so it shows up in the picker and /model suggestions.`, refreshList: "Refresh list", openRouterChecking: "Checking OpenRouter for the current free-model list...", openRouterAvailable: (count: number) => - `${count} live OpenRouter free model${count === 1 ? " is" : "s are"} currently compatible with CUT3's native tool-calling path, plus the built-in router.`, + `${count} live OpenRouter free model${count === 1 ? " is" : "s are"} currently compatible with Rowl's native tool-calling path, plus the built-in router.`, openRouterCached: (count: number) => - `CUT3 is showing the last known-good OpenRouter catalog (${count} compatible free model${count === 1 ? "" : "s"} plus the built-in router) because the live fetch is currently unavailable.`, + `Rowl is showing the last known-good OpenRouter catalog (${count} compatible free model${count === 1 ? "" : "s"} plus the built-in router) because the live fetch is currently unavailable.`, openRouterUnavailable: "Live OpenRouter free-model discovery is currently unavailable.", openRouterFilteringNote: (routerSlug: string) => - `CUT3 only lists OpenRouter picks that are locked to :free or ${routerSlug} and advertise tool use.`, + `Rowl only lists OpenRouter picks that are locked to :free or ${routerSlug} and advertise tool use.`, lastCheckedAt: (label: string) => `Last checked at ${label}.`, builtIn: "Built in", saved: "Saved", @@ -457,7 +457,7 @@ function getSettingsCopy(language: AppLanguage) { desktopNotifications: "Desktop notifications", desktopNotificationsDescription: "Show a desktop notification when the agent finishes a task and the window is not focused.", - desktopNotificationsGranted: "Browser notifications are allowed for CUT3.", + desktopNotificationsGranted: "Browser notifications are allowed for Rowl.", desktopNotificationsBlocked: "Notifications are blocked in your browser or site settings. Re-enable them there to use this feature.", desktopNotificationsUnsupported: "Desktop notifications are not supported in this environment.", @@ -483,11 +483,11 @@ function getSettingsCopy(language: AppLanguage) { modelTooLong: (maxLength: number) => `Model slugs must be ${maxLength} characters or less.`, customModelAlreadySaved: "That custom model is already saved.", openRouterMustBeFree: - "OpenRouter model ids must use openrouter/free or an explicit :free slug so CUT3 cannot drift onto a billed model.", + "OpenRouter model ids must use openrouter/free or an explicit :free slug so Rowl cannot drift onto a billed model.", openRouterNotInCatalog: "That OpenRouter model is not in the current live free catalog. Refresh the list and pick a currently free :free model.", openRouterNeedsTools: - "CUT3 requires OpenRouter models that advertise both tools and tool_choice. Pick another listed free model or use openrouter/free.", + "Rowl requires OpenRouter models that advertise both tools and tool_choice. Pick another listed free model or use openrouter/free.", noEditorsFound: "No available editors found.", openKeybindingsFailed: "Unable to open keybindings file.", openRouterWarningMissingCatalog: "No longer appears in OpenRouter's current live free catalog.", diff --git a/apps/web/src/sidebarPreferencesStore.ts b/apps/web/src/sidebarPreferencesStore.ts index bb10af20f62..44a85c56cc5 100644 --- a/apps/web/src/sidebarPreferencesStore.ts +++ b/apps/web/src/sidebarPreferencesStore.ts @@ -3,7 +3,7 @@ import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; import { filterArchivedIds, type SidebarProjectSortMode } from "./lib/threadOrdering"; -const SIDEBAR_PREFERENCES_STORAGE_KEY = "cut3:sidebar-preferences:v1"; +const SIDEBAR_PREFERENCES_STORAGE_KEY = "rowl:sidebar-preferences:v1"; interface PersistedSidebarPreferencesState { pinnedProjectIds: ProjectId[]; diff --git a/apps/web/src/store.ts b/apps/web/src/store.ts index 6ebb1d59964..c7021243fba 100644 --- a/apps/web/src/store.ts +++ b/apps/web/src/store.ts @@ -25,7 +25,7 @@ export interface AppState { threadsHydrated: boolean; } -const PERSISTED_STATE_KEY = "cut3:renderer-state:v8"; +const PERSISTED_STATE_KEY = "rowl:renderer-state:v8"; const LEGACY_PERSISTED_STATE_KEYS = [ "cut3:renderer-state:v7", "cut3:renderer-state:v6", diff --git a/apps/web/src/terminalStateStore.ts b/apps/web/src/terminalStateStore.ts index ef73d6c1368..ddaf4ab4cb1 100644 --- a/apps/web/src/terminalStateStore.ts +++ b/apps/web/src/terminalStateStore.ts @@ -25,7 +25,7 @@ interface ThreadTerminalState { activeTerminalGroupId: string; } -const TERMINAL_STATE_STORAGE_KEY = "cut3:terminal-state:v1"; +const TERMINAL_STATE_STORAGE_KEY = "rowl:terminal-state:v1"; function normalizeTerminalIds(terminalIds: string[]): string[] { const ids = [...new Set(terminalIds.map((id) => id.trim()).filter((id) => id.length > 0))]; diff --git a/bun.lock b/bun.lock index c9e20aedf8f..28c3ac26a3d 100644 --- a/bun.lock +++ b/bun.lock @@ -42,10 +42,10 @@ }, }, "apps/server": { - "name": "cut3", + "name": "rowl", "version": "1.0.1", "bin": { - "cut3": "./dist/index.mjs", + "rowl": "./dist/index.mjs", }, "dependencies": { "@agentclientprotocol/sdk": "^0.15.0", @@ -1260,8 +1260,6 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "cut3": ["cut3@workspace:apps/server"], - "data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], @@ -2022,6 +2020,8 @@ "rolldown-plugin-dts": ["rolldown-plugin-dts@0.22.5", "", { "dependencies": { "@babel/generator": "8.0.0-rc.2", "@babel/helper-validator-identifier": "8.0.0-rc.2", "@babel/parser": "8.0.0-rc.2", "@babel/types": "8.0.0-rc.2", "ast-kit": "^3.0.0-beta.1", "birpc": "^4.0.0", "dts-resolver": "^2.1.3", "get-tsconfig": "^4.13.6", "obug": "^2.1.1" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-rc.3", "typescript": "^5.0.0 || ^6.0.0-beta", "vue-tsc": "~3.2.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-M/HXfM4cboo+jONx9Z0X+CUf3B5tCi7ni+kR5fUW50Fp9AlZk0oVLesibGWgCXDKFp5lpgQ9yhKoImUFjl3VZw=="], + "rowl": ["rowl@workspace:apps/server"], + "run-applescript": ["run-applescript@7.1.0", "", {}, "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q=="], "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], diff --git a/package.json b/package.json index 2632c039cc5..e37ea7a1341 100644 --- a/package.json +++ b/package.json @@ -27,12 +27,12 @@ "dev:web": "node scripts/dev-runner.ts dev:web", "dev:marketing": "turbo run dev --filter=@t3tools/marketing", "dev:desktop": "node scripts/dev-runner.ts dev:desktop", - "start": "turbo run start --filter=cut3", + "start": "turbo run start --filter=rowl", "start:desktop": "turbo run start --filter=@t3tools/desktop", "start:marketing": "turbo run preview --filter=@t3tools/marketing", "build": "turbo run build", "build:marketing": "turbo run build --filter=@t3tools/marketing", - "build:desktop": "turbo run build --filter=@t3tools/desktop --filter=cut3", + "build:desktop": "turbo run build --filter=@t3tools/desktop --filter=rowl", "typecheck": "turbo run typecheck", "lint": "oxlint --report-unused-disable-directives", "test": "turbo run test", diff --git a/packages/shared/src/appRelease.ts b/packages/shared/src/appRelease.ts index 8bfdc5afa8b..0b1500f165d 100644 --- a/packages/shared/src/appRelease.ts +++ b/packages/shared/src/appRelease.ts @@ -1,9 +1,9 @@ const VERSION_PRERELEASE_PATTERN = /^\d+\.\d+\.\d+-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)$/; -const DEFAULT_DESKTOP_PRODUCT_NAME = "CUT3"; -const DEFAULT_DESKTOP_APP_ID = "com.t3tools.cut3"; -const DEFAULT_STAGE_LABEL = "CUT3"; -const DEFAULT_STATE_DIR_NAME = "cut3"; -const DEFAULT_USER_DATA_DIR_NAME = "cut3"; +const DEFAULT_DESKTOP_PRODUCT_NAME = "Rowl"; +const DEFAULT_DESKTOP_APP_ID = "com.t3tools.rowl"; +const DEFAULT_STAGE_LABEL = "Rowl"; +const DEFAULT_STATE_DIR_NAME = "rowl"; +const DEFAULT_USER_DATA_DIR_NAME = "rowl"; export interface AppReleaseBrandingInput { readonly version: string; @@ -11,7 +11,7 @@ export interface AppReleaseBrandingInput { } export interface AppReleaseBranding { - readonly stageLabel: "CUT3"; + readonly stageLabel: "Rowl"; readonly displayName: string; readonly productName: string; readonly appId: string; diff --git a/packages/shared/src/desktopBackend.ts b/packages/shared/src/desktopBackend.ts index 56b3670b69b..f297f7dfe45 100644 --- a/packages/shared/src/desktopBackend.ts +++ b/packages/shared/src/desktopBackend.ts @@ -1,4 +1,4 @@ -export const DESKTOP_BACKEND_READY_PREFIX = "[cut3-desktop-ready]"; +export const DESKTOP_BACKEND_READY_PREFIX = "[rowl-desktop-ready]"; export interface DesktopBackendReadyPayload { readonly port: number; diff --git a/packages/shared/src/shell.ts b/packages/shared/src/shell.ts index 08c3c9d0a20..4916ddc9e22 100644 --- a/packages/shared/src/shell.ts +++ b/packages/shared/src/shell.ts @@ -1,7 +1,7 @@ import { execFileSync } from "node:child_process"; -const PATH_CAPTURE_START = "__CUT3_PATH_START__"; -const PATH_CAPTURE_END = "__CUT3_PATH_END__"; +const PATH_CAPTURE_START = "__ROWL_PATH_START__"; +const PATH_CAPTURE_END = "__ROWL_PATH_END__"; const PATH_CAPTURE_COMMAND = [ `printf '%s\n' '${PATH_CAPTURE_START}'`, "printenv PATH", diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 5d696f9fff2..94a22fe1da8 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -169,7 +169,7 @@ interface StagePackageJson { readonly name: string; readonly version: string; readonly buildVersion: string; - readonly cut3CommitHash: string; + readonly rowlCommitHash: string; readonly private: true; readonly description: string; readonly author: string; @@ -196,15 +196,15 @@ const AzureTrustedSigningOptionsConfig = Config.all({ }); const BuildEnvConfig = Config.all({ - platform: Config.schema(BuildPlatform, "CUT3_DESKTOP_PLATFORM").pipe(Config.option), - target: Config.string("CUT3_DESKTOP_TARGET").pipe(Config.option), - arch: Config.schema(BuildArch, "CUT3_DESKTOP_ARCH").pipe(Config.option), - version: Config.string("CUT3_DESKTOP_VERSION").pipe(Config.option), - outputDir: Config.string("CUT3_DESKTOP_OUTPUT_DIR").pipe(Config.option), - skipBuild: Config.boolean("CUT3_DESKTOP_SKIP_BUILD").pipe(Config.withDefault(false)), - keepStage: Config.boolean("CUT3_DESKTOP_KEEP_STAGE").pipe(Config.withDefault(false)), - signed: Config.boolean("CUT3_DESKTOP_SIGNED").pipe(Config.withDefault(false)), - verbose: Config.boolean("CUT3_DESKTOP_VERBOSE").pipe(Config.withDefault(false)), + platform: Config.schema(BuildPlatform, "ROWL_DESKTOP_PLATFORM").pipe(Config.option), + target: Config.string("ROWL_DESKTOP_TARGET").pipe(Config.option), + arch: Config.schema(BuildArch, "ROWL_DESKTOP_ARCH").pipe(Config.option), + version: Config.string("ROWL_DESKTOP_VERSION").pipe(Config.option), + outputDir: Config.string("ROWL_DESKTOP_OUTPUT_DIR").pipe(Config.option), + skipBuild: Config.boolean("ROWL_DESKTOP_SKIP_BUILD").pipe(Config.withDefault(false)), + keepStage: Config.boolean("ROWL_DESKTOP_KEEP_STAGE").pipe(Config.withDefault(false)), + signed: Config.boolean("ROWL_DESKTOP_SIGNED").pipe(Config.withDefault(false)), + verbose: Config.boolean("ROWL_DESKTOP_VERBOSE").pipe(Config.withDefault(false)), }); const resolveBooleanFlag = (flag: Option.Option, envValue: boolean) => @@ -427,7 +427,7 @@ function resolveGitHubPublishConfig(): } | undefined { const rawRepo = - process.env.CUT3_DESKTOP_UPDATE_REPOSITORY?.trim() || + process.env.ROWL_DESKTOP_UPDATE_REPOSITORY?.trim() || process.env.GITHUB_REPOSITORY?.trim() || ""; if (!rawRepo) return undefined; @@ -452,10 +452,10 @@ const createBuildConfig = Effect.fn("createBuildConfig")(function* ( ) { const artifactName = platform === "mac" - ? "CUT3-macOS-${version}-${arch}.${ext}" + ? "Rowl-macOS-${version}-${arch}.${ext}" : platform === "linux" - ? "CUT3-linux-${version}-${arch}.${ext}" - : "CUT3-windows-${version}-${arch}.${ext}"; + ? "Rowl-linux-${version}-${arch}.${ext}" + : "Rowl-windows-${version}-${arch}.${ext}"; const buildConfig: Record = { appId, productName, @@ -637,12 +637,12 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( yield* fs.copy(stageResourcesDir, path.join(stageAppDir, "apps/desktop/prod-resources")); const stagePackageJson: StagePackageJson = { - name: "cut3-desktop", + name: "rowl-desktop", version: appVersion, buildVersion: appVersion, - cut3CommitHash: commitHash, + rowlCommitHash: commitHash, private: true, - description: "CUT3 desktop build", + description: "Rowl desktop build", author: "T3 Tools", main: "apps/desktop/dist-electron/main.js", build: yield* createBuildConfig( @@ -748,49 +748,49 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( const buildDesktopArtifactCli = Command.make("build-desktop-artifact", { platform: Flag.choice("platform", BuildPlatform.literals).pipe( - Flag.withDescription("Build platform (env: CUT3_DESKTOP_PLATFORM)."), + Flag.withDescription("Build platform (env: ROWL_DESKTOP_PLATFORM)."), Flag.optional, ), target: Flag.string("target").pipe( Flag.withDescription( - "Artifact target, for example dmg/AppImage/nsis (env: CUT3_DESKTOP_TARGET).", + "Artifact target, for example dmg/AppImage/nsis (env: ROWL_DESKTOP_TARGET).", ), Flag.optional, ), arch: Flag.choice("arch", BuildArch.literals).pipe( - Flag.withDescription("Build arch, for example arm64/x64/universal (env: CUT3_DESKTOP_ARCH)."), + Flag.withDescription("Build arch, for example arm64/x64/universal (env: ROWL_DESKTOP_ARCH)."), Flag.optional, ), buildVersion: Flag.string("build-version").pipe( - Flag.withDescription("Artifact version metadata (env: CUT3_DESKTOP_VERSION)."), + Flag.withDescription("Artifact version metadata (env: ROWL_DESKTOP_VERSION)."), Flag.optional, ), outputDir: Flag.string("output-dir").pipe( - Flag.withDescription("Output directory for artifacts (env: CUT3_DESKTOP_OUTPUT_DIR)."), + Flag.withDescription("Output directory for artifacts (env: ROWL_DESKTOP_OUTPUT_DIR)."), Flag.optional, ), skipBuild: Flag.boolean("skip-build").pipe( Flag.withDescription( - "Skip `bun run build:desktop` and use existing dist artifacts (env: CUT3_DESKTOP_SKIP_BUILD).", + "Skip `bun run build:desktop` and use existing dist artifacts (env: ROWL_DESKTOP_SKIP_BUILD).", ), Flag.optional, ), keepStage: Flag.boolean("keep-stage").pipe( - Flag.withDescription("Keep temporary staging files (env: CUT3_DESKTOP_KEEP_STAGE)."), + Flag.withDescription("Keep temporary staging files (env: ROWL_DESKTOP_KEEP_STAGE)."), Flag.optional, ), signed: Flag.boolean("signed").pipe( Flag.withDescription( - "Enable signing/notarization discovery; Windows uses Azure Trusted Signing (env: CUT3_DESKTOP_SIGNED).", + "Enable signing/notarization discovery; Windows uses Azure Trusted Signing (env: ROWL_DESKTOP_SIGNED).", ), Flag.optional, ), verbose: Flag.boolean("verbose").pipe( - Flag.withDescription("Stream subprocess stdout (env: CUT3_DESKTOP_VERBOSE)."), + Flag.withDescription("Stream subprocess stdout (env: ROWL_DESKTOP_VERBOSE)."), Flag.optional, ), }).pipe( - Command.withDescription("Build a desktop artifact for CUT3."), + Command.withDescription("Build a desktop artifact for Rowl."), Command.withHandler((input) => Effect.flatMap(resolveBuildOptions(input), buildDesktopArtifact)), ); diff --git a/scripts/create-release-checksums.test.ts b/scripts/create-release-checksums.test.ts index e86c76a0754..a45aafd10d7 100644 --- a/scripts/create-release-checksums.test.ts +++ b/scripts/create-release-checksums.test.ts @@ -12,25 +12,25 @@ import { describe("create-release-checksums", () => { it("writes stable SHA256SUMS entries for release assets", () => { - const rootDir = mkdtempSync(join(tmpdir(), "cut3-release-checksums-")); + const rootDir = mkdtempSync(join(tmpdir(), "rowl-release-checksums-")); try { - writeFileSync(resolve(rootDir, "CUT3-linux-1.0.0-x86_64.AppImage"), "linux-app"); + writeFileSync(resolve(rootDir, "Rowl-linux-1.0.0-x86_64.AppImage"), "linux-app"); writeFileSync(resolve(rootDir, "latest.yml"), "channel-manifest"); mkdirSync(resolve(rootDir, "nested"), { recursive: true }); - writeFileSync(resolve(rootDir, "nested", "CUT3-macOS-1.0.0-arm64.zip"), "mac-zip"); + writeFileSync(resolve(rootDir, "nested", "Rowl-macOS-1.0.0-arm64.zip"), "mac-zip"); writeFileSync(resolve(rootDir, "SHA256SUMS"), "stale-manifest"); const entries = createReleaseChecksums(rootDir); assert.deepStrictEqual( entries.map((entry) => entry.path), - ["CUT3-linux-1.0.0-x86_64.AppImage", "latest.yml", "nested/CUT3-macOS-1.0.0-arm64.zip"], + ["latest.yml", "nested/Rowl-macOS-1.0.0-arm64.zip", "Rowl-linux-1.0.0-x86_64.AppImage"], ); const serialized = serializeReleaseChecksums(entries); assert.equal(serialized.split("\n").length, 3); - assert.ok(serialized.includes("CUT3-linux-1.0.0-x86_64.AppImage")); - assert.ok(serialized.includes("nested/CUT3-macOS-1.0.0-arm64.zip")); + assert.ok(serialized.includes("Rowl-linux-1.0.0-x86_64.AppImage")); + assert.ok(serialized.includes("nested/Rowl-macOS-1.0.0-arm64.zip")); const outputPath = writeReleaseChecksums(rootDir); const written = readFileSync(outputPath, "utf8"); diff --git a/scripts/dev-runner.test.ts b/scripts/dev-runner.test.ts index a4432235d9a..507c7845d66 100644 --- a/scripts/dev-runner.test.ts +++ b/scripts/dev-runner.test.ts @@ -13,12 +13,12 @@ import { it.layer(NodeServices.layer)("dev-runner", (it) => { describe("resolveOffset", () => { - it.effect("uses explicit CUT3_PORT_OFFSET when provided", () => + it.effect("uses explicit ROWL_PORT_OFFSET when provided", () => Effect.sync(() => { const result = resolveOffset({ portOffset: 12, devInstance: undefined }); assert.deepStrictEqual(result, { offset: 12, - source: "CUT3_PORT_OFFSET=12", + source: "ROWL_PORT_OFFSET=12", }); }), ); @@ -40,7 +40,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { }), ); - assert.ok(error.includes("Invalid CUT3_PORT_OFFSET")); + assert.ok(error.includes("Invalid ROWL_PORT_OFFSET")); }), ); }); @@ -66,7 +66,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { DEFAULT_DEV_STATE_DIR, ]); - assert.equal(env.CUT3_STATE_DIR, defaultStateDir); + assert.equal(env.ROWL_STATE_DIR, defaultStateDir); }), ); @@ -87,13 +87,13 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { devUrl: new URL("http://localhost:7331"), }); - assert.equal(env.CUT3_STATE_DIR, resolve("/tmp/override-state")); - assert.equal(env.CUT3_PORT, "4222"); + assert.equal(env.ROWL_STATE_DIR, resolve("/tmp/override-state")); + assert.equal(env.ROWL_PORT, "4222"); assert.equal(env.VITE_WS_URL, "ws://127.0.0.1:4222/?token=secret"); - assert.equal(env.CUT3_NO_BROWSER, "1"); - assert.equal(env.CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD, "0"); - assert.equal(env.CUT3_LOG_WS_EVENTS, "1"); - assert.equal(env.CUT3_HOST, "0.0.0.0"); + assert.equal(env.ROWL_NO_BROWSER, "1"); + assert.equal(env.ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD, "0"); + assert.equal(env.ROWL_LOG_WS_EVENTS, "1"); + assert.equal(env.ROWL_HOST, "0.0.0.0"); assert.equal(env.VITE_DEV_SERVER_URL, "http://localhost:7331/"); }), ); @@ -103,7 +103,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { const env = yield* createDevRunnerEnv({ mode: "dev", baseEnv: { - CUT3_LOG_WS_EVENTS: "keep-me-out", + ROWL_LOG_WS_EVENTS: "keep-me-out", }, serverOffset: 0, webOffset: 0, @@ -117,8 +117,8 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { devUrl: undefined, }); - assert.equal(env.CUT3_MODE, "web"); - assert.equal(env.CUT3_LOG_WS_EVENTS, undefined); + assert.equal(env.ROWL_MODE, "web"); + assert.equal(env.ROWL_LOG_WS_EVENTS, undefined); }), ); @@ -139,7 +139,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { devUrl: undefined, }); - assert.equal(env.CUT3_LOG_WS_EVENTS, "0"); + assert.equal(env.ROWL_LOG_WS_EVENTS, "0"); }), ); }); diff --git a/scripts/dev-runner.ts b/scripts/dev-runner.ts index 4a6a2920248..5a33a206ed1 100644 --- a/scripts/dev-runner.ts +++ b/scripts/dev-runner.ts @@ -30,10 +30,10 @@ const MODE_ARGS = { "--ui=tui", "--filter=@t3tools/contracts", "--filter=@t3tools/web", - "--filter=cut3", + "--filter=rowl", "--parallel", ], - "dev:server": ["run", "dev", "--filter=cut3"], + "dev:server": ["run", "dev", "--filter=rowl"], "dev:web": ["run", "dev", "--filter=@t3tools/web"], "dev:desktop": ["run", "dev", "--filter=@t3tools/desktop", "--filter=@t3tools/web", "--parallel"], } as const satisfies Record>; @@ -86,8 +86,8 @@ const optionalUrlConfig = (name: string): Config.Config => ); const OffsetConfig = Config.all({ - portOffset: optionalIntegerConfig("CUT3_PORT_OFFSET"), - devInstance: optionalStringConfig("CUT3_DEV_INSTANCE"), + portOffset: optionalIntegerConfig("ROWL_PORT_OFFSET"), + devInstance: optionalStringConfig("ROWL_DEV_INSTANCE"), }); export function resolveOffset(config: { @@ -96,11 +96,11 @@ export function resolveOffset(config: { }): { readonly offset: number; readonly source: string } { if (config.portOffset !== undefined) { if (config.portOffset < 0) { - throw new Error(`Invalid CUT3_PORT_OFFSET: ${config.portOffset}`); + throw new Error(`Invalid ROWL_PORT_OFFSET: ${config.portOffset}`); } return { offset: config.portOffset, - source: `CUT3_PORT_OFFSET=${config.portOffset}`, + source: `ROWL_PORT_OFFSET=${config.portOffset}`, }; } @@ -110,11 +110,11 @@ export function resolveOffset(config: { } if (/^\d+$/.test(seed)) { - return { offset: Number(seed), source: `numeric CUT3_DEV_INSTANCE=${seed}` }; + return { offset: Number(seed), source: `numeric ROWL_DEV_INSTANCE=${seed}` }; } const offset = ((Hash.string(seed) >>> 0) % MAX_HASH_OFFSET) + 1; - return { offset, source: `hashed CUT3_DEV_INSTANCE=${seed}` }; + return { offset, source: `hashed ROWL_DEV_INSTANCE=${seed}` }; } function resolveStateDir(stateDir: string | undefined): Effect.Effect { @@ -288,50 +288,50 @@ export function createDevRunnerEnv({ const output: NodeJS.ProcessEnv = { ...baseEnv, - CUT3_PORT: String(serverPort), + ROWL_PORT: String(serverPort), PORT: String(webPort), ELECTRON_RENDERER_PORT: String(webPort), VITE_WS_URL: withWsAuthToken(`ws://${urlHost}:${serverPort}`, authToken), VITE_DEV_SERVER_URL: devUrl?.toString() ?? `http://${urlHost}:${webPort}`, - CUT3_STATE_DIR: resolvedStateDir, + ROWL_STATE_DIR: resolvedStateDir, }; if (host !== undefined) { - output.CUT3_HOST = host; + output.ROWL_HOST = host; } if (authToken !== undefined) { - output.CUT3_AUTH_TOKEN = authToken; + output.ROWL_AUTH_TOKEN = authToken; } else { - delete output.CUT3_AUTH_TOKEN; + delete output.ROWL_AUTH_TOKEN; } if (noBrowser !== undefined) { - output.CUT3_NO_BROWSER = noBrowser ? "1" : "0"; + output.ROWL_NO_BROWSER = noBrowser ? "1" : "0"; } else { - delete output.CUT3_NO_BROWSER; + delete output.ROWL_NO_BROWSER; } if (autoBootstrapProjectFromCwd !== undefined) { - output.CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD = autoBootstrapProjectFromCwd ? "1" : "0"; + output.ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD = autoBootstrapProjectFromCwd ? "1" : "0"; } else { - delete output.CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD; + delete output.ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD; } if (logWebSocketEvents !== undefined) { - output.CUT3_LOG_WS_EVENTS = logWebSocketEvents ? "1" : "0"; + output.ROWL_LOG_WS_EVENTS = logWebSocketEvents ? "1" : "0"; } else { - delete output.CUT3_LOG_WS_EVENTS; + delete output.ROWL_LOG_WS_EVENTS; } if (mode === "dev") { - output.CUT3_MODE = "web"; - delete output.CUT3_DESKTOP_WS_URL; + output.ROWL_MODE = "web"; + delete output.ROWL_DESKTOP_WS_URL; } if (mode === "dev:server" || mode === "dev:web") { - output.CUT3_MODE = "web"; - delete output.CUT3_DESKTOP_WS_URL; + output.ROWL_MODE = "web"; + delete output.ROWL_DESKTOP_WS_URL; } return output; @@ -525,7 +525,7 @@ export function runDevRunnerWithInput(input: DevRunnerCliInput) { Effect.mapError( (cause) => new DevRunnerError({ - message: "Failed to read CUT3_PORT_OFFSET/CUT3_DEV_INSTANCE configuration.", + message: "Failed to read ROWL_PORT_OFFSET/ROWL_DEV_INSTANCE configuration.", cause, }), ), @@ -541,9 +541,9 @@ export function runDevRunnerWithInput(input: DevRunnerCliInput) { }); const envOverrides = { - noBrowser: readOptionalBooleanEnv("CUT3_NO_BROWSER"), - autoBootstrapProjectFromCwd: readOptionalBooleanEnv("CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD"), - logWebSocketEvents: readOptionalBooleanEnv("CUT3_LOG_WS_EVENTS"), + noBrowser: readOptionalBooleanEnv("ROWL_NO_BROWSER"), + autoBootstrapProjectFromCwd: readOptionalBooleanEnv("ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD"), + logWebSocketEvents: readOptionalBooleanEnv("ROWL_LOG_WS_EVENTS"), }; const resolvedStateDir = yield* resolveStateDir(input.stateDir); @@ -609,7 +609,7 @@ export function runDevRunnerWithInput(input: DevRunnerCliInput) { : ""; yield* Effect.logInfo( - `[dev-runner] mode=${input.mode} source=${source}${selectionSuffix} serverPort=${String(env.CUT3_PORT)} webPort=${String(env.PORT)} stateDir=${String(env.CUT3_STATE_DIR)}`, + `[dev-runner] mode=${input.mode} source=${source}${selectionSuffix} serverPort=${String(env.ROWL_PORT)} webPort=${String(env.PORT)} stateDir=${String(env.ROWL_STATE_DIR)}`, ); if (input.dryRun) { @@ -658,37 +658,37 @@ const devRunnerCli = Command.make("dev-runner", { Argument.withDescription("Development mode to run."), ), stateDir: Flag.string("state-dir").pipe( - Flag.withDescription("State directory path (forwards to CUT3_STATE_DIR)."), - Flag.withFallbackConfig(optionalStringConfig("CUT3_STATE_DIR")), + Flag.withDescription("State directory path (forwards to ROWL_STATE_DIR)."), + Flag.withFallbackConfig(optionalStringConfig("ROWL_STATE_DIR")), ), authToken: Flag.string("auth-token").pipe( - Flag.withDescription("Auth token (forwards to CUT3_AUTH_TOKEN)."), + Flag.withDescription("Auth token (forwards to ROWL_AUTH_TOKEN)."), Flag.withAlias("token"), - Flag.withFallbackConfig(optionalStringConfig("CUT3_AUTH_TOKEN")), + Flag.withFallbackConfig(optionalStringConfig("ROWL_AUTH_TOKEN")), ), noBrowser: Flag.boolean("no-browser").pipe( - Flag.withDescription("Browser auto-open toggle (equivalent to CUT3_NO_BROWSER)."), - Flag.withFallbackConfig(optionalBooleanConfig("CUT3_NO_BROWSER")), + Flag.withDescription("Browser auto-open toggle (equivalent to ROWL_NO_BROWSER)."), + Flag.withFallbackConfig(optionalBooleanConfig("ROWL_NO_BROWSER")), ), autoBootstrapProjectFromCwd: Flag.boolean("auto-bootstrap-project-from-cwd").pipe( Flag.withDescription( - "Auto-bootstrap toggle (equivalent to CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD).", + "Auto-bootstrap toggle (equivalent to ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD).", ), - Flag.withFallbackConfig(optionalBooleanConfig("CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD")), + Flag.withFallbackConfig(optionalBooleanConfig("ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD")), ), logWebSocketEvents: Flag.boolean("log-websocket-events").pipe( - Flag.withDescription("WebSocket event logging toggle (equivalent to CUT3_LOG_WS_EVENTS)."), + Flag.withDescription("WebSocket event logging toggle (equivalent to ROWL_LOG_WS_EVENTS)."), Flag.withAlias("log-ws-events"), - Flag.withFallbackConfig(optionalBooleanConfig("CUT3_LOG_WS_EVENTS")), + Flag.withFallbackConfig(optionalBooleanConfig("ROWL_LOG_WS_EVENTS")), ), host: Flag.string("host").pipe( - Flag.withDescription("Server host/interface override (forwards to CUT3_HOST)."), - Flag.withFallbackConfig(optionalStringConfig("CUT3_HOST")), + Flag.withDescription("Server host/interface override (forwards to ROWL_HOST)."), + Flag.withFallbackConfig(optionalStringConfig("ROWL_HOST")), ), port: Flag.integer("port").pipe( Flag.withSchema(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 65535 }))), - Flag.withDescription("Server port override (forwards to CUT3_PORT)."), - Flag.withFallbackConfig(optionalPortConfig("CUT3_PORT")), + Flag.withDescription("Server port override (forwards to ROWL_PORT)."), + Flag.withFallbackConfig(optionalPortConfig("ROWL_PORT")), ), devUrl: Flag.string("dev-url").pipe( Flag.withSchema(Schema.URLFromString), diff --git a/scripts/merge-mac-update-manifests.compat.test.ts b/scripts/merge-mac-update-manifests.compat.test.ts index a891f92532a..9af508b4359 100644 --- a/scripts/merge-mac-update-manifests.compat.test.ts +++ b/scripts/merge-mac-update-manifests.compat.test.ts @@ -12,13 +12,13 @@ function makeMergedMacManifestYaml(): string { const arm64 = parseMacUpdateManifest( `version: 0.0.12-fork.2 files: - - url: CUT3-macOS-0.0.12-fork.2-arm64.zip + - url: Rowl-macOS-0.0.12-fork.2-arm64.zip sha512: arm64zip size: 10 - - url: CUT3-macOS-0.0.12-fork.2-arm64.dmg + - url: Rowl-macOS-0.0.12-fork.2-arm64.dmg sha512: arm64dmg size: 11 -path: CUT3-macOS-0.0.12-fork.2-arm64.zip +path: Rowl-macOS-0.0.12-fork.2-arm64.zip sha512: arm64zip releaseDate: '2026-03-12T23:30:00.000Z' `, @@ -28,13 +28,13 @@ releaseDate: '2026-03-12T23:30:00.000Z' const x64 = parseMacUpdateManifest( `version: 0.0.12-fork.2 files: - - url: CUT3-macOS-0.0.12-fork.2-x64.zip + - url: Rowl-macOS-0.0.12-fork.2-x64.zip sha512: x64zip size: 12 - - url: CUT3-macOS-0.0.12-fork.2-x64.dmg + - url: Rowl-macOS-0.0.12-fork.2-x64.dmg sha512: x64dmg size: 13 -path: CUT3-macOS-0.0.12-fork.2-x64.zip +path: Rowl-macOS-0.0.12-fork.2-x64.zip sha512: x64zip releaseDate: '2026-03-12T23:31:00.000Z' `, @@ -151,18 +151,18 @@ describe("merge-mac-update-manifests electron-updater compatibility", () => { it("selects the x64 zip for x64 macOS hosts", async () => { await expect( selectMacUpdateFile({ uname: "Darwin x86_64 Apple Kernel Version", rosetta: false }), - ).resolves.toContain("CUT3-macOS-0.0.12-fork.2-x64.zip"); + ).resolves.toContain("Rowl-macOS-0.0.12-fork.2-x64.zip"); }); it("selects the arm64 zip for native arm64 macOS hosts", async () => { await expect( selectMacUpdateFile({ uname: "Darwin ARM64 Apple Kernel Version", rosetta: false }), - ).resolves.toContain("CUT3-macOS-0.0.12-fork.2-arm64.zip"); + ).resolves.toContain("Rowl-macOS-0.0.12-fork.2-arm64.zip"); }); it("selects the arm64 zip for Rosetta-translated macOS hosts", async () => { await expect( selectMacUpdateFile({ uname: "Darwin x86_64 Apple Kernel Version", rosetta: true }), - ).resolves.toContain("CUT3-macOS-0.0.12-fork.2-arm64.zip"); + ).resolves.toContain("Rowl-macOS-0.0.12-fork.2-arm64.zip"); }); }); diff --git a/scripts/merge-mac-update-manifests.test.ts b/scripts/merge-mac-update-manifests.test.ts index 71a5545cf9a..925d2ccb866 100644 --- a/scripts/merge-mac-update-manifests.test.ts +++ b/scripts/merge-mac-update-manifests.test.ts @@ -11,13 +11,13 @@ describe("merge-mac-update-manifests", () => { const arm64 = parseMacUpdateManifest( `version: 0.0.4 files: - - url: CUT3-macOS-0.0.4-arm64.zip + - url: Rowl-macOS-0.0.4-arm64.zip sha512: arm64zip size: 125621344 - - url: CUT3-macOS-0.0.4-arm64.dmg + - url: Rowl-macOS-0.0.4-arm64.dmg sha512: arm64dmg size: 131754935 -path: CUT3-macOS-0.0.4-arm64.zip +path: Rowl-macOS-0.0.4-arm64.zip sha512: arm64zip releaseDate: '2026-03-07T10:32:14.587Z' `, @@ -27,13 +27,13 @@ releaseDate: '2026-03-07T10:32:14.587Z' const x64 = parseMacUpdateManifest( `version: 0.0.4 files: - - url: CUT3-macOS-0.0.4-x64.zip + - url: Rowl-macOS-0.0.4-x64.zip sha512: x64zip size: 132000112 - - url: CUT3-macOS-0.0.4-x64.dmg + - url: Rowl-macOS-0.0.4-x64.dmg sha512: x64dmg size: 138148807 -path: CUT3-macOS-0.0.4-x64.zip +path: Rowl-macOS-0.0.4-x64.zip sha512: x64zip releaseDate: '2026-03-07T10:36:07.540Z' `, @@ -47,10 +47,10 @@ releaseDate: '2026-03-07T10:36:07.540Z' assert.deepStrictEqual( merged.files.map((file) => file.url), [ - "CUT3-macOS-0.0.4-arm64.zip", - "CUT3-macOS-0.0.4-arm64.dmg", - "CUT3-macOS-0.0.4-x64.zip", - "CUT3-macOS-0.0.4-x64.dmg", + "Rowl-macOS-0.0.4-arm64.zip", + "Rowl-macOS-0.0.4-arm64.dmg", + "Rowl-macOS-0.0.4-x64.zip", + "Rowl-macOS-0.0.4-x64.dmg", ], ); @@ -63,7 +63,7 @@ releaseDate: '2026-03-07T10:36:07.540Z' const arm64 = parseMacUpdateManifest( `version: 0.0.4 files: - - url: CUT3-macOS-0.0.4-arm64.zip + - url: Rowl-macOS-0.0.4-arm64.zip sha512: arm64zip size: 1 releaseDate: '2026-03-07T10:32:14.587Z' @@ -74,7 +74,7 @@ releaseDate: '2026-03-07T10:32:14.587Z' const x64 = parseMacUpdateManifest( `version: 0.0.5 files: - - url: CUT3-macOS-0.0.5-x64.zip + - url: Rowl-macOS-0.0.5-x64.zip sha512: x64zip size: 1 releaseDate: '2026-03-07T10:36:07.540Z' @@ -89,7 +89,7 @@ releaseDate: '2026-03-07T10:36:07.540Z' const manifest = parseMacUpdateManifest( `version: '1.0' files: - - url: CUT3-macOS-1.0-x64.zip + - url: Rowl-macOS-1.0-x64.zip sha512: zipsha size: 1 releaseName: 'true' diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts index 21fa74d1b41..8e54aee3b12 100644 --- a/scripts/release-smoke.ts +++ b/scripts/release-smoke.ts @@ -38,13 +38,13 @@ function writeMacManifestFixtures(targetRoot: string): { arm64Path: string; x64P arm64Path, `version: 9.9.9-smoke.0 files: - - url: CUT3-macOS-9.9.9-smoke.0-arm64.zip + - url: Rowl-macOS-9.9.9-smoke.0-arm64.zip sha512: arm64zip size: 125621344 - - url: CUT3-macOS-9.9.9-smoke.0-arm64.dmg + - url: Rowl-macOS-9.9.9-smoke.0-arm64.dmg sha512: arm64dmg size: 131754935 -path: CUT3-macOS-9.9.9-smoke.0-arm64.zip +path: Rowl-macOS-9.9.9-smoke.0-arm64.zip sha512: arm64zip releaseDate: '2026-03-08T10:32:14.587Z' `, @@ -54,13 +54,13 @@ releaseDate: '2026-03-08T10:32:14.587Z' x64Path, `version: 9.9.9-smoke.0 files: - - url: CUT3-macOS-9.9.9-smoke.0-x64.zip + - url: Rowl-macOS-9.9.9-smoke.0-x64.zip sha512: x64zip size: 132000112 - - url: CUT3-macOS-9.9.9-smoke.0-x64.dmg + - url: Rowl-macOS-9.9.9-smoke.0-x64.dmg sha512: x64dmg size: 138148807 -path: CUT3-macOS-9.9.9-smoke.0-x64.zip +path: Rowl-macOS-9.9.9-smoke.0-x64.zip sha512: x64zip releaseDate: '2026-03-08T10:36:07.540Z' `, @@ -119,19 +119,19 @@ try { const mergedManifest = readFileSync(arm64Path, "utf8"); assertContains( mergedManifest, - "CUT3-macOS-9.9.9-smoke.0-arm64.zip", + "Rowl-macOS-9.9.9-smoke.0-arm64.zip", "Merged manifest is missing the arm64 asset.", ); assertContains( mergedManifest, - "CUT3-macOS-9.9.9-smoke.0-x64.zip", + "Rowl-macOS-9.9.9-smoke.0-x64.zip", "Merged manifest is missing the x64 asset.", ); const checksumAssetPath = resolve( tempRoot, "release-assets", - "CUT3-linux-9.9.9-smoke.0-x86_64.AppImage", + "Rowl-linux-9.9.9-smoke.0-x86_64.AppImage", ); writeFileSync(checksumAssetPath, "linux-release-asset"); execFileSync( @@ -151,7 +151,7 @@ try { const checksumManifest = readFileSync(resolve(tempRoot, "release-assets", "SHA256SUMS"), "utf8"); assertContains( checksumManifest, - "CUT3-linux-9.9.9-smoke.0-x86_64.AppImage", + "Rowl-linux-9.9.9-smoke.0-x86_64.AppImage", "Expected SHA256SUMS to include the Linux release asset.", ); assertContains( diff --git a/turbo.json b/turbo.json index d248e9f44bf..f96f9be370f 100644 --- a/turbo.json +++ b/turbo.json @@ -5,15 +5,15 @@ "VITE_WS_URL", "VITE_DEV_SERVER_URL", "ELECTRON_RENDERER_PORT", - "CUT3_LOG_WS_EVENTS", - "CUT3_MODE", - "CUT3_PORT", - "CUT3_NO_BROWSER", - "CUT3_STATE_DIR", - "CUT3_AUTH_TOKEN", - "CUT3_DESKTOP_WS_URL", - "CUT3_HOST", - "CUT3_AUTO_BOOTSTRAP_PROJECT_FROM_CWD", + "ROWL_LOG_WS_EVENTS", + "ROWL_MODE", + "ROWL_PORT", + "ROWL_NO_BROWSER", + "ROWL_STATE_DIR", + "ROWL_AUTH_TOKEN", + "ROWL_DESKTOP_WS_URL", + "ROWL_HOST", + "ROWL_AUTO_BOOTSTRAP_PROJECT_FROM_CWD", "T3CODE_LOG_WS_EVENTS", "T3CODE_MODE", "T3CODE_PORT",