authorgravatar for ashvin.nihalani@gmail.comAshvin Nihalani <ashvin.nihalani@gmail.com> 2026-04-13 12:16:00-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-13 19:16:00+00:00
log5f7becf38688ddbd305f352b6cd9bd57d82c164c
tree12c54610bc26c94c722ef77eab40a4768f3ff458
parent9b29be916b2c5e3736fa685d30c417f4fddcce9c
signature Signed by PGP key B5690EEEBB952194

feat: Add Kiro editor support to open picker (#1974)

Co-authored-by: Julius Marminge <jmarminge@gmail.com>

6 files changed, 106 insertions(+), 2 deletions(-)

apps/server/src/open.test.ts+22-1
...@@ -42,6 +42,16 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {...@@ -42,6 +42,16 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
42 args: ["/tmp/workspace"],42 args: ["/tmp/workspace"],
43 });43 });
4444
45 const kiroLaunch = yield* resolveEditorLaunch(
46 { cwd: "/tmp/workspace", editor: "kiro" },
47 "darwin",
48 { PATH: "" },
49 );
50 assert.deepEqual(kiroLaunch, {
51 command: "kiro",
52 args: ["ide", "/tmp/workspace"],
53 });
54
45 const vscodeLaunch = yield* resolveEditorLaunch(55 const vscodeLaunch = yield* resolveEditorLaunch(
46 { cwd: "/tmp/workspace", editor: "vscode" },56 { cwd: "/tmp/workspace", editor: "vscode" },
47 "darwin",57 "darwin",
...@@ -122,6 +132,16 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {...@@ -122,6 +132,16 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
122 args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],132 args: ["--goto", "/tmp/workspace/src/open.ts:71:5"],
123 });133 });
124134
135 const kiroLineAndColumn = yield* resolveEditorLaunch(
136 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "kiro" },
137 "darwin",
138 { PATH: "" },
139 );
140 assert.deepEqual(kiroLineAndColumn, {
141 command: "kiro",
142 args: ["ide", "--goto", "/tmp/workspace/src/open.ts:71:5"],
143 });
144
125 const vscodeLineAndColumn = yield* resolveEditorLaunch(145 const vscodeLineAndColumn = yield* resolveEditorLaunch(
126 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" },146 { cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" },
127 "darwin",147 "darwin",
...@@ -354,6 +374,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {...@@ -354,6 +374,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
354 const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });374 const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });
355375
356 yield* fs.writeFileString(path.join(dir, "trae.CMD"), "@echo off\r\n");376 yield* fs.writeFileString(path.join(dir, "trae.CMD"), "@echo off\r\n");
377 yield* fs.writeFileString(path.join(dir, "kiro.CMD"), "@echo off\r\n");
357 yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");378 yield* fs.writeFileString(path.join(dir, "code-insiders.CMD"), "@echo off\r\n");
358 yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");379 yield* fs.writeFileString(path.join(dir, "codium.CMD"), "@echo off\r\n");
359 yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");380 yield* fs.writeFileString(path.join(dir, "explorer.CMD"), "MZ");
...@@ -361,7 +382,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {...@@ -361,7 +382,7 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
361 PATH: dir,382 PATH: dir,
362 PATHEXT: ".COM;.EXE;.BAT;.CMD",383 PATHEXT: ".COM;.EXE;.BAT;.CMD",
363 });384 });
364 assert.deepEqual(editors, ["trae", "vscode-insiders", "vscodium", "file-manager"]);385 assert.deepEqual(editors, ["trae", "kiro", "vscode-insiders", "vscodium", "file-manager"]);
365 }),386 }),
366 );387 );
367388
apps/server/src/open.ts+9-1
...@@ -75,6 +75,14 @@ function resolveCommandEditorArgs(...@@ -75,6 +75,14 @@ function resolveCommandEditorArgs(
75 }75 }
76}76}
7777
78function resolveEditorArgs(
79 editor: (typeof EDITORS)[number],
80 target: string,
81): ReadonlyArray<string> {
82 const baseArgs = "baseArgs" in editor ? editor.baseArgs : [];
83 return [...baseArgs, ...resolveCommandEditorArgs(editor, target)];
84}
85
78function resolveAvailableCommand(86function resolveAvailableCommand(
79 commands: ReadonlyArray<string>,87 commands: ReadonlyArray<string>,
80 options: CommandAvailabilityOptions = {},88 options: CommandAvailabilityOptions = {},
...@@ -273,7 +281,7 @@ export const resolveEditorLaunch = Effect.fn("resolveEditorLaunch")(function* (...@@ -273,7 +281,7 @@ export const resolveEditorLaunch = Effect.fn("resolveEditorLaunch")(function* (
273 resolveAvailableCommand(editorDef.commands, { platform, env }) ?? editorDef.commands[0];281 resolveAvailableCommand(editorDef.commands, { platform, env }) ?? editorDef.commands[0];
274 return {282 return {
275 command,283 command,
276 args: resolveCommandEditorArgs(editorDef, input.cwd),284 args: resolveEditorArgs(editorDef, input.cwd),
277 };285 };
278 }286 }
279287
apps/web/src/components/ChatView.browser.tsx+49
...@@ -1803,6 +1803,55 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -1803,6 +1803,55 @@ describe("ChatView timeline estimator parity (full app)", () => {
1803 }1803 }
1804 });1804 });
18051805
1806 it("shows Kiro in the open picker menu and opens the project cwd with it", async () => {
1807 setDraftThreadWithoutWorktree();
1808
1809 const mounted = await mountChatView({
1810 viewport: DEFAULT_VIEWPORT,
1811 snapshot: createDraftOnlySnapshot(),
1812 configureFixture: (nextFixture) => {
1813 nextFixture.serverConfig = {
1814 ...nextFixture.serverConfig,
1815 availableEditors: ["kiro"],
1816 };
1817 },
1818 });
1819
1820 try {
1821 await waitForServerConfigToApply();
1822 const menuButton = await waitForElement(
1823 () => document.querySelector('button[aria-label="Copy options"]'),
1824 "Unable to find Open picker button.",
1825 );
1826 (menuButton as HTMLButtonElement).click();
1827
1828 const kiroItem = await waitForElement(
1829 () =>
1830 Array.from(document.querySelectorAll('[data-slot="menu-item"]')).find((item) =>
1831 item.textContent?.includes("Kiro"),
1832 ) ?? null,
1833 "Unable to find Kiro menu item.",
1834 );
1835 (kiroItem as HTMLElement).click();
1836
1837 await vi.waitFor(
1838 () => {
1839 const openRequest = wsRequests.find(
1840 (request) => request._tag === WS_METHODS.shellOpenInEditor,
1841 );
1842 expect(openRequest).toMatchObject({
1843 _tag: WS_METHODS.shellOpenInEditor,
1844 cwd: "/repo/project",
1845 editor: "kiro",
1846 });
1847 },
1848 { timeout: 8_000, interval: 16 },
1849 );
1850 } finally {
1851 await mounted.cleanup();
1852 }
1853 });
1854
1806 it("filters the open picker menu and opens VSCodium from the menu", async () => {1855 it("filters the open picker menu and opens VSCodium from the menu", async () => {
1807 setDraftThreadWithoutWorktree();1856 setDraftThreadWithoutWorktree();
18081857
apps/web/src/components/Icons.tsx+18
...@@ -34,6 +34,24 @@ export const TraeIcon: Icon = (props) => (...@@ -34,6 +34,24 @@ export const TraeIcon: Icon = (props) => (
34 </svg>34 </svg>
35);35);
3636
37export const KiroIcon: Icon = (props) => (
38 <svg {...props} viewBox="0 0 1200 1200" fill="none">
39 <rect width="1200" height="1200" rx="260" fill="#9046FF" />
40 <path
41 d="M398.554 818.914C316.315 1001.03 491.477 1046.74 620.672 940.156C658.687 1059.66 801.052 970.473 852.234 877.795C964.787 673.567 919.318 465.357 907.64 422.374C827.637 129.443 427.623 128.946 358.8 423.865C342.651 475.544 342.402 534.18 333.458 595.051C328.986 625.86 325.507 645.488 313.83 677.785C306.873 696.424 297.68 712.819 282.773 740.645C259.915 783.881 269.604 867.113 387.87 823.883L399.051 818.914H398.554Z"
42 fill="#fff"
43 />
44 <path
45 d="M636.123 549.353C603.328 549.353 598.359 510.097 598.359 486.742C598.359 465.623 602.086 448.977 609.293 438.293C615.504 428.852 624.697 424.131 636.123 424.131C647.555 424.131 657.492 428.852 664.447 438.541C672.398 449.474 676.623 466.12 676.623 486.742C676.623 525.998 661.471 549.353 636.375 549.353H636.123Z"
46 fill="#000"
47 />
48 <path
49 d="M771.24 549.353C738.445 549.353 733.477 510.097 733.477 486.742C733.477 465.623 737.203 448.977 744.41 438.293C750.621 428.852 759.814 424.131 771.24 424.131C782.672 424.131 792.609 428.852 799.564 438.541C807.516 449.474 811.74 466.12 811.74 486.742C811.74 525.998 796.588 549.353 771.492 549.353H771.24Z"
50 fill="#000"
51 />
52 </svg>
53);
54
37export const VisualStudioCode: Icon = (props) => {55export const VisualStudioCode: Icon = (props) => {
38 const id = useId();56 const id = useId();
39 const maskId = `${id}-vscode-a`;57 const maskId = `${id}-vscode-a`;
apps/web/src/components/chat/OpenInPicker.tsx+6
...@@ -10,6 +10,7 @@ import {...@@ -10,6 +10,7 @@ import {
10 AntigravityIcon,10 AntigravityIcon,
11 CursorIcon,11 CursorIcon,
12 Icon,12 Icon,
13 KiroIcon,
13 TraeIcon,14 TraeIcon,
14 IntelliJIdeaIcon,15 IntelliJIdeaIcon,
15 VisualStudioCode,16 VisualStudioCode,
...@@ -32,6 +33,11 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor...@@ -32,6 +33,11 @@ const resolveOptions = (platform: string, availableEditors: ReadonlyArray<Editor
32 Icon: TraeIcon,33 Icon: TraeIcon,
33 value: "trae",34 value: "trae",
34 },35 },
36 {
37 label: "Kiro",
38 Icon: KiroIcon,
39 value: "kiro",
40 },
35 {41 {
36 label: "VS Code",42 label: "VS Code",
37 Icon: VisualStudioCode,43 Icon: VisualStudioCode,
packages/contracts/src/editor.ts+2
...@@ -8,12 +8,14 @@ type EditorDefinition = {...@@ -8,12 +8,14 @@ type EditorDefinition = {
8 readonly id: string;8 readonly id: string;
9 readonly label: string;9 readonly label: string;
10 readonly commands: readonly [string, ...string[]] | null;10 readonly commands: readonly [string, ...string[]] | null;
11 readonly baseArgs?: readonly string[];
11 readonly launchStyle: EditorLaunchStyle;12 readonly launchStyle: EditorLaunchStyle;
12};13};
1314
14export const EDITORS = [15export const EDITORS = [
15 { id: "cursor", label: "Cursor", commands: ["cursor"], launchStyle: "goto" },16 { id: "cursor", label: "Cursor", commands: ["cursor"], launchStyle: "goto" },
16 { id: "trae", label: "Trae", commands: ["trae"], launchStyle: "goto" },17 { id: "trae", label: "Trae", commands: ["trae"], launchStyle: "goto" },
18 { id: "kiro", label: "Kiro", commands: ["kiro"], baseArgs: ["ide"], launchStyle: "goto" },
17 { id: "vscode", label: "VS Code", commands: ["code"], launchStyle: "goto" },19 { id: "vscode", label: "VS Code", commands: ["code"], launchStyle: "goto" },
18 {20 {
19 id: "vscode-insiders",21 id: "vscode-insiders",