Merge pull request 'POC: runnable VS Code extension on @cline/sdk (Feature #2)' (#3) from poc/cline-sdk-extension into main
This commit was merged in pull request #3.
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
out/
|
||||||
|
*.vsix
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Run Extension",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||||
|
"outFiles": ["${workspaceFolder}/out/**/*.cjs"],
|
||||||
|
"preLaunchTask": "npm: build"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+13
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "build",
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": ["$esbuild"],
|
||||||
|
"label": "npm: build",
|
||||||
|
"detail": "Bundle the extension with esbuild"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@~/.claude/wiggleverse.md
|
||||||
|
|
||||||
|
# vscode-cowriting-plugin
|
||||||
|
|
||||||
|
A standalone **VS Code extension** delivering the inner-loop human+machine
|
||||||
|
coauthoring experience (from `ben.stull/rfc-app#48`) on top of **Cline's
|
||||||
|
`@cline/sdk`** — no fork of Cline, its own coauthoring UI driving the SDK's
|
||||||
|
programmatic agent API. Apache-2.0 SDK.
|
||||||
|
|
||||||
|
- **Not** a GCP/standard-stack webapp — no flotilla/cloud infra.
|
||||||
|
- App manifest: [`app.json`](./app.json). Content/corpus repo:
|
||||||
|
`benstull/vscode-cowriting-plugin-content`.
|
||||||
|
- Tracker: Epic **#1** (the plugin), first Feature **#2** (the runnable POC
|
||||||
|
skeleton on `@cline/sdk`).
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# vscode-cowriting-plugin
|
||||||
|
|
||||||
|
Non-shippable **proof-of-concept** (Feature #2 of Epic #1): a standalone VS Code
|
||||||
|
extension that drives **[`@cline/sdk`](https://www.npmjs.com/package/@cline/sdk)**
|
||||||
|
— validating Approach A (own coauthoring extension on the Cline SDK, no fork).
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
Registers one command, **`Cowriting: Show Cline SDK Info`**, which loads
|
||||||
|
`@cline/sdk` and shows the SDK build version plus the agent's builtin tool
|
||||||
|
catalog (a pure, key-free SDK call) in a notification and the
|
||||||
|
"Cowriting (Cline SDK)" output channel.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- CommonJS extension bundled with esbuild (`src/extension.ts` → `out/extension.cjs`).
|
||||||
|
- `@cline/sdk` is ESM-only (Node ≥22) and uses `createRequire(import.meta.url)`,
|
||||||
|
so it is **not** bundled — it is shipped in `node_modules` and loaded at
|
||||||
|
runtime via dynamic `import()` from the vscode-free `src/cline.ts`.
|
||||||
|
|
||||||
|
## Run it (F5)
|
||||||
|
|
||||||
|
1. `npm install`
|
||||||
|
2. `npm run build`
|
||||||
|
3. Press **F5** (or Run → "Run Extension") to launch the Extension Development Host.
|
||||||
|
4. In the new window: **Cmd/Ctrl+Shift+P** → **"Cowriting: Show Cline SDK Info"**.
|
||||||
|
|
||||||
|
## Develop
|
||||||
|
|
||||||
|
- `npm run watch` — rebuild on change.
|
||||||
|
- `npm test` — run the unit test for the SDK driver.
|
||||||
|
- `npm run typecheck` — type-check without emit.
|
||||||
@@ -0,0 +1,468 @@
|
|||||||
|
# POC: Runnable VS Code Extension on `@cline/sdk` — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Scaffold a standalone, non-shippable VS Code extension that builds, launches in the Extension Development Host on F5, and runs one command that drives `@cline/sdk` and shows a real result — validating Approach A (standalone extension on the Cline SDK, no fork).
|
||||||
|
|
||||||
|
**Architecture:** A CommonJS VS Code extension bundled with esbuild (`vscode` and `@cline/sdk` both kept external). `@cline/sdk` is ESM-only (Node ≥22) and uses `createRequire(import.meta.url)`, so it **cannot** be bundled into a CJS extension — instead it is shipped in `node_modules` and loaded at runtime via dynamic `import('@cline/sdk')`. The SDK-driving logic lives in a vscode-free module (`src/cline.ts`) so it is unit-testable in Node; the vscode command wiring lives in `src/extension.ts`. The command calls the SDK's pure `getCoreBuiltinToolCatalog()` + `CORE_BUILD_VERSION` (no API keys, no agent run) and renders the result.
|
||||||
|
|
||||||
|
**Tech Stack:** TypeScript, VS Code Extension API (`engines.vscode`), esbuild (bundler), vitest (unit test), `@cline/sdk@0.0.46`.
|
||||||
|
|
||||||
|
**De-risking already done (empirical, see session 0002 transcript):**
|
||||||
|
- `@cline/sdk@0.0.46` loads in Node 24; `CORE_BUILD_VERSION === "0.0.46"`; `getCoreBuiltinToolCatalog()` returns 9 entries with shape `{id, description, headlessToolNames, defaultEnabled}` (e.g. `read_files`, `search_codebase`, `run_commands`, `editor`, `fetch_web_content`).
|
||||||
|
- Bundling the SDK **into** CJS fails (`import.meta.url` undefined → `createRequire` throws). Keeping it external + dynamic `import()` from a CJS bundle works and yields a ~2KB extension bundle.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| File | Responsibility |
|
||||||
|
| --- | --- |
|
||||||
|
| `package.json` | Extension manifest: `main`, `engines.vscode`, `contributes.commands`, `activationEvents`, scripts, deps. |
|
||||||
|
| `tsconfig.json` | TypeScript config (typecheck only; esbuild does the emit). |
|
||||||
|
| `esbuild.mjs` | Bundle `src/extension.ts` → `out/extension.cjs`, externalize `vscode` + `@cline/sdk`. |
|
||||||
|
| `src/cline.ts` | vscode-free SDK driver: `fetchSdkSummary()` dynamic-imports `@cline/sdk`, returns `{version, tools}`. Unit-testable. |
|
||||||
|
| `src/extension.ts` | `activate`/`deactivate`; registers the `cowriting.showClineSdkInfo` command; renders the summary via notification + output channel. |
|
||||||
|
| `test/cline.test.ts` | vitest unit test asserting `fetchSdkSummary()` returns a semver version and a non-empty tool list including `read_files`. |
|
||||||
|
| `.vscode/launch.json` | F5 → "Run Extension" launches the Extension Development Host with this extension. |
|
||||||
|
| `.vscode/tasks.json` | The `npm: build` task the launch config depends on. |
|
||||||
|
| `.gitignore` | Ignore `node_modules/`, `out/`. |
|
||||||
|
| `vitest.config.ts` | vitest node environment config. |
|
||||||
|
| `README.md` | What the POC is + how to run it (F5). |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: Project manifest and tooling config
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `package.json`
|
||||||
|
- Create: `tsconfig.json`
|
||||||
|
- Create: `.gitignore`
|
||||||
|
- Create: `vitest.config.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write `package.json`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "vscode-cowriting-plugin",
|
||||||
|
"displayName": "Cowriting (Cline SDK POC)",
|
||||||
|
"description": "Non-shippable POC: a standalone VS Code extension that drives @cline/sdk.",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"publisher": "benstull",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.90.0",
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"categories": ["Other"],
|
||||||
|
"main": "./out/extension.cjs",
|
||||||
|
"activationEvents": [],
|
||||||
|
"contributes": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "cowriting.showClineSdkInfo",
|
||||||
|
"title": "Cowriting: Show Cline SDK Info",
|
||||||
|
"category": "Cowriting"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node esbuild.mjs",
|
||||||
|
"watch": "node esbuild.mjs --watch",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"test": "vitest run",
|
||||||
|
"vscode:prepublish": "node esbuild.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@cline/sdk": "0.0.46"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/vscode": "^1.90.0",
|
||||||
|
"esbuild": "^0.23.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"vitest": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: `contributes.commands` declares the command without a `when`, so it appears in the Command Palette as soon as the extension activates. `activationEvents` is empty because declaring a command auto-generates `onCommand:` activation in VS Code ≥1.74.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write `tsconfig.json`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src", "test", "esbuild.mjs"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Write `.gitignore`**
|
||||||
|
|
||||||
|
```
|
||||||
|
node_modules/
|
||||||
|
out/
|
||||||
|
*.vsix
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Write `vitest.config.ts`**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: "node",
|
||||||
|
include: ["test/**/*.test.ts"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 5: Install dependencies**
|
||||||
|
|
||||||
|
Run: `npm install`
|
||||||
|
Expected: `node_modules/` populated, `@cline/sdk` present, no error exit.
|
||||||
|
|
||||||
|
- [ ] **Step 6: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add package.json tsconfig.json .gitignore vitest.config.ts package-lock.json
|
||||||
|
git commit -m "POC scaffold: extension manifest + tooling config (Feature #2)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: SDK driver module (TDD)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/cline.ts`
|
||||||
|
- Test: `test/cline.test.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the failing test**
|
||||||
|
|
||||||
|
`test/cline.test.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { fetchSdkSummary } from "../src/cline";
|
||||||
|
|
||||||
|
describe("fetchSdkSummary", () => {
|
||||||
|
it("loads @cline/sdk and returns its version and builtin tool catalog", async () => {
|
||||||
|
const summary = await fetchSdkSummary();
|
||||||
|
expect(summary.version).toMatch(/^\d+\.\d+\.\d+/);
|
||||||
|
expect(summary.tools.length).toBeGreaterThan(0);
|
||||||
|
expect(summary.tools.map((t) => t.id)).toContain("read_files");
|
||||||
|
for (const tool of summary.tools) {
|
||||||
|
expect(typeof tool.id).toBe("string");
|
||||||
|
expect(typeof tool.description).toBe("string");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run test to verify it fails**
|
||||||
|
|
||||||
|
Run: `npm test`
|
||||||
|
Expected: FAIL — cannot resolve `../src/cline` (module not created yet).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Write minimal implementation**
|
||||||
|
|
||||||
|
`src/cline.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
/**
|
||||||
|
* vscode-free driver for @cline/sdk.
|
||||||
|
*
|
||||||
|
* @cline/sdk is ESM-only (Node >=22) and uses createRequire(import.meta.url),
|
||||||
|
* so it must NOT be bundled into the CJS extension. We load it at runtime via
|
||||||
|
* dynamic import(); the bundler keeps it external (see esbuild.mjs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ClineTool {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SdkSummary {
|
||||||
|
version: string;
|
||||||
|
tools: ClineTool[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drive @cline/sdk with pure, key-free calls and return a renderable summary:
|
||||||
|
* the SDK build version and the agent's builtin tool catalog. Proves the SDK
|
||||||
|
* is linked and callable from the extension host.
|
||||||
|
*/
|
||||||
|
export async function fetchSdkSummary(): Promise<SdkSummary> {
|
||||||
|
const sdk = await import("@cline/sdk");
|
||||||
|
const catalog = sdk.getCoreBuiltinToolCatalog();
|
||||||
|
return {
|
||||||
|
version: sdk.CORE_BUILD_VERSION,
|
||||||
|
tools: catalog.map((entry) => ({
|
||||||
|
id: entry.id,
|
||||||
|
description: entry.description,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run test to verify it passes**
|
||||||
|
|
||||||
|
Run: `npm test`
|
||||||
|
Expected: PASS — version matches semver, tools include `read_files`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/cline.ts test/cline.test.ts
|
||||||
|
git commit -m "POC: vscode-free @cline/sdk driver with unit test (Feature #2)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Extension entry point and command wiring
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/extension.ts`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write `src/extension.ts`**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import * as vscode from "vscode";
|
||||||
|
import { fetchSdkSummary } from "./cline";
|
||||||
|
|
||||||
|
const CHANNEL_NAME = "Cowriting (Cline SDK)";
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext): void {
|
||||||
|
const output = vscode.window.createOutputChannel(CHANNEL_NAME);
|
||||||
|
context.subscriptions.push(output);
|
||||||
|
|
||||||
|
const command = vscode.commands.registerCommand(
|
||||||
|
"cowriting.showClineSdkInfo",
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
const summary = await fetchSdkSummary();
|
||||||
|
output.clear();
|
||||||
|
output.appendLine(`@cline/sdk version: ${summary.version}`);
|
||||||
|
output.appendLine(`Builtin tools (${summary.tools.length}):`);
|
||||||
|
for (const tool of summary.tools) {
|
||||||
|
output.appendLine(` • ${tool.id} — ${tool.description}`);
|
||||||
|
}
|
||||||
|
output.show(true);
|
||||||
|
await vscode.window.showInformationMessage(
|
||||||
|
`Cline SDK ${summary.version} loaded — ${summary.tools.length} builtin tools. See the "${CHANNEL_NAME}" output channel.`
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
output.appendLine(`Failed to drive @cline/sdk: ${message}`);
|
||||||
|
output.show(true);
|
||||||
|
await vscode.window.showErrorMessage(
|
||||||
|
`Cowriting: failed to load @cline/sdk — ${message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
context.subscriptions.push(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deactivate(): void {
|
||||||
|
// Nothing to clean up beyond the disposables registered on the context.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Typecheck**
|
||||||
|
|
||||||
|
Run: `npm run typecheck`
|
||||||
|
Expected: PASS — no type errors (requires `@types/vscode` and `@types/node`).
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add src/extension.ts
|
||||||
|
git commit -m "POC: extension activate + showClineSdkInfo command (Feature #2)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 4: esbuild bundle
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `esbuild.mjs`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write `esbuild.mjs`**
|
||||||
|
|
||||||
|
```js
|
||||||
|
import { build, context } from "esbuild";
|
||||||
|
|
||||||
|
const watch = process.argv.includes("--watch");
|
||||||
|
|
||||||
|
/** @type {import('esbuild').BuildOptions} */
|
||||||
|
const options = {
|
||||||
|
entryPoints: ["src/extension.ts"],
|
||||||
|
outfile: "out/extension.cjs",
|
||||||
|
bundle: true,
|
||||||
|
platform: "node",
|
||||||
|
format: "cjs",
|
||||||
|
target: "node20",
|
||||||
|
sourcemap: true,
|
||||||
|
// vscode is provided by the host; @cline/sdk is ESM-only and is loaded at
|
||||||
|
// runtime via dynamic import() from node_modules, so keep both external.
|
||||||
|
external: ["vscode", "@cline/sdk"],
|
||||||
|
logLevel: "info",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (watch) {
|
||||||
|
const ctx = await context(options);
|
||||||
|
await ctx.watch();
|
||||||
|
console.log("esbuild: watching…");
|
||||||
|
} else {
|
||||||
|
await build(options);
|
||||||
|
console.log("esbuild: build complete → out/extension.cjs");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Build**
|
||||||
|
|
||||||
|
Run: `npm run build`
|
||||||
|
Expected: `out/extension.cjs` created; log "build complete". Bundle is small (~few KB) because the SDK is external.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify the bundle keeps the SDK external**
|
||||||
|
|
||||||
|
Run: `node -e "const s=require('fs').readFileSync('out/extension.cjs','utf8'); console.log('has dynamic import of sdk:', s.includes('@cline/sdk')); console.log('bytes:', s.length)"`
|
||||||
|
Expected: `has dynamic import of sdk: true`; byte count in the low thousands (SDK not inlined).
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add esbuild.mjs
|
||||||
|
git commit -m "POC: esbuild bundle (vscode + @cline/sdk external) (Feature #2)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 5: F5 launch configuration
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `.vscode/launch.json`
|
||||||
|
- Create: `.vscode/tasks.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write `.vscode/tasks.json`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"type": "npm",
|
||||||
|
"script": "build",
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": ["$esbuild"],
|
||||||
|
"label": "npm: build",
|
||||||
|
"detail": "Bundle the extension with esbuild"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Write `.vscode/launch.json`**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Run Extension",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
|
||||||
|
"outFiles": ["${workspaceFolder}/out/**/*.cjs"],
|
||||||
|
"preLaunchTask": "npm: build"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add .vscode/launch.json .vscode/tasks.json
|
||||||
|
git commit -m "POC: F5 Run Extension launch config (Feature #2)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 6: README and verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `README.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write `README.md`**
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# vscode-cowriting-plugin
|
||||||
|
|
||||||
|
Non-shippable **proof-of-concept** (Feature #2 of Epic #1): a standalone VS Code
|
||||||
|
extension that drives **[`@cline/sdk`](https://www.npmjs.com/package/@cline/sdk)**
|
||||||
|
— validating Approach A (own coauthoring extension on the Cline SDK, no fork).
|
||||||
|
|
||||||
|
## What it does
|
||||||
|
|
||||||
|
Registers one command, **`Cowriting: Show Cline SDK Info`**, which loads
|
||||||
|
`@cline/sdk` and shows the SDK build version plus the agent's builtin tool
|
||||||
|
catalog (a pure, key-free SDK call) in a notification and the
|
||||||
|
"Cowriting (Cline SDK)" output channel.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- CommonJS extension bundled with esbuild (`src/extension.ts` → `out/extension.cjs`).
|
||||||
|
- `@cline/sdk` is ESM-only (Node ≥22) and uses `createRequire(import.meta.url)`,
|
||||||
|
so it is **not** bundled — it is shipped in `node_modules` and loaded at
|
||||||
|
runtime via dynamic `import()` from the vscode-free `src/cline.ts`.
|
||||||
|
|
||||||
|
## Run it (F5)
|
||||||
|
|
||||||
|
1. `npm install`
|
||||||
|
2. `npm run build`
|
||||||
|
3. Press **F5** (or Run → "Run Extension") to launch the Extension Development Host.
|
||||||
|
4. In the new window: **Cmd/Ctrl+Shift+P** → **"Cowriting: Show Cline SDK Info"**.
|
||||||
|
|
||||||
|
## Develop
|
||||||
|
|
||||||
|
- `npm run watch` — rebuild on change.
|
||||||
|
- `npm test` — run the unit test for the SDK driver.
|
||||||
|
- `npm run typecheck` — type-check without emit.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Full verification sweep**
|
||||||
|
|
||||||
|
Run: `npm install && npm run typecheck && npm test && npm run build`
|
||||||
|
Expected: install OK; typecheck OK; 1 test passes; build writes `out/extension.cjs`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add README.md
|
||||||
|
git commit -m "POC: README with run/dev instructions (Feature #2)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual / operator verification (cannot be automated in-session)
|
||||||
|
|
||||||
|
- [ ] Press **F5** in VS Code → Extension Development Host opens with no activation error.
|
||||||
|
- [ ] Run **"Cowriting: Show Cline SDK Info"** → notification shows `Cline SDK 0.0.46 loaded — 9 builtin tools`, and the output channel lists the 9 tool IDs with descriptions.
|
||||||
|
|
||||||
|
This is the POC's acceptance: repo builds, F5 launches the host with the
|
||||||
|
extension active, and one command drives `@cline/sdk` and shows a real result.
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
import { build, context } from "esbuild";
|
||||||
|
|
||||||
|
const watch = process.argv.includes("--watch");
|
||||||
|
|
||||||
|
/** @type {import('esbuild').BuildOptions} */
|
||||||
|
const options = {
|
||||||
|
entryPoints: ["src/extension.ts"],
|
||||||
|
outfile: "out/extension.cjs",
|
||||||
|
bundle: true,
|
||||||
|
platform: "node",
|
||||||
|
format: "cjs",
|
||||||
|
target: "node20",
|
||||||
|
sourcemap: true,
|
||||||
|
// vscode is provided by the host; @cline/sdk is ESM-only and is loaded at
|
||||||
|
// runtime via dynamic import() from node_modules, so keep both external.
|
||||||
|
external: ["vscode", "@cline/sdk"],
|
||||||
|
logLevel: "info",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (watch) {
|
||||||
|
const ctx = await context(options);
|
||||||
|
await ctx.watch();
|
||||||
|
console.log("esbuild: watching…");
|
||||||
|
} else {
|
||||||
|
await build(options);
|
||||||
|
console.log("esbuild: build complete → out/extension.cjs");
|
||||||
|
}
|
||||||
Generated
+6273
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "vscode-cowriting-plugin",
|
||||||
|
"displayName": "Cowriting (Cline SDK POC)",
|
||||||
|
"description": "Non-shippable POC: a standalone VS Code extension that drives @cline/sdk.",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"private": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"publisher": "benstull",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.90.0",
|
||||||
|
"node": ">=22"
|
||||||
|
},
|
||||||
|
"categories": ["Other"],
|
||||||
|
"main": "./out/extension.cjs",
|
||||||
|
"activationEvents": [],
|
||||||
|
"contributes": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "cowriting.showClineSdkInfo",
|
||||||
|
"title": "Cowriting: Show Cline SDK Info",
|
||||||
|
"category": "Cowriting"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node esbuild.mjs",
|
||||||
|
"watch": "node esbuild.mjs --watch",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"test": "vitest run",
|
||||||
|
"vscode:prepublish": "node esbuild.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@cline/sdk": "0.0.46"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.0.0",
|
||||||
|
"@types/vscode": "^1.90.0",
|
||||||
|
"esbuild": "^0.23.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"vitest": "^2.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* vscode-free driver for @cline/sdk.
|
||||||
|
*
|
||||||
|
* @cline/sdk is ESM-only (Node >=22) and uses createRequire(import.meta.url),
|
||||||
|
* so it must NOT be bundled into the CJS extension. We load it at runtime via
|
||||||
|
* dynamic import(); the bundler keeps it external (see esbuild.mjs).
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface ClineTool {
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SdkSummary {
|
||||||
|
version: string;
|
||||||
|
tools: ClineTool[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drive @cline/sdk with pure, key-free calls and return a renderable summary:
|
||||||
|
* the SDK build version and the agent's builtin tool catalog. Proves the SDK
|
||||||
|
* is linked and callable from the extension host.
|
||||||
|
*/
|
||||||
|
export async function fetchSdkSummary(): Promise<SdkSummary> {
|
||||||
|
const sdk = await import("@cline/sdk");
|
||||||
|
const catalog = sdk.getCoreBuiltinToolCatalog();
|
||||||
|
return {
|
||||||
|
version: sdk.CORE_BUILD_VERSION,
|
||||||
|
tools: catalog.map((entry) => ({
|
||||||
|
id: entry.id,
|
||||||
|
description: entry.description,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import * as vscode from "vscode";
|
||||||
|
import { fetchSdkSummary } from "./cline";
|
||||||
|
|
||||||
|
const CHANNEL_NAME = "Cowriting (Cline SDK)";
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext): void {
|
||||||
|
const output = vscode.window.createOutputChannel(CHANNEL_NAME);
|
||||||
|
context.subscriptions.push(output);
|
||||||
|
|
||||||
|
const command = vscode.commands.registerCommand(
|
||||||
|
"cowriting.showClineSdkInfo",
|
||||||
|
async () => {
|
||||||
|
try {
|
||||||
|
const summary = await fetchSdkSummary();
|
||||||
|
output.clear();
|
||||||
|
output.appendLine(`@cline/sdk version: ${summary.version}`);
|
||||||
|
output.appendLine(`Builtin tools (${summary.tools.length}):`);
|
||||||
|
for (const tool of summary.tools) {
|
||||||
|
output.appendLine(` • ${tool.id} — ${tool.description}`);
|
||||||
|
}
|
||||||
|
output.show(true);
|
||||||
|
await vscode.window.showInformationMessage(
|
||||||
|
`Cline SDK ${summary.version} loaded — ${summary.tools.length} builtin tools. See the "${CHANNEL_NAME}" output channel.`
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
const message = err instanceof Error ? err.message : String(err);
|
||||||
|
output.appendLine(`Failed to drive @cline/sdk: ${message}`);
|
||||||
|
output.show(true);
|
||||||
|
await vscode.window.showErrorMessage(
|
||||||
|
`Cowriting: failed to load @cline/sdk — ${message}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
context.subscriptions.push(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deactivate(): void {
|
||||||
|
// Nothing to clean up beyond the disposables registered on the context.
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import { fetchSdkSummary } from "../src/cline";
|
||||||
|
|
||||||
|
describe("fetchSdkSummary", () => {
|
||||||
|
it("loads @cline/sdk and returns its version and builtin tool catalog", async () => {
|
||||||
|
const summary = await fetchSdkSummary();
|
||||||
|
expect(summary.version).toMatch(/^\d+\.\d+\.\d+/);
|
||||||
|
expect(summary.tools.length).toBeGreaterThan(0);
|
||||||
|
expect(summary.tools.map((t) => t.id)).toContain("read_files");
|
||||||
|
for (const tool of summary.tools) {
|
||||||
|
expect(typeof tool.id).toBe("string");
|
||||||
|
expect(typeof tool.description).toBe("string");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src", "test", "esbuild.mjs"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { defineConfig } from "vitest/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
test: {
|
||||||
|
environment: "node",
|
||||||
|
include: ["test/**/*.test.ts"],
|
||||||
|
},
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user