diff --git a/BUILD b/BUILD index 78dee47..bc715f1 100644 --- a/BUILD +++ b/BUILD @@ -116,7 +116,7 @@ test_suite( "//crates/code-webhooks:lint", "//sdks/rust:lint", "//sdks/typescript:lint", - "//extensions/vs-code:lint", + "//extensions/vscode:lint", "//extensions/slack-agent:lint", "//tests/mock-gateway:lint", ], diff --git a/bun.lock b/bun.lock index a5ef6ba..73bd784 100644 --- a/bun.lock +++ b/bun.lock @@ -9,17 +9,6 @@ "@changesets/cli": "^2.26.2", }, }, - "extensions/zod": { - "name": "@frontal-labs/zod-schemas", - "version": "0.1.0", - "dependencies": { - "zod": "^4.6.7", - }, - "devDependencies": { - "typescript": "^5.4.0", - "vitest": "^4.1.12", - }, - }, "npm/code-cli": { "name": "@frontal-labs/code-cli", "version": "0.2.2", @@ -31,7 +20,7 @@ "name": "@frontal-labs/code-sdk", "version": "0.1.0", "devDependencies": { - "@frontal-labs/zod-schemas": "file:../../extensions/zod", + "@frontal-labs/code-zod-schemas": "file:../../extensions/zod", "@types/node": "^20.12.0", "typescript": "^5.4.0", "vite": "^6.4.4", @@ -137,8 +126,6 @@ "@frontal-labs/code-sdk": ["@frontal-labs/code-sdk@workspace:sdks/typescript"], - "@frontal-labs/zod-schemas": ["@frontal-labs/zod-schemas@workspace:extensions/zod"], - "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="], "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.6.0", "", {}, "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw=="], @@ -455,7 +442,7 @@ "@changesets/write/@changesets/types": ["@changesets/types@6.1.0", "", {}, "sha512-rKQcJ+o1nKNgeoYRHKOS07tAMNd3YSN0uHaJOZYjBAgxfV7TUE7JE+z4BzZdQwb5hKaYbayKN5KrYV7ODb2rAA=="], - "@frontal-labs/code-sdk/@frontal-labs/zod-schemas": ["@frontal-labs/zod-schemas@file:extensions/zod", { "dependencies": { "zod": "^4.6.7" }, "devDependencies": { "typescript": "^5.4.0", "vitest": "^4.1.12" } }], + "@frontal-labs/code-sdk/@frontal-labs/code-zod-schemas": ["@frontal-labs/code-zod-schemas@file:extensions/zod", { "dependencies": { "zod": "^4.6.7" }, "devDependencies": { "typescript": "^5.4.0", "vitest": "^4.1.12" } }], "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], diff --git a/extensions/BUILD.bazel b/extensions/BUILD.bazel index 72883c4..3eb5e85 100644 --- a/extensions/BUILD.bazel +++ b/extensions/BUILD.bazel @@ -3,8 +3,9 @@ package(default_visibility = ["//visibility:public"]) filegroup( name = "all_extensions", srcs = [ - "//extensions/vs-code:srcs", + "//extensions/vscode:srcs", "//extensions/slack-agent:srcs", "//extensions/zod:srcs", + "//extensions/zed:srcs", ], ) diff --git a/extensions/slack-agent/bun.config.ts b/extensions/slack-agent/bun.config.ts index 9602ac4..a876415 100644 --- a/extensions/slack-agent/bun.config.ts +++ b/extensions/slack-agent/bun.config.ts @@ -1,4 +1,4 @@ -// Bun configuration for frontal-code-slack extension +// Bun configuration for @frontal-labs/code-slack extension const config = { // Build configuration diff --git a/extensions/slack-agent/bun.lock b/extensions/slack-agent/bun.lock index 65fdc46..d8d4c61 100644 --- a/extensions/slack-agent/bun.lock +++ b/extensions/slack-agent/bun.lock @@ -3,7 +3,7 @@ "configVersion": 0, "workspaces": { "": { - "name": "code-slack", + "name": "@frontal-labs/code-slack", "dependencies": { "@openchannelprotocol/sdk": "^0.1.0", "@sentry/node": "^11.8.0", diff --git a/extensions/slack-agent/bunfig.toml b/extensions/slack-agent/bunfig.toml index ffc049a..bf8096e 100644 --- a/extensions/slack-agent/bunfig.toml +++ b/extensions/slack-agent/bunfig.toml @@ -1,4 +1,4 @@ -# Bun configuration for frontal-code-slack extension +# Bun configuration for @frontal-labs/code-slack extension [install] # Use production dependencies only in production diff --git a/extensions/slack-agent/package-lock.json b/extensions/slack-agent/package-lock.json index 99ec999..f5f3bf5 100644 --- a/extensions/slack-agent/package-lock.json +++ b/extensions/slack-agent/package-lock.json @@ -1,11 +1,11 @@ { - "name": "code-slack", + "name": "@frontal-labs/code-slack", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "code-slack", + "name": "@frontal-labs/code-slack", "version": "0.1.0", "license": "Apache-2.0", "dependencies": { diff --git a/extensions/slack-agent/package.json b/extensions/slack-agent/package.json index 59e80dc..7b91a64 100644 --- a/extensions/slack-agent/package.json +++ b/extensions/slack-agent/package.json @@ -1,5 +1,5 @@ { - "name": "code-slack", + "name": "@frontal-labs/code-slack", "version": "0.1.0", "description": "Frontal Code Slack agent for autonomous coding operations", "main": "dist/index.js", diff --git a/extensions/vs-code/.gitignore b/extensions/vscode/.gitignore similarity index 100% rename from extensions/vs-code/.gitignore rename to extensions/vscode/.gitignore diff --git a/extensions/vs-code/BUILD.bazel b/extensions/vscode/BUILD.bazel similarity index 92% rename from extensions/vs-code/BUILD.bazel rename to extensions/vscode/BUILD.bazel index d786f82..cf56632 100644 --- a/extensions/vs-code/BUILD.bazel +++ b/extensions/vscode/BUILD.bazel @@ -18,7 +18,7 @@ sh_test( srcs = ["//tools:extension.sh"], data = [":srcs", "//:biome.json", "//:ultracite.json", "//:.gitignore", "package.json"], args = [ - "extensions/vs-code", + "extensions/vscode", "lint", ], tags = [ @@ -32,7 +32,7 @@ sh_test( srcs = ["//tools:extension.sh"], data = [":srcs", "//:biome.json", "//:ultracite.json", "//:.gitignore", "package.json"], args = [ - "extensions/vs-code", + "extensions/vscode", "build", ], tags = [ diff --git a/extensions/vs-code/README.md b/extensions/vscode/README.md similarity index 100% rename from extensions/vs-code/README.md rename to extensions/vscode/README.md diff --git a/extensions/vs-code/extension.js b/extensions/vscode/extension.js similarity index 100% rename from extensions/vs-code/extension.js rename to extensions/vscode/extension.js diff --git a/extensions/vs-code/package.json b/extensions/vscode/package.json similarity index 95% rename from extensions/vs-code/package.json rename to extensions/vscode/package.json index e4c3713..4842f04 100644 --- a/extensions/vs-code/package.json +++ b/extensions/vscode/package.json @@ -1,6 +1,6 @@ { - "name": "frontal-code-ide", - "displayName": "Frontal Code IDE", + "name": "@frontal-labs/code-vscode", + "displayName": "Frontal Code VSCode IDE", "description": "Frontal Code integration for VS Code and Cursor", "version": "0.1.0", "publisher": "frontal-code", diff --git a/extensions/zed/BUILD.bazel b/extensions/zed/BUILD.bazel new file mode 100644 index 0000000..fa1bed8 --- /dev/null +++ b/extensions/zed/BUILD.bazel @@ -0,0 +1,42 @@ +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "srcs", + srcs = glob( + ["**/*"], + exclude = [ + "node_modules/**", + ".git/**", + "BUILD.bazel", + ], + ), +) + +sh_test( + name = "lint", + size = "small", + srcs = ["//tools:extension.sh"], + data = [":srcs", "//:biome.json", "//:ultracite.json", "//:.gitignore", "package.json"], + args = [ + "extensions/zed", + "lint", + ], + tags = [ + "lint", + "typescript", + ], +) + +sh_test( + name = "build", + srcs = ["//tools:extension.sh"], + data = [":srcs", "//:biome.json", "//:ultracite.json", "//:.gitignore", "package.json"], + args = [ + "extensions/zed", + "build", + ], + tags = [ + "build", + "typescript", + ], +) diff --git a/extensions/zed/README.md b/extensions/zed/README.md new file mode 100644 index 0000000..d425dca --- /dev/null +++ b/extensions/zed/README.md @@ -0,0 +1,21 @@ +# Frontal Code Zed Extension + +Local Frontal Code integration for the [Zed](https://zed.dev) editor. + +## Commands + +- `frontal-code.startRepl` — Start the Frontal Code REPL +- `frontal-code.askInput` — Ask Frontal Code a question +- `frontal-code.askSelection` — Ask Frontal Code about the current selection + +## Settings + +- `frontal-code.cliPath` (default: `frontal-code` from PATH, or `code`) + +## Installation + +Zed discovers extensions placed in `~/.config/zed/extensions/` or workspace-level `extensions/` directories. Copy or symlink the contents of this package into your Zed extensions directory, or use `zed extension install` from a published marketplace entry. + +## `/ide` Integration + +`/ide` currently supports `vscode`, `cursor`, `antigravity`, and `windsurf`. Zed support will be added as a future target. diff --git a/extensions/zed/extension.js b/extensions/zed/extension.js new file mode 100644 index 0000000..95a510b --- /dev/null +++ b/extensions/zed/extension.js @@ -0,0 +1,149 @@ +const { zed } = require("@zed-industries/extension-api"); +const { execFile } = require("node:child_process"); +const { accessSync, constants } = require("node:fs"); +const path = require("node:path"); + +// The two names every install channel provides for the same CLI. +const COMMAND_NAMES = ["frontal-code", "code"]; + +/** @param {import("@zed-industries/extension-api").ExtensionContext} context */ +function activate(context) { + context.subscriptions.push( + zed.registerCommand("frontal-code.startRepl", () => startRepl()), + zed.registerCommand("frontal-code.askSelection", () => askSelection()), + zed.registerCommand("frontal-code.askInput", () => askInput()), + ); +} + +function deactivate() { + // Intentionally empty: Zed owns extension teardown. +} + +function frontalCodeCliPath() { + const inspected = zed.config().get("frontal-code.cliPath"); + const value = + typeof inspected === "string" && inspected.trim().length > 0 + ? inspected.trim() + : defaultCliCommand(); + return value; +} + +// `frontal-code` when it is on PATH, otherwise `code`: the same program under either name. +function defaultCliCommand() { + for (const name of COMMAND_NAMES) { + if (isOnPath(name)) { + return name; + } + } + return COMMAND_NAMES[0]; +} + +function isOnPath(name) { + const dirs = (process.env.PATH || "").split(path.delimiter).filter((dir) => dir.length > 0); + const suffixes = process.platform === "win32" ? [".exe", ""] : [""]; + return dirs.some((dir) => + suffixes.some((suffix) => { + try { + accessSync(path.join(dir, name + suffix), constants.X_OK); + return true; + } catch { + return false; + } + }), + ); +} + +function startRepl() { + if (!requireTrustedWorkspace()) { + return; + } + const terminal = zed.window.createTerminal({ + name: "Frontal Code", + shellPath: frontalCodeCliPath(), + shellArgs: [], + }); + terminal.show(true); +} + +function requireTrustedWorkspace() { + if (zed.workspace.isTrusted) { + return true; + } + zed.window.showWarningMessage("Frontal Code: trust this workspace to run the CLI."); + return false; +} + +async function askInput() { + const question = await zed.window.showInputBox({ + prompt: "Ask Frontal Code", + placeHolder: "Explain the active file", + }); + if (!question?.trim()) { + return; + } + await runFrontalCodePrompt(question.trim()); +} + +async function askSelection() { + const editor = zed.window.activeTextEditor; + if (!editor) { + zed.window.showWarningMessage("Frontal Code: open an editor first."); + return; + } + const selected = editor.document.getText(editor.selection).trim(); + if (!selected) { + zed.window.showWarningMessage("Frontal Code: select some text first."); + return; + } + const question = await zed.window.showInputBox({ + prompt: "What should Frontal Code do with this selection?", + placeHolder: "Explain this code", + }); + if (!question?.trim()) { + return; + } + const prompt = `${question.trim()}\n\nSelected code:\n${selected}`; + await runFrontalCodePrompt(prompt); +} + +async function runFrontalCodePrompt(prompt) { + if (!requireTrustedWorkspace()) { + return; + } + const output = zed.window.createOutputChannel("Frontal Code"); + output.show(true); + output.appendLine("Running Frontal Code..."); + + const args = ["--output-format", "text", "prompt", prompt]; + const cwd = zed.workspace.workspaceFolders?.[0]?.uri?.fsPath; + const execOptions = cwd ? { cwd, maxBuffer: 16 * 1024 * 1024 } : { maxBuffer: 16 * 1024 * 1024 }; + + const startedAt = Date.now(); + execFile(frontalCodeCliPath(), args, execOptions, (error, stdout, stderr) => { + const elapsedMs = Date.now() - startedAt; + output.appendLine(`Frontal Code finished in ${elapsedMs} ms.`); + if (stdout?.trim()) { + output.appendLine(""); + output.appendLine(stdout.trimEnd()); + } + if (stderr?.trim()) { + output.appendLine(""); + output.appendLine("stderr:"); + output.appendLine(stderr.trimEnd()); + } + + if (error) { + const message = `Frontal Code command failed: ${error.message}`; + output.appendLine(message); + zed.window.showErrorMessage(message); + return; + } + + zed.window.showInformationMessage("Frontal Code response ready."); + }); +} + +module.exports = { + activate, + deactivate, +}; diff --git a/extensions/zed/package.json b/extensions/zed/package.json new file mode 100644 index 0000000..15b5705 --- /dev/null +++ b/extensions/zed/package.json @@ -0,0 +1,25 @@ +{ + "name": "@frontal-labs/code-zed", + "displayName": "Frontal Code Zed", + "description": "Frontal Code integration for the Zed editor", + "version": "0.1.0", + "publisher": "frontal-labs", + "repository": { + "type": "git", + "url": "https://github.com/frontal-labs/code.git", + "directory": "extensions/zed" + }, + "keywords": [ + "frontal-code", + "zed", + "extension" + ], + "author": "Frontal Labs", + "license": "Apache-2.0", + "scripts": { + "build": "node --check extension.js" + }, + "dependencies": { + "@zed-industries/extension-api": "^0.4.2" + } +} diff --git a/extensions/zed/zed/extensions.json b/extensions/zed/zed/extensions.json new file mode 100644 index 0000000..09ac28a --- /dev/null +++ b/extensions/zed/zed/extensions.json @@ -0,0 +1,20 @@ +{ + "id": "frontal-code", + "name": "Frontal Code", + "description": "Frontal Code integration for the Zed editor", + "version": "0.1.0", + "author": { + "name": "Frontal Labs" + }, + "homepage": "https://github.com/frontal-labs/code", + "repository": "https://github.com/frontal-labs/code", + "license": "Apache-2.0", + "icon": "", + "features": { + "commands": { + "startRepl": "Start the Frontal Code REPL", + "askSelection": "Ask Frontal Code about the current selection", + "askInput": "Ask Frontal Code a question" + } + } +} diff --git a/extensions/zod/README.md b/extensions/zod/README.md index 7b11d22..0e0b0cd 100644 --- a/extensions/zod/README.md +++ b/extensions/zod/README.md @@ -1,9 +1,9 @@ -# @frontal-labs/zod-schemas +# @frontal-labs/code-zod-schemas Zod schemas for the Frontal Code event protocol. The SDKs and extensions share them, so every consumer checks the same contract. ```ts -import { FrontalCodeEventSchema, parseFrontalCodeEvent } from "@frontal-labs/zod-schemas"; +import { FrontalCodeEventSchema, parseFrontalCodeEvent } from "@frontal-labs/code-zod-schemas"; const event = parseFrontalCodeEvent('{"type":"turn.started"}'); FrontalCodeEventSchema.parse(event); diff --git a/extensions/zod/package.json b/extensions/zod/package.json index 34c69f4..902add8 100644 --- a/extensions/zod/package.json +++ b/extensions/zod/package.json @@ -1,5 +1,5 @@ { - "name": "@frontal-labs/zod-schemas", + "name": "@frontal-labs/code-zod-schemas", "version": "0.1.0", "private": true, "description": "Zod schemas for the Frontal Code event protocol, shared by the SDKs and extensions.", diff --git a/sdks/typescript/package-lock.json b/sdks/typescript/package-lock.json index 6b2e463..1bc2d7e 100644 --- a/sdks/typescript/package-lock.json +++ b/sdks/typescript/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "license": "Apache-2.0", "devDependencies": { - "@frontal-labs/zod-schemas": "file:../../extensions/zod", + "@frontal-labs/code-zod-schemas": "file:../../extensions/zod", "@types/node": "^20.12.0", "typescript": "^5.4.0", "vite": "^6.4.4", @@ -21,7 +21,7 @@ } }, "../../extensions/zod": { - "name": "@frontal-labs/zod-schemas", + "name": "@frontal-labs/code-zod-schemas", "version": "0.1.0", "dev": true, "license": "Apache-2.0", @@ -475,7 +475,7 @@ "node": ">=18" } }, - "node_modules/@frontal-labs/zod-schemas": { + "node_modules/@frontal-labs/code-zod-schemas": { "resolved": "../../extensions/zod", "link": true }, diff --git a/sdks/typescript/package.json b/sdks/typescript/package.json index 405bd3f..f563ecf 100644 --- a/sdks/typescript/package.json +++ b/sdks/typescript/package.json @@ -62,7 +62,7 @@ "typescript": "^5.4.0", "vite": "^6.4.4", "vitest": "^4.1.12", - "@frontal-labs/zod-schemas": "file:../../extensions/zod", + "@frontal-labs/code-zod-schemas": "file:../../extensions/zod", "zod": "^4.6.7" }, "overrides": { diff --git a/sdks/typescript/tests/e2e.test.ts b/sdks/typescript/tests/e2e.test.ts index 6d337a5..795a7a2 100644 --- a/sdks/typescript/tests/e2e.test.ts +++ b/sdks/typescript/tests/e2e.test.ts @@ -1,7 +1,7 @@ import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { FrontalCodeEventSchema } from "@frontal-labs/zod-schemas"; +import { FrontalCodeEventSchema } from "@frontal-labs/code-zod-schemas"; import { describe, expect, it } from "vitest"; import { FrontalCode } from "../src/frontal-code.js"; import type { FrontalCodeEvent } from "../src/protocol.js";