whipped 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -3529,7 +3529,7 @@ function isResumableSessionState(state) {
3529
3529
  function normalizeTag(raw2) {
3530
3530
  return raw2.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
3531
3531
  }
3532
- var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
3532
+ var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, notificationSoundsConfigSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
3533
3533
  var init_api_contract = __esm({
3534
3534
  "src/core/api-contract.ts"() {
3535
3535
  "use strict";
@@ -3847,6 +3847,15 @@ Do NOT include:
3847
3847
  columns: z.array(runtimeBoardColumnSchema),
3848
3848
  cards: z.record(z.string(), runtimeBoardCardSchema)
3849
3849
  });
3850
+ notificationSoundsConfigSchema = z.object({
3851
+ enabled: z.boolean().default(false),
3852
+ readyForReview: z.boolean().default(true),
3853
+ prComment: z.boolean().default(true),
3854
+ done: z.boolean().default(true),
3855
+ reopened: z.boolean().default(true),
3856
+ blocked: z.boolean().default(true),
3857
+ runError: z.boolean().default(true)
3858
+ });
3850
3859
  runtimeGlobalConfigSchema = z.object({
3851
3860
  defaultAgent: runtimeAgentIdSchema.default("claude"),
3852
3861
  maxParallelTasks: z.number().int().positive().default(4),
@@ -3855,6 +3864,7 @@ Do NOT include:
3855
3864
  pollingIntervalSeconds: z.number().int().positive().default(30),
3856
3865
  prPollingIntervalSeconds: z.number().int().positive().default(60),
3857
3866
  terminalApp: z.string().optional(),
3867
+ notificationSounds: notificationSoundsConfigSchema.prefault({}),
3858
3868
  slackEnabled: z.boolean().default(true),
3859
3869
  slackBotToken: z.string().optional(),
3860
3870
  slackSigningSecret: z.string().optional(),
@@ -8973,7 +8983,7 @@ var require_tree_kill = __commonJS({
8973
8983
  "node_modules/.pnpm/tree-kill@1.2.2/node_modules/tree-kill/index.js"(exports, module) {
8974
8984
  "use strict";
8975
8985
  var childProcess4 = __require("child_process");
8976
- var spawn8 = childProcess4.spawn;
8986
+ var spawn9 = childProcess4.spawn;
8977
8987
  var exec = childProcess4.exec;
8978
8988
  module.exports = function(pid, signal, callback) {
8979
8989
  if (typeof signal === "function" && callback === void 0) {
@@ -8998,7 +9008,7 @@ var require_tree_kill = __commonJS({
8998
9008
  break;
8999
9009
  case "darwin":
9000
9010
  buildProcessTree(pid, tree, pidsToProcess, function(parentPid) {
9001
- return spawn8("pgrep", ["-P", parentPid]);
9011
+ return spawn9("pgrep", ["-P", parentPid]);
9002
9012
  }, function() {
9003
9013
  killAll(tree, signal, callback);
9004
9014
  });
@@ -9010,7 +9020,7 @@ var require_tree_kill = __commonJS({
9010
9020
  // break;
9011
9021
  default:
9012
9022
  buildProcessTree(pid, tree, pidsToProcess, function(parentPid) {
9013
- return spawn8("ps", ["-o", "pid", "--no-headers", "--ppid", parentPid]);
9023
+ return spawn9("ps", ["-o", "pid", "--no-headers", "--ppid", parentPid]);
9014
9024
  }, function() {
9015
9025
  killAll(tree, signal, callback);
9016
9026
  });
@@ -13606,8 +13616,8 @@ init_logger();
13606
13616
  // src/server/runtime-server.ts
13607
13617
  import { existsSync as existsSync14, readFileSync as readFileSync8 } from "node:fs";
13608
13618
  import { createServer } from "node:http";
13609
- import { join as join21 } from "node:path";
13610
- import { fileURLToPath as fileURLToPath4 } from "node:url";
13619
+ import { join as join22 } from "node:path";
13620
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
13611
13621
  import * as nodePty2 from "node-pty";
13612
13622
 
13613
13623
  // node_modules/.pnpm/ws@8.20.0/node_modules/ws/wrapper.mjs
@@ -14389,6 +14399,39 @@ init_runtime_config();
14389
14399
  init_logger();
14390
14400
  init_task_id();
14391
14401
 
14402
+ // src/notifications/sound-player.ts
14403
+ init_runtime_config();
14404
+ init_logger();
14405
+ import { spawn as spawn4 } from "node:child_process";
14406
+ import { dirname as dirname4, join as join11 } from "node:path";
14407
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
14408
+ var SOUNDS_DIR = join11(dirname4(fileURLToPath3(import.meta.url)), "sounds");
14409
+ var SOUND_FILES = {
14410
+ readyForReview: "ready-for-review.wav",
14411
+ prComment: "pr-comment.wav",
14412
+ done: "done.wav",
14413
+ reopened: "reopened.wav",
14414
+ blocked: "blocked.wav",
14415
+ runError: "run-error.wav"
14416
+ };
14417
+ async function playNotificationSound(event) {
14418
+ try {
14419
+ const { notificationSounds } = await loadGlobalConfig();
14420
+ if (!notificationSounds.enabled || !notificationSounds[event]) return;
14421
+ playOnHost(join11(SOUNDS_DIR, SOUND_FILES[event]));
14422
+ } catch (err) {
14423
+ logger.debug({ err }, `[sound] could not play notification sound for ${event}`);
14424
+ }
14425
+ }
14426
+ function playOnHost(file) {
14427
+ const [command, args] = process.platform === "darwin" ? ["afplay", [file]] : process.platform === "linux" ? ["paplay", [file]] : [null, null];
14428
+ if (!command) return;
14429
+ const child = spawn4(command, [...args], { stdio: "ignore", detached: true });
14430
+ child.on("error", () => {
14431
+ });
14432
+ child.unref();
14433
+ }
14434
+
14392
14435
  // src/daemon/poller.ts
14393
14436
  init_runtime_config();
14394
14437
  init_logger();
@@ -14400,7 +14443,7 @@ import { existsSync as existsSync8 } from "node:fs";
14400
14443
  import { execFile as execFile7, spawnSync } from "node:child_process";
14401
14444
  import { existsSync as existsSync5, mkdirSync as mkdirSync7, rmSync as rmSync2 } from "node:fs";
14402
14445
  import { homedir as homedir2 } from "node:os";
14403
- import { dirname as dirname4, join as join11, resolve, sep as sep2 } from "node:path";
14446
+ import { dirname as dirname5, join as join12, resolve, sep as sep2 } from "node:path";
14404
14447
  import { promisify as promisify7 } from "node:util";
14405
14448
 
14406
14449
  // node_modules/.pnpm/universal-user-agent@7.0.3/node_modules/universal-user-agent/index.js
@@ -17953,7 +17996,7 @@ var Octokit2 = Octokit.plugin(requestLog, legacyRestEndpointMethods, paginateRes
17953
17996
  // src/git/merge-operations.ts
17954
17997
  init_logger();
17955
17998
  var execFileAsync5 = promisify7(execFile7);
17956
- var WORKTREES_DIR = join11(homedir2(), ".whipped", "worktrees");
17999
+ var WORKTREES_DIR = join12(homedir2(), ".whipped", "worktrees");
17957
18000
  function git(args, cwd) {
17958
18001
  const r = spawnSync("git", args, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
17959
18002
  return { stdout: r.stdout?.trim() ?? "", stderr: r.stderr?.trim() ?? "", ok: r.status === 0 };
@@ -18002,7 +18045,7 @@ async function commitIfDirty(worktreePath, message) {
18002
18045
  return true;
18003
18046
  }
18004
18047
  function attemptMerge(repoPath, effectiveWorktreeId, taskBranch) {
18005
- const worktreePath = join11(WORKTREES_DIR, effectiveWorktreeId);
18048
+ const worktreePath = join12(WORKTREES_DIR, effectiveWorktreeId);
18006
18049
  if (existsSync5(worktreePath)) {
18007
18050
  rmSync2(worktreePath, { recursive: true, force: true });
18008
18051
  git(["worktree", "prune"], repoPath);
@@ -18040,10 +18083,10 @@ async function pushBranch(worktreePath, branch) {
18040
18083
  throw new Error(`Failed to push: ${detail}`);
18041
18084
  });
18042
18085
  }
18043
- var YOLO_DIR = join11(WORKTREES_DIR, ".yolo");
18086
+ var YOLO_DIR = join12(WORKTREES_DIR, ".yolo");
18044
18087
  function createYoloWorktree(repoPath, workspaceId, cardId, baseRef) {
18045
- const tmpPath = join11(YOLO_DIR, workspaceId, `${cardId}-${Date.now()}`);
18046
- mkdirSync7(dirname4(tmpPath), { recursive: true });
18088
+ const tmpPath = join12(YOLO_DIR, workspaceId, `${cardId}-${Date.now()}`);
18089
+ mkdirSync7(dirname5(tmpPath), { recursive: true });
18047
18090
  git(["worktree", "prune"], repoPath);
18048
18091
  const res = git(["worktree", "add", "--detach", tmpPath, baseRef], repoPath);
18049
18092
  if (!res.ok) throw new Error(`Failed to create YOLO worktree at ${tmpPath}: ${res.stderr}`);
@@ -18268,10 +18311,10 @@ init_logger();
18268
18311
  import { execFile as execFile8, spawnSync as spawnSync2 } from "node:child_process";
18269
18312
  import { existsSync as existsSync6, mkdirSync as mkdirSync8 } from "node:fs";
18270
18313
  import { rm as rm2 } from "node:fs/promises";
18271
- import { join as join12 } from "node:path";
18314
+ import { join as join13 } from "node:path";
18272
18315
  import { promisify as promisify8 } from "node:util";
18273
18316
  var execFileAsync6 = promisify8(execFile8);
18274
- var WORKTREES_DIR2 = join12(WHIPPED_HOME_DIR, "worktrees");
18317
+ var WORKTREES_DIR2 = join13(WHIPPED_HOME_DIR, "worktrees");
18275
18318
  function git2(args, cwd) {
18276
18319
  const result = spawnSync2("git", args, {
18277
18320
  cwd,
@@ -18289,7 +18332,7 @@ function getCardBranch(card) {
18289
18332
  function createWorktree(taskId, repoPath, baseRef, branchName) {
18290
18333
  mkdirSync8(WORKTREES_DIR2, { recursive: true });
18291
18334
  let branch = branchName ?? `task/${taskId}`;
18292
- const worktreePath = join12(WORKTREES_DIR2, taskId);
18335
+ const worktreePath = join13(WORKTREES_DIR2, taskId);
18293
18336
  logger.info(`[worktree:create] taskId=${taskId} branch=${branch} baseRef=${baseRef} worktreePath=${worktreePath}`);
18294
18337
  if (existsSync6(worktreePath)) {
18295
18338
  const actualBranch = git2(["rev-parse", "--abbrev-ref", "HEAD"], worktreePath);
@@ -18367,7 +18410,7 @@ function createWorktree(taskId, repoPath, baseRef, branchName) {
18367
18410
  return { taskId, path: worktreePath, branch, isNew: true, conflictedFiles: [] };
18368
18411
  }
18369
18412
  async function removeWorktreeAsync(taskId, repoPath, branchName) {
18370
- const worktreePath = join12(WORKTREES_DIR2, taskId);
18413
+ const worktreePath = join13(WORKTREES_DIR2, taskId);
18371
18414
  const branch = branchName ?? `task/${taskId}`;
18372
18415
  const t0 = Date.now();
18373
18416
  logger.info(`[cleanup:${taskId}] starting worktree removal`);
@@ -18389,7 +18432,7 @@ async function removeWorktreeAsync(taskId, repoPath, branchName) {
18389
18432
  logger.info(`[cleanup:${taskId}] done in ${Date.now() - t0}ms`);
18390
18433
  }
18391
18434
  function getWorktreePath(taskId) {
18392
- return join12(WORKTREES_DIR2, taskId);
18435
+ return join13(WORKTREES_DIR2, taskId);
18393
18436
  }
18394
18437
  function resolveWorktreeOwnerId(cardId, cards) {
18395
18438
  const story = Object.values(cards).find((c) => c.type === "story" && (c.subtaskIds ?? []).includes(cardId));
@@ -18849,6 +18892,7 @@ var BoardPoller = class {
18849
18892
  `[poller] ${readyEntries.length} new comment(s) from GitHub PR for "${card.description?.split("\n")[0]?.slice(0, 60) ?? card.id}"`
18850
18893
  );
18851
18894
  await appendActivityLog(workspaceId, taskId, `${readyEntries.length} new comment(s) imported from GitHub PR`);
18895
+ void playNotificationSound("prComment");
18852
18896
  }
18853
18897
  updated = true;
18854
18898
  }
@@ -18859,6 +18903,7 @@ var BoardPoller = class {
18859
18903
  await moveCard(workspaceId, taskId, "done");
18860
18904
  await clearCardSession(workspaceId, taskId);
18861
18905
  await appendActivityLog(workspaceId, taskId, "PR merged on GitHub \u2192 Done");
18906
+ void playNotificationSound("done");
18862
18907
  const boardAfterDone = await loadBoard(workspaceId);
18863
18908
  const ownerId = resolveWorktreeOwnerId(taskId, boardAfterDone.cards);
18864
18909
  const groupCards = Object.values(boardAfterDone.cards).filter(
@@ -18876,6 +18921,7 @@ var BoardPoller = class {
18876
18921
  await moveCard(workspaceId, taskId, "blocked");
18877
18922
  await clearCardSession(workspaceId, taskId);
18878
18923
  await appendActivityLog(workspaceId, taskId, "PR closed without merging \u2192 Blocked");
18924
+ void playNotificationSound("blocked");
18879
18925
  updated = true;
18880
18926
  } else if (info2.mergeable === "CONFLICTING") {
18881
18927
  if (!card.terminalSessions?.some((ts) => !ts.endedAt)) {
@@ -18892,6 +18938,7 @@ var BoardPoller = class {
18892
18938
  await moveCard(workspaceId, taskId, "reopened");
18893
18939
  await appendActivityLog(workspaceId, taskId, `${reason} \u2192 Reopened`);
18894
18940
  await clearCardSession(workspaceId, taskId);
18941
+ void playNotificationSound("reopened");
18895
18942
  updated = true;
18896
18943
  }
18897
18944
  if (updated) stateHub.broadcastWorkspaceUpdate(workspaceId);
@@ -19002,10 +19049,10 @@ function buildCodexEffortOverride(effort) {
19002
19049
 
19003
19050
  // src/agents/playwright-mcp.ts
19004
19051
  init_runtime_config();
19005
- import { join as join13 } from "node:path";
19052
+ import { join as join14 } from "node:path";
19006
19053
  var PLAYWRIGHT_MCP_SERVER_NAME = "playwright";
19007
19054
  function buildBrowserMcpServer(cardId) {
19008
- const outputDir = join13(ATTACHMENTS_DIR, cardId);
19055
+ const outputDir = join14(ATTACHMENTS_DIR, cardId);
19009
19056
  return {
19010
19057
  command: "npx",
19011
19058
  args: ["-y", "@playwright/mcp", "--headless", "--isolated", "--output-dir", outputDir],
@@ -20389,22 +20436,22 @@ init_workspace_state();
20389
20436
  init_workspace_state();
20390
20437
 
20391
20438
  // src/daemon/scheduler.ts
20392
- import { spawn as spawn5 } from "node:child_process";
20439
+ import { spawn as spawn6 } from "node:child_process";
20393
20440
  import { cpSync, existsSync as existsSync10, mkdirSync as mkdirSync9 } from "node:fs";
20394
20441
  import { unlink as unlink2 } from "node:fs/promises";
20395
- import { dirname as dirname5, join as join16, resolve as resolve2 } from "node:path";
20396
- import { fileURLToPath as fileURLToPath3 } from "node:url";
20442
+ import { dirname as dirname6, join as join17, resolve as resolve2 } from "node:path";
20443
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
20397
20444
  init_api_contract();
20398
20445
  init_logger();
20399
20446
 
20400
20447
  // src/core/prompt-resolver.ts
20401
20448
  init_logger();
20402
20449
  import { readFileSync as readFileSync5 } from "node:fs";
20403
- import { isAbsolute, join as join14 } from "node:path";
20450
+ import { isAbsolute, join as join15 } from "node:path";
20404
20451
  function resolvePromptText(prompt, repoPath) {
20405
20452
  if (!prompt) return "";
20406
20453
  if (prompt.source === "inline") return prompt.text;
20407
- const path2 = isAbsolute(prompt.path) ? prompt.path : join14(repoPath, prompt.path);
20454
+ const path2 = isAbsolute(prompt.path) ? prompt.path : join15(repoPath, prompt.path);
20408
20455
  try {
20409
20456
  return readFileSync5(path2, "utf-8");
20410
20457
  } catch (err) {
@@ -20421,7 +20468,7 @@ init_workspace_state();
20421
20468
  import { spawnSync as spawnSync5 } from "node:child_process";
20422
20469
  import { existsSync as existsSync9, readFileSync as readFileSync6 } from "node:fs";
20423
20470
  import { readdir, readFile as readFile2, stat, unlink } from "node:fs/promises";
20424
- import { join as join15 } from "node:path";
20471
+ import { join as join16 } from "node:path";
20425
20472
  init_runtime_config();
20426
20473
  init_api_contract();
20427
20474
  init_logger();
@@ -20706,7 +20753,7 @@ function getSlotTriggerWord(type) {
20706
20753
  }
20707
20754
  var SCREENSHOT_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "webp"]);
20708
20755
  async function attachBrowserArtifacts(workspaceId, card, result, since) {
20709
- const dir = join15(ATTACHMENTS_DIR, card.id);
20756
+ const dir = join16(ATTACHMENTS_DIR, card.id);
20710
20757
  let entries;
20711
20758
  try {
20712
20759
  entries = await readdir(dir);
@@ -20719,7 +20766,7 @@ async function attachBrowserArtifacts(workspaceId, card, result, since) {
20719
20766
  for (const name of entries) {
20720
20767
  const ext = name.split(".").pop()?.toLowerCase() ?? "";
20721
20768
  if (!SCREENSHOT_EXTENSIONS.has(ext)) continue;
20722
- const filePath = join15(dir, name);
20769
+ const filePath = join16(dir, name);
20723
20770
  try {
20724
20771
  const info2 = await stat(filePath);
20725
20772
  if (!info2.isFile() || info2.mtimeMs < since) continue;
@@ -20862,6 +20909,7 @@ async function handleReviewSuccess(card, options) {
20862
20909
  await moveCard(workspaceId, card.id, "ready_for_review");
20863
20910
  await appendActivityLog(workspaceId, card.id, "All reviews passed \u2192 moved to Ready for Review");
20864
20911
  stateHub.broadcastWorkspaceUpdate(workspaceId);
20912
+ void playNotificationSound("readyForReview");
20865
20913
  if (deliveryMode === "off") return;
20866
20914
  if (card.type === "subtask") return;
20867
20915
  if (deliveryMode === "yolo") {
@@ -21022,7 +21070,7 @@ ${diffParts.join("\n")}
21022
21070
  if (newUntracked.length > 0) {
21023
21071
  const newFileContents = [];
21024
21072
  for (const file of newUntracked) {
21025
- const content = readFileSafe(join15(worktreePath, file));
21073
+ const content = readFileSafe(join16(worktreePath, file));
21026
21074
  const ext = file.split(".").pop() ?? "";
21027
21075
  newFileContents.push(content ? `### ${file}
21028
21076
  \`\`\`${ext}
@@ -22019,10 +22067,10 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
22019
22067
  if (filesToCopy.length > 0) {
22020
22068
  const copied = [];
22021
22069
  for (const relPath of filesToCopy) {
22022
- const src = join16(repoPath, relPath);
22070
+ const src = join17(repoPath, relPath);
22023
22071
  if (!existsSync10(src)) continue;
22024
- const dst = join16(worktree.path, relPath);
22025
- mkdirSync9(dirname5(dst), { recursive: true });
22072
+ const dst = join17(worktree.path, relPath);
22073
+ mkdirSync9(dirname6(dst), { recursive: true });
22026
22074
  try {
22027
22075
  cpSync(src, dst, { recursive: true });
22028
22076
  copied.push(relPath);
@@ -22038,7 +22086,7 @@ ${assistantSystemPrompt}` : assistantSystemPrompt;
22038
22086
  await appendActivityLog(workspaceId, taskId, `Running: ${installCommand.trim()}`);
22039
22087
  stateHub.broadcastWorkspaceUpdate(workspaceId);
22040
22088
  await new Promise((resolve5) => {
22041
- const proc = spawn5("sh", ["-c", installCommand.trim()], {
22089
+ const proc = spawn6("sh", ["-c", installCommand.trim()], {
22042
22090
  cwd: worktree.path,
22043
22091
  stdio: "ignore",
22044
22092
  env: { ...process.env, REPO_PATH: repoPath }
@@ -22242,6 +22290,7 @@ ${devSystemPromptResult.text}`;
22242
22290
  if (!hasReviewSlots) {
22243
22291
  await moveCard(workspaceId, taskId, "ready_for_review");
22244
22292
  await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 moved to Ready for Review");
22293
+ void playNotificationSound("readyForReview");
22245
22294
  } else {
22246
22295
  await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 AI review starting");
22247
22296
  }
@@ -22268,6 +22317,7 @@ ${devSystemPromptResult.text}`;
22268
22317
  );
22269
22318
  }
22270
22319
  await moveCard(workspaceId, taskId, destination);
22320
+ void playNotificationSound(destination === "blocked" ? "blocked" : "reopened");
22271
22321
  }
22272
22322
  stateHub.broadcastWorkspaceUpdate(workspaceId);
22273
22323
  this.options.onTaskCompleted(taskId);
@@ -22469,6 +22519,7 @@ ${devSystemPromptResult.text}`;
22469
22519
  if (!hookHasReview) {
22470
22520
  await moveCard(workspaceId, taskId, "ready_for_review");
22471
22521
  await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 moved to Ready for Review");
22522
+ void playNotificationSound("readyForReview");
22472
22523
  } else {
22473
22524
  await appendActivityLog(workspaceId, taskId, "Agent finished \u2192 AI review starting");
22474
22525
  }
@@ -22568,8 +22619,8 @@ ${devSystemPromptResult.text}`;
22568
22619
  }
22569
22620
  };
22570
22621
  function getMcpServerPath() {
22571
- const thisFile = fileURLToPath3(import.meta.url);
22572
- const thisDir = dirname5(thisFile);
22622
+ const thisFile = fileURLToPath4(import.meta.url);
22623
+ const thisDir = dirname6(thisFile);
22573
22624
  const isDev = thisFile.endsWith(".ts");
22574
22625
  if (isDev) {
22575
22626
  const projectRoot = resolve2(thisDir, "../..");
@@ -26406,13 +26457,13 @@ init_runtime_config();
26406
26457
  import { spawnSync as spawnSync8 } from "node:child_process";
26407
26458
  import { existsSync as existsSync12, readdirSync as readdirSync2, statSync } from "node:fs";
26408
26459
  import { homedir as homedir4 } from "node:os";
26409
- import { dirname as dirname6, join as join18, resolve as resolve3 } from "node:path";
26460
+ import { dirname as dirname7, join as join19, resolve as resolve3 } from "node:path";
26410
26461
 
26411
26462
  // src/core/terminal-apps.ts
26412
26463
  import { spawnSync as spawnSync7 } from "node:child_process";
26413
26464
  import { existsSync as existsSync11 } from "node:fs";
26414
26465
  import { homedir as homedir3 } from "node:os";
26415
- import { join as join17 } from "node:path";
26466
+ import { join as join18 } from "node:path";
26416
26467
  var MACOS_TERMINALS = [
26417
26468
  { bundle: "Terminal", label: "Terminal" },
26418
26469
  { bundle: "iTerm", label: "iTerm" },
@@ -26444,7 +26495,7 @@ function appExists(bundle) {
26444
26495
  `/Applications/${bundle}.app`,
26445
26496
  `/System/Applications/${bundle}.app`,
26446
26497
  `/System/Applications/Utilities/${bundle}.app`,
26447
- join17(homedir3(), "Applications", `${bundle}.app`)
26498
+ join18(homedir3(), "Applications", `${bundle}.app`)
26448
26499
  ];
26449
26500
  return paths.some((p2) => existsSync11(p2));
26450
26501
  }
@@ -26520,15 +26571,15 @@ var openTerminal = async (path2) => {
26520
26571
  };
26521
26572
  var listDir = async (path2, includeFiles, showHidden) => {
26522
26573
  let target = resolve3(path2 || homedir4());
26523
- while (target !== dirname6(target) && !(existsSync12(target) && statSync(target).isDirectory())) {
26524
- target = dirname6(target);
26574
+ while (target !== dirname7(target) && !(existsSync12(target) && statSync(target).isDirectory())) {
26575
+ target = dirname7(target);
26525
26576
  }
26526
- const parent = dirname6(target);
26577
+ const parent = dirname7(target);
26527
26578
  const visible = (name) => showHidden || !name.startsWith(".");
26528
26579
  try {
26529
26580
  const entries = readdirSync2(target, { withFileTypes: true });
26530
- const dirs = entries.filter((e) => e.isDirectory() && visible(e.name)).map((e) => ({ name: e.name, path: join18(target, e.name) })).sort((a, b2) => a.name.localeCompare(b2.name));
26531
- const files = includeFiles ? entries.filter((e) => e.isFile() && visible(e.name)).map((e) => ({ name: e.name, path: join18(target, e.name) })).sort((a, b2) => a.name.localeCompare(b2.name)) : [];
26581
+ const dirs = entries.filter((e) => e.isDirectory() && visible(e.name)).map((e) => ({ name: e.name, path: join19(target, e.name) })).sort((a, b2) => a.name.localeCompare(b2.name));
26582
+ const files = includeFiles ? entries.filter((e) => e.isFile() && visible(e.name)).map((e) => ({ name: e.name, path: join19(target, e.name) })).sort((a, b2) => a.name.localeCompare(b2.name)) : [];
26532
26583
  return { current: target, parent: parent !== target ? parent : null, dirs, files };
26533
26584
  } catch {
26534
26585
  return { current: target, parent: parent !== target ? parent : null, dirs: [], files: [] };
@@ -26984,7 +27035,7 @@ var removeProject = async (workspaceId) => {
26984
27035
  const cards = boardCards;
26985
27036
  enqueueCleanup2(async () => {
26986
27037
  const { rm: rm3 } = await import("node:fs/promises");
26987
- const { join: join22 } = await import("node:path");
27038
+ const { join: join23 } = await import("node:path");
26988
27039
  for (const [cardId, card] of Object.entries(cards)) {
26989
27040
  if (resolveWorktreeOwnerId(cardId, cards) === cardId) {
26990
27041
  await removeWorktreeAsync(cardId, repoPath, card.branchName).catch((err) => {
@@ -26992,11 +27043,11 @@ var removeProject = async (workspaceId) => {
26992
27043
  });
26993
27044
  }
26994
27045
  }
26995
- await rm3(join22(WORKSPACES_DIR, workspaceId), { recursive: true, force: true }).catch((err) => {
27046
+ await rm3(join23(WORKSPACES_DIR, workspaceId), { recursive: true, force: true }).catch((err) => {
26996
27047
  logger.warn(`[cleanup:project:${workspaceId}] workspace dir failed: ${String(err)}`);
26997
27048
  });
26998
27049
  for (const cardId of Object.keys(cards)) {
26999
- await rm3(join22(ATTACHMENTS_DIR, cardId), { recursive: true, force: true }).catch(() => {
27050
+ await rm3(join23(ATTACHMENTS_DIR, cardId), { recursive: true, force: true }).catch(() => {
27000
27051
  });
27001
27052
  }
27002
27053
  logger.info(`[cleanup:project:${workspaceId}] done`);
@@ -27287,13 +27338,13 @@ init_runtime_config();
27287
27338
  // src/tunnel/cloudflare-setup.ts
27288
27339
  init_runtime_config();
27289
27340
  init_logger();
27290
- import { execFile as execFile9, spawn as spawn6 } from "node:child_process";
27341
+ import { execFile as execFile9, spawn as spawn7 } from "node:child_process";
27291
27342
  import { mkdir as mkdir3, writeFile as writeFile3, readFile as readFile3, access, unlink as unlink4 } from "node:fs/promises";
27292
27343
  import { homedir as homedir5 } from "node:os";
27293
- import { join as join19 } from "node:path";
27344
+ import { join as join20 } from "node:path";
27294
27345
  import { promisify as promisify9 } from "node:util";
27295
27346
  var execFileAsync7 = promisify9(execFile9);
27296
- var CLOUDFLARED_DIR = join19(homedir5(), ".cloudflared");
27347
+ var CLOUDFLARED_DIR = join20(homedir5(), ".cloudflared");
27297
27348
  async function checkCloudflaredInstalled() {
27298
27349
  try {
27299
27350
  const { stdout } = await execFileAsync7("cloudflared", ["--version"]);
@@ -27305,7 +27356,7 @@ async function checkCloudflaredInstalled() {
27305
27356
  }
27306
27357
  async function checkCloudflaredAuth() {
27307
27358
  try {
27308
- await access(join19(CLOUDFLARED_DIR, "cert.pem"));
27359
+ await access(join20(CLOUDFLARED_DIR, "cert.pem"));
27309
27360
  return true;
27310
27361
  } catch {
27311
27362
  return false;
@@ -27316,12 +27367,12 @@ async function openCloudflaredLogin(force = false) {
27316
27367
  if (alreadyLoggedIn && !force) return { alreadyLoggedIn: true };
27317
27368
  if (force) {
27318
27369
  try {
27319
- await unlink4(join19(CLOUDFLARED_DIR, "cert.pem"));
27370
+ await unlink4(join20(CLOUDFLARED_DIR, "cert.pem"));
27320
27371
  } catch {
27321
27372
  }
27322
27373
  }
27323
27374
  return new Promise((resolve5) => {
27324
- const proc = spawn6("cloudflared", ["tunnel", "login"], {
27375
+ const proc = spawn7("cloudflared", ["tunnel", "login"], {
27325
27376
  stdio: ["ignore", "pipe", "pipe"]
27326
27377
  });
27327
27378
  let buffer = "";
@@ -27334,7 +27385,7 @@ async function openCloudflaredLogin(force = false) {
27334
27385
  resolved = true;
27335
27386
  const loginUrl = match2[1].trim();
27336
27387
  logger.info(`[cloudflared-login] Auth URL: ${loginUrl}`);
27337
- spawn6("open", [loginUrl], { stdio: "ignore" });
27388
+ spawn7("open", [loginUrl], { stdio: "ignore" });
27338
27389
  proc.unref();
27339
27390
  resolve5({ alreadyLoggedIn: false, loginUrl });
27340
27391
  }
@@ -27384,7 +27435,7 @@ async function createTunnel(tunnelName) {
27384
27435
  }
27385
27436
  }
27386
27437
  async function writeTunnelConfig(tunnelId, tunnelName, domain) {
27387
- const credentialsFile = join19(CLOUDFLARED_DIR, `${tunnelId}.json`);
27438
+ const credentialsFile = join20(CLOUDFLARED_DIR, `${tunnelId}.json`);
27388
27439
  const config = [
27389
27440
  `tunnel: ${tunnelId}`,
27390
27441
  `credentials-file: ${credentialsFile}`,
@@ -27395,12 +27446,12 @@ async function writeTunnelConfig(tunnelId, tunnelName, domain) {
27395
27446
  ` - service: http_status:404`
27396
27447
  ].join("\n");
27397
27448
  await mkdir3(CLOUDFLARED_DIR, { recursive: true });
27398
- await writeFile3(join19(CLOUDFLARED_DIR, "config.yml"), config, "utf-8");
27449
+ await writeFile3(join20(CLOUDFLARED_DIR, "config.yml"), config, "utf-8");
27399
27450
  logger.info(`[tunnel-setup] Wrote ~/.cloudflared/config.yml for tunnel ${tunnelName}`);
27400
27451
  }
27401
27452
  async function readTunnelConfig() {
27402
27453
  try {
27403
- const raw2 = await readFile3(join19(CLOUDFLARED_DIR, "config.yml"), "utf-8");
27454
+ const raw2 = await readFile3(join20(CLOUDFLARED_DIR, "config.yml"), "utf-8");
27404
27455
  const tunnelMatch = raw2.match(/^tunnel:\s*(.+)$/m);
27405
27456
  const hostnameMatch = raw2.match(/hostname:\s*(.+)$/m);
27406
27457
  return {
@@ -27449,9 +27500,9 @@ var resetTunnel = async () => {
27449
27500
  await updateGlobalConfig({ tunnelId: void 0, tunnelDomain: void 0, autoStartTunnel: false });
27450
27501
  const { unlink: unlink5 } = await import("node:fs/promises");
27451
27502
  const { homedir: homedir6 } = await import("node:os");
27452
- const { join: join22 } = await import("node:path");
27503
+ const { join: join23 } = await import("node:path");
27453
27504
  try {
27454
- await unlink5(join22(homedir6(), ".cloudflared", "config.yml"));
27505
+ await unlink5(join23(homedir6(), ".cloudflared", "config.yml"));
27455
27506
  } catch {
27456
27507
  }
27457
27508
  };
@@ -27484,7 +27535,7 @@ import { z as z15 } from "zod";
27484
27535
  init_workspace_state();
27485
27536
  import { existsSync as existsSync13 } from "node:fs";
27486
27537
  import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
27487
- import { dirname as dirname7, isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
27538
+ import { dirname as dirname8, isAbsolute as isAbsolute2, resolve as resolve4 } from "node:path";
27488
27539
  var resolvePromptPath = async (workspaceId, requestedPath) => {
27489
27540
  const workspaces = await listWorkspaces();
27490
27541
  const ws = workspaces.find((w2) => w2.workspaceId === workspaceId);
@@ -27521,7 +27572,7 @@ var deleteWorkflow = async (workspaceId, workflowId) => {
27521
27572
  };
27522
27573
  var writePromptFile = async (workspaceId, path2, content) => {
27523
27574
  const targetPath = await resolvePromptPath(workspaceId, path2);
27524
- await mkdir4(dirname7(targetPath), { recursive: true });
27575
+ await mkdir4(dirname8(targetPath), { recursive: true });
27525
27576
  await writeFile4(targetPath, content, "utf-8");
27526
27577
  return { path: path2 };
27527
27578
  };
@@ -27745,8 +27796,8 @@ var RuntimeStateHub = class {
27745
27796
  // src/core/update-check.ts
27746
27797
  init_paths();
27747
27798
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
27748
- import { join as join20 } from "node:path";
27749
- var CACHE_FILE = join20(WHIPPED_HOME_DIR, "update-check.json");
27799
+ import { join as join21 } from "node:path";
27800
+ var CACHE_FILE = join21(WHIPPED_HOME_DIR, "update-check.json");
27750
27801
  var REGISTRY_URL = "https://registry.npmjs.org/whipped/latest";
27751
27802
  var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1e3;
27752
27803
  function readCache() {
@@ -27797,7 +27848,7 @@ function scheduleUpdateChecks(currentVersion, onUpdate) {
27797
27848
  }
27798
27849
 
27799
27850
  // src/server/runtime-server.ts
27800
- var __dirname2 = fileURLToPath4(new URL(".", import.meta.url));
27851
+ var __dirname2 = fileURLToPath5(new URL(".", import.meta.url));
27801
27852
  async function cleanupStaleTasks(workspaceId, hub) {
27802
27853
  const board = await loadBoard(workspaceId);
27803
27854
  const now = Date.now();
@@ -27894,6 +27945,7 @@ async function createRuntimeServer(options) {
27894
27945
  session.status = "error";
27895
27946
  session.errorMessage = `Process exited with code ${exitCode}`;
27896
27947
  stateHub.broadcastRunSessionChange(workspaceId, cardId, "error", session.errorMessage);
27948
+ void playNotificationSound("runError");
27897
27949
  }
27898
27950
  });
27899
27951
  }
@@ -28032,8 +28084,8 @@ async function createRuntimeServer(options) {
28032
28084
  };
28033
28085
  }
28034
28086
  const apiApp = createApiApp(createContext());
28035
- const webUiDistPath = join21(__dirname2, "web-ui");
28036
- const webUiIndexPath = join21(webUiDistPath, "index.html");
28087
+ const webUiDistPath = join22(__dirname2, "web-ui");
28088
+ const webUiIndexPath = join22(webUiDistPath, "index.html");
28037
28089
  const hasWebUi = existsSync14(webUiIndexPath);
28038
28090
  const httpServer = createServer(async (req, res) => {
28039
28091
  const url = new URL(req.url ?? "/", `http://${host}`);
@@ -28267,7 +28319,7 @@ async function createRuntimeServer(options) {
28267
28319
  res.end("Bad request");
28268
28320
  return;
28269
28321
  }
28270
- const filePath = join21(ATTACHMENTS_DIR, cardId, filename);
28322
+ const filePath = join22(ATTACHMENTS_DIR, cardId, filename);
28271
28323
  const { readFile: readFile5 } = await import("node:fs/promises");
28272
28324
  try {
28273
28325
  const data = await readFile5(filePath);
@@ -28342,7 +28394,7 @@ async function createRuntimeServer(options) {
28342
28394
  return;
28343
28395
  }
28344
28396
  if (hasWebUi) {
28345
- const filePath = url.pathname === "/" || !url.pathname.includes(".") ? webUiIndexPath : join21(webUiDistPath, url.pathname);
28397
+ const filePath = url.pathname === "/" || !url.pathname.includes(".") ? webUiIndexPath : join22(webUiDistPath, url.pathname);
28346
28398
  if (existsSync14(filePath)) {
28347
28399
  const content = readFileSync8(filePath);
28348
28400
  res.writeHead(200, { "Content-Type": getContentType(filePath) });
@@ -28561,7 +28613,7 @@ process.on("uncaughtException", (err) => {
28561
28613
  if (err.code === "EPIPE" || err.code === "ECONNRESET") return;
28562
28614
  throw err;
28563
28615
  });
28564
- var VERSION9 = true ? "0.5.1" : "0.0.0-dev";
28616
+ var VERSION9 = true ? "0.6.0" : "0.0.0-dev";
28565
28617
  async function isPortAvailable(port, host) {
28566
28618
  return new Promise((resolve5) => {
28567
28619
  const probe = createServer2();
@@ -33,7 +33,7 @@ function highestWorkflowLevel(workflow) {
33
33
  }
34
34
  return LEVEL_ORDER[bestIdx] ?? "medium";
35
35
  }
36
- var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
36
+ var ASSISTANT_AGENT_PREFIX, runtimeAgentIdSchema, effortLevelSchema, agentModelChoiceSchema, workflowSlotTypeSchema, tierLevelSchema, LEVEL_ORDER, modelPairSchema, pairSelectionModeSchema, SLOT_TOOL_IDS, slotToolSchema, slotModelConfigSchema, cardModelConfigSchema, promptValueSchema, EMPTY_INLINE_PROMPT, workflowSlotSchema, DEFAULT_MODEL_PAIR, DEFAULT_SLOT_MODEL_FIELDS, workflowSchema, DEFAULT_WORKFLOW, DEFAULT_STORY_WORKFLOW, DEFAULT_GIT_INSTRUCTIONS, runtimeBoardColumnIdSchema, BOARD_COLUMNS, reviewActorSchema, reviewIssueSchema, reviewAttachmentSchema, runtimeReviewCommentSchema, runtimeActivityEntrySchema, runtimeTaskSessionStateSchema, runtimeTerminalSessionEntrySchema, runtimeCardPrioritySchema, cardTypeSchema, runtimePrMetaSchema, runtimeBoardCardSchema, runtimeBoardColumnSchema, runtimeBoardDataSchema, notificationSoundsConfigSchema, runtimeGlobalConfigSchema, runtimeGithubConfigSchema, runtimeWorktreeSetupSchema, runtimeProjectSecretSchema, runtimeProjectConfigSchema, runtimeWorkspaceStateResponseSchema, runtimeWorkspaceStateSaveRequestSchema, runtimeVisualElementSchema, runtimeVisualCommentSchema, runtimeCardCreateRequestSchema, runtimeBulkCardImportItemSchema, runtimeBulkCardsCreateRequestSchema, runtimeCardMoveRequestSchema, runtimeCardUpdateRequestSchema, memoryScopeSchema, memoryTypeSchema, memorySourceTypeSchema, memoryStatusSchema, runtimeMemoryOriginAgentSchema, runtimeMemorySchema, recurringScheduleKindSchema, recurringScheduleSchema, recurringRunStatusSchema, recurringRunTriggerSchema, recurringAgentRunSchema, recurringAgentSchema, recurringAgentCreateRequestSchema, recurringAgentUpdateRequestSchema, projectFolderSchema, topLevelItemSchema, projectsLayoutSchema, runtimeProjectSchema;
37
37
  var init_api_contract = __esm({
38
38
  "src/core/api-contract.ts"() {
39
39
  "use strict";
@@ -351,6 +351,15 @@ Do NOT include:
351
351
  columns: z.array(runtimeBoardColumnSchema),
352
352
  cards: z.record(z.string(), runtimeBoardCardSchema)
353
353
  });
354
+ notificationSoundsConfigSchema = z.object({
355
+ enabled: z.boolean().default(false),
356
+ readyForReview: z.boolean().default(true),
357
+ prComment: z.boolean().default(true),
358
+ done: z.boolean().default(true),
359
+ reopened: z.boolean().default(true),
360
+ blocked: z.boolean().default(true),
361
+ runError: z.boolean().default(true)
362
+ });
354
363
  runtimeGlobalConfigSchema = z.object({
355
364
  defaultAgent: runtimeAgentIdSchema.default("claude"),
356
365
  maxParallelTasks: z.number().int().positive().default(4),
@@ -359,6 +368,7 @@ Do NOT include:
359
368
  pollingIntervalSeconds: z.number().int().positive().default(30),
360
369
  prPollingIntervalSeconds: z.number().int().positive().default(60),
361
370
  terminalApp: z.string().optional(),
371
+ notificationSounds: notificationSoundsConfigSchema.prefault({}),
362
372
  slackEnabled: z.boolean().default(true),
363
373
  slackBotToken: z.string().optional(),
364
374
  slackSigningSecret: z.string().optional(),
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -21339,22 +21339,39 @@ const createLucideIcon = (iconName, iconNode) => {
21339
21339
  * This source code is licensed under the ISC license.
21340
21340
  * See the LICENSE file in the root directory of this source tree.
21341
21341
  */
21342
- const __iconNode$18 = [
21342
+ const __iconNode$19 = [
21343
21343
  ["path", { d: "m12 19-7-7 7-7", key: "1l729n" }],
21344
21344
  ["path", { d: "M19 12H5", key: "x3x0zl" }]
21345
21345
  ];
21346
- const ArrowLeft = createLucideIcon("arrow-left", __iconNode$18);
21346
+ const ArrowLeft = createLucideIcon("arrow-left", __iconNode$19);
21347
21347
  /**
21348
21348
  * @license lucide-react v0.577.0 - ISC
21349
21349
  *
21350
21350
  * This source code is licensed under the ISC license.
21351
21351
  * See the LICENSE file in the root directory of this source tree.
21352
21352
  */
21353
- const __iconNode$17 = [
21353
+ const __iconNode$18 = [
21354
21354
  ["path", { d: "M5 12h14", key: "1ays0h" }],
21355
21355
  ["path", { d: "m12 5 7 7-7 7", key: "xquz4c" }]
21356
21356
  ];
21357
- const ArrowRight = createLucideIcon("arrow-right", __iconNode$17);
21357
+ const ArrowRight = createLucideIcon("arrow-right", __iconNode$18);
21358
+ /**
21359
+ * @license lucide-react v0.577.0 - ISC
21360
+ *
21361
+ * This source code is licensed under the ISC license.
21362
+ * See the LICENSE file in the root directory of this source tree.
21363
+ */
21364
+ const __iconNode$17 = [
21365
+ ["path", { d: "M10.268 21a2 2 0 0 0 3.464 0", key: "vwvbt9" }],
21366
+ [
21367
+ "path",
21368
+ {
21369
+ d: "M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326",
21370
+ key: "11g9vi"
21371
+ }
21372
+ ]
21373
+ ];
21374
+ const Bell = createLucideIcon("bell", __iconNode$17);
21358
21375
  /**
21359
21376
  * @license lucide-react v0.577.0 - ISC
21360
21377
  *
@@ -27543,6 +27560,15 @@ const runtimeBoardDataSchema = object({
27543
27560
  columns: array(runtimeBoardColumnSchema),
27544
27561
  cards: record(string$2(), runtimeBoardCardSchema)
27545
27562
  });
27563
+ const notificationSoundsConfigSchema = object({
27564
+ enabled: boolean$1().default(false),
27565
+ readyForReview: boolean$1().default(true),
27566
+ prComment: boolean$1().default(true),
27567
+ done: boolean$1().default(true),
27568
+ reopened: boolean$1().default(true),
27569
+ blocked: boolean$1().default(true),
27570
+ runError: boolean$1().default(true)
27571
+ });
27546
27572
  const runtimeGlobalConfigSchema = object({
27547
27573
  defaultAgent: runtimeAgentIdSchema.default("claude"),
27548
27574
  maxParallelTasks: number$2().int().positive().default(4),
@@ -27551,6 +27577,7 @@ const runtimeGlobalConfigSchema = object({
27551
27577
  pollingIntervalSeconds: number$2().int().positive().default(30),
27552
27578
  prPollingIntervalSeconds: number$2().int().positive().default(60),
27553
27579
  terminalApp: string$2().optional(),
27580
+ notificationSounds: notificationSoundsConfigSchema.prefault({}),
27554
27581
  slackEnabled: boolean$1().default(true),
27555
27582
  slackBotToken: string$2().optional(),
27556
27583
  slackSigningSecret: string$2().optional(),
@@ -78159,6 +78186,7 @@ const globalConfigFormSchema = runtimeGlobalConfigSchema.extend({
78159
78186
  pollingIntervalSeconds: number$1().int().positive(),
78160
78187
  prPollingIntervalSeconds: number$1().int().positive()
78161
78188
  });
78189
+ const notificationSoundsFormSchema = notificationSoundsConfigSchema;
78162
78190
  const environmentFormSchema = runtimeWorktreeSetupSchema.extend({
78163
78191
  startCommand: string$2().default("")
78164
78192
  });
@@ -78180,19 +78208,19 @@ const instructionsFormSchema = object({
78180
78208
  systemPrompt: string$2().optional(),
78181
78209
  gitInstructions: string$2().optional()
78182
78210
  });
78183
- function PageHeader$1({ title, description }) {
78211
+ function PageHeader$2({ title, description }) {
78184
78212
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "shrink-0 flex flex-col gap-1 px-10 py-6 border-b border-[#2a2a35]", children: [
78185
78213
  /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-xl font-semibold text-[#f0f0f5]", children: title }),
78186
78214
  /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[13px] text-[#60607a]", children: description })
78187
78215
  ] });
78188
78216
  }
78189
- function SectionDivider$3({ title }) {
78217
+ function SectionDivider$4({ title }) {
78190
78218
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
78191
78219
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[15px] font-semibold text-[#f0f0f5]", children: title }),
78192
78220
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 h-px bg-[#1a1a1f]" })
78193
78221
  ] });
78194
78222
  }
78195
- function FieldRow$1({ label, description, children }) {
78223
+ function FieldRow$2({ label, description, children }) {
78196
78224
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
78197
78225
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [
78198
78226
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[13px] font-medium text-[#c0c0d0]", children: label }),
@@ -78222,7 +78250,7 @@ function GlobalSettings({ section }) {
78222
78250
  });
78223
78251
  if (!config2) {
78224
78252
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col", children: [
78225
- /* @__PURE__ */ jsxRuntimeExports.jsx(PageHeader$1, { title: "Global Runtime Config", description: "Settings that apply across all projects" }),
78253
+ /* @__PURE__ */ jsxRuntimeExports.jsx(PageHeader$2, { title: "Global Runtime Config", description: "Settings that apply across all projects" }),
78226
78254
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-20 text-sm text-[#60607a]", children: "Loading..." })
78227
78255
  ] });
78228
78256
  }
@@ -78231,13 +78259,13 @@ function GlobalSettings({ section }) {
78231
78259
  label: t2.label
78232
78260
  }));
78233
78261
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
78234
- /* @__PURE__ */ jsxRuntimeExports.jsx(PageHeader$1, { title: "Global Runtime Config", description: "Settings that apply across all projects" }),
78262
+ /* @__PURE__ */ jsxRuntimeExports.jsx(PageHeader$2, { title: "Global Runtime Config", description: "Settings that apply across all projects" }),
78235
78263
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 overflow-y-auto px-10 py-6", children: [
78236
78264
  /* @__PURE__ */ jsxRuntimeExports.jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit, className: "flex flex-col gap-6", children: [
78237
78265
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4", children: [
78238
- /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$3, { title: "Defaults" }),
78239
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "Default Agent", description: "Agent binary for new workflow slots", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RHFSelect_default, { wrapperClassName: "w-fit", name: "defaultAgent", className: selectClassName, children: AGENT_BINARY_OPTIONS.map((o2) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectOption_default, { value: o2.value, label: o2.label }, o2.value)) }) }),
78240
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "Terminal App", description: "Application for opening terminals", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78266
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$4, { title: "Defaults" }),
78267
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "Default Agent", description: "Agent binary for new workflow slots", children: /* @__PURE__ */ jsxRuntimeExports.jsx(RHFSelect_default, { wrapperClassName: "w-fit", name: "defaultAgent", className: selectClassName, children: AGENT_BINARY_OPTIONS.map((o2) => /* @__PURE__ */ jsxRuntimeExports.jsx(SelectOption_default, { value: o2.value, label: o2.label }, o2.value)) }) }),
78268
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "Terminal App", description: "Application for opening terminals", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78241
78269
  RHFSelect_default,
78242
78270
  {
78243
78271
  wrapperClassName: "w-fit",
@@ -78250,8 +78278,8 @@ function GlobalSettings({ section }) {
78250
78278
  ) })
78251
78279
  ] }),
78252
78280
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4", children: [
78253
- /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$3, { title: "Concurrency & Limits" }),
78254
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "Max Parallel Tasks", description: "Concurrent task executions", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78281
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$4, { title: "Concurrency & Limits" }),
78282
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "Max Parallel Tasks", description: "Concurrent task executions", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78255
78283
  RHFNumberInput_default,
78256
78284
  {
78257
78285
  name: "maxParallelTasks",
@@ -78260,7 +78288,7 @@ function GlobalSettings({ section }) {
78260
78288
  inputClassName: "text-center"
78261
78289
  }
78262
78290
  ) }),
78263
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "Max Parallel QA", description: "Concurrent QA slot runs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78291
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "Max Parallel QA", description: "Concurrent QA slot runs", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78264
78292
  RHFNumberInput_default,
78265
78293
  {
78266
78294
  name: "maxParallelQA",
@@ -78269,7 +78297,7 @@ function GlobalSettings({ section }) {
78269
78297
  inputClassName: "text-center"
78270
78298
  }
78271
78299
  ) }),
78272
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "Max Auto-Fix Attempts", description: "Retries before marking blocked", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78300
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "Max Auto-Fix Attempts", description: "Retries before marking blocked", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78273
78301
  RHFNumberInput_default,
78274
78302
  {
78275
78303
  name: "maxAutoFixAttempts",
@@ -78280,8 +78308,8 @@ function GlobalSettings({ section }) {
78280
78308
  ) })
78281
78309
  ] }),
78282
78310
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4", children: [
78283
- /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$3, { title: "Polling" }),
78284
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "Polling Interval", description: "Board refresh interval (seconds)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78311
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$4, { title: "Polling" }),
78312
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "Polling Interval", description: "Board refresh interval (seconds)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78285
78313
  RHFNumberInput_default,
78286
78314
  {
78287
78315
  name: "pollingIntervalSeconds",
@@ -78290,7 +78318,7 @@ function GlobalSettings({ section }) {
78290
78318
  inputClassName: "text-center"
78291
78319
  }
78292
78320
  ) }),
78293
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "PR Poll Interval", description: "PR status check interval (seconds)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78321
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "PR Poll Interval", description: "PR status check interval (seconds)", children: /* @__PURE__ */ jsxRuntimeExports.jsx(
78294
78322
  RHFNumberInput_default,
78295
78323
  {
78296
78324
  name: "prPollingIntervalSeconds",
@@ -78303,12 +78331,86 @@ function GlobalSettings({ section }) {
78303
78331
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end pt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button_default, { type: "submit", disabled: saving, children: saving ? "Saving..." : "Save" }) })
78304
78332
  ] }) }),
78305
78333
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4 pt-6 mt-6 border-t border-[#1a1a1f]", children: [
78306
- /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$3, { title: "Session" }),
78307
- /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: "Sign out", description: "End your session on this browser. Local access stays open.", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button_default, { variant: "outlined", onClick: () => logout(), children: "Sign out" }) })
78334
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$4, { title: "Session" }),
78335
+ /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$2, { label: "Sign out", description: "End your session on this browser. Local access stays open.", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button_default, { variant: "outlined", onClick: () => logout(), children: "Sign out" }) })
78308
78336
  ] })
78309
78337
  ] })
78310
78338
  ] });
78311
78339
  }
78340
+ function PageHeader$1({ title, description }) {
78341
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "shrink-0 flex flex-col gap-1 px-10 py-6 border-b border-[#2a2a35]", children: [
78342
+ /* @__PURE__ */ jsxRuntimeExports.jsx("h1", { className: "text-xl font-semibold text-[#f0f0f5]", children: title }),
78343
+ /* @__PURE__ */ jsxRuntimeExports.jsx("p", { className: "text-[13px] text-[#60607a]", children: description })
78344
+ ] });
78345
+ }
78346
+ function SectionDivider$3({ title }) {
78347
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-3", children: [
78348
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[15px] font-semibold text-[#f0f0f5]", children: title }),
78349
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 h-px bg-[#1a1a1f]" })
78350
+ ] });
78351
+ }
78352
+ function FieldRow$1({ label, description, children }) {
78353
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex items-center gap-4", children: [
78354
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col gap-0.5", children: [
78355
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[13px] font-medium text-[#c0c0d0]", children: label }),
78356
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { className: "text-[11px] text-[#60607a]", children: description })
78357
+ ] }),
78358
+ children
78359
+ ] });
78360
+ }
78361
+ const PER_EVENT_ROWS = [
78362
+ { name: "readyForReview", label: "Ready for Review", description: "A task finished and is waiting for your review" },
78363
+ { name: "prComment", label: "New PR comment", description: "A reviewer commented on a task's pull request" },
78364
+ { name: "done", label: "Done / PR merged", description: "A task's pull request was merged" },
78365
+ { name: "reopened", label: "Reopened", description: "Changes were requested — the task needs another pass" },
78366
+ { name: "blocked", label: "Blocked", description: "A task was blocked (PR closed, or auto-fix attempts exhausted)" },
78367
+ { name: "runError", label: "Run error", description: "A run/preview process exited with an error" }
78368
+ ];
78369
+ function NotificationsSettings() {
78370
+ const { data: config2 } = useRead((api) => api("config").GET());
78371
+ const { trigger: saveConfig, loading: saving } = useWrite((api) => api("config").PUT());
78372
+ const methods = useForm({
78373
+ resolver: u(notificationSoundsFormSchema),
78374
+ values: config2 == null ? void 0 : config2.notificationSounds
78375
+ });
78376
+ const onSubmit = methods.handleSubmit(async (values) => {
78377
+ const res = await saveConfig({ body: { notificationSounds: values } });
78378
+ if (res.error) {
78379
+ toast.error("Failed to save settings");
78380
+ return;
78381
+ }
78382
+ methods.reset(values);
78383
+ toast.success("Settings saved");
78384
+ });
78385
+ if (!config2) {
78386
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col", children: [
78387
+ /* @__PURE__ */ jsxRuntimeExports.jsx(PageHeader$1, { title: "Notifications", description: "Sounds played on the daemon host when tasks need you" }),
78388
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex items-center justify-center py-20 text-sm text-[#60607a]", children: "Loading..." })
78389
+ ] });
78390
+ }
78391
+ const enabled = methods.watch("enabled");
78392
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex-1 flex flex-col overflow-hidden", children: [
78393
+ /* @__PURE__ */ jsxRuntimeExports.jsx(PageHeader$1, { title: "Notifications", description: "Sounds played on the daemon host when tasks need you" }),
78394
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-y-auto px-10 py-6", children: /* @__PURE__ */ jsxRuntimeExports.jsx(FormProvider, { ...methods, children: /* @__PURE__ */ jsxRuntimeExports.jsxs("form", { onSubmit, className: "flex flex-col gap-6", children: [
78395
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: "flex flex-col gap-4", children: [
78396
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$3, { title: "Sounds" }),
78397
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
78398
+ FieldRow$1,
78399
+ {
78400
+ label: "Notification sounds",
78401
+ description: "Play a sound on the machine running Whipped — works even when no browser is open",
78402
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(RHFSwitch_default, { name: "enabled" })
78403
+ }
78404
+ )
78405
+ ] }),
78406
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { className: enabled ? "flex flex-col gap-4" : "flex flex-col gap-4 opacity-50", children: [
78407
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SectionDivider$3, { title: "Events" }),
78408
+ PER_EVENT_ROWS.map((row) => /* @__PURE__ */ jsxRuntimeExports.jsx(FieldRow$1, { label: row.label, description: row.description, children: /* @__PURE__ */ jsxRuntimeExports.jsx(RHFSwitch_default, { name: row.name }) }, row.name))
78409
+ ] }),
78410
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex justify-end pt-2", children: /* @__PURE__ */ jsxRuntimeExports.jsx(Button_default, { type: "submit", disabled: saving, children: saving ? "Saving..." : "Save" }) })
78411
+ ] }) }) })
78412
+ ] });
78413
+ }
78312
78414
  function FilePickerDialog({ initialPath, onSelect, onClose }) {
78313
78415
  const [path2, setPath] = reactExports.useState(initialPath ?? "");
78314
78416
  const [selectedFile, setSelectedFile] = reactExports.useState(null);
@@ -81797,6 +81899,7 @@ const PROJECT_NAV = [
81797
81899
  ];
81798
81900
  const GLOBAL_NAV = [
81799
81901
  { id: "runtime", label: "Runtime Config", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Server, { size: 15 }) },
81902
+ { id: "notifications", label: "Notifications", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Bell, { size: 15 }) },
81800
81903
  { id: "tunnel", label: "Tunnel", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Globe, { size: 15 }) },
81801
81904
  { id: "slack", label: "Slack", icon: /* @__PURE__ */ jsxRuntimeExports.jsx(Slack, { size: 15 }) }
81802
81905
  ];
