diff --git a/packages/treebeard/src/App.test.tsx b/packages/treebeard/src/App.test.tsx index d5d8214..58308d8 100644 --- a/packages/treebeard/src/App.test.tsx +++ b/packages/treebeard/src/App.test.tsx @@ -56,7 +56,6 @@ vi.mock('./components/WorkspaceDashboard', () => ({ const config: AppConfig = { repositories: [{ id: 'repo-1', name: 'treebeard', path: '/repo' }], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, diff --git a/packages/treebeard/src/bun/index.ts b/packages/treebeard/src/bun/index.ts index 597f2e9..fb9cdd1 100644 --- a/packages/treebeard/src/bun/index.ts +++ b/packages/treebeard/src/bun/index.ts @@ -20,7 +20,6 @@ import { } from './services/git' import { getPRForBranch, getPRStackDetails, getPRStackSummary } from './services/github' import { getJiraIssue, getMyJiraIssues } from './services/jira' -import { isKiroCrewAvailable, openKiroCrewSession } from './services/kiro-crew' import { launchGhostty, launchIde, launchOpencode, launchPippinShell, launchURL } from './services/launcher' import { getShellEnv } from './services/shell-env' import { @@ -43,7 +42,6 @@ let isUpdateCheckInFlight = false let isUpdatePromptOpen = false let dependencyStatus: DependencyStatus | null = null let dependencyCheckInFlight: Promise | null = null -let kiroCrewAvailability: Promise | null = null interface UpdateCheckResult { success: boolean @@ -256,9 +254,6 @@ const mainviewRPC = BrowserView.defineRPC({ 'launch:opencode': ({ worktreePath }) => { launchOpencode(worktreePath) }, - 'launch:kiroCrew': async ({ worktreePath }) => { - return openKiroCrewSession(worktreePath) - }, 'launch:url': async ({ url }) => { if (Utils.openExternal(url)) { return { success: true } @@ -288,12 +283,6 @@ const mainviewRPC = BrowserView.defineRPC({ const result = (await new Response(proc.stdout).text()).trim() return result || null }, - 'system:kiroCrewAvailable': () => { - if (!kiroCrewAvailability) { - kiroCrewAvailability = isKiroCrewAvailable() - } - return kiroCrewAvailability - }, 'dialog:openDirectory': async () => { const paths = await Utils.openFileDialog({ startingFolder: os.homedir(), diff --git a/packages/treebeard/src/bun/services/config.test.ts b/packages/treebeard/src/bun/services/config.test.ts index e9de5c8..8801e5c 100644 --- a/packages/treebeard/src/bun/services/config.test.ts +++ b/packages/treebeard/src/bun/services/config.test.ts @@ -49,7 +49,6 @@ describe('config service', () => { expect(getConfig()).toEqual({ repositories: [], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, @@ -65,7 +64,6 @@ describe('config service', () => { setConfig({ repositories: [], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 1, fetchIntervalSec: 30, autoUpdateEnabled: false, @@ -79,7 +77,6 @@ describe('config service', () => { expect(getConfig()).toEqual({ repositories: [], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 10, fetchIntervalSec: 60, autoUpdateEnabled: false, @@ -96,18 +93,6 @@ describe('config service', () => { expect(getCollapsedRepos()).toEqual(['repo-1', 'repo-2']) }) - it('sanitizes Kiro Crew session associations', () => { - mockReadFileSync.mockImplementation(() => JSON.stringify({ - kiroCrewSessions: { - '/repo/worktree': 'chat-123', - '/repo/invalid': 42, - '': 'chat-456' - } - })) - - expect(getConfig().kiroCrewSessions).toEqual({ '/repo/worktree': 'chat-123' }) - }) - it('preserves direct-path workspace members created before symlink support', () => { mockReadFileSync.mockImplementation(() => JSON.stringify({ workspaces: [{ diff --git a/packages/treebeard/src/bun/services/config.ts b/packages/treebeard/src/bun/services/config.ts index 006f597..7cb70a8 100644 --- a/packages/treebeard/src/bun/services/config.ts +++ b/packages/treebeard/src/bun/services/config.ts @@ -20,7 +20,6 @@ const WORKSPACES_ROOT = path.join(os.homedir(), 'Developer', 'workspaces') const DEFAULTS: AppConfig = { repositories: [], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, @@ -35,16 +34,6 @@ function clamp(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max) } -function sanitizeKiroCrewSessions(sessions: unknown): Record { - if (!sessions || typeof sessions !== 'object' || Array.isArray(sessions)) return {} - - return Object.fromEntries( - Object.entries(sessions).filter(([worktreePath, slotKey]) => - worktreePath.length > 0 && typeof slotKey === 'string' && slotKey.length > 0 - ) - ) -} - function isWorkspacePath(workspacePath: string): boolean { const relative = path.relative(WORKSPACES_ROOT, workspacePath) return relative.length > 0 && !relative.startsWith('..') && !path.isAbsolute(relative) @@ -125,7 +114,6 @@ function sanitizeConfig(config: Partial): AppConfig { return { repositories: Array.isArray(config.repositories) ? [...config.repositories] : [], workspaces: sanitizeWorkspaces(config.workspaces), - kiroCrewSessions: sanitizeKiroCrewSessions(config.kiroCrewSessions), pollIntervalSec, fetchIntervalSec, autoUpdateEnabled: typeof config.autoUpdateEnabled === 'boolean' ? config.autoUpdateEnabled : DEFAULTS.autoUpdateEnabled, diff --git a/packages/treebeard/src/bun/services/kiro-crew.test.ts b/packages/treebeard/src/bun/services/kiro-crew.test.ts deleted file mode 100644 index 95e6cdd..0000000 --- a/packages/treebeard/src/bun/services/kiro-crew.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest' -import { isKiroCrewAvailable, openKiroCrewSession } from './kiro-crew' -import { setBunSpawnQueue } from '../../test/bun' -import type { AppConfig } from '../../shared/types' - -const { getConfig, setConfig } = vi.hoisted(() => ({ - getConfig: vi.fn(), - setConfig: vi.fn() -})) - -vi.mock('./config', () => ({ - getConfig, - setConfig -})) - -vi.mock('./shell-env', () => ({ - getShellEnv: vi.fn(async () => ({ PATH: '/usr/bin' })) -})) - -function createConfig(kiroCrewSessions: Record = {}): AppConfig { - return { - repositories: [], - workspaces: [], - kiroCrewSessions, - pollIntervalSec: 60, - fetchIntervalSec: 300, - autoUpdateEnabled: true, - updateCheckIntervalMin: 30, - collapsedRepos: [], - defaultIde: 'vscode', - jiraPanelOpen: false, - jiraPanelWidth: 260 - } -} - -describe('Kiro Crew service', () => { - beforeEach(() => { - getConfig.mockReset() - setConfig.mockReset() - getConfig.mockReturnValue(createConfig()) - vi.stubGlobal('fetch', vi.fn()) - }) - - it('reuses an existing session and scopes it to the worktree', async () => { - const fetch = vi.mocked(globalThis.fetch) - fetch - .mockResolvedValueOnce(new Response('{}')) - .mockResolvedValueOnce(new Response('{"ok":true}')) - getConfig.mockReturnValue(createConfig({ '/repo/worktree': 'chat-123' })) - const spawn = setBunSpawnQueue([ - { stdout: 'http://localhost:5476/?token=secret\n' }, - { stdout: '' } - ]) - - await expect(openKiroCrewSession('/repo/worktree')).resolves.toEqual({ success: true }) - - expect(fetch).toHaveBeenNthCalledWith( - 1, - 'http://localhost:5476/api/chat/slots/chat-123?token=secret' - ) - expect(fetch).toHaveBeenNthCalledWith( - 2, - 'http://localhost:5476/api/chat/slots/chat-123/project?token=secret', - expect.objectContaining({ body: JSON.stringify({ project: '/repo/worktree' }) }) - ) - expect(setConfig).not.toHaveBeenCalled() - expect(spawn).toHaveBeenLastCalledWith( - ['/usr/bin/open', '-a', 'KiroCrew'], - { stdout: 'ignore', stderr: 'ignore' } - ) - }) - - it('creates and stores a session when no association exists', async () => { - const fetch = vi.mocked(globalThis.fetch) - fetch - .mockResolvedValueOnce(new Response('{"key":"chat-456"}')) - .mockResolvedValueOnce(new Response('{"ok":true}')) - setBunSpawnQueue([ - { stdout: 'http://localhost:5476/?token=secret\n' }, - { stdout: '' } - ]) - - await expect(openKiroCrewSession('/repo/worktree')).resolves.toEqual({ success: true }) - - expect(fetch).toHaveBeenNthCalledWith( - 1, - 'http://localhost:5476/api/chat/slots?token=secret', - expect.objectContaining({ - method: 'POST', - body: JSON.stringify({ title: 'Treebeard: repo / worktree' }) - }) - ) - expect(setConfig).toHaveBeenCalledWith(createConfig({ '/repo/worktree': 'chat-456' })) - }) - - it('returns an unavailable error when the local gateway cannot provide a token', async () => { - const fetch = vi.mocked(globalThis.fetch) - setBunSpawnQueue([{ stderr: 'gateway unavailable', exitCode: 1 }]) - - await expect(openKiroCrewSession('/repo/worktree')).resolves.toEqual({ - success: false, - error: 'Kiro Crew is unavailable. Start its gateway and try again.' - }) - - expect(fetch).not.toHaveBeenCalled() - }) - - it('requires both the Kiro Crew CLI and desktop app', async () => { - const spawn = setBunSpawnQueue([ - { stdout: '/opt/homebrew/bin/kirocrew\n' }, - { stdout: '/Applications/KiroCrew.app\n' } - ]) - - await expect(isKiroCrewAvailable()).resolves.toBe(true) - - expect(spawn).toHaveBeenNthCalledWith( - 1, - ['which', 'kirocrew'], - expect.objectContaining({ stdout: 'pipe', stderr: 'ignore', env: { PATH: '/usr/bin' } }) - ) - expect(spawn).toHaveBeenNthCalledWith( - 2, - ['/usr/bin/mdfind', 'kMDItemCFBundleIdentifier == "com.amazon.kiro.crew*"cd'], - { stdout: 'pipe', stderr: 'ignore' } - ) - }) - - it('returns false when the Kiro Crew desktop app is absent', async () => { - setBunSpawnQueue([ - { stdout: '/opt/homebrew/bin/kirocrew\n' }, - { stdout: '' } - ]) - - await expect(isKiroCrewAvailable()).resolves.toBe(false) - }) - - it('reports a desktop app failure after persisting the scoped session', async () => { - const fetch = vi.mocked(globalThis.fetch) - fetch - .mockResolvedValueOnce(new Response('{"key":"chat-456"}')) - .mockResolvedValueOnce(new Response('{"ok":true}')) - setBunSpawnQueue([ - { stdout: 'http://localhost:5476/?token=secret\n' }, - { stderr: 'application not found', exitCode: 1 } - ]) - - await expect(openKiroCrewSession('/repo/worktree')).resolves.toEqual({ - success: false, - error: 'Kiro Crew session is ready, but its desktop app could not be opened.' - }) - - expect(setConfig).toHaveBeenCalledWith(createConfig({ '/repo/worktree': 'chat-456' })) - }) -}) diff --git a/packages/treebeard/src/bun/services/kiro-crew.ts b/packages/treebeard/src/bun/services/kiro-crew.ts deleted file mode 100644 index 4e57f92..0000000 --- a/packages/treebeard/src/bun/services/kiro-crew.ts +++ /dev/null @@ -1,184 +0,0 @@ -import path from 'node:path' -import { getConfig, setConfig } from './config' -import { getShellEnv } from './shell-env' - -interface KiroCrewToken { - origin: string - token: string -} - -interface KiroCrewSlot { - key: string -} - -interface KiroCrewResult { - success: boolean - error?: string -} - -type SlotLookupResult = 'exists' | 'missing' | 'failed' - -function apiURL(origin: string, token: string, endpoint: string): string { - const url = new URL(endpoint, origin) - url.searchParams.set('token', token) - return url.toString() -} - -function parseKiroCrewToken(output: string): KiroCrewToken | null { - const match = output.match(/https?:\/\/[^\s]+/) - if (!match) return null - - try { - const url = new URL(match[0]) - if (!['localhost', '127.0.0.1', '[::1]'].includes(url.hostname)) return null - - const token = url.searchParams.get('token') - if (!token) return null - - return { origin: url.origin, token } - } catch { - return null - } -} - -async function getKiroCrewToken(): Promise { - try { - const env = await getShellEnv() - const proc = Bun.spawn(['kirocrew', 'token'], { stdout: 'pipe', stderr: 'ignore', env }) - const output = await new Response(proc.stdout).text() - const exitCode = await proc.exited - if (exitCode !== 0) return null - return parseKiroCrewToken(output) - } catch { - return null - } -} - -/** Check whether the Kiro Crew CLI and desktop app are both installed */ -export async function isKiroCrewAvailable(): Promise { - try { - const env = await getShellEnv() - const cliProc = Bun.spawn(['which', 'kirocrew'], { stdout: 'pipe', stderr: 'ignore', env }) - const appProc = Bun.spawn( - ['/usr/bin/mdfind', 'kMDItemCFBundleIdentifier == "com.amazon.kiro.crew*"cd'], - { stdout: 'pipe', stderr: 'ignore' } - ) - const [cliPath, appPath, cliExitCode, appExitCode] = await Promise.all([ - new Response(cliProc.stdout).text(), - new Response(appProc.stdout).text(), - cliProc.exited, - appProc.exited - ]) - - return cliExitCode === 0 && appExitCode === 0 && Boolean(cliPath.trim()) && Boolean(appPath.trim()) - } catch { - return false - } -} - -async function getSlotLookupResult(token: KiroCrewToken, slotKey: string): Promise { - try { - const response = await fetch(apiURL(token.origin, token.token, `/api/chat/slots/${encodeURIComponent(slotKey)}`)) - if (response.ok) return 'exists' - return response.status === 404 ? 'missing' : 'failed' - } catch { - return 'failed' - } -} - -function getWorktreeTitle(worktreePath: string): string { - const worktreeName = path.basename(worktreePath) - const projectName = path.basename(path.dirname(worktreePath)) - return `Treebeard: ${projectName} / ${worktreeName}` -} - -async function createSlot(token: KiroCrewToken, title: string): Promise { - try { - const response = await fetch(apiURL(token.origin, token.token, '/api/chat/slots'), { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ title }) - }) - if (!response.ok) return null - - const slot = await response.json() as KiroCrewSlot - return typeof slot.key === 'string' && slot.key.length > 0 ? slot.key : null - } catch { - return null - } -} - -async function setSlotProject(token: KiroCrewToken, slotKey: string, worktreePath: string): Promise { - try { - const response = await fetch( - apiURL(token.origin, token.token, `/api/chat/slots/${encodeURIComponent(slotKey)}/project`), - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ project: worktreePath }) - } - ) - return response.ok - } catch { - return false - } -} - -async function focusKiroCrew(): Promise { - try { - const proc = Bun.spawn(['/usr/bin/open', '-a', 'KiroCrew'], { stdout: 'ignore', stderr: 'ignore' }) - return (await proc.exited) === 0 - } catch { - return false - } -} - -/** Create or reuse a Kiro Crew session scoped to a worktree and focus its desktop app */ -export async function openKiroCrewSession(worktreePath: string): Promise { - const token = await getKiroCrewToken() - if (!token) { - return { success: false, error: 'Kiro Crew is unavailable. Start its gateway and try again.' } - } - - const normalizedPath = path.resolve(worktreePath) - const config = getConfig() - let slotKey: string | undefined = config.kiroCrewSessions[normalizedPath] - - if (slotKey) { - const lookupResult = await getSlotLookupResult(token, slotKey) - if (lookupResult === 'failed') { - return { success: false, error: 'Kiro Crew could not open its saved session.' } - } - if (lookupResult === 'missing') { - slotKey = undefined - } - } - - if (!slotKey) { - const createdSlotKey = await createSlot(token, getWorktreeTitle(normalizedPath)) - if (!createdSlotKey) { - return { success: false, error: 'Kiro Crew could not create a session.' } - } - slotKey = createdSlotKey - } - - if (!(await setSlotProject(token, slotKey, normalizedPath))) { - return { success: false, error: 'Kiro Crew could not use this worktree.' } - } - - if (config.kiroCrewSessions[normalizedPath] !== slotKey) { - setConfig({ - ...config, - kiroCrewSessions: { - ...config.kiroCrewSessions, - [normalizedPath]: slotKey - } - }) - } - - if (!(await focusKiroCrew())) { - return { success: false, error: 'Kiro Crew session is ready, but its desktop app could not be opened.' } - } - - return { success: true } -} diff --git a/packages/treebeard/src/bun/services/workspaces.test.ts b/packages/treebeard/src/bun/services/workspaces.test.ts index e9e2d8b..2181874 100644 --- a/packages/treebeard/src/bun/services/workspaces.test.ts +++ b/packages/treebeard/src/bun/services/workspaces.test.ts @@ -36,7 +36,6 @@ function createConfig(): AppConfig { return { repositories: [{ id: 'repo-1', name: 'Treebeard App', path: '/repos/treebeard' }], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, diff --git a/packages/treebeard/src/bun/services/workspaces.ts b/packages/treebeard/src/bun/services/workspaces.ts index f6bbcc1..37e5c02 100644 --- a/packages/treebeard/src/bun/services/workspaces.ts +++ b/packages/treebeard/src/bun/services/workspaces.ts @@ -176,8 +176,7 @@ export function removeWorkspaceMember( const nextWorkspace = { ...workspace, members: workspace.members.filter((item) => item.repoId !== repoId) } setConfig({ ...config, - workspaces: config.workspaces.map((candidate) => candidate.id === workspaceId ? nextWorkspace : candidate), - kiroCrewSessions: config.kiroCrewSessions + workspaces: config.workspaces.map((candidate) => candidate.id === workspaceId ? nextWorkspace : candidate) }) if (nextWorkspace.members.length > 0) { writeWorkspaceAgentsMd(nextWorkspace, config) @@ -196,12 +195,7 @@ export function removeWorkspace(workspaceId: string): { success: boolean; error? setConfig({ ...config, - workspaces: config.workspaces.filter((candidate) => candidate.id !== workspaceId), - kiroCrewSessions: Object.fromEntries( - Object.entries(config.kiroCrewSessions).filter(([worktreePath]) => - worktreePath !== workspace.path - ) - ) + workspaces: config.workspaces.filter((candidate) => candidate.id !== workspaceId) }) removeEmptyWorkspaceDirectory(workspace.path) return { success: true } @@ -228,13 +222,7 @@ export function detachWorkspaceWorktree(worktreePath: string): void { if (!changed) return for (const member of removedMembers) removeWorkspaceLink(member) - const nextConfig = { - ...config, - workspaces, - kiroCrewSessions: Object.fromEntries( - Object.entries(config.kiroCrewSessions).filter(([pathKey]) => pathKey !== worktreePath) - ) - } + const nextConfig = { ...config, workspaces } setConfig(nextConfig) for (const workspace of workspaces) { if (!changedWorkspaceIds.has(workspace.id)) continue @@ -243,7 +231,7 @@ export function detachWorkspaceWorktree(worktreePath: string): void { } } -/** Keep workspace membership and Kiro Crew sessions aligned after a standard worktree rename. */ +/** Keep workspace membership aligned after a standard worktree rename. */ export function updateWorkspaceWorktreePath(previousPath: string, nextPath: string): void { const config = getConfig() let changed = false @@ -268,12 +256,7 @@ export function updateWorkspaceWorktreePath(previousPath: string, nextPath: stri if (!changed) return for (const member of linksToUpdate) replaceWorkspaceLink(member, nextPath) - const sessions = { ...config.kiroCrewSessions } - if (sessions[previousPath]) { - sessions[nextPath] = sessions[previousPath] - delete sessions[previousPath] - } - const nextConfig = { ...config, workspaces, kiroCrewSessions: sessions } + const nextConfig = { ...config, workspaces } setConfig(nextConfig) for (const workspace of workspaces) { if (changedWorkspaceIds.has(workspace.id)) writeWorkspaceAgentsMd(workspace, nextConfig) diff --git a/packages/treebeard/src/components/KiroCrewIcon.tsx b/packages/treebeard/src/components/KiroCrewIcon.tsx deleted file mode 100644 index bb96ed6..0000000 --- a/packages/treebeard/src/components/KiroCrewIcon.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { useId } from 'react' - -interface KiroCrewIconProps { - size?: number -} - -export function KiroCrewIcon({ size = 16 }: KiroCrewIconProps) { - const iconId = useId() - const gradientId = `${iconId}-gradient` - const clipPathId = `${iconId}-clip-path` - const largeShadowId = `${iconId}-large-shadow` - const smallShadowId = `${iconId}-small-shadow` - const topShadowId = `${iconId}-top-shadow` - - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ) -} diff --git a/packages/treebeard/src/components/LaunchButtons.test.tsx b/packages/treebeard/src/components/LaunchButtons.test.tsx index f11d564..8e3c0b4 100644 --- a/packages/treebeard/src/components/LaunchButtons.test.tsx +++ b/packages/treebeard/src/components/LaunchButtons.test.tsx @@ -1,20 +1,16 @@ -import { fireEvent, screen, waitFor } from '@testing-library/react' +import { fireEvent, screen } from '@testing-library/react' import { beforeEach, describe, expect, it, vi } from 'vitest' import { LaunchButtons } from './LaunchButtons' import { renderWithMantine } from '../test/render' const launchIdeRequest = vi.fn() const launchGhosttyRequest = vi.fn() -const launchKiroCrewRequest = vi.fn() -const kiroCrewAvailableRequest = vi.fn() vi.mock('../rpc', () => ({ rpc: () => ({ request: { 'launch:ide': launchIdeRequest, 'launch:ghostty': launchGhosttyRequest, - 'launch:kiroCrew': launchKiroCrewRequest, - 'system:kiroCrewAvailable': kiroCrewAvailableRequest, } }) })) @@ -23,43 +19,30 @@ describe('LaunchButtons', () => { beforeEach(() => { launchIdeRequest.mockReset() launchGhosttyRequest.mockReset() - launchKiroCrewRequest.mockReset() - kiroCrewAvailableRequest.mockReset() launchIdeRequest.mockResolvedValue(undefined) launchGhosttyRequest.mockResolvedValue(undefined) - launchKiroCrewRequest.mockResolvedValue({ success: true }) - kiroCrewAvailableRequest.mockResolvedValue(true) Object.assign(navigator, { clipboard: { writeText: vi.fn().mockResolvedValue(undefined) } }) }) - it('launches configured IDE, Ghostty, and Kiro Crew for the selected worktree', async () => { + it('launches configured IDE and Ghostty for the selected worktree', () => { renderWithMantine() - await waitFor(() => { - expect(screen.getAllByRole('button')).toHaveLength(4) - }) - const buttons = screen.getAllByRole('button') + expect(buttons).toHaveLength(3) fireEvent.click(buttons[0]) fireEvent.click(buttons[1]) - fireEvent.click(buttons[2]) expect(launchIdeRequest).toHaveBeenCalledWith({ ideId: 'vscode', worktreePath: '/repo/worktrees/feat' }) expect(launchGhosttyRequest).toHaveBeenCalledWith({ worktreePath: '/repo/worktrees/feat' }) - expect(launchKiroCrewRequest).toHaveBeenCalledWith({ worktreePath: '/repo/worktrees/feat' }) }) - it('copies the worktree path to clipboard', async () => { + it('copies the worktree path to clipboard', () => { renderWithMantine() - await waitFor(() => { - expect(screen.getAllByRole('button')).toHaveLength(4) - }) - const buttons = screen.getAllByRole('button') - fireEvent.click(buttons[3]) + fireEvent.click(buttons[2]) expect(navigator.clipboard.writeText).toHaveBeenCalledWith('/repo/worktrees/feat') }) @@ -72,25 +55,4 @@ describe('LaunchButtons', () => { expect(launchIdeRequest).toHaveBeenCalledWith({ ideId: 'intellij', worktreePath: '/repo/worktrees/feat' }) }) - - it('shows Kiro Crew failures', async () => { - launchKiroCrewRequest.mockResolvedValue({ success: false, error: 'Kiro Crew is unavailable.' }) - renderWithMantine() - - await waitFor(() => { - expect(screen.getAllByRole('button')).toHaveLength(4) - }) - fireEvent.click(screen.getAllByRole('button')[2]) - - expect(await screen.findByText('Kiro Crew is unavailable.')).toBeTruthy() - }) - - it('hides Kiro Crew when its CLI or desktop app is unavailable', async () => { - kiroCrewAvailableRequest.mockResolvedValue(false) - renderWithMantine() - - await waitFor(() => { - expect(screen.getAllByRole('button')).toHaveLength(3) - }) - }) }) diff --git a/packages/treebeard/src/components/LaunchButtons.tsx b/packages/treebeard/src/components/LaunchButtons.tsx index d9eab2a..64d4a9a 100644 --- a/packages/treebeard/src/components/LaunchButtons.tsx +++ b/packages/treebeard/src/components/LaunchButtons.tsx @@ -1,8 +1,6 @@ -import { useEffect, useState } from 'react' -import { ActionIcon, Group, Stack, Text, Tooltip } from '@mantine/core' +import { ActionIcon, Group, Tooltip } from '@mantine/core' import { IconCopy, IconGhost } from '@tabler/icons-react' import { IdeIcon } from './IdeIcon' -import { KiroCrewIcon } from './KiroCrewIcon' import { IDE_REGISTRY } from '../shared/ide-registry' import { rpc } from '../rpc' import type { IdeId } from '../shared/types' @@ -14,28 +12,6 @@ interface LaunchButtonsProps { export function LaunchButtons({ worktreePath, defaultIde }: LaunchButtonsProps) { const ide = IDE_REGISTRY[defaultIde] - const [kiroCrewLoading, setKiroCrewLoading] = useState(false) - const [kiroCrewError, setKiroCrewError] = useState(null) - const [kiroCrewAvailable, setKiroCrewAvailable] = useState(false) - - useEffect(() => { - let cancelled = false - - const checkKiroCrewAvailability = async () => { - try { - const available = await rpc().request['system:kiroCrewAvailable']({}) - if (!cancelled) setKiroCrewAvailable(available) - } catch { - if (!cancelled) setKiroCrewAvailable(false) - } - } - - void checkKiroCrewAvailability() - - return () => { - cancelled = true - } - }, []) const handleIde = async () => { await rpc().request['launch:ide']({ ideId: defaultIde, worktreePath }) @@ -49,54 +25,23 @@ export function LaunchButtons({ worktreePath, defaultIde }: LaunchButtonsProps) void navigator.clipboard.writeText(worktreePath) } - const handleKiroCrew = async () => { - setKiroCrewLoading(true) - setKiroCrewError(null) - try { - const result = await rpc().request['launch:kiroCrew']({ worktreePath }) - if (!result.success) { - setKiroCrewError(result.error ?? 'Kiro Crew could not open this worktree.') - } - } catch { - setKiroCrewError('Kiro Crew could not open this worktree.') - } finally { - setKiroCrewLoading(false) - } - } - return ( - - - - - - - - - - - - - {kiroCrewAvailable && ( - - - - - - )} - - - - - - - {kiroCrewError && {kiroCrewError}} - + + + + + + + + + + + + + + + + + ) } diff --git a/packages/treebeard/src/components/SettingsModal.test.tsx b/packages/treebeard/src/components/SettingsModal.test.tsx index 4a8312a..f3c551d 100644 --- a/packages/treebeard/src/components/SettingsModal.test.tsx +++ b/packages/treebeard/src/components/SettingsModal.test.tsx @@ -27,7 +27,6 @@ vi.mock('../hooks/useHomedir', () => ({ const config: AppConfig = { repositories: [{ id: 'repo-1', name: 'treebeard', path: '/repo' }], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, diff --git a/packages/treebeard/src/components/WorkspaceLaunchButtons.tsx b/packages/treebeard/src/components/WorkspaceLaunchButtons.tsx index 98355be..8289235 100644 --- a/packages/treebeard/src/components/WorkspaceLaunchButtons.tsx +++ b/packages/treebeard/src/components/WorkspaceLaunchButtons.tsx @@ -1,7 +1,5 @@ -import { useEffect, useState } from 'react' -import { ActionIcon, Group, Stack, Text, Tooltip } from '@mantine/core' +import { ActionIcon, Group, Tooltip } from '@mantine/core' import { IconGhost } from '@tabler/icons-react' -import { KiroCrewIcon } from './KiroCrewIcon' import { rpc } from '../rpc' interface WorkspaceLaunchButtonsProps { @@ -9,60 +7,17 @@ interface WorkspaceLaunchButtonsProps { } export function WorkspaceLaunchButtons({ workspacePath }: WorkspaceLaunchButtonsProps) { - const [kiroCrewLoading, setKiroCrewLoading] = useState(false) - const [kiroCrewError, setKiroCrewError] = useState(null) - const [kiroCrewAvailable, setKiroCrewAvailable] = useState(false) - - useEffect(() => { - let cancelled = false - - const checkKiroCrewAvailability = async () => { - try { - const available = await rpc().request['system:kiroCrewAvailable']({}) - if (!cancelled) setKiroCrewAvailable(available) - } catch { - if (!cancelled) setKiroCrewAvailable(false) - } - } - - void checkKiroCrewAvailability() - return () => { cancelled = true } - }, []) - const handleGhostty = async () => { await rpc().request['launch:ghostty']({ worktreePath: workspacePath }) } - const handleKiroCrew = async () => { - setKiroCrewLoading(true) - setKiroCrewError(null) - try { - const result = await rpc().request['launch:kiroCrew']({ worktreePath: workspacePath }) - if (!result.success) setKiroCrewError(result.error ?? 'Kiro Crew could not open this workspace.') - } catch { - setKiroCrewError('Kiro Crew could not open this workspace.') - } finally { - setKiroCrewLoading(false) - } - } - return ( - - - - - - - - {kiroCrewAvailable && ( - - - - - - )} - - {kiroCrewError && {kiroCrewError}} - + + + + + + + ) } diff --git a/packages/treebeard/src/hooks/useConfig.test.ts b/packages/treebeard/src/hooks/useConfig.test.ts index 6a154de..6f96414 100644 --- a/packages/treebeard/src/hooks/useConfig.test.ts +++ b/packages/treebeard/src/hooks/useConfig.test.ts @@ -26,7 +26,6 @@ describe('useConfig', () => { const initial: AppConfig = { repositories: [], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, @@ -50,7 +49,6 @@ describe('useConfig', () => { const initial: AppConfig = { repositories: [{ id: '1', name: 'repo', path: '/repo' }], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, @@ -102,7 +100,6 @@ describe('useConfig', () => { { id: '2', name: 'repo-b', path: '/repo-b' } ], workspaces: [], - kiroCrewSessions: {}, pollIntervalSec: 60, fetchIntervalSec: 300, autoUpdateEnabled: true, diff --git a/packages/treebeard/src/shared/rpc-types.ts b/packages/treebeard/src/shared/rpc-types.ts index 9c753b2..81c754c 100644 --- a/packages/treebeard/src/shared/rpc-types.ts +++ b/packages/treebeard/src/shared/rpc-types.ts @@ -129,10 +129,6 @@ export type TreebeardRPC = { params: { worktreePath: string } response: void } - 'launch:kiroCrew': { - params: { worktreePath: string } - response: { success: boolean; error?: string } - } 'launch:url': { params: { url: string } response: { success: boolean; error?: string } @@ -149,10 +145,6 @@ export type TreebeardRPC = { params: Record response: string | null } - 'system:kiroCrewAvailable': { - params: Record - response: boolean - } 'dialog:openDirectory': { params: Record response: string | null diff --git a/packages/treebeard/src/shared/types.ts b/packages/treebeard/src/shared/types.ts index 2e8e305..cdb55d3 100644 --- a/packages/treebeard/src/shared/types.ts +++ b/packages/treebeard/src/shared/types.ts @@ -25,7 +25,6 @@ export interface Workspace { export interface AppConfig { repositories: RepoConfig[] workspaces: Workspace[] - kiroCrewSessions: Record pollIntervalSec: number fetchIntervalSec: number autoUpdateEnabled: boolean