Merge pull request #2811 from Hmbown/codex/v090-vscode-scaffold
feat(vscode): add local runtime extension scaffold
This commit is contained in:
@@ -39,6 +39,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
`workflow_run` tool until cancellation, replay, and worktree semantics are
|
||||
release-safe. Thanks @AdityaVG13 for the WhaleFlow draft and cost-tracking
|
||||
direction.
|
||||
- Added an official VS Code extension Phase 0 scaffold with terminal launch,
|
||||
local runtime attach checks, status bar state, and a CodeWhale runtime status
|
||||
view. This answers the VS Code GUI lane without exposing chat webviews,
|
||||
Agent View, inline edits, or retry/undo runtime endpoints yet (#461, #462,
|
||||
#480, #2580). Thanks @AiurArtanis for the Agent View prompt, @lbcheng888 for
|
||||
the earlier scaffold, and @BigBenLabs, @lzx1545642258, @yangdaowan,
|
||||
@mangdehuang, @VerrPower, @hejia-v, and @ygzhang-cn for the GUI/VS Code
|
||||
demand and validation trail.
|
||||
- Added `POST /v1/sessions` for runtime clients to save a completed thread as a
|
||||
managed session. The endpoint preserves thread title/model/mode/workspace
|
||||
metadata, maps missing threads to 404, and returns 409 instead of snapshotting
|
||||
|
||||
@@ -644,6 +644,10 @@ Current v0.9 track credits:
|
||||
- **[aboimpinto](https://github.com/aboimpinto)** — sidebar command polish and
|
||||
pausable custom-command lifecycle direction harvested into the v0.9 track
|
||||
(#2788, #2732)
|
||||
- **[lbcheng888](https://github.com/lbcheng888)** and
|
||||
**[AiurArtanis](https://github.com/AiurArtanis)** — VS Code extension
|
||||
scaffold direction and Agent View request that shaped the official Phase 0
|
||||
extension (#1022, #2580)
|
||||
- **[HUQIANTAO](https://github.com/HUQIANTAO)** — `web_run` cache-state
|
||||
lock-splitting, turn-metadata prefix-cache stability, and project-context
|
||||
cache work (#2502, #2517, #2636)
|
||||
|
||||
@@ -39,6 +39,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
`workflow_run` tool until cancellation, replay, and worktree semantics are
|
||||
release-safe. Thanks @AdityaVG13 for the WhaleFlow draft and cost-tracking
|
||||
direction.
|
||||
- Added an official VS Code extension Phase 0 scaffold with terminal launch,
|
||||
local runtime attach checks, status bar state, and a CodeWhale runtime status
|
||||
view. This answers the VS Code GUI lane without exposing chat webviews,
|
||||
Agent View, inline edits, or retry/undo runtime endpoints yet (#461, #462,
|
||||
#480, #2580). Thanks @AiurArtanis for the Agent View prompt, @lbcheng888 for
|
||||
the earlier scaffold, and @BigBenLabs, @lzx1545642258, @yangdaowan,
|
||||
@mangdehuang, @VerrPower, @hejia-v, and @ygzhang-cn for the GUI/VS Code
|
||||
demand and validation trail.
|
||||
- Added `POST /v1/sessions` for runtime clients to save a completed thread as a
|
||||
managed session. The endpoint preserves thread title/model/mode/workspace
|
||||
metadata, maps missing threads to 404, and returns 409 instead of snapshotting
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
*.vsix
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024-2025 DeepSeek CLI Contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,29 @@
|
||||
# CodeWhale for VS Code
|
||||
|
||||
Official CodeWhale extension scaffold for local development.
|
||||
|
||||
This first slice is intentionally small:
|
||||
|
||||
- open CodeWhale in an integrated terminal
|
||||
- start `codewhale serve --http` in a visible terminal
|
||||
- check a local runtime through `/health` and `/v1/runtime/info`
|
||||
- show connection state in the status bar and CodeWhale activity view
|
||||
|
||||
It does not expose the full chat webview, VS Code Agent View integration,
|
||||
inline edit application, marketplace publish workflow, or retry/undo/snapshot
|
||||
GUI endpoints yet.
|
||||
|
||||
## Local Use
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run compile
|
||||
npm run package
|
||||
code --install-extension codewhale-vscode-0.8.53.vsix
|
||||
```
|
||||
|
||||
Configure `codewhale.commandPath`, `codewhale.runtimeHost`,
|
||||
`codewhale.runtimePort`, and `codewhale.runtimeToken` from VS Code settings.
|
||||
|
||||
Keep the runtime on `127.0.0.1` unless you deliberately front it with trusted
|
||||
local networking controls.
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="CodeWhale">
|
||||
<rect width="128" height="128" rx="20" fill="#0f172a"/>
|
||||
<path d="M26 72c0-21 17-38 38-38h21c10 0 19 6 23 15 2 4 6 7 10 8-5 4-11 6-18 6h-2c-1 23-20 41-43 41H34c-7 0-12-5-12-12V80c0-4 3-8 8-8h-4z" fill="#38bdf8"/>
|
||||
<circle cx="82" cy="56" r="5" fill="#0f172a"/>
|
||||
<path d="M42 85h36M45 96h25" stroke="#0f172a" stroke-width="6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 484 B |
@@ -0,0 +1,95 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.activate = activate;
|
||||
exports.deactivate = deactivate;
|
||||
const vscode = __importStar(require("vscode"));
|
||||
const runtime_1 = require("./runtime");
|
||||
const status_1 = require("./status");
|
||||
function activate(context) {
|
||||
const output = vscode.window.createOutputChannel("CodeWhale");
|
||||
const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
|
||||
const statusView = new status_1.RuntimeStatusView();
|
||||
status.command = "codewhale.checkRuntime";
|
||||
context.subscriptions.push(output, status);
|
||||
context.subscriptions.push(vscode.window.registerWebviewViewProvider(status_1.RuntimeStatusView.viewType, statusView));
|
||||
const updateStatus = (text, tooltip) => {
|
||||
status.text = text;
|
||||
status.tooltip = tooltip;
|
||||
status.show();
|
||||
};
|
||||
updateStatus("$(terminal) CodeWhale", "Check CodeWhale runtime");
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.openTerminal", () => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
(0, runtime_1.openCodeWhaleTerminal)(config);
|
||||
output.appendLine(`Opened CodeWhale terminal using ${config.commandPath}.`);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.startRuntime", () => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
(0, runtime_1.startRuntimeTerminal)(config);
|
||||
const baseUrl = (0, runtime_1.runtimeBaseUrl)(config);
|
||||
updateStatus("$(sync~spin) CodeWhale", `Runtime terminal started for ${baseUrl}`);
|
||||
output.appendLine(`Started CodeWhale runtime terminal at ${baseUrl}.`);
|
||||
void vscode.window.showInformationMessage(`CodeWhale runtime starting at ${baseUrl}`);
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.checkRuntime", async () => {
|
||||
const config = (0, runtime_1.readRuntimeConfig)();
|
||||
updateStatus("$(sync~spin) CodeWhale", "Checking CodeWhale runtime...");
|
||||
const state = await (0, runtime_1.checkRuntime)(config);
|
||||
statusView.update(state);
|
||||
switch (state.kind) {
|
||||
case "connected":
|
||||
updateStatus("$(check) CodeWhale", state.detail);
|
||||
break;
|
||||
case "auth-required":
|
||||
updateStatus("$(lock) CodeWhale", state.detail);
|
||||
break;
|
||||
case "offline":
|
||||
case "error":
|
||||
updateStatus("$(warning) CodeWhale", state.detail);
|
||||
break;
|
||||
}
|
||||
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
|
||||
return state;
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand("codewhale.openRuntimeDocs", () => {
|
||||
void vscode.env.openExternal(vscode.Uri.parse("https://github.com/Hmbown/CodeWhale/blob/main/docs/RUNTIME_API.md"));
|
||||
}));
|
||||
void vscode.commands.executeCommand("codewhale.checkRuntime");
|
||||
}
|
||||
function deactivate() {
|
||||
// No background process is owned by the extension; runtime starts in a user-visible terminal.
|
||||
}
|
||||
//# sourceMappingURL=extension.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,161 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.readRuntimeConfig = readRuntimeConfig;
|
||||
exports.runtimeBaseUrl = runtimeBaseUrl;
|
||||
exports.checkRuntime = checkRuntime;
|
||||
exports.startRuntimeTerminal = startRuntimeTerminal;
|
||||
exports.openCodeWhaleTerminal = openCodeWhaleTerminal;
|
||||
const http = __importStar(require("node:http"));
|
||||
const vscode = __importStar(require("vscode"));
|
||||
function readRuntimeConfig() {
|
||||
const config = vscode.workspace.getConfiguration("codewhale");
|
||||
const commandPath = config.get("commandPath", "codewhale").trim() || "codewhale";
|
||||
const host = config.get("runtimeHost", "127.0.0.1").trim() || "127.0.0.1";
|
||||
const port = config.get("runtimePort", 7878);
|
||||
const token = config.get("runtimeToken", "").trim();
|
||||
return {
|
||||
commandPath,
|
||||
host,
|
||||
port,
|
||||
token: token.length > 0 ? token : undefined,
|
||||
};
|
||||
}
|
||||
function runtimeBaseUrl(config) {
|
||||
return `http://${config.host}:${config.port}`;
|
||||
}
|
||||
async function checkRuntime(config) {
|
||||
const baseUrl = runtimeBaseUrl(config);
|
||||
const health = await requestJson(`${baseUrl}/health`, config.token);
|
||||
if (health.statusCode === 0) {
|
||||
return { kind: "offline", baseUrl, detail: "Runtime is not reachable." };
|
||||
}
|
||||
if (health.statusCode === 401) {
|
||||
return { kind: "auth-required", baseUrl, detail: "Runtime requires a token." };
|
||||
}
|
||||
if (health.statusCode !== 200) {
|
||||
return {
|
||||
kind: "error",
|
||||
baseUrl,
|
||||
detail: `Health check returned HTTP ${health.statusCode}.`,
|
||||
};
|
||||
}
|
||||
const info = await requestJson(`${baseUrl}/v1/runtime/info`, config.token);
|
||||
if (info.statusCode === 401) {
|
||||
return { kind: "auth-required", baseUrl, detail: "Runtime info requires a token." };
|
||||
}
|
||||
const version = readVersion(info.body);
|
||||
return {
|
||||
kind: "connected",
|
||||
baseUrl,
|
||||
detail: version ? `Connected to CodeWhale ${version}.` : "Connected to CodeWhale runtime.",
|
||||
version,
|
||||
};
|
||||
}
|
||||
function startRuntimeTerminal(config) {
|
||||
const terminal = vscode.window.createTerminal("CodeWhale Runtime");
|
||||
const args = [
|
||||
"serve",
|
||||
"--http",
|
||||
"--host",
|
||||
shellQuote(config.host),
|
||||
"--port",
|
||||
String(config.port),
|
||||
];
|
||||
if (config.token) {
|
||||
args.push("--auth-token", shellQuote(config.token));
|
||||
}
|
||||
terminal.sendText(`${shellQuote(config.commandPath)} ${args.join(" ")}`);
|
||||
terminal.show();
|
||||
return terminal;
|
||||
}
|
||||
function openCodeWhaleTerminal(config) {
|
||||
const terminal = vscode.window.createTerminal("CodeWhale");
|
||||
terminal.sendText(shellQuote(config.commandPath));
|
||||
terminal.show();
|
||||
return terminal;
|
||||
}
|
||||
async function requestJson(url, token) {
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
const request = http.get(url, {
|
||||
timeout: 2500,
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
||||
}, (response) => {
|
||||
let body = "";
|
||||
response.setEncoding("utf8");
|
||||
response.on("data", (chunk) => {
|
||||
body += chunk;
|
||||
});
|
||||
response.on("end", () => {
|
||||
resolve({
|
||||
statusCode: response.statusCode ?? 0,
|
||||
body: parseJson(body),
|
||||
});
|
||||
});
|
||||
});
|
||||
request.on("timeout", () => {
|
||||
request.destroy(new Error("Runtime check timed out."));
|
||||
});
|
||||
request.on("error", reject);
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
return { statusCode: 0, body: { error: detail } };
|
||||
}
|
||||
}
|
||||
function parseJson(raw) {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
function readVersion(value) {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const version = value.version;
|
||||
return typeof version === "string" ? version : undefined;
|
||||
}
|
||||
function shellQuote(value) {
|
||||
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `'${value.replace(/'/g, "'\\''")}'`;
|
||||
}
|
||||
//# sourceMappingURL=runtime.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,131 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.RuntimeStatusView = void 0;
|
||||
const vscode = __importStar(require("vscode"));
|
||||
class RuntimeStatusView {
|
||||
static viewType = "codewhale.runtimeStatus";
|
||||
view;
|
||||
state = {
|
||||
kind: "offline",
|
||||
baseUrl: "http://127.0.0.1:7878",
|
||||
detail: "Runtime has not been checked yet.",
|
||||
};
|
||||
resolveWebviewView(view) {
|
||||
this.view = view;
|
||||
view.webview.options = { enableScripts: true };
|
||||
view.webview.onDidReceiveMessage((message) => {
|
||||
if (message.command === "check") {
|
||||
void vscode.commands.executeCommand("codewhale.checkRuntime");
|
||||
}
|
||||
else if (message.command === "start") {
|
||||
void vscode.commands.executeCommand("codewhale.startRuntime");
|
||||
}
|
||||
else if (message.command === "terminal") {
|
||||
void vscode.commands.executeCommand("codewhale.openTerminal");
|
||||
}
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
update(state) {
|
||||
this.state = state;
|
||||
this.render();
|
||||
}
|
||||
render() {
|
||||
if (!this.view) {
|
||||
return;
|
||||
}
|
||||
const badge = labelFor(this.state.kind);
|
||||
const nonce = makeNonce();
|
||||
this.view.webview.html = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body { padding: 14px; color: var(--vscode-foreground); font-family: var(--vscode-font-family); }
|
||||
.status { margin-bottom: 12px; font-weight: 600; }
|
||||
.detail { margin: 0 0 14px; color: var(--vscode-descriptionForeground); line-height: 1.45; }
|
||||
code { color: var(--vscode-textLink-foreground); }
|
||||
button { width: 100%; margin: 4px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="status">${escapeHtml(badge)}</div>
|
||||
<p class="detail">${escapeHtml(this.state.detail)}</p>
|
||||
<p class="detail"><code>${escapeHtml(this.state.baseUrl)}</code></p>
|
||||
<button data-command="check">Check Runtime</button>
|
||||
<button data-command="start">Start Local Runtime</button>
|
||||
<button data-command="terminal">Open CodeWhale Terminal</button>
|
||||
<script nonce="${nonce}">
|
||||
const vscode = acquireVsCodeApi();
|
||||
for (const button of document.querySelectorAll("button[data-command]")) {
|
||||
button.addEventListener("click", () => vscode.postMessage({ command: button.dataset.command }));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
exports.RuntimeStatusView = RuntimeStatusView;
|
||||
function labelFor(kind) {
|
||||
switch (kind) {
|
||||
case "connected":
|
||||
return "Connected";
|
||||
case "auth-required":
|
||||
return "Token Required";
|
||||
case "error":
|
||||
return "Runtime Error";
|
||||
case "offline":
|
||||
return "Offline";
|
||||
}
|
||||
}
|
||||
function escapeHtml(value) {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
function makeNonce() {
|
||||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let nonce = "";
|
||||
for (let index = 0; index < 32; index += 1) {
|
||||
nonce += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
|
||||
}
|
||||
return nonce;
|
||||
}
|
||||
//# sourceMappingURL=status.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"status.js","sourceRoot":"","sources":["../src/status.ts"],"names":[],"mappings":"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"}
|
||||
Generated
+3851
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"name": "codewhale-vscode",
|
||||
"displayName": "CodeWhale",
|
||||
"description": "Official CodeWhale VS Code integration scaffold for local runtime attach and terminal launch.",
|
||||
"version": "0.8.53",
|
||||
"publisher": "codewhale",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Hmbown/CodeWhale.git",
|
||||
"directory": "extensions/vscode"
|
||||
},
|
||||
"engines": {
|
||||
"vscode": "^1.90.0"
|
||||
},
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onCommand:codewhale.openTerminal",
|
||||
"onCommand:codewhale.startRuntime",
|
||||
"onCommand:codewhale.checkRuntime",
|
||||
"onCommand:codewhale.openRuntimeDocs",
|
||||
"onView:codewhale.runtimeStatus"
|
||||
],
|
||||
"main": "./out/extension.js",
|
||||
"files": [
|
||||
"out",
|
||||
"media",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "codewhale.openTerminal",
|
||||
"title": "CodeWhale: Open Terminal"
|
||||
},
|
||||
{
|
||||
"command": "codewhale.startRuntime",
|
||||
"title": "CodeWhale: Start Local Runtime"
|
||||
},
|
||||
{
|
||||
"command": "codewhale.checkRuntime",
|
||||
"title": "CodeWhale: Check Runtime"
|
||||
},
|
||||
{
|
||||
"command": "codewhale.openRuntimeDocs",
|
||||
"title": "CodeWhale: Open Runtime API Docs"
|
||||
}
|
||||
],
|
||||
"configuration": {
|
||||
"title": "CodeWhale",
|
||||
"properties": {
|
||||
"codewhale.commandPath": {
|
||||
"type": "string",
|
||||
"default": "codewhale",
|
||||
"description": "Command or absolute path used to launch CodeWhale."
|
||||
},
|
||||
"codewhale.runtimeHost": {
|
||||
"type": "string",
|
||||
"default": "127.0.0.1",
|
||||
"description": "Local host used for CodeWhale runtime attach checks."
|
||||
},
|
||||
"codewhale.runtimePort": {
|
||||
"type": "number",
|
||||
"default": 7878,
|
||||
"minimum": 1,
|
||||
"maximum": 65535,
|
||||
"description": "Local port used for CodeWhale runtime attach checks."
|
||||
},
|
||||
"codewhale.runtimeToken": {
|
||||
"type": "string",
|
||||
"default": "",
|
||||
"description": "Optional bearer token for authenticated runtime endpoints."
|
||||
}
|
||||
}
|
||||
},
|
||||
"viewsContainers": {
|
||||
"activitybar": [
|
||||
{
|
||||
"id": "codewhale",
|
||||
"title": "CodeWhale",
|
||||
"icon": "media/codewhale.svg"
|
||||
}
|
||||
]
|
||||
},
|
||||
"views": {
|
||||
"codewhale": [
|
||||
{
|
||||
"type": "webview",
|
||||
"id": "codewhale.runtimeStatus",
|
||||
"name": "Runtime"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "tsc -p ./",
|
||||
"check": "npm run compile",
|
||||
"package": "vsce package --no-dependencies"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.27",
|
||||
"@types/vscode": "^1.90.0",
|
||||
"@vscode/vsce": "^3.7.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import * as vscode from "vscode";
|
||||
import {
|
||||
checkRuntime,
|
||||
openCodeWhaleTerminal,
|
||||
readRuntimeConfig,
|
||||
runtimeBaseUrl,
|
||||
startRuntimeTerminal,
|
||||
} from "./runtime";
|
||||
import { RuntimeStatusView } from "./status";
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
const output = vscode.window.createOutputChannel("CodeWhale");
|
||||
const status = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 100);
|
||||
const statusView = new RuntimeStatusView();
|
||||
|
||||
status.command = "codewhale.checkRuntime";
|
||||
context.subscriptions.push(output, status);
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(RuntimeStatusView.viewType, statusView),
|
||||
);
|
||||
|
||||
const updateStatus = (text: string, tooltip: string): void => {
|
||||
status.text = text;
|
||||
status.tooltip = tooltip;
|
||||
status.show();
|
||||
};
|
||||
|
||||
updateStatus("$(terminal) CodeWhale", "Check CodeWhale runtime");
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("codewhale.openTerminal", () => {
|
||||
const config = readRuntimeConfig();
|
||||
openCodeWhaleTerminal(config);
|
||||
output.appendLine(`Opened CodeWhale terminal using ${config.commandPath}.`);
|
||||
}),
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("codewhale.startRuntime", () => {
|
||||
const config = readRuntimeConfig();
|
||||
startRuntimeTerminal(config);
|
||||
const baseUrl = runtimeBaseUrl(config);
|
||||
updateStatus("$(sync~spin) CodeWhale", `Runtime terminal started for ${baseUrl}`);
|
||||
output.appendLine(`Started CodeWhale runtime terminal at ${baseUrl}.`);
|
||||
void vscode.window.showInformationMessage(`CodeWhale runtime starting at ${baseUrl}`);
|
||||
}),
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("codewhale.checkRuntime", async () => {
|
||||
const config = readRuntimeConfig();
|
||||
updateStatus("$(sync~spin) CodeWhale", "Checking CodeWhale runtime...");
|
||||
const state = await checkRuntime(config);
|
||||
statusView.update(state);
|
||||
|
||||
switch (state.kind) {
|
||||
case "connected":
|
||||
updateStatus("$(check) CodeWhale", state.detail);
|
||||
break;
|
||||
case "auth-required":
|
||||
updateStatus("$(lock) CodeWhale", state.detail);
|
||||
break;
|
||||
case "offline":
|
||||
case "error":
|
||||
updateStatus("$(warning) CodeWhale", state.detail);
|
||||
break;
|
||||
}
|
||||
|
||||
output.appendLine(`${new Date().toISOString()} ${state.kind}: ${state.detail}`);
|
||||
return state;
|
||||
}),
|
||||
);
|
||||
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("codewhale.openRuntimeDocs", () => {
|
||||
void vscode.env.openExternal(
|
||||
vscode.Uri.parse(
|
||||
"https://github.com/Hmbown/CodeWhale/blob/main/docs/RUNTIME_API.md",
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
void vscode.commands.executeCommand("codewhale.checkRuntime");
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
// No background process is owned by the extension; runtime starts in a user-visible terminal.
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
import * as http from "node:http";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
export type RuntimeStateKind = "connected" | "offline" | "auth-required" | "error";
|
||||
|
||||
export interface RuntimeState {
|
||||
kind: RuntimeStateKind;
|
||||
baseUrl: string;
|
||||
detail: string;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface RuntimeConfig {
|
||||
commandPath: string;
|
||||
host: string;
|
||||
port: number;
|
||||
token?: string;
|
||||
}
|
||||
|
||||
export function readRuntimeConfig(): RuntimeConfig {
|
||||
const config = vscode.workspace.getConfiguration("codewhale");
|
||||
const commandPath = config.get<string>("commandPath", "codewhale").trim() || "codewhale";
|
||||
const host = config.get<string>("runtimeHost", "127.0.0.1").trim() || "127.0.0.1";
|
||||
const port = config.get<number>("runtimePort", 7878);
|
||||
const token = config.get<string>("runtimeToken", "").trim();
|
||||
return {
|
||||
commandPath,
|
||||
host,
|
||||
port,
|
||||
token: token.length > 0 ? token : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function runtimeBaseUrl(config: RuntimeConfig): string {
|
||||
return `http://${config.host}:${config.port}`;
|
||||
}
|
||||
|
||||
export async function checkRuntime(config: RuntimeConfig): Promise<RuntimeState> {
|
||||
const baseUrl = runtimeBaseUrl(config);
|
||||
const health = await requestJson(`${baseUrl}/health`, config.token);
|
||||
if (health.statusCode === 0) {
|
||||
return { kind: "offline", baseUrl, detail: "Runtime is not reachable." };
|
||||
}
|
||||
if (health.statusCode === 401) {
|
||||
return { kind: "auth-required", baseUrl, detail: "Runtime requires a token." };
|
||||
}
|
||||
if (health.statusCode !== 200) {
|
||||
return {
|
||||
kind: "error",
|
||||
baseUrl,
|
||||
detail: `Health check returned HTTP ${health.statusCode}.`,
|
||||
};
|
||||
}
|
||||
|
||||
const info = await requestJson(`${baseUrl}/v1/runtime/info`, config.token);
|
||||
if (info.statusCode === 401) {
|
||||
return { kind: "auth-required", baseUrl, detail: "Runtime info requires a token." };
|
||||
}
|
||||
|
||||
const version = readVersion(info.body);
|
||||
return {
|
||||
kind: "connected",
|
||||
baseUrl,
|
||||
detail: version ? `Connected to CodeWhale ${version}.` : "Connected to CodeWhale runtime.",
|
||||
version,
|
||||
};
|
||||
}
|
||||
|
||||
export function startRuntimeTerminal(config: RuntimeConfig): vscode.Terminal {
|
||||
const terminal = vscode.window.createTerminal("CodeWhale Runtime");
|
||||
const args = [
|
||||
"serve",
|
||||
"--http",
|
||||
"--host",
|
||||
shellQuote(config.host),
|
||||
"--port",
|
||||
String(config.port),
|
||||
];
|
||||
if (config.token) {
|
||||
args.push("--auth-token", shellQuote(config.token));
|
||||
}
|
||||
terminal.sendText(`${shellQuote(config.commandPath)} ${args.join(" ")}`);
|
||||
terminal.show();
|
||||
return terminal;
|
||||
}
|
||||
|
||||
export function openCodeWhaleTerminal(config: RuntimeConfig): vscode.Terminal {
|
||||
const terminal = vscode.window.createTerminal("CodeWhale");
|
||||
terminal.sendText(shellQuote(config.commandPath));
|
||||
terminal.show();
|
||||
return terminal;
|
||||
}
|
||||
|
||||
async function requestJson(
|
||||
url: string,
|
||||
token: string | undefined,
|
||||
): Promise<{ statusCode: number; body: unknown }> {
|
||||
try {
|
||||
return await new Promise<{ statusCode: number; body: unknown }>((resolve, reject) => {
|
||||
const request = http.get(
|
||||
url,
|
||||
{
|
||||
timeout: 2500,
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
||||
},
|
||||
(response) => {
|
||||
let body = "";
|
||||
response.setEncoding("utf8");
|
||||
response.on("data", (chunk: string) => {
|
||||
body += chunk;
|
||||
});
|
||||
response.on("end", () => {
|
||||
resolve({
|
||||
statusCode: response.statusCode ?? 0,
|
||||
body: parseJson(body),
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
request.on("timeout", () => {
|
||||
request.destroy(new Error("Runtime check timed out."));
|
||||
});
|
||||
request.on("error", reject);
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const detail = error instanceof Error ? error.message : String(error);
|
||||
return { statusCode: 0, body: { error: detail } };
|
||||
}
|
||||
}
|
||||
|
||||
function parseJson(raw: string): unknown {
|
||||
try {
|
||||
return JSON.parse(raw);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function readVersion(value: unknown): string | undefined {
|
||||
if (!value || typeof value !== "object") {
|
||||
return undefined;
|
||||
}
|
||||
const version = (value as { version?: unknown }).version;
|
||||
return typeof version === "string" ? version : undefined;
|
||||
}
|
||||
|
||||
function shellQuote(value: string): string {
|
||||
if (/^[A-Za-z0-9_./:=+-]+$/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `'${value.replace(/'/g, "'\\''")}'`;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import * as vscode from "vscode";
|
||||
import type { RuntimeState } from "./runtime";
|
||||
|
||||
export class RuntimeStatusView implements vscode.WebviewViewProvider {
|
||||
public static readonly viewType = "codewhale.runtimeStatus";
|
||||
|
||||
private view?: vscode.WebviewView;
|
||||
private state: RuntimeState = {
|
||||
kind: "offline",
|
||||
baseUrl: "http://127.0.0.1:7878",
|
||||
detail: "Runtime has not been checked yet.",
|
||||
};
|
||||
|
||||
resolveWebviewView(view: vscode.WebviewView): void {
|
||||
this.view = view;
|
||||
view.webview.options = { enableScripts: true };
|
||||
view.webview.onDidReceiveMessage((message: { command?: string }) => {
|
||||
if (message.command === "check") {
|
||||
void vscode.commands.executeCommand("codewhale.checkRuntime");
|
||||
} else if (message.command === "start") {
|
||||
void vscode.commands.executeCommand("codewhale.startRuntime");
|
||||
} else if (message.command === "terminal") {
|
||||
void vscode.commands.executeCommand("codewhale.openTerminal");
|
||||
}
|
||||
});
|
||||
this.render();
|
||||
}
|
||||
|
||||
update(state: RuntimeState): void {
|
||||
this.state = state;
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
if (!this.view) {
|
||||
return;
|
||||
}
|
||||
|
||||
const badge = labelFor(this.state.kind);
|
||||
const nonce = makeNonce();
|
||||
this.view.webview.html = `<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'nonce-${nonce}';">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
body { padding: 14px; color: var(--vscode-foreground); font-family: var(--vscode-font-family); }
|
||||
.status { margin-bottom: 12px; font-weight: 600; }
|
||||
.detail { margin: 0 0 14px; color: var(--vscode-descriptionForeground); line-height: 1.45; }
|
||||
code { color: var(--vscode-textLink-foreground); }
|
||||
button { width: 100%; margin: 4px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="status">${escapeHtml(badge)}</div>
|
||||
<p class="detail">${escapeHtml(this.state.detail)}</p>
|
||||
<p class="detail"><code>${escapeHtml(this.state.baseUrl)}</code></p>
|
||||
<button data-command="check">Check Runtime</button>
|
||||
<button data-command="start">Start Local Runtime</button>
|
||||
<button data-command="terminal">Open CodeWhale Terminal</button>
|
||||
<script nonce="${nonce}">
|
||||
const vscode = acquireVsCodeApi();
|
||||
for (const button of document.querySelectorAll("button[data-command]")) {
|
||||
button.addEventListener("click", () => vscode.postMessage({ command: button.dataset.command }));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
function labelFor(kind: RuntimeState["kind"]): string {
|
||||
switch (kind) {
|
||||
case "connected":
|
||||
return "Connected";
|
||||
case "auth-required":
|
||||
return "Token Required";
|
||||
case "error":
|
||||
return "Runtime Error";
|
||||
case "offline":
|
||||
return "Offline";
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(value: string): string {
|
||||
return value
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
function makeNonce(): string {
|
||||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let nonce = "";
|
||||
for (let index = 0; index < 32; index += 1) {
|
||||
nonce += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
|
||||
}
|
||||
return nonce;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"outDir": "out",
|
||||
"rootDir": "src",
|
||||
"strict": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user