@@ -81930,7 +82033,7 @@ function SettingsPage() {
81930
82033
  /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "h-4" })
81931
82034
  ] })
81932
82035
  ] }),
81933
- /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden flex flex-col", children: isProject ? /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectSettings, { workspaceId, section }) : section === "slack" ? /* @__PURE__ */ jsxRuntimeExports.jsx(SlackSettings, {}) : section === "tunnel" ? /* @__PURE__ */ jsxRuntimeExports.jsx(TunnelSettings, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(GlobalSettings, { section }) })
82036
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { className: "flex-1 overflow-hidden flex flex-col", children: isProject ? /* @__PURE__ */ jsxRuntimeExports.jsx(ProjectSettings, { workspaceId, section }) : section === "slack" ? /* @__PURE__ */ jsxRuntimeExports.jsx(SlackSettings, {}) : section === "tunnel" ? /* @__PURE__ */ jsxRuntimeExports.jsx(TunnelSettings, {}) : section === "notifications" ? /* @__PURE__ */ jsxRuntimeExports.jsx(NotificationsSettings, {}) : /* @__PURE__ */ jsxRuntimeExports.jsx(GlobalSettings, { section }) })
81934
82037
  ] });
81935
82038
  }
81936
82039
  function NavItem({
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <link rel="icon" type="image/png" href="/favicon.png" />
7
7
  <title>whipped</title>
8
- <script type="module" crossorigin src="/assets/index-BBh0-Z42.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-peNzhkq8.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-Do7b5IJu.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "whipped",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Autonomous AI agent kanban board for Claude, Codex, Opencode, and Cursor.",
5
5
  "type": "module",
6
6
  "homepage": "https://github.com/nxnom/whipped#readme",
@@ -22,6 +22,20 @@
22
22
  "engines": {
23
23
  "node": ">=22"
24
24
  },
25
+ "scripts": {
26
+ "clean": "rm -rf dist",
27
+ "build": "pnpm clean && pnpm web:build && node scripts/build.mjs",
28
+ "prepublishOnly": "pnpm build",
29
+ "postinstall": "node scripts/postinstall.mjs",
30
+ "dev": "NODE_ENV=development tsx src/cli.ts",
31
+ "dev:full": "node scripts/dev-full.mjs",
32
+ "web:dev": "pnpm --filter @whipped/web dev",
33
+ "web:build": "pnpm --filter @whipped/web build",
34
+ "typecheck": "tsc -p tsconfig.json --noEmit",
35
+ "web:typecheck": "pnpm --filter @whipped/web typecheck",
36
+ "lint": "biome lint src web-ui/src",
37
+ "format": "biome format --write ."
38
+ },
25
39
  "dependencies": {
26
40
  "@hono/zod-validator": "^0.8.0",
27
41
  "@modelcontextprotocol/sdk": "^1.29.0",
@@ -54,17 +68,14 @@
54
68
  "tsx": "^4.20.3",
55
69
  "typescript": "^5.9.3"
56
70
  },
57
- "scripts": {
58
- "clean": "rm -rf dist",
59
- "build": "pnpm clean && pnpm web:build && node scripts/build.mjs",
60
- "postinstall": "node scripts/postinstall.mjs",
61
- "dev": "NODE_ENV=development tsx src/cli.ts",
62
- "dev:full": "node scripts/dev-full.mjs",
63
- "web:dev": "pnpm --filter @whipped/web dev",
64
- "web:build": "pnpm --filter @whipped/web build",
65
- "typecheck": "tsc -p tsconfig.json --noEmit",
66
- "web:typecheck": "pnpm --filter @whipped/web typecheck",
67
- "lint": "biome lint src web-ui/src",
68
- "format": "biome format --write ."
71
+ "pnpm": {
72
+ "onlyBuiltDependencies": [
73
+ "better-sqlite3",
74
+ "esbuild",
75
+ "node-pty"
76
+ ],
77
+ "overrides": {
78
+ "hono": "4.12.23"
79
+ }
69
80
  }
70
- }
81
+ }