Compare commits

..

43 Commits

Author SHA1 Message Date
GitHub Action
d74c969b5c chore: bump version to 0.1.19-pthis.3 2026-02-25 08:52:48 +00:00
anastasiya1155
41c3715f7b fix: prevent resource.lib LIBPATH collision in Windows Tauri build
The previous fix only neutralized codex-windows-sandbox's resource.lib,
but the linker was also finding our OWN resource.lib a second time via
LIBPATH search (triggered by codex-windows-sandbox's transitive
cargo:rustc-link-lib=dylib=resource directive).

New approach:
1. Copy resource.lib -> tauri_resource.lib (keeps real VERSION + icon)
2. Overwrite resource.lib with empty COFF archive (both the original
   direct link-arg and LIBPATH search now find an empty file)
3. Emit cargo:rustc-link-arg-bins for tauri_resource.lib (single source
   of VERSION resources)
4. Also neutralize codex-windows-sandbox's resource.lib

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 09:44:10 +01:00
GitHub Action
4f470b36c0 chore: bump version to 0.1.19-pthis.2 2026-02-25 06:55:39 +00:00
anastasiya1155
2ec90924cb fix: neutralize codex-windows-sandbox VERSION resource for Windows build
The /FORCE:MULTIPLE approach didn't work because CVTRES runs before the
linker processes that flag. The winres crate (used by codex-windows-sandbox)
emits cargo:rustc-link-lib=dylib=resource which propagates transitively,
so simply deleting resource.lib causes a linker error.

Instead, overwrite codex-windows-sandbox's resource.lib with a minimal
valid empty COFF archive (!<arch>\n). The linker finds the file (satisfying
the -lresource directive) but it contributes no VERSION resource, avoiding
the CVTRES CVT1100 duplicate resource conflict with tauri-winres.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 07:54:10 +01:00
GitHub Action
d772269e69 chore: bump version to 0.1.19-pthis.1 2026-02-25 05:53:13 +00:00
anastasiya1155
041eb71ece fix: use /FORCE:MULTIPLE linker flag for Windows Tauri build
The previous approach of deleting codex-windows-sandbox's resource files
was ineffective because the VERSION resource is baked into the .rlib at
compile time. Instead, pass /FORCE:MULTIPLE to the MSVC linker to
tolerate the duplicate VERSION resource from codex-windows-sandbox and
tauri-winres.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 06:14:13 +01:00
GitHub Action
648b841dcf chore: bump version to 0.1.19-pthis.0 2026-02-25 03:39:11 +00:00
anastasiya1155
cb5dbad59b fix: restore correct main branch versions of files misresolved during rebase
During the rebase, several web files had conflicts where the branch's
old-path versions were taken instead of main's versions. Restore the
correct files from main and remove a stale ContextBar.tsx that was
created in the legacy frontend/ path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 04:36:25 +01:00
anastasiya1155
083d73e9bb fix: update pre-release workflow for packages/web -> packages/local-web rename
The frontend was moved from packages/web to packages/local-web during
the monorepo restructuring. Update all references in the pre-release
workflow: version bumping, lint, type check, build, Sentry sourcemaps,
and artifact upload/download paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:53:54 +01:00
anastasiya1155
023951ff8c chore: add frontend/dist to gitignore
The frontend/ directory exists on this branch from before the move to
packages/. Its dist/ build output should not be tracked.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:47:51 +01:00
anastasiya1155
30697bc701 fix: resolve duplicate VERSION resource on Windows Tauri build
codex-windows-sandbox's build.rs uses winres to embed a VERSION
resource. tauri-build also embeds one via tauri-winres. Both define
`1 VERSIONINFO`, causing CVTRES to fail with CVT1100 during linking.

Fix by removing the codex-windows-sandbox resource files from the
build directory in the tauri-app build.rs, so only the tauri-winres
VERSION resource is linked into the final binary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:45:20 +01:00
anastasiya1155
b5671044b5 fix: exclude tauri-app from CI clippy and test runs
The tauri-app crate requires WebKit2GTK system libraries (gobject-sys,
glib-sys) that aren't available on the standard CI runner. Exclude it
from workspace clippy and nextest runs — it gets built and checked in
the dedicated build-tauri CI job instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:45:20 +01:00
anastasiya1155
4b0c3b6059 feat: add Windows x86_64 Tauri build to CI pipeline
- Add x86_64-pc-windows-msvc on windows-latest to the build-tauri matrix
- Set default shell to bash for cross-platform script compatibility
- Collect NSIS exe and MSI installers alongside DMG/AppImage/deb
- No code signing yet (SmartScreen warning expected until certificate added)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:45:20 +01:00
anastasiya1155
62760bc825 fix: pin aws-lc-sys to 0.37.0 to fix Windows cross-compilation
The 0.37.1 version was pulled in during Tauri dependency resolution,
breaking the Windows backend build (aws-lc-sys uses wrong clang target
on 0.37.1). Matches the pin from c230c56a4 on main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:45:20 +01:00
anastasiya1155
c64b0d3a84 feat: add Linux x86_64 Tauri build to CI pipeline
- Add x86_64-unknown-linux-gnu to the build-tauri matrix
- Install Linux system deps (WebKit2GTK, appindicator, etc.)
- Replace macOS-only sed -i with cross-platform Node.js patching
- Gate Apple signing/notarization steps on runner.os == 'macOS'
- Collect AppImage and deb installers alongside DMGs
- Include all platform installers in GitHub Release

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:45:20 +01:00
anastasiya1155
3a9fae1ea4 fix: disable overscroll bounce in desktop app
Add overscroll-behavior: none to html element to prevent the macOS
elastic bounce effect when scrolling past the page edges in the Tauri
webview.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:45:20 +01:00
anastasiya1155
b32c75bf87 feat: enable Cmd+/Cmd- zoom hotkeys in Tauri webview
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:44:57 +01:00
anastasiya1155
0f13710a93 chore: rename app display name to "Vibe Kanban"
Update productName, window title, bundle description, and capability
description to use the proper "Vibe Kanban" branding. Internal
identifiers (crate name, bundle identifier) remain kebab-case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:44:57 +01:00
anastasiya1155
8b366bc723 fix: keep OAuth tab open for desktop app, auto-close for browser
When running in Tauri, the OAuth flow opens in the system browser.
Previously the callback page always tried window.close(), which closed
the tab before the user could see the success message.

Now the frontend tags the callback URL with ?source=desktop when
running inside Tauri. The server checks this param and skips the
auto-close script, leaving the tab open with a "return to the app"
message. Browser popup flows continue to auto-close as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:44:57 +01:00
anastasiya1155
15fe7828d7 fix: don't auto-close OAuth tab when opened from desktop app
Check window.opener to determine if the page was opened as a popup
(normal browser flow) vs a regular tab (Tauri desktop app). Only
auto-close in the popup case; otherwise show a message telling the
user to close the tab and return to the app.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:44:28 +01:00
anastasiya1155
11dbded9be revert: remove Tauri window focus hack after OAuth login
The window.focus() / IPC set_focus approach doesn't reliably bring the
Tauri window to the front. Instead, rely on the server's existing
close_window_response which already shows "you may close it manually"
when window.close() fails (as it does in a system browser tab opened
by Tauri).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:44:28 +01:00
anastasiya1155
e0199a3071 style: apply Prettier formatting
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:44:22 +01:00
anastasiya1155
29b8f81391 feat: bring Tauri window to front after OAuth login completes
After authenticating in the system browser, focus the app window so the
user doesn't have to find it manually. Uses window.focus() as a
fallback and the Tauri-specific plugin:window|set_focus IPC call with
the core:window:allow-set-focus capability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:44:21 +01:00
anastasiya1155
cb83683209 fix: load .env file in tauri:dev script for VK_SHARED_API_BASE
Source .env before setting env vars so VK_SHARED_API_BASE is available
for VITE_VK_SHARED_API_BASE and other vars that depend on it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:43:34 +01:00
anastasiya1155
6b67458a49 fix: skip updater plugin registration in dev builds
The updater plugin validates config endpoints at init time, causing a
panic on the __TAURI_UPDATE_ENDPOINT__ placeholder. Conditionally
register the plugin only in release builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:43:34 +01:00
anastasiya1155
09ab3b7652 fix: fix Tauri dev mode to use Vite dev server with HMR
- Add beforeDevCommand to start frontend + backend concurrently
- Skip embedded Axum server in dev builds (cfg!(debug_assertions))
- Skip updater check in dev to avoid placeholder endpoint errors
- Update tauri:dev script with proper env vars (RUST_LOG, etc.)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:43:34 +01:00
anastasiya1155
1bdfbac37e feat: open OAuth popups in system browser for Tauri app
Tauri's webview blocks window.open() by default, breaking OAuth login.
Create the main window programmatically with on_new_window handler that
intercepts popup requests and opens them in the system browser via
tauri-plugin-opener. The existing polling mechanism detects auth
completion without needing a popup reference.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:43:34 +01:00
anastasiya1155
42db088534 fix: use individual secrets for Tauri notarization instead of parsing JSON
The APP_STORE_API_KEY JSON didn't have the expected field names,
causing TypeError in CI. Switch to APPLE_API_KEY_ID, APPLE_API_ISSUER,
and APPLE_API_PRIVATE_KEY secrets which already exist in the repo.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:43:34 +01:00
anastasiya1155
706669754c fix: collect DMG artifacts and improve Tauri code signing setup
- Add DMG collection to build-tauri CI job so DMGs appear in GitHub Release
- Add explicit APPLE_API_KEY_PATH env var for notarization
- Add APPLE_SIGNING_IDENTITY env var for code signing identity
- Format frontend code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:43:34 +01:00
anastasiya1155
e5b4cb0aa2 fix: add Apple code signing and notarization to Tauri CI build
Pass APPLE_CERTIFICATE and APPLE_CERTIFICATE_PASSWORD env vars to
cargo tauri build so Tauri signs the .app bundle via codesign. Extract
API key fields from APP_STORE_API_KEY JSON secret and write the .p8
file for Tauri's notarization via xcrun notarytool.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:34 +01:00
anastasiya1155
338a3b3704 chore: generate Tauri icons from vibe-kanban favicon with black background
Replace placeholder purple icons with proper icons generated from
frontend/public/favicon-vk-dark.svg using cargo tauri icon.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:34 +01:00
anastasiya1155
434d420465 fix: skip beforeBuildCommand in Tauri CI build to avoid OOM
The frontend is already pre-built and downloaded as an artifact in CI,
so the beforeBuildCommand (pnpm run build) is redundant and causes a
JavaScript heap out of memory error on the macOS runner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:34 +01:00
anastasiya1155
07041c1c3e fix: update startup.rs for rebased API changes and regenerate Cargo.lock
Remove stale warm_most_active call (API changed to warm_repos on main),
add database copy migration and executor preload cache from main branch,
and regenerate Cargo.lock after rebase.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:34 +01:00
anastasiya1155
5e084fed31 feat: add Tauri auto-update CI pipeline with R2-hosted update manifest
Add build-tauri and upload-tauri-update jobs to pre-release workflow that
build the Tauri app for macOS (aarch64 + x86_64), sign updater artifacts,
generate a latest.json manifest, and upload everything to R2. The updater
endpoint in tauri.conf.json uses a __TAURI_UPDATE_ENDPOINT__ placeholder
that CI substitutes with the real R2 public URL before building.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:28 +01:00
anastasiya1155
d238ad8475 feat: add Tauri auto-updater plugin, remove Electron wrapper
Remove the temporary Electron wrapper (electron/ directory and scripts).
Add tauri-plugin-updater v2 with background update checking on startup,
event emission to frontend (update-available, update-installed), and
automatic download+install. Updater pubkey and endpoint are placeholders
that need to be configured before release.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:28 +01:00
anastasiya1155
14ce8e5e5e fix: load .env in local-deployment build.rs for VK_SHARED_API_BASE
The local-deployment crate reads VK_SHARED_API_BASE via option_env!()
at compile time, but its build script had no dotenv loading. Since .env
files use KEY=VALUE without export, the var was never visible to Cargo
child processes. Now both server and local-deployment build scripts
load .env from the workspace root and pass the value via cargo:rustc-env.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:28 +01:00
anastasiya1155
6fda2e84c4 fix: add build.rs to local-deployment for VK_SHARED_API_BASE recompilation
The crate uses option_env!("VK_SHARED_API_BASE") at compile time but
had no rerun-if-env-changed directive, so Cargo never recompiled it
when the env var was set or changed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:28 +01:00
anastasiya1155
27d22ec7f1 fix: load .env from workspace root in server build.rs
dotenv::dotenv() searched CWD which is the crate directory, not the
workspace root. Use CARGO_MANIFEST_DIR to resolve the workspace root
and load .env via dotenv::from_path() explicitly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:28 +01:00
anastasiya1155
729d8f75d4 fix: add rerun-if-env-changed to server build.rs for compile-time env vars
Without these directives, Cargo cached the build script output and
never re-embedded env vars like VK_SHARED_API_BASE when they changed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:28 +01:00
anastasiya1155
e7c72010ca fix: skip macOS code signing for Electron dev builds
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:28 +01:00
anastasiya1155
9564229a8b feat: add Electron wrapper for comparison testing with Tauri
Spawns the existing Rust server binary as a child process, parses
stdout for the auto-assigned port, and opens a BrowserWindow. Temporary
setup for evaluating Electron vs Tauri desktop distribution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:27 +01:00
anastasiya1155
477a5c6399 fix: use object-form beforeBuildCommand with explicit cwd for Tauri build
The plain string form ran from the wrong directory, causing the frontend
build step to fail with "No such file or directory".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:27 +01:00
anastasiya1155
85be746bcf feat: wrap app in Tauri v2 for native desktop distribution
Adds a new `crates/tauri-app` workspace crate that runs the existing
Axum server in-process and opens a native webview window. Extracts
shared server initialization logic into `crates/server/src/startup.rs`
so both the standalone binary and Tauri app can reuse it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:42:27 +01:00
1264 changed files with 33821 additions and 84738 deletions

View File

@@ -1,15 +1,12 @@
# Node modules
node_modules/
**/node_modules
.pnpm-store/
.pnpm/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Build artifacts
**/target/
target/
dist/
build/
*.tgz
@@ -36,12 +33,6 @@ Thumbs.db
.git/
.gitignore
# Repo content not needed for Docker builds
.github/
docs/
dev_assets/
dev_assets_seed/
# Docker
Dockerfile*
.dockerignore
@@ -68,4 +59,4 @@ coverage/
# Temporary folders
tmp/
temp/
temp/

View File

@@ -1,163 +0,0 @@
name: container
# Build the self-hosted images from THIS repo and publish them to the Gitea
# registry:
#
# git.lair.cafe/lair/vibe-kanban-remote crates/remote/Dockerfile :8081
# git.lair.cafe/lair/vibe-kanban-relay crates/relay-tunnel/Dockerfile :8082
#
# This build used to live in lair/containers, which is for *third-party* images
# built from someone else's source. We own this fork now, so the build belongs
# next to the code it builds — a source change and its image are one commit.
#
# Both crates ship their own Dockerfile (context = repo root), so there is no
# vendored Containerfile here. Nothing in this workflow reaches out to GitHub:
# upstream is sunset and may disappear, and a build that asks github.com what to
# build would disappear with it.
on:
push:
branches: [main]
paths:
- "crates/**"
- "packages/**"
- "shared/**"
- "assets/**"
- "patches/**"
- "Cargo.toml"
- "Cargo.lock"
- "package.json"
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "rust-toolchain.toml"
- ".gitea/workflows/container.yml"
workflow_dispatch:
# Never let two builds race: they would fight over the :latest tags and the
# loser's digest would win at random.
concurrency:
group: container
cancel-in-progress: false
jobs:
remote:
runs-on:
- metal
- podman
# The Rust workspace builds in release mode; from a cold cache the default
# job timeout is not enough headroom.
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: derive image tags
id: meta
run: |
set -euo pipefail
version=$(jq -r .version package.json)
short=$(git rev-parse --short HEAD)
# Immutable tag first. :latest is a convenience for humans, never a
# deployment target -- a floating tag decides which build you are
# running based on whenever something last pulled.
echo "immutable=${version}-g${short}" >> "$GITHUB_OUTPUT"
- name: login to registry
run: podman login -u ${{ gitea.actor }} -p ${{ secrets.REGISTRY_TOKEN }} git.lair.cafe
- name: build
env:
IMMUTABLE: ${{ steps.meta.outputs.immutable }}
run: |
set -euo pipefail
IMAGE=git.lair.cafe/lair/vibe-kanban-remote
# FEATURES is deliberately unset: the Dockerfile strips the private
# billing crate only when it is empty (the self-host path), and we have
# no access to BloopAI/vibe-kanban-private.
#
# VITE_RELAY_API_BASE_URL is baked into the SPA at build time and is
# ORIGIN-RELATIVE on purpose. It is not a switch that disables the
# relay -- leaving it empty makes Bootstrap.tsx fall back to
# window.location.origin, which aims relay calls at the remote API,
# which does not serve them. "/relay-api" instead means the SPA calls
# whichever host served it, so one build works unchanged behind both
# kanban.internal and a future public name, with no CORS and no second
# certificate. nginx strips the prefix and forwards to the relay.
podman build --pull=newer \
-f crates/remote/Dockerfile \
--build-arg VITE_RELAY_API_BASE_URL=/relay-api \
-t "${IMAGE}:${IMMUTABLE}" \
-t "${IMAGE}:latest" \
.
- name: verify the sunset really is gone
env:
IMMUTABLE: ${{ steps.meta.outputs.immutable }}
run: |
set -euo pipefail
IMAGE=git.lair.cafe/lair/vibe-kanban-remote
# This fork exists because upstream replaced the board with an
# export-only page. If a merge or a bad rebase ever reinstates it, fail
# here rather than publish an image that greets us with a dead product.
if podman run --rm --entrypoint sh "${IMAGE}:${IMMUTABLE}" -c \
'grep -rlq "Project functionality has been retired" /srv/static' 2>/dev/null; then
echo "ERROR: the sunset page is present in the built bundle."
echo "The revert of #3387 has been lost -- refusing to publish."
exit 1
fi
echo "ok: no sunset page in the bundle"
- name: push
env:
IMMUTABLE: ${{ steps.meta.outputs.immutable }}
run: |
set -euo pipefail
IMAGE=git.lair.cafe/lair/vibe-kanban-remote
podman push "${IMAGE}:${IMMUTABLE}"
podman push "${IMAGE}:latest"
echo "published ${IMAGE}:${IMMUTABLE} (and :latest)"
echo
echo "pin the quadlet to the immutable tag:"
echo " Image=${IMAGE}:${IMMUTABLE}"
relay:
runs-on:
- metal
- podman
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: derive image tags
id: meta
run: |
set -euo pipefail
version=$(jq -r .version package.json)
short=$(git rev-parse --short HEAD)
echo "immutable=${version}-g${short}" >> "$GITHUB_OUTPUT"
- name: login to registry
run: podman login -u ${{ gitea.actor }} -p ${{ secrets.REGISTRY_TOKEN }} git.lair.cafe
- name: build
env:
IMMUTABLE: ${{ steps.meta.outputs.immutable }}
run: |
set -euo pipefail
IMAGE=git.lair.cafe/lair/vibe-kanban-relay
# No build args: the relay takes all of its configuration from the
# environment at runtime (DATABASE_URL, RELAY_LISTEN_ADDR and the JWT
# secret it shares with the remote server). It has no notion of its own
# public address, which is what makes mounting it under a path safe.
podman build --pull=newer \
-f crates/relay-tunnel/Dockerfile \
-t "${IMAGE}:${IMMUTABLE}" \
-t "${IMAGE}:latest" \
.
- name: push
env:
IMMUTABLE: ${{ steps.meta.outputs.immutable }}
run: |
set -euo pipefail
IMAGE=git.lair.cafe/lair/vibe-kanban-relay
podman push "${IMAGE}:${IMMUTABLE}"
podman push "${IMAGE}:latest"
echo "published ${IMAGE}:${IMMUTABLE} (and :latest)"

View File

@@ -1,198 +0,0 @@
name: host-release
# Build the LOCAL vibe-kanban server and publish it as a signed RPM to
# rpm.lair.cafe, so a host installs it with dnf instead of `npx vibe-kanban`.
#
# npx is not an option here: npx-cli resolves its download from R2_BASE_URL, a
# placeholder substituted at npm-publish time to BloopAI's bucket, with no
# runtime override (npx-cli/src/download.ts). That serves upstream's binary --
# which still carries the product sunset -- from infrastructure that disappears
# when they do.
#
# Convention mirrors lair/claude-desktop-package: package and publish on the
# `rpm` runner, sign with RPM_SIGNING_KEY, rsync to oolon, createrepo under
# flock. The compile happens first on a podman runner rather than on the rpm
# runner, so this never depends on a host toolchain matching the pinned
# nightly in rust-toolchain.toml.
on:
push:
branches: [main]
paths:
- "crates/**"
- "packages/local-web/**"
- "packages/web-core/**"
- "packages/ui/**"
- "shared/**"
- "Cargo.toml"
- "Cargo.lock"
- "rust-toolchain.toml"
- "rpm/**"
- ".gitea/workflows/host-release.yml"
workflow_dispatch:
concurrency:
group: host-release
cancel-in-progress: false
jobs:
build:
runs-on:
- metal
- podman
timeout-minutes: 120
steps:
- uses: actions/checkout@v4
- name: derive version
id: meta
run: |
set -euo pipefail
echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT"
echo "commit=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
- name: build the host binary
run: |
set -euo pipefail
# --target export writes the binary straight out of the scratch stage,
# so nothing has to be extracted from an image afterwards.
mkdir -p out
podman build --pull=newer \
-f rpm/Containerfile \
--target export \
--output type=local,dest=out \
.
test -s out/vibe-kanban
chmod 0755 out/vibe-kanban
file out/vibe-kanban || true
- name: sanity-check the binary
run: |
set -euo pipefail
# The whole point of building our own: upstream's carries the sunset.
if grep -qa "Project functionality has been retired" out/vibe-kanban; then
echo "ERROR: the sunset page is embedded in the host binary."
echo "The revert of #3387 has been lost -- refusing to publish."
exit 1
fi
if grep -qa "Cloud is shutting down" out/vibe-kanban; then
echo "ERROR: the cloud-shutdown banner is embedded in the host binary."
exit 1
fi
# And it must point at us, not at api.vibekanban.com.
grep -qa "kanban.internal" out/vibe-kanban \
|| { echo "ERROR: baked API base missing from the binary"; exit 1; }
echo "ok: no sunset UI, lair endpoints baked in"
- uses: actions/upload-artifact@v3
with:
name: host-binary
path: out/vibe-kanban
retention-days: 7
outputs:
version: ${{ steps.meta.outputs.version }}
commit: ${{ steps.meta.outputs.commit }}
package:
needs: build
runs-on: rpm
strategy:
fail-fast: false
matrix:
fedora_version: ["43", "44"]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
with:
name: host-binary
path: bin/
- name: rpmbuild
run: |
set -euo pipefail
find bin/ -name vibe-kanban -exec mv --target-directory=bin/ {} + 2>/dev/null || true
mkdir -p ~/rpmbuild/SOURCES
install -m 0755 bin/vibe-kanban ~/rpmbuild/SOURCES/vibe-kanban
rpmbuild -bb rpm/vibe-kanban.spec \
--define "vk_version ${{ needs.build.outputs.version }}" \
--define "vk_commit ${{ needs.build.outputs.commit }}" \
--define "dist .fc${{ matrix.fedora_version }}"
ls -l ~/rpmbuild/RPMS/x86_64/
- uses: actions/upload-artifact@v3
with:
name: rpm-fc${{ matrix.fedora_version }}
path: ~/rpmbuild/RPMS/x86_64/*.rpm
retention-days: 7
publish:
needs: package
runs-on: rpm
env:
RPM_REPO_HOST: oolon.kosherinata.internal
strategy:
fail-fast: false
matrix:
fedora_version: ["43", "44"]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v3
with:
name: rpm-fc${{ matrix.fedora_version }}
path: rpms/
- name: Flatten RPM artifacts
run: |
find rpms/ -name '*.rpm' -exec mv --target-directory=rpms/ {} +
find rpms/ -mindepth 1 -type d -empty -delete
- name: Import signing key
run: |
echo "${{ secrets.RPM_SIGNING_KEY }}" | gpg --batch --import
fpr=$(gpg --batch --with-colons --list-keys "${{ secrets.RPM_SIGNING_KEY_ID }}" | awk -F: '/^fpr:/ { print $10; exit }')
echo "${fpr}:6:" | gpg --batch --import-ownertrust
sed "s/@GPG_NAME@/${{ secrets.RPM_SIGNING_KEY_ID }}/" rpm/rpmmacros > ~/.rpmmacros
- name: Sign RPMs
run: |
for rpm in rpms/*.rpm; do
echo "signing ${rpm}..."
rpm --addsign "${rpm}"
done
- name: Set up SSH
run: |
install --directory --mode 700 ~/.ssh
echo "${RSYNC_SSH_KEY}" | install --mode 600 /dev/stdin ~/.ssh/id_ed25519
env:
RSYNC_SSH_KEY: ${{ secrets.RSYNC_SSH_KEY }}
- name: Test SSH connectivity
run: |
ssh -o StrictHostKeyChecking=accept-new "gitea_ci@${RPM_REPO_HOST}" exit
- name: Sync RPMs to repo
run: |
rsync \
--archive \
--verbose \
--chmod D755,F644 \
rpms/*.rpm \
"gitea_ci@${RPM_REPO_HOST}:/var/www/rpm/fedora/${{ matrix.fedora_version }}/x86_64/"
- name: Update repo metadata
run: |
# flock guards createrepo against concurrent publishes into the
# shared repo tree (other package repos publish here too).
ssh "gitea_ci@${RPM_REPO_HOST}" \
"flock /var/www/rpm/.publish.lock -c 'cd /var/www/rpm/fedora/${{ matrix.fedora_version }}/x86_64 && createrepo_c --update .'"
- name: Generate packages.json
run: |
scp rpm/generate-packages-json.py "gitea_ci@${RPM_REPO_HOST}:/tmp/"
ssh "gitea_ci@${RPM_REPO_HOST}" \
"flock /var/www/rpm/.publish.lock -c 'python3 /tmp/generate-packages-json.py \
--repodata-dir /var/www/rpm/fedora/${{ matrix.fedora_version }}/x86_64/repodata \
--output /var/www/rpm/fedora/${{ matrix.fedora_version }}/x86_64/packages.json \
--base-url https://rpm.lair.cafe/fedora/${{ matrix.fedora_version }}/x86_64'"

View File

@@ -33,19 +33,19 @@ runs:
- name: Install Rust toolchain
if: ${{ inputs.components == '' }}
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ inputs.toolchain }}
- name: Install Rust toolchain with components
if: ${{ inputs.components != '' }}
uses: dtolnay/rust-toolchain@631a55b12751854ce901bb631d5902ceb48146f7 # stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ inputs.toolchain }}
components: ${{ inputs.components }}
- name: Cache Rust dependencies
uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
uses: Swatinem/rust-cache@v2
env:
RUST_CACHE_DEBUG: true
with:

View File

@@ -1,43 +0,0 @@
name: 'Setup Jsign'
description: 'Downloads and caches Jsign for cross-platform Windows code signing'
inputs:
version:
description: 'Jsign version'
required: false
default: '7.4'
sha256:
description: 'Expected SHA256 of the jar'
required: false
default: '2abf2ade9ea322acc2d60c24794eadc465ff9380938fca4c932d09e0b25f1c28'
outputs:
jar-path:
description: 'Path to the Jsign jar'
value: ${{ steps.path.outputs.jar }}
runs:
using: 'composite'
steps:
- name: Set jar path
id: path
shell: bash
run: echo "jar=${{ runner.temp }}/jsign-${{ inputs.version }}.jar" >> $GITHUB_OUTPUT
- name: Cache Jsign
id: cache
uses: actions/cache@v5
with:
path: ${{ steps.path.outputs.jar }}
key: jsign-${{ inputs.version }}-${{ inputs.sha256 }}
- name: Download Jsign
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: |
curl -sL "https://github.com/ebourg/jsign/releases/download/${{ inputs.version }}/jsign-${{ inputs.version }}.jar" \
-o "${{ steps.path.outputs.jar }}"
- name: Verify checksum
shell: bash
run: echo "${{ inputs.sha256 }} ${{ steps.path.outputs.jar }}" | sha256sum -c -

View File

@@ -11,7 +11,7 @@ runs:
registry-url: 'https://registry.npmjs.org'
- name: Setup pnpm
uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}

952
.github/workflows/pre-release.yml vendored Normal file
View File

@@ -0,0 +1,952 @@
name: Create GitHub Pre-Release
on:
workflow_dispatch:
inputs:
version_type:
description: "Version bump type"
required: true
default: "patch"
type: choice
options:
- patch
- minor
- major
- prerelease
concurrency:
group: release-${{ github.ref_name }} # allow concurrent prerelease from different branches
cancel-in-progress: true
permissions:
contents: write
packages: write
pull-requests: write
env:
NODE_VERSION: 22
PNPM_VERSION: 10.13.1
RUST_TOOLCHAIN: nightly-2025-12-04
CARGO_XWIN_VERSION: 0.20.2
jobs:
bump-version:
runs-on: ubuntu-latest-m
outputs:
new_tag: ${{ steps.version.outputs.new_tag }}
new_version: ${{ steps.version.outputs.new_version }}
branch_suffix: ${{ steps.branch.outputs.suffix }}
steps:
- name: Cache cargo-edit
uses: actions/cache@v5
id: cache-cargo-edit
with:
path: ~/.cargo/bin/cargo-set-version
key: cargo-edit-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}
- name: Install cargo-edit
if: steps.cache-cargo-edit.outputs.cache-hit != 'true'
run: cargo install cargo-edit
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: setup node
uses: ./.github/actions/setup-node
- name: Setup SSH Agent for private dependencies
id: ssh-setup
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.VK_PRIVATE_DEPLOY_KEY }}
- name: Generate branch suffix
id: branch
run: |
branch_name="${{ github.ref_name }}"
# Get last 6 characters of branch name, remove all special chars (including dashes)
suffix=$(echo "$branch_name" | tail -c 7 | sed 's/[^a-zA-Z0-9]//g' | tr '[:upper:]' '[:lower:]')
echo "Branch: $branch_name"
echo "Suffix: $suffix"
echo "suffix=$suffix" >> $GITHUB_OUTPUT
- name: Determine and update versions
id: version
run: |
# Get the latest version from npm registry
latest_npm_version=$(npm view vibe-kanban version 2>/dev/null || echo "0.0.0")
echo "Latest npm version: $latest_npm_version"
# Get current repo version
current_repo_version=$(node -p "require('./package.json').version")
echo "Current repo version: $current_repo_version"
# Use the higher of the two versions as the base (prevents downgrade errors with cargo set-version)
base_version=$(node -e "
const npm = '$latest_npm_version'.split('.').map(Number);
const repo = '$current_repo_version'.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((npm[i] || 0) > (repo[i] || 0)) { console.log('$latest_npm_version'); process.exit(); }
if ((npm[i] || 0) < (repo[i] || 0)) { console.log('$current_repo_version'); process.exit(); }
}
console.log('$current_repo_version');
")
echo "Base version for bump: $base_version"
timestamp=$(date +%Y%m%d%H%M%S)
# Update root package.json based on base version
if [[ "${{ github.event.inputs.version_type }}" == "prerelease" ]]; then
# For prerelease, use current package.json version and add branch suffix
npm version prerelease --preid="${{ steps.branch.outputs.suffix }}" --no-git-tag-version
new_version=$(node -p "require('./package.json').version")
new_tag="v${new_version}.${timestamp}"
else
# For regular releases, use base version and bump it
npm version $base_version --no-git-tag-version --allow-same-version
npm version ${{ github.event.inputs.version_type }} --no-git-tag-version
new_version=$(node -p "require('./package.json').version")
new_tag="v${new_version}-${timestamp}"
fi
# Update npx-cli package.json to match
cd npx-cli
npm version $new_version --no-git-tag-version --allow-same-version
cd ..
# Update web app package.json to match
cd packages/local-web
npm version $new_version --no-git-tag-version --allow-same-version
cd ..
cargo set-version --workspace "$new_version"
echo "New version: $new_version"
echo "new_version=$new_version" >> $GITHUB_OUTPUT
echo "new_tag=$new_tag" >> $GITHUB_OUTPUT
- name: Update remote crate lockfile
if: ${{ steps.ssh-setup.outcome == 'success' }}
run: cargo generate-lockfile --manifest-path crates/remote/Cargo.toml
- name: Stop SSH agent
if: ${{ steps.ssh-setup.outcome == 'success' }}
run: ssh-agent -k
- name: Commit changes and create tag
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add package.json pnpm-lock.yaml npx-cli/package.json packages/local-web/package.json Cargo.lock
git add $(find . -name Cargo.toml)
[ -f crates/remote/Cargo.lock ] && git add crates/remote/Cargo.lock || true
git commit -m "chore: bump version to ${{ steps.version.outputs.new_version }}"
git tag -a ${{ steps.version.outputs.new_tag }} -m "Release ${{ steps.version.outputs.new_tag }}"
git push
git push --tags
build-frontend:
needs: bump-version
runs-on: ubuntu-latest-x64-l
env:
VITE_PUBLIC_REACT_VIRTUOSO_LICENSE_KEY: ${{ secrets.PUBLIC_REACT_VIRTUOSO_LICENSE_KEY }}
VITE_VK_SHARED_API_BASE: ${{ secrets.VK_SHARED_API_BASE }}
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.new_tag }}
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Install dependencies
run: pnpm install
- name: Lint frontend
run: cd packages/local-web && npm run lint
- name: Type check frontend
run: cd packages/local-web && npx tsc --noEmit
- name: Build frontend
run: cd packages/local-web && npm run build
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
VITE_POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
VITE_POSTHOG_API_ENDPOINT: ${{ secrets.POSTHOG_API_ENDPOINT }}
VITE_SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
- name: Create Sentry release
uses: getsentry/action-release@v3
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
with:
release: ${{ needs.bump-version.outputs.new_version }}
environment: production
sourcemaps: "./packages/local-web/dist"
ignore_missing: true
- name: Upload frontend artifact
uses: actions/upload-artifact@v6
with:
name: frontend-dist
path: packages/local-web/dist/
retention-days: 1
build-backend:
needs: [bump-version, build-frontend]
runs-on: ${{ matrix.os }}
strategy:
# Platform matrix - keep target/name in sync with package-npx-cli job
matrix:
include:
- target: x86_64-unknown-linux-musl
os: ubuntu-latest-x64-l
name: linux-x64
- target: aarch64-unknown-linux-musl
os: ubuntu-latest-arm64-l
name: linux-arm64
- target: x86_64-pc-windows-msvc
os: ubuntu-latest-x64-l
name: windows-x64
- target: x86_64-apple-darwin
os: macos-15-xlarge
name: macos-x64
- target: aarch64-apple-darwin
os: macos-15-xlarge
name: macos-arm64
- target: aarch64-pc-windows-msvc
os: ubuntu-latest-x64-l
name: windows-arm64
env:
CARGO_INCREMENTAL: "0"
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
SCCACHE_CACHE_SIZE: "10G"
CARGO_HOME: ${{ github.workspace }}/.cargo
RUSTUP_HOME: ${{ github.workspace }}/.rustup
XWIN_CACHE_DIR: ${{ github.workspace }}/.xwin-cache
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.new_tag }}
- name: Setup sccache
uses: BloopAI/sccache-action@main
- name: Cache Rust toolchain
uses: actions/cache@v5
with:
path: .rustup/toolchains
key: rust-toolchain-${{ runner.os }}-${{ matrix.target }}-${{ env.RUST_TOOLCHAIN }}
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
targets: ${{ matrix.target }}
components: rustfmt, clippy
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: |
sudo apt-get update
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y clang libclang-dev lld llvm nasm cmake ninja-build
if [[ "${{ matrix.target }}" == *"windows"* ]]; then
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y clang-19 clang-tools-19 llvm-19 lld-19
echo "/usr/lib/llvm-19/bin" >> $GITHUB_PATH
fi
- name: Cache Cargo registry
uses: actions/cache@v5
with:
path: |
.cargo/registry/cache
.cargo/registry/index
.cargo/git/db
.cargo/bin
.cargo/.crates.toml
.cargo/.crates2.json
key: cargo-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}-${{ matrix.target }}-
- name: Install Zig
if: runner.os == 'Linux' && !contains(matrix.target, 'windows')
uses: BloopAI/setup-zig@main
with:
version: 0.15.2
- name: Install cargo zigbuild
if: runner.os == 'Linux' && !contains(matrix.target, 'windows')
run: cargo install --locked cargo-zigbuild --version 0.20.1
- name: Install cargo xwin
if: runner.os == 'Linux' && contains(matrix.target, 'windows')
run: cargo install --locked cargo-xwin --version ${{ env.CARGO_XWIN_VERSION }}
- name: Cache xwin downloads
if: runner.os == 'Linux' && contains(matrix.target, 'windows')
uses: actions/cache@v5
with:
path: ${{ github.workspace }}/.xwin-cache
key: xwin-${{ runner.os }}-${{ matrix.target }}-cargo-xwin-${{ env.CARGO_XWIN_VERSION }}
- name: Cache target
uses: actions/cache@v5
with:
path: target
key: target-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}-${{ matrix.target }}-${{ github.ref_name }}-${{ github.sha }}
restore-keys: |
target-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}-${{ matrix.target }}-${{ github.ref_name }}-
target-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}-${{ matrix.target }}-
- name: Setup cargo-sweep
shell: bash
run: |
cargo install --locked cargo-sweep --version 0.8.0
- name: Download frontend artifact
uses: actions/download-artifact@v7
with:
name: frontend-dist
path: packages/local-web/dist/
- name: Build backend (Linux)
if: runner.os == 'Linux' && !contains(matrix.target, 'windows')
run: |
cargo zigbuild --release --target ${{ matrix.target }} -p server -p mcp -p review --bin server --bin mcp_task_server --bin review
env:
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
POSTHOG_API_ENDPOINT: ${{ secrets.POSTHOG_API_ENDPOINT }}
VK_SHARED_API_BASE: ${{ secrets.VK_SHARED_API_BASE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
- name: Build backend (macOS)
if: runner.os == 'macOS'
run: |
if [[ "${{ matrix.target }}" == "x86_64-apple-darwin" ]]; then
export MACOSX_DEPLOYMENT_TARGET=10.12
elif [[ "${{ matrix.target }}" == "aarch64-apple-darwin" ]]; then
export MACOSX_DEPLOYMENT_TARGET=11.0
fi
cargo build --release --target ${{ matrix.target }} -p server -p mcp -p review --bin server --bin mcp_task_server --bin review
env:
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
POSTHOG_API_ENDPOINT: ${{ secrets.POSTHOG_API_ENDPOINT }}
VK_SHARED_API_BASE: ${{ secrets.VK_SHARED_API_BASE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
- name: Build backend (Windows)
if: runner.os == 'Linux' && contains(matrix.target, 'windows')
run: |
if [[ "${{ matrix.target }}" == "aarch64-pc-windows-msvc" ]]; then
# ring requires clang on arm64 windows. See https://github.com/briansmith/ring/issues/2117
chmod +x scripts/ring-cc-wrapper.sh scripts/clang
export PATH="${{ github.workspace }}/scripts:$PATH"
export RING_CC=/usr/lib/llvm-19/bin/clang
export DEFAULT_CC=clang-cl
export CC_aarch64_pc_windows_msvc="${{ github.workspace }}/scripts/ring-cc-wrapper.sh"
fi
cargo xwin build --cross-compiler clang-cl --release --target ${{ matrix.target }} -p server -p mcp -p review --bin server --bin mcp_task_server --bin review
env:
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
POSTHOG_API_ENDPOINT: ${{ secrets.POSTHOG_API_ENDPOINT }}
VK_SHARED_API_BASE: ${{ secrets.VK_SHARED_API_BASE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
# Avoid aws-lc-sys CMake failures when Rust's `release` profile includes debug info.
# Without this, cmake-rs selects RelWithDebInfo and CMake fails when ASM is enabled.
CARGO_PROFILE_RELEASE_DEBUG: 0
- name: Setup Sentry CLI
uses: matbour/setup-sentry-cli@v2
with:
token: ${{ secrets.SENTRY_AUTH_TOKEN }}
organization: ${{ secrets.SENTRY_ORG }}
project: ${{ secrets.SENTRY_PROJECT }}
version: 2.21.2
- name: Upload source maps to Sentry
run: sentry-cli debug-files upload --include-sources target/${{ matrix.target }}/release
- name: Prepare binaries (non-macOS)
if: runner.os != 'macOS'
shell: bash
run: |
mkdir -p dist
if [[ "${{ matrix.name }}" == *"windows"* ]]; then
cp target/${{ matrix.target }}/release/server.exe dist/vibe-kanban-${{ matrix.name }}.exe
cp target/${{ matrix.target }}/release/mcp_task_server.exe dist/vibe-kanban-mcp-${{ matrix.name }}.exe
cp target/${{ matrix.target }}/release/review.exe dist/vibe-kanban-review-${{ matrix.name }}.exe
else
cp target/${{ matrix.target }}/release/server dist/vibe-kanban-${{ matrix.name }}
cp target/${{ matrix.target }}/release/mcp_task_server dist/vibe-kanban-mcp-${{ matrix.name }}
cp target/${{ matrix.target }}/release/review dist/vibe-kanban-review-${{ matrix.name }}
fi
# Code signing for macOS only
- name: Prepare Apple certificate (macOS)
if: runner.os == 'macOS'
run: |
echo "${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}" | base64 --decode > certificate.p12
- name: Write API Key to file
if: runner.os == 'macOS'
env:
API_KEY: ${{ secrets.APP_STORE_API_KEY }}
run: echo $API_KEY > app_store_key.json
- name: Sign main binary (macOS)
if: runner.os == 'macOS'
uses: BloopAI/apple-code-sign-action@v1
with:
input_path: target/${{ matrix.target }}/release/server
output_path: vibe-kanban
p12_file: certificate.p12
p12_password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
sign: true
sign_args: "--code-signature-flags=runtime"
- name: Package main binary (macOS)
if: runner.os == 'macOS'
run: zip vibe-kanban.zip vibe-kanban
- name: Sign MCP binary (macOS)
if: runner.os == 'macOS'
uses: BloopAI/apple-code-sign-action@v1
with:
input_path: target/${{ matrix.target }}/release/mcp_task_server
output_path: vibe-kanban-mcp
p12_file: certificate.p12
p12_password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
sign: true
sign_args: "--code-signature-flags=runtime"
- name: Package MCP binary (macOS)
if: runner.os == 'macOS'
run: zip vibe-kanban-mcp.zip vibe-kanban-mcp
- name: Sign Review binary (macOS)
if: runner.os == 'macOS'
uses: BloopAI/apple-code-sign-action@v1
with:
input_path: target/${{ matrix.target }}/release/review
output_path: vibe-kanban-review
p12_file: certificate.p12
p12_password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
sign: true
sign_args: "--code-signature-flags=runtime"
- name: Package Review binary (macOS)
if: runner.os == 'macOS'
run: zip vibe-kanban-review.zip vibe-kanban-review
- name: Notarize signed binaries (macOS)
if: runner.os == 'macOS'
uses: BloopAI/apple-code-sign-action@main
continue-on-error: true
with:
input_path: |
vibe-kanban.zip
vibe-kanban-mcp.zip
vibe-kanban-review.zip
sign: false
notarize: true
app_store_connect_api_key_json_file: app_store_key.json
- name: Prepare signed binaries (macOS)
if: runner.os == 'macOS'
run: |
mkdir -p dist
cp vibe-kanban.zip dist/vibe-kanban-${{ matrix.name }}.zip
cp vibe-kanban-mcp.zip dist/vibe-kanban-mcp-${{ matrix.name }}.zip
cp vibe-kanban-review.zip dist/vibe-kanban-review-${{ matrix.name }}.zip
- name: Clean up certificates (macOS)
if: runner.os == 'macOS'
run: |
rm -f certificate.p12
rm -rf private_keys/
- name: Upload binary artifact
uses: actions/upload-artifact@v6
with:
name: backend-binary-${{ matrix.name }}
path: dist/
retention-days: 1
- name: Sweep Cargo target cache
shell: bash
run: |
cargo sweep --maxsize 10GB
cargo sweep --time 30
package-npx-cli:
needs: [bump-version, build-frontend, build-backend]
runs-on: ubuntu-latest-m
strategy:
# NOTE: This matrix must be kept in sync with build-backend job above
# GitHub Actions doesn't support YAML anchors, so duplication is unavoidable
matrix:
include:
- target: x86_64-unknown-linux-musl
name: linux-x64
binary: vibe-kanban
mcp_binary: vibe-kanban-mcp
review_binary: vibe-kanban-review
- target: x86_64-pc-windows-msvc
name: windows-x64
binary: vibe-kanban.exe
mcp_binary: vibe-kanban-mcp.exe
review_binary: vibe-kanban-review.exe
- target: x86_64-apple-darwin
name: macos-x64
binary: vibe-kanban
mcp_binary: vibe-kanban-mcp
review_binary: vibe-kanban-review
- target: aarch64-apple-darwin
name: macos-arm64
binary: vibe-kanban
mcp_binary: vibe-kanban-mcp
review_binary: vibe-kanban-review
- target: aarch64-pc-windows-msvc
name: windows-arm64
binary: vibe-kanban.exe
mcp_binary: vibe-kanban-mcp.exe
review_binary: vibe-kanban-review.exe
- target: aarch64-unknown-linux-musl
name: linux-arm64
binary: vibe-kanban
mcp_binary: vibe-kanban-mcp
review_binary: vibe-kanban-review
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.new_tag }}
- name: Download frontend artifact
uses: actions/download-artifact@v7
with:
name: frontend-dist
path: packages/local-web/dist/
- name: Download backend binary artifact
uses: actions/download-artifact@v7
with:
name: backend-binary-${{ matrix.name }}
path: dist/
- name: List downloaded artifacts
run: |
echo "Downloaded backend binaries:"
find dist/
- name: Create platform package
if: matrix.name != 'macos-arm64' && matrix.name != 'macos-x64'
run: |
mkdir -p npx-cli/dist/${{ matrix.name }}
mkdir vibe-kanban-${{ matrix.name }}
mkdir vibe-kanban-mcp-${{ matrix.name }}
mkdir vibe-kanban-review-${{ matrix.name }}
cp dist/vibe-kanban-${{ matrix.name }}* vibe-kanban-${{ matrix.name }}/${{ matrix.binary }}
cp dist/vibe-kanban-mcp-${{ matrix.name }}* vibe-kanban-mcp-${{ matrix.name }}/${{ matrix.mcp_binary }}
cp dist/vibe-kanban-review-${{ matrix.name }}* vibe-kanban-review-${{ matrix.name }}/${{ matrix.review_binary }}
zip -j npx-cli/dist/${{ matrix.name }}/vibe-kanban.zip vibe-kanban-${{ matrix.name }}/${{ matrix.binary }}
zip -j npx-cli/dist/${{ matrix.name }}/vibe-kanban-mcp.zip vibe-kanban-mcp-${{ matrix.name }}/${{ matrix.mcp_binary }}
zip -j npx-cli/dist/${{ matrix.name }}/vibe-kanban-review.zip vibe-kanban-review-${{ matrix.name }}/${{ matrix.review_binary }}
- name: Create platform package (macOS)
if: matrix.name == 'macos-arm64' || matrix.name == 'macos-x64'
run: |
mkdir -p npx-cli/dist/${{ matrix.name }}
mkdir vibe-kanban-${{ matrix.name }}
cp dist/vibe-kanban-${{ matrix.name }}* npx-cli/dist/${{ matrix.name }}/vibe-kanban.zip
cp dist/vibe-kanban-mcp-${{ matrix.name }}* npx-cli/dist/${{ matrix.name }}/vibe-kanban-mcp.zip
cp dist/vibe-kanban-review-${{ matrix.name }}* npx-cli/dist/${{ matrix.name }}/vibe-kanban-review.zip
- name: Upload platform package artifact
uses: actions/upload-artifact@v6
with:
name: npx-platform-${{ matrix.name }}
path: npx-cli/dist/
retention-days: 1
upload-to-r2:
needs: [bump-version, package-npx-cli]
runs-on: ubuntu-latest-m
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.new_tag }}
- name: Download all platform packages
uses: actions/download-artifact@v7
with:
pattern: npx-platform-*
path: binaries/
merge-multiple: true
- name: List downloaded binaries
run: |
echo "Downloaded binaries:"
find binaries/
- name: Configure AWS CLI for R2
run: |
aws configure set aws_access_key_id ${{ secrets.R2_BINARIES_ACCESS_KEY_ID }}
aws configure set aws_secret_access_key ${{ secrets.R2_BINARIES_SECRET_ACCESS_KEY }}
aws configure set default.region auto
- name: Generate manifest and upload to R2
run: |
TAG="${{ needs.bump-version.outputs.new_tag }}"
ENDPOINT="${{ secrets.R2_BINARIES_ENDPOINT }}"
BUCKET="${{ secrets.R2_BINARIES_BUCKET }}"
# Generate version manifest with checksums
node -e "
const fs = require('fs');
const crypto = require('crypto');
const manifest = { version: '$TAG', platforms: {} };
const platforms = ['linux-x64', 'linux-arm64', 'windows-x64', 'windows-arm64', 'macos-x64', 'macos-arm64'];
const binaries = ['vibe-kanban', 'vibe-kanban-mcp', 'vibe-kanban-review'];
for (const platform of platforms) {
manifest.platforms[platform] = {};
for (const binary of binaries) {
const zipPath = \`binaries/\${platform}/\${binary}.zip\`;
if (fs.existsSync(zipPath)) {
const data = fs.readFileSync(zipPath);
manifest.platforms[platform][binary] = {
sha256: crypto.createHash('sha256').update(data).digest('hex'),
size: data.length
};
}
}
}
fs.writeFileSync('version-manifest.json', JSON.stringify(manifest, null, 2));
console.log('Generated manifest:');
console.log(JSON.stringify(manifest, null, 2));
"
# Upload binaries (use full tag for path, allows multiple pre-releases to coexist)
for platform in linux-x64 linux-arm64 windows-x64 windows-arm64 macos-x64 macos-arm64; do
for binary in vibe-kanban vibe-kanban-mcp vibe-kanban-review; do
if [ -f "binaries/$platform/$binary.zip" ]; then
echo "Uploading binaries/$platform/$binary.zip..."
aws s3 cp "binaries/$platform/$binary.zip" \
"s3://$BUCKET/binaries/$TAG/$platform/$binary.zip" \
--endpoint-url "$ENDPOINT"
fi
done
done
# Upload version manifest
echo "Uploading version manifest..."
aws s3 cp version-manifest.json \
"s3://$BUCKET/binaries/$TAG/manifest.json" \
--endpoint-url "$ENDPOINT" --content-type "application/json"
# Update global manifest
VERSION="${{ needs.bump-version.outputs.new_version }}"
echo "Updating global manifest..."
echo "{\"latest\": \"$VERSION\"}" | aws s3 cp - \
"s3://$BUCKET/binaries/manifest.json" \
--endpoint-url "$ENDPOINT" --content-type "application/json"
- name: Verify upload
run: |
TAG="${{ needs.bump-version.outputs.new_tag }}"
ENDPOINT="${{ secrets.R2_BINARIES_ENDPOINT }}"
BUCKET="${{ secrets.R2_BINARIES_BUCKET }}"
echo "Listing uploaded files..."
aws s3 ls "s3://$BUCKET/binaries/$TAG/" \
--endpoint-url "$ENDPOINT" \
--recursive
build-tauri:
needs: [bump-version, build-frontend]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
strategy:
matrix:
include:
- target: aarch64-apple-darwin
os: macos-15-xlarge
platform: darwin-aarch64
- target: x86_64-apple-darwin
os: macos-15-xlarge
platform: darwin-x86_64
- target: x86_64-unknown-linux-gnu
os: ubuntu-latest-m
platform: linux-x86_64
- target: x86_64-pc-windows-msvc
os: windows-latest
platform: windows-x86_64
env:
CARGO_INCREMENTAL: "0"
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
SCCACHE_CACHE_SIZE: "10G"
CARGO_HOME: ${{ github.workspace }}/.cargo
RUSTUP_HOME: ${{ github.workspace }}/.rustup
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.new_tag }}
- name: Setup sccache
uses: BloopAI/sccache-action@main
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
targets: ${{ matrix.target }}
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential \
libssl-dev libayatana-appindicator3-dev librsvg2-dev \
libxdo-dev file pkg-config
- name: Install dependencies
run: pnpm install
- name: Install Tauri CLI
run: cargo install tauri-cli --version "^2" --locked
- name: Download frontend artifact
uses: actions/download-artifact@v7
with:
name: frontend-dist
path: frontend/dist/
- name: Patch tauri.conf.json for CI
run: |
node -e "
const fs = require('fs');
const conf = JSON.parse(fs.readFileSync('crates/tauri-app/tauri.conf.json', 'utf8'));
// Inject the real updater endpoint (replaces __TAURI_UPDATE_ENDPOINT__ placeholder)
const endpoint = '${{ secrets.R2_BINARIES_PUBLIC_URL }}/binaries/tauri-update/latest.json';
conf.plugins.updater.endpoints = conf.plugins.updater.endpoints.map(e =>
e === '__TAURI_UPDATE_ENDPOINT__' ? endpoint : e
);
// Remove beforeBuildCommand — frontend is already pre-built and downloaded as an artifact
delete conf.build.beforeBuildCommand;
fs.writeFileSync('crates/tauri-app/tauri.conf.json', JSON.stringify(conf, null, 2) + '\n');
"
- name: Set up Apple notarization key
if: runner.os == 'macOS'
env:
API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
API_PRIVATE_KEY: ${{ secrets.APPLE_API_PRIVATE_KEY }}
run: |
# Write the .p8 key file where Tauri/notarytool can find it
mkdir -p ~/.private_keys
KEY_PATH="$HOME/.private_keys/AuthKey_${API_KEY_ID}.p8"
printf '%s' "$API_PRIVATE_KEY" > "$KEY_PATH"
echo "APPLE_API_KEY=$API_KEY_ID" >> $GITHUB_ENV
echo "APPLE_API_ISSUER=$API_ISSUER" >> $GITHUB_ENV
echo "APPLE_API_KEY_PATH=$KEY_PATH" >> $GITHUB_ENV
- name: Build Tauri app
run: cargo tauri build --target ${{ matrix.target }} --ci
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE_P12_BASE64 }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_API_KEY: ${{ env.APPLE_API_KEY }}
APPLE_API_ISSUER: ${{ env.APPLE_API_ISSUER }}
APPLE_API_KEY_PATH: ${{ env.APPLE_API_KEY_PATH }}
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
POSTHOG_API_ENDPOINT: ${{ secrets.POSTHOG_API_ENDPOINT }}
VK_SHARED_API_BASE: ${{ secrets.VK_SHARED_API_BASE }}
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
- name: Collect updater artifacts and installers
run: |
mkdir -p tauri-artifacts/${{ matrix.platform }}
# Collect updater artifacts (.sig files and their corresponding bundles)
find target/${{ matrix.target }}/release/bundle -name "*.sig" | while read sig; do
artifact="${sig%.sig}"
if [ -f "$artifact" ]; then
cp "$artifact" "tauri-artifacts/${{ matrix.platform }}/"
cp "$sig" "tauri-artifacts/${{ matrix.platform }}/"
echo "Collected updater artifact: $(basename $artifact)"
fi
done
# Collect installer files for GitHub Release (DMG, AppImage, deb, msi, NSIS exe)
find target/${{ matrix.target }}/release/bundle \
\( -name "*.dmg" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.msi" -o -name "*-setup.exe" \) | while read f; do
cp "$f" "tauri-artifacts/${{ matrix.platform }}/"
echo "Collected installer: $(basename $f)"
done
echo "All artifacts for ${{ matrix.platform }}:"
ls -la tauri-artifacts/${{ matrix.platform }}/
- name: Clean up signing keys
if: always() && runner.os == 'macOS'
run: rm -rf ~/.private_keys
- name: Upload Tauri artifacts
uses: actions/upload-artifact@v6
with:
name: tauri-artifacts-${{ matrix.platform }}
path: tauri-artifacts/
retention-days: 1
upload-tauri-update:
needs: [bump-version, build-tauri]
runs-on: ubuntu-latest-m
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.new_tag }}
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Download all Tauri artifacts
uses: actions/download-artifact@v7
with:
pattern: tauri-artifacts-*
path: tauri-artifacts/
merge-multiple: true
- name: List Tauri artifacts
run: find tauri-artifacts/
- name: Generate updater manifest
run: |
node scripts/generate-tauri-update-json.js \
--version "${{ needs.bump-version.outputs.new_version }}" \
--notes "Release ${{ needs.bump-version.outputs.new_tag }}" \
--artifacts-dir ./tauri-artifacts \
--download-base "${{ secrets.R2_BINARIES_PUBLIC_URL }}/binaries/${{ needs.bump-version.outputs.new_tag }}/tauri" \
--output latest.json
echo "Generated latest.json:"
cat latest.json
- name: Configure AWS CLI for R2
run: |
aws configure set aws_access_key_id ${{ secrets.R2_BINARIES_ACCESS_KEY_ID }}
aws configure set aws_secret_access_key ${{ secrets.R2_BINARIES_SECRET_ACCESS_KEY }}
aws configure set default.region auto
- name: Upload Tauri artifacts and update manifest to R2
run: |
TAG="${{ needs.bump-version.outputs.new_tag }}"
ENDPOINT="${{ secrets.R2_BINARIES_ENDPOINT }}"
BUCKET="${{ secrets.R2_BINARIES_BUCKET }}"
# Upload individual platform artifacts
for platform in darwin-aarch64 darwin-x86_64 linux-x86_64 windows-x86_64; do
if [ -d "tauri-artifacts/$platform" ]; then
for file in tauri-artifacts/$platform/*; do
[ -f "$file" ] || continue
# Skip .sig files from artifact upload (signatures are in latest.json)
[[ "$file" == *.sig ]] && continue
filename=$(basename "$file")
echo "Uploading $filename for $platform..."
aws s3 cp "$file" \
"s3://$BUCKET/binaries/$TAG/tauri/$filename" \
--endpoint-url "$ENDPOINT"
done
fi
done
# Upload latest.json to the fixed update endpoint path
echo "Uploading updater manifest..."
aws s3 cp latest.json \
"s3://$BUCKET/binaries/tauri-update/latest.json" \
--endpoint-url "$ENDPOINT" --content-type "application/json"
echo "Update manifest uploaded to: binaries/tauri-update/latest.json"
create-prerelease:
needs: [bump-version, build-frontend, upload-to-r2, upload-tauri-update]
runs-on: ubuntu-latest-m
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.bump-version.outputs.new_tag }}
- name: Download frontend artifact
uses: actions/download-artifact@v7
with:
name: frontend-dist
path: packages/local-web/dist/
- name: Download Tauri artifacts for release
uses: actions/download-artifact@v7
with:
pattern: tauri-artifacts-*
path: tauri-release/
merge-multiple: true
- name: Collect Tauri installers for release
run: |
mkdir -p tauri-installers
find tauri-release \( -name "*.dmg" -o -name "*.AppImage" -o -name "*.deb" -o -name "*.msi" -o -name "*-setup.exe" \) \
-exec cp {} tauri-installers/ \;
echo "Tauri installers for release:"
ls -la tauri-installers/ 2>/dev/null || echo "No installers found"
- name: List downloaded artifacts
run: |
echo "Web dist:"
find packages/local-web/dist
- name: Zip frontend
run: |
mkdir vibe-kanban-${{ needs.bump-version.outputs.new_tag }}
mv packages/local-web/dist vibe-kanban-${{ needs.bump-version.outputs.new_tag }}
zip -r vibe-kanban-${{ needs.bump-version.outputs.new_tag }}.zip vibe-kanban-${{ needs.bump-version.outputs.new_tag }}
- name: Setup Node for npm pack
uses: ./.github/actions/setup-node
- name: Inject R2 URL and tag, then Pack
run: |
cd npx-cli
# Replace placeholders with actual values
sed -i "s|__R2_PUBLIC_URL__|${{ secrets.R2_BINARIES_PUBLIC_URL }}|g" bin/download.js
sed -i "s|__BINARY_TAG__|${{ needs.bump-version.outputs.new_tag }}|g" bin/download.js
npm pack
- name: Create GitHub Pre-Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.bump-version.outputs.new_tag }}
name: Pre-release ${{ needs.bump-version.outputs.new_tag }}
prerelease: true
generate_release_notes: true
files: |
vibe-kanban-${{ needs.bump-version.outputs.new_tag }}.zip
npx-cli/vibe-kanban-*.tgz
tauri-installers/*

143
.github/workflows/publish.yml vendored Normal file
View File

@@ -0,0 +1,143 @@
name: Publish to npm
on:
release:
types: [released]
workflow_dispatch:
inputs:
tag_name:
description: "Release tag (e.g., v1.2.3)"
required: true
release_id:
description: "GitHub release ID"
required: true
concurrency:
group: publish
cancel-in-progress: true
permissions:
contents: write
packages: write
id-token: write # Required for OIDC trusted publishing
env:
NODE_VERSION: 22
PNPM_VERSION: 10.13.1
jobs:
publish:
runs-on: ubuntu-latest
# Only run for main app releases (not remote-v* tags) that were converted from pre-release
if: github.event.release.prerelease == false && !startsWith(github.event.release.tag_name, 'remote-')
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.release.tag_name || inputs.tag_name }}
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Upgrade npm for OIDC support
run: npm install -g npm@latest
- name: Download release assets
uses: actions/github-script@v8
env:
RELEASE_ID: ${{ inputs.release_id }}
with:
script: |
const fs = require('fs');
const path = require('path');
const releaseId = context.payload.release?.id || process.env.RELEASE_ID;
console.log("releaseId:", releaseId);
if (!releaseId) {
core.setFailed('No release ID found.');
return;
}
const release = await github.rest.repos.getRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId
});
// Find the .tgz file
const tgzAsset = release.data.assets.find(asset => asset.name.endsWith('.tgz'));
if (!tgzAsset) {
core.setFailed('No .tgz file found in release assets');
return;
}
// Download the asset
const response = await github.rest.repos.getReleaseAsset({
owner: context.repo.owner,
repo: context.repo.repo,
asset_id: tgzAsset.id,
headers: {
Accept: 'application/octet-stream'
}
});
// Save to npx-cli directory
const filePath = path.join('npx-cli', tgzAsset.name);
fs.writeFileSync(filePath, Buffer.from(response.data));
console.log(`Downloaded ${tgzAsset.name} to ${filePath}`);
// Set output for next step
core.setOutput('package-file', filePath);
core.setOutput('package-name', tgzAsset.name);
- name: Verify package integrity
id: verify
run: |
cd npx-cli
# List files to confirm download
ls -la *.tgz
# Verify the package can be read
npm pack --dry-run || echo "Note: This is expected to show differences since we're using the pre-built package"
# Extract package name from the downloaded file
PACKAGE_FILE=$(ls *.tgz | head -n1)
echo "package-file=$PACKAGE_FILE" >> $GITHUB_OUTPUT
- name: Publish to npm
run: |
cd npx-cli
# Publish the exact same package that was tested
PACKAGE_FILE="${{ steps.verify.outputs.package-file }}"
echo "Publishing $PACKAGE_FILE to npm..."
npm publish "$PACKAGE_FILE" --provenance --access public
echo "✅ Successfully published to npm!"
- name: Update release description
uses: actions/github-script@v8
env:
RELEASE_ID: ${{ inputs.release_id }}
with:
script: |
const releaseId = context.payload.release?.id || process.env.RELEASE_ID;;
// Fetch the release to get the current body
const release = await github.rest.repos.getRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId
});
const currentBody = release.data.body || '';
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: releaseId,
body: currentBody + '\n\n✅ **Published to npm registry**'
});

28
.github/workflows/relay-deploy-dev.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: Relay Deploy Dev
on:
push:
branches:
- main
paths:
- crates/relay-tunnel/**
workflow_dispatch:
jobs:
run-relay-deploy:
name: Deploy Relay Dev
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Dispatch dev relay deployment workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
token: ${{ secrets.REMOTE_DEPLOYMENT_TOKEN }}
repository: BloopAI/vibe-kanban-remote-deployment
event-type: vibe-kanban-relay-deploy-dev
client-payload: |
{
"ref": "${{ github.ref_name }}",
"sha": "${{ github.sha }}"
}

110
.github/workflows/relay-deploy-prod.yml vendored Normal file
View File

@@ -0,0 +1,110 @@
name: Deploy Relay Prod
on:
workflow_dispatch:
inputs:
version_type:
description: "Version bump type"
required: true
default: "patch"
type: choice
options:
- patch
- minor
- major
concurrency:
group: relay-deploy-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: write
env:
RUST_TOOLCHAIN: nightly-2025-12-04
jobs:
release-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Cache cargo-edit
uses: actions/cache@v5
id: cache-cargo-edit
with:
path: ~/.cargo/bin/cargo-set-version
key: cargo-edit-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}
- name: Install cargo-edit
if: steps.cache-cargo-edit.outputs.cache-hit != 'true'
run: cargo install cargo-edit
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: Determine and update version
id: version
run: |
# Get current version from crates/relay-tunnel/Cargo.toml
current_version=$(grep '^version = ' crates/relay-tunnel/Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
echo "Current relay-tunnel version: $current_version"
# Parse version components
IFS='.' read -r major minor patch <<< "$current_version"
# Bump based on type
case "${{ github.event.inputs.version_type }}" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
esac
new_version="${major}.${minor}.${patch}"
new_tag="relay-v${new_version}"
# Update version in crates/relay-tunnel/Cargo.toml
cd crates/relay-tunnel
cargo set-version "$new_version"
cd ../..
# Update workspace Cargo.lock
cargo update relay-tunnel
echo "New version: $new_version"
echo "New tag: $new_tag"
echo "new_version=$new_version" >> $GITHUB_OUTPUT
echo "new_tag=$new_tag" >> $GITHUB_OUTPUT
echo "new_ref=$new_tag" >> $GITHUB_OUTPUT
- name: Commit changes and create tag
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add crates/relay-tunnel/Cargo.toml Cargo.lock
git commit -m "chore: bump relay-tunnel version to ${{ steps.version.outputs.new_version }}"
git tag -a ${{ steps.version.outputs.new_tag }} -m "Relay release ${{ steps.version.outputs.new_tag }}"
git push
git push --tags
- name: Dispatch relay deployment workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
token: ${{ secrets.REMOTE_DEPLOYMENT_TOKEN }}
repository: BloopAI/vibe-kanban-remote-deployment
event-type: vibe-kanban-relay-deploy-prod
client-payload: |
{
"ref": "${{ steps.version.outputs.new_ref }}",
"sha": "${{ github.sha }}",
"version": "${{ steps.version.outputs.new_version }}"
}

22
.github/workflows/relay-release.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Create Relay Release
on:
repository_dispatch:
types: [relay-deploy-success]
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.client_payload.tag }}
name: ${{ github.event.client_payload.tag }}
generate_release_notes: true
body: |
## Relay Tunnel Service Release
Deployed to ${{ github.event.client_payload.environment }}

29
.github/workflows/remote-deploy-dev.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Remote Deploy Dev
on:
push:
branches:
- main
paths:
- crates/remote/**
- packages/remote-web/**
workflow_dispatch:
jobs:
run-remote-deploy:
name: Deploy Remote Dev
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Dispatch dev remote deployment workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
token: ${{ secrets.REMOTE_DEPLOYMENT_TOKEN }}
repository: BloopAI/vibe-kanban-remote-deployment
event-type: vibe-kanban-remote-deploy-dev
client-payload: |
{
"ref": "${{ github.ref_name }}",
"sha": "${{ github.sha }}"
}

116
.github/workflows/remote-deploy-prod.yml vendored Normal file
View File

@@ -0,0 +1,116 @@
name: Deploy Remote Prod
on:
workflow_dispatch:
inputs:
version_type:
description: "Version bump type"
required: true
default: "patch"
type: choice
options:
- patch
- minor
- major
concurrency:
group: remote-deploy-${{ github.ref_name }}
cancel-in-progress: true
permissions:
contents: write
env:
RUST_TOOLCHAIN: nightly-2025-12-04
jobs:
release-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Cache cargo-edit
uses: actions/cache@v5
id: cache-cargo-edit
with:
path: ~/.cargo/bin/cargo-set-version
key: cargo-edit-${{ runner.os }}-${{ env.RUST_TOOLCHAIN }}
- name: Install cargo-edit
if: steps.cache-cargo-edit.outputs.cache-hit != 'true'
run: cargo install cargo-edit
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh-key: ${{ secrets.DEPLOY_KEY }}
- name: Setup SSH Agent for private dependencies
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.VK_PRIVATE_DEPLOY_KEY }}
- name: Determine and update version
id: version
run: |
# Get current version from crates/remote/Cargo.toml
current_version=$(grep '^version = ' crates/remote/Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
echo "Current remote version: $current_version"
# Parse version components
IFS='.' read -r major minor patch <<< "$current_version"
# Bump based on type
case "${{ github.event.inputs.version_type }}" in
major)
major=$((major + 1))
minor=0
patch=0
;;
minor)
minor=$((minor + 1))
patch=0
;;
patch)
patch=$((patch + 1))
;;
esac
new_version="${major}.${minor}.${patch}"
new_tag="remote-v${new_version}"
# Update version in crates/remote/Cargo.toml
cd crates/remote
cargo set-version "$new_version"
cargo update remote
cd ../..
echo "New version: $new_version"
echo "New tag: $new_tag"
echo "new_version=$new_version" >> $GITHUB_OUTPUT
echo "new_tag=$new_tag" >> $GITHUB_OUTPUT
echo "new_ref=$new_tag" >> $GITHUB_OUTPUT
- name: Stop SSH agent
run: ssh-agent -k
- name: Commit changes and create tag
run: |
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add crates/remote/Cargo.toml crates/remote/Cargo.lock
git commit -m "chore: bump remote version to ${{ steps.version.outputs.new_version }}"
git tag -a ${{ steps.version.outputs.new_tag }} -m "Remote release ${{ steps.version.outputs.new_tag }}"
git push
git push --tags
- name: Dispatch remote deployment workflow
uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1
with:
token: ${{ secrets.REMOTE_DEPLOYMENT_TOKEN }}
repository: BloopAI/vibe-kanban-remote-deployment
event-type: vibe-kanban-remote-deploy-prod
client-payload: |
{
"ref": "${{ steps.version.outputs.new_ref }}",
"sha": "${{ github.sha }}",
"version": "${{ steps.version.outputs.new_version }}"
}

22
.github/workflows/remote-release.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: Create Remote Release
on:
repository_dispatch:
types: [remote-deploy-success]
jobs:
create-release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event.client_payload.tag }}
name: ${{ github.event.client_payload.tag }}
generate_release_notes: true
body: |
## Remote Service Release
✅ Deployed to ${{ github.event.client_payload.environment }}

232
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,232 @@
name: Test
on:
pull_request:
branches:
- main
paths-ignore:
- .github/workflows/**
- '!.github/workflows/test.yml'
push:
branches:
- main
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
env:
CARGO_TERM_COLOR: always
NODE_VERSION: 22
PNPM_VERSION: 10.13.1
RUST_TOOLCHAIN: nightly-2025-12-04
RUNNER_LABEL: &runner_label blacksmith-16vcpu-ubuntu-2404
jobs:
changes:
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
permissions:
contents: read
pull-requests: read
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
backend-remote: ${{ steps.filter.outputs['backend-remote'] }}
steps:
- uses: actions/checkout@v6
- uses: dorny/paths-filter@0bc4621a3135347011ad047f9ecf449bf72ce2bd # v3.0.0
id: filter
with:
filters: |
frontend:
- 'packages/local-web/**'
- 'packages/web-core/**'
- 'packages/remote-web/**'
- 'packages/ui/**'
- 'shared/**'
- 'scripts/check-i18n.sh'
- 'scripts/check-legacy-frontend-paths.sh'
- 'scripts/legacy-frontend-paths-allowlist.txt'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'package.json'
- '.npmrc'
- '.github/workflows/test.yml'
- '.github/actions/**'
backend:
- 'crates/api-types/**'
- 'crates/db/**'
- 'crates/deployment/**'
- 'crates/executors/**'
- 'crates/git/**'
- 'crates/local-deployment/**'
- 'crates/review/**'
- 'crates/server/**'
- 'crates/services/**'
- 'crates/utils/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'shared/**'
- 'scripts/prepare-db.js'
- 'pnpm-lock.yaml'
- 'package.json'
- 'rustfmt.toml'
- 'rust-toolchain.toml'
- '.cargo/**'
- '.github/workflows/test.yml'
- '.github/actions/**'
backend-remote:
- 'crates/remote/**'
- 'crates/api-types/**'
- 'crates/utils/**'
- 'Cargo.toml'
- 'Cargo.lock'
- 'shared/**'
- 'rust-toolchain.toml'
- '.cargo/**'
- 'pnpm-lock.yaml'
- 'package.json'
- '.github/workflows/test.yml'
- '.github/actions/**'
frontend-checks:
needs: changes
if: always() && (needs.changes.outputs.frontend == 'true' || needs.changes.result == 'skipped')
runs-on: *runner_label
steps:
- uses: actions/checkout@v6
- name: Setup Node
uses: ./.github/actions/setup-node
- name: Install dependencies
run: pnpm install
- name: Run frontend checks
env:
NODE_OPTIONS: --max-old-space-size=8192
run: |
npx concurrently \
--kill-others-on-fail \
--names "local:lint,local:fmt,local:build,remote:fmt,remote:build,ui:check,ui:lint,ui:fmt,core:check,core:fmt,i18n,legacy" \
--timings \
"cd packages/local-web && npm run lint" \
"cd packages/local-web && npm run format:check" \
"cd packages/local-web && npm run build" \
"cd packages/remote-web && npm run format:check" \
"cd packages/remote-web && npm run build" \
"cd packages/ui && npm run check" \
"cd packages/ui && npm run lint" \
"cd packages/ui && npm run format:check" \
"cd packages/web-core && npm run check" \
"cd packages/web-core && npm run format:check" \
"GITHUB_BASE_REF=${{ github.base_ref || 'main' }} ./scripts/check-i18n.sh" \
"./scripts/check-legacy-frontend-paths.sh"
backend-schema-checks:
needs: changes
if: always() && (needs.changes.outputs.backend == 'true' || needs.changes.result == 'skipped')
runs-on: *runner_label
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps:
- uses: actions/checkout@v6
- name: Setup Rust
uses: ./.github/actions/cargo-checks-common-setup
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
cache-key: backend-schema-checks-${{ runner.os }}-${{ runner.arch }}-${{ env.RUNNER_LABEL }}
setup-node: 'true'
setup-sqlx-cli: 'true'
- name: Check generated types
run: npm run generate-types:check
- name: Sqlx checks
run: npm run prepare-db:check
backend-remote-checks:
needs: changes
if: >-
always()
&& (needs.changes.outputs['backend-remote'] == 'true' || needs.changes.result == 'skipped')
&& (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
runs-on: *runner_label
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps:
- uses: actions/checkout@v6
- name: Setup Rust
uses: ./.github/actions/cargo-checks-common-setup
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
cache-key: backend-remote-checks-${{ runner.os }}-${{ runner.arch }}-${{ env.RUNNER_LABEL }}
setup-node: 'true'
setup-sqlx-cli: 'true'
- name: Setup SSH Agent for private dependencies
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.VK_PRIVATE_DEPLOY_KEY }}
- name: Check remote Cargo.lock is consistent
run: |
cargo metadata --locked --format-version 1 --manifest-path crates/remote/Cargo.toml > /dev/null
- name: Check generated types
run: npm run remote:generate-types:check
- name: Sqlx checks
run: npm run remote:prepare-db:check
backend-clippy:
needs: changes
if: always() && (needs.changes.outputs.backend == 'true' || needs.changes.result == 'skipped')
runs-on: *runner_label
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps:
- uses: actions/checkout@v6
- name: Setup Rust
uses: ./.github/actions/cargo-checks-common-setup
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
components: rustfmt, clippy
cache-key: backend-clippy-${{ runner.os }}-${{ runner.arch }}-${{ env.RUNNER_LABEL }}
- name: Run Clippy and fmt checks
run: |
set -x
cargo fmt --all -- --check & pid_fmt=$!
cargo clippy --workspace --all-targets --exclude vibe-kanban-tauri -- -D warnings
wait $pid_fmt
backend-test:
needs: changes
if: always() && (needs.changes.outputs.backend == 'true' || needs.changes.result == 'skipped')
runs-on: *runner_label
env:
SCCACHE_GHA_ENABLED: "true"
RUSTC_WRAPPER: "sccache"
steps:
- uses: actions/checkout@v6
- name: Setup Rust
uses: ./.github/actions/cargo-checks-common-setup
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
cache-key: backend-test-${{ runner.os }}-${{ runner.arch }}-${{ env.RUNNER_LABEL }}
- name: Install cargo-nextest
uses: taiki-e/install-action@nextest
- name: Run Cargo tests
run: cargo nextest run --workspace --exclude vibe-kanban-tauri

8
.gitignore vendored
View File

@@ -67,10 +67,10 @@ coverage/
.env
packages/**/dist
frontend/dist
build-npm-package-codesign.sh
npx-cli/bin/
npx-cli/dist
npx-cli/vibe-kanban-*
vibe-kanban-*.tgz
@@ -89,9 +89,3 @@ vibe-kanban-cloud/
# Vite temp files
*.timestamp-*.mjs
# Mobile testing (per-developer, do not commit)
*.ts.net.crt
*.ts.net.key
Caddyfile
Caddyfile.dev

View File

@@ -30,17 +30,17 @@ Do not manually edit shared/remote-types.ts, instead edit crates/remote/src/bin/
- Run dev (web app + backend with ports auto-assigned): `pnpm run dev`
- Backend (watch): `pnpm run backend:dev:watch`
- Web app (dev): `pnpm run local-web:dev`
- Type checks: `pnpm run check` (frontend + all backend Rust workspaces) and `pnpm run backend:check` (all backend Rust workspaces, including `crates/remote`)
- Type checks: `pnpm run check` (workspace) and `pnpm run backend:check` (Rust cargo check)
- Rust tests: `cargo test --workspace`
- Generate TS types from Rust: `pnpm run generate-types` (or `generate-types:check` in CI)
- Prepare SQLx (offline): `pnpm run prepare-db`
- Prepare SQLx (remote package, postgres): `pnpm run remote:prepare-db`
- Local NPX build: `pnpm run build:npx` then `pnpm pack` in `npx-cli/`
- Format code: `pnpm run format` (runs `cargo fmt` for all backend Rust workspaces + web-core/web Prettier)
- Lint: `pnpm run lint` (runs web/ui ESLint + `cargo clippy` for all backend Rust workspaces)
- Format code: `pnpm run format` (runs `cargo fmt` + web-core/web Prettier)
- Lint: `pnpm run lint` (runs web/ui ESLint + `cargo clippy`)
## Before Completing a Task
- Run `pnpm run format` to format all Rust workspaces and web code.
- Run `pnpm run format` to format all Rust and web code.
## Coding Style & Naming Conventions
- Rust: `rustfmt` enforced (`rustfmt.toml`); group imports by crate; snake_case modules, PascalCase types.
@@ -55,4 +55,3 @@ Do not manually edit shared/remote-types.ts, instead edit crates/remote/src/bin/
- Use `.env` for local overrides; never commit secrets. Key envs: `FRONTEND_PORT`, `BACKEND_PORT`, `HOST`
- Dev ports and assets are managed by `scripts/setup-dev-environment.js`.

View File

@@ -1,61 +0,0 @@
# Contributing & Change Control
## Change Control Policy
All changes to production code are governed by formal change control procedures. These procedures ensure that modifications are reviewed, approved, and deployed in a controlled manner.
## Code Review Requirements
A maintainer must review pull requests before they are merged into any production branch. No code changes shall be merged without explicit approval from a qualified reviewer.
## Pull Request Process
1. Create a feature or fix branch from the base branch.
2. Make changes and open a pull request.
3. Obtain the required review and approval from a maintainer.
4. All required CI checks must pass before merging.
5. Merge only after approval has been granted and CI checks have passed.
## Separation of Duties
Development, testing, and deployment of changes shall not be performed by a single individual without approval and oversight. All significant changes require independent review to ensure correctness, security, and alignment with project standards.
## Coding Practices
Contributors are expected to follow the project's coding standards throughout the development cycle. These standards cover code quality, style consistency, and security.
### Style & Formatting
- **Rust**: Code must be formatted with `rustfmt` (config in `rustfmt.toml`). Use `snake_case` for modules and functions, `PascalCase` for types. Group imports by crate.
- **TypeScript/React**: Code must pass ESLint and Prettier (2 spaces, single quotes, 80-column width). Use `PascalCase` for components, `camelCase` for variables and functions, and `kebab-case` for file names.
- Run `pnpm run format` before submitting a pull request.
- Run `pnpm run lint` to verify there are no linting errors.
### Code Quality
- Keep functions small and focused on a single responsibility.
- Write clear, self-documenting code. Add comments only where the logic is not self-evident.
- Do not introduce unnecessary abstractions or over-engineer solutions.
- Do not manually edit generated files (e.g., `shared/types.ts`). Modify the source and regenerate.
### Testing
- **Rust**: Add unit tests alongside code using `#[cfg(test)]`. Run `cargo test --workspace` to verify.
- **TypeScript**: Ensure `pnpm run check` and `pnpm run lint` pass. Include lightweight tests (e.g., Vitest) for new runtime logic.
- All CI checks must pass before a pull request can be merged.
### Security
- Never commit secrets, credentials, or API keys. Use `.env` for local configuration.
- Be mindful of common vulnerabilities (injection, XSS, insecure deserialization) when writing code that handles user input or external data.
- Report security issues privately to the maintainers rather than opening a public issue.
### Commit Messages
- Use clear, descriptive commit messages that explain the _why_ behind a change.
- Prefix with a conventional type where appropriate (e.g., `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`).
- Keep the subject line under 72 characters. Use the body for additional context if needed.
## Scope
These procedures apply to all production branches in this repository.

View File

@@ -1,15 +0,0 @@
localhost:3001, relay.localhost:3001, *.relay.localhost:3001 {
tls internal
@relay host relay.localhost *.relay.localhost
handle @relay {
reverse_proxy 127.0.0.1:8082
}
@app expression `{http.request.host} == "localhost:3001" || {http.request.host} == "localhost"`
handle @app {
reverse_proxy 127.0.0.1:3000
}
respond "not found" 404
}

4586
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,47 +2,30 @@
resolver = "3"
members = [
"crates/api-types",
"crates/relay-client",
"crates/server",
"crates/relay-types",
"crates/trusted-key-auth",
"crates/mcp",
"crates/db",
"crates/executors",
"crates/services",
"crates/worktree-manager",
"crates/workspace-manager",
"crates/relay-control",
"crates/relay-protocol",
"crates/relay-ws",
"crates/ws-bridge",
"crates/relay-hosts",
"crates/client-info",
"crates/remote-info",
"crates/utils",
"crates/git",
"crates/git-host",
"crates/local-deployment",
"crates/deployment",
"crates/review",
"crates/embedded-ssh",
"crates/desktop-bridge",
"crates/tauri-app",
"crates/preview-proxy",
"crates/relay-tunnel-core",
"crates/relay-webrtc",
]
exclude = ["crates/remote", "crates/relay-tunnel"]
exclude = ["crates/remote"]
[workspace.dependencies]
tokio = { version = "1.0", features = ["full"] }
axum = { version = "0.8.4", features = ["macros", "multipart", "ws"] }
tower-http = { version = "0.5", features = ["cors", "request-id", "trace", "fs", "validate-request", "compression-gzip", "compression-br"] }
tower-http = { version = "0.5", features = ["cors", "request-id", "trace", "fs", "validate-request"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = { version = "1.0", features = ["preserve_order"] }
anyhow = "1.0"
git2 = { version = "0.20.3", default-features = false }
reqwest = { version = "0.13", default-features = false, features = ["json", "query", "stream", "rustls"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "stream", "rustls-tls-webpki-roots-no-provider"] }
rustls = { version = "0.23", default-features = false, features = ["aws_lc_rs", "std", "tls12"] }
thiserror = "2.0.12"
tracing = "0.1.43"
@@ -51,10 +34,13 @@ ts-rs = { git = "https://github.com/xazukx/ts-rs.git", branch = "use-ts-enum", f
schemars = { version = "1.0.4", features = ["derive", "chrono04", "uuid1", "preserve_order"] }
serde_with = "3"
async-trait = "0.1"
aws-lc-sys = "=0.37.0"
aws-lc-rs = "=1.16.0"
[profile.release]
debug = 1
split-debuginfo = "packed"
strip = true
# Patches for codex-core's forked tokio-tungstenite with proxy support
[patch.crates-io]
tokio-tungstenite = { git = "https://github.com/openai-oss-forks/tokio-tungstenite", rev = "132f5b39c862e3a970f731d709608b3e6276d5f6" }
tungstenite = { git = "https://github.com/openai-oss-forks/tungstenite-rs", rev = "9200079d3b54a1ff51072e24d81fd354f085156f" }

View File

@@ -1,152 +1,82 @@
# syntax=docker/dockerfile:1.6
# Build stage
FROM node:24-alpine AS builder
FROM node:24-alpine AS fe-builder
# Install build dependencies
RUN apk add --no-cache \
curl \
build-base \
perl \
llvm-dev \
clang-dev
ARG POSTHOG_API_KEY=""
ARG POSTHOG_API_ENDPOINT=""
# Allow linking libclang on musl
ENV RUSTFLAGS="-C target-feature=-crt-static"
# Install Rust
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
ENV PATH="/root/.cargo/bin:${PATH}"
ARG POSTHOG_API_KEY
ARG POSTHOG_API_ENDPOINT
ENV VITE_PUBLIC_POSTHOG_KEY=$POSTHOG_API_KEY
ENV VITE_PUBLIC_POSTHOG_HOST=$POSTHOG_API_ENDPOINT
# Set working directory
WORKDIR /app
ENV PNPM_HOME=/pnpm
ENV PATH=${PNPM_HOME}:${PATH}
ENV VITE_PUBLIC_POSTHOG_KEY=${POSTHOG_API_KEY}
ENV VITE_PUBLIC_POSTHOG_HOST=${POSTHOG_API_ENDPOINT}
ENV NODE_OPTIONS=--max-old-space-size=4096
# Copy package files for dependency caching
COPY package*.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/local-web/package*.json ./packages/local-web/
COPY npx-cli/package*.json ./npx-cli/
RUN corepack enable
RUN pnpm config set store-dir /pnpm/store
# Install pnpm and dependencies
RUN npm install -g pnpm && pnpm install
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
COPY packages/local-web/package.json packages/local-web/package.json
COPY packages/ui/package.json packages/ui/package.json
COPY packages/web-core/package.json packages/web-core/package.json
# Copy source code
COPY . .
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install --frozen-lockfile
# Build application
RUN npm run generate-types
RUN cd packages/local-web && pnpm run build
RUN cargo build --release --bin server
COPY packages/local-web/ packages/local-web/
COPY packages/public/ packages/public/
COPY packages/ui/ packages/ui/
COPY packages/web-core/ packages/web-core/
COPY shared/ shared/
# Runtime stage
FROM alpine:latest AS runtime
RUN pnpm -C packages/local-web build
FROM rust:1.93-slim-bookworm AS builder
ARG POSTHOG_API_KEY=""
ARG POSTHOG_API_ENDPOINT=""
ARG SENTRY_DSN=""
ARG VK_SHARED_API_BASE=""
ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse
ENV CARGO_NET_GIT_FETCH_WITH_CLI=true
ENV CARGO_TARGET_DIR=/app/target
ENV POSTHOG_API_KEY=${POSTHOG_API_KEY}
ENV POSTHOG_API_ENDPOINT=${POSTHOG_API_ENDPOINT}
ENV SENTRY_DSN=${SENTRY_DSN}
ENV VK_SHARED_API_BASE=${VK_SHARED_API_BASE}
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
# Install runtime dependencies
RUN apk add --no-cache \
ca-certificates \
git \
libclang-dev \
libssl-dev \
pkg-config \
&& rm -rf /var/lib/apt/lists/*
COPY rust-toolchain.toml ./
RUN cargo --version >/dev/null
COPY Cargo.toml Cargo.lock ./
COPY crates/api-types/Cargo.toml crates/api-types/Cargo.toml
COPY crates/db/Cargo.toml crates/db/Cargo.toml
COPY crates/deployment/Cargo.toml crates/deployment/Cargo.toml
COPY crates/executors/Cargo.toml crates/executors/Cargo.toml
COPY crates/git/Cargo.toml crates/git/Cargo.toml
COPY crates/git-host/Cargo.toml crates/git-host/Cargo.toml
COPY crates/local-deployment/Cargo.toml crates/local-deployment/Cargo.toml
COPY crates/mcp/Cargo.toml crates/mcp/Cargo.toml
COPY crates/relay-control/Cargo.toml crates/relay-control/Cargo.toml
COPY crates/relay-hosts/Cargo.toml crates/relay-hosts/Cargo.toml
COPY crates/relay-protocol/Cargo.toml crates/relay-protocol/Cargo.toml
COPY crates/relay-tunnel-core/Cargo.toml crates/relay-tunnel-core/Cargo.toml
COPY crates/relay-webrtc/Cargo.toml crates/relay-webrtc/Cargo.toml
COPY crates/relay-ws/Cargo.toml crates/relay-ws/Cargo.toml
COPY crates/review/Cargo.toml crates/review/Cargo.toml
COPY crates/server/Cargo.toml crates/server/Cargo.toml
COPY crates/server-info/Cargo.toml crates/server-info/Cargo.toml
COPY crates/services/Cargo.toml crates/services/Cargo.toml
COPY crates/tauri-app/Cargo.toml crates/tauri-app/Cargo.toml
COPY crates/trusted-key-auth/Cargo.toml crates/trusted-key-auth/Cargo.toml
COPY crates/utils/Cargo.toml crates/utils/Cargo.toml
COPY crates/workspace-manager/Cargo.toml crates/workspace-manager/Cargo.toml
COPY crates/worktree-manager/Cargo.toml crates/worktree-manager/Cargo.toml
COPY crates/ws-bridge/Cargo.toml crates/ws-bridge/Cargo.toml
COPY crates/api-types/ crates/api-types/
COPY crates/db/ crates/db/
COPY crates/deployment/ crates/deployment/
COPY crates/executors/ crates/executors/
COPY crates/git/ crates/git/
COPY crates/git-host/ crates/git-host/
COPY crates/local-deployment/ crates/local-deployment/
COPY crates/mcp/ crates/mcp/
COPY crates/relay-control/ crates/relay-control/
COPY crates/relay-hosts/ crates/relay-hosts/
COPY crates/relay-protocol/ crates/relay-protocol/
COPY crates/relay-tunnel-core/ crates/relay-tunnel-core/
COPY crates/relay-webrtc/ crates/relay-webrtc/
COPY crates/relay-ws/ crates/relay-ws/
COPY crates/review/ crates/review/
COPY crates/server/ crates/server/
COPY crates/server-info/ crates/server-info/
COPY crates/services/ crates/services/
COPY crates/trusted-key-auth/ crates/trusted-key-auth/
COPY crates/utils/ crates/utils/
COPY crates/workspace-manager/ crates/workspace-manager/
COPY crates/worktree-manager/ crates/worktree-manager/
COPY crates/ws-bridge/ crates/ws-bridge/
COPY assets/ assets/
COPY --from=fe-builder /app/packages/local-web/dist packages/local-web/dist
RUN --mount=type=cache,id=cargo-registry,target=/usr/local/cargo/registry \
--mount=type=cache,id=cargo-git,target=/usr/local/cargo/git \
--mount=type=cache,id=workspace-target,target=/app/target \
cargo build --locked --release --bin server \
&& cp /app/target/release/server /usr/local/bin/server
FROM debian:bookworm-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates \
git \
openssh-client \
tini \
wget \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --system --create-home --uid 10001 appuser
libgcc \
wget
WORKDIR /repos
# Create app user for security
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
COPY --from=builder /usr/local/bin/server /usr/local/bin/server
# Copy binary from builder
COPY --from=builder /app/target/release/server /usr/local/bin/server
RUN mkdir -p /repos \
&& chown -R appuser:appuser /repos
# Create repos directory and set permissions
RUN mkdir -p /repos && \
chown -R appuser:appgroup /repos
# Switch to non-root user
USER appuser
# Set runtime environment
ENV HOST=0.0.0.0
ENV PORT=3000
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD ["/bin/sh", "-c", "wget --spider -q http://127.0.0.1:${PORT:-3000}/health"]
# Set working directory
WORKDIR /repos
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/server"]
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider "http://${HOST:-localhost}:${PORT:-3000}" || exit 1
# Run the application
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["server"]

View File

@@ -1,9 +1,9 @@
<p align="center">
<a href="https://vibekanban.com">
<picture>
<source srcset="packages/public/vibe-kanban-logo-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/public/vibe-kanban-logo.svg" media="(prefers-color-scheme: light)">
<img src="packages/public/vibe-kanban-logo.svg" alt="Vibe Kanban Logo">
<source srcset="packages/local-web/public/vibe-kanban-logo-dark.svg" media="(prefers-color-scheme: dark)">
<source srcset="packages/local-web/public/vibe-kanban-logo.svg" media="(prefers-color-scheme: light)">
<img src="packages/local-web/public/vibe-kanban-logo.svg" alt="Vibe Kanban Logo">
</picture>
</a>
</p>
@@ -19,33 +19,24 @@
<a href="https://jobs.polymer.co/vibe-kanban?source=github"><strong>We're hiring!</strong></a>
</h1>
![](packages/public/vibe-kanban-screenshot-overview.png)
![](packages/local-web/public/vibe-kanban-screenshot-overview.png)
## Overview
In a world where software engineers spend most of their time planning and reviewing coding agents, the most impactful way to ship more is to get faster at planning and review.
AI coding agents are increasingly writing the world's code and human engineers now spend the majority of their time planning, reviewing, and orchestrating tasks. Vibe Kanban streamlines this process, enabling you to:
Vibe Kanban is built for this. Use kanban issues to plan work, either privately or with your team. When you're ready to begin, create workspaces where coding agents can execute.
- **Plan with kanban issues** — create, prioritise, and assign issues on a kanban board
- **Run coding agents in workspaces** — each workspace gives an agent a branch, a terminal, and a dev server
- **Review diffs and leave inline comments** — send feedback directly to the agent without leaving the UI
- **Preview your app** — built-in browser with devtools, inspect mode, and device emulation
- **Switch between 10+ coding agents** — Claude Code, Codex, Gemini CLI, GitHub Copilot, Amp, Cursor, OpenCode, Droid, CCR, and Qwen Code
- **Create pull requests and merge** — open PRs with AI-generated descriptions, review on GitHub, and merge
![](packages/public/vibe-kanban-screenshot-workspace.png)
One command. Describe the work, review the diff, ship it.
```bash
npx vibe-kanban
```
- Easily switch between different coding agents
- Orchestrate the execution of multiple coding agents in parallel or in sequence
- Quickly review work and start dev servers
- Track the status of tasks that your coding agents are working on
- Centralise configuration of coding agent MCP configs
- Open projects remotely via SSH when running Vibe Kanban on a remote server
You can watch a video overview [here](https://youtu.be/TFT3KnZOOAk).
## Installation
Make sure you have authenticated with your favourite coding agent. A full list of supported coding agents can be found in the [docs](https://vibekanban.com/docs/supported-coding-agents). Then in your terminal run:
Make sure you have authenticated with your favourite coding agent. A full list of supported coding agents can be found in the [docs](https://vibekanban.com/docs). Then in your terminal run:
```bash
npx vibe-kanban
@@ -53,12 +44,12 @@ npx vibe-kanban
## Documentation
Head to the [website](https://vibekanban.com/docs) for the latest documentation and user guides.
Please head to the [website](https://vibekanban.com/docs) for the latest documentation and user guides.
## Self-Hosting
Want to host your own Vibe Kanban Cloud instance? See our [self-hosting guide](https://vibekanban.com/docs/self-hosting/deploy-docker).
Want to host your own Vibe Kanban Cloud instance? See our [self-hosting guide](https://vibekanban.com/docs/self-hosting).
## Support
We use [GitHub Discussions](https://github.com/BloopAI/vibe-kanban/discussions) for feature requests. Please open a discussion to create a feature request. For bugs please open an issue on this repo.
@@ -124,9 +115,6 @@ The following environment variables can be configured at build time or runtime:
| `MCP_PORT` | Runtime | Value of `BACKEND_PORT` | MCP server connection port |
| `DISABLE_WORKTREE_CLEANUP` | Runtime | Not set | Disable all git worktree cleanup including orphan and expired workspace cleanup (for debugging) |
| `VK_ALLOWED_ORIGINS` | Runtime | Not set | Comma-separated list of origins that are allowed to make backend API requests (e.g., `https://my-vibekanban-frontend.com`) |
| `VK_SHARED_API_BASE` | Runtime | Not set | Base URL for the remote/cloud API used by the local desktop app |
| `VK_SHARED_RELAY_API_BASE` | Runtime | Not set | Base URL for the relay API used by tunnel-mode connections |
| `VK_TUNNEL` | Runtime | Not set | Enable relay tunnel mode when set (requires relay API base URL) |
**Build-time variables** must be set when running `pnpm run build`. **Runtime variables** are read when the application starts.
@@ -159,4 +147,4 @@ When running Vibe Kanban on a remote server (e.g., via systemctl, Docker, or clo
When configured, the "Open in VSCode" buttons will generate URLs like `vscode://vscode-remote/ssh-remote+user@host/path` that open your local editor and connect to the remote server.
See the [documentation](https://vibekanban.com/docs/settings/general) for detailed setup instructions.
See the [documentation](https://vibekanban.com/docs/configuration-customisation/global-settings#remote-ssh-configuration) for detailed setup instructions.

Binary file not shown.

View File

@@ -1,6 +1,6 @@
[package]
name = "api-types"
version = "0.1.44"
version = "0.1.19-pthis.3"
edition = "2024"
[dependencies]

View File

@@ -5,6 +5,7 @@ use uuid::Uuid;
/// An attachment links a blob to an issue or comment.
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Attachment {
pub id: Uuid,
pub blob_id: Uuid,
@@ -16,6 +17,7 @@ pub struct Attachment {
/// An attachment with its associated blob data (for API responses).
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct AttachmentWithBlob {
pub id: Uuid,
pub blob_id: Uuid,
@@ -50,6 +52,7 @@ pub struct ListAttachmentsResponse {
/// Response containing a presigned URL for an attachment file or thumbnail.
#[derive(Debug, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct AttachmentUrlResponse {
pub url: String,
}

View File

@@ -1,26 +0,0 @@
use chrono::{DateTime, Duration, Utc};
use serde::Serialize;
use uuid::Uuid;
#[derive(Debug, Clone, sqlx::FromRow, Serialize)]
pub struct AuthSession {
pub id: Uuid,
pub user_id: Uuid,
pub created_at: DateTime<Utc>,
pub last_used_at: Option<DateTime<Utc>>,
pub revoked_at: Option<DateTime<Utc>>,
pub refresh_token_id: Option<Uuid>,
pub refresh_token_issued_at: Option<DateTime<Utc>>,
pub previous_refresh_token_id: Option<Uuid>,
pub previous_refresh_token_grace_expires_at: Option<DateTime<Utc>>,
}
impl AuthSession {
pub fn last_activity_at(&self) -> DateTime<Utc> {
self.last_used_at.unwrap_or(self.created_at)
}
pub fn inactivity_duration(&self, now: DateTime<Utc>) -> Duration {
now.signed_duration_since(self.last_activity_at())
}
}

View File

@@ -4,6 +4,7 @@ use ts_rs::TS;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[ts(export)]
pub struct Blob {
pub id: Uuid,
pub project_id: Uuid,

View File

@@ -1,11 +0,0 @@
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct ExportRequest {
pub organization_id: Uuid,
/// If empty, exports all projects in the organization.
pub project_ids: Vec<Uuid>,
pub include_attachments: bool,
}

View File

@@ -17,7 +17,7 @@ pub enum IssuePriority {
Low,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS, sqlx::FromRow)]
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct Issue {
pub id: Uuid,
pub project_id: Uuid,
@@ -39,23 +39,6 @@ pub struct Issue {
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
pub enum IssueSortField {
SortOrder,
Priority,
CreatedAt,
UpdatedAt,
Title,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
pub enum SortDirection {
Asc,
Desc,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct CreateIssueRequest {
/// Optional client-generated ID. If not provided, server generates one.
@@ -146,59 +129,12 @@ pub struct UpdateIssueRequest {
pub extension_metadata: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[derive(Debug, Clone, Deserialize)]
pub struct ListIssuesQuery {
pub project_id: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct SearchIssuesRequest {
pub project_id: Uuid,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub status_id: Option<Uuid>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub status_ids: Option<Vec<Uuid>>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub priority: Option<IssuePriority>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub parent_issue_id: Option<Uuid>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub search: Option<String>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub simple_id: Option<String>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub assignee_user_id: Option<Uuid>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub tag_id: Option<Uuid>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub tag_ids: Option<Vec<Uuid>>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_field: Option<IssueSortField>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub sort_direction: Option<SortDirection>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<i32>,
#[ts(optional)]
#[serde(skip_serializing_if = "Option::is_none")]
pub offset: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct ListIssuesResponse {
pub issues: Vec<Issue>,
pub total_count: usize,
pub limit: usize,
pub offset: usize,
}

View File

@@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;
use uuid::Uuid;
use crate::some_if_present;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct IssueAssignee {
pub id: Uuid,
@@ -21,6 +23,12 @@ pub struct CreateIssueAssigneeRequest {
pub user_id: Uuid,
}
#[derive(Debug, Clone, Deserialize, TS)]
pub struct UpdateIssueAssigneeRequest {
#[serde(default, deserialize_with = "some_if_present")]
pub user_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ListIssueAssigneesQuery {
pub issue_id: Uuid,

View File

@@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;
use uuid::Uuid;
use crate::some_if_present;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct IssueFollower {
pub id: Uuid,
@@ -19,6 +21,12 @@ pub struct CreateIssueFollowerRequest {
pub user_id: Uuid,
}
#[derive(Debug, Clone, Deserialize, TS)]
pub struct UpdateIssueFollowerRequest {
#[serde(default, deserialize_with = "some_if_present")]
pub user_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ListIssueFollowersQuery {
pub issue_id: Uuid,

View File

@@ -5,6 +5,8 @@ use sqlx::Type;
use ts_rs::TS;
use uuid::Uuid;
use crate::some_if_present;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, TS, JsonSchema)]
#[sqlx(type_name = "issue_relationship_type", rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
@@ -34,6 +36,14 @@ pub struct CreateIssueRelationshipRequest {
pub relationship_type: IssueRelationshipType,
}
#[derive(Debug, Clone, Deserialize, TS)]
pub struct UpdateIssueRelationshipRequest {
#[serde(default, deserialize_with = "some_if_present")]
pub related_issue_id: Option<Uuid>,
#[serde(default, deserialize_with = "some_if_present")]
pub relationship_type: Option<IssueRelationshipType>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ListIssueRelationshipsQuery {
pub issue_id: Uuid,

View File

@@ -2,6 +2,8 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;
use uuid::Uuid;
use crate::some_if_present;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct IssueTag {
pub id: Uuid,
@@ -19,6 +21,12 @@ pub struct CreateIssueTagRequest {
pub tag_id: Uuid,
}
#[derive(Debug, Clone, Deserialize, TS)]
pub struct UpdateIssueTagRequest {
#[serde(default, deserialize_with = "some_if_present")]
pub tag_id: Option<Uuid>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ListIssueTagsQuery {
pub issue_id: Uuid,

View File

@@ -8,9 +8,7 @@
use serde::{Deserialize, Deserializer};
pub mod attachment;
pub mod auth;
pub mod blob;
pub mod export;
pub mod issue;
pub mod issue_assignee;
pub mod issue_comment;
@@ -18,6 +16,7 @@ pub mod issue_comment_reaction;
pub mod issue_follower;
pub mod issue_relationship;
pub mod issue_tag;
pub mod migration;
pub mod notification;
pub mod oauth;
pub mod organization_member;
@@ -33,9 +32,7 @@ pub mod workspace;
pub mod workspaces;
pub use attachment::*;
pub use auth::*;
pub use blob::*;
pub use export::*;
pub use issue::*;
pub use issue_assignee::*;
pub use issue_comment::*;
@@ -43,6 +40,7 @@ pub use issue_comment_reaction::*;
pub use issue_follower::*;
pub use issue_relationship::*;
pub use issue_tag::*;
pub use migration::*;
pub use notification::*;
pub use oauth::*;
pub use organization_member::*;

View File

@@ -0,0 +1,50 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigrateProjectRequest {
pub organization_id: Uuid,
pub name: String,
pub color: String,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigrateIssueRequest {
pub project_id: Uuid,
pub status_name: String,
pub title: String,
pub description: Option<String>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigratePullRequestRequest {
pub url: String,
pub number: i32,
pub status: String,
pub merged_at: Option<DateTime<Utc>>,
pub merge_commit_sha: Option<String>,
pub target_branch_name: String,
pub issue_id: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MigrateWorkspaceRequest {
pub project_id: Uuid,
pub issue_id: Option<Uuid>,
pub local_workspace_id: Uuid,
pub archived: bool,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BulkMigrateRequest<T> {
pub items: Vec<T>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BulkMigrateResponse {
pub ids: Vec<Uuid>,
}

View File

@@ -1,34 +1,18 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use sqlx::Type;
use ts_rs::TS;
use uuid::Uuid;
use crate::{IssuePriority, some_if_present};
use crate::some_if_present;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, TS)]
#[serde(rename_all = "snake_case")]
#[sqlx(type_name = "notification_type", rename_all = "snake_case")]
pub enum NotificationType {
IssueCommentAdded,
IssueStatusChanged,
IssueAssigneeChanged,
IssuePriorityChanged,
IssueUnassigned,
IssueCommentReaction,
IssueDeleted,
IssueTitleChanged,
IssueDescriptionChanged,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "snake_case")]
pub enum NotificationGroupKind {
Single,
IssueChanges,
StatusChanges,
Comments,
Reactions,
IssueDeleted,
}
@@ -38,7 +22,7 @@ pub struct Notification {
pub organization_id: Uuid,
pub user_id: Uuid,
pub notification_type: NotificationType,
pub payload: NotificationPayload,
pub payload: Value,
pub issue_id: Option<Uuid>,
pub comment_id: Option<Uuid>,
pub seen: bool,
@@ -46,38 +30,14 @@ pub struct Notification {
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
pub struct NotificationPayload {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deeplink_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issue_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issue_simple_id: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub issue_title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub actor_user_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub comment_preview: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub old_status_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub new_status_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub old_status_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub new_status_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub new_title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub old_priority: Option<IssuePriority>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub new_priority: Option<IssuePriority>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assignee_user_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub emoji: Option<String>,
#[derive(Debug, Clone, Deserialize, TS)]
pub struct CreateNotificationRequest {
/// Optional client-generated ID. If not provided, server generates one.
/// Using client-generated IDs enables stable optimistic updates.
#[ts(optional)]
pub id: Option<Uuid>,
pub organization_id: Uuid,
pub seen: bool,
}
#[derive(Debug, Clone, Deserialize, TS)]
@@ -85,3 +45,13 @@ pub struct UpdateNotificationRequest {
#[serde(default, deserialize_with = "some_if_present")]
pub seen: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ListNotificationsQuery {
pub organization_id: Uuid,
}
#[derive(Debug, Clone, Serialize, TS)]
pub struct ListNotificationsResponse {
pub notifications: Vec<Notification>,
}

View File

@@ -28,24 +28,6 @@ pub struct HandoffRedeemResponse {
pub refresh_token: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, TS)]
pub struct LocalLoginRequest {
pub email: String,
pub password: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, TS)]
pub struct LocalLoginResponse {
pub access_token: String,
pub refresh_token: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, TS)]
pub struct AuthMethodsResponse {
pub local_auth_enabled: bool,
pub oauth_providers: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, TS)]
pub struct TokenRefreshRequest {
pub refresh_token: String,
@@ -78,10 +60,7 @@ pub struct ProfileResponse {
#[serde(tag = "status", rename_all = "lowercase")]
pub enum LoginStatus {
LoggedOut,
LoggedIn {
#[serde(skip_serializing_if = "Option::is_none")]
profile: Option<ProfileResponse>,
},
LoggedIn { profile: ProfileResponse },
}
#[derive(Debug, Serialize, Deserialize, Clone, TS)]

View File

@@ -23,21 +23,12 @@ pub struct PullRequest {
pub merged_at: Option<DateTime<Utc>>,
pub merge_commit_sha: Option<String>,
pub target_branch_name: String,
pub project_id: Uuid,
#[deprecated(note = "use pull_request_issues join table instead")]
pub issue_id: Uuid,
pub workspace_id: Option<Uuid>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct PullRequestIssue {
pub id: Uuid,
pub pull_request_id: Uuid,
pub issue_id: Uuid,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ListPullRequestsQuery {
pub issue_id: Uuid,
@@ -47,35 +38,3 @@ pub struct ListPullRequestsQuery {
pub struct ListPullRequestsResponse {
pub pull_requests: Vec<PullRequest>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct ListPullRequestIssuesResponse {
pub pull_request_issues: Vec<PullRequestIssue>,
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
pub struct CreatePullRequestIssueRequest {
/// Optional client-generated ID. If not provided, server generates one.
/// Using client-generated IDs enables stable optimistic updates.
#[ts(optional)]
pub id: Option<Uuid>,
pub issue_id: Uuid,
pub url: String,
pub number: i32,
pub status: PullRequestStatus,
pub merged_at: Option<DateTime<Utc>>,
pub merge_commit_sha: Option<String>,
pub target_branch_name: String,
}
/// Request to update a PR status on the remote server.
#[derive(Debug, Deserialize, Serialize)]
pub struct UpdatePullRequestApiRequest {
pub url: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<PullRequestStatus>,
#[serde(skip_serializing_if = "Option::is_none")]
pub merged_at: Option<Option<DateTime<Utc>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub merge_commit_sha: Option<Option<String>>,
}

View File

@@ -1,4 +0,0 @@
[package]
name = "client-info"
version = "0.1.44"
edition = "2024"

View File

@@ -1,43 +0,0 @@
use std::{net::SocketAddr, sync::OnceLock};
/// Runtime information about the local server.
#[derive(Clone)]
pub struct ClientInfo {
server_addr: OnceLock<SocketAddr>,
preview_proxy_port: OnceLock<u16>,
}
impl Default for ClientInfo {
fn default() -> Self {
Self::new()
}
}
impl ClientInfo {
pub fn new() -> Self {
Self {
server_addr: OnceLock::new(),
preview_proxy_port: OnceLock::new(),
}
}
pub fn set_server_addr(&self, addr: SocketAddr) -> Result<(), String> {
self.server_addr
.set(addr)
.map_err(|_| "server address already set".to_string())
}
pub fn get_server_addr(&self) -> Option<SocketAddr> {
self.server_addr.get().copied()
}
pub fn set_preview_proxy_port(&self, port: u16) -> Result<(), String> {
self.preview_proxy_port
.set(port)
.map_err(|_| "preview proxy port already set".to_string())
}
pub fn get_preview_proxy_port(&self) -> Option<u16> {
self.preview_proxy_port.get().copied()
}
}

View File

@@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n execution_process_id as \"execution_process_id!: Uuid\",\n agent_session_id,\n agent_message_id,\n prompt,\n summary,\n seen as \"seen!: bool\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM coding_agent_turns\n WHERE agent_session_id = ?\n ORDER BY updated_at DESC\n LIMIT 1",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "execution_process_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "agent_session_id",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "agent_message_id",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "prompt",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "summary",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "seen!: bool",
"ordinal": 6,
"type_info": "Integer"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
true,
true,
true,
true,
false,
false,
false
]
},
"hash": "04e5a05c7cad438d39c4c8590410889ab1eefa7376d474a10c119d3f4d9143c7"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!: Uuid\",\n task_id AS \"task_id: Uuid\",\n container_ref,\n branch,\n setup_completed_at AS \"setup_completed_at: DateTime<Utc>\",\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n archived AS \"archived!: bool\",\n pinned AS \"pinned!: bool\",\n name,\n worktree_deleted AS \"worktree_deleted!: bool\"\n FROM workspaces\n WHERE id = $1",
"query": "SELECT id AS \"id!: Uuid\",\n task_id AS \"task_id: Uuid\",\n container_ref,\n branch,\n agent_working_dir,\n setup_completed_at AS \"setup_completed_at: DateTime<Utc>\",\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n archived AS \"archived!: bool\",\n pinned AS \"pinned!: bool\",\n name\n FROM workspaces\n WHERE id = $1",
"describe": {
"columns": [
{
@@ -24,39 +24,39 @@
"type_info": "Text"
},
{
"name": "setup_completed_at: DateTime<Utc>",
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"name": "setup_completed_at: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"name": "created_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "archived!: bool",
"name": "updated_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Integer"
"type_info": "Text"
},
{
"name": "pinned!: bool",
"name": "archived!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "name",
"name": "pinned!: bool",
"ordinal": 9,
"type_info": "Text"
"type_info": "Integer"
},
{
"name": "worktree_deleted!: bool",
"name": "name",
"ordinal": 10,
"type_info": "Bool"
"type_info": "Text"
}
],
"parameters": {
@@ -68,13 +68,13 @@
true,
false,
true,
false,
false,
false,
false,
true,
false
false,
false,
false,
false,
true
]
},
"hash": "766fa107de23b7e6c579223b083d916e252d422e2908c27f6718fcbd851de2c1"
"hash": "08ecee6680d027f444d684111a7c0c84a4ed5cab0c0d015e09ab2cf5b84271f0"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE migration_state\n SET status = 'migrated',\n remote_id = $3,\n error_message = NULL,\n updated_at = datetime('now', 'subsec')\n WHERE entity_type = $1 AND local_id = $2",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "0a805c219c9028b2677bd94ccabd47916e60d26c1cede27e467f0ae91f6639ab"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE migration_state\n SET status = 'pending',\n error_message = NULL,\n updated_at = datetime('now', 'subsec')\n WHERE status = 'failed'",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "0ac0d0f3826330836e3fd1bf57c42777eb489ac41a650f9361e6b563fc69bf35"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "DELETE FROM pull_requests WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "0ee24d51e7557d9d6c9418a06156c03d7dd49dfe938194cd6951942dfd8f179d"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE migration_state\n SET status = 'skipped',\n error_message = $3,\n updated_at = datetime('now', 'subsec')\n WHERE entity_type = $1 AND local_id = $2",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "0f90844fc62261ed140e02515ae464b940743113814507313c9fdc176000d1bf"
}

View File

@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n workspace_id as \"workspace_id!: Uuid\",\n repo_id as \"repo_id!: Uuid\",\n merge_type as \"merge_type!: MergeType\",\n merge_commit,\n pr_number,\n pr_url,\n pr_status as \"pr_status?: MergeStatus\",\n pr_merged_at as \"pr_merged_at?: DateTime<Utc>\",\n pr_merge_commit_sha,\n target_branch_name as \"target_branch_name!: String\",\n created_at as \"created_at!: DateTime<Utc>\"\n FROM merges\n WHERE workspace_id = $1 AND repo_id = $2\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id!: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "merge_type!: MergeType",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "merge_commit",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 5,
"type_info": "Integer"
},
{
"name": "pr_url",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "pr_status?: MergeStatus",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "pr_merged_at?: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "pr_merge_commit_sha",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "target_branch_name!: String",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 2
},
"nullable": [
true,
false,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "1085d1f8107c7e16fc2058ef610918760d8d420f0fca97adecd76d698f6f3a51"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT id as \"id!: Uuid\",\n file_path as \"file_path!\",\n original_name as \"original_name!\",\n mime_type,\n size_bytes as \"size_bytes!\",\n hash as \"hash!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM attachments\n WHERE id = $1",
"query": "SELECT id as \"id!: Uuid\",\n file_path as \"file_path!\",\n original_name as \"original_name!\",\n mime_type,\n size_bytes as \"size_bytes!\",\n hash as \"hash!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM images\n WHERE id = $1",
"describe": {
"columns": [
{
@@ -58,5 +58,5 @@
false
]
},
"hash": "dc5d0ad507cbd962235c9e85c3e43f34c7c38eb2e08ab7899073010a6e77b37d"
"hash": "1280290c78a1f55b3f0074ebcb61d855cab3e9be9ab0bc8c3f678adf4506b9cc"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT i.id as \"id!: Uuid\",\n i.file_path as \"file_path!\",\n i.original_name as \"original_name!\",\n i.mime_type,\n i.size_bytes as \"size_bytes!\",\n i.hash as \"hash!\",\n i.created_at as \"created_at!: DateTime<Utc>\",\n i.updated_at as \"updated_at!: DateTime<Utc>\"\n FROM attachments i\n LEFT JOIN workspace_attachments wa ON i.id = wa.attachment_id\n WHERE wa.workspace_id IS NULL",
"query": "SELECT i.id as \"id!: Uuid\",\n i.file_path as \"file_path!\",\n i.original_name as \"original_name!\",\n i.mime_type,\n i.size_bytes as \"size_bytes!\",\n i.hash as \"hash!\",\n i.created_at as \"created_at!: DateTime<Utc>\",\n i.updated_at as \"updated_at!: DateTime<Utc>\"\n FROM images i\n LEFT JOIN workspace_images wi ON i.id = wi.image_id\n WHERE wi.workspace_id IS NULL",
"describe": {
"columns": [
{
@@ -58,5 +58,5 @@
false
]
},
"hash": "df2f35912a8055dff6cb24c83ea67fc49b432f457961fa584c6a13389bfdcea5"
"hash": "144e0cfb4810f51fe65577077e2f57b08eb278f2a819420a242aad5ab5be7944"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT COUNT(1) as \"count!: i64\"\n FROM merges\n WHERE workspace_id = $1\n AND merge_type = 'pr'\n AND pr_status = 'open'",
"describe": {
"columns": [
{
"name": "count!: i64",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "167c1d13ee37ebe62cd2316feaf6b5354eb26d0a8fc16efb22827a5cde59a60e"
}

View File

@@ -1,86 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n id,\n workspace_id AS \"workspace_id: Uuid\",\n repo_id AS \"repo_id: Uuid\",\n pr_url,\n pr_number,\n pr_status AS \"pr_status: MergeStatus\",\n target_branch_name,\n merged_at AS \"merged_at: DateTime<Utc>\",\n merge_commit_sha,\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n synced_at AS \"synced_at: DateTime<Utc>\"\n FROM pull_requests\n WHERE synced_at IS NULL OR synced_at < updated_at",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "workspace_id: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "pr_url",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 4,
"type_info": "Integer"
},
{
"name": "pr_status: MergeStatus",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "target_branch_name",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "merged_at: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "merge_commit_sha",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 9,
"type_info": "Datetime"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 10,
"type_info": "Datetime"
},
{
"name": "synced_at: DateTime<Utc>",
"ordinal": 11,
"type_info": "Datetime"
}
],
"parameters": {
"Right": 0
},
"nullable": [
false,
true,
true,
false,
false,
false,
false,
true,
true,
false,
false,
true
]
},
"hash": "1af7a606a64d9662d6edc4fbeaae0d53a4d657a6e29b2561e820085a2d9e3348"
}

View File

@@ -1,20 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT COUNT(*) as \"count!: i64\"\n FROM execution_processes ep\n WHERE ep.session_id = $1\n AND ep.status = 'running'\n AND ep.run_reason = 'codingagent'",
"describe": {
"columns": [
{
"name": "count!: i64",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "1b186dc075846fc1f7270a942afbf82a88806ee6ababdb437ab5e97ddd2122da"
}

View File

@@ -0,0 +1,44 @@
{
"db_name": "SQLite",
"query": "SELECT s.id AS \"id!: Uuid\",\n s.workspace_id AS \"workspace_id!: Uuid\",\n s.executor,\n s.created_at AS \"created_at!: DateTime<Utc>\",\n s.updated_at AS \"updated_at!: DateTime<Utc>\"\n FROM sessions s\n LEFT JOIN (\n SELECT ep.session_id, MAX(ep.created_at) as last_used\n FROM execution_processes ep\n WHERE ep.run_reason != 'devserver' AND ep.dropped = FALSE\n GROUP BY ep.session_id\n ) latest_ep ON s.id = latest_ep.session_id\n WHERE s.workspace_id = $1\n ORDER BY COALESCE(latest_ep.last_used, s.created_at) DESC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "executor",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 4,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
true,
false,
false
]
},
"hash": "2462cbaa0c7bc7cc0d925b467bcc40ab4406977d1032e81371f7b2dda115f632"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT id as \"id!: Uuid\",\n file_path as \"file_path!\",\n original_name as \"original_name!\",\n mime_type,\n size_bytes as \"size_bytes!\",\n hash as \"hash!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM attachments\n WHERE hash = $1",
"query": "SELECT id as \"id!: Uuid\",\n file_path as \"file_path!\",\n original_name as \"original_name!\",\n mime_type,\n size_bytes as \"size_bytes!\",\n hash as \"hash!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM images\n WHERE hash = $1",
"describe": {
"columns": [
{
@@ -58,5 +58,5 @@
false
]
},
"hash": "04f17449e3e12785affab91e4eab308103491e34c022199b7b060e04fa8aed0f"
"hash": "2827716b6501cc3c44ec50ae1a3d90f759cc3940c8fe6ffe383d0f741e3a2a78"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE pull_requests SET pr_status = ?, merged_at = ?, merge_commit_sha = ?, updated_at = ?, synced_at = NULL WHERE pr_url = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "2948ec0606a39ee67bdbaf4f2cf82592448a1d91a66d7d89cf7204f71f706274"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT w.id as \"workspace_id!: Uuid\"\n FROM workspaces w\n WHERE w.container_ref = ?",
"describe": {
"columns": [
{
"name": "workspace_id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true
]
},
"hash": "29cc3aa8d0ad5deda94494402500a4125e29381d63f18ef083cc4da95e2c5db5"
}

View File

@@ -1,20 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT w.name AS \"name: String\"\n FROM workspaces w\n JOIN workspace_repos wr ON wr.workspace_id = w.id\n WHERE wr.repo_id = $1\n AND w.archived = FALSE",
"describe": {
"columns": [
{
"name": "name: String",
"ordinal": 0,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true
]
},
"hash": "2c71bf4dd5683e0dedf2341e52880ff2c0765659d3cf53d62faa54adc91071dd"
}

View File

@@ -1,86 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n id,\n workspace_id AS \"workspace_id: Uuid\",\n repo_id AS \"repo_id: Uuid\",\n pr_url,\n pr_number,\n pr_status AS \"pr_status: MergeStatus\",\n target_branch_name,\n merged_at AS \"merged_at: DateTime<Utc>\",\n merge_commit_sha,\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n synced_at AS \"synced_at: DateTime<Utc>\"\n FROM pull_requests\n WHERE workspace_id = $1 AND repo_id = $2\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "workspace_id: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "pr_url",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 4,
"type_info": "Integer"
},
{
"name": "pr_status: MergeStatus",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "target_branch_name",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "merged_at: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "merge_commit_sha",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 9,
"type_info": "Datetime"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 10,
"type_info": "Datetime"
},
{
"name": "synced_at: DateTime<Utc>",
"ordinal": 11,
"type_info": "Datetime"
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
true,
true,
false,
false,
false,
false,
true,
true,
false,
false,
true
]
},
"hash": "32c74a54fdd72a6b167583228f2c30944837b7cc29c9cc4acfbb4f047cea7118"
}

View File

@@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n entity_type as \"entity_type!: EntityType\",\n local_id as \"local_id!: Uuid\",\n remote_id as \"remote_id: Uuid\",\n status as \"status!: MigrationStatus\",\n error_message,\n attempt_count as \"attempt_count!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM migration_state\n WHERE entity_type = $1 AND status = 'pending'\n ORDER BY created_at ASC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "entity_type!: EntityType",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "local_id!: Uuid",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "remote_id: Uuid",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "status!: MigrationStatus",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "error_message",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "attempt_count!",
"ordinal": 6,
"type_info": "Integer"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
true,
false,
true,
false,
false,
false
]
},
"hash": "33f23656ba343bd75a88b0fadf2a4ba01eda330f9b549e625e27701e3b0b5a31"
}

View File

@@ -1,26 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT id as \"id!: Uuid\",\n container_ref as \"container_ref!\"\n FROM workspaces\n WHERE container_ref IS NOT NULL",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "container_ref!",
"ordinal": 1,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
true
]
},
"hash": "3634e2bab8fef106721bb64a791edd81d3d49eb34fbabd34e4feadfb5f229a6e"
}

View File

@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT\n m.id as \"id!: Uuid\",\n m.workspace_id as \"workspace_id!: Uuid\",\n m.repo_id as \"repo_id!: Uuid\",\n m.merge_type as \"merge_type!: MergeType\",\n m.merge_commit,\n m.pr_number,\n m.pr_url,\n m.pr_status as \"pr_status?: MergeStatus\",\n m.pr_merged_at as \"pr_merged_at?: DateTime<Utc>\",\n m.pr_merge_commit_sha,\n m.target_branch_name as \"target_branch_name!: String\",\n m.created_at as \"created_at!: DateTime<Utc>\"\n FROM merges m\n INNER JOIN (\n SELECT workspace_id, MAX(created_at) as max_created_at\n FROM merges\n WHERE merge_type = 'pr'\n GROUP BY workspace_id\n ) latest ON m.workspace_id = latest.workspace_id\n AND m.created_at = latest.max_created_at\n INNER JOIN workspaces w ON m.workspace_id = w.id\n WHERE m.merge_type = 'pr' AND w.archived = $1",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id!: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "merge_type!: MergeType",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "merge_commit",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 5,
"type_info": "Integer"
},
{
"name": "pr_url",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "pr_status?: MergeStatus",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "pr_merged_at?: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "pr_merge_commit_sha",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "target_branch_name!: String",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "3ace1ee8dba0669400d69891912b86823e41ca643092d990b12c1a6160112427"
}

View File

@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "INSERT INTO merges (\n id, workspace_id, repo_id, merge_type, pr_number, pr_url, pr_status, created_at, target_branch_name\n ) VALUES ($1, $2, $3, 'pr', $4, $5, 'open', $6, $7)\n RETURNING\n id as \"id!: Uuid\",\n workspace_id as \"workspace_id!: Uuid\",\n repo_id as \"repo_id!: Uuid\",\n merge_type as \"merge_type!: MergeType\",\n merge_commit,\n pr_number,\n pr_url,\n pr_status as \"pr_status?: MergeStatus\",\n pr_merged_at as \"pr_merged_at?: DateTime<Utc>\",\n pr_merge_commit_sha,\n created_at as \"created_at!: DateTime<Utc>\",\n target_branch_name as \"target_branch_name!: String\"\n ",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id!: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "merge_type!: MergeType",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "merge_commit",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 5,
"type_info": "Integer"
},
{
"name": "pr_url",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "pr_status?: MergeStatus",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "pr_merged_at?: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "pr_merge_commit_sha",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "target_branch_name!: String",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 7
},
"nullable": [
true,
false,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "3d85256618729c1c0bf2758ffab9cdb4ec2af0751e3a37db4009c02f95f6f556"
}

View File

@@ -0,0 +1,80 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n w.id as \"id!: Uuid\",\n w.task_id as \"task_id: Uuid\",\n w.container_ref,\n w.branch as \"branch!\",\n w.agent_working_dir,\n w.setup_completed_at as \"setup_completed_at: DateTime<Utc>\",\n w.created_at as \"created_at!: DateTime<Utc>\",\n w.updated_at as \"updated_at!: DateTime<Utc>\",\n w.archived as \"archived!: bool\",\n w.pinned as \"pinned!: bool\",\n w.name\n FROM workspaces w\n LEFT JOIN sessions s ON w.id = s.workspace_id\n LEFT JOIN execution_processes ep ON s.id = ep.session_id AND ep.completed_at IS NOT NULL\n WHERE w.container_ref IS NOT NULL\n AND w.id NOT IN (\n SELECT DISTINCT s2.workspace_id\n FROM sessions s2\n JOIN execution_processes ep2 ON s2.id = ep2.session_id\n WHERE ep2.completed_at IS NULL\n )\n GROUP BY w.id, w.container_ref, w.updated_at\n HAVING datetime('now', 'localtime',\n CASE\n WHEN w.archived = 1\n THEN '-1 hours'\n ELSE '-72 hours'\n END\n ) > datetime(\n MAX(\n max(\n datetime(w.updated_at),\n datetime(ep.completed_at)\n )\n )\n )\n ORDER BY MAX(\n CASE\n WHEN ep.completed_at IS NOT NULL THEN ep.completed_at\n ELSE w.updated_at\n END\n ) ASC\n ",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "task_id: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "container_ref",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "branch!",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "setup_completed_at: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "archived!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "pinned!: bool",
"ordinal": 9,
"type_info": "Integer"
},
{
"name": "name",
"ordinal": 10,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true
]
},
"hash": "424ee349862b29e2b21267e5266065b036f03be70161ccf3c299174df9f8b96e"
}

View File

@@ -1,56 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT s.id AS \"id!: Uuid\",\n s.workspace_id AS \"workspace_id!: Uuid\",\n s.name,\n s.executor,\n s.agent_working_dir,\n s.created_at AS \"created_at!: DateTime<Utc>\",\n s.updated_at AS \"updated_at!: DateTime<Utc>\"\n FROM sessions s\n LEFT JOIN (\n SELECT ep.session_id, MAX(ep.created_at) as last_used\n FROM execution_processes ep\n WHERE ep.run_reason != 'devserver' AND ep.dropped = FALSE\n GROUP BY ep.session_id\n ) latest_ep ON s.id = latest_ep.session_id\n WHERE s.workspace_id = $1\n ORDER BY COALESCE(latest_ep.last_used, s.created_at) DESC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "executor",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
true,
true,
true,
false,
false
]
},
"hash": "4506eeeb85b49f00143a9d23636c976159e1c72c9cfce005599c5eb52dc15095"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "DELETE FROM attachments WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "4ac35216ead7e5be9cc2de504a06b6e375e23ca2ed14493ec991f53e458a6a34"
}

View File

@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n workspace_id as \"workspace_id!: Uuid\",\n repo_id as \"repo_id!: Uuid\",\n merge_type as \"merge_type!: MergeType\",\n merge_commit,\n pr_number,\n pr_url,\n pr_status as \"pr_status?: MergeStatus\",\n pr_merged_at as \"pr_merged_at?: DateTime<Utc>\",\n pr_merge_commit_sha,\n target_branch_name as \"target_branch_name!: String\",\n created_at as \"created_at!: DateTime<Utc>\"\n FROM merges\n WHERE workspace_id = $1\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id!: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "merge_type!: MergeType",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "merge_commit",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 5,
"type_info": "Integer"
},
{
"name": "pr_url",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "pr_status?: MergeStatus",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "pr_merged_at?: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "pr_merge_commit_sha",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "target_branch_name!: String",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "4b59b958807be54c7c9949d96ced96e4ab1498f1056e7d0d7956aff46352d90f"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE execution_process_repo_states\n SET merge_commit = $1, updated_at = $2\n WHERE execution_process_id = $3\n AND repo_id = $4",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "4d84b308a2cc7677da65111d080bf02e5e35c052048360d3dbea656bbbcd3edb"
}

View File

@@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n entity_type as \"entity_type!: EntityType\",\n local_id as \"local_id!: Uuid\",\n remote_id as \"remote_id: Uuid\",\n status as \"status!: MigrationStatus\",\n error_message,\n attempt_count as \"attempt_count!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM migration_state\n WHERE entity_type = $1\n ORDER BY created_at ASC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "entity_type!: EntityType",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "local_id!: Uuid",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "remote_id: Uuid",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "status!: MigrationStatus",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "error_message",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "attempt_count!",
"ordinal": 6,
"type_info": "Integer"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
true,
false,
true,
false,
false,
false
]
},
"hash": "517fb82570b9624e166696af53963ec499966562b23b5833fc4ca4cf43bcaccc"
}

View File

@@ -0,0 +1,86 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n workspace_id as \"workspace_id!: Uuid\",\n repo_id as \"repo_id!: Uuid\",\n merge_type as \"merge_type!: MergeType\",\n merge_commit,\n pr_number,\n pr_url,\n pr_status as \"pr_status?: MergeStatus\",\n pr_merged_at as \"pr_merged_at?: DateTime<Utc>\",\n pr_merge_commit_sha,\n created_at as \"created_at!: DateTime<Utc>\",\n target_branch_name as \"target_branch_name!: String\"\n FROM merges\n WHERE merge_type = 'pr'\n ORDER BY created_at ASC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id!: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "merge_type!: MergeType",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "merge_commit",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 5,
"type_info": "Integer"
},
{
"name": "pr_url",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "pr_status?: MergeStatus",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "pr_merged_at?: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "pr_merge_commit_sha",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "target_branch_name!: String",
"ordinal": 11,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
false,
false,
false,
true,
true,
true,
true,
true,
true,
false,
false
]
},
"hash": "570f62a32921c4a9a7e4e1006e9b31c4c58e69ab8681d76dfe9b184ff1e0bc65"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE merges\n SET pr_status = $1,\n pr_merge_commit_sha = $2,\n pr_merged_at = $3\n WHERE id = $4",
"describe": {
"columns": [],
"parameters": {
"Right": 4
},
"nullable": []
},
"hash": "5785a10c3d51ff9b001aa455f6296a4dcba61cec700a4b72031c5c643b273938"
}

View File

@@ -0,0 +1,20 @@
{
"db_name": "SQLite",
"query": "SELECT EXISTS(\n SELECT 1 FROM coding_agent_turns cat\n JOIN execution_processes ep ON cat.execution_process_id = ep.id\n JOIN sessions s ON ep.session_id = s.id\n WHERE s.workspace_id = $1 AND cat.seen = 0\n ) as \"has_unseen!: bool\"",
"describe": {
"columns": [
{
"name": "has_unseen!: bool",
"ordinal": 0,
"type_info": "Integer"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "606016bf31cf0abd87d7eb95ea99d7c8c014523cb02e482d608299ceefaeffc5"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT i.id as \"id!: Uuid\",\n i.file_path as \"file_path!\",\n i.original_name as \"original_name!\",\n i.mime_type,\n i.size_bytes as \"size_bytes!\",\n i.hash as \"hash!\",\n i.created_at as \"created_at!: DateTime<Utc>\",\n i.updated_at as \"updated_at!: DateTime<Utc>\"\n FROM attachments i\n JOIN workspace_attachments wa ON i.id = wa.attachment_id\n WHERE wa.workspace_id = $1\n ORDER BY wa.created_at",
"query": "SELECT i.id as \"id!: Uuid\",\n i.file_path as \"file_path!\",\n i.original_name as \"original_name!\",\n i.mime_type,\n i.size_bytes as \"size_bytes!\",\n i.hash as \"hash!\",\n i.created_at as \"created_at!: DateTime<Utc>\",\n i.updated_at as \"updated_at!: DateTime<Utc>\"\n FROM images i\n JOIN workspace_images wi ON i.id = wi.image_id\n WHERE wi.workspace_id = $1\n ORDER BY wi.created_at",
"describe": {
"columns": [
{
@@ -58,5 +58,5 @@
false
]
},
"hash": "586d5ab95899967c8a8f74996c4a598a73661823bc2bcb240cebb7cd0533abb6"
"hash": "60e47682fe6fc150ee737176c9221ab1958595e92dc0fc5928688af847ab30ce"
}

View File

@@ -1,86 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n t.id,\n t.workspace_id AS \"workspace_id: Uuid\",\n t.repo_id AS \"repo_id: Uuid\",\n t.pr_url,\n t.pr_number,\n t.pr_status AS \"pr_status: MergeStatus\",\n t.target_branch_name,\n t.merged_at AS \"merged_at: DateTime<Utc>\",\n t.merge_commit_sha,\n t.created_at AS \"created_at!: DateTime<Utc>\",\n t.updated_at AS \"updated_at!: DateTime<Utc>\",\n t.synced_at AS \"synced_at: DateTime<Utc>\"\n FROM pull_requests t\n INNER JOIN (\n SELECT workspace_id, MAX(created_at) as max_created_at\n FROM pull_requests\n WHERE workspace_id IS NOT NULL\n GROUP BY workspace_id\n ) latest ON t.workspace_id = latest.workspace_id AND t.created_at = latest.max_created_at\n INNER JOIN workspaces w ON t.workspace_id = w.id\n WHERE t.workspace_id IS NOT NULL AND w.archived = $1",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "workspace_id: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "pr_url",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 4,
"type_info": "Integer"
},
{
"name": "pr_status: MergeStatus",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "target_branch_name",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "merged_at: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "merge_commit_sha",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 9,
"type_info": "Datetime"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 10,
"type_info": "Datetime"
},
{
"name": "synced_at: DateTime<Utc>",
"ordinal": 11,
"type_info": "Datetime"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
true,
true,
false,
false,
false,
false,
true,
true,
false,
false,
true
]
},
"hash": "64cdb811a6d15a483fc0413dc49ba8aa093b1892831699c456afe0507ac51aa3"
}

View File

@@ -1,50 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n id AS \"id!: Uuid\",\n workspace_id AS \"workspace_id!: Uuid\",\n repo_id AS \"repo_id!: Uuid\",\n merge_commit,\n target_branch_name,\n created_at AS \"created_at!: DateTime<Utc>\"\n FROM merges\n WHERE workspace_id = ? AND merge_type = 'direct'\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id!: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "merge_commit",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "target_branch_name",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
true,
false,
false
]
},
"hash": "67a7192158ad406cf8b97165c46a602330e807ed2757497e483295ea21952f1f"
}

View File

@@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n entity_type as \"entity_type!: EntityType\",\n local_id as \"local_id!: Uuid\",\n remote_id as \"remote_id: Uuid\",\n status as \"status!: MigrationStatus\",\n error_message,\n attempt_count as \"attempt_count!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM migration_state\n WHERE entity_type = $1 AND local_id = $2",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "entity_type!: EntityType",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "local_id!: Uuid",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "remote_id: Uuid",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "status!: MigrationStatus",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "error_message",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "attempt_count!",
"ordinal": 6,
"type_info": "Integer"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 2
},
"nullable": [
true,
false,
false,
true,
false,
true,
false,
false,
false
]
},
"hash": "686810c5271e1d44042b6ea2c6cc434eb2e3f5d3540c97d703c34dd4e978c690"
}

View File

@@ -1,50 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n id AS \"id!: Uuid\",\n workspace_id AS \"workspace_id!: Uuid\",\n repo_id AS \"repo_id!: Uuid\",\n merge_commit,\n target_branch_name,\n created_at AS \"created_at!: DateTime<Utc>\"\n FROM merges\n WHERE workspace_id = ? AND repo_id = ? AND merge_type = 'direct'\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id!: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "merge_commit",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "target_branch_name",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 2
},
"nullable": [
true,
false,
false,
true,
false,
false
]
},
"hash": "687a386ec0c65676bcd9d1ced30549e4c2a3d2bdb63a0a5623027e50aa6e4389"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO workspace_attachments (id, workspace_id, attachment_id)\n SELECT $1, $2, $3\n WHERE NOT EXISTS (\n SELECT 1 FROM workspace_attachments WHERE workspace_id = $2 AND attachment_id = $3\n )",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "6ae5eb2719382d4d081ee17dbd5de654c156b06e2af4ddfb917d36002146be5b"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT COUNT(1) AS \"count!: i64\" FROM pull_requests WHERE workspace_id = ? AND pr_status = 'open'",
"query": "SELECT COUNT(*) as \"count!: i64\" FROM workspaces",
"describe": {
"columns": [
{
@@ -10,11 +10,11 @@
}
],
"parameters": {
"Right": 1
"Right": 0
},
"nullable": [
false
]
},
"hash": "21c7a9f62322212a5f6b923bb410c25ac06e6a0c857d910d93fb80ab77fc9b35"
"hash": "70b21c5c0a2ba5c21c9c1132f14a68c02a8a2cd555caea74e57a0aeb206770d3"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!: Uuid\",\n task_id AS \"task_id: Uuid\",\n container_ref,\n branch,\n setup_completed_at AS \"setup_completed_at: DateTime<Utc>\",\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n archived AS \"archived!: bool\",\n pinned AS \"pinned!: bool\",\n name,\n worktree_deleted AS \"worktree_deleted!: bool\"\n FROM workspaces\n ORDER BY created_at DESC",
"query": "SELECT id AS \"id!: Uuid\",\n task_id AS \"task_id: Uuid\",\n container_ref,\n branch,\n agent_working_dir,\n setup_completed_at AS \"setup_completed_at: DateTime<Utc>\",\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n archived AS \"archived!: bool\",\n pinned AS \"pinned!: bool\",\n name\n FROM workspaces\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
@@ -24,39 +24,39 @@
"type_info": "Text"
},
{
"name": "setup_completed_at: DateTime<Utc>",
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"name": "setup_completed_at: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"name": "created_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "archived!: bool",
"name": "updated_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Integer"
"type_info": "Text"
},
{
"name": "pinned!: bool",
"name": "archived!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "name",
"name": "pinned!: bool",
"ordinal": 9,
"type_info": "Text"
"type_info": "Integer"
},
{
"name": "worktree_deleted!: bool",
"name": "name",
"ordinal": 10,
"type_info": "Bool"
"type_info": "Text"
}
],
"parameters": {
@@ -68,13 +68,13 @@
true,
false,
true,
false,
false,
false,
false,
true,
false
false,
false,
false,
false,
true
]
},
"hash": "64f31710ab7ba14047f31cce44ad36c60a53624f9bcb03a5eaff5d61ca8cc9cf"
"hash": "72c04c3a56fc9dc0b08ada7c4260d7813f4c02005bc0a0935c8c7716c51c5047"
}

View File

@@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "INSERT INTO migration_state (id, entity_type, local_id)\n VALUES ($1, $2, $3)\n ON CONFLICT (entity_type, local_id) DO UPDATE SET\n updated_at = datetime('now', 'subsec')\n RETURNING\n id as \"id!: Uuid\",\n entity_type as \"entity_type!: EntityType\",\n local_id as \"local_id!: Uuid\",\n remote_id as \"remote_id: Uuid\",\n status as \"status!: MigrationStatus\",\n error_message,\n attempt_count as \"attempt_count!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "entity_type!: EntityType",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "local_id!: Uuid",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "remote_id: Uuid",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "status!: MigrationStatus",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "error_message",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "attempt_count!",
"ordinal": 6,
"type_info": "Integer"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
false,
true,
false,
true,
false,
false,
false
]
},
"hash": "7364150098bec681451c43762117a1f5c5b4e27f5f65186c3cc16092b3491c37"
}

View File

@@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n entity_type as \"entity_type!: EntityType\",\n local_id as \"local_id!: Uuid\",\n remote_id as \"remote_id: Uuid\",\n status as \"status!: MigrationStatus\",\n error_message,\n attempt_count as \"attempt_count!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM migration_state\n ORDER BY created_at ASC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "entity_type!: EntityType",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "local_id!: Uuid",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "remote_id: Uuid",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "status!: MigrationStatus",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "error_message",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "attempt_count!",
"ordinal": 6,
"type_info": "Integer"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
false,
false,
true,
false,
true,
false,
false,
false
]
},
"hash": "7807cb09da72c5a1e35bf4f4da1bea1743a578588e72444ede98f5f969af08c1"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!: Uuid\",\n workspace_id AS \"workspace_id!: Uuid\",\n name,\n executor,\n agent_working_dir,\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\"\n FROM sessions\n WHERE id = $1",
"query": "SELECT id AS \"id!: Uuid\",\n workspace_id AS \"workspace_id!: Uuid\",\n executor,\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\"\n FROM sessions\n WHERE id = $1",
"describe": {
"columns": [
{
@@ -14,28 +14,18 @@
"type_info": "Blob"
},
{
"name": "name",
"name": "executor",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "executor",
"name": "created_at!: DateTime<Utc>",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 6,
"ordinal": 4,
"type_info": "Text"
}
],
@@ -46,11 +36,9 @@
true,
false,
true,
true,
true,
false,
false
]
},
"hash": "db39f7ab7391c1289299e7f8aa7e1f642874eed0179e91a9558f9df534db797c"
"hash": "7811b89c22f9d9a440d84c347b1824738c8d01ce7979d791778320b92ccf7a30"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "DELETE FROM repos WHERE id = $1",
"query": "DELETE FROM images WHERE id = $1",
"describe": {
"columns": [],
"parameters": {
@@ -8,5 +8,5 @@
},
"nullable": []
},
"hash": "3a9148e9e914d644d4d82a1c2dc8bd0e093d4f4c638afa7fd8f5211892fb6d84"
"hash": "79b35e39d668ad2285b3a05c15d5243cc91d35e03104d222488c7e27b8bbb569"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "INSERT INTO workspaces (id, task_id, container_ref, branch, setup_completed_at, name)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING id as \"id!: Uuid\", task_id as \"task_id: Uuid\", container_ref, branch, setup_completed_at as \"setup_completed_at: DateTime<Utc>\", created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\", archived as \"archived!: bool\", pinned as \"pinned!: bool\", name, worktree_deleted as \"worktree_deleted!: bool\"",
"query": "INSERT INTO workspaces (id, task_id, container_ref, branch, agent_working_dir, setup_completed_at)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING id as \"id!: Uuid\", task_id as \"task_id: Uuid\", container_ref, branch, agent_working_dir, setup_completed_at as \"setup_completed_at: DateTime<Utc>\", created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\", archived as \"archived!: bool\", pinned as \"pinned!: bool\", name",
"describe": {
"columns": [
{
@@ -24,39 +24,39 @@
"type_info": "Text"
},
{
"name": "setup_completed_at: DateTime<Utc>",
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"name": "setup_completed_at: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"name": "created_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "archived!: bool",
"name": "updated_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Integer"
"type_info": "Text"
},
{
"name": "pinned!: bool",
"name": "archived!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "name",
"name": "pinned!: bool",
"ordinal": 9,
"type_info": "Text"
"type_info": "Integer"
},
{
"name": "worktree_deleted!: bool",
"name": "name",
"ordinal": 10,
"type_info": "Bool"
"type_info": "Text"
}
],
"parameters": {
@@ -68,13 +68,13 @@
true,
false,
true,
false,
false,
false,
false,
true,
false
false,
false,
false,
false,
true
]
},
"hash": "7410e8128e63af1c3127e833accee637e65f7efcd9111ecb891587294042129c"
"hash": "7d08c4f085511474324aa4f4e291cf80bd75a439fdb8648a2646304cee93da25"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE pull_requests SET synced_at = ? WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "8076c3d62eba1cdf2a050c9d61dc20ef2fc3e24b0830d1e20099a42ae78333d0"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE workspaces SET container_ref = NULL, updated_at = datetime('now') WHERE id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "83cff8501927f930b9db7c131051c3103a74b1ca12d936cf523594945020c665"
}

View File

@@ -0,0 +1,68 @@
{
"db_name": "SQLite",
"query": "SELECT\n id as \"id!: Uuid\",\n entity_type as \"entity_type!: EntityType\",\n local_id as \"local_id!: Uuid\",\n remote_id as \"remote_id: Uuid\",\n status as \"status!: MigrationStatus\",\n error_message,\n attempt_count as \"attempt_count!\",\n created_at as \"created_at!: DateTime<Utc>\",\n updated_at as \"updated_at!: DateTime<Utc>\"\n FROM migration_state\n WHERE status = $1\n ORDER BY created_at ASC",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "entity_type!: EntityType",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "local_id!: Uuid",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "remote_id: Uuid",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "status!: MigrationStatus",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "error_message",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "attempt_count!",
"ordinal": 6,
"type_info": "Integer"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
false,
true,
false,
true,
false,
false,
false
]
},
"hash": "84ee994f0aad005cf62ca318eb20ae29d218a72cdd1fadf2a5ae399b0719ca19"
}

View File

@@ -1,80 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n w.id as \"id!: Uuid\",\n w.task_id as \"task_id: Uuid\",\n w.container_ref,\n w.branch as \"branch!\",\n w.setup_completed_at as \"setup_completed_at: DateTime<Utc>\",\n w.created_at as \"created_at!: DateTime<Utc>\",\n w.updated_at as \"updated_at!: DateTime<Utc>\",\n w.archived as \"archived!: bool\",\n w.pinned as \"pinned!: bool\",\n w.name,\n w.worktree_deleted as \"worktree_deleted!: bool\"\n FROM workspaces w\n LEFT JOIN sessions s ON w.id = s.workspace_id\n LEFT JOIN execution_processes ep ON s.id = ep.session_id AND ep.completed_at IS NOT NULL\n WHERE w.container_ref IS NOT NULL\n AND w.worktree_deleted = FALSE\n AND w.id NOT IN (\n SELECT DISTINCT s2.workspace_id\n FROM sessions s2\n JOIN execution_processes ep2 ON s2.id = ep2.session_id\n WHERE ep2.completed_at IS NULL\n )\n GROUP BY w.id, w.container_ref, w.updated_at\n HAVING datetime('now', 'localtime',\n CASE\n WHEN w.archived = 1\n THEN '-1 hours'\n ELSE '-72 hours'\n END\n ) > datetime(\n MAX(\n max(\n datetime(w.updated_at),\n datetime(ep.completed_at)\n )\n )\n )\n ORDER BY MAX(\n CASE\n WHEN ep.completed_at IS NOT NULL THEN ep.completed_at\n ELSE w.updated_at\n END\n ) ASC\n ",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "task_id: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "container_ref",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "branch!",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "setup_completed_at: DateTime<Utc>",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "archived!: bool",
"ordinal": 7,
"type_info": "Integer"
},
{
"name": "pinned!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "name",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "worktree_deleted!: bool",
"ordinal": 10,
"type_info": "Bool"
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
true,
true,
true,
true,
true,
true,
true,
true,
true,
true
]
},
"hash": "91810eeed4804827717a182ad1b61c641648e2659100f43ef9504fc60e5d244e"
}

View File

@@ -0,0 +1,44 @@
{
"db_name": "SQLite",
"query": "SELECT s.id AS \"id!: Uuid\",\n s.workspace_id AS \"workspace_id!: Uuid\",\n s.executor,\n s.created_at AS \"created_at!: DateTime<Utc>\",\n s.updated_at AS \"updated_at!: DateTime<Utc>\"\n FROM sessions s\n LEFT JOIN (\n SELECT ep.session_id, MAX(ep.created_at) as last_used\n FROM execution_processes ep\n WHERE ep.run_reason != 'devserver' AND ep.dropped = FALSE\n GROUP BY ep.session_id\n ) latest_ep ON s.id = latest_ep.session_id\n WHERE s.workspace_id = $1\n ORDER BY COALESCE(latest_ep.last_used, s.created_at) DESC\n LIMIT 1",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "executor",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 4,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
true,
false,
false
]
},
"hash": "91d94506317abcd36e0da72066a5d31c3ec52530661a96c8231912c5b2a683f8"
}

View File

@@ -1,56 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT s.id AS \"id!: Uuid\",\n s.workspace_id AS \"workspace_id!: Uuid\",\n s.name,\n s.executor,\n s.agent_working_dir,\n s.created_at AS \"created_at!: DateTime<Utc>\",\n s.updated_at AS \"updated_at!: DateTime<Utc>\"\n FROM sessions s\n LEFT JOIN (\n SELECT ep.session_id, MAX(ep.created_at) as last_used\n FROM execution_processes ep\n WHERE ep.run_reason != 'devserver' AND ep.dropped = FALSE\n GROUP BY ep.session_id\n ) latest_ep ON s.id = latest_ep.session_id\n WHERE s.workspace_id = $1\n ORDER BY COALESCE(latest_ep.last_used, s.created_at) DESC\n LIMIT 1",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "executor",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
true,
false,
true,
true,
true,
false,
false
]
},
"hash": "93efe07b91d232fc0c371be8ee618ba6ccfd6930454fc11845d5dfc2ba0bad62"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "INSERT INTO attachments (id, file_path, original_name, mime_type, size_bytes, hash)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING id as \"id!: Uuid\", \n file_path as \"file_path!\", \n original_name as \"original_name!\", \n mime_type,\n size_bytes as \"size_bytes!\",\n hash as \"hash!\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"",
"query": "INSERT INTO images (id, file_path, original_name, mime_type, size_bytes, hash)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING id as \"id!: Uuid\", \n file_path as \"file_path!\", \n original_name as \"original_name!\", \n mime_type,\n size_bytes as \"size_bytes!\",\n hash as \"hash!\",\n created_at as \"created_at!: DateTime<Utc>\", \n updated_at as \"updated_at!: DateTime<Utc>\"",
"describe": {
"columns": [
{
@@ -58,5 +58,5 @@
false
]
},
"hash": "4b952fb779fbcf70bd402b6bcc0eec07b75879333614b8ef98e5b8073ad66ca6"
"hash": "943c19a516ecc2060133457fc8104ad612dfb872f616cd47bb900646b7f5af37"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "INSERT INTO execution_process_logs (execution_id, logs, byte_size, inserted_at)\n VALUES ($1, $2, $3, datetime('now', 'subsec'))",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "9747ebaebd562d65f0c333b0f5efc74fa63ab9fcb35a43f75f57da3fcb9a2588"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "INSERT INTO merges (id, workspace_id, repo_id, merge_type, merge_commit, created_at, target_branch_name)\n VALUES (?, ?, ?, 'direct', ?, ?, ?)",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "9ab0fedd6e06a0372797e66c9353dd807e85e059520f20db4b03d9a27bb3efb7"
}

View File

@@ -1,6 +1,6 @@
{
"db_name": "SQLite",
"query": "SELECT id AS \"id!: Uuid\",\n task_id AS \"task_id: Uuid\",\n container_ref,\n branch,\n setup_completed_at AS \"setup_completed_at: DateTime<Utc>\",\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n archived AS \"archived!: bool\",\n pinned AS \"pinned!: bool\",\n name,\n worktree_deleted AS \"worktree_deleted!: bool\"\n FROM workspaces\n WHERE rowid = $1",
"query": "SELECT id AS \"id!: Uuid\",\n task_id AS \"task_id: Uuid\",\n container_ref,\n branch,\n agent_working_dir,\n setup_completed_at AS \"setup_completed_at: DateTime<Utc>\",\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n archived AS \"archived!: bool\",\n pinned AS \"pinned!: bool\",\n name\n FROM workspaces\n WHERE rowid = $1",
"describe": {
"columns": [
{
@@ -24,39 +24,39 @@
"type_info": "Text"
},
{
"name": "setup_completed_at: DateTime<Utc>",
"name": "agent_working_dir",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"name": "setup_completed_at: DateTime<Utc>",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"name": "created_at!: DateTime<Utc>",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "archived!: bool",
"name": "updated_at!: DateTime<Utc>",
"ordinal": 7,
"type_info": "Integer"
"type_info": "Text"
},
{
"name": "pinned!: bool",
"name": "archived!: bool",
"ordinal": 8,
"type_info": "Integer"
},
{
"name": "name",
"name": "pinned!: bool",
"ordinal": 9,
"type_info": "Text"
"type_info": "Integer"
},
{
"name": "worktree_deleted!: bool",
"name": "name",
"ordinal": 10,
"type_info": "Bool"
"type_info": "Text"
}
],
"parameters": {
@@ -68,13 +68,13 @@
true,
false,
true,
false,
false,
false,
false,
true,
false
false,
false,
false,
false,
true
]
},
"hash": "f2dbb49b2f839e84a46fdd865d9982b758160517b93bc92d8e12060426daa05d"
"hash": "9fb28e24e01689c959f8a039139407de7ee65535e6233f20a83250bd76ed3300"
}

View File

@@ -0,0 +1,44 @@
{
"db_name": "SQLite",
"query": "INSERT INTO sessions (id, workspace_id, executor)\n VALUES ($1, $2, $3)\n RETURNING id AS \"id!: Uuid\",\n workspace_id AS \"workspace_id!: Uuid\",\n executor,\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\"",
"describe": {
"columns": [
{
"name": "id!: Uuid",
"ordinal": 0,
"type_info": "Blob"
},
{
"name": "workspace_id!: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "executor",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 4,
"type_info": "Text"
}
],
"parameters": {
"Right": 3
},
"nullable": [
true,
false,
true,
false,
false
]
},
"hash": "a25ebd2f707a19c565d61682bede36628e906b68ebc93bbf7f71d8bb57f72bfc"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM migration_state",
"describe": {
"columns": [],
"parameters": {
"Right": 0
},
"nullable": []
},
"hash": "a4a50fcfb903e6d0a315676f4f760e5bb7718e10ea550aedf990c9da84834416"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "UPDATE migration_state\n SET status = 'failed',\n error_message = $3,\n attempt_count = attempt_count + 1,\n updated_at = datetime('now', 'subsec')\n WHERE entity_type = $1 AND local_id = $2",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "a915c22f5ed0bb86c3a242ca38cbc1bfca40ebfe9096058c27e94479b67c7c02"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "UPDATE sessions SET\n name = CASE WHEN $1 THEN $2 ELSE name END,\n updated_at = datetime('now', 'subsec')\n WHERE id = $3",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "abbda92ba42bea0f7d17d0945d51b011bf50e7b36ee50ed74988e053f6fb0eec"
}

View File

@@ -1,86 +0,0 @@
{
"db_name": "SQLite",
"query": "SELECT\n id,\n workspace_id AS \"workspace_id: Uuid\",\n repo_id AS \"repo_id: Uuid\",\n pr_url,\n pr_number,\n pr_status AS \"pr_status: MergeStatus\",\n target_branch_name,\n merged_at AS \"merged_at: DateTime<Utc>\",\n merge_commit_sha,\n created_at AS \"created_at!: DateTime<Utc>\",\n updated_at AS \"updated_at!: DateTime<Utc>\",\n synced_at AS \"synced_at: DateTime<Utc>\"\n FROM pull_requests\n WHERE workspace_id = $1\n ORDER BY created_at DESC",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "workspace_id: Uuid",
"ordinal": 1,
"type_info": "Blob"
},
{
"name": "repo_id: Uuid",
"ordinal": 2,
"type_info": "Blob"
},
{
"name": "pr_url",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "pr_number",
"ordinal": 4,
"type_info": "Integer"
},
{
"name": "pr_status: MergeStatus",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "target_branch_name",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "merged_at: DateTime<Utc>",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "merge_commit_sha",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "created_at!: DateTime<Utc>",
"ordinal": 9,
"type_info": "Datetime"
},
{
"name": "updated_at!: DateTime<Utc>",
"ordinal": 10,
"type_info": "Datetime"
},
{
"name": "synced_at: DateTime<Utc>",
"ordinal": 11,
"type_info": "Datetime"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
true,
true,
false,
false,
false,
false,
true,
true,
false,
false,
true
]
},
"hash": "b21ce1663242be3d16c21a076d7b4b584692eb6ae19a3a5f19caba8ad16edce6"
}

Some files were not shown because too many files have changed in this diff Show More