From 96dd1a076d8bb5cc7ffe10d5cebba83a4cde2af8 Mon Sep 17 00:00:00 2001 From: atryan Date: Fri, 31 Jul 2026 23:39:17 +0000 Subject: [PATCH 1/6] feat(release): prepare Command Center desktop v1 --- .github/workflows/release.yml | 177 ++++++++++++++---- apps/desktop/package.json | 2 +- apps/desktop/scripts/electron-launcher.mjs | 8 +- .../src/app/DesktopAppIdentity.test.ts | 4 +- apps/desktop/src/app/DesktopClerk.test.ts | 6 +- .../src/app/DesktopEnvironment.test.ts | 13 +- apps/desktop/src/app/DesktopEnvironment.ts | 12 +- .../src/electron/ElectronProtocol.test.ts | 48 ++--- apps/desktop/src/electron/ElectronProtocol.ts | 10 +- apps/desktop/src/window/DesktopWindow.test.ts | 41 ++-- apps/desktop/vite.config.ts | 6 +- apps/server/package.json | 5 +- apps/server/scripts/cli.ts | 4 +- apps/server/src/auth/EnvironmentAuth.ts | 2 +- apps/server/src/auth/EnvironmentAuthPolicy.ts | 2 +- apps/server/src/auth/PairingGrantStore.ts | 2 +- apps/server/src/auth/ServerSecretStore.ts | 2 +- apps/server/src/auth/SessionStore.ts | 2 +- apps/server/src/auth/websocketOrigin.test.ts | 10 +- apps/server/src/auth/websocketOrigin.ts | 8 +- .../src/checkpointing/CheckpointDiffQuery.ts | 2 +- .../src/checkpointing/CheckpointStore.ts | 2 +- apps/server/src/cloud/CliTokenManager.ts | 2 +- .../src/cloud/ManagedEndpointRuntime.ts | 2 +- apps/server/src/cloud/bootService.test.ts | 11 +- apps/server/src/cloud/bootService.ts | 19 +- .../AutomationDefinitionConfig.ts | 2 +- .../src/command-center/AutomationRuns.ts | 2 +- apps/server/src/command-center/Config.ts | 2 +- .../src/command-center/ConnectionHealth.ts | 2 +- .../src/command-center/CredentialStore.ts | 2 +- apps/server/src/command-center/EventStream.ts | 2 +- .../src/command-center/GoogleReadConnector.ts | 2 +- .../src/command-center/MemorySearchIndex.ts | 2 +- .../src/command-center/ReadinessGate.ts | 2 +- .../src/command-center/RunDispatcher.ts | 2 +- .../server/src/command-center/RunLifecycle.ts | 2 +- .../command-center/RunRecoveryCoordinator.ts | 2 +- apps/server/src/command-center/Service.ts | 2 +- .../src/command-center/WebhookAdmission.ts | 2 +- .../automation/AutomationScopedShell.ts | 2 +- .../automation/RecoveryCoordinator.ts | 4 +- .../src/command-center/automation/Runtime.ts | 2 +- .../automation/ScheduleRunner.ts | 2 +- .../automation/TriggerCoordinator.ts | 4 +- .../automation/VerifiedScopedShell.ts | 4 +- apps/server/src/config.ts | 2 +- .../src/database/SupabaseMcpConnector.ts | 2 +- .../src/diagnostics/ProcessDiagnostics.ts | 2 +- .../src/diagnostics/ProcessResourceMonitor.ts | 2 +- .../src/diagnostics/TraceDiagnostics.ts | 2 +- .../src/environment/ServerEnvironment.ts | 2 +- apps/server/src/git/GitManager.ts | 2 +- apps/server/src/git/GitWorkflowService.ts | 2 +- apps/server/src/httpCors.ts | 8 +- apps/server/src/keybindings.ts | 2 +- apps/server/src/mcp/McpInvocationContext.ts | 2 +- apps/server/src/mcp/McpSessionRegistry.ts | 2 +- .../server/src/mcp/PreviewAutomationBroker.ts | 2 +- .../observability/BrowserTraceCollector.ts | 2 +- .../src/orchestration/CommandDispatcher.ts | 2 +- .../Services/CheckpointReactor.ts | 2 +- .../Services/OrchestrationEngine.ts | 4 +- .../Services/OrchestrationReactor.ts | 2 +- .../Services/ProjectionPipeline.ts | 4 +- .../Services/ProjectionSnapshotQuery.ts | 2 +- .../Services/ProviderCommandReactor.ts | 2 +- .../Services/ProviderRuntimeIngestion.ts | 4 +- .../Services/RuntimeReceiptBus.ts | 2 +- .../Services/StalledTurnWatchdog.ts | 2 +- .../Services/ThreadDeletionReactor.ts | 2 +- .../src/persistence/AuthPairingLinks.ts | 2 +- apps/server/src/persistence/AuthSessions.ts | 2 +- .../src/persistence/ProviderSessionRuntime.ts | 4 +- .../Services/OrchestrationCommandReceipts.ts | 4 +- .../Services/OrchestrationEventStore.ts | 2 +- .../Services/ProjectionCheckpoints.ts | 4 +- .../Services/ProjectionPendingApprovals.ts | 4 +- .../Services/ProjectionProjects.ts | 4 +- .../persistence/Services/ProjectionState.ts | 2 +- .../Services/ProjectionThreadActivities.ts | 4 +- .../Services/ProjectionThreadMessages.ts | 4 +- .../Services/ProjectionThreadProposedPlans.ts | 2 +- .../Services/ProjectionThreadSessions.ts | 4 +- .../persistence/Services/ProjectionThreads.ts | 2 +- .../persistence/Services/ProjectionTurns.ts | 2 +- .../Services/ProviderTurnSendClaims.ts | 4 +- apps/server/src/preview/Manager.ts | 2 +- apps/server/src/preview/PortScanner.ts | 2 +- apps/server/src/process/externalLauncher.ts | 2 +- apps/server/src/processRunner.ts | 2 +- .../src/project/ProjectFaviconResolver.ts | 2 +- .../src/project/ProjectSetupScriptRunner.ts | 2 +- .../src/project/RepositoryIdentityResolver.ts | 2 +- .../src/provider/Layers/ClaudeAdapter.test.ts | 2 +- .../src/provider/Layers/CodexAdapter.test.ts | 2 +- .../src/provider/Layers/CursorAdapter.test.ts | 2 +- .../provider/Layers/OpenCodeAdapter.test.ts | 2 +- .../provider/Layers/ProviderEventLoggers.ts | 2 +- .../Services/ProviderAdapterRegistry.ts | 2 +- .../Services/ProviderInstanceRegistry.ts | 2 +- .../ProviderInstanceRegistryMutator.ts | 2 +- .../src/provider/Services/ProviderRegistry.ts | 2 +- .../src/provider/Services/ProviderService.ts | 2 +- .../Services/ProviderSessionDirectory.ts | 2 +- .../Services/ProviderSessionReaper.ts | 2 +- .../src/provider/acp/AcpSessionRuntime.ts | 2 +- apps/server/src/provider/opencodeRuntime.ts | 2 +- .../src/provider/providerMaintenanceRunner.ts | 2 +- apps/server/src/relay/AgentAwarenessRelay.ts | 2 +- apps/server/src/review/ReviewService.ts | 2 +- apps/server/src/server.test.ts | 7 +- apps/server/src/serverLifecycleEvents.ts | 2 +- apps/server/src/serverRuntimeStartup.ts | 2 +- apps/server/src/serverSettings.ts | 2 +- .../src/sourceControl/AzureDevOpsCli.ts | 2 +- apps/server/src/sourceControl/BitbucketApi.ts | 2 +- apps/server/src/sourceControl/GitHubCli.ts | 2 +- apps/server/src/sourceControl/GitLabCli.ts | 2 +- .../sourceControl/SourceControlDiscovery.ts | 2 +- .../sourceControl/SourceControlProvider.ts | 2 +- .../SourceControlProviderRegistry.ts | 2 +- .../SourceControlRepositoryService.ts | 2 +- apps/server/src/telemetry/AnalyticsService.ts | 2 +- apps/server/src/terminal/Manager.ts | 2 +- apps/server/src/terminal/PtyAdapter.ts | 2 +- .../src/textGeneration/TextGeneration.ts | 2 +- apps/server/src/vcs/GitVcsDriver.ts | 2 +- apps/server/src/vcs/VcsDriver.ts | 2 +- apps/server/src/vcs/VcsDriverRegistry.ts | 2 +- apps/server/src/vcs/VcsProcess.ts | 2 +- apps/server/src/vcs/VcsProjectConfig.ts | 2 +- apps/server/src/vcs/VcsProvisioningService.ts | 2 +- apps/server/src/vcs/VcsStatusBroadcaster.ts | 2 +- apps/server/src/workspace/WorkspaceEntries.ts | 2 +- .../src/workspace/WorkspaceFileSystem.ts | 2 +- apps/server/src/workspace/WorkspacePaths.ts | 2 +- .../src/workspace/WorkspaceSearchIndex.ts | 4 +- apps/web/src/branding.test.ts | 22 ++- apps/web/src/branding.ts | 4 +- .../components/settings/SettingsPanels.tsx | 10 +- apps/web/src/vite-env.d.ts | 1 + apps/web/vercel.ts | 56 +----- docs/architecture/remote.md | 2 +- docs/cloud/t3-connect-clerk.md | 24 +-- docs/operations/release.md | 126 +++++++------ docs/reference/scripts.md | 4 +- docs/user/remote-access.md | 53 +++--- package.json | 4 +- packages/contracts/src/ipc.ts | 4 +- packages/shared/src/connectAuth.test.ts | 4 +- packages/shared/src/connectAuth.ts | 2 +- packages/shared/src/remote.test.ts | 7 +- packages/ssh/src/command.test.ts | 68 +++---- packages/ssh/src/command.ts | 7 +- packages/ssh/src/tunnel.test.ts | 38 ++-- packages/ssh/src/tunnel.ts | 11 +- scripts/build-desktop-artifact.test.ts | 56 ++++-- scripts/build-desktop-artifact.ts | 8 +- scripts/dev-runner.test.ts | 8 +- scripts/dev-runner.ts | 6 +- scripts/notify-discord-release.test.ts | 24 +-- scripts/notify-discord-release.ts | 6 +- scripts/release-smoke.ts | 2 +- scripts/resolve-nightly-release.test.ts | 2 +- scripts/resolve-nightly-release.ts | 2 +- scripts/validate-release-assets.test.ts | 85 +++++++++ scripts/validate-release-assets.ts | 91 +++++++++ 168 files changed, 895 insertions(+), 511 deletions(-) create mode 100644 scripts/validate-release-assets.test.ts create mode 100644 scripts/validate-release-assets.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 12ac0370c3b..6890ab362e0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,14 @@ on: - cron: "0 */3 * * *" workflow_dispatch: inputs: + mode: + description: "Validate artifacts without publishing, or publish the release" + required: false + default: validate + type: choice + options: + - validate + - publish channel: description: "Release channel" required: false @@ -78,6 +86,7 @@ jobs: cli_dist_tag: ${{ steps.release_meta.outputs.cli_dist_tag }} is_prerelease: ${{ steps.release_meta.outputs.is_prerelease }} make_latest: ${{ steps.release_meta.outputs.make_latest }} + release_mode: ${{ steps.release_meta.outputs.release_mode }} ref: ${{ github.sha }} steps: - name: Checkout @@ -100,11 +109,23 @@ jobs: shell: bash env: DISPATCH_CHANNEL: ${{ github.event.inputs.channel }} + DISPATCH_MODE: ${{ github.event.inputs.mode }} DISPATCH_VERSION: ${{ github.event.inputs.version }} NIGHTLY_DATE: ${{ github.run_started_at }} NIGHTLY_SHA: ${{ github.sha }} NIGHTLY_RUN_NUMBER: ${{ github.run_number }} run: | + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + release_mode="${DISPATCH_MODE:-validate}" + else + release_mode="publish" + fi + if [[ "$release_mode" != "validate" && "$release_mode" != "publish" ]]; then + echo "Invalid release mode: $release_mode" >&2 + exit 1 + fi + echo "release_mode=$release_mode" >> "$GITHUB_OUTPUT" + if [[ "${GITHUB_EVENT_NAME}" == "schedule" || ( "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && "${DISPATCH_CHANNEL:-stable}" == "nightly" ) ]]; then nightly_date="$(date -u -d "$NIGHTLY_DATE" +%Y%m%d)" @@ -138,7 +159,7 @@ jobs: echo "release_channel=stable" >> "$GITHUB_OUTPUT" echo "version=$version" >> "$GITHUB_OUTPUT" echo "tag=v$version" >> "$GITHUB_OUTPUT" - echo "name=T3 Code v$version" >> "$GITHUB_OUTPUT" + echo "name=Command Center v$version" >> "$GITHUB_OUTPUT" echo "cli_dist_tag=latest" >> "$GITHUB_OUTPUT" if [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then echo "is_prerelease=false" >> "$GITHUB_OUTPUT" @@ -317,6 +338,7 @@ jobs: T3CODE_CLERK_JWT_TEMPLATE: ${{ needs.relay_public_config.outputs.clerk_jwt_template }} T3CODE_CLERK_CLI_OAUTH_CLIENT_ID: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} T3CODE_RELAY_URL: ${{ needs.relay_public_config.outputs.relay_url }} + REQUIRE_SIGNING: ${{ needs.preflight.outputs.release_channel == 'stable' && 'true' || 'false' }} strategy: fail-fast: false matrix: @@ -440,6 +462,10 @@ jobs: $env:AZURE_TRUSTED_SIGNING_PUBLISHER_NAME ) if ($requiredSecrets | Where-Object { [string]::IsNullOrWhiteSpace($_) }) { + if ($env:REQUIRE_SIGNING -eq "true") { + Write-Error "Stable releases require complete Azure Trusted Signing credentials." + exit 1 + } Write-Host "Azure Trusted Signing disabled; skipping TrustedSigning module preparation." exit 0 } @@ -514,12 +540,27 @@ jobs: return 0 } - if [[ "${{ matrix.platform }}" == "mac" ]]; then - if has_all "$CSC_LINK" "$CSC_KEY_PASSWORD" "$APPLE_API_KEY" "$APPLE_API_KEY_ID" "$APPLE_API_ISSUER"; then - if ! has_all "$APPLE_TEAM_ID" "$MACOS_PROVISIONING_PROFILE"; then - echo "macOS signing is configured, but APPLE_TEAM_ID or MACOS_PROVISIONING_PROFILE is missing." >&2 - exit 1 + has_any() { + for value in "$@"; do + if [[ -n "$value" ]]; then + return 0 fi + done + return 1 + } + + if [[ "${{ matrix.platform }}" == "mac" ]]; then + mac_signing_values=( + "$CSC_LINK" + "$CSC_KEY_PASSWORD" + "$APPLE_API_KEY" + "$APPLE_API_KEY_ID" + "$APPLE_API_ISSUER" + "$APPLE_TEAM_ID" + "$MACOS_PROVISIONING_PROFILE" + "$T3CODE_CLERK_PASSKEY_RP_DOMAINS" + ) + if has_all "${mac_signing_values[@]}"; then key_path="$RUNNER_TEMP/AuthKey_${APPLE_API_KEY_ID}.p8" printf '%s' "$APPLE_API_KEY" > "$key_path" @@ -534,6 +575,10 @@ jobs: echo "macOS signing enabled." args+=(--signed) else + if [[ "$REQUIRE_SIGNING" == "true" ]] || has_any "${mac_signing_values[@]}"; then + echo "Stable macOS releases require complete signing, notarization, provisioning, team, and associated-domain configuration." >&2 + exit 1 + fi echo "macOS signing disabled (missing one or more Apple signing secrets)." fi elif [[ "${{ matrix.platform }}" == "win" ]]; then @@ -552,6 +597,19 @@ jobs: echo "Windows signing enabled (Azure Trusted Signing)." args+=(--signed) else + windows_signing_values=( + "$AZURE_TENANT_ID" + "$AZURE_CLIENT_ID" + "$AZURE_CLIENT_SECRET" + "$AZURE_TRUSTED_SIGNING_ENDPOINT" + "$AZURE_TRUSTED_SIGNING_ACCOUNT_NAME" + "$AZURE_TRUSTED_SIGNING_CERTIFICATE_PROFILE_NAME" + "$AZURE_TRUSTED_SIGNING_PUBLISHER_NAME" + ) + if [[ "$REQUIRE_SIGNING" == "true" ]] || has_any "${windows_signing_values[@]}"; then + echo "Stable Windows releases require complete Azure Trusted Signing credentials." >&2 + exit 1 + fi echo "Windows signing disabled (missing one or more Azure Trusted Signing secrets)." fi else @@ -605,10 +663,64 @@ jobs: path: release-publish/* if-no-files-found: error + validate_artifacts: + name: Validate release artifacts + needs: [preflight, build] + if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'validate' && needs.build.result == 'success' }} + runs-on: blacksmith-8vcpu-ubuntu-2404 + timeout-minutes: 10 + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ needs.preflight.outputs.ref }} + + - name: Setup Vite+ + uses: voidzero-dev/setup-vp@v1 + with: + node-version-file: package.json + cache: true + run-install: | + args: + - --filter=@t3tools/scripts... + + - name: Download all desktop artifacts + uses: actions/download-artifact@v8 + with: + pattern: desktop-* + merge-multiple: true + path: release-assets + + - name: Merge macOS updater manifests + run: | + shopt -s nullglob + for x64_manifest in release-assets/*-mac-x64.yml; do + arm64_manifest="${x64_manifest%-x64.yml}.yml" + if [[ -f "$arm64_manifest" ]]; then + node scripts/merge-update-manifests.ts --platform mac "$arm64_manifest" "$x64_manifest" + rm -f "$x64_manifest" + fi + done + + - name: Validate installers and updater paths + run: node scripts/validate-release-assets.ts --directory release-assets --version "${{ needs.preflight.outputs.version }}" + + - name: Generate release checksums + working-directory: release-assets + run: sha256sum *.dmg *.zip *.AppImage *.exe *.blockmap *.yml > SHA256SUMS.txt + + - name: Retain consolidated validation bundle + uses: actions/upload-artifact@v7 + with: + name: command-center-${{ needs.preflight.outputs.version }}-validation + path: release-assets/* + if-no-files-found: error + retention-days: 14 + publish_cli: name: Publish CLI to npm needs: [preflight, relay_public_config, build] - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.build.result == 'success' }} + if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.relay_public_config.result == 'success' && needs.build.result == 'success' }} runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404 timeout-minutes: 10 permissions: @@ -632,7 +744,7 @@ jobs: cache: true run-install: | args: - - --filter=t3... + - --filter=@awtprod/command-center... - --filter=@t3tools/web... - --filter=@t3tools/scripts... @@ -657,7 +769,7 @@ jobs: run: vp run --filter @t3tools/web build - name: Build CLI package - run: vp run --filter t3 build + run: vp run --filter @awtprod/command-center build - name: Publish CLI package run: node apps/server/scripts/cli.ts publish --tag "${{ needs.preflight.outputs.cli_dist_tag }}" --app-version "${{ needs.preflight.outputs.version }}" --verbose @@ -665,7 +777,7 @@ jobs: release: name: Publish GitHub Release needs: [preflight, build, publish_cli] - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.build.result == 'success' && needs.publish_cli.result == 'success' }} + if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.build.result == 'success' && needs.publish_cli.result == 'success' }} runs-on: blacksmith-8vcpu-ubuntu-2404 timeout-minutes: 10 steps: @@ -709,6 +821,13 @@ jobs: fi done + - name: Generate release checksums + working-directory: release-assets + run: sha256sum *.dmg *.zip *.AppImage *.exe *.blockmap *.yml > SHA256SUMS.txt + + - name: Validate installers and updater paths + run: node scripts/validate-release-assets.ts --directory release-assets --version "${{ needs.preflight.outputs.version }}" + # - name: Merge Windows updater manifests # run: | # shopt -s nullglob @@ -756,6 +875,7 @@ jobs: release-assets/*.exe release-assets/*.blockmap release-assets/*.yml + release-assets/SHA256SUMS.txt fail_on_unmatched_files: true token: ${{ steps.app_token.outputs.token }} @@ -776,13 +896,14 @@ jobs: release-assets/*.exe release-assets/*.blockmap release-assets/*.yml + release-assets/SHA256SUMS.txt fail_on_unmatched_files: true token: ${{ steps.app_token.outputs.token }} deploy_web: name: Deploy hosted web app needs: [preflight, relay_public_config, release] - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.relay_public_config.result == 'success' && needs.release.result == 'success' }} + if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.relay_public_config.result == 'success' && needs.release.result == 'success' }} runs-on: blacksmith-8vcpu-ubuntu-2404 timeout-minutes: 10 env: @@ -793,8 +914,6 @@ jobs: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} T3CODE_WEB_ROUTER_URL: ${{ vars.T3CODE_WEB_ROUTER_URL }} - T3CODE_WEB_LATEST_DOMAIN: ${{ vars.T3CODE_WEB_LATEST_DOMAIN }} - T3CODE_WEB_NIGHTLY_DOMAIN: ${{ vars.T3CODE_WEB_NIGHTLY_DOMAIN }} VERCEL_TEAM_SLUG: ${{ vars.VERCEL_TEAM_SLUG }} steps: - name: Checkout @@ -832,7 +951,7 @@ jobs: - name: Refresh release lockfile run: vp install --lockfile-only --ignore-scripts - - name: Deploy and alias channel + - name: Deploy hosted channel shell: bash run: | set -euo pipefail @@ -842,19 +961,14 @@ jobs: exit 1 fi - router_url="${T3CODE_WEB_ROUTER_URL:-https://app.t3.codes}" - latest_domain="${T3CODE_WEB_LATEST_DOMAIN:-latest.app.t3.codes}" - nightly_domain="${T3CODE_WEB_NIGHTLY_DOMAIN:-nightly.app.t3.codes}" - router_domain="${router_url#http://}" - router_domain="${router_domain#https://}" - router_domain="${router_domain%%/*}" + router_url="${T3CODE_WEB_ROUTER_URL:-https://awtprod-command-center.vercel.app}" if [[ "${{ needs.preflight.outputs.release_channel }}" == "stable" ]]; then - channel_domain="$latest_domain" channel_name="latest" + deployment_target_args=(--prod) else - channel_domain="$nightly_domain" channel_name="nightly" + deployment_target_args=() fi vercel_scope="${VERCEL_TEAM_SLUG:-$VERCEL_ORG_ID}" @@ -863,8 +977,7 @@ jobs: echo "Deploying hosted web app for $channel_name channel." deployment_url="$( vp dlx vercel@53.1.1 deploy \ - --prod \ - --skip-domain \ + "${deployment_target_args[@]}" \ --yes \ --token "$VERCEL_TOKEN" \ "${vercel_scope_args[@]}" \ @@ -879,21 +992,12 @@ jobs: --build-env "VITE_HOSTED_APP_CHANNEL=$channel_name" )" - echo "Aliasing $deployment_url to $channel_domain." - vp dlx vercel@53.1.1 alias set "$deployment_url" "$channel_domain" \ - --token "$VERCEL_TOKEN" \ - "${vercel_scope_args[@]}" - - if [[ "$channel_name" == "latest" && -n "$router_domain" && "$router_domain" != "$channel_domain" ]]; then - echo "Aliasing $deployment_url to router domain $router_domain." - vp dlx vercel@53.1.1 alias set "$deployment_url" "$router_domain" \ - --token "$VERCEL_TOKEN" \ - "${vercel_scope_args[@]}" - fi + echo "Hosted $channel_name deployment: $deployment_url" + printf '### Hosted %s deployment\n\n%s\n' "$channel_name" "$deployment_url" >> "$GITHUB_STEP_SUMMARY" finalize: name: Finalize release - if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }} + if: ${{ !failure() && !cancelled() && needs.preflight.result == 'success' && needs.preflight.outputs.release_mode == 'publish' && needs.release.result == 'success' && needs.preflight.outputs.release_channel == 'stable' }} needs: [preflight, release] runs-on: blacksmith-8vcpu-ubuntu-2404 timeout-minutes: 10 @@ -971,6 +1075,7 @@ jobs: if: | always() && !cancelled() && needs.preflight.result == 'success' && + needs.preflight.outputs.release_mode == 'publish' && needs.relay_public_config.result == 'success' && needs.release.result == 'success' && needs.deploy_web.result == 'success' && diff --git a/apps/desktop/package.json b/apps/desktop/package.json index b3829e43e32..1250b70ae00 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -35,5 +35,5 @@ "tailwindcss": "^4.0.0", "vite-plus": "catalog:" }, - "productName": "Command Center (Alpha)" + "productName": "Command Center" } diff --git a/apps/desktop/scripts/electron-launcher.mjs b/apps/desktop/scripts/electron-launcher.mjs index edbbac04d94..5a8fa8b2752 100644 --- a/apps/desktop/scripts/electron-launcher.mjs +++ b/apps/desktop/scripts/electron-launcher.mjs @@ -17,10 +17,10 @@ const devBundleIdSuffix = NodePath.basename(repoRoot) .replaceAll(/[^a-z0-9]+/g, ""); export const APP_DISPLAY_NAME = isDevelopment ? "Command Center (Dev)" : "Command Center"; export const APP_BUNDLE_ID = isDevelopment - ? `com.t3tools.t3code.dev.${devBundleIdSuffix || "local"}` - : "com.t3tools.t3code"; -const APP_PROTOCOL_SCHEMES = isDevelopment ? ["t3code-dev"] : ["t3code"]; -const LAUNCHER_VERSION = 14; + ? `com.awtprod.commandcenter.dev.${devBundleIdSuffix || "local"}` + : "com.awtprod.commandcenter"; +const APP_PROTOCOL_SCHEMES = isDevelopment ? ["commandcenter-dev"] : ["commandcenter"]; +const LAUNCHER_VERSION = 15; const defaultIconPath = NodePath.join(desktopDir, "resources", "icon.icns"); const developmentMacIconPngPath = NodePath.join( repoRoot, diff --git a/apps/desktop/src/app/DesktopAppIdentity.test.ts b/apps/desktop/src/app/DesktopAppIdentity.test.ts index 42f6b044519..84838d74781 100644 --- a/apps/desktop/src/app/DesktopAppIdentity.test.ts +++ b/apps/desktop/src/app/DesktopAppIdentity.test.ts @@ -209,8 +209,8 @@ describe("DesktopAppIdentity", () => { const identity = yield* DesktopAppIdentity.DesktopAppIdentity; yield* identity.configure; - assert.deepEqual(calls.setName, ["Command Center (Alpha)"]); - assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "Command Center (Alpha)"); + assert.deepEqual(calls.setName, ["Command Center"]); + assert.equal(calls.setAboutPanelOptions[0]?.applicationName, "Command Center"); assert.equal(calls.setAboutPanelOptions[0]?.applicationVersion, "1.2.3"); assert.equal(calls.setAboutPanelOptions[0]?.version, "0123456789ab"); assert.deepEqual(calls.setDockIcon, ["/icon.png"]); diff --git a/apps/desktop/src/app/DesktopClerk.test.ts b/apps/desktop/src/app/DesktopClerk.test.ts index 9b5ed56d1f3..c36a2c46948 100644 --- a/apps/desktop/src/app/DesktopClerk.test.ts +++ b/apps/desktop/src/app/DesktopClerk.test.ts @@ -66,7 +66,7 @@ describe("DesktopClerk", () => { { storage: storageAdapter, passkeys: true, - renderer: { scheme: "t3code-dev", host: "app" }, + renderer: { scheme: "commandcenter-dev", host: "app" }, }, ], ]); @@ -125,8 +125,8 @@ describe("DesktopClerk", () => { }); it.each([ - { isDevelopment: true, scheme: "t3code-dev" }, - { isDevelopment: false, scheme: "t3code" }, + { isDevelopment: true, scheme: "commandcenter-dev" }, + { isDevelopment: false, scheme: "commandcenter" }, ])("configures the SDK with the $scheme renderer origin", ({ isDevelopment, scheme }) => { const bridge = { cleanup: vi.fn() }; storageMock.mockReturnValue(storageAdapter); diff --git a/apps/desktop/src/app/DesktopEnvironment.test.ts b/apps/desktop/src/app/DesktopEnvironment.test.ts index 257af09bfd2..6d3ac520986 100644 --- a/apps/desktop/src/app/DesktopEnvironment.test.ts +++ b/apps/desktop/src/app/DesktopEnvironment.test.ts @@ -67,8 +67,8 @@ describe("DesktopEnvironment", () => { assert.equal(environment.appRoot, "/repo"); assert.equal(environment.backendEntryPath, "/repo/apps/server/dist/bin.mjs"); assert.equal(environment.backendCwd, "/repo"); - assert.equal(environment.appUserModelId, "com.t3tools.t3code.dev"); - assert.equal(environment.linuxWmClass, "t3code-dev"); + assert.equal(environment.appUserModelId, "com.awtprod.commandcenter.dev"); + assert.equal(environment.linuxWmClass, "commandcenter-dev"); assert.deepEqual( Option.map(environment.devServerUrl, (url) => url.href), Option.some("http://localhost:5173/"), @@ -108,6 +108,11 @@ describe("DesktopEnvironment", () => { assert.equal(development.stateDir, "/user-home/.command-center/dev"); assert.equal(production.stateDir, "/user-home/.command-center/userdata"); + assert.equal(production.displayName, "Command Center"); + assert.equal(production.branding.stageLabel, "Latest"); + assert.equal(production.appUserModelId, "com.awtprod.commandcenter"); + assert.equal(production.linuxDesktopEntryName, "command-center.desktop"); + assert.equal(production.linuxWmClass, "command-center"); }), ); @@ -131,12 +136,12 @@ describe("DesktopEnvironment", () => { const environment = yield* makeEnvironment( {}, { - T3CODE_DESKTOP_APP_USER_MODEL_ID: " com.t3tools.t3code.dev.local ", + T3CODE_DESKTOP_APP_USER_MODEL_ID: " com.awtprod.commandcenter.dev.local ", VITE_DEV_SERVER_URL: "http://localhost:5173", }, ); - assert.equal(environment.appUserModelId, "com.t3tools.t3code.dev.local"); + assert.equal(environment.appUserModelId, "com.awtprod.commandcenter.dev.local"); }), ); diff --git a/apps/desktop/src/app/DesktopEnvironment.ts b/apps/desktop/src/app/DesktopEnvironment.ts index a8edb2a51ac..aab42a71c62 100644 --- a/apps/desktop/src/app/DesktopEnvironment.ts +++ b/apps/desktop/src/app/DesktopEnvironment.ts @@ -86,7 +86,7 @@ function resolveDesktopAppStageLabel(input: { return "Dev"; } - return isNightlyDesktopVersion(input.appVersion) ? "Nightly" : "Alpha"; + return isNightlyDesktopVersion(input.appVersion) ? "Nightly" : "Latest"; } function resolveDesktopAppBranding(input: { @@ -97,7 +97,7 @@ function resolveDesktopAppBranding(input: { return { baseName: APP_BASE_NAME, stageLabel, - displayName: `${APP_BASE_NAME} (${stageLabel})`, + displayName: stageLabel === "Latest" ? APP_BASE_NAME : `${APP_BASE_NAME} (${stageLabel})`, }; } @@ -167,7 +167,7 @@ const make = Effect.fn("desktop.environment.make")(function* ( const userDataDirName = isDevelopment ? "command-center-dev" : "command-center"; const legacyUserDataDirNames = isDevelopment ? ["t3code-dev", "T3 Code (Dev)"] - : ["t3code", "T3 Code (Alpha)"]; + : ["t3code", "T3 Code (Alpha)", "Command Center (Alpha)"]; const resourcesPath = input.resourcesPath; return DesktopEnvironment.of({ @@ -207,10 +207,10 @@ const make = Effect.fn("desktop.environment.make")(function* ( branding, displayName, appUserModelId: Option.getOrElse(config.appUserModelIdOverride, () => - isDevelopment ? "com.t3tools.t3code.dev" : "com.t3tools.t3code", + isDevelopment ? "com.awtprod.commandcenter.dev" : "com.awtprod.commandcenter", ), - linuxDesktopEntryName: isDevelopment ? "t3code-dev.desktop" : "t3code.desktop", - linuxWmClass: isDevelopment ? "t3code-dev" : "t3code", + linuxDesktopEntryName: isDevelopment ? "commandcenter-dev.desktop" : "command-center.desktop", + linuxWmClass: isDevelopment ? "commandcenter-dev" : "command-center", userDataDirName, legacyUserDataDirNames, defaultDesktopSettings: DesktopAppSettings.resolveDefaultDesktopSettings(input.appVersion), diff --git a/apps/desktop/src/electron/ElectronProtocol.test.ts b/apps/desktop/src/electron/ElectronProtocol.test.ts index 92e30000427..109ab8eb968 100644 --- a/apps/desktop/src/electron/ElectronProtocol.test.ts +++ b/apps/desktop/src/electron/ElectronProtocol.test.ts @@ -35,7 +35,7 @@ describe("ElectronProtocol", () => { Effect.gen(function* () { const protocol = yield* ElectronProtocol.ElectronProtocol; yield* protocol.registerDesktopProtocol({ - scheme: "t3code-dev", + scheme: "commandcenter-dev", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3774/"), clerkFrontendApiHostname: "clerk.t3.codes", @@ -44,11 +44,11 @@ describe("ElectronProtocol", () => { const response = yield* Effect.promise(() => handler!( - new Request("t3code-dev://app/api/health?verbose=1", { + new Request("commandcenter-dev://app/api/health?verbose=1", { headers: { accept: "application/json", - origin: "t3code-dev://app", - referer: "t3code-dev://app/", + origin: "commandcenter-dev://app", + referer: "commandcenter-dev://app/", "sec-fetch-site": "same-origin", }, }), @@ -65,18 +65,18 @@ describe("ElectronProtocol", () => { ); assert.include( response.headers.get("content-security-policy") ?? "", - "img-src 'self' t3code-dev: blob: data: http: https:", + "img-src 'self' commandcenter-dev: blob: data: http: https:", ); assert.include( response.headers.get("content-security-policy") ?? "", - "font-src 'self' t3code-dev: data:", + "font-src 'self' commandcenter-dev: data:", ); }), ); assert.deepEqual( handleMock.mock.calls.map((call) => call[0]), - ["t3code-dev"], + ["commandcenter-dev"], ); assert.equal(netFetchMock.mock.calls[0]?.[0], "http://127.0.0.1:3773/api/health?verbose=1"); const forwardedHeaders = new Headers(netFetchMock.mock.calls[0]?.[1]?.headers); @@ -84,7 +84,7 @@ describe("ElectronProtocol", () => { assert.isNull(forwardedHeaders.get("origin")); assert.isNull(forwardedHeaders.get("referer")); assert.isNull(forwardedHeaders.get("sec-fetch-site")); - assert.deepEqual(unhandleMock.mock.calls, [["t3code-dev"]]); + assert.deepEqual(unhandleMock.mock.calls, [["commandcenter-dev"]]); }).pipe(Effect.provide(ElectronProtocol.layer)), ); @@ -99,12 +99,12 @@ describe("ElectronProtocol", () => { Effect.gen(function* () { const protocol = yield* ElectronProtocol.ElectronProtocol; yield* protocol.registerDesktopProtocol({ - scheme: "t3code", + scheme: "commandcenter", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: undefined, }); - return yield* Effect.promise(() => handler!(new Request("t3code://other/"))); + return yield* Effect.promise(() => handler!(new Request("commandcenter://other/"))); }), ); @@ -127,12 +127,12 @@ describe("ElectronProtocol", () => { Effect.gen(function* () { const protocol = yield* ElectronProtocol.ElectronProtocol; yield* protocol.registerDesktopProtocol({ - scheme: "t3code-dev", + scheme: "commandcenter-dev", targetOrigin: new URL("http://127.0.0.1:5733/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: undefined, }); - return yield* Effect.promise(() => handler!(new Request("t3code-dev://app/"))); + return yield* Effect.promise(() => handler!(new Request("commandcenter-dev://app/"))); }), ); @@ -151,7 +151,7 @@ describe("ElectronProtocol", () => { const protocol = yield* ElectronProtocol.ElectronProtocol; const error = yield* Effect.scoped( protocol.registerDesktopProtocol({ - scheme: "t3code-dev", + scheme: "commandcenter-dev", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3774/"), clerkFrontendApiHostname: undefined, @@ -159,9 +159,12 @@ describe("ElectronProtocol", () => { ).pipe(Effect.flip); assert.instanceOf(error, ElectronProtocol.ElectronProtocolRegistrationError); - assert.equal(error.scheme, "t3code-dev"); + assert.equal(error.scheme, "commandcenter-dev"); assert.strictEqual(error.cause, cause); - assert.equal(error.message, 'Failed to register Electron protocol scheme "t3code-dev".'); + assert.equal( + error.message, + 'Failed to register Electron protocol scheme "commandcenter-dev".', + ); }).pipe(Effect.provide(ElectronProtocol.layer)), ); @@ -176,7 +179,7 @@ describe("ElectronProtocol", () => { const exit = yield* Effect.exit( Effect.scoped( protocol.registerDesktopProtocol({ - scheme: "t3code", + scheme: "commandcenter", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: undefined, @@ -188,16 +191,19 @@ describe("ElectronProtocol", () => { if (exit._tag === "Failure") { const error = Cause.squash(exit.cause); assert.instanceOf(error, ElectronProtocol.ElectronProtocolUnregistrationError); - assert.equal(error.scheme, "t3code"); + assert.equal(error.scheme, "commandcenter"); assert.strictEqual(error.cause, cause); - assert.equal(error.message, 'Failed to unregister Electron protocol scheme "t3code".'); + assert.equal( + error.message, + 'Failed to unregister Electron protocol scheme "commandcenter".', + ); } }).pipe(Effect.provide(ElectronProtocol.layer)), ); it("keeps executable sources host-restricted while allowing runtime network resources", () => { const policy = ElectronProtocol.makeDesktopContentSecurityPolicy({ - scheme: "t3code", + scheme: "commandcenter", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: "clerk.t3.codes", @@ -218,12 +224,12 @@ describe("ElectronProtocol", () => { assert.deepEqual(directives["connect-src"], ["'self'", "http:", "https:", "ws:", "wss:"]); assert.deepEqual(directives["img-src"], [ "'self'", - "t3code:", + "commandcenter:", "blob:", "data:", "http:", "https:", ]); - assert.deepEqual(directives["font-src"], ["'self'", "t3code:", "data:"]); + assert.deepEqual(directives["font-src"], ["'self'", "commandcenter:", "data:"]); }); }); diff --git a/apps/desktop/src/electron/ElectronProtocol.ts b/apps/desktop/src/electron/ElectronProtocol.ts index 7ad2bd8ddac..49c9c516132 100644 --- a/apps/desktop/src/electron/ElectronProtocol.ts +++ b/apps/desktop/src/electron/ElectronProtocol.ts @@ -9,8 +9,8 @@ import * as Scope from "effect/Scope"; import * as Electron from "electron"; export const DESKTOP_HOST = "app"; -export const DESKTOP_PRODUCTION_SCHEME = "t3code"; -export const DESKTOP_DEVELOPMENT_SCHEME = "t3code-dev"; +export const DESKTOP_PRODUCTION_SCHEME = "commandcenter"; +export const DESKTOP_DEVELOPMENT_SCHEME = "commandcenter-dev"; export function getDesktopScheme(isDevelopment: boolean): string { return isDevelopment ? DESKTOP_DEVELOPMENT_SCHEME : DESKTOP_PRODUCTION_SCHEME; @@ -21,15 +21,15 @@ export function getDesktopScheme(isDevelopment: boolean): string { * * Custom schemes are non-standard by default, and Chromium gives a * non-standard scheme an **opaque** origin — every request the renderer makes - * carries `Origin: null` rather than `t3code://app`. That matters beyond + * carries `Origin: null` rather than `commandcenter://app`. That matters beyond * tidiness: the server rejects `null` on the control-plane WebSocket upgrade * (`apps/server/src/auth/websocketOrigin.ts`), because `null` is also what a * sandboxed hostile iframe sends, so it cannot be allow-listed. Without this * call the renderer is indistinguishable from that attacker and is refused. * - * Measured with a real Electron renderer on `t3code://app`: with + * Measured with a real Electron renderer on `commandcenter://app`: with * `protocol.handle` alone the upgrade arrives as `Origin: null`; adding this - * registration makes it arrive as `Origin: t3code://app`. + * registration makes it arrive as `Origin: commandcenter://app`. * * Must be called at module scope — Electron requires it before the `ready` * event, and it throws if called afterwards. Measured on Electron 41.5.0: diff --git a/apps/desktop/src/window/DesktopWindow.test.ts b/apps/desktop/src/window/DesktopWindow.test.ts index 492ef1ad577..2faca08cb7d 100644 --- a/apps/desktop/src/window/DesktopWindow.test.ts +++ b/apps/desktop/src/window/DesktopWindow.test.ts @@ -13,7 +13,7 @@ vi.mock("electron", async (importOriginal) => ({ ...(await importOriginal()), session: { fromPartition: vi.fn(() => ({ - getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/1.2.3"), + getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 commandcenter/1.2.3"), setPermissionRequestHandler: vi.fn(), setUserAgent: vi.fn(), })), @@ -50,7 +50,7 @@ function makeFakeBrowserWindow() { const webContentsListeners = new Map void>(); const webContents = { copyImageAt: vi.fn(), - getURL: vi.fn(() => "t3code-dev://app/"), + getURL: vi.fn(() => "commandcenter-dev://app/"), isLoadingMainFrame: vi.fn(() => false), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { webContentsListeners.set(eventName, listener); @@ -191,8 +191,8 @@ function makeTestLayer(input: { Layer.mock(PreviewManager.PreviewManager)({ getBrowserSession: () => Effect.succeed({} as Electron.Session), setMainWindow: () => Effect.void, - isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"), - getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"), + isBrowserPartition: (partition) => partition.startsWith("persist:commandcenter-preview-"), + getBrowserPartition: () => Effect.succeed("persist:commandcenter-preview-test"), }), ), ), @@ -283,8 +283,9 @@ const makeSplashScenario = (createOutcomes: readonly (Electron.BrowserWindow | n Layer.mock(PreviewManager.PreviewManager)({ getBrowserSession: () => Effect.succeed({} as Electron.Session), setMainWindow: () => Effect.void, - isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"), - getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"), + isBrowserPartition: (partition) => + partition.startsWith("persist:commandcenter-preview-"), + getBrowserPartition: () => Effect.succeed("persist:commandcenter-preview-test"), }), ), ), @@ -297,19 +298,19 @@ describe("DesktopWindow", () => { it("recognizes only same-origin renderer navigations", () => { assert.isTrue( DesktopWindow.isSameOriginRendererNavigation({ - applicationUrl: "t3code://app/", - navigationUrl: "t3code://app/settings/connections", + applicationUrl: "commandcenter://app/", + navigationUrl: "commandcenter://app/settings/connections", }), ); assert.isFalse( DesktopWindow.isSameOriginRendererNavigation({ - applicationUrl: "t3code://app/", + applicationUrl: "commandcenter://app/", navigationUrl: "https://accounts.microsoft.com/oauth", }), ); assert.isFalse( DesktopWindow.isSameOriginRendererNavigation({ - applicationUrl: "t3code://app/", + applicationUrl: "commandcenter://app/", navigationUrl: "not a url", }), ); @@ -337,7 +338,7 @@ describe("DesktopWindow", () => { assert.equal(yield* Ref.get(createCount), 1); assert.isTrue(createdWindowOptions[0]?.disableAutoHideCursor); assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]); - assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["t3code-dev://app/"]); + assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["commandcenter-dev://app/"]); assert.equal(fakeWindow.openDevTools.mock.calls.length, 1); }).pipe(Effect.provide(layer)); }), @@ -395,17 +396,17 @@ describe("DesktopWindow", () => { return yield* Effect.die("renderer load listeners were not registered"); } - didFailLoad({}, -9, "ERR_UNEXPECTED", "t3code-dev://app/", true); + didFailLoad({}, -9, "ERR_UNEXPECTED", "commandcenter-dev://app/", true); assert.equal(fakeWindow.loadURL.mock.calls.length, 1); yield* TestClock.adjust(100); assert.deepEqual(fakeWindow.loadURL.mock.calls, [ - ["t3code-dev://app/"], - ["t3code-dev://app/"], + ["commandcenter-dev://app/"], + ["commandcenter-dev://app/"], ]); assert.equal(fakeWindow.reload.mock.calls.length, 0); - didFailLoad({}, -9, "ERR_UNEXPECTED", "t3code-dev://app/", true); + didFailLoad({}, -9, "ERR_UNEXPECTED", "commandcenter-dev://app/", true); didFinishLoad(); yield* TestClock.adjust(250); assert.equal(fakeWindow.loadURL.mock.calls.length, 2); @@ -417,23 +418,23 @@ describe("DesktopWindow", () => { it("retries only transient failures for the development renderer", () => { assert.isTrue( DesktopWindow.isRetryableDevelopmentRendererLoadFailure({ - applicationUrl: "t3code-dev://app/", + applicationUrl: "commandcenter-dev://app/", errorCode: -102, isMainFrame: true, - validatedUrl: "t3code-dev://app/", + validatedUrl: "commandcenter-dev://app/", }), ); assert.isFalse( DesktopWindow.isRetryableDevelopmentRendererLoadFailure({ - applicationUrl: "t3code-dev://app/", + applicationUrl: "commandcenter-dev://app/", errorCode: -3, isMainFrame: true, - validatedUrl: "t3code-dev://app/", + validatedUrl: "commandcenter-dev://app/", }), ); assert.isFalse( DesktopWindow.isRetryableDevelopmentRendererLoadFailure({ - applicationUrl: "t3code-dev://app/", + applicationUrl: "commandcenter-dev://app/", errorCode: -102, isMainFrame: true, validatedUrl: "https://example.com/", diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 96e089b9183..12d8147b687 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -15,13 +15,13 @@ export default defineConfig({ tasks: { build: { command: "node scripts/build-preview-annotation-css.mjs && vp pack", - dependsOn: ["t3#build"], + dependsOn: ["@awtprod/command-center#build"], cache: false, }, dev: { command: "node scripts/build-preview-annotation-css.mjs && cross-env T3CODE_DESKTOP_DEV=1 vp pack --watch", - dependsOn: ["t3#build"], + dependsOn: ["@awtprod/command-center#build"], cache: false, }, "dev:bundle": { @@ -30,7 +30,7 @@ export default defineConfig({ }, "dev:electron": { command: "node scripts/dev-electron.mjs", - dependsOn: ["t3#build"], + dependsOn: ["@awtprod/command-center#build"], cache: false, }, }, diff --git a/apps/server/package.json b/apps/server/package.json index 5df71bf415f..0a13e8ac071 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -1,13 +1,14 @@ { - "name": "t3", + "name": "@awtprod/command-center", "version": "0.0.28", "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/pingdotgg/t3code", + "url": "https://github.com/awtprod/t3-code", "directory": "apps/server" }, "bin": { + "command-center": "./dist/bin.mjs", "t3": "./dist/bin.mjs" }, "files": [ diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts index 00b6c4cfcce..9224fbf0aae 100644 --- a/apps/server/scripts/cli.ts +++ b/apps/server/scripts/cli.ts @@ -205,7 +205,7 @@ const createVpPmPublishArgs = (config: PublishCommandConfig): ReadonlyArray Effect.Effect; } ->()("t3/auth/EnvironmentAuth") {} +>()("@awtprod/command-center/auth/EnvironmentAuth") {} type BootstrapExchangeResult = { readonly response: AuthBrowserSessionResult; diff --git a/apps/server/src/auth/EnvironmentAuthPolicy.ts b/apps/server/src/auth/EnvironmentAuthPolicy.ts index 7ffef0ff0a5..e8c5f4d4439 100644 --- a/apps/server/src/auth/EnvironmentAuthPolicy.ts +++ b/apps/server/src/auth/EnvironmentAuthPolicy.ts @@ -12,7 +12,7 @@ export class EnvironmentAuthPolicy extends Context.Service< { readonly getDescriptor: () => Effect.Effect; } ->()("t3/auth/EnvironmentAuthPolicy") {} +>()("@awtprod/command-center/auth/EnvironmentAuthPolicy") {} export const make = Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; diff --git a/apps/server/src/auth/PairingGrantStore.ts b/apps/server/src/auth/PairingGrantStore.ts index 588d5e3775f..bb7bb4c1582 100644 --- a/apps/server/src/auth/PairingGrantStore.ts +++ b/apps/server/src/auth/PairingGrantStore.ts @@ -216,7 +216,7 @@ export class PairingGrantStore extends Context.Service< }, ) => Effect.Effect; } ->()("t3/auth/PairingGrantStore") {} +>()("@awtprod/command-center/auth/PairingGrantStore") {} interface StoredBootstrapGrant extends BootstrapGrant { readonly remainingUses: number | "unbounded"; diff --git a/apps/server/src/auth/ServerSecretStore.ts b/apps/server/src/auth/ServerSecretStore.ts index 5e9890c1ea2..650307f9034 100644 --- a/apps/server/src/auth/ServerSecretStore.ts +++ b/apps/server/src/auth/ServerSecretStore.ts @@ -147,7 +147,7 @@ export class ServerSecretStore extends Context.Service< ) => Effect.Effect; readonly remove: (name: string) => Effect.Effect; } ->()("t3/auth/ServerSecretStore") {} +>()("@awtprod/command-center/auth/ServerSecretStore") {} export const make = Effect.gen(function* () { const crypto = yield* Crypto.Crypto; diff --git a/apps/server/src/auth/SessionStore.ts b/apps/server/src/auth/SessionStore.ts index 12ecb7dba4d..3984de4e0f2 100644 --- a/apps/server/src/auth/SessionStore.ts +++ b/apps/server/src/auth/SessionStore.ts @@ -397,7 +397,7 @@ export class SessionStore extends Context.Service< readonly markConnected: (sessionId: AuthSessionId) => Effect.Effect; readonly markDisconnected: (sessionId: AuthSessionId) => Effect.Effect; } ->()("t3/auth/SessionStore") {} +>()("@awtprod/command-center/auth/SessionStore") {} const SIGNING_SECRET_NAME = "server-signing-key"; const DEFAULT_SESSION_TTL = Duration.days(30); diff --git a/apps/server/src/auth/websocketOrigin.test.ts b/apps/server/src/auth/websocketOrigin.test.ts index 70698da1673..90ca3e34b7d 100644 --- a/apps/server/src/auth/websocketOrigin.test.ts +++ b/apps/server/src/auth/websocketOrigin.test.ts @@ -120,12 +120,18 @@ describe("decideWebSocketOrigin", () => { // The Electron renderer loads a custom scheme, which matches no Host by // construction and only ever compares equal as an exact string. it("allows the desktop renderer custom-scheme origins", () => { - for (const origin of ["t3code://app", "t3code-dev://app"]) { + const allowedOrigins = [ + "commandcenter://app", + "commandcenter-dev://app", + "t3code://app", + "t3code-dev://app", + ]; + for (const origin of allowedOrigins) { assert.deepEqual( decide({ origin, host: "127.0.0.1:13773", - allowedOrigins: ["t3code://app", "t3code-dev://app"], + allowedOrigins, }), { allowed: true, reason: "allow-listed" }, ); diff --git a/apps/server/src/auth/websocketOrigin.ts b/apps/server/src/auth/websocketOrigin.ts index c97c3b9d4bf..3c7ee9de62f 100644 --- a/apps/server/src/auth/websocketOrigin.ts +++ b/apps/server/src/auth/websocketOrigin.ts @@ -35,14 +35,14 @@ * 3. **Extra origins are allowed explicitly.** In dev the document is served by * Vite on another port and proxied, so the browser's `Origin` is the Vite * origin while `Host` is the backend's — `config.devUrl` closes that gap. The - * Electron renderer loads a custom scheme (`t3code://app`), which matches no + * Electron renderer loads a custom scheme (`commandcenter://app`), which matches no * `Host` by construction; those two origins are already enumerated in * `../http.ts` as `DESKTOP_RENDERER_ORIGINS` for the CORS layer. That entry * only works because the desktop app registers its scheme as *standard* * (`registerDesktopSchemesAsPrivileged` in `apps/desktop`) — a non-standard * custom scheme gets an opaque origin from Chromium and would arrive here as * `null`, which rule 4 refuses. Measured in a real renderer: without that - * registration the upgrade carries `Origin: null`; with it, `t3code://app`. + * registration the upgrade carries `Origin: null`; with it, `commandcenter://app`. * 4. **The literal `null` is refused.** It is what Chromium and Firefox send for * an opaque origin — a sandboxed iframe, a `data:` document — which is * exactly the shape hostile embedded content takes. It cannot be @@ -162,7 +162,7 @@ function originsEqual(left: string, right: string): boolean { const leftUrl = parseOrigin(left); const rightUrl = parseOrigin(right); if (leftUrl === undefined || rightUrl === undefined) { - // Custom schemes (`t3code://app`) do not always expose a hostname through + // Custom schemes (`commandcenter://app`) do not always expose a hostname through // `URL`, so fall back to an exact match on the serialised value. return left === right; } @@ -176,7 +176,7 @@ function originsEqual(left: string, right: string): boolean { function parseOrigin(value: string): URL | undefined { try { const url = new URL(value); - // A custom-scheme URL like `t3code://app` parses but exposes no hostname; + // A custom-scheme URL like `commandcenter://app` parses but exposes no hostname; // treat it as unparseable so callers fall back to exact comparison. return url.hostname === "" ? undefined : url; } catch { diff --git a/apps/server/src/checkpointing/CheckpointDiffQuery.ts b/apps/server/src/checkpointing/CheckpointDiffQuery.ts index 077506ff3a8..ab10db6cb08 100644 --- a/apps/server/src/checkpointing/CheckpointDiffQuery.ts +++ b/apps/server/src/checkpointing/CheckpointDiffQuery.ts @@ -55,7 +55,7 @@ export class CheckpointDiffQuery extends Context.Service< input: OrchestrationGetFullThreadDiffInput, ) => Effect.Effect; } ->()("t3/checkpointing/CheckpointDiffQuery") {} +>()("@awtprod/command-center/checkpointing/CheckpointDiffQuery") {} const isTurnDiffResult = Schema.is(OrchestrationGetTurnDiffResult); diff --git a/apps/server/src/checkpointing/CheckpointStore.ts b/apps/server/src/checkpointing/CheckpointStore.ts index f13aa4572c1..9e79d140d9f 100644 --- a/apps/server/src/checkpointing/CheckpointStore.ts +++ b/apps/server/src/checkpointing/CheckpointStore.ts @@ -94,7 +94,7 @@ export class CheckpointStore extends Context.Service< input: DeleteCheckpointRefsInput, ) => Effect.Effect; } ->()("t3/checkpointing/CheckpointStore") {} +>()("@awtprod/command-center/checkpointing/CheckpointStore") {} export const make = Effect.gen(function* () { const vcsRegistry = yield* VcsDriverRegistry.VcsDriverRegistry; diff --git a/apps/server/src/cloud/CliTokenManager.ts b/apps/server/src/cloud/CliTokenManager.ts index f01599bb96f..e4d038e7f3b 100644 --- a/apps/server/src/cloud/CliTokenManager.ts +++ b/apps/server/src/cloud/CliTokenManager.ts @@ -231,7 +231,7 @@ export class CloudCliTokenManager extends Context.Service< readonly store: (token: PersistedToken) => Effect.Effect; readonly clear: Effect.Effect; } ->()("t3/cloud/CliTokenManager/CloudCliTokenManager") {} +>()("@awtprod/command-center/cloud/CliTokenManager/CloudCliTokenManager") {} function stringToBytes(value: string): Uint8Array { return new TextEncoder().encode(value); diff --git a/apps/server/src/cloud/ManagedEndpointRuntime.ts b/apps/server/src/cloud/ManagedEndpointRuntime.ts index a1d7112a929..15bd3849423 100644 --- a/apps/server/src/cloud/ManagedEndpointRuntime.ts +++ b/apps/server/src/cloud/ManagedEndpointRuntime.ts @@ -59,7 +59,7 @@ export class CloudManagedEndpointRuntime extends Context.Service< config: RelayManagedEndpointRuntimeConfig | null, ) => Effect.Effect; } ->()("t3/cloud/ManagedEndpointRuntime/CloudManagedEndpointRuntime") {} +>()("@awtprod/command-center/cloud/ManagedEndpointRuntime/CloudManagedEndpointRuntime") {} interface ActiveConnector { readonly child: ChildProcessSpawner.ChildProcessHandle; diff --git a/apps/server/src/cloud/bootService.test.ts b/apps/server/src/cloud/bootService.test.ts index 07b19cfab5c..b38586c57f8 100644 --- a/apps/server/src/cloud/bootService.test.ts +++ b/apps/server/src/cloud/bootService.test.ts @@ -233,11 +233,18 @@ it.layer(NodeServices.layer)("BootService", (it) => { const runtimeDir = path.join(dirs.baseDir, "runtime", "versions", "0.0.27"); assert.equal( plan.t3EntryPath, - path.join(runtimeDir, "node_modules", "t3", "dist", "bin.mjs"), + path.join(runtimeDir, "node_modules", "@awtprod", "command-center", "dist", "bin.mjs"), ); assert.deepEqual(commands[0], { command: "npm", - args: ["install", "--prefix", runtimeDir, "--no-fund", "--no-audit", "t3@0.0.27"], + args: [ + "install", + "--prefix", + runtimeDir, + "--no-fund", + "--no-audit", + "@awtprod/command-center@0.0.27", + ], }); // Success is recorded via a sentinel so interrupted installs re-run. assert.isTrue(yield* fs.exists(path.join(runtimeDir, ".install-complete"))); diff --git a/apps/server/src/cloud/bootService.ts b/apps/server/src/cloud/bootService.ts index 2bf0aeea0c9..19bb3769afb 100644 --- a/apps/server/src/cloud/bootService.ts +++ b/apps/server/src/cloud/bootService.ts @@ -73,7 +73,7 @@ export function quoteSystemdValue(value: string): string { export interface BootServicePlan { /** Absolute path of the node binary running this CLI. */ readonly nodePath: string; - /** Absolute path of the pinned t3 entry point the unit will run. */ + /** Absolute path of the pinned Command Center entry point the unit will run. */ readonly t3EntryPath: string; readonly baseDir: string; readonly logPath: string; @@ -176,7 +176,7 @@ export class BootService extends Context.Service< readonly uninstall: Effect.Effect; readonly status: Effect.Effect; } ->()("t3/cloud/bootService") {} +>()("@awtprod/command-center/cloud/bootService") {} export interface BootServiceHost { readonly execPath: string; @@ -212,7 +212,14 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: { "versions", input.cliVersion, ); - const runtimeEntryPath = path.join(runtimeVersionDir, "node_modules", "t3", "dist", "bin.mjs"); + const runtimeEntryPath = path.join( + runtimeVersionDir, + "node_modules", + "@awtprod", + "command-center", + "dist", + "bin.mjs", + ); const runtimeSentinelPath = path.join(runtimeVersionDir, ".install-complete"); const requireSystemdLinux = Effect.gen(function* () { @@ -257,7 +264,7 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: { * install (global bin, repo checkout) is used as-is; an ephemeral cache * entry is replaced by `npm install --prefix`-ing the exact running * version into /runtime/versions/. A real install (not a copy - * of bin.mjs) because t3 ships native deps like node-pty. + * of bin.mjs) because Command Center ships native deps like node-pty. */ const ensurePinnedRuntime = Effect.gen(function* () { if (!isEphemeralCacheEntry(host.cliEntryPath)) { @@ -282,7 +289,7 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: { Effect.mapError((cause) => new BootServiceInstallError({ cause })), ); yield* runStep( - "installing the pinned t3 runtime (this can take a few minutes)", + "installing the pinned Command Center runtime (this can take a few minutes)", "npm", [ "install", @@ -290,7 +297,7 @@ export const make = Effect.fn("cloud.boot_service.make")(function* (input: { runtimeVersionDir, "--no-fund", "--no-audit", - `t3@${input.cliVersion}`, + `@awtprod/command-center@${input.cliVersion}`, ], // Native deps (node-pty) can compile from source on slow boxes; the // ProcessRunner default of 60s would kill a healthy install. diff --git a/apps/server/src/command-center/AutomationDefinitionConfig.ts b/apps/server/src/command-center/AutomationDefinitionConfig.ts index 2d221847179..0c187d1e1a5 100644 --- a/apps/server/src/command-center/AutomationDefinitionConfig.ts +++ b/apps/server/src/command-center/AutomationDefinitionConfig.ts @@ -140,7 +140,7 @@ export interface AutomationDefinitionConfigShape { export class AutomationDefinitionConfig extends Context.Service< AutomationDefinitionConfig, AutomationDefinitionConfigShape ->()("t3/command-center/AutomationDefinitionConfig") {} +>()("@awtprod/command-center/command-center/AutomationDefinitionConfig") {} interface LoadedSource { readonly configDirectory: string; diff --git a/apps/server/src/command-center/AutomationRuns.ts b/apps/server/src/command-center/AutomationRuns.ts index d223cd2b3d5..2edef6b17e0 100644 --- a/apps/server/src/command-center/AutomationRuns.ts +++ b/apps/server/src/command-center/AutomationRuns.ts @@ -96,7 +96,7 @@ export interface AutomationRecoveryReport { } export class AutomationRuns extends Context.Service()( - "t3/command-center/AutomationRuns", + "@awtprod/command-center/command-center/AutomationRuns", ) {} export const layer = Layer.effect( diff --git a/apps/server/src/command-center/Config.ts b/apps/server/src/command-center/Config.ts index 807eb8b3a02..334a64d27c9 100644 --- a/apps/server/src/command-center/Config.ts +++ b/apps/server/src/command-center/Config.ts @@ -109,7 +109,7 @@ export interface CommandCenterConfigShape { export class CommandCenterConfig extends Context.Service< CommandCenterConfig, CommandCenterConfigShape ->()("t3/command-center/Config/CommandCenterConfig") {} +>()("@awtprod/command-center/command-center/Config/CommandCenterConfig") {} const decodeRoot = Schema.decodeUnknownEffect(RootConfigFile); const decodeSpace = Schema.decodeUnknownEffect(SpaceConfigFile); diff --git a/apps/server/src/command-center/ConnectionHealth.ts b/apps/server/src/command-center/ConnectionHealth.ts index d68653a8757..be59123bbee 100644 --- a/apps/server/src/command-center/ConnectionHealth.ts +++ b/apps/server/src/command-center/ConnectionHealth.ts @@ -39,7 +39,7 @@ export interface ConnectionHealthShape { } export class ConnectionHealth extends Context.Service()( - "t3/command-center/ConnectionHealth", + "@awtprod/command-center/command-center/ConnectionHealth", ) {} const persistenceError = (cause: unknown) => diff --git a/apps/server/src/command-center/CredentialStore.ts b/apps/server/src/command-center/CredentialStore.ts index 2fb226a3036..2b2b302b556 100644 --- a/apps/server/src/command-center/CredentialStore.ts +++ b/apps/server/src/command-center/CredentialStore.ts @@ -157,7 +157,7 @@ export interface CommandCenterCredentialStoreShape { export class CommandCenterCredentialStore extends Context.Service< CommandCenterCredentialStore, CommandCenterCredentialStoreShape ->()("t3/command-center/CredentialStore/CommandCenterCredentialStore") {} +>()("@awtprod/command-center/command-center/CredentialStore/CommandCenterCredentialStore") {} const isCredentialStoreError = Schema.is(CommandCenterCredentialStoreError); diff --git a/apps/server/src/command-center/EventStream.ts b/apps/server/src/command-center/EventStream.ts index 4364d3c3d10..f6c0db152b5 100644 --- a/apps/server/src/command-center/EventStream.ts +++ b/apps/server/src/command-center/EventStream.ts @@ -388,7 +388,7 @@ export interface CommandCenterEventStreamShape { export class CommandCenterEventStream extends Context.Service< CommandCenterEventStream, CommandCenterEventStreamShape ->()("t3/command-center/EventStream/CommandCenterEventStream") {} +>()("@awtprod/command-center/command-center/EventStream/CommandCenterEventStream") {} const make = Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; diff --git a/apps/server/src/command-center/GoogleReadConnector.ts b/apps/server/src/command-center/GoogleReadConnector.ts index a9900b50f9d..7906878c2a4 100644 --- a/apps/server/src/command-center/GoogleReadConnector.ts +++ b/apps/server/src/command-center/GoogleReadConnector.ts @@ -182,7 +182,7 @@ export interface GoogleReadConnectorShape { export class GoogleReadConnector extends Context.Service< GoogleReadConnector, GoogleReadConnectorShape ->()("t3/command-center/GoogleReadConnector") {} +>()("@awtprod/command-center/command-center/GoogleReadConnector") {} const decodeUnknownJsonString = Schema.decodeUnknownEffect(Schema.UnknownFromJsonString); const decodeArtifact = Schema.decodeUnknownEffect(Artifact); diff --git a/apps/server/src/command-center/MemorySearchIndex.ts b/apps/server/src/command-center/MemorySearchIndex.ts index aed1e10ec9f..09d19ca2fdb 100644 --- a/apps/server/src/command-center/MemorySearchIndex.ts +++ b/apps/server/src/command-center/MemorySearchIndex.ts @@ -64,7 +64,7 @@ export interface MemorySearchIndexShape { } export class MemorySearchIndex extends Context.Service()( - "t3/command-center/MemorySearchIndex", + "@awtprod/command-center/command-center/MemorySearchIndex", ) {} interface SourceMemoryRow { diff --git a/apps/server/src/command-center/ReadinessGate.ts b/apps/server/src/command-center/ReadinessGate.ts index ab75684103b..a4563f1287e 100644 --- a/apps/server/src/command-center/ReadinessGate.ts +++ b/apps/server/src/command-center/ReadinessGate.ts @@ -30,7 +30,7 @@ export interface CommandCenterReadinessGateShape { export class CommandCenterReadinessGate extends Context.Service< CommandCenterReadinessGate, CommandCenterReadinessGateShape ->()("t3/command-center/ReadinessGate/CommandCenterReadinessGate") {} +>()("@awtprod/command-center/command-center/ReadinessGate/CommandCenterReadinessGate") {} export const makeCommandCenterReadinessGate = Effect.gen(function* () { const state = yield* Ref.make("pending"); diff --git a/apps/server/src/command-center/RunDispatcher.ts b/apps/server/src/command-center/RunDispatcher.ts index 893cfde43bd..fe2276e5e8c 100644 --- a/apps/server/src/command-center/RunDispatcher.ts +++ b/apps/server/src/command-center/RunDispatcher.ts @@ -120,7 +120,7 @@ export interface RunDispatcherShape { } export class RunDispatcher extends Context.Service()( - "t3/command-center/RunDispatcher", + "@awtprod/command-center/command-center/RunDispatcher", ) {} export interface StoredRun { diff --git a/apps/server/src/command-center/RunLifecycle.ts b/apps/server/src/command-center/RunLifecycle.ts index 58c22159943..a82bd0ab844 100644 --- a/apps/server/src/command-center/RunLifecycle.ts +++ b/apps/server/src/command-center/RunLifecycle.ts @@ -401,7 +401,7 @@ export interface RunLifecycleShape { } export class RunLifecycle extends Context.Service()( - "t3/command-center/RunLifecycle", + "@awtprod/command-center/command-center/RunLifecycle", ) {} interface RuntimeDependencies { diff --git a/apps/server/src/command-center/RunRecoveryCoordinator.ts b/apps/server/src/command-center/RunRecoveryCoordinator.ts index 661319e058a..f415733a221 100644 --- a/apps/server/src/command-center/RunRecoveryCoordinator.ts +++ b/apps/server/src/command-center/RunRecoveryCoordinator.ts @@ -45,7 +45,7 @@ export interface RunRecoveryCoordinatorShape { export class RunRecoveryCoordinator extends Context.Service< RunRecoveryCoordinator, RunRecoveryCoordinatorShape ->()("t3/command-center/RunRecoveryCoordinator") {} +>()("@awtprod/command-center/command-center/RunRecoveryCoordinator") {} export interface RunRecoveryDependencies { readonly syncConfiguration: Effect.Effect; diff --git a/apps/server/src/command-center/Service.ts b/apps/server/src/command-center/Service.ts index 878021ce68d..5830e420d06 100644 --- a/apps/server/src/command-center/Service.ts +++ b/apps/server/src/command-center/Service.ts @@ -613,7 +613,7 @@ export interface CommandCenterServiceShape { export class CommandCenterService extends Context.Service< CommandCenterService, CommandCenterServiceShape ->()("t3/command-center/Service/CommandCenterService") {} +>()("@awtprod/command-center/command-center/Service/CommandCenterService") {} export function itemNeedsYou(item: Pick): boolean { return ( diff --git a/apps/server/src/command-center/WebhookAdmission.ts b/apps/server/src/command-center/WebhookAdmission.ts index b37592c1bf9..83dc9a105e5 100644 --- a/apps/server/src/command-center/WebhookAdmission.ts +++ b/apps/server/src/command-center/WebhookAdmission.ts @@ -199,7 +199,7 @@ export interface WebhookAdmissionShape { } export class WebhookAdmission extends Context.Service()( - "t3/command-center/WebhookAdmission", + "@awtprod/command-center/command-center/WebhookAdmission", ) {} export function makeWebhookAdmission(dependencies: { diff --git a/apps/server/src/command-center/automation/AutomationScopedShell.ts b/apps/server/src/command-center/automation/AutomationScopedShell.ts index 232c007f975..de226423264 100644 --- a/apps/server/src/command-center/automation/AutomationScopedShell.ts +++ b/apps/server/src/command-center/automation/AutomationScopedShell.ts @@ -111,7 +111,7 @@ export class AutomationScopedShell extends Context.Service< input: AutomationScopedShellRequest, ) => Effect.Effect; } ->()("t3/command-center/automation/AutomationScopedShell") {} +>()("@awtprod/command-center/command-center/automation/AutomationScopedShell") {} interface SecureFileIdentity { readonly type: FileSystem.File.Type; diff --git a/apps/server/src/command-center/automation/RecoveryCoordinator.ts b/apps/server/src/command-center/automation/RecoveryCoordinator.ts index c8ba9aff808..f26b486c05c 100644 --- a/apps/server/src/command-center/automation/RecoveryCoordinator.ts +++ b/apps/server/src/command-center/automation/RecoveryCoordinator.ts @@ -20,7 +20,9 @@ export interface AutomationRecoveryCoordinatorShape { export class AutomationRecoveryCoordinator extends Context.Service< AutomationRecoveryCoordinator, AutomationRecoveryCoordinatorShape ->()("t3/command-center/automation/RecoveryCoordinator/AutomationRecoveryCoordinator") {} +>()( + "@awtprod/command-center/command-center/automation/RecoveryCoordinator/AutomationRecoveryCoordinator", +) {} export const make = Effect.gen(function* () { const runs = yield* AutomationRuns.AutomationRuns; diff --git a/apps/server/src/command-center/automation/Runtime.ts b/apps/server/src/command-center/automation/Runtime.ts index 42caf595dcb..110ef2b6d32 100644 --- a/apps/server/src/command-center/automation/Runtime.ts +++ b/apps/server/src/command-center/automation/Runtime.ts @@ -238,7 +238,7 @@ export interface AutomationRuntimeShape { } export class AutomationRuntime extends Context.Service()( - "t3/command-center/automation/Runtime/AutomationRuntime", + "@awtprod/command-center/command-center/automation/Runtime/AutomationRuntime", ) {} interface StoredAutomationRow { diff --git a/apps/server/src/command-center/automation/ScheduleRunner.ts b/apps/server/src/command-center/automation/ScheduleRunner.ts index f69a01227eb..881deae555e 100644 --- a/apps/server/src/command-center/automation/ScheduleRunner.ts +++ b/apps/server/src/command-center/automation/ScheduleRunner.ts @@ -61,7 +61,7 @@ export interface AutomationScheduleRunnerShape { export class AutomationScheduleRunner extends Context.Service< AutomationScheduleRunner, AutomationScheduleRunnerShape ->()("t3/command-center/automation/ScheduleRunner/AutomationScheduleRunner") {} +>()("@awtprod/command-center/command-center/automation/ScheduleRunner/AutomationScheduleRunner") {} function occurrenceMinute(source: string) { const parsed = DateTime.make(source); diff --git a/apps/server/src/command-center/automation/TriggerCoordinator.ts b/apps/server/src/command-center/automation/TriggerCoordinator.ts index bbf9793c0a6..b67864df410 100644 --- a/apps/server/src/command-center/automation/TriggerCoordinator.ts +++ b/apps/server/src/command-center/automation/TriggerCoordinator.ts @@ -224,7 +224,9 @@ export function webhookIdempotencyKey(input: { export class AutomationTriggerCoordinator extends Context.Service< AutomationTriggerCoordinator, AutomationTriggerCoordinatorShape ->()("t3/command-center/automation/TriggerCoordinator/AutomationTriggerCoordinator") {} +>()( + "@awtprod/command-center/command-center/automation/TriggerCoordinator/AutomationTriggerCoordinator", +) {} export const make = Effect.gen(function* () { const commandCenter = yield* CommandCenterService.CommandCenterService; diff --git a/apps/server/src/command-center/automation/VerifiedScopedShell.ts b/apps/server/src/command-center/automation/VerifiedScopedShell.ts index 0cb3397f600..e628613610d 100644 --- a/apps/server/src/command-center/automation/VerifiedScopedShell.ts +++ b/apps/server/src/command-center/automation/VerifiedScopedShell.ts @@ -650,7 +650,9 @@ export class VerifiedLinuxScopedShell extends Context.Service< input: VerifiedScopedShellExecuteInput, ) => Effect.Effect; } ->()("t3/command-center/automation/VerifiedScopedShell/VerifiedLinuxScopedShell") {} +>()( + "@awtprod/command-center/command-center/automation/VerifiedScopedShell/VerifiedLinuxScopedShell", +) {} interface VerifiedLinuxScopedShellTestingOptions { readonly spawnSettlementDelayMs: number; diff --git a/apps/server/src/config.ts b/apps/server/src/config.ts index 61bec53014d..5470985f368 100644 --- a/apps/server/src/config.ts +++ b/apps/server/src/config.ts @@ -97,7 +97,7 @@ export class ServerConfig extends Context.Service< /** HTTPS port Tailscale Serve publishes the gateway on, when Serve is enabled. */ readonly previewGatewayServePort: number; } ->()("t3/config/ServerConfig") { +>()("@awtprod/command-center/config/ServerConfig") { /** @deprecated Import and use `layerTest` from this module. */ static readonly layerTest = ( cwd: string, diff --git a/apps/server/src/database/SupabaseMcpConnector.ts b/apps/server/src/database/SupabaseMcpConnector.ts index f24ae3f2533..43d96a2a7b5 100644 --- a/apps/server/src/database/SupabaseMcpConnector.ts +++ b/apps/server/src/database/SupabaseMcpConnector.ts @@ -173,7 +173,7 @@ export function makeSupabaseMcpConnector(input: { export class SupabaseMcpConnector extends Context.Service< SupabaseMcpConnector, SupabaseMcpConnectorShape ->()("t3/database/SupabaseMcpConnector") {} +>()("@awtprod/command-center/database/SupabaseMcpConnector") {} const make = Effect.gen(function* () { const serverSettings = yield* ServerSettingsModule.ServerSettingsService; diff --git a/apps/server/src/diagnostics/ProcessDiagnostics.ts b/apps/server/src/diagnostics/ProcessDiagnostics.ts index b39d560a228..79c5ee6c4db 100644 --- a/apps/server/src/diagnostics/ProcessDiagnostics.ts +++ b/apps/server/src/diagnostics/ProcessDiagnostics.ts @@ -41,7 +41,7 @@ export class ProcessDiagnostics extends Context.Service< readonly signal: ServerProcessSignal; }) => Effect.Effect; } ->()("t3/diagnostics/ProcessDiagnostics") {} +>()("@awtprod/command-center/diagnostics/ProcessDiagnostics") {} class ProcessDiagnosticsQueryTimeoutError extends Schema.TaggedErrorClass()( "ProcessDiagnosticsQueryTimeoutError", diff --git a/apps/server/src/diagnostics/ProcessResourceMonitor.ts b/apps/server/src/diagnostics/ProcessResourceMonitor.ts index 6030e4172e1..dea72191d06 100644 --- a/apps/server/src/diagnostics/ProcessResourceMonitor.ts +++ b/apps/server/src/diagnostics/ProcessResourceMonitor.ts @@ -58,7 +58,7 @@ export class ProcessResourceMonitor extends Context.Service< input: ServerProcessResourceHistoryInput, ) => Effect.Effect; } ->()("t3/diagnostics/ProcessResourceMonitor") {} +>()("@awtprod/command-center/diagnostics/ProcessResourceMonitor") {} function dateTimeFromMillis(ms: number): DateTime.Utc { return DateTime.makeUnsafe(ms); diff --git a/apps/server/src/diagnostics/TraceDiagnostics.ts b/apps/server/src/diagnostics/TraceDiagnostics.ts index d54e033380c..c2b5f73af3b 100644 --- a/apps/server/src/diagnostics/TraceDiagnostics.ts +++ b/apps/server/src/diagnostics/TraceDiagnostics.ts @@ -61,7 +61,7 @@ export class TraceDiagnostics extends Context.Service< options: TraceDiagnosticsOptions, ) => Effect.Effect; } ->()("t3/diagnostics/TraceDiagnostics") {} +>()("@awtprod/command-center/diagnostics/TraceDiagnostics") {} interface TraceDiagnosticsInput { readonly traceFilePath: string; diff --git a/apps/server/src/environment/ServerEnvironment.ts b/apps/server/src/environment/ServerEnvironment.ts index b5fbd8e1088..0175a04dfdd 100644 --- a/apps/server/src/environment/ServerEnvironment.ts +++ b/apps/server/src/environment/ServerEnvironment.ts @@ -32,7 +32,7 @@ export class ServerEnvironment extends Context.Service< readonly getEnvironmentId: Effect.Effect; readonly getDescriptor: Effect.Effect; } ->()("t3/environment/ServerEnvironment") {} +>()("@awtprod/command-center/environment/ServerEnvironment") {} function platformOs(platform: NodeJS.Platform): ExecutionEnvironmentDescriptor["platform"]["os"] { switch (platform) { diff --git a/apps/server/src/git/GitManager.ts b/apps/server/src/git/GitManager.ts index f1fb03e7e45..ccbe574980e 100644 --- a/apps/server/src/git/GitManager.ts +++ b/apps/server/src/git/GitManager.ts @@ -87,7 +87,7 @@ export class GitManager extends Context.Service< options?: GitRunStackedActionOptions, ) => Effect.Effect; } ->()("t3/git/GitManager") {} +>()("@awtprod/command-center/git/GitManager") {} const COMMIT_TIMEOUT_MS = 10 * 60_000; const MAX_PROGRESS_TEXT_LENGTH = 500; diff --git a/apps/server/src/git/GitWorkflowService.ts b/apps/server/src/git/GitWorkflowService.ts index 100b9beadba..88ac4e84a90 100644 --- a/apps/server/src/git/GitWorkflowService.ts +++ b/apps/server/src/git/GitWorkflowService.ts @@ -92,7 +92,7 @@ export class GitWorkflowService extends Context.Service< readonly newBranch: string; }) => Effect.Effect<{ readonly branch: string }, GitManagerServiceError>; } ->()("t3/git/GitWorkflowService") {} +>()("@awtprod/command-center/git/GitWorkflowService") {} function nonRepositoryLocalStatus(): VcsStatusLocalResult { return { diff --git a/apps/server/src/httpCors.ts b/apps/server/src/httpCors.ts index 30f27042396..79f88993908 100644 --- a/apps/server/src/httpCors.ts +++ b/apps/server/src/httpCors.ts @@ -7,7 +7,13 @@ * check (`./ws.ts`) so the two cannot drift into disagreeing about which * clients are legitimate. */ -export const DESKTOP_RENDERER_ORIGINS = ["t3code://app", "t3code-dev://app"] as const; +export const DESKTOP_RENDERER_ORIGINS = [ + "commandcenter://app", + "commandcenter-dev://app", + // Retained for the existing T3 mobile clients and upstream-compatible builds. + "t3code://app", + "t3code-dev://app", +] as const; export const browserApiCorsAllowedMethods = ["GET", "POST", "OPTIONS"] as const; export const browserApiCorsAllowedHeaders = [ diff --git a/apps/server/src/keybindings.ts b/apps/server/src/keybindings.ts index 18a78fe3623..aacd130e685 100644 --- a/apps/server/src/keybindings.ts +++ b/apps/server/src/keybindings.ts @@ -285,7 +285,7 @@ export class Keybindings extends Context.Service< input: ServerRemoveKeybindingInput, ) => Effect.Effect; } ->()("t3/keybindings") {} +>()("@awtprod/command-center/keybindings") {} const make = Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig.ServerConfig; diff --git a/apps/server/src/mcp/McpInvocationContext.ts b/apps/server/src/mcp/McpInvocationContext.ts index 044ee1bf74a..4c6a81b9673 100644 --- a/apps/server/src/mcp/McpInvocationContext.ts +++ b/apps/server/src/mcp/McpInvocationContext.ts @@ -34,7 +34,7 @@ export interface McpInvocationScope { export class McpInvocationContext extends Context.Service< McpInvocationContext, McpInvocationScope ->()("t3/mcp/McpInvocationContext") {} +>()("@awtprod/command-center/mcp/McpInvocationContext") {} export const requirePreviewCapability = Effect.fn("mcp.requirePreviewCapability")(function* () { const invocation = yield* McpInvocationContext; diff --git a/apps/server/src/mcp/McpSessionRegistry.ts b/apps/server/src/mcp/McpSessionRegistry.ts index 70efcf108fd..451d94a8167 100644 --- a/apps/server/src/mcp/McpSessionRegistry.ts +++ b/apps/server/src/mcp/McpSessionRegistry.ts @@ -49,7 +49,7 @@ export interface McpSessionRegistryShape { export class McpSessionRegistry extends Context.Service< McpSessionRegistry, McpSessionRegistryShape ->()("t3/mcp/McpSessionRegistry") {} +>()("@awtprod/command-center/mcp/McpSessionRegistry") {} interface CredentialRecord { readonly tokenHash: string; diff --git a/apps/server/src/mcp/PreviewAutomationBroker.ts b/apps/server/src/mcp/PreviewAutomationBroker.ts index 7ed77aabdf1..16894880df5 100644 --- a/apps/server/src/mcp/PreviewAutomationBroker.ts +++ b/apps/server/src/mcp/PreviewAutomationBroker.ts @@ -57,7 +57,7 @@ export class PreviewAutomationBroker extends Context.Service< request: PreviewAutomationInvokeInput, ) => Effect.Effect; } ->()("t3/mcp/PreviewAutomationBroker") {} +>()("@awtprod/command-center/mcp/PreviewAutomationBroker") {} interface ClientConnection { readonly clientId: string; diff --git a/apps/server/src/observability/BrowserTraceCollector.ts b/apps/server/src/observability/BrowserTraceCollector.ts index 300a50fe330..1025a06b864 100644 --- a/apps/server/src/observability/BrowserTraceCollector.ts +++ b/apps/server/src/observability/BrowserTraceCollector.ts @@ -8,7 +8,7 @@ export class BrowserTraceCollector extends Context.Service< { readonly record: (records: ReadonlyArray) => Effect.Effect; } ->()("t3/observability/BrowserTraceCollector") {} +>()("@awtprod/command-center/observability/BrowserTraceCollector") {} export const make = (sink: TraceSink): BrowserTraceCollector["Service"] => BrowserTraceCollector.of({ diff --git a/apps/server/src/orchestration/CommandDispatcher.ts b/apps/server/src/orchestration/CommandDispatcher.ts index d2ff45a9bf7..a92824d2302 100644 --- a/apps/server/src/orchestration/CommandDispatcher.ts +++ b/apps/server/src/orchestration/CommandDispatcher.ts @@ -86,7 +86,7 @@ export interface OrchestrationCommandDispatcherShape { export class OrchestrationCommandDispatcher extends Context.Service< OrchestrationCommandDispatcher, OrchestrationCommandDispatcherShape ->()("t3/orchestration/CommandDispatcher/OrchestrationCommandDispatcher") {} +>()("@awtprod/command-center/orchestration/CommandDispatcher/OrchestrationCommandDispatcher") {} export const make = Effect.gen(function* () { const crypto = yield* Crypto.Crypto; diff --git a/apps/server/src/orchestration/Services/CheckpointReactor.ts b/apps/server/src/orchestration/Services/CheckpointReactor.ts index bd3ee3e88f9..ddcb1dab817 100644 --- a/apps/server/src/orchestration/Services/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Services/CheckpointReactor.ts @@ -36,5 +36,5 @@ export interface CheckpointReactorShape { * CheckpointReactor - Service tag for checkpoint reactor workers. */ export class CheckpointReactor extends Context.Service()( - "t3/orchestration/Services/CheckpointReactor", + "@awtprod/command-center/orchestration/Services/CheckpointReactor", ) {} diff --git a/apps/server/src/orchestration/Services/OrchestrationEngine.ts b/apps/server/src/orchestration/Services/OrchestrationEngine.ts index dc1b5fd3003..d97f7804508 100644 --- a/apps/server/src/orchestration/Services/OrchestrationEngine.ts +++ b/apps/server/src/orchestration/Services/OrchestrationEngine.ts @@ -72,4 +72,6 @@ export interface OrchestrationEngineShape { export class OrchestrationEngineService extends Context.Service< OrchestrationEngineService, OrchestrationEngineShape ->()("t3/orchestration/Services/OrchestrationEngine/OrchestrationEngineService") {} +>()( + "@awtprod/command-center/orchestration/Services/OrchestrationEngine/OrchestrationEngineService", +) {} diff --git a/apps/server/src/orchestration/Services/OrchestrationReactor.ts b/apps/server/src/orchestration/Services/OrchestrationReactor.ts index 76e9643eb0f..d8ac572e823 100644 --- a/apps/server/src/orchestration/Services/OrchestrationReactor.ts +++ b/apps/server/src/orchestration/Services/OrchestrationReactor.ts @@ -35,4 +35,4 @@ export interface OrchestrationReactorShape { export class OrchestrationReactor extends Context.Service< OrchestrationReactor, OrchestrationReactorShape ->()("t3/orchestration/Services/OrchestrationReactor") {} +>()("@awtprod/command-center/orchestration/Services/OrchestrationReactor") {} diff --git a/apps/server/src/orchestration/Services/ProjectionPipeline.ts b/apps/server/src/orchestration/Services/ProjectionPipeline.ts index bb4736ca57a..beb7d9456ec 100644 --- a/apps/server/src/orchestration/Services/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Services/ProjectionPipeline.ts @@ -39,4 +39,6 @@ export interface OrchestrationProjectionPipelineShape { export class OrchestrationProjectionPipeline extends Context.Service< OrchestrationProjectionPipeline, OrchestrationProjectionPipelineShape ->()("t3/orchestration/Services/ProjectionPipeline/OrchestrationProjectionPipeline") {} +>()( + "@awtprod/command-center/orchestration/Services/ProjectionPipeline/OrchestrationProjectionPipeline", +) {} diff --git a/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts b/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts index 23b291d8778..adccde2974e 100644 --- a/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts +++ b/apps/server/src/orchestration/Services/ProjectionSnapshotQuery.ts @@ -176,4 +176,4 @@ export interface ProjectionSnapshotQueryShape { export class ProjectionSnapshotQuery extends Context.Service< ProjectionSnapshotQuery, ProjectionSnapshotQueryShape ->()("t3/orchestration/Services/ProjectionSnapshotQuery") {} +>()("@awtprod/command-center/orchestration/Services/ProjectionSnapshotQuery") {} diff --git a/apps/server/src/orchestration/Services/ProviderCommandReactor.ts b/apps/server/src/orchestration/Services/ProviderCommandReactor.ts index 65aa9949fe1..f62ce8db796 100644 --- a/apps/server/src/orchestration/Services/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Services/ProviderCommandReactor.ts @@ -38,4 +38,4 @@ export interface ProviderCommandReactorShape { export class ProviderCommandReactor extends Context.Service< ProviderCommandReactor, ProviderCommandReactorShape ->()("t3/orchestration/Services/ProviderCommandReactor") {} +>()("@awtprod/command-center/orchestration/Services/ProviderCommandReactor") {} diff --git a/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts index e2c9043402d..a33c6fc31aa 100644 --- a/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts +++ b/apps/server/src/orchestration/Services/ProviderRuntimeIngestion.ts @@ -44,4 +44,6 @@ export interface ProviderRuntimeIngestionShape { export class ProviderRuntimeIngestionService extends Context.Service< ProviderRuntimeIngestionService, ProviderRuntimeIngestionShape ->()("t3/orchestration/Services/ProviderRuntimeIngestion/ProviderRuntimeIngestionService") {} +>()( + "@awtprod/command-center/orchestration/Services/ProviderRuntimeIngestion/ProviderRuntimeIngestionService", +) {} diff --git a/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts b/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts index 0b880ee6999..8b35c2617b9 100644 --- a/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts +++ b/apps/server/src/orchestration/Services/RuntimeReceiptBus.ts @@ -62,5 +62,5 @@ export interface RuntimeReceiptBusShape { } export class RuntimeReceiptBus extends Context.Service()( - "t3/orchestration/Services/RuntimeReceiptBus", + "@awtprod/command-center/orchestration/Services/RuntimeReceiptBus", ) {} diff --git a/apps/server/src/orchestration/Services/StalledTurnWatchdog.ts b/apps/server/src/orchestration/Services/StalledTurnWatchdog.ts index 52e0788991c..cd29586fce4 100644 --- a/apps/server/src/orchestration/Services/StalledTurnWatchdog.ts +++ b/apps/server/src/orchestration/Services/StalledTurnWatchdog.ts @@ -17,4 +17,4 @@ export interface StalledTurnWatchdogShape { export class StalledTurnWatchdog extends Context.Service< StalledTurnWatchdog, StalledTurnWatchdogShape ->()("t3/orchestration/Services/StalledTurnWatchdog") {} +>()("@awtprod/command-center/orchestration/Services/StalledTurnWatchdog") {} diff --git a/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts b/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts index 7c6718965a6..8fd1e4b4c2f 100644 --- a/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts +++ b/apps/server/src/orchestration/Services/ThreadDeletionReactor.ts @@ -35,4 +35,4 @@ export interface ThreadDeletionReactorShape { export class ThreadDeletionReactor extends Context.Service< ThreadDeletionReactor, ThreadDeletionReactorShape ->()("t3/orchestration/Services/ThreadDeletionReactor") {} +>()("@awtprod/command-center/orchestration/Services/ThreadDeletionReactor") {} diff --git a/apps/server/src/persistence/AuthPairingLinks.ts b/apps/server/src/persistence/AuthPairingLinks.ts index e54c977e7ab..dfd96f7126a 100644 --- a/apps/server/src/persistence/AuthPairingLinks.ts +++ b/apps/server/src/persistence/AuthPairingLinks.ts @@ -102,7 +102,7 @@ export class AuthPairingLinkRepository extends Context.Service< input: GetAuthPairingLinkByCredentialInput, ) => Effect.Effect, AuthPairingLinkRepositoryError>; } ->()("t3/persistence/AuthPairingLinks/AuthPairingLinkRepository") {} +>()("@awtprod/command-center/persistence/AuthPairingLinks/AuthPairingLinkRepository") {} function toPersistenceSqlOrDecodeError( sqlOperation: string, diff --git a/apps/server/src/persistence/AuthSessions.ts b/apps/server/src/persistence/AuthSessions.ts index 545688e3822..d690fb6f690 100644 --- a/apps/server/src/persistence/AuthSessions.ts +++ b/apps/server/src/persistence/AuthSessions.ts @@ -104,7 +104,7 @@ export class AuthSessionRepository extends Context.Service< input: SetAuthSessionLastConnectedAtInput, ) => Effect.Effect; } ->()("t3/persistence/AuthSessions/AuthSessionRepository") {} +>()("@awtprod/command-center/persistence/AuthSessions/AuthSessionRepository") {} const AuthSessionDbRow = Schema.Struct({ sessionId: AuthSessionId, diff --git a/apps/server/src/persistence/ProviderSessionRuntime.ts b/apps/server/src/persistence/ProviderSessionRuntime.ts index 2ccdd862522..c70683e78e1 100644 --- a/apps/server/src/persistence/ProviderSessionRuntime.ts +++ b/apps/server/src/persistence/ProviderSessionRuntime.ts @@ -100,7 +100,9 @@ export class ProviderSessionRuntimeRepository extends Context.Service< input: DeleteProviderSessionRuntimeInput, ) => Effect.Effect; } ->()("t3/persistence/ProviderSessionRuntime/ProviderSessionRuntimeRepository") {} +>()( + "@awtprod/command-center/persistence/ProviderSessionRuntime/ProviderSessionRuntimeRepository", +) {} const ProviderSessionRuntimeDbRowSchema = ProviderSessionRuntime.mapFields( Struct.assign({ diff --git a/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts b/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts index 1498984827e..1dc09381a72 100644 --- a/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts +++ b/apps/server/src/persistence/Services/OrchestrationCommandReceipts.ts @@ -68,4 +68,6 @@ export interface OrchestrationCommandReceiptRepositoryShape { export class OrchestrationCommandReceiptRepository extends Context.Service< OrchestrationCommandReceiptRepository, OrchestrationCommandReceiptRepositoryShape ->()("t3/persistence/Services/OrchestrationCommandReceipts/OrchestrationCommandReceiptRepository") {} +>()( + "@awtprod/command-center/persistence/Services/OrchestrationCommandReceipts/OrchestrationCommandReceiptRepository", +) {} diff --git a/apps/server/src/persistence/Services/OrchestrationEventStore.ts b/apps/server/src/persistence/Services/OrchestrationEventStore.ts index 1007cb83177..16f33771a0c 100644 --- a/apps/server/src/persistence/Services/OrchestrationEventStore.ts +++ b/apps/server/src/persistence/Services/OrchestrationEventStore.ts @@ -213,4 +213,4 @@ export interface OrchestrationEventStoreShape { export class OrchestrationEventStore extends Context.Service< OrchestrationEventStore, OrchestrationEventStoreShape ->()("t3/persistence/Services/OrchestrationEventStore") {} +>()("@awtprod/command-center/persistence/Services/OrchestrationEventStore") {} diff --git a/apps/server/src/persistence/Services/ProjectionCheckpoints.ts b/apps/server/src/persistence/Services/ProjectionCheckpoints.ts index 7796ebec2a8..0923ccec7c1 100644 --- a/apps/server/src/persistence/Services/ProjectionCheckpoints.ts +++ b/apps/server/src/persistence/Services/ProjectionCheckpoints.ts @@ -92,4 +92,6 @@ export interface ProjectionCheckpointRepositoryShape { export class ProjectionCheckpointRepository extends Context.Service< ProjectionCheckpointRepository, ProjectionCheckpointRepositoryShape ->()("t3/persistence/Services/ProjectionCheckpoints/ProjectionCheckpointRepository") {} +>()( + "@awtprod/command-center/persistence/Services/ProjectionCheckpoints/ProjectionCheckpointRepository", +) {} diff --git a/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts b/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts index 967e6da9d3a..d3bb89bf4a6 100644 --- a/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts +++ b/apps/server/src/persistence/Services/ProjectionPendingApprovals.ts @@ -90,4 +90,6 @@ export interface ProjectionPendingApprovalRepositoryShape { export class ProjectionPendingApprovalRepository extends Context.Service< ProjectionPendingApprovalRepository, ProjectionPendingApprovalRepositoryShape ->()("t3/persistence/Services/ProjectionPendingApprovals/ProjectionPendingApprovalRepository") {} +>()( + "@awtprod/command-center/persistence/Services/ProjectionPendingApprovals/ProjectionPendingApprovalRepository", +) {} diff --git a/apps/server/src/persistence/Services/ProjectionProjects.ts b/apps/server/src/persistence/Services/ProjectionProjects.ts index 5632205a269..9b661e8dbce 100644 --- a/apps/server/src/persistence/Services/ProjectionProjects.ts +++ b/apps/server/src/persistence/Services/ProjectionProjects.ts @@ -78,4 +78,6 @@ export interface ProjectionProjectRepositoryShape { export class ProjectionProjectRepository extends Context.Service< ProjectionProjectRepository, ProjectionProjectRepositoryShape ->()("t3/persistence/Services/ProjectionProjects/ProjectionProjectRepository") {} +>()( + "@awtprod/command-center/persistence/Services/ProjectionProjects/ProjectionProjectRepository", +) {} diff --git a/apps/server/src/persistence/Services/ProjectionState.ts b/apps/server/src/persistence/Services/ProjectionState.ts index ba1ca151736..44327511e0e 100644 --- a/apps/server/src/persistence/Services/ProjectionState.ts +++ b/apps/server/src/persistence/Services/ProjectionState.ts @@ -63,4 +63,4 @@ export interface ProjectionStateRepositoryShape { export class ProjectionStateRepository extends Context.Service< ProjectionStateRepository, ProjectionStateRepositoryShape ->()("t3/persistence/Services/ProjectionState/ProjectionStateRepository") {} +>()("@awtprod/command-center/persistence/Services/ProjectionState/ProjectionStateRepository") {} diff --git a/apps/server/src/persistence/Services/ProjectionThreadActivities.ts b/apps/server/src/persistence/Services/ProjectionThreadActivities.ts index 2fa775eac1c..26f0e5d1c90 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadActivities.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadActivities.ts @@ -83,4 +83,6 @@ export interface ProjectionThreadActivityRepositoryShape { export class ProjectionThreadActivityRepository extends Context.Service< ProjectionThreadActivityRepository, ProjectionThreadActivityRepositoryShape ->()("t3/persistence/Services/ProjectionThreadActivities/ProjectionThreadActivityRepository") {} +>()( + "@awtprod/command-center/persistence/Services/ProjectionThreadActivities/ProjectionThreadActivityRepository", +) {} diff --git a/apps/server/src/persistence/Services/ProjectionThreadMessages.ts b/apps/server/src/persistence/Services/ProjectionThreadMessages.ts index d50ff320256..8e5abb5f8b7 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadMessages.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadMessages.ts @@ -92,4 +92,6 @@ export interface ProjectionThreadMessageRepositoryShape { export class ProjectionThreadMessageRepository extends Context.Service< ProjectionThreadMessageRepository, ProjectionThreadMessageRepositoryShape ->()("t3/persistence/Services/ProjectionThreadMessages/ProjectionThreadMessageRepository") {} +>()( + "@awtprod/command-center/persistence/Services/ProjectionThreadMessages/ProjectionThreadMessageRepository", +) {} diff --git a/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts b/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts index b4bc2bcc328..6572adbeeb9 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadProposedPlans.ts @@ -51,5 +51,5 @@ export class ProjectionThreadProposedPlanRepository extends Context.Service< ProjectionThreadProposedPlanRepository, ProjectionThreadProposedPlanRepositoryShape >()( - "t3/persistence/Services/ProjectionThreadProposedPlans/ProjectionThreadProposedPlanRepository", + "@awtprod/command-center/persistence/Services/ProjectionThreadProposedPlans/ProjectionThreadProposedPlanRepository", ) {} diff --git a/apps/server/src/persistence/Services/ProjectionThreadSessions.ts b/apps/server/src/persistence/Services/ProjectionThreadSessions.ts index be8815ee35a..d5fb6626c34 100644 --- a/apps/server/src/persistence/Services/ProjectionThreadSessions.ts +++ b/apps/server/src/persistence/Services/ProjectionThreadSessions.ts @@ -76,4 +76,6 @@ export interface ProjectionThreadSessionRepositoryShape { export class ProjectionThreadSessionRepository extends Context.Service< ProjectionThreadSessionRepository, ProjectionThreadSessionRepositoryShape ->()("t3/persistence/Services/ProjectionThreadSessions/ProjectionThreadSessionRepository") {} +>()( + "@awtprod/command-center/persistence/Services/ProjectionThreadSessions/ProjectionThreadSessionRepository", +) {} diff --git a/apps/server/src/persistence/Services/ProjectionThreads.ts b/apps/server/src/persistence/Services/ProjectionThreads.ts index 44fdc147a4a..23b7b93316a 100644 --- a/apps/server/src/persistence/Services/ProjectionThreads.ts +++ b/apps/server/src/persistence/Services/ProjectionThreads.ts @@ -100,4 +100,4 @@ export interface ProjectionThreadRepositoryShape { export class ProjectionThreadRepository extends Context.Service< ProjectionThreadRepository, ProjectionThreadRepositoryShape ->()("t3/persistence/Services/ProjectionThreads/ProjectionThreadRepository") {} +>()("@awtprod/command-center/persistence/Services/ProjectionThreads/ProjectionThreadRepository") {} diff --git a/apps/server/src/persistence/Services/ProjectionTurns.ts b/apps/server/src/persistence/Services/ProjectionTurns.ts index 18bf30e2b5a..2b5e55df0a1 100644 --- a/apps/server/src/persistence/Services/ProjectionTurns.ts +++ b/apps/server/src/persistence/Services/ProjectionTurns.ts @@ -250,4 +250,4 @@ export interface ProjectionTurnRepositoryShape { export class ProjectionTurnRepository extends Context.Service< ProjectionTurnRepository, ProjectionTurnRepositoryShape ->()("t3/persistence/Services/ProjectionTurns/ProjectionTurnRepository") {} +>()("@awtprod/command-center/persistence/Services/ProjectionTurns/ProjectionTurnRepository") {} diff --git a/apps/server/src/persistence/Services/ProviderTurnSendClaims.ts b/apps/server/src/persistence/Services/ProviderTurnSendClaims.ts index 286db864879..0a0612c03a4 100644 --- a/apps/server/src/persistence/Services/ProviderTurnSendClaims.ts +++ b/apps/server/src/persistence/Services/ProviderTurnSendClaims.ts @@ -276,4 +276,6 @@ export interface ProviderTurnSendClaimRepositoryShape { export class ProviderTurnSendClaimRepository extends Context.Service< ProviderTurnSendClaimRepository, ProviderTurnSendClaimRepositoryShape ->()("t3/persistence/Services/ProviderTurnSendClaims/ProviderTurnSendClaimRepository") {} +>()( + "@awtprod/command-center/persistence/Services/ProviderTurnSendClaims/ProviderTurnSendClaimRepository", +) {} diff --git a/apps/server/src/preview/Manager.ts b/apps/server/src/preview/Manager.ts index 193ea85b21b..ad752200666 100644 --- a/apps/server/src/preview/Manager.ts +++ b/apps/server/src/preview/Manager.ts @@ -56,7 +56,7 @@ export class PreviewManager extends Context.Service< readonly events: Stream.Stream; readonly subscribeEvents: Effect.Effect, never, Scope.Scope>; } ->()("t3/preview/Manager/PreviewManager") {} +>()("@awtprod/command-center/preview/Manager/PreviewManager") {} interface PreviewSessionState { readonly threadId: string; diff --git a/apps/server/src/preview/PortScanner.ts b/apps/server/src/preview/PortScanner.ts index c306fca2b33..23f04c9a7c5 100644 --- a/apps/server/src/preview/PortScanner.ts +++ b/apps/server/src/preview/PortScanner.ts @@ -44,7 +44,7 @@ export class PortDiscovery extends Context.Service< readonly terminalId: string; }) => Effect.Effect; } ->()("t3/preview/PortScanner/PortDiscovery") {} +>()("@awtprod/command-center/preview/PortScanner/PortDiscovery") {} export const COMMON_DEV_PORTS: ReadonlyArray = Object.freeze([ 3000, 3001, 3333, 4173, 4200, 4321, 5000, 5173, 5174, 5175, 5500, 8000, 8080, 8081, 8888, 9000, diff --git a/apps/server/src/process/externalLauncher.ts b/apps/server/src/process/externalLauncher.ts index 9c2f0e417d3..8f5399926c7 100644 --- a/apps/server/src/process/externalLauncher.ts +++ b/apps/server/src/process/externalLauncher.ts @@ -314,7 +314,7 @@ export class ExternalLauncher extends Context.Service< */ readonly launchEditor: (input: LaunchEditorInput) => Effect.Effect; } ->()("t3/process/externalLauncher") {} +>()("@awtprod/command-center/process/externalLauncher") {} // ============================== // Implementations diff --git a/apps/server/src/processRunner.ts b/apps/server/src/processRunner.ts index d0b14545000..d14e95b1056 100644 --- a/apps/server/src/processRunner.ts +++ b/apps/server/src/processRunner.ts @@ -148,7 +148,7 @@ export class ProcessRunner extends Context.Service< { readonly run: (input: ProcessRunInput) => Effect.Effect; } ->()("t3/processRunner") {} +>()("@awtprod/command-center/processRunner") {} const DEFAULT_TIMEOUT = "60 seconds"; const DEFAULT_MAX_OUTPUT_BYTES = 8 * 1024 * 1024; diff --git a/apps/server/src/project/ProjectFaviconResolver.ts b/apps/server/src/project/ProjectFaviconResolver.ts index e644df06ae6..3c182317299 100644 --- a/apps/server/src/project/ProjectFaviconResolver.ts +++ b/apps/server/src/project/ProjectFaviconResolver.ts @@ -92,7 +92,7 @@ export class ProjectFaviconResolver extends Context.Service< cwd: string, ) => Effect.Effect; } ->()("t3/project/ProjectFaviconResolver") {} +>()("@awtprod/command-center/project/ProjectFaviconResolver") {} function extractIconHref(source: string): string | null { const htmlMatch = source.match(LINK_ICON_HTML_RE); diff --git a/apps/server/src/project/ProjectSetupScriptRunner.ts b/apps/server/src/project/ProjectSetupScriptRunner.ts index 41bf0fabf48..014a3a16a9e 100644 --- a/apps/server/src/project/ProjectSetupScriptRunner.ts +++ b/apps/server/src/project/ProjectSetupScriptRunner.ts @@ -76,7 +76,7 @@ export class ProjectSetupScriptRunner extends Context.Service< input: ProjectSetupScriptRunnerInput, ) => Effect.Effect; } ->()("t3/project/ProjectSetupScriptRunner") {} +>()("@awtprod/command-center/project/ProjectSetupScriptRunner") {} export const make = Effect.gen(function* () { const projectionSnapshotQuery = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; diff --git a/apps/server/src/project/RepositoryIdentityResolver.ts b/apps/server/src/project/RepositoryIdentityResolver.ts index 8cb51b8e783..48e23e56bb8 100644 --- a/apps/server/src/project/RepositoryIdentityResolver.ts +++ b/apps/server/src/project/RepositoryIdentityResolver.ts @@ -32,7 +32,7 @@ export class RepositoryIdentityResolver extends Context.Service< { readonly resolve: (cwd: string) => Effect.Effect; } ->()("t3/project/RepositoryIdentityResolver") {} +>()("@awtprod/command-center/project/RepositoryIdentityResolver") {} function parseRemoteFetchUrls(stdout: string): Map { const remotes = new Map(); diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index bf61385a9cb..ad5a0fbbd26 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -42,7 +42,7 @@ const decodeClaudeSettings = Schema.decodeSync(ClaudeSettings); // Test-local service tag so the rest of the file can keep using `yield* ClaudeAdapter`. class ClaudeAdapter extends Context.Service()( - "t3/provider/Layers/ClaudeAdapter.test/ClaudeAdapter", + "@awtprod/command-center/provider/Layers/ClaudeAdapter.test/ClaudeAdapter", ) {} class FakeClaudeQuery implements AsyncIterable { diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index f09abfc8d59..f19cf375fee 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -57,7 +57,7 @@ const decodeCodexSettings = Schema.decodeSync(CodexSettings); // Test-local service tag so the rest of the file can keep using `yield* CodexAdapter`. class CodexAdapter extends Context.Service()( - "t3/provider/Layers/CodexAdapter.test/CodexAdapter", + "@awtprod/command-center/provider/Layers/CodexAdapter.test/CodexAdapter", ) {} const asThreadId = (value: string): ThreadId => ThreadId.make(value); diff --git a/apps/server/src/provider/Layers/CursorAdapter.test.ts b/apps/server/src/provider/Layers/CursorAdapter.test.ts index 62c967db9c2..efc896adae2 100644 --- a/apps/server/src/provider/Layers/CursorAdapter.test.ts +++ b/apps/server/src/provider/Layers/CursorAdapter.test.ts @@ -33,7 +33,7 @@ const decodeCursorSettings = Schema.decodeSync(CursorSettings); // Test-local service tag so the rest of the file can keep using `yield* CursorAdapter`. class CursorAdapter extends Context.Service()( - "t3/provider/Layers/CursorAdapter.test/CursorAdapter", + "@awtprod/command-center/provider/Layers/CursorAdapter.test/CursorAdapter", ) {} const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)); diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts index 0829bc06941..68d5f4b0bf0 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts @@ -37,7 +37,7 @@ import { // Test-local service tag so the rest of the file can keep using `yield* OpenCodeAdapter`. class OpenCodeAdapter extends Context.Service()( - "t3/provider/Layers/OpenCodeAdapter.test/OpenCodeAdapter", + "@awtprod/command-center/provider/Layers/OpenCodeAdapter.test/OpenCodeAdapter", ) {} const asThreadId = (value: string): ThreadId => ThreadId.make(value); diff --git a/apps/server/src/provider/Layers/ProviderEventLoggers.ts b/apps/server/src/provider/Layers/ProviderEventLoggers.ts index 711aa6e76b6..8274f4f14a7 100644 --- a/apps/server/src/provider/Layers/ProviderEventLoggers.ts +++ b/apps/server/src/provider/Layers/ProviderEventLoggers.ts @@ -50,7 +50,7 @@ export interface ProviderEventLoggersShape { export class ProviderEventLoggers extends Context.Service< ProviderEventLoggers, ProviderEventLoggersShape ->()("t3/provider/Layers/ProviderEventLoggers") {} +>()("@awtprod/command-center/provider/Layers/ProviderEventLoggers") {} /** * Constant value used by tests / boot layers that want to opt out of native diff --git a/apps/server/src/provider/Services/ProviderAdapterRegistry.ts b/apps/server/src/provider/Services/ProviderAdapterRegistry.ts index 5b755c42eed..4227868b468 100644 --- a/apps/server/src/provider/Services/ProviderAdapterRegistry.ts +++ b/apps/server/src/provider/Services/ProviderAdapterRegistry.ts @@ -97,4 +97,4 @@ export interface ProviderAdapterRegistryShape { export class ProviderAdapterRegistry extends Context.Service< ProviderAdapterRegistry, ProviderAdapterRegistryShape ->()("t3/provider/Services/ProviderAdapterRegistry") {} +>()("@awtprod/command-center/provider/Services/ProviderAdapterRegistry") {} diff --git a/apps/server/src/provider/Services/ProviderInstanceRegistry.ts b/apps/server/src/provider/Services/ProviderInstanceRegistry.ts index cfea1142666..06457099356 100644 --- a/apps/server/src/provider/Services/ProviderInstanceRegistry.ts +++ b/apps/server/src/provider/Services/ProviderInstanceRegistry.ts @@ -84,4 +84,4 @@ export interface ProviderInstanceRegistryShape { export class ProviderInstanceRegistry extends Context.Service< ProviderInstanceRegistry, ProviderInstanceRegistryShape ->()("t3/provider/Services/ProviderInstanceRegistry") {} +>()("@awtprod/command-center/provider/Services/ProviderInstanceRegistry") {} diff --git a/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts b/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts index 98d45080237..7e26148e7ff 100644 --- a/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts +++ b/apps/server/src/provider/Services/ProviderInstanceRegistryMutator.ts @@ -49,4 +49,4 @@ export interface ProviderInstanceRegistryMutatorShape { export class ProviderInstanceRegistryMutator extends Context.Service< ProviderInstanceRegistryMutator, ProviderInstanceRegistryMutatorShape ->()("t3/provider/Services/ProviderInstanceRegistryMutator") {} +>()("@awtprod/command-center/provider/Services/ProviderInstanceRegistryMutator") {} diff --git a/apps/server/src/provider/Services/ProviderRegistry.ts b/apps/server/src/provider/Services/ProviderRegistry.ts index b7426b30338..334f89aeadc 100644 --- a/apps/server/src/provider/Services/ProviderRegistry.ts +++ b/apps/server/src/provider/Services/ProviderRegistry.ts @@ -77,5 +77,5 @@ export interface ProviderRegistryShape { } export class ProviderRegistry extends Context.Service()( - "t3/provider/Services/ProviderRegistry", + "@awtprod/command-center/provider/Services/ProviderRegistry", ) {} diff --git a/apps/server/src/provider/Services/ProviderService.ts b/apps/server/src/provider/Services/ProviderService.ts index 4d4cb4fa01a..2640e50e6b6 100644 --- a/apps/server/src/provider/Services/ProviderService.ts +++ b/apps/server/src/provider/Services/ProviderService.ts @@ -117,5 +117,5 @@ export interface ProviderServiceShape { * ProviderService - Service tag for provider orchestration. */ export class ProviderService extends Context.Service()( - "t3/provider/Services/ProviderService", + "@awtprod/command-center/provider/Services/ProviderService", ) {} diff --git a/apps/server/src/provider/Services/ProviderSessionDirectory.ts b/apps/server/src/provider/Services/ProviderSessionDirectory.ts index f2dd4323f7a..9fed35a5432 100644 --- a/apps/server/src/provider/Services/ProviderSessionDirectory.ts +++ b/apps/server/src/provider/Services/ProviderSessionDirectory.ts @@ -67,4 +67,4 @@ export interface ProviderSessionDirectoryShape { export class ProviderSessionDirectory extends Context.Service< ProviderSessionDirectory, ProviderSessionDirectoryShape ->()("t3/provider/Services/ProviderSessionDirectory") {} +>()("@awtprod/command-center/provider/Services/ProviderSessionDirectory") {} diff --git a/apps/server/src/provider/Services/ProviderSessionReaper.ts b/apps/server/src/provider/Services/ProviderSessionReaper.ts index 7c4627eca89..512734e5314 100644 --- a/apps/server/src/provider/Services/ProviderSessionReaper.ts +++ b/apps/server/src/provider/Services/ProviderSessionReaper.ts @@ -12,4 +12,4 @@ export interface ProviderSessionReaperShape { export class ProviderSessionReaper extends Context.Service< ProviderSessionReaper, ProviderSessionReaperShape ->()("t3/provider/Services/ProviderSessionReaper") {} +>()("@awtprod/command-center/provider/Services/ProviderSessionReaper") {} diff --git a/apps/server/src/provider/acp/AcpSessionRuntime.ts b/apps/server/src/provider/acp/AcpSessionRuntime.ts index 09fce6d56f9..61e41a72e85 100644 --- a/apps/server/src/provider/acp/AcpSessionRuntime.ts +++ b/apps/server/src/provider/acp/AcpSessionRuntime.ts @@ -244,7 +244,7 @@ export class AcpSessionRuntime extends Context.Service< payload: unknown, ) => Effect.Effect; } ->()("t3/provider/acp/AcpSessionRuntime") {} +>()("@awtprod/command-center/provider/acp/AcpSessionRuntime") {} interface AcpStartedState extends AcpSessionRuntimeStartResult {} diff --git a/apps/server/src/provider/opencodeRuntime.ts b/apps/server/src/provider/opencodeRuntime.ts index 46d0b3019bd..e7e8369a66a 100644 --- a/apps/server/src/provider/opencodeRuntime.ts +++ b/apps/server/src/provider/opencodeRuntime.ts @@ -552,7 +552,7 @@ const makeOpenCodeRuntime = Effect.gen(function* () { }); export class OpenCodeRuntime extends Context.Service()( - "t3/provider/opencodeRuntime", + "@awtprod/command-center/provider/opencodeRuntime", ) {} export const OpenCodeRuntimeLive = Layer.effect(OpenCodeRuntime, makeOpenCodeRuntime).pipe( diff --git a/apps/server/src/provider/providerMaintenanceRunner.ts b/apps/server/src/provider/providerMaintenanceRunner.ts index ba5fd37bfb1..4a4cb5a3ea7 100644 --- a/apps/server/src/provider/providerMaintenanceRunner.ts +++ b/apps/server/src/provider/providerMaintenanceRunner.ts @@ -54,7 +54,7 @@ export interface ProviderMaintenanceRunnerShape { export class ProviderMaintenanceRunner extends Context.Service< ProviderMaintenanceRunner, ProviderMaintenanceRunnerShape ->()("t3/provider/providerMaintenanceRunner") {} +>()("@awtprod/command-center/provider/providerMaintenanceRunner") {} class ProviderMaintenanceCommandError extends Data.TaggedError("ProviderMaintenanceCommandError")<{ readonly message: string; diff --git a/apps/server/src/relay/AgentAwarenessRelay.ts b/apps/server/src/relay/AgentAwarenessRelay.ts index 3d3d78816e3..245322aec62 100644 --- a/apps/server/src/relay/AgentAwarenessRelay.ts +++ b/apps/server/src/relay/AgentAwarenessRelay.ts @@ -51,7 +51,7 @@ export class AgentAwarenessRelay extends Context.Service< readonly publishThread: (threadId: ThreadId) => Effect.Effect; readonly start: () => Effect.Effect; } ->()("t3/relay/AgentAwarenessRelay") {} +>()("@awtprod/command-center/relay/AgentAwarenessRelay") {} export function eventThreadId(event: OrchestrationEvent): ThreadId | null { const payload = event.payload as { readonly threadId?: unknown }; diff --git a/apps/server/src/review/ReviewService.ts b/apps/server/src/review/ReviewService.ts index db1dc5bc8d2..a169048eb30 100644 --- a/apps/server/src/review/ReviewService.ts +++ b/apps/server/src/review/ReviewService.ts @@ -24,7 +24,7 @@ export class ReviewService extends Context.Service< input: ReviewDiffPreviewInput, ) => Effect.Effect; } ->()("t3/review/ReviewService") {} +>()("@awtprod/command-center/review/ReviewService") {} export const make = Effect.gen(function* () { const config = yield* ServerConfig.ServerConfig; diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index 8269535e034..a784032205b 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -3291,7 +3291,12 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); - for (const desktopOrigin of ["t3code://app", "t3code-dev://app"]) { + for (const desktopOrigin of [ + "commandcenter://app", + "commandcenter-dev://app", + "t3code://app", + "t3code-dev://app", + ]) { it.effect(`allows credentialed preflights from ${desktopOrigin} in development`, () => Effect.gen(function* () { yield* buildAppUnderTest({ diff --git a/apps/server/src/serverLifecycleEvents.ts b/apps/server/src/serverLifecycleEvents.ts index 855d03490ef..6ae072bb7bc 100644 --- a/apps/server/src/serverLifecycleEvents.ts +++ b/apps/server/src/serverLifecycleEvents.ts @@ -22,7 +22,7 @@ export class ServerLifecycleEvents extends Context.Service< readonly snapshot: Effect.Effect; readonly stream: Stream.Stream; } ->()("t3/serverLifecycleEvents") {} +>()("@awtprod/command-center/serverLifecycleEvents") {} const make = Effect.gen(function* () { const pubsub = yield* PubSub.unbounded(); diff --git a/apps/server/src/serverRuntimeStartup.ts b/apps/server/src/serverRuntimeStartup.ts index bbdaef0278d..806aa6691ce 100644 --- a/apps/server/src/serverRuntimeStartup.ts +++ b/apps/server/src/serverRuntimeStartup.ts @@ -77,7 +77,7 @@ export class ServerRuntimeStartup extends Context.Service< effect: Effect.Effect, ) => Effect.Effect; } ->()("t3/serverRuntimeStartup") {} +>()("@awtprod/command-center/serverRuntimeStartup") {} interface QueuedCommand { readonly run: Effect.Effect; diff --git a/apps/server/src/serverSettings.ts b/apps/server/src/serverSettings.ts index 8d389975b10..c0316a2523f 100644 --- a/apps/server/src/serverSettings.ts +++ b/apps/server/src/serverSettings.ts @@ -153,7 +153,7 @@ export class ServerSettingsService extends Context.Service< /** Stream of settings change events. */ readonly streamChanges: Stream.Stream; } ->()("t3/serverSettings/ServerSettingsService") { +>()("@awtprod/command-center/serverSettings/ServerSettingsService") { /** @deprecated Import and use `layerTest` from this module. */ static readonly layerTest = (overrides: DeepPartial = {}) => layerTest(overrides); } diff --git a/apps/server/src/sourceControl/AzureDevOpsCli.ts b/apps/server/src/sourceControl/AzureDevOpsCli.ts index 06f14d50710..94980b5c963 100644 --- a/apps/server/src/sourceControl/AzureDevOpsCli.ts +++ b/apps/server/src/sourceControl/AzureDevOpsCli.ts @@ -248,7 +248,7 @@ export class AzureDevOpsCli extends Context.Service< readonly remoteName?: string; }) => Effect.Effect; } ->()("t3/sourceControl/AzureDevOpsCli") {} +>()("@awtprod/command-center/sourceControl/AzureDevOpsCli") {} function normalizeChangeRequestId(reference: string): string { const trimmed = reference.trim().replace(/^#/, ""); diff --git a/apps/server/src/sourceControl/BitbucketApi.ts b/apps/server/src/sourceControl/BitbucketApi.ts index f7d7f6671a4..dc2975aecbb 100644 --- a/apps/server/src/sourceControl/BitbucketApi.ts +++ b/apps/server/src/sourceControl/BitbucketApi.ts @@ -290,7 +290,7 @@ export class BitbucketApi extends Context.Service< readonly force?: boolean; }) => Effect.Effect; } ->()("t3/sourceControl/BitbucketApi") {} +>()("@awtprod/command-center/sourceControl/BitbucketApi") {} function nonEmpty(value: string | undefined): Option.Option { const trimmed = value?.trim(); diff --git a/apps/server/src/sourceControl/GitHubCli.ts b/apps/server/src/sourceControl/GitHubCli.ts index 961385d6e9e..d480536037c 100644 --- a/apps/server/src/sourceControl/GitHubCli.ts +++ b/apps/server/src/sourceControl/GitHubCli.ts @@ -246,7 +246,7 @@ export class GitHubCli extends Context.Service< readonly force?: boolean; }) => Effect.Effect; } ->()("t3/sourceControl/GitHubCli") {} +>()("@awtprod/command-center/sourceControl/GitHubCli") {} const RawGitHubRepositoryCloneUrlsSchema = Schema.Struct({ nameWithOwner: TrimmedNonEmptyString, diff --git a/apps/server/src/sourceControl/GitLabCli.ts b/apps/server/src/sourceControl/GitLabCli.ts index c1264e9e45e..a171eb8c418 100644 --- a/apps/server/src/sourceControl/GitLabCli.ts +++ b/apps/server/src/sourceControl/GitLabCli.ts @@ -296,7 +296,7 @@ export class GitLabCli extends Context.Service< readonly force?: boolean; }) => Effect.Effect; } ->()("t3/sourceControl/GitLabCli") {} +>()("@awtprod/command-center/sourceControl/GitLabCli") {} const RawGitLabRepositoryCloneUrlsSchema = Schema.Struct({ path_with_namespace: TrimmedNonEmptyString, diff --git a/apps/server/src/sourceControl/SourceControlDiscovery.ts b/apps/server/src/sourceControl/SourceControlDiscovery.ts index 660f32283e0..f78278f64be 100644 --- a/apps/server/src/sourceControl/SourceControlDiscovery.ts +++ b/apps/server/src/sourceControl/SourceControlDiscovery.ts @@ -62,7 +62,7 @@ export class SourceControlDiscovery extends Context.Service< { readonly discover: Effect.Effect; } ->()("t3/sourceControl/SourceControlDiscovery") {} +>()("@awtprod/command-center/sourceControl/SourceControlDiscovery") {} export const make = Effect.gen(function* () { const config = yield* ServerConfig; diff --git a/apps/server/src/sourceControl/SourceControlProvider.ts b/apps/server/src/sourceControl/SourceControlProvider.ts index 5f93dbcaa42..e525dc07e76 100644 --- a/apps/server/src/sourceControl/SourceControlProvider.ts +++ b/apps/server/src/sourceControl/SourceControlProvider.ts @@ -127,4 +127,4 @@ export class SourceControlProvider extends Context.Service< readonly force?: boolean; }) => Effect.Effect; } ->()("t3/sourceControl/SourceControlProvider") {} +>()("@awtprod/command-center/sourceControl/SourceControlProvider") {} diff --git a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts index fb70d677e43..2a52af06509 100644 --- a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts +++ b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts @@ -59,7 +59,7 @@ export class SourceControlProviderRegistry extends Context.Service< >; readonly discover: Effect.Effect>; } ->()("t3/sourceControl/SourceControlProviderRegistry") {} +>()("@awtprod/command-center/sourceControl/SourceControlProviderRegistry") {} function unsupportedProvider( kind: SourceControlProviderKind, diff --git a/apps/server/src/sourceControl/SourceControlRepositoryService.ts b/apps/server/src/sourceControl/SourceControlRepositoryService.ts index 1b46369e25c..e1536f29343 100644 --- a/apps/server/src/sourceControl/SourceControlRepositoryService.ts +++ b/apps/server/src/sourceControl/SourceControlRepositoryService.ts @@ -37,7 +37,7 @@ export class SourceControlRepositoryService extends Context.Service< input: SourceControlPublishRepositoryInput, ) => Effect.Effect; } ->()("t3/sourceControl/SourceControlRepositoryService") {} +>()("@awtprod/command-center/sourceControl/SourceControlRepositoryService") {} function mapRepositoryError(operation: string, provider: SourceControlProviderKind) { return Effect.mapError((cause: unknown) => diff --git a/apps/server/src/telemetry/AnalyticsService.ts b/apps/server/src/telemetry/AnalyticsService.ts index 5fdc7bdeb19..8ef91e30a83 100644 --- a/apps/server/src/telemetry/AnalyticsService.ts +++ b/apps/server/src/telemetry/AnalyticsService.ts @@ -55,7 +55,7 @@ export class AnalyticsService extends Context.Service< /** Flush all currently queued telemetry events. */ readonly flush: Effect.Effect; } ->()("t3/telemetry/AnalyticsService") { +>()("@awtprod/command-center/telemetry/AnalyticsService") { /** No-op layer for callers that intentionally disable telemetry. */ static readonly layerTest = Layer.succeed( AnalyticsService, diff --git a/apps/server/src/terminal/Manager.ts b/apps/server/src/terminal/Manager.ts index caa5106bb9f..6de169223dc 100644 --- a/apps/server/src/terminal/Manager.ts +++ b/apps/server/src/terminal/Manager.ts @@ -186,7 +186,7 @@ export class TerminalManager extends Context.Service< listener: (event: TerminalMetadataStreamEvent) => Effect.Effect, ) => Effect.Effect<() => void>; } ->()("t3/terminal/Manager/TerminalManager") {} +>()("@awtprod/command-center/terminal/Manager/TerminalManager") {} interface TerminalSubprocessInspectResult { readonly hasRunningSubprocess: boolean; diff --git a/apps/server/src/terminal/PtyAdapter.ts b/apps/server/src/terminal/PtyAdapter.ts index 67147035bb5..e709f0ff079 100644 --- a/apps/server/src/terminal/PtyAdapter.ts +++ b/apps/server/src/terminal/PtyAdapter.ts @@ -63,4 +63,4 @@ export class PtyAdapter extends Context.Service< */ readonly spawn: (input: PtySpawnInput) => Effect.Effect; } ->()("t3/terminal/PtyAdapter") {} +>()("@awtprod/command-center/terminal/PtyAdapter") {} diff --git a/apps/server/src/textGeneration/TextGeneration.ts b/apps/server/src/textGeneration/TextGeneration.ts index e62a79afe78..abb2c837bf3 100644 --- a/apps/server/src/textGeneration/TextGeneration.ts +++ b/apps/server/src/textGeneration/TextGeneration.ts @@ -110,7 +110,7 @@ export class TextGeneration extends Context.Service< input: ThreadTitleGenerationInput, ) => Effect.Effect; } ->()("t3/textGeneration/TextGeneration") {} +>()("@awtprod/command-center/textGeneration/TextGeneration") {} /** @deprecated Use `TextGeneration["Service"]`. */ export type TextGenerationShape = TextGeneration["Service"]; diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts index 408c799d562..6a4faf711c0 100644 --- a/apps/server/src/vcs/GitVcsDriver.ts +++ b/apps/server/src/vcs/GitVcsDriver.ts @@ -265,7 +265,7 @@ export class GitVcsDriver extends Context.Service< readonly initRepo: (input: VcsInitInput) => Effect.Effect; readonly listLocalBranchNames: (cwd: string) => Effect.Effect; } ->()("t3/vcs/GitVcsDriver") {} +>()("@awtprod/command-center/vcs/GitVcsDriver") {} const WORKSPACE_FILES_MAX_OUTPUT_BYTES = 16 * 1024 * 1024; const GIT_CHECK_IGNORE_MAX_STDIN_BYTES = 256 * 1024; diff --git a/apps/server/src/vcs/VcsDriver.ts b/apps/server/src/vcs/VcsDriver.ts index f2daf793502..2721f49d8a8 100644 --- a/apps/server/src/vcs/VcsDriver.ts +++ b/apps/server/src/vcs/VcsDriver.ts @@ -77,4 +77,4 @@ export class VcsDriver extends Context.Service< input: ReviewDiffPreviewInput, ) => Effect.Effect; } ->()("t3/vcs/VcsDriver") {} +>()("@awtprod/command-center/vcs/VcsDriver") {} diff --git a/apps/server/src/vcs/VcsDriverRegistry.ts b/apps/server/src/vcs/VcsDriverRegistry.ts index 0bf95c2ffba..4b1995cfbe4 100644 --- a/apps/server/src/vcs/VcsDriverRegistry.ts +++ b/apps/server/src/vcs/VcsDriverRegistry.ts @@ -34,7 +34,7 @@ export class VcsDriverRegistry extends Context.Service< ) => Effect.Effect; readonly resolve: (input: VcsDriverResolveInput) => Effect.Effect; } ->()("t3/vcs/VcsDriverRegistry") {} +>()("@awtprod/command-center/vcs/VcsDriverRegistry") {} function detectionCacheKey(input: { readonly cwd: string; diff --git a/apps/server/src/vcs/VcsProcess.ts b/apps/server/src/vcs/VcsProcess.ts index 04f0b4c7cb3..566e7d66642 100644 --- a/apps/server/src/vcs/VcsProcess.ts +++ b/apps/server/src/vcs/VcsProcess.ts @@ -53,7 +53,7 @@ export class VcsProcess extends Context.Service< { readonly run: (input: VcsProcessInput) => Effect.Effect; } ->()("t3/vcs/VcsProcess") {} +>()("@awtprod/command-center/vcs/VcsProcess") {} const DEFAULT_TIMEOUT_MS = 30_000; const DEFAULT_MAX_OUTPUT_BYTES = 1_000_000; diff --git a/apps/server/src/vcs/VcsProjectConfig.ts b/apps/server/src/vcs/VcsProjectConfig.ts index 6abce9a3ef3..c8e6347338d 100644 --- a/apps/server/src/vcs/VcsProjectConfig.ts +++ b/apps/server/src/vcs/VcsProjectConfig.ts @@ -48,7 +48,7 @@ export class VcsProjectConfig extends Context.Service< input: VcsProjectConfigResolveInput, ) => Effect.Effect; } ->()("t3/vcs/VcsProjectConfig") {} +>()("@awtprod/command-center/vcs/VcsProjectConfig") {} function configuredKind(config: ProjectVcsConfigFile): VcsDriverKindType | "auto" { return config.vcs?.kind ?? config.vcsKind ?? "auto"; diff --git a/apps/server/src/vcs/VcsProvisioningService.ts b/apps/server/src/vcs/VcsProvisioningService.ts index 9febacf2256..9e0083d5ff7 100644 --- a/apps/server/src/vcs/VcsProvisioningService.ts +++ b/apps/server/src/vcs/VcsProvisioningService.ts @@ -15,7 +15,7 @@ export class VcsProvisioningService extends Context.Service< { readonly initRepository: (input: VcsInitInput) => Effect.Effect; } ->()("t3/vcs/VcsProvisioningService") {} +>()("@awtprod/command-center/vcs/VcsProvisioningService") {} function resolveRequestedKind( kind: VcsDriverKind | undefined, diff --git a/apps/server/src/vcs/VcsStatusBroadcaster.ts b/apps/server/src/vcs/VcsStatusBroadcaster.ts index c238154f58c..ccbb6c2a2d8 100644 --- a/apps/server/src/vcs/VcsStatusBroadcaster.ts +++ b/apps/server/src/vcs/VcsStatusBroadcaster.ts @@ -166,7 +166,7 @@ export class VcsStatusBroadcaster extends Context.Service< options?: StreamStatusOptions, ) => Stream.Stream; } ->()("t3/vcs/VcsStatusBroadcaster") {} +>()("@awtprod/command-center/vcs/VcsStatusBroadcaster") {} function fingerprintStatusPart(status: unknown): string { return JSON.stringify(status); diff --git a/apps/server/src/workspace/WorkspaceEntries.ts b/apps/server/src/workspace/WorkspaceEntries.ts index 7501cbe0eab..274f2749e61 100644 --- a/apps/server/src/workspace/WorkspaceEntries.ts +++ b/apps/server/src/workspace/WorkspaceEntries.ts @@ -95,7 +95,7 @@ export class WorkspaceEntries extends Context.Service< ) => Effect.Effect; readonly refresh: (cwd: string) => Effect.Effect; } ->()("t3/workspace/WorkspaceEntries") {} +>()("@awtprod/command-center/workspace/WorkspaceEntries") {} function expandHomePath(input: string, path: Path.Path): string { if (input === "~") { diff --git a/apps/server/src/workspace/WorkspaceFileSystem.ts b/apps/server/src/workspace/WorkspaceFileSystem.ts index e2dc9cbbb39..459e299e36e 100644 --- a/apps/server/src/workspace/WorkspaceFileSystem.ts +++ b/apps/server/src/workspace/WorkspaceFileSystem.ts @@ -124,7 +124,7 @@ export class WorkspaceFileSystem extends Context.Service< WorkspaceFileSystemError | WorkspacePaths.WorkspacePathOutsideRootError >; } ->()("t3/workspace/WorkspaceFileSystem") {} +>()("@awtprod/command-center/workspace/WorkspaceFileSystem") {} export const make = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; diff --git a/apps/server/src/workspace/WorkspacePaths.ts b/apps/server/src/workspace/WorkspacePaths.ts index 5acf6677cde..65094fcca13 100644 --- a/apps/server/src/workspace/WorkspacePaths.ts +++ b/apps/server/src/workspace/WorkspacePaths.ts @@ -115,7 +115,7 @@ export class WorkspacePaths extends Context.Service< WorkspacePathOutsideRootError >; } ->()("t3/workspace/WorkspacePaths") {} +>()("@awtprod/command-center/workspace/WorkspacePaths") {} function toPosixRelativePath(input: string): string { return input.replaceAll("\\", "/"); diff --git a/apps/server/src/workspace/WorkspaceSearchIndex.ts b/apps/server/src/workspace/WorkspaceSearchIndex.ts index db4d46851e7..cf360133d16 100644 --- a/apps/server/src/workspace/WorkspaceSearchIndex.ts +++ b/apps/server/src/workspace/WorkspaceSearchIndex.ts @@ -102,7 +102,7 @@ export class WorkspaceSearchIndex extends Context.Service< WorkspaceSearchIndexRefreshFailed | WorkspaceSearchIndexScanTimedOut >; } ->()("t3/workspace/WorkspaceSearchIndex") {} +>()("@awtprod/command-center/workspace/WorkspaceSearchIndex") {} function toPosixPath(input: string): string { return input.replaceAll("\\", "/"); @@ -318,7 +318,7 @@ export const make = Effect.fn("WorkspaceSearchIndex.make")(function* (cwd: strin export const layer = (cwd: string) => Layer.effect(WorkspaceSearchIndex, make(cwd)); export class WorkspaceSearchIndexMap extends LayerMap.Service()( - "t3/workspace/WorkspaceSearchIndexMap", + "@awtprod/command-center/workspace/WorkspaceSearchIndexMap", { lookup: layer, idleTimeToLive: WORKSPACE_INDEX_IDLE_TTL, diff --git a/apps/web/src/branding.test.ts b/apps/web/src/branding.test.ts index d3889d93b35..824cff73757 100644 --- a/apps/web/src/branding.test.ts +++ b/apps/web/src/branding.test.ts @@ -69,6 +69,18 @@ describe("branding", () => { expect(branding.HOSTED_APP_CHANNEL).toBeNull(); expect(branding.HOSTED_APP_CHANNEL_LABEL).toBeNull(); }); + + it("keeps hosted channel switching disabled unless explicitly configured", async () => { + vi.stubEnv("VITE_HOSTED_APP_CHANNEL", "nightly"); + + let branding = await import("./branding"); + expect(branding.HOSTED_APP_CHANNEL_SWITCHING_ENABLED).toBe(false); + + vi.resetModules(); + vi.stubEnv("VITE_HOSTED_APP_CHANNEL_SWITCHING", "1"); + branding = await import("./branding"); + expect(branding.HOSTED_APP_CHANNEL_SWITCHING_ENABLED).toBe(true); + }); }); describe("branding logic", () => { @@ -85,7 +97,7 @@ describe("branding logic", () => { expect( resolveServerBackedAppDisplayName({ baseName: "Command Center", - fallbackDisplayName: "Command Center (Alpha)", + fallbackDisplayName: "Command Center", fallbackStageLabel: "Alpha", primaryServerVersion: "0.0.28-nightly.20260616.12", }), @@ -96,21 +108,21 @@ describe("branding logic", () => { expect( resolveServerBackedAppDisplayName({ baseName: "Command Center", - fallbackDisplayName: "Command Center (Alpha)", + fallbackDisplayName: "Command Center", fallbackStageLabel: "Alpha", primaryServerVersion: "0.0.27", }), - ).toBe("Command Center (Alpha)"); + ).toBe("Command Center"); }); it("keeps the fallback display name for malformed nightly primary server versions", () => { expect( resolveServerBackedAppDisplayName({ baseName: "Command Center", - fallbackDisplayName: "Command Center (Alpha)", + fallbackDisplayName: "Command Center", fallbackStageLabel: "Alpha", primaryServerVersion: "0.0.28-nightly.20260616", }), - ).toBe("Command Center (Alpha)"); + ).toBe("Command Center"); }); }); diff --git a/apps/web/src/branding.ts b/apps/web/src/branding.ts index f16713664f3..53bc490702d 100644 --- a/apps/web/src/branding.ts +++ b/apps/web/src/branding.ts @@ -16,11 +16,13 @@ export const HOSTED_APP_CHANNEL = hostedAppChannel === "latest" || hostedAppChannel === "nightly" ? hostedAppChannel : null; export const HOSTED_APP_CHANNEL_LABEL = HOSTED_APP_CHANNEL === "nightly" ? "Nightly" : HOSTED_APP_CHANNEL === "latest" ? "Latest" : null; +export const HOSTED_APP_CHANNEL_SWITCHING_ENABLED = + import.meta.env.VITE_HOSTED_APP_CHANNEL_SWITCHING === "1"; export const APP_BASE_NAME = injectedDesktopAppBranding?.baseName ?? "Command Center"; export const APP_STAGE_LABEL = injectedDesktopAppBranding?.stageLabel ?? HOSTED_APP_CHANNEL_LABEL ?? - (import.meta.env.DEV ? "Dev" : "Alpha"); + (import.meta.env.DEV ? "Dev" : "Latest"); export const APP_DISPLAY_NAME = injectedDesktopAppBranding?.displayName ?? formatAppDisplayName({ baseName: APP_BASE_NAME, stageLabel: APP_STAGE_LABEL }); diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 9f89cd64750..a2df41e8017 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -24,7 +24,12 @@ import * as Arr from "effect/Array"; import * as Duration from "effect/Duration"; import * as Equal from "effect/Equal"; import * as Result from "effect/Result"; -import { APP_VERSION, HOSTED_APP_CHANNEL, HOSTED_APP_CHANNEL_LABEL } from "../../branding"; +import { + APP_VERSION, + HOSTED_APP_CHANNEL, + HOSTED_APP_CHANNEL_LABEL, + HOSTED_APP_CHANNEL_SWITCHING_ENABLED, +} from "../../branding"; import { canCheckForUpdate, getDesktopUpdateButtonTooltip, @@ -173,7 +178,8 @@ function AboutVersionSection() { const hasDesktopBridge = typeof window !== "undefined" && Boolean(window.desktopBridge); const selectedUpdateChannel = updateState?.channel ?? "latest"; - const selectedHostedAppChannel = hasDesktopBridge ? null : HOSTED_APP_CHANNEL; + const selectedHostedAppChannel = + hasDesktopBridge || !HOSTED_APP_CHANNEL_SWITCHING_ENABLED ? null : HOSTED_APP_CHANNEL; const handleUpdateChannelChange = useCallback( (channel: DesktopUpdateChannel) => { diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts index ac1da93f5c8..09c175f5bfc 100644 --- a/apps/web/src/vite-env.d.ts +++ b/apps/web/src/vite-env.d.ts @@ -7,6 +7,7 @@ interface ImportMetaEnv { readonly VITE_WS_URL: string; readonly VITE_HOSTED_APP_URL: string; readonly VITE_HOSTED_APP_CHANNEL: string; + readonly VITE_HOSTED_APP_CHANNEL_SWITCHING?: string; readonly VITE_CLERK_PUBLISHABLE_KEY: string; readonly VITE_CLERK_JWT_TEMPLATE: string; readonly VITE_CLERK_CLI_OAUTH_CLIENT_ID: string; diff --git a/apps/web/vercel.ts b/apps/web/vercel.ts index 784eceffa53..7f5a4420a12 100644 --- a/apps/web/vercel.ts +++ b/apps/web/vercel.ts @@ -1,27 +1,4 @@ -import { matchers, routes, type Transform, type VercelConfig } from "@vercel/config/v1"; - -const ROUTER_HOST = "app.t3.codes"; -const HOSTED_WEB_CHANNEL_COOKIE = "t3code_web_channel"; -const LATEST_ORIGIN = "https://latest.app.t3.codes"; -const NIGHTLY_ORIGIN = "https://nightly.app.t3.codes"; -const CLEAN_CHANNEL_QUERY_TRANSFORMS = [ - { - type: "request.query", - op: "delete", - target: { key: "channel" }, - }, -] satisfies Transform[]; - -function channelCookie(channel: "latest" | "nightly"): string { - return [ - `${HOSTED_WEB_CHANNEL_COOKIE}=${channel}`, - "Path=/", - "Max-Age=31536000", - "HttpOnly", - "Secure", - "SameSite=Lax", - ].join("; "); -} +import { routes, type VercelConfig } from "@vercel/config/v1"; export const config: VercelConfig = { buildCommand: @@ -31,36 +8,5 @@ export const config: VercelConfig = { }, installCommand: "npm install -g vite-plus && vp install --filter '@t3tools/scripts...' --filter '@t3tools/web...'", - routes: [ - { - src: "/__t3code/channel", - has: [matchers.query("channel", "nightly")], - transforms: CLEAN_CHANNEL_QUERY_TRANSFORMS, - headers: { - Location: "/", - "Set-Cookie": channelCookie("nightly"), - }, - status: 302, - }, - { - src: "/__t3code/channel", - transforms: CLEAN_CHANNEL_QUERY_TRANSFORMS, - headers: { - Location: "/", - "Set-Cookie": channelCookie("latest"), - }, - status: 302, - }, - { - src: "/(.*)", - has: [matchers.host(ROUTER_HOST), matchers.cookie(HOSTED_WEB_CHANNEL_COOKIE, "nightly")], - dest: `${NIGHTLY_ORIGIN}/$1`, - }, - { - src: "/(.*)", - has: [matchers.host(ROUTER_HOST)], - dest: `${LATEST_ORIGIN}/$1`, - }, - ], rewrites: [routes.rewrite("/(.*)", "/index.html")], }; diff --git a/docs/architecture/remote.md b/docs/architecture/remote.md index 75274095a12..8a6b3314d94 100644 --- a/docs/architecture/remote.md +++ b/docs/architecture/remote.md @@ -158,7 +158,7 @@ A hosted pairing request is a bootstrap URL for the static web app, not a transp Example: ```text -https://app.t3.codes/pair?host=https://backend.example.com:3773#token=PAIRCODE +https://awtprod-command-center.vercel.app/pair?host=https://backend.example.com:3773#token=PAIRCODE ``` The hosted app reads the `host` parameter and pairing token, exchanges the token directly with that backend, then saves the resulting environment record in browser local storage. diff --git a/docs/cloud/t3-connect-clerk.md b/docs/cloud/t3-connect-clerk.md index c53fa8ee87b..8e17a1770f1 100644 --- a/docs/cloud/t3-connect-clerk.md +++ b/docs/cloud/t3-connect-clerk.md @@ -124,14 +124,14 @@ In **Clerk Dashboard > Native applications**, enable the Native API and add thes mobile SSO redirect allowlist: ```text -t3code-dev://app/ -t3code://app/ +commandcenter-dev://app/ +commandcenter://app/ ``` -Local desktop development uses `t3code-dev://app`, while packaged builds use `t3code://app`. Add the +Local desktop development uses `commandcenter-dev://app`, while packaged builds use `commandcenter://app`. Add the matching origin to each Clerk instance's Backend API `allowed_origins` array as well. The development -Clerk instance should only need `t3code-dev://app`; the production Clerk instance should only need -`t3code://app`. `@clerk/electron` owns the native request adapter, encrypted Clerk token persistence, +Clerk instance should only need `commandcenter-dev://app`; the production Clerk instance should only need +`commandcenter://app`. `@clerk/electron` owns the native request adapter, encrypted Clerk token persistence, external-browser OAuth transport, and callback delivery for initial sign-in and linked-account flows. There is currently no Dashboard UI for `allowed_origins`. Preserve any existing entries and update @@ -141,7 +141,7 @@ the instance through the Backend API: curl -X PATCH https://api.clerk.com/v1/instance \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $CLERK_SECRET_KEY" \ - -d '{"allowed_origins":["t3code://app"]}' + -d '{"allowed_origins":["commandcenter://app"]}' ``` Never put `CLERK_SECRET_KEY` in the desktop app, a client-facing environment file, or a build @@ -149,16 +149,16 @@ artifact. ## Desktop Passkeys -The production macOS bundle ID is `com.t3tools.t3code`. To enable native passkeys: +The production macOS bundle ID is `com.awtprod.commandcenter`. To enable native passkeys: -1. Create an explicit macOS App ID for `com.t3tools.t3code` in the Apple Developer portal and enable +1. Create an explicit macOS App ID for `com.awtprod.commandcenter` in the Apple Developer portal and enable **Associated Domains**. 2. Create a compatible macOS provisioning profile for that App ID and the certificate used to sign the distributed app. 3. In Clerk's Native API settings, add an iOS app with the same Apple Team ID and bundle ID. This is also the configuration point for Electron/macOS passkeys. 4. Confirm Clerk serves `https:///.well-known/apple-app-site-association` and that - `webcredentials.apps` contains `.com.t3tools.t3code`. + `webcredentials.apps` contains `.com.awtprod.commandcenter`. 5. Set the local or CI signing configuration described below. For a local signed build, add these values to `.env.local` or export them before invoking the @@ -188,7 +188,7 @@ binary from another: ```sh VITE_DEV_SERVER_URL=http://127.0.0.1:5733 \ T3CODE_PORT=13773 \ - "/Applications/Command Center (Alpha).app/Contents/MacOS/Command Center (Alpha)" + "/Applications/Command Center.app/Contents/MacOS/Command Center" ``` After changing Associated Domains, bump the build version before rebuilding; macOS may otherwise @@ -197,8 +197,8 @@ reuse stale Shared Web Credentials metadata for the same app/version pair. Verify the installed bundle before testing: ```sh -codesign --verify --deep --strict "/Applications/Command Center (Alpha).app" -codesign -d --entitlements :- "/Applications/Command Center (Alpha).app" +codesign --verify --deep --strict "/Applications/Command Center.app" +codesign -d --entitlements :- "/Applications/Command Center.app" ``` The current mobile UI uses Clerk's native authentication view. If a future mobile browser OAuth diff --git a/docs/operations/release.md b/docs/operations/release.md index 76f787dc023..e87a66db6c2 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -8,7 +8,7 @@ This document covers the unified release workflow for stable and nightly desktop - Triggers: - push tag matching `v*.*.*` for stable releases - scheduled nightly check every three hours - - manual `workflow_dispatch` for either channel + - manual `workflow_dispatch` with an explicit `validate` or `publish` mode - Runs quality gates first: lint, typecheck, test. - Reads the shared production T3 Connect relay URL and Clerk client configuration before packaging clients. - Builds four artifacts in parallel for both channels: @@ -16,19 +16,24 @@ This document covers the unified release workflow for stable and nightly desktop - macOS `x64` DMG - Linux `x64` AppImage - Windows `x64` NSIS installer -- Publishes one GitHub Release with all produced files. +- In `validate` mode, retains one consolidated signed artifact bundle for 14 days without publishing + npm, GitHub Releases, Vercel deployments, tags, aliases, or version commits. +- In `publish` mode, publishes one GitHub Release with all produced files. - Stable tags with a suffix after `X.Y.Z` (for example `1.2.3-alpha.1`) are published as GitHub prereleases. - Only plain stable `X.Y.Z` releases are marked as the repository's latest release. - Nightly runs are always GitHub prereleases and never marked latest. - Automatically generated release notes are pinned to the previous tag in the same channel, so stable compares to the previous stable tag and nightly compares to the previous nightly tag. - Includes Electron auto-update metadata (for example `latest*.yml`, `nightly*.yml`, and `*.blockmap`) in release assets. -- Publishes the CLI package (`apps/server`, npm package `t3`) with OIDC trusted publishing from the same workflow file: +- Generates `SHA256SUMS.txt` and validates every updater-manifest file path and byte size before publication. +- Publishes the CLI package (`apps/server`, npm package `@awtprod/command-center`) with OIDC trusted publishing from the same workflow file: - stable releases publish npm dist-tag `latest` - nightly releases publish npm dist-tag `nightly` -- Deploys the hosted web app to Vercel only after a release is published: - - stable releases are aliased to the `latest` hosted app channel - - nightly releases are aliased to the `nightly` hosted app channel -- Signing is optional and auto-detected per platform from secrets. +- Deploys the hosted web app to the dedicated `awtprod-command-center` Vercel project only after a + release is published. Stable releases update its production `vercel.app` domain; nightlies retain + the generated preview deployment URL. +- Stable versions, including validation runs and prereleases, fail closed unless macOS and Windows signing + configuration is complete. Pre-release and nightly builds may be unsigned only when no partial + signing configuration is present. ## T3 Connect relay deployment @@ -88,6 +93,20 @@ Developers deploy personal stages locally rather than through pull-request autom vp run --filter t3code-relay deploy -- --stage "$USER" --env-file .env.local ``` +## GitHub release infrastructure + +Configure the `production` GitHub environment with the relay and Clerk values above. Configure +these repository-level release credentials before running stable validation: + +- Release GitHub App: `RELEASE_APP_ID` and `RELEASE_APP_PRIVATE_KEY`; the installation needs + repository Contents read/write access so the workflow can create releases and finalize versions. +- Apple signing/notarization: the secrets and variables in the macOS section below. +- Azure Trusted Signing: the secrets in the Windows section below. +- Vercel: `VERCEL_TOKEN`, `VERCEL_ORG_ID`, and `VERCEL_PROJECT_ID` for the dedicated project. + +Discord secrets are optional. Both announcement steps use `continue-on-error`, so missing Discord +configuration cannot block a release. + ## Hosted web app release deployment The hosted app is intentionally not deployed by Vercel's Git integration. The @@ -104,41 +123,28 @@ Required GitHub Actions secrets: Optional GitHub Actions variables: - `VERCEL_TEAM_SLUG`: overrides the Vercel CLI scope when the team slug is preferred over the `VERCEL_ORG_ID` secret. -- `T3CODE_WEB_ROUTER_URL`: defaults to `https://app.t3.codes`. -- `T3CODE_WEB_LATEST_DOMAIN`: defaults to `latest.app.t3.codes`. -- `T3CODE_WEB_NIGHTLY_DOMAIN`: defaults to `nightly.app.t3.codes`. - -Required Vercel domains: - -- `app.t3.codes`: the router domain users open, updated by stable releases. -- `latest.app.t3.codes`: channel alias updated by stable releases. -- `nightly.app.t3.codes`: channel alias updated by nightly releases. - -The router domain uses `apps/web/vercel.ts` routes. Users opt into a channel by -visiting `/__t3code/channel?channel=latest` or -`/__t3code/channel?channel=nightly`; the router stores the -`t3code_web_channel` cookie and rewrites future requests on `app.t3.codes` to -the matching channel alias. - -The release deploy job rewrites release package versions before upload so the -hosted app's About panel renders the release version. Stable deploys alias the -same deployment to both the `latest` channel and the router domain so the router -rules stay current. Nightly deploys only alias the `nightly` channel. The job -also passes `VITE_HOSTED_APP_CHANNEL=latest|nightly`, which renders the hosted -update track selector in the About panel. Changing the selector navigates -through `/__t3code/channel` on the router domain so the user's channel cookie is -updated before redirecting to the hosted app root. +- `T3CODE_WEB_ROUTER_URL`: defaults to `https://awtprod-command-center.vercel.app`. + +Required Vercel project: + +- Project name: `awtprod-command-center` +- Production domain: `https://awtprod-command-center.vercel.app` +- Root directory: `apps/web` + +The release deploy job rewrites package versions before upload so the About panel renders the +release version. Stable releases use a production deployment. Nightly releases use a preview +deployment and print its generated URL in the workflow summary. Because there are no fixed channel +aliases, hosted channel switching is hidden unless `VITE_HOSTED_APP_CHANNEL_SWITCHING=1` is supplied +for a future deployment strategy that supports it. One-time Vercel dashboard setup: -1. Confirm the web project root directory remains `apps/web`. -2. Add the three domains above to the web project. +1. Create or select `awtprod-command-center` and set its root directory to `apps/web`. +2. Record its project and organization IDs in the repository secrets above. 3. Disable automatic Git deployments in the dashboard if desired; the committed `vercel.ts` setting is the source-of-truth, but disconnecting Git in the dashboard is also safe. -4. Run one stable release deployment, or manually alias the current stable - deployment, so `app.t3.codes` points at a deployment containing the router - rules in `apps/web/vercel.ts`. Future stable releases keep this alias current. +4. Confirm the project production domain is `https://awtprod-command-center.vercel.app`. ## Nightly builds @@ -153,7 +159,7 @@ One-time Vercel dashboard setup: - `make_latest` is always `false` - Uses the next stable patch version as the nightly base. For example, `0.0.17` produces nightlies on `0.0.18-nightly.*`. - Publishes Electron auto-update metadata to the dedicated `nightly` updater channel, so desktop users can opt into that track independently from stable. -- Publishes the CLI package (`apps/server`, npm package `t3`) to the `nightly` npm dist-tag using the same nightly version. +- Publishes the CLI package (`apps/server`, npm package `@awtprod/command-center`) to the `nightly` npm dist-tag using the same nightly version. - Does not commit version bumps back to `main`. ## Desktop auto-update notes @@ -185,7 +191,7 @@ the package version to the release tag version. Checklist: -1. Confirm npm org/user owns package `t3` (or rename package first if needed). +1. Create the public npm package `@awtprod/command-center` under the `awtprod` scope. 2. In npm package settings, configure Trusted Publisher: - Provider: GitHub Actions - Repository: this repo @@ -198,17 +204,19 @@ Checklist: - run `npm publish --access public --tag latest` 5. Nightly runs from the same workflow file publish with `npm publish --access public --tag nightly`. -## 1) Dry-run release without signing +## 1) Signed validation release -Use this first to validate the release pipeline. +Use this before every stable publication. It performs the full signed build matrix but has no +publication side effects. -1. Confirm no signing secrets are required for this test. -2. Create a test tag: - - `git tag v0.0.0-test.1` - - `git push origin v0.0.0-test.1` -3. Wait for `.github/workflows/release.yml` to finish. -4. Verify the GitHub Release contains all platform artifacts. -5. Download each artifact and sanity-check installation on each OS. +1. Open **Actions → Release → Run workflow**. +2. Select `mode=validate`, `channel=stable`, and enter the exact version (for v1, `1.0.0`). +3. Wait for every preflight, relay, signing, build, and artifact-validation job to pass. +4. Download the `command-center--validation` artifact. +5. Verify signatures, notarization, install/uninstall behavior, protocols, embedded backend startup, + clean-profile functionality, SSH launch, persistence, and mock-server updater detection on every + supported OS. +6. Only after acceptance passes, run the same version with `mode=publish`. ## 2) Apple signing + notarization setup (macOS) @@ -227,14 +235,14 @@ Required repository variables: Optional repository variables: -- `CLERK_PASSKEY_RP_DOMAINS`: comma-separated RP-domain override. By default, the build derives the - domain from the production Clerk publishable key. +- `CLERK_PASSKEY_RP_DOMAINS`: comma-separated production Clerk RP domains. It is required by the + stable release workflow so associated-domain intent is explicit. Checklist: 1. Apple Developer account access: - Team has rights to create Developer ID certificates. -2. Create an explicit App ID for `com.t3tools.t3code` and enable Associated Domains. +2. Create an explicit App ID for `com.awtprod.commandcenter` and enable Associated Domains. 3. Create a `Developer ID Application` certificate and a compatible provisioning profile for that App ID with Associated Domains enabled. 4. Export the certificate + private key as `.p12` from Keychain. @@ -248,7 +256,7 @@ Checklist: - `APPLE_API_KEY_ID`: Key ID - `APPLE_API_ISSUER`: Issuer ID 10. Complete the Clerk Native API and AASA setup in [T3 Connect Clerk Setup](../cloud/t3-connect-clerk.md#desktop-passkeys). -11. Re-run a tag release and confirm macOS artifacts are signed/notarized and contain the expected +11. Run a stable validation and confirm macOS artifacts are signed/notarized and contain the expected `com.apple.developer.associated-domains` entitlement. Notes: @@ -287,23 +295,23 @@ Checklist: ## 4) Ongoing release checklist 1. Ensure `main` is green in CI. -2. Bump app version as needed. -3. Create release tag: `vX.Y.Z`. -4. Push tag. -5. Verify workflow steps: +2. Run `mode=validate` for the intended version and complete the clean-profile acceptance matrix. +3. Run `mode=publish` for the same version. A pushed stable tag also invokes publish mode, but the + manual interface is preferred for the first release. +4. Verify workflow steps: - preflight passes - all matrix builds pass - release job uploads expected files -6. Smoke test downloaded artifacts. +5. Smoke test downloaded artifacts. ## 5) Troubleshooting - macOS build unsigned when expected signed: - Check all Apple secrets plus `APPLE_TEAM_ID` are populated and non-empty. - - Confirm the provisioning profile belongs to `APPLE_TEAM_ID.com.t3tools.t3code` and includes + - Confirm the provisioning profile belongs to `APPLE_TEAM_ID.com.awtprod.commandcenter` and includes Associated Domains. - Windows build unsigned when expected signed: - Check all Azure ATS and auth secrets are populated and non-empty. - Build fails with signing error: - - Retry with secrets removed to confirm unsigned path still works. - - Re-check certificate/profile names and tenant/client credentials. + - Re-check certificate/profile names, associated domains, and tenant/client credentials. Stable + validation and publication intentionally do not have an unsigned fallback. diff --git a/docs/reference/scripts.md b/docs/reference/scripts.md index 1831f32b0a3..c8970baeaf1 100644 --- a/docs/reference/scripts.md +++ b/docs/reference/scripts.md @@ -25,8 +25,8 @@ - Default build is unsigned/not notarized for local sharing. - The DMG build uses `assets/prod/black-macos-1024.png` as the production app icon source. -- Desktop production windows load the bundled UI from `t3code://app/index.html` (not a `127.0.0.1` document URL). -- Desktop packaging includes `apps/server/dist` (the `t3` backend) and starts it on loopback with an auth token for WebSocket/API traffic. +- Desktop production windows load the bundled UI from `commandcenter://app/index.html` (not a `127.0.0.1` document URL). +- Desktop packaging includes `apps/server/dist` (the `@awtprod/command-center` backend) and starts it on loopback with an auth token for WebSocket/API traffic. - Your tester can still open it on macOS by right-clicking the app and choosing **Open** on first launch. - To keep staging files for debugging package contents, run: `vp run dist:desktop:dmg -- --keep-stage` - To allow code-signing/notarization when configured in CI/secrets, add: `--signed`. diff --git a/docs/user/remote-access.md b/docs/user/remote-access.md index cf7cb9f224c..19d9365c58c 100644 --- a/docs/user/remote-access.md +++ b/docs/user/remote-access.md @@ -1,6 +1,6 @@ # Remote Access -Use this when you want to connect to a T3 Code server from another device such as a phone, tablet, or separate desktop app. +Use this when you want to connect to a Command Center server from another device such as a phone, tablet, or separate desktop app. ## Recommended Setup @@ -30,12 +30,12 @@ The default endpoint controls the QR code and primary copy action for pairing li When no user default is saved, the app uses the built-in LAN endpoint for pairing links when available. You can set another endpoint as the default from the expanded endpoint list. -- HTTPS/WSS-compatible endpoints work from `https://app.t3.codes`, but are not made the default +- HTTPS/WSS-compatible endpoints work from `https://awtprod-command-center.vercel.app`, but are not made the default automatically. - Non-loopback HTTP endpoints are useful for direct LAN pairing. - Loopback-only endpoints are not useful for another device unless that device is the same machine. -If the copied link points directly at `http://192.168.x.y:3773`, open it from a client that can reach that LAN address. If it points at `https://app.t3.codes/pair?...`, the hosted web app will save the environment and connect directly to the backend URL in the link. +If the copied link points directly at `http://192.168.x.y:3773`, open it from a client that can reach that LAN address. If it points at `https://awtprod-command-center.vercel.app/pair?...`, the hosted web app will save the environment and connect directly to the backend URL in the link. ### Tailscale Endpoints @@ -50,24 +50,24 @@ Depending on your Tailscale setup, this may include: The Tailscale HTTPS endpoint uses the clean MagicDNS URL, such as `https://machine.tailnet.ts.net/`, and is disabled until the app verifies that the URL reaches this backend. Use **Setup** on the Tailscale HTTPS row to opt in. The desktop app restarts the backend -with the same server-side behavior as `t3 serve --tailscale-serve`, then the server asks Tailscale +with the same server-side behavior as `command-center serve --tailscale-serve`, then the server asks Tailscale Serve to proxy HTTPS traffic to the local backend. The Tailscale support is an endpoint provider add-on. The core remote model still works without Tailscale: LAN HTTP endpoints, custom HTTPS endpoints, future tunnels, and SSH-launched environments all use the same saved environment and pairing flow. -For `https://app.t3.codes`, prefer an HTTPS Tailnet or other HTTPS endpoint. A plain `http://100.x.y.z:3773` endpoint can still work from a desktop client or another browser page served over HTTP, but it will not work from the hosted HTTPS app because of browser mixed-content rules. +For `https://awtprod-command-center.vercel.app`, prefer an HTTPS Tailnet or other HTTPS endpoint. A plain `http://100.x.y.z:3773` endpoint can still work from a desktop client or another browser page served over HTTP, but it will not work from the hosted HTTPS app because of browser mixed-content rules. ### Option 2: Headless Server (CLI) Use this when you want to run the server without a GUI, for example on a remote machine over SSH. -Run the server with `t3 serve`. +Run the server with `command-center serve`. ```bash -npx t3 serve --host "$(tailscale ip -4)" +npx @awtprod/command-center serve --host "$(tailscale ip -4)" ``` -`t3 serve` starts the server without opening a browser and prints: +`command-center serve` starts the server without opening a browser and prints: - a connection string - a pairing token @@ -81,37 +81,37 @@ From there, connect from another device in either of these ways: - in the desktop app, enter the host and token separately - in the hosted web app, open a hosted pairing URL when the backend is reachable over HTTPS -Use `t3 serve --help` for the full flag reference. It supports the same general startup options as the normal server command, including an optional `cwd` argument. +Use `command-center serve --help` for the full flag reference. It supports the same general startup options as the normal server command, including an optional `cwd` argument. For hosted web pairing over Tailscale HTTPS, opt in to Tailscale Serve: ```bash -npx t3 serve --tailscale-serve +npx @awtprod/command-center serve --tailscale-serve ``` By default this configures Tailscale Serve on HTTPS port 443 and advertises `https://machine.tailnet.ts.net/`. Advanced users can choose a different HTTPS port: ```bash -npx t3 serve --tailscale-serve --tailscale-serve-port 8443 +npx @awtprod/command-center serve --tailscale-serve --tailscale-serve-port 8443 ``` > Note > The GUIs do not currently support adding projects on remote environments. -> For now, use `t3 project ...` on the server machine instead. +> For now, use `command-center project ...` on the server machine instead. > Full GUI support for remote project management is coming soon. ### Option 3: Desktop-Managed SSH Launch -Use this when you want the desktop app to start or reuse T3 Code on another machine over SSH. +Use this when you want the desktop app to start or reuse Command Center on another machine over SSH. 1. Open **Settings** → **Connections**. 2. Under **Remote Environments**, choose **Add environment**. 3. Select the SSH launch flow. 4. Enter the SSH target, such as `user@example.com`. -5. Confirm the launch. The desktop app probes the host, starts or reuses a remote T3 server, opens a local port forward, and saves the environment. +5. Confirm the launch. The desktop app probes the host, starts or reuses a remote Command Center server, opens a local port forward, and saves the environment. -After setup, the renderer connects to a local forwarded HTTP/WebSocket endpoint. The remote host still owns the actual T3 server, projects, files, git state, terminals, and provider sessions. +After setup, the renderer connects to a local forwarded HTTP/WebSocket endpoint. The remote host still owns the actual Command Center server, projects, files, git state, terminals, and provider sessions. SSH launch is a desktop feature because it needs local process and SSH access. Once the environment is paired and saved, it uses the same environment list and connection model as direct LAN, Tailscale, HTTPS, or future tunnel-backed environments. @@ -119,13 +119,13 @@ SSH launch is a desktop feature because it needs local process and SSH access. O The desktop SSH launcher connects with a non-interactive `sh` session, writes a small launcher script under `~/.command-center/ssh-launch//`, starts or reuses a remote Command Center server, and forwards the remote loopback port back to your desktop. -The remote host must have a compatible Node.js runtime. T3 Code uses the server package's `engines.node` requirement: +The remote host must have a compatible Node.js runtime. Command Center uses the server package's `engines.node` requirement: ```text ^22.16 || ^23.11 || >=24.10 ``` -During SSH launch, T3 Code first checks whether `node` is already available on `PATH`. If it is missing, the launcher tries common non-interactive shell locations and version-manager shims/activation hooks: +During SSH launch, Command Center first checks whether `node` is already available on `PATH`. If it is missing, the launcher tries common non-interactive shell locations and version-manager shims/activation hooks: - `~/.local/bin`, `~/bin`, `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`, `/bin` - Volta via `~/.volta/bin` @@ -136,7 +136,7 @@ During SSH launch, T3 Code first checks whether `node` is already available on ` - nvm via `$NVM_DIR/nvm.sh`, then `nvm use default`, `nvm use node`, or `nvm use --lts` - installed nvm versions under `$NVM_DIR/versions/node/*/bin` -If launch fails with `node: command not found`, a port-scan failure, or a message that the remote Node version does not satisfy the required range, SSH into the host and check the same non-interactive shell path T3 Code uses: +If launch fails with `node: command not found`, a port-scan failure, or a message that the remote Node version does not satisfy the required range, SSH into the host and check the same non-interactive shell path Command Center uses: ```bash ssh user@example.com 'sh -lc "command -v node && node --version"' @@ -158,7 +158,7 @@ The remote device does not need a long-lived secret up front. Instead: -1. `t3 serve` issues a one-time owner pairing token. +1. `command-center serve` issues a one-time owner pairing token. 2. The remote device exchanges that token with the server. 3. The server creates an authenticated session for that device. @@ -166,21 +166,24 @@ After pairing, future access is session-based. You do not need to keep reusing t ## Hosted Web App Pairing -The hosted web app at `https://app.t3.codes` can save a remote backend in browser local storage from a URL like: +The hosted web app at `https://awtprod-command-center.vercel.app` can save a remote backend in browser local storage from a URL like: ```text -https://app.t3.codes/pair?host=https://backend.example.com:3773#token=PAIRCODE +https://awtprod-command-center.vercel.app/pair?host=https://backend.example.com:3773#token=PAIRCODE ``` Use hosted pairing when the backend is reachable from the browser over HTTPS/WSS. This includes a backend behind a trusted HTTPS tunnel or another HTTPS endpoint you operate. Do not use hosted pairing for plain HTTP LAN URLs such as `http://192.168.x.y:3773`. Browsers block an HTTPS page from connecting to an insecure HTTP or WS backend. For those endpoints, use the direct pairing URL shown by the desktop app or CLI from a client that can open that HTTP URL directly. -Hosted pairing does not proxy traffic through T3 Code. The browser still connects directly to the backend URL in the pairing link. +Hosted pairing does not proxy traffic through Command Center. The browser still connects directly to the backend URL in the pairing link. ## Managing Access Later -Use `t3 auth` to manage access after the initial pairing flow. +Use `command-center auth` to manage access after the initial pairing flow. + +The v1 package also installs `t3` as a temporary compatibility alias, but new scripts and remote +bootstrap commands should use `command-center`. Typical uses: @@ -188,7 +191,7 @@ Typical uses: - inspect active sessions - revoke old pairing links or sessions -Use `t3 auth --help` and the nested subcommand help pages for the full reference. +Use `command-center auth --help` and the nested subcommand help pages for the full reference. ## Security Notes @@ -196,4 +199,4 @@ Use `t3 auth --help` and the nested subcommand help pages for the full reference - Prefer binding `--host` to a trusted private address, such as a Tailnet IP, instead of exposing the server broadly. - Anyone with a valid pairing credential can create a session until that credential expires or is revoked. - Hosted pairing links keep the credential in the URL hash so it is not sent to the hosted app server, but it can still be exposed through browser history, screenshots, logs, or copy/paste. -- Use `t3 auth` to revoke credentials or sessions you no longer trust. +- Use `command-center auth` to revoke credentials or sessions you no longer trust. diff --git a/package.json b/package.json index 37795298c5b..afb9cb8b252 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dev:stop": "node scripts/dev-stop.ts", "serve": "node scripts/dev-runner.ts serve", "serve:fast": "node scripts/dev-runner.ts serve --skip-build", - "start": "vp run --filter t3 start", + "start": "vp run --filter @awtprod/command-center start", "start:desktop": "vp run --filter @t3tools/desktop start", "start:marketing": "vp run --filter @t3tools/marketing preview", "start:mock-update-server": "node scripts/mock-update-server.ts", @@ -21,7 +21,7 @@ "icons:check": "node scripts/export-brand-icons.ts --check", "build": "vp run --filter './apps/*' --filter './packages/*' --filter './oxlint-plugin-t3code' --filter './scripts' build", "build:marketing": "vp run --filter @t3tools/marketing build", - "build:desktop": "vp run --filter @t3tools/desktop --filter t3 build", + "build:desktop": "vp run --filter @t3tools/desktop --filter @awtprod/command-center build", "typecheck": "vp run -r --concurrency-limit 2 typecheck", "tc": "vp run -r --concurrency-limit 2 typecheck", "lint": "vp lint --report-unused-disable-directives", diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts index 6c853ca1631..dcd3c664ce6 100644 --- a/packages/contracts/src/ipc.ts +++ b/packages/contracts/src/ipc.ts @@ -162,7 +162,7 @@ export type DesktopUpdateStatus = export type DesktopRuntimeArch = "arm64" | "x64" | "other"; export type DesktopTheme = "light" | "dark" | "system"; export type DesktopUpdateChannel = "latest" | "nightly"; -export type DesktopAppStageLabel = "Alpha" | "Dev" | "Nightly"; +export type DesktopAppStageLabel = "Dev" | "Latest" | "Nightly"; export const DesktopUpdateStatusSchema = Schema.Literals([ "disabled", @@ -177,7 +177,7 @@ export const DesktopUpdateStatusSchema = Schema.Literals([ export const DesktopRuntimeArchSchema = Schema.Literals(["arm64", "x64", "other"]); export const DesktopThemeSchema = Schema.Literals(["light", "dark", "system"]); export const DesktopUpdateChannelSchema = Schema.Literals(["latest", "nightly"]); -export const DesktopAppStageLabelSchema = Schema.Literals(["Alpha", "Dev", "Nightly"]); +export const DesktopAppStageLabelSchema = Schema.Literals(["Dev", "Latest", "Nightly"]); export interface DesktopAppBranding { baseName: string; diff --git a/packages/shared/src/connectAuth.test.ts b/packages/shared/src/connectAuth.test.ts index 9ffef3936bb..d51474f6116 100644 --- a/packages/shared/src/connectAuth.test.ts +++ b/packages/shared/src/connectAuth.test.ts @@ -12,13 +12,13 @@ import { describe("connectAuth", () => { it("round-trips state and challenge through the authorize URL fragment", () => { const url = buildConnectAuthorizeRequestUrl({ - hostedAppUrl: "https://app.t3.codes", + hostedAppUrl: "https://awtprod-command-center.vercel.app", state: "q7mK9xV2pL4nR8sT6wYzAQ", challenge: "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", }); const parsed = new URL(url); - expect(parsed.origin).toBe("https://app.t3.codes"); + expect(parsed.origin).toBe("https://awtprod-command-center.vercel.app"); expect(parsed.pathname).toBe("/connect"); expect(parsed.search).toBe(""); expect(readConnectAuthorizeRequest(parsed)).toEqual({ diff --git a/packages/shared/src/connectAuth.ts b/packages/shared/src/connectAuth.ts index 8d849d77ae6..2ec18dd553a 100644 --- a/packages/shared/src/connectAuth.ts +++ b/packages/shared/src/connectAuth.ts @@ -12,7 +12,7 @@ const CONNECT_CALLBACK_PATH = "/connect/callback"; * decide whether it is the hosted deployment — the two must agree, so the * default lives here. */ -export const DEFAULT_HOSTED_APP_URL = "https://app.t3.codes"; +export const DEFAULT_HOSTED_APP_URL = "https://awtprod-command-center.vercel.app"; /** * Requested at authorize time by the hosted page and honored by the CLI's diff --git a/packages/shared/src/remote.test.ts b/packages/shared/src/remote.test.ts index 5b7a9973652..45543c8c8bd 100644 --- a/packages/shared/src/remote.test.ts +++ b/packages/shared/src/remote.test.ts @@ -37,7 +37,7 @@ describe("remote", () => { expect( resolveRemotePairingTarget({ pairingUrl: - "https://app.t3.codes/pair?host=https%3A%2F%2Fdesktop.tailnet.ts.net%3A44342%2F#token=pairing-token", + "https://awtprod-command-center.vercel.app/pair?host=https%3A%2F%2Fdesktop.tailnet.ts.net%3A44342%2F#token=pairing-token", }), ).toEqual({ credential: "pairing-token", @@ -88,7 +88,8 @@ describe("remote", () => { it("normalizes a protocol-relative host from a hosted pairing link", () => { expect( resolveRemotePairingTarget({ - pairingUrl: "https://app.t3.codes/pair?host=%2F%2Fremote.example.com#token=pairing-token", + pairingUrl: + "https://awtprod-command-center.vercel.app/pair?host=%2F%2Fremote.example.com#token=pairing-token", }), ).toEqual({ credential: "pairing-token", @@ -156,7 +157,7 @@ describe("remote", () => { try { resolveRemotePairingTarget({ pairingUrl: - "https://app.t3.codes/pair?host=ftp%3A%2F%2Fremote.example.com#token=pairing-token", + "https://awtprod-command-center.vercel.app/pair?host=ftp%3A%2F%2Fremote.example.com#token=pairing-token", }); } catch (cause) { hostError = cause; diff --git a/packages/ssh/src/command.test.ts b/packages/ssh/src/command.test.ts index e5b621f87aa..06880a55aad 100644 --- a/packages/ssh/src/command.test.ts +++ b/packages/ssh/src/command.test.ts @@ -99,39 +99,41 @@ describe("ssh command", () => { }), ); - it.effect("resolves the remote t3 package spec from the desktop release channel", () => - Effect.sync(() => { - assert.equal( - resolveRemoteT3CliPackageSpec({ - appVersion: "0.0.17", - updateChannel: "latest", - }), - "t3@0.0.17", - ); - assert.equal( - resolveRemoteT3CliPackageSpec({ - appVersion: "0.0.17-nightly.20260415.44", - updateChannel: "nightly", - }), - "t3@0.0.17-nightly.20260415.44", - ); - assert.equal( - resolveRemoteT3CliPackageSpec({ - appVersion: "0.0.0-dev", - updateChannel: "nightly", - isDevelopment: true, - }), - "t3@nightly", - ); - assert.equal( - resolveRemoteT3CliPackageSpec({ - appVersion: "0.0.0-dev", - updateChannel: "latest", - isDevelopment: true, - }), - "t3@nightly", - ); - }), + it.effect( + "resolves the remote Command Center package spec from the desktop release channel", + () => + Effect.sync(() => { + assert.equal( + resolveRemoteT3CliPackageSpec({ + appVersion: "0.0.17", + updateChannel: "latest", + }), + "@awtprod/command-center@0.0.17", + ); + assert.equal( + resolveRemoteT3CliPackageSpec({ + appVersion: "0.0.17-nightly.20260415.44", + updateChannel: "nightly", + }), + "@awtprod/command-center@0.0.17-nightly.20260415.44", + ); + assert.equal( + resolveRemoteT3CliPackageSpec({ + appVersion: "0.0.0-dev", + updateChannel: "nightly", + isDevelopment: true, + }), + "@awtprod/command-center@nightly", + ); + assert.equal( + resolveRemoteT3CliPackageSpec({ + appVersion: "0.0.0-dev", + updateChannel: "latest", + isDevelopment: true, + }), + "@awtprod/command-center@nightly", + ); + }), ); it.effect("reads the last non-empty ssh output line", () => diff --git a/packages/ssh/src/command.ts b/packages/ssh/src/command.ts index 10927b43089..f448fd8074d 100644 --- a/packages/ssh/src/command.ts +++ b/packages/ssh/src/command.ts @@ -15,6 +15,7 @@ import { buildSshChildEnvironment, type SshAuthOptions } from "./auth.ts"; import { SshCommandError, SshInvalidTargetError } from "./errors.ts"; const PUBLISHABLE_T3_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u; +const COMMAND_CENTER_CLI_PACKAGE = "@awtprod/command-center"; const DEFAULT_SSH_COMMAND_TIMEOUT_MS = 60_000; const MAX_SSH_ERROR_OUTPUT_LENGTH = 4_000; @@ -371,12 +372,12 @@ export function resolveRemoteT3CliPackageSpec(input: { }): string { const appVersion = input.appVersion.trim(); if (!input.isDevelopment && PUBLISHABLE_T3_VERSION_PATTERN.test(appVersion)) { - return `t3@${appVersion}`; + return `${COMMAND_CENTER_CLI_PACKAGE}@${appVersion}`; } if (input.isDevelopment) { - return "t3@nightly"; + return `${COMMAND_CENTER_CLI_PACKAGE}@nightly`; } - return input.updateChannel === "nightly" ? "t3@nightly" : "t3@latest"; + return `${COMMAND_CENTER_CLI_PACKAGE}@${input.updateChannel === "nightly" ? "nightly" : "latest"}`; } diff --git a/packages/ssh/src/tunnel.test.ts b/packages/ssh/src/tunnel.test.ts index 7e7a5a54276..ade8c6d9d23 100644 --- a/packages/ssh/src/tunnel.test.ts +++ b/packages/ssh/src/tunnel.test.ts @@ -89,14 +89,15 @@ function commandArgs(command: ChildProcess.Command): ReadonlyArray { } describe("ssh tunnel scripts", () => { - it("builds the remote t3 runner with npx and npm fallbacks", () => { + it("builds the remote Command Center runner with installed and npm fallbacks", () => { const script = buildRemoteT3RunnerScript({ nodeEngineRange: TEST_NODE_ENGINE_RANGE }); assert.include(script, "T3_NODE_SCRIPT_PATH=''"); + assert.include(script, 'exec command-center "$@"'); assert.include(script, 'exec t3 "$@"'); - assert.include(script, "exec npx --yes 't3@latest' \"$@\""); - assert.include(script, "exec npm exec --yes 't3@latest' -- \"$@\""); - assert.include(script, "could not install 't3@latest'"); + assert.include(script, "exec npx --yes '@awtprod/command-center@latest' \"$@\""); + assert.include(script, "exec npm exec --yes '@awtprod/command-center@latest' -- \"$@\""); + assert.include(script, "could not install '@awtprod/command-center@latest'"); assert.include(script, 'prepend_path_if_dir "$HOME/.local/bin"'); assert.include(script, `T3_NODE_ENGINE_RANGE='${TEST_NODE_ENGINE_RANGE}'`); assert.include(script, "remote_node_satisfies_engine()"); @@ -124,12 +125,21 @@ describe("ssh tunnel scripts", () => { it("shell-quotes package specs in the remote t3 runner", () => { const script = buildRemoteT3RunnerScript({ - packageSpec: "t3@nightly; touch /tmp/t3-owned", + packageSpec: "@awtprod/command-center@nightly; touch /tmp/t3-owned", }); - assert.include(script, "exec npx --yes 't3@nightly; touch /tmp/t3-owned' \"$@\""); - assert.include(script, "exec npm exec --yes 't3@nightly; touch /tmp/t3-owned' -- \"$@\""); - assert.notInclude(script, "exec npx --yes t3@nightly; touch /tmp/t3-owned"); + assert.include( + script, + "exec npx --yes '@awtprod/command-center@nightly; touch /tmp/t3-owned' \"$@\"", + ); + assert.include( + script, + "exec npm exec --yes '@awtprod/command-center@nightly; touch /tmp/t3-owned' -- \"$@\"", + ); + assert.notInclude( + script, + "exec npx --yes @awtprod/command-center@nightly; touch /tmp/t3-owned", + ); }); it("builds the remote t3 runner with a node script override", () => { @@ -172,15 +182,21 @@ describe("ssh tunnel scripts", () => { assert.include(buildRemoteLaunchScript(), '"$RUNNER_FILE" serve --host 127.0.0.1'); assert.include(buildRemoteLaunchScript(), '--base-dir "$DEFAULT_SERVER_HOME"'); assert.notInclude(buildRemoteLaunchScript(), "server-home"); - assert.include(buildRemoteLaunchScript(), "Remote T3 server did not become ready"); - assert.include(buildRemoteLaunchScript({ packageSpec: "t3@nightly" }), "t3@nightly"); + assert.include(buildRemoteLaunchScript(), "Remote Command Center server did not become ready"); + assert.include( + buildRemoteLaunchScript({ packageSpec: "@awtprod/command-center@nightly" }), + "@awtprod/command-center@nightly", + ); assert.include( buildRemotePairingScript(target), '"$RUNNER_FILE" auth pairing create --base-dir "$PAIRING_BASE_DIR" --json', ); assert.include(buildRemotePairingScript(target), 'PAIRING_BASE_DIR="$DEFAULT_SERVER_HOME"'); assert.notInclude(buildRemotePairingScript(target), "server-home"); - assert.include(buildRemotePairingScript(target, { packageSpec: "t3@nightly" }), "t3@nightly"); + assert.include( + buildRemotePairingScript(target, { packageSpec: "@awtprod/command-center@nightly" }), + "@awtprod/command-center@nightly", + ); assert.include( buildRemoteStopScript(target), 'if [ "$REMOTE_MANAGED" != "external" ] && [ -n "$REMOTE_PID" ]', diff --git a/packages/ssh/src/tunnel.ts b/packages/ssh/src/tunnel.ts index 81343dcfdfe..1c9a1edff73 100644 --- a/packages/ssh/src/tunnel.ts +++ b/packages/ssh/src/tunnel.ts @@ -422,6 +422,9 @@ if [ -n "$T3_NODE_SCRIPT_PATH" ]; then fi exec node "$T3_NODE_SCRIPT_PATH" "$@" fi +if command -v command-center >/dev/null 2>&1; then + exec command-center "$@" +fi if command -v t3 >/dev/null 2>&1; then exec t3 "$@" fi @@ -431,7 +434,7 @@ fi if command -v npm >/dev/null 2>&1; then exec npm exec --yes @@T3_PACKAGE_SPEC@@ -- "$@" fi -printf 'Remote host is missing the t3 CLI and could not install @@T3_PACKAGE_SPEC@@ because node/npm/npx are unavailable on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2 +printf 'Remote host is missing the Command Center CLI and could not install @@T3_PACKAGE_SPEC@@ because node/npm/npx are unavailable on PATH. Install Node or configure a supported version manager for non-interactive shells.\\n' >&2 exit 1 `; @@ -579,7 +582,7 @@ if [ -z "$REMOTE_PORT" ]; then printf '%s\\n' "$REMOTE_PORT" >"$PORT_FILE" printf 'managed\\n' >"$MANAGED_FILE" if ! wait_ready "@@T3_READY_TIMEOUT_MS@@"; then - printf 'Remote T3 server did not become ready on 127.0.0.1:%s.\\n' "$REMOTE_PORT" >&2 + printf 'Remote Command Center server did not become ready on 127.0.0.1:%s.\\n' "$REMOTE_PORT" >&2 tail -n 80 "$LOG_FILE" >&2 2>/dev/null || true kill "$REMOTE_PID" 2>/dev/null || true wait_for_pid_exit "$REMOTE_PID" @@ -631,7 +634,9 @@ fi `; export function buildRemoteT3RunnerScript(input?: RemoteT3RunnerOptions): string { - const packageSpec = shellSingleQuote(input?.packageSpec?.trim() || "t3@latest"); + const packageSpec = shellSingleQuote( + input?.packageSpec?.trim() || "@awtprod/command-center@latest", + ); const nodeScriptPath = input?.nodeScriptPath?.trim() || ""; return stripTrailingNewlines( applyScriptPlaceholders(REMOTE_RUNNER_SCRIPT, { diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index 399f7230cf6..be1b8c1d9b4 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -85,7 +85,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { }); it("switches desktop packaging product names to nightly for nightly builds", () => { - assert.equal(resolveDesktopProductName("0.0.17"), "Command Center (Alpha)"); + assert.equal(resolveDesktopProductName("0.0.17"), "Command Center"); assert.equal( resolveDesktopProductName("0.0.17-nightly.20260413.42"), "Command Center (Nightly)", @@ -118,7 +118,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { ConfigProvider.layer( ConfigProvider.fromEnv({ env: { - T3CODE_DESKTOP_UPDATE_REPOSITORY: "pingdotgg/t3code", + T3CODE_DESKTOP_UPDATE_REPOSITORY: "pingdotgg/commandcenter", }, }), ), @@ -129,7 +129,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { ConfigProvider.layer( ConfigProvider.fromEnv({ env: { - GITHUB_REPOSITORY: "pingdotgg/t3code", + GITHUB_REPOSITORY: "pingdotgg/commandcenter", }, }), ), @@ -139,13 +139,13 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { assert.deepStrictEqual(latestConfig, { provider: "github", owner: "pingdotgg", - repo: "t3code", + repo: "commandcenter", releaseType: "release", }); assert.deepStrictEqual(nightlyConfig, { provider: "github", owner: "pingdotgg", - repo: "t3code", + repo: "commandcenter", releaseType: "prerelease", channel: "nightly", }); @@ -353,22 +353,22 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { it("derives macOS passkey signing configuration from the Clerk publishable key", () => { const configuration = resolveMacPasskeySigningConfiguration({ T3CODE_APPLE_TEAM_ID: "abc1234567", - T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile", + T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/commandcenter.provisionprofile", T3CODE_CLERK_PUBLISHABLE_KEY: `pk_test_${btoa("example.clerk.accounts.dev$")}`, }); assert.deepStrictEqual(configuration, { - appId: "com.t3tools.t3code", + appId: "com.awtprod.commandcenter", teamId: "ABC1234567", rpDomains: ["example.clerk.accounts.dev"], - provisioningProfilePath: "/tmp/t3code.provisionprofile", + provisioningProfilePath: "/tmp/commandcenter.provisionprofile", }); }); it("normalizes explicit macOS passkey RP domains and renders required entitlements", () => { const configuration = resolveMacPasskeySigningConfiguration({ T3CODE_APPLE_TEAM_ID: "ABC1234567", - T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile", + T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/commandcenter.provisionprofile", T3CODE_CLERK_PASSKEY_RP_DOMAINS: " Clerk.Example.com,example.clerk.accounts.dev,clerk.example.com ", }); @@ -378,7 +378,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { "clerk.example.com", "example.clerk.accounts.dev", ]); - assert.include(entitlements, "ABC1234567.com.t3tools.t3code"); + assert.include(entitlements, "ABC1234567.com.awtprod.commandcenter"); assert.include(entitlements, "webcredentials:clerk.example.com"); assert.include(entitlements, "webcredentials:example.clerk.accounts.dev"); assert.include(entitlements, "com.apple.security.cs.allow-jit"); @@ -408,7 +408,7 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { "https://domain-user:domain-secret@example.clerk.accounts.dev/path?token=query-secret"; const invalidDomainError = captureError({ T3CODE_APPLE_TEAM_ID: "ABC1234567", - T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile", + T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/commandcenter.provisionprofile", T3CODE_CLERK_PASSKEY_RP_DOMAINS: unsafeDomain, }); assert.instanceOf(invalidDomainError, InvalidMacPasskeyRpDomainError); @@ -426,14 +426,14 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { () => resolveMacPasskeySigningConfiguration({ T3CODE_APPLE_TEAM_ID: "ABC1234567", - T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile", + T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/commandcenter.provisionprofile", T3CODE_CLERK_PASSKEY_RP_DOMAINS: "example.clerk.accounts.dev:8443", }), /Invalid passkey RP domain/u, ); const invalidPublishableKeyError = captureError({ T3CODE_APPLE_TEAM_ID: "ABC1234567", - T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/t3code.provisionprofile", + T3CODE_MACOS_PROVISIONING_PROFILE: "/tmp/commandcenter.provisionprofile", T3CODE_CLERK_PUBLISHABLE_KEY: "pk_test_%", }); assert.instanceOf(invalidPublishableKeyError, InvalidMacPasskeyPublishableKeyError); @@ -469,19 +469,41 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { Effect.gen(function* () { const config = yield* createBuildConfig("mac", "dmg", "1.2.3", true, false, undefined, { entitlementsPath: "/tmp/entitlements.mac.plist", - provisioningProfilePath: "/tmp/t3code.provisionprofile", + provisioningProfilePath: "/tmp/commandcenter.provisionprofile", }); const mac = config.mac as Record; - assert.equal(config.appId, "com.t3tools.t3code"); + assert.equal(config.appId, "com.awtprod.commandcenter"); assert.equal(mac.entitlements, "/tmp/entitlements.mac.plist"); - assert.equal(mac.provisioningProfile, "/tmp/t3code.provisionprofile"); + assert.equal(mac.provisioningProfile, "/tmp/commandcenter.provisionprofile"); assert.deepStrictEqual(mac.protocols, [ - { name: "Command Center", schemes: ["t3code", "t3code-dev"] }, + { name: "Command Center", schemes: ["commandcenter", "commandcenter-dev"] }, ]); }).pipe(Effect.provide(ConfigProvider.layer(ConfigProvider.fromEnv({ env: {} })))), ); + it.effect("uses the canonical Linux executable and window class", () => + Effect.gen(function* () { + const config = yield* createBuildConfig( + "linux", + "AppImage", + "1.0.0", + false, + false, + undefined, + undefined, + ); + const linux = config.linux as { + readonly executableName: string; + readonly desktop: { readonly entry: { readonly StartupWMClass: string } }; + }; + + assert.equal(config.appId, "com.awtprod.commandcenter"); + assert.equal(linux.executableName, "command-center"); + assert.equal(linux.desktop.entry.StartupWMClass, "command-center"); + }), + ); + it.effect("keeps executable resource editing enabled for unsigned Windows builds", () => Effect.gen(function* () { const config = yield* createBuildConfig( diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 1f717bfb7b0..d7f05e35f54 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -35,7 +35,7 @@ import { Command, Flag } from "effect/unstable/cli"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; const LINUX_ICON_SIZES = [16, 22, 24, 32, 48, 64, 128, 256, 512] as const; -const DESKTOP_APP_ID = "com.t3tools.t3code"; +const DESKTOP_APP_ID = "com.awtprod.commandcenter"; const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u; const BuildPlatform = Schema.Literals(["mac", "linux", "win"]); @@ -1429,7 +1429,7 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( protocols: [ { name: "Command Center", - schemes: ["t3code", "t3code-dev"], + schemes: ["commandcenter", "commandcenter-dev"], }, ], ...(macPasskeySigning @@ -1444,12 +1444,12 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( if (platform === "linux") { buildConfig.linux = { target: [target], - executableName: "t3code", + executableName: "command-center", icon: "icons", category: "Development", desktop: { entry: { - StartupWMClass: "t3code", + StartupWMClass: "command-center", }, }, }; diff --git a/scripts/dev-runner.test.ts b/scripts/dev-runner.test.ts index 90c9c90b3b4..c2a76cae083 100644 --- a/scripts/dev-runner.test.ts +++ b/scripts/dev-runner.test.ts @@ -109,7 +109,11 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { it.effect("runs only the backend in serve mode — no Vite dev server", () => Effect.sync(() => { - assert.deepStrictEqual(getDevRunnerModeArgs("serve"), ["run", "--filter=t3", "dev"]); + assert.deepStrictEqual(getDevRunnerModeArgs("serve"), [ + "run", + "--filter=@awtprod/command-center", + "dev", + ]); }), ); @@ -119,7 +123,7 @@ it.layer(NodeServices.layer)("dev-runner", (it) => { "run", "--filter=@t3tools/contracts", "--filter=@t3tools/web", - "--filter=t3", + "--filter=@awtprod/command-center", "--parallel", "dev", ]); diff --git a/scripts/dev-runner.ts b/scripts/dev-runner.ts index 54ae8288b4f..f12d1d388b1 100644 --- a/scripts/dev-runner.ts +++ b/scripts/dev-runner.ts @@ -39,17 +39,17 @@ const MODE_ARGS = { "run", "--filter=@t3tools/contracts", "--filter=@t3tools/web", - "--filter=t3", + "--filter=@awtprod/command-center", "--parallel", "dev", ], - "dev:server": ["run", "--filter=t3", "dev"], + "dev:server": ["run", "--filter=@awtprod/command-center", "dev"], "dev:web": ["run", "--filter=@t3tools/web", "dev"], "dev:desktop": ["run", "--filter=@t3tools/desktop", "--filter=@t3tools/web", "dev"], // Production-build daily driver: no Vite dev server at all. The backend // serves apps/web/dist itself (see resolveStaticDir/staticAndDevRouteLayer), // so app, /api, and /ws share one origin. - serve: ["run", "--filter=t3", "dev"], + serve: ["run", "--filter=@awtprod/command-center", "dev"], } as const satisfies Record>; /** Web build that `serve` mode runs before starting the backend. */ diff --git a/scripts/notify-discord-release.test.ts b/scripts/notify-discord-release.test.ts index 7ca108f3188..ff890e55b4a 100644 --- a/scripts/notify-discord-release.test.ts +++ b/scripts/notify-discord-release.test.ts @@ -12,10 +12,10 @@ import { const latestAnnouncement = { target: "latest", roleId: "222222222222222222", - releaseName: "T3 Code v1.2.3", + releaseName: "Command Center v1.2.3", version: "1.2.3", tag: "v1.2.3", - releaseUrl: new URL("https://github.com/t3dotgg/t3-code/releases/tag/v1.2.3"), + releaseUrl: new URL("https://github.com/awtprod/t3-code/releases/tag/v1.2.3"), timestamp: "2026-05-01T01:41:00.000Z", } as const; @@ -26,25 +26,25 @@ it("builds a prerelease Discord announcement for nightly subscribers", () => { buildDiscordReleaseAnnouncement({ target: "prerelease", roleId: "111111111111111111", - releaseName: "T3 Code Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", + releaseName: "Command Center Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", version: "1.2.4-nightly.20260501.17", tag: "v1.2.4-nightly.20260501.17", releaseUrl: new URL( - "https://github.com/t3dotgg/t3-code/releases/tag/v1.2.4-nightly.20260501.17", + "https://github.com/awtprod/t3-code/releases/tag/v1.2.4-nightly.20260501.17", ), timestamp: "2026-05-01T01:41:00.000Z", }), { content: - "<@&111111111111111111> Prerelease published: T3 Code Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", + "<@&111111111111111111> Prerelease published: Command Center Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", allowed_mentions: { roles: ["111111111111111111"], }, embeds: [ { - title: "T3 Code Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", - url: "https://github.com/t3dotgg/t3-code/releases/tag/v1.2.4-nightly.20260501.17", - description: "A new T3 Code prerelease is available for nightly testers.", + title: "Command Center Nightly 1.2.4-nightly.20260501.17 (abcdef123456)", + url: "https://github.com/awtprod/t3-code/releases/tag/v1.2.4-nightly.20260501.17", + description: "A new Command Center prerelease is available for nightly testers.", color: 0x5865f2, fields: [ { @@ -67,15 +67,15 @@ it("builds a prerelease Discord announcement for nightly subscribers", () => { it("builds a latest Discord announcement for stable subscribers", () => { assert.deepStrictEqual(buildDiscordReleaseAnnouncement(latestAnnouncement), { - content: "<@&222222222222222222> Latest published: T3 Code v1.2.3", + content: "<@&222222222222222222> Latest published: Command Center v1.2.3", allowed_mentions: { roles: ["222222222222222222"], }, embeds: [ { - title: "T3 Code v1.2.3", - url: "https://github.com/t3dotgg/t3-code/releases/tag/v1.2.3", - description: "A new T3 Code latest release is available.", + title: "Command Center v1.2.3", + url: "https://github.com/awtprod/t3-code/releases/tag/v1.2.3", + description: "A new Command Center latest release is available.", color: 0x2ecc71, fields: [ { diff --git a/scripts/notify-discord-release.ts b/scripts/notify-discord-release.ts index d013e7c2f65..ca07fd1ad01 100644 --- a/scripts/notify-discord-release.ts +++ b/scripts/notify-discord-release.ts @@ -137,8 +137,8 @@ export const buildDiscordReleaseAnnouncement = ( url: options.releaseUrl.href, description: options.target === "prerelease" - ? "A new T3 Code prerelease is available for nightly testers." - : "A new T3 Code latest release is available.", + ? "A new Command Center prerelease is available for nightly testers." + : "A new Command Center latest release is available.", color: targetColors[options.target], fields: [ { @@ -278,7 +278,7 @@ export const notifyDiscordReleaseCommand = Command.make( yield* postDiscordWebhook(webhookUrl, payload, announcement); yield* Effect.logInfo("discord release announcement completed"); }), -).pipe(Command.withDescription("Post a T3 Code release announcement to Discord.")); +).pipe(Command.withDescription("Post a Command Center release announcement to Discord.")); if (import.meta.main) { Command.run(notifyDiscordReleaseCommand, { version: "0.0.0" }).pipe( diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts index 4cadd52f033..adba266b485 100644 --- a/scripts/release-smoke.ts +++ b/scripts/release-smoke.ts @@ -254,7 +254,7 @@ try { ); assertContains( nightlyReleaseMetadata, - "name=T3 Code Nightly 9.9.10-nightly.20260413.321 (abcdef123456)", + "name=Command Center Nightly 9.9.10-nightly.20260413.321 (abcdef123456)", "Expected nightly metadata to include the short commit SHA in the release name.", ); diff --git a/scripts/resolve-nightly-release.test.ts b/scripts/resolve-nightly-release.test.ts index dda1e7081be..e80403a10e2 100644 --- a/scripts/resolve-nightly-release.test.ts +++ b/scripts/resolve-nightly-release.test.ts @@ -46,7 +46,7 @@ it("derives nightly metadata including the short commit sha in the release name" baseVersion: "9.9.10", version: "9.9.10-nightly.20260413.321", tag: "v9.9.10-nightly.20260413.321", - name: "T3 Code Nightly 9.9.10-nightly.20260413.321 (abcdef123456)", + name: "Command Center Nightly 9.9.10-nightly.20260413.321 (abcdef123456)", shortSha: "abcdef123456", }, ); diff --git a/scripts/resolve-nightly-release.ts b/scripts/resolve-nightly-release.ts index 5b42f931d7b..ad3e5fc6672 100644 --- a/scripts/resolve-nightly-release.ts +++ b/scripts/resolve-nightly-release.ts @@ -108,7 +108,7 @@ export const resolveNightlyReleaseMetadata = ( baseVersion, version, tag: `v${version}`, - name: `T3 Code Nightly ${version} (${shortSha})`, + name: `Command Center Nightly ${version} (${shortSha})`, shortSha, }; }; diff --git a/scripts/validate-release-assets.test.ts b/scripts/validate-release-assets.test.ts new file mode 100644 index 00000000000..86096f521ea --- /dev/null +++ b/scripts/validate-release-assets.test.ts @@ -0,0 +1,85 @@ +// @effect-diagnostics nodeBuiltinImport:off +import * as NodeFS from "node:fs"; +import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; + +import { assert, it } from "@effect/vitest"; + +import { validateReleaseAssets } from "./validate-release-assets.ts"; + +function writeAsset(directory: string, fileName: string, contents = fileName): number { + const path = NodePath.join(directory, fileName); + NodeFS.writeFileSync(path, contents); + return NodeFS.statSync(path).size; +} + +function writeManifest( + directory: string, + fileName: string, + version: string, + assets: ReadonlyArray<{ readonly name: string; readonly size: number }>, +): void { + NodeFS.writeFileSync( + NodePath.join(directory, fileName), + [ + `version: '${version}'`, + "files:", + ...assets.flatMap((asset) => [ + ` - url: ${asset.name}`, + " sha512: fixture", + ` size: ${asset.size}`, + ]), + "releaseDate: '2026-07-31T00:00:00.000Z'", + "", + ].join("\n"), + ); +} + +it("validates the complete stable desktop release matrix and manifest paths", () => { + const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "command-center-assets-")); + try { + const version = "1.0.0"; + const assets = [ + `Command-Center-${version}-arm64.dmg`, + `Command-Center-${version}-x64.dmg`, + `Command-Center-${version}-arm64.zip`, + `Command-Center-${version}-x64.zip`, + `Command-Center-${version}-x64.AppImage`, + `Command-Center-${version}-x64.exe`, + ].map((name) => ({ name, size: writeAsset(directory, name) })); + writeAsset(directory, `Command-Center-${version}-x64.exe.blockmap`); + + writeManifest(directory, "latest-mac.yml", version, assets.slice(0, 4)); + writeManifest(directory, "latest-linux.yml", version, [assets[4]!]); + writeManifest(directory, "latest.yml", version, [assets[5]!]); + + assert.doesNotThrow(() => validateReleaseAssets({ directory, version })); + } finally { + NodeFS.rmSync(directory, { recursive: true, force: true }); + } +}); + +it("rejects an updater manifest that references a missing asset", () => { + const directory = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "command-center-assets-")); + try { + const version = "1.0.0"; + const assets = [ + `Command-Center-${version}-arm64.dmg`, + `Command-Center-${version}-x64.dmg`, + `Command-Center-${version}-arm64.zip`, + `Command-Center-${version}-x64.zip`, + `Command-Center-${version}-x64.AppImage`, + `Command-Center-${version}-x64.exe`, + ]; + for (const asset of assets) writeAsset(directory, asset); + writeAsset(directory, `Command-Center-${version}-x64.exe.blockmap`); + + writeManifest(directory, "latest-mac.yml", version, []); + writeManifest(directory, "latest-linux.yml", version, []); + writeManifest(directory, "latest.yml", version, [{ name: "missing.exe", size: 1 }]); + + assert.throws(() => validateReleaseAssets({ directory, version })); + } finally { + NodeFS.rmSync(directory, { recursive: true, force: true }); + } +}); diff --git a/scripts/validate-release-assets.ts b/scripts/validate-release-assets.ts new file mode 100644 index 00000000000..6fd659e0698 --- /dev/null +++ b/scripts/validate-release-assets.ts @@ -0,0 +1,91 @@ +// @effect-diagnostics nodeBuiltinImport:off +import * as NodeFS from "node:fs"; +import * as NodePath from "node:path"; + +import { parseUpdateManifest } from "./lib/update-manifest.ts"; + +export interface ValidateReleaseAssetsOptions { + readonly directory: string; + readonly version: string; +} + +function requireAsset(files: ReadonlySet, fileName: string): void { + if (!files.has(fileName)) { + throw new Error(`Release asset '${fileName}' is missing.`); + } +} + +function requireArchAsset( + files: ReadonlyArray, + version: string, + arch: "arm64" | "x64", + extension: ".dmg" | ".zip" | ".AppImage" | ".exe", +): void { + const expected = `Command-Center-${version}-${arch}${extension}`; + if (!files.includes(expected)) { + throw new Error(`Release asset '${expected}' is missing.`); + } +} + +export function validateReleaseAssets(options: ValidateReleaseAssetsOptions): void { + const directory = NodePath.resolve(options.directory); + const fileNames = NodeFS.readdirSync(directory).filter( + (fileName) => !NodeFS.statSync(NodePath.join(directory, fileName)).isDirectory(), + ); + const files = new Set(fileNames); + + requireArchAsset(fileNames, options.version, "arm64", ".dmg"); + requireArchAsset(fileNames, options.version, "x64", ".dmg"); + requireArchAsset(fileNames, options.version, "arm64", ".zip"); + requireArchAsset(fileNames, options.version, "x64", ".zip"); + requireArchAsset(fileNames, options.version, "x64", ".AppImage"); + requireArchAsset(fileNames, options.version, "x64", ".exe"); + + const channel = options.version.includes("-nightly.") ? "nightly" : "latest"; + const manifests = [`${channel}-mac.yml`, `${channel}-linux.yml`, `${channel}.yml`]; + for (const manifestName of manifests) { + requireAsset(files, manifestName); + const manifestPath = NodePath.join(directory, manifestName); + const manifest = parseUpdateManifest( + NodeFS.readFileSync(manifestPath, "utf8"), + manifestPath, + manifestName, + ); + if (manifest.version !== options.version) { + throw new Error( + `Update manifest '${manifestName}' has version '${manifest.version}', expected '${options.version}'.`, + ); + } + + for (const entry of manifest.files) { + const assetName = NodePath.basename(new URL(entry.url, "https://updates.invalid/").pathname); + requireAsset(files, assetName); + const actualSize = NodeFS.statSync(NodePath.join(directory, assetName)).size; + if (entry.size !== actualSize) { + throw new Error( + `Update manifest '${manifestName}' reports ${entry.size} bytes for '${assetName}', but the asset is ${actualSize} bytes.`, + ); + } + } + } + + if (!fileNames.some((fileName) => fileName.endsWith(".blockmap"))) { + throw new Error("Release assets are missing updater blockmaps."); + } +} + +function readArgument(name: string): string { + const index = process.argv.indexOf(name); + const value = index >= 0 ? process.argv[index + 1] : undefined; + if (!value) { + throw new Error(`Missing required argument ${name}.`); + } + return value; +} + +if (process.argv[1]?.endsWith("validate-release-assets.ts")) { + validateReleaseAssets({ + directory: readArgument("--directory"), + version: readArgument("--version"), + }); +} From 2ef0cb47caf517da9062e9bf290b7244132d9a5a Mon Sep 17 00:00:00 2001 From: atryan Date: Fri, 31 Jul 2026 23:44:13 +0000 Subject: [PATCH 2/6] fix(ci): allow public release publisher identity --- docs/operations/public-repository-safety.md | 3 +++ scripts/lib/public-leak-scan.test.ts | 20 ++++++++++++++++++++ scripts/lib/public-leak-scan.ts | 8 +++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/docs/operations/public-repository-safety.md b/docs/operations/public-repository-safety.md index 87798b1b477..ec56ad88c4d 100644 --- a/docs/operations/public-repository-safety.md +++ b/docs/operations/public-repository-safety.md @@ -49,6 +49,9 @@ configure the scanner. Supply them privately in either of these ways: - Put one term per line in `.command-center-private-denylist`. This filename is ignored by Git. The scanner reports only the rule, file, and location. It does not print the matched value. +The exact `awtprod` identifier is excluded during parsing because it is the reviewed, deliberately +public publisher identity for Command Center. Public identity exceptions live in source control so +that adding or changing one requires code review; all other private terms continue to fail closed. Repository maintainers must configure the `COMMAND_CENTER_PUBLIC_DENYLIST` Actions secret before publishing or running an upstream sync. Trusted CI changes fail when that secret is absent. Fork pull requests receive the generic safety checks without exposing the private denylist; maintainers must diff --git a/scripts/lib/public-leak-scan.test.ts b/scripts/lib/public-leak-scan.test.ts index fec900c88c4..6961e0fee50 100644 --- a/scripts/lib/public-leak-scan.test.ts +++ b/scripts/lib/public-leak-scan.test.ts @@ -233,6 +233,26 @@ describe("public leak scan", () => { expect(findings[0]?.rule).toBe("private-denylist"); }); + it("excludes only the approved public release identity from a private denylist", () => { + const denylist = parsePrivateDenylist("AWTPROD, private label"); + + expect(denylist).toEqual(["private label"]); + expect( + scanPublicText({ + path: "package.json", + text: "@awtprod/command-center", + denylist, + }), + ).toEqual([]); + expect( + scanPublicText({ + path: "config.json", + text: "private label", + denylist, + })[0]?.rule, + ).toBe("private-denylist"); + }); + it("detects private denylist terms in normalized repository paths", () => { const privateLabel = "private label"; const findings = scanPublicPath("docs/private-label/guide.md", [privateLabel]); diff --git a/scripts/lib/public-leak-scan.ts b/scripts/lib/public-leak-scan.ts index 978682bbb88..e3218978d1e 100644 --- a/scripts/lib/public-leak-scan.ts +++ b/scripts/lib/public-leak-scan.ts @@ -157,6 +157,11 @@ const FORBIDDEN_FILE_PATTERNS: readonly RegExp[] = [ const RESERVED_EMAIL_SUFFIXES = ["@example.com", "@example.net", "@example.org", "@example.test"]; const PUBLIC_GIT_SSH_USERS = new Set(["git@bitbucket.org", "git@github.com", "git@gitlab.com"]); +// These exact identifiers are an intentional part of Command Center's public release identity. +// Keeping the exception here makes a private-to-public identity transition reviewable in Git while +// leaving every other operator-supplied denylist term fail-closed. +const PUBLIC_RELEASE_IDENTIFIERS = new Set(["awtprod"]); + export function parsePrivateDenylist(value: string | undefined): readonly string[] { if (!value) return []; return [ @@ -164,7 +169,8 @@ export function parsePrivateDenylist(value: string | undefined): readonly string value .split(/[\n,]/u) .map((term) => term.trim()) - .filter((term) => term.length >= 3), + .filter((term) => term.length >= 3) + .filter((term) => !PUBLIC_RELEASE_IDENTIFIERS.has(term.toLocaleLowerCase("en-US"))), ), ]; } From 27439884cbc8f665dc5264227811372c26385e56 Mon Sep 17 00:00:00 2001 From: atryan Date: Sat, 1 Aug 2026 01:37:07 +0000 Subject: [PATCH 3/6] fix(web): avoid Windows preview path collision --- apps/web/src/browser/WebPreviewFrame.tsx | 2 +- apps/web/src/browser/webPreviewFrame.test.ts | 2 +- ...reviewFrame.ts => webPreviewFrameState.ts} | 0 scripts/release-smoke.ts | 28 +++++++++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) rename apps/web/src/browser/{webPreviewFrame.ts => webPreviewFrameState.ts} (100%) diff --git a/apps/web/src/browser/WebPreviewFrame.tsx b/apps/web/src/browser/WebPreviewFrame.tsx index dc88aca3c2a..1e1c8a18a10 100644 --- a/apps/web/src/browser/WebPreviewFrame.tsx +++ b/apps/web/src/browser/WebPreviewFrame.tsx @@ -12,7 +12,7 @@ import { useCallback, useRef } from "react"; import { previewEnvironment } from "~/state/preview"; import { useAtomCommand } from "~/state/use-atom-command"; -import { resolveWebPreviewFrameState } from "./webPreviewFrame"; +import { resolveWebPreviewFrameState } from "./webPreviewFrameState"; /** * The preview surface for a plain browser, where there is no Electron diff --git a/apps/web/src/browser/webPreviewFrame.test.ts b/apps/web/src/browser/webPreviewFrame.test.ts index 014e1413342..021a1b99092 100644 --- a/apps/web/src/browser/webPreviewFrame.test.ts +++ b/apps/web/src/browser/webPreviewFrame.test.ts @@ -1,6 +1,6 @@ import { assert, describe, it } from "vite-plus/test"; -import { resolveWebPreviewFrameState } from "./webPreviewFrame"; +import { resolveWebPreviewFrameState } from "./webPreviewFrameState"; describe("resolveWebPreviewFrameState", () => { it("frames the URL a loading session is navigating to", () => { diff --git a/apps/web/src/browser/webPreviewFrame.ts b/apps/web/src/browser/webPreviewFrameState.ts similarity index 100% rename from apps/web/src/browser/webPreviewFrame.ts rename to apps/web/src/browser/webPreviewFrameState.ts diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts index adba266b485..bd2d4ed7647 100644 --- a/scripts/release-smoke.ts +++ b/scripts/release-smoke.ts @@ -186,9 +186,37 @@ function assertMissing(path: string, message: string): void { } } +function assertNoCaseInsensitiveTrackedPathCollisions(): void { + const trackedPaths = NodeChildProcess.execFileSync("git", ["ls-files", "-z"], { + cwd: repoRoot, + encoding: "utf8", + }) + .split("\0") + .filter(Boolean); + const canonicalPaths = new Map(); + const collisions: string[] = []; + + for (const trackedPath of trackedPaths) { + const canonicalPath = trackedPath.normalize("NFC").toLocaleLowerCase("en-US"); + const existingPath = canonicalPaths.get(canonicalPath); + if (existingPath !== undefined && existingPath !== trackedPath) { + collisions.push(`${existingPath} <> ${trackedPath}`); + continue; + } + canonicalPaths.set(canonicalPath, trackedPath); + } + + if (collisions.length > 0) { + throw new Error( + `Tracked paths collide on case-insensitive filesystems:\n${collisions.join("\n")}`, + ); + } +} + const tempRoot = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-release-smoke-")); try { + assertNoCaseInsensitiveTrackedPathCollisions(); copyWorkspaceManifestFixture(tempRoot); NodeChildProcess.execFileSync( From 581d86c4b141f91aa896d822cf516bbcd3e3db71 Mon Sep 17 00:00:00 2001 From: atryan Date: Sat, 1 Aug 2026 01:38:23 +0000 Subject: [PATCH 4/6] test(release): reject Windows module path collisions --- scripts/release-smoke.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/release-smoke.ts b/scripts/release-smoke.ts index bd2d4ed7647..3b170775a72 100644 --- a/scripts/release-smoke.ts +++ b/scripts/release-smoke.ts @@ -194,6 +194,7 @@ function assertNoCaseInsensitiveTrackedPathCollisions(): void { .split("\0") .filter(Boolean); const canonicalPaths = new Map(); + const canonicalModulePaths = new Map(); const collisions: string[] = []; for (const trackedPath of trackedPaths) { @@ -204,6 +205,19 @@ function assertNoCaseInsensitiveTrackedPathCollisions(): void { continue; } canonicalPaths.set(canonicalPath, trackedPath); + + if (/\.[cm]?[jt]sx?$/u.test(trackedPath)) { + const canonicalModulePath = trackedPath + .replace(/\.[cm]?[jt]sx?$/u, "") + .normalize("NFC") + .toLocaleLowerCase("en-US"); + const existingModulePath = canonicalModulePaths.get(canonicalModulePath); + if (existingModulePath !== undefined && existingModulePath !== trackedPath) { + collisions.push(`${existingModulePath} <> ${trackedPath} (module resolution)`); + continue; + } + canonicalModulePaths.set(canonicalModulePath, trackedPath); + } } if (collisions.length > 0) { From 650e9771e7c0fd33ac776d1f3e17e36487f0a442 Mon Sep 17 00:00:00 2001 From: atryan Date: Sat, 1 Aug 2026 01:48:24 +0000 Subject: [PATCH 5/6] fix(release): bundle command center core --- apps/server/package.json | 2 +- apps/server/src/bin.test.ts | 6 +++--- apps/server/src/bin.ts | 4 ++-- apps/server/vite.config.test.ts | 24 ++++++++++++++++++++++++ apps/server/vite.config.ts | 1 + pnpm-lock.yaml | 6 +++--- 6 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 apps/server/vite.config.test.ts diff --git a/apps/server/package.json b/apps/server/package.json index 0a13e8ac071..51423a5a5be 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -25,7 +25,6 @@ }, "dependencies": { "@anthropic-ai/claude-agent-sdk": "^0.3.170", - "@command-center/core": "workspace:*", "@effect/platform-bun": "catalog:", "@effect/platform-node": "catalog:", "@effect/platform-node-shared": "catalog:", @@ -38,6 +37,7 @@ "node-pty": "^1.1.0" }, "devDependencies": { + "@command-center/core": "workspace:*", "@effect/vitest": "catalog:", "@t3tools/contracts": "workspace:*", "@t3tools/shared": "workspace:*", diff --git a/apps/server/src/bin.test.ts b/apps/server/src/bin.test.ts index 512030c8c38..ae94e6c95f6 100644 --- a/apps/server/src/bin.test.ts +++ b/apps/server/src/bin.test.ts @@ -189,7 +189,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { if (error._tag !== "ShowHelp") { assert.fail(`Expected ShowHelp, got ${error._tag}`); } - assert.deepEqual(error.commandPath, ["t3", "connect"]); + assert.deepEqual(error.commandPath, ["command-center", "connect"]); assert.include(error.errors[0]?.message ?? "", "missing T3 Connect public configuration"); const output = (yield* TestConsole.errorLines).join("\n"); @@ -409,7 +409,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { if (error._tag !== "ShowHelp") { assert.fail(`Expected ShowHelp, got ${error._tag}`); } - assert.deepEqual(error.commandPath, ["t3", "auth", "pairing", "create"]); + assert.deepEqual(error.commandPath, ["command-center", "auth", "pairing", "create"]); const ttlError = error.errors[0] as CliError.CliError | undefined; if (!ttlError || ttlError._tag !== "InvalidValue") { assert.fail(`Expected InvalidValue, got ${String(ttlError?._tag)}`); @@ -520,7 +520,7 @@ it.layer(NodeServices.layer)("bin cli parsing", (it) => { if (error._tag !== "ShowHelp") { assert.fail(`Expected ShowHelp, got ${error._tag}`); } - assert.deepEqual(error.commandPath, ["t3", "project", "add"]); + assert.deepEqual(error.commandPath, ["command-center", "project", "add"]); const optionError = error.errors[0] as CliError.CliError | undefined; if (!optionError || optionError._tag !== "UnrecognizedOption") { assert.fail(`Expected UnrecognizedOption, got ${String(optionError?._tag)}`); diff --git a/apps/server/src/bin.ts b/apps/server/src/bin.ts index 2bc65a46f37..0b28dd63e55 100644 --- a/apps/server/src/bin.ts +++ b/apps/server/src/bin.ts @@ -31,7 +31,7 @@ const connectUnavailableCommand = Command.make("connect").pipe( Command.withHandler(() => Effect.fail( new CliError.ShowHelp({ - commandPath: ["t3", "connect"], + commandPath: ["command-center", "connect"], errors: [new ConnectPublicConfigMissingError({ cause: connectPublicConfigMissingMessage })], }), ), @@ -39,7 +39,7 @@ const connectUnavailableCommand = Command.make("connect").pipe( ); export const makeCli = ({ cloudEnabled = hasCloudPublicConfig } = {}) => - Command.make("t3", { ...sharedServerCommandFlags }).pipe( + Command.make("command-center", { ...sharedServerCommandFlags }).pipe( Command.withDescription("Run the Command Center server."), Command.withHandler((flags) => runServerCommand(flags)), Command.withSubcommands([ diff --git a/apps/server/vite.config.test.ts b/apps/server/vite.config.test.ts new file mode 100644 index 00000000000..c94a30d2627 --- /dev/null +++ b/apps/server/vite.config.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from "vite-plus/test"; + +import packageJson from "./package.json" with { type: "json" }; +import { shouldBundleCliDependency } from "./vite.config.ts"; + +describe("server bundle dependencies", () => { + it("bundles private Command Center workspace packages", () => { + expect(shouldBundleCliDependency("@command-center/core")).toBe(true); + expect(shouldBundleCliDependency("@command-center/core/domain")).toBe(true); + }); + + it("keeps registry runtime dependencies external", () => { + expect(shouldBundleCliDependency("effect")).toBe(false); + expect(shouldBundleCliDependency("@effect/platform-node")).toBe(false); + }); + + it("does not expose private workspace packages as runtime dependencies", () => { + const workspaceRuntimeDependencies = Object.entries(packageJson.dependencies).filter( + ([, dependencySpec]) => dependencySpec.startsWith("workspace:"), + ); + + expect(workspaceRuntimeDependencies).toEqual([]); + }); +}); diff --git a/apps/server/vite.config.ts b/apps/server/vite.config.ts index 521654f3279..4089495f6c6 100644 --- a/apps/server/vite.config.ts +++ b/apps/server/vite.config.ts @@ -6,6 +6,7 @@ import { loadRepoEnv } from "../../scripts/lib/public-config.ts"; import packageJson from "./package.json" with { type: "json" }; const bundledPackagePrefixes = [ + "@command-center/", "@pierre/diffs", "@t3tools/", "effect-acp", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e6d943a97a..6f3b80f12d0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -441,9 +441,6 @@ importers: '@anthropic-ai/claude-agent-sdk': specifier: ^0.3.170 version: 0.3.170(@anthropic-ai/sdk@0.93.0(zod@4.4.3))(@modelcontextprotocol/sdk@1.29.0(zod@4.4.3))(zod@4.4.3) - '@command-center/core': - specifier: workspace:* - version: link:../../packages/command-center-core '@effect/platform-bun': specifier: 4.0.0-beta.78 version: 4.0.0-beta.78(bufferutil@4.1.0)(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5))(utf-8-validate@6.0.6) @@ -475,6 +472,9 @@ importers: specifier: ^1.1.0 version: 1.1.0 devDependencies: + '@command-center/core': + specifier: workspace:* + version: link:../../packages/command-center-core '@effect/vitest': specifier: 4.0.0-beta.78 version: 4.0.0-beta.78(patch_hash=42b87cc47e70d74e62496e7a8261b3fd298ecad4464d209348ab04b96f853a5f)(effect@4.0.0-beta.78(patch_hash=c502bc684210b707dfceb87d8fe6ad6843395af6e19cfc02cd65854898bde2c5)) From 8980f7c34b460d5bc4c2e1b6fda64285f67cdc66 Mon Sep 17 00:00:00 2001 From: atryan Date: Sun, 2 Aug 2026 17:05:18 -0400 Subject: [PATCH 6/6] fix(server): provide orchestration engine to run lifecycle --- apps/server/src/server.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 6709ece33bb..4ab5422bb80 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -360,6 +360,7 @@ const AutomationRecoveryCoordinatorLayerLive = AutomationRecoveryCoordinator.lay const RunLifecycleLayerLive = RunLifecycle.layer.pipe( Layer.provide(ProviderLayerLive), Layer.provide(OrchestrationProjectionSnapshotQueryLive), + Layer.provide(OrchestrationLayerLive), Layer.provide(PersistenceLayerLive), );