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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions packages/app/src/components/dialog-edit-project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
color: props.project.icon?.color || "pink",
iconUrl: props.project.icon?.override || "",
startup: props.project.commands?.start ?? "",
shutdown: props.project.commands?.stop ?? "",
dragOver: false,
iconHover: false,
})
Expand Down Expand Up @@ -75,14 +76,15 @@ export function DialogEditProject(props: { project: LocalProject }) {
mutationFn: async () => {
const name = store.name.trim() === folderName() ? "" : store.name.trim()
const start = store.startup.trim()
const stop = store.shutdown.trim()

if (props.project.id && props.project.id !== "global") {
await globalSDK.client.project.update({
projectID: props.project.id,
directory: props.project.worktree,
name,
icon: { color: store.color, override: store.iconUrl },
commands: { start },
commands: { start, stop },
})
globalSync.project.icon(props.project.worktree, store.iconUrl || undefined)
dialog.close()
Expand All @@ -92,7 +94,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
globalSync.project.meta(props.project.worktree, {
name,
icon: { color: store.color, override: store.iconUrl || undefined },
commands: { start: start || undefined },
commands: { start: start || undefined, stop: stop || undefined },
})
dialog.close()
},
Expand All @@ -105,9 +107,12 @@ export function DialogEditProject(props: { project: LocalProject }) {
}

return (
<Dialog title={language.t("dialog.project.edit.title")} class="w-full max-w-[480px] mx-auto">
<form onSubmit={handleSubmit} class="flex flex-col gap-6 p-6 pt-0">
<div class="flex flex-col gap-4">
<Dialog
title={language.t("dialog.project.edit.title")}
class="w-full max-w-[480px] mx-auto max-h-[calc(100vh-120px)] flex flex-col"
>
<form onSubmit={handleSubmit} class="flex flex-col min-h-0 flex-1">
<div class="flex flex-col gap-4 p-6 pt-0 overflow-y-auto">
<TextField
autofocus
type="text"
Expand Down Expand Up @@ -239,9 +244,20 @@ export function DialogEditProject(props: { project: LocalProject }) {
spellcheck={false}
class="max-h-14 w-full overflow-y-auto font-mono text-xs"
/>

<TextField
multiline
label={language.t("dialog.project.edit.worktree.shutdown")}
description={language.t("dialog.project.edit.worktree.shutdown.description")}
placeholder={language.t("dialog.project.edit.worktree.shutdown.placeholder")}
value={store.shutdown}
onChange={(v) => setStore("shutdown", v)}
spellcheck={false}
class="max-h-14 w-full overflow-y-auto font-mono text-xs"
/>
</div>

<div class="flex justify-end gap-2">
<div class="flex justify-end items-center gap-2 px-6 py-4 border-t border-border-base mt-auto shrink-0">
<Button type="button" variant="ghost" size="large" onClick={() => dialog.close()}>
{language.t("common.cancel")}
</Button>
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/context/global-sync/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export type ProjectMeta = {
}
commands?: {
start?: string
stop?: string
}
}

Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "سكريبت بدء تشغيل مساحة العمل",
"dialog.project.edit.worktree.startup.description": "يتم تشغيله بعد إنشاء مساحة عمل جديدة (شجرة عمل).",
"dialog.project.edit.worktree.startup.placeholder": "مثال: bun install",
"dialog.project.edit.worktree.shutdown": "سكريبت إيقاف تشغيل مساحة العمل",
"dialog.project.edit.worktree.shutdown.description": "يتم تشغيله قبل حذف مساحة العمل (شجرة عمل).",
"dialog.project.edit.worktree.shutdown.placeholder": "مثال: docker compose down",
"context.breakdown.title": "تفصيل السياق",
"context.breakdown.note": 'تفصيل تقريبي لرموز الإدخال. يشمل "أخرى" تعريفات الأدوات والنفقات العامة.',
"context.breakdown.system": "النظام",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "Script de inicialização do espaço de trabalho",
"dialog.project.edit.worktree.startup.description": "Executa após criar um novo espaço de trabalho (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "ex: bun install",
"dialog.project.edit.worktree.shutdown": "Script de desligamento do espaço de trabalho",
"dialog.project.edit.worktree.shutdown.description": "Executado antes de excluir um espaço de trabalho (worktree).",
"dialog.project.edit.worktree.shutdown.placeholder": "ex: docker compose down",
"context.breakdown.title": "Detalhamento do Contexto",
"context.breakdown.note":
'Detalhamento aproximado dos tokens de entrada. "Outros" inclui definições de ferramentas e overhead.',
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/bs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "Skripta za pokretanje radnog prostora",
"dialog.project.edit.worktree.startup.description": "Pokreće se nakon kreiranja novog radnog prostora (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "npr. bun install",
"dialog.project.edit.worktree.shutdown": "Skripta za gašenje radnog prostora",
"dialog.project.edit.worktree.shutdown.description": "Pokreće se prije brisanja radnog prostora (worktree).",
"dialog.project.edit.worktree.shutdown.placeholder": "npr. docker compose down",

"context.breakdown.title": "Razlaganje konteksta",
"context.breakdown.note":
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/da.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "Opstartsscript for arbejdsområde",
"dialog.project.edit.worktree.startup.description": "Køres efter oprettelse af et nyt arbejdsområde (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "f.eks. bun install",
"dialog.project.edit.worktree.shutdown": "Nedlukningsscript til arbejdsområde",
"dialog.project.edit.worktree.shutdown.description": "Køres før sletning af et arbejdsområde (worktree).",
"dialog.project.edit.worktree.shutdown.placeholder": "f.eks. docker compose down",
"context.breakdown.title": "Kontekstfordeling",
"context.breakdown.note":
'Omtrentlig fordeling af input-tokens. "Andre" inkluderer værktøjsdefinitioner og overhead.',
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,10 @@ export const dict = {
"dialog.project.edit.worktree.startup.description":
"Wird nach dem Erstellen eines neuen Arbeitsbereichs (Worktree) ausgeführt.",
"dialog.project.edit.worktree.startup.placeholder": "z. B. bun install",
"dialog.project.edit.worktree.shutdown": "Herunterfahren-Skript für Arbeitsbereich",
"dialog.project.edit.worktree.shutdown.description":
"Wird vor dem Löschen eines Arbeitsbereichs (Worktree) ausgeführt.",
"dialog.project.edit.worktree.shutdown.placeholder": "z. B. docker compose down",
"context.breakdown.title": "Kontext-Aufschlüsselung",
"context.breakdown.note":
'Ungefähre Aufschlüsselung der Eingabe-Token. "Andere" beinhaltet Werkzeugdefinitionen und Overhead.',
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "Workspace startup script",
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "e.g. bun install",
"dialog.project.edit.worktree.shutdown": "Workspace shutdown script",
"dialog.project.edit.worktree.shutdown.description": "Runs before deleting a workspace (worktree).",
"dialog.project.edit.worktree.shutdown.placeholder": "e.g. docker compose down",

"dialog.releaseNotes.action.getStarted": "Get started",
"dialog.releaseNotes.action.next": "Next",
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/i18n/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,10 @@ export const dict = {
"dialog.project.edit.worktree.startup.description":
"Se ejecuta después de crear un nuevo espacio de trabajo (árbol de trabajo).",
"dialog.project.edit.worktree.startup.placeholder": "p. ej. bun install",
"dialog.project.edit.worktree.shutdown": "Script de cierre del espacio de trabajo",
"dialog.project.edit.worktree.shutdown.description":
"Se ejecuta antes de eliminar un espacio de trabajo (árbol de trabajo).",
"dialog.project.edit.worktree.shutdown.placeholder": "p. ej. docker compose down",

"context.breakdown.title": "Desglose de Contexto",
"context.breakdown.note":
Expand Down
4 changes: 4 additions & 0 deletions packages/app/src/i18n/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,10 @@ export const dict = {
"dialog.project.edit.worktree.startup.description":
"S'exécute après la création d'un nouvel espace de travail (arbre de travail).",
"dialog.project.edit.worktree.startup.placeholder": "p. ex. bun install",
"dialog.project.edit.worktree.shutdown": "Script d'arrêt de l'espace de travail",
"dialog.project.edit.worktree.shutdown.description":
"S'exécute avant la suppression d'un espace de travail (arbre de travail).",
"dialog.project.edit.worktree.shutdown.placeholder": "p. ex. docker compose down",
"context.breakdown.title": "Répartition du contexte",
"context.breakdown.note":
"Répartition approximative des jetons d'entrée. \"Autre\" inclut les définitions d'outils et les frais généraux.",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,9 @@ export const dict = {
"dialog.project.edit.worktree.startup.description":
"新しいワークスペース (ワークツリー) を作成した後に実行されます。",
"dialog.project.edit.worktree.startup.placeholder": "例: bun install",
"dialog.project.edit.worktree.shutdown": "ワークスペース シャットダウンスクリプト",
"dialog.project.edit.worktree.shutdown.description": "ワークスペース(ワークツリー)を削除する前に実行されます。",
"dialog.project.edit.worktree.shutdown.placeholder": "例: docker compose down",
"context.breakdown.title": "コンテキストの内訳",
"context.breakdown.note": '入力トークンのおおよその内訳です。"その他"にはツールの定義やオーバーヘッドが含まれます。',
"context.breakdown.system": "システム",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "작업 공간 시작 스크립트",
"dialog.project.edit.worktree.startup.description": "새 작업 공간(작업 트리)을 만든 뒤 실행됩니다.",
"dialog.project.edit.worktree.startup.placeholder": "예: bun install",
"dialog.project.edit.worktree.shutdown": "워크스페이스 종료 스크립트",
"dialog.project.edit.worktree.shutdown.description": "워크스페이스(worktree) 삭제 전에 실행됩니다.",
"dialog.project.edit.worktree.shutdown.placeholder": "예: docker compose down",
"context.breakdown.title": "컨텍스트 분석",
"context.breakdown.note": '입력 토큰의 대략적인 분석입니다. "기타"에는 도구 정의 및 오버헤드가 포함됩니다.',
"context.breakdown.system": "시스템",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/no.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "Oppstartsskript for arbeidsområde",
"dialog.project.edit.worktree.startup.description": "Kjører etter at et nytt arbeidsområde (worktree) er opprettet.",
"dialog.project.edit.worktree.startup.placeholder": "f.eks. bun install",
"dialog.project.edit.worktree.shutdown": "Nedstengingsskript for arbeidsområde",
"dialog.project.edit.worktree.shutdown.description": "Kjøres før sletting av et arbeidsområde (worktree).",
"dialog.project.edit.worktree.shutdown.placeholder": "f.eks. docker compose down",

"context.breakdown.title": "Kontekstfordeling",
"context.breakdown.note": 'Omtrentlig fordeling av input-tokens. "Annet" inkluderer verktøydefinisjoner og overhead.',
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "Skrypt uruchamiania przestrzeni roboczej",
"dialog.project.edit.worktree.startup.description": "Runs after creating a new workspace (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "np. bun install",
"dialog.project.edit.worktree.shutdown": "Skrypt zamykania obszaru roboczego",
"dialog.project.edit.worktree.shutdown.description": "Uruchamiany przed usunięciem obszaru roboczego (worktree).",
"dialog.project.edit.worktree.shutdown.placeholder": "np. docker compose down",
"context.breakdown.title": "Podział kontekstu",
"context.breakdown.note": 'Przybliżony podział tokenów wejściowych. "Inne" obejmuje definicje narzędzi i narzut.',
"context.breakdown.system": "System",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,9 @@ export const dict = {
"dialog.project.edit.worktree.startup.description":
"Запускается после создания нового рабочего пространства (worktree).",
"dialog.project.edit.worktree.startup.placeholder": "например, bun install",
"dialog.project.edit.worktree.shutdown": "Скрипт закрытия рабочей области",
"dialog.project.edit.worktree.shutdown.description": "Выполняется перед удалением рабочей области (worktree).",
"dialog.project.edit.worktree.shutdown.placeholder": "например, docker compose down",
"context.breakdown.title": "Разбивка контекста",
"context.breakdown.note":
'Приблизительная разбивка входных токенов. "Другое" включает определения инструментов и накладные расходы.',
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/th.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "สคริปต์เริ่มต้นพื้นที่ทำงาน",
"dialog.project.edit.worktree.startup.description": "ทำงานหลังจากสร้างพื้นที่ทำงานใหม่ (worktree)",
"dialog.project.edit.worktree.startup.placeholder": "เช่น bun install",
"dialog.project.edit.worktree.shutdown": "สคริปต์ปิดพื้นที่ทำงาน",
"dialog.project.edit.worktree.shutdown.description": "ทำงานก่อนลบพื้นที่ทำงาน (worktree)",
"dialog.project.edit.worktree.shutdown.placeholder": "เช่น docker compose down",

"context.breakdown.title": "การแบ่งบริบท",
"context.breakdown.note": 'การแบ่งโดยประมาณของโทเค็นนำเข้า "อื่น ๆ" รวมถึงคำนิยามเครื่องมือและโอเวอร์เฮด',
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/tr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "Çalışma alanı başlatma betiği",
"dialog.project.edit.worktree.startup.description": "Yeni bir çalışma alanı (worktree) oluşturduktan sonra çalışır.",
"dialog.project.edit.worktree.startup.placeholder": "örneğin bun install",
"dialog.project.edit.worktree.shutdown": "Çalışma alanı kapatma betiği",
"dialog.project.edit.worktree.shutdown.description": "Bir çalışma alanı (worktree) silinmeden önce çalıştırılır.",
"dialog.project.edit.worktree.shutdown.placeholder": "örneğin docker compose down",

"context.breakdown.title": "Bağlam Dökümü",
"context.breakdown.note": 'Girdi tokenlerinin yaklaşık dökümü. "Diğer" araç tanımları ve ek yükleri içerir.',
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "工作区启动脚本",
"dialog.project.edit.worktree.startup.description": "在创建新的工作区 (worktree) 后运行。",
"dialog.project.edit.worktree.startup.placeholder": "例如 bun install",
"dialog.project.edit.worktree.shutdown": "工作区关闭脚本",
"dialog.project.edit.worktree.shutdown.description": "在删除工作区(worktree)之前运行。",
"dialog.project.edit.worktree.shutdown.placeholder": "例如 docker compose down",

"context.breakdown.title": "上下文拆分",
"context.breakdown.note": "输入 token 的大致拆分。“其他”包含工具定义和开销。",
Expand Down
3 changes: 3 additions & 0 deletions packages/app/src/i18n/zht.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,9 @@ export const dict = {
"dialog.project.edit.worktree.startup": "工作區啟動腳本",
"dialog.project.edit.worktree.startup.description": "在建立新的工作區 (worktree) 後執行。",
"dialog.project.edit.worktree.startup.placeholder": "例如 bun install",
"dialog.project.edit.worktree.shutdown": "工作區關閉腳本",
"dialog.project.edit.worktree.shutdown.description": "在刪除工作區(worktree)之前執行。",
"dialog.project.edit.worktree.shutdown.placeholder": "例如 docker compose down",
"context.breakdown.title": "上下文拆分",
"context.breakdown.note": "輸入 token 的大致拆分。「其他」包含工具定義和額外開銷。",
"context.breakdown.system": "系統",
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/src/project/project.sql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ export const ProjectTable = sqliteTable("project", {
...Timestamps,
time_initialized: integer(),
sandboxes: text({ mode: "json" }).notNull().$type<string[]>(),
commands: text({ mode: "json" }).$type<{ start?: string }>(),
commands: text({ mode: "json" }).$type<{ start?: string; stop?: string }>(),
})
1 change: 1 addition & 0 deletions packages/opencode/src/project/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export namespace Project {
commands: z
.object({
start: z.string().optional().describe("Startup script to run when creating a new workspace (worktree)"),
stop: z.string().optional().describe("Cleanup script to run before deleting a workspace (worktree)"),
})
.optional(),
time: z.object({
Expand Down
33 changes: 33 additions & 0 deletions packages/opencode/src/worktree/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export namespace Worktree {
export const RemoveInput = z
.object({
directory: z.string(),
stopCommand: z.string().optional().describe("Additional cleanup script to run before the project's stop command"),
})
.meta({
ref: "WorktreeRemoveInput",
Expand Down Expand Up @@ -323,6 +324,34 @@ export namespace Worktree {
return true
}

type StopKind = "worktree" | "project"

async function runStopScript(directory: string, cmd: string, kind: StopKind) {
const text = cmd.trim()
if (!text) return true

const ran = await runStartCommand(directory, text)
if (ran.code === 0) return true

log.error("worktree stop command failed", {
kind,
directory,
message: errorText(ran),
})
return false
}

async function runStopScripts(directory: string, input: { projectID: ProjectID; extra?: string }) {
const extra = input.extra ?? ""
await runStopScript(directory, extra, "worktree")

const row = Database.use((db) => db.select().from(ProjectTable).where(eq(ProjectTable.id, input.projectID)).get())
const project = row ? Project.fromRow(row) : undefined
const stopcmd = project?.commands?.stop?.trim() ?? ""
await runStopScript(directory, stopcmd, "project")
return true
}

function queueStartScripts(directory: string, input: { projectID: ProjectID; extra?: string }) {
setTimeout(() => {
const start = async () => {
Expand Down Expand Up @@ -498,6 +527,10 @@ export namespace Worktree {
return true
}

const projectID = Instance.project.id
const extra = input?.stopCommand?.trim()
await runStopScripts(directory, { projectID, extra })

await stop(entry.path)
const removed = await git(["worktree", "remove", "--force", entry.path], {
cwd: Instance.worktree,
Expand Down
4 changes: 4 additions & 0 deletions packages/sdk/js/src/v2/gen/sdk.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,10 @@ export class Project extends HeyApiClient {
* Startup script to run when creating a new workspace (worktree)
*/
start?: string
/**
* Cleanup script to run before deleting a workspace (worktree)
*/
stop?: string
}
},
options?: Options<never, ThrowOnError>,
Expand Down
Loading
Loading