vscode-pi-companion 0.0.10 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mcp.json +7 -0
- package/.vscodeignore +6 -4
- package/extensions/index.ts +148 -172
- package/package.json +2 -14
- package/scripts/release.sh +47 -0
- package/src/extension.ts +0 -29
- package/.github/workflows/publish.yml +0 -62
- package/.github/workflows/release.yml +0 -19
- package/.vscode/launch.json +0 -17
- package/.vscode/tasks.json +0 -12
- package/screenshot.png +0 -0
- package/tsconfig.json +0 -16
package/.mcp.json
ADDED
package/.vscodeignore
CHANGED
package/extensions/index.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* PI Companion Extension
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
4
|
* Connects to the VS Code extension via SSE for real-time context updates.
|
|
5
|
-
* No polling, instant updates, no tool calls visible.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
7
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
@@ -13,70 +12,11 @@ import * as http from "http";
|
|
|
13
12
|
import { execSync } from "child_process";
|
|
14
13
|
import { Type } from "@sinclair/typebox";
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Install VS Code extension from marketplace
|
|
22
|
-
function installFromMarketplace(ctx: ExtensionContext): void {
|
|
23
|
-
try {
|
|
24
|
-
execSync('code --install-extension ravshansbox.vscode-pi-companion --force', { stdio: 'pipe' });
|
|
25
|
-
ctx.ui.notify("PI Companion: VS Code extension installed. Reload window to activate.", "info");
|
|
26
|
-
} catch {
|
|
27
|
-
// Silently ignore
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Check if extension is installed
|
|
32
|
-
function isExtensionInstalled(): boolean {
|
|
33
|
-
try {
|
|
34
|
-
const result = execSync('code --list-extensions', { encoding: 'utf8' });
|
|
35
|
-
return result.includes('ravshansbox.vscode-pi-companion');
|
|
36
|
-
} catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Install/update VS Code extension if running in VS Code terminal
|
|
42
|
-
async function installVSCodeExtension(ctx: ExtensionContext): Promise<void> {
|
|
43
|
-
if (!isRunningInVSCode()) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const isInstalled = isExtensionInstalled();
|
|
48
|
-
|
|
49
|
-
if (isInstalled) {
|
|
50
|
-
// Already installed - check for updates
|
|
51
|
-
const latestVersion = await getLatestVersion();
|
|
52
|
-
if (latestVersion) {
|
|
53
|
-
console.log(`[PI Companion] Update available: v${latestVersion}`);
|
|
54
|
-
console.log(`[PI Companion] Run: pi install git:github.com/ravshansbox/vscode-pi-companion`);
|
|
55
|
-
}
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Not installed - install from marketplace
|
|
60
|
-
installFromMarketplace(ctx);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Get latest version from GitHub
|
|
64
|
-
async function getLatestVersion(): Promise<string | null> {
|
|
65
|
-
try {
|
|
66
|
-
const response = await fetch('https://api.github.com/repos/ravshansbox/vscode-pi-companion/releases/latest', {
|
|
67
|
-
headers: { 'Accept': 'application/vnd.github.v3+json' }
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
if (!response.ok) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const release = await response.json() as { tag_name?: string };
|
|
75
|
-
return release.tag_name?.replace('v', '') || null;
|
|
76
|
-
} catch {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
15
|
+
const CONNECTION_DIR = path.join(os.tmpdir(), "pi-companion");
|
|
16
|
+
const WIDGET_DEBOUNCE_MS = 50;
|
|
17
|
+
const RECONNECT_DELAY_MS = 2000;
|
|
18
|
+
const PORT_VALIDATE_TIMEOUT_MS = 750;
|
|
19
|
+
const MAX_INJECTED_LINES = 100;
|
|
80
20
|
|
|
81
21
|
interface IdeContext {
|
|
82
22
|
workspaceState: {
|
|
@@ -95,6 +35,12 @@ interface IdeContext {
|
|
|
95
35
|
};
|
|
96
36
|
}
|
|
97
37
|
|
|
38
|
+
interface ConnectionInfo {
|
|
39
|
+
port: number;
|
|
40
|
+
workspacePath: string;
|
|
41
|
+
authToken: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
98
44
|
let currentContext: IdeContext | undefined;
|
|
99
45
|
let currentCtx: ExtensionContext | undefined;
|
|
100
46
|
let lastWidgetKey = "";
|
|
@@ -102,34 +48,95 @@ let widgetDebounceTimer: NodeJS.Timeout | undefined;
|
|
|
102
48
|
let reconnectTimer: NodeJS.Timeout | undefined;
|
|
103
49
|
let sseRequest: http.ClientRequest | undefined;
|
|
104
50
|
|
|
105
|
-
function
|
|
106
|
-
|
|
51
|
+
function isRunningInVSCode(): boolean {
|
|
52
|
+
return process.env.TERM_PROGRAM === "vscode";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isExtensionInstalled(): boolean {
|
|
56
|
+
try {
|
|
57
|
+
const result = execSync("code --list-extensions", { encoding: "utf8" });
|
|
58
|
+
return result.includes("ravshansbox.vscode-pi-companion");
|
|
59
|
+
} catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function installFromMarketplace(ctx: ExtensionContext): void {
|
|
65
|
+
try {
|
|
66
|
+
execSync("code --install-extension ravshansbox.vscode-pi-companion --force", { stdio: "pipe" });
|
|
67
|
+
ctx.ui.notify("PI Companion: VS Code extension installed. Reload window to activate.", "info");
|
|
68
|
+
} catch {
|
|
69
|
+
ctx.ui.notify("PI Companion: Failed to auto-install VS Code extension.", "warning");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
107
72
|
|
|
73
|
+
function ensureVSCodeExtensionInstalled(ctx: ExtensionContext): void {
|
|
74
|
+
if (!isRunningInVSCode()) return;
|
|
75
|
+
if (!isExtensionInstalled()) {
|
|
76
|
+
installFromMarketplace(ctx);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function readLatestConnectionInfo(): ConnectionInfo | undefined {
|
|
108
81
|
try {
|
|
109
|
-
const files = fs.readdirSync(
|
|
82
|
+
const files = fs.readdirSync(CONNECTION_DIR);
|
|
110
83
|
const connectionFiles = files
|
|
111
84
|
.filter((f) => f.startsWith("connection-") && f.endsWith(".json"))
|
|
112
85
|
.map((f) => ({
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
mtime: fs.statSync(path.join(configDir, f)).mtime.getTime(),
|
|
86
|
+
path: path.join(CONNECTION_DIR, f),
|
|
87
|
+
mtime: fs.statSync(path.join(CONNECTION_DIR, f)).mtime.getTime(),
|
|
116
88
|
}))
|
|
117
89
|
.sort((a, b) => b.mtime - a.mtime);
|
|
118
90
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
91
|
+
for (const file of connectionFiles) {
|
|
92
|
+
try {
|
|
93
|
+
const content = fs.readFileSync(file.path, "utf-8");
|
|
94
|
+
const info = JSON.parse(content) as ConnectionInfo;
|
|
95
|
+
if (typeof info.port === "number" && info.port > 0) {
|
|
96
|
+
return info;
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
// Ignore malformed files and try the next one.
|
|
100
|
+
}
|
|
123
101
|
}
|
|
124
102
|
} catch {
|
|
125
|
-
// No connection files found
|
|
103
|
+
// No connection files found.
|
|
126
104
|
}
|
|
127
105
|
return undefined;
|
|
128
106
|
}
|
|
129
107
|
|
|
108
|
+
function isPortAlive(port: number): Promise<boolean> {
|
|
109
|
+
return new Promise((resolve) => {
|
|
110
|
+
const req = http.get(
|
|
111
|
+
{
|
|
112
|
+
hostname: "127.0.0.1",
|
|
113
|
+
port,
|
|
114
|
+
path: "/context",
|
|
115
|
+
timeout: PORT_VALIDATE_TIMEOUT_MS,
|
|
116
|
+
},
|
|
117
|
+
(res) => {
|
|
118
|
+
res.resume();
|
|
119
|
+
resolve(res.statusCode === 200);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
req.on("timeout", () => {
|
|
124
|
+
req.destroy();
|
|
125
|
+
resolve(false);
|
|
126
|
+
});
|
|
127
|
+
req.on("error", () => resolve(false));
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function getLivePort(): Promise<number | undefined> {
|
|
132
|
+
const info = readLatestConnectionInfo();
|
|
133
|
+
if (!info) return undefined;
|
|
134
|
+
return (await isPortAlive(info.port)) ? info.port : undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
130
137
|
function updateWidget(): void {
|
|
131
138
|
if (!currentCtx) return;
|
|
132
|
-
|
|
139
|
+
|
|
133
140
|
if (!currentContext || !currentContext.workspaceState.openFiles.length) {
|
|
134
141
|
currentCtx.ui.setWidget("pi-companion", ["PI Companion: No files open"]);
|
|
135
142
|
return;
|
|
@@ -137,25 +144,16 @@ function updateWidget(): void {
|
|
|
137
144
|
|
|
138
145
|
const { workspaceState } = currentContext;
|
|
139
146
|
const lines: string[] = [];
|
|
140
|
-
|
|
141
147
|
const activeFile = workspaceState.openFiles.find((f) => f.isActive);
|
|
142
148
|
|
|
143
149
|
if (activeFile) {
|
|
144
150
|
const filename = activeFile.path.split("/").pop() || activeFile.path;
|
|
145
|
-
|
|
146
|
-
// Build location info - ONLY show when text is actually selected
|
|
147
151
|
let location = "";
|
|
148
152
|
if (activeFile.selectionStart && activeFile.selectionEnd) {
|
|
149
153
|
const start = activeFile.selectionStart.line;
|
|
150
154
|
const end = activeFile.selectionEnd.line;
|
|
151
|
-
|
|
152
|
-
location = ` L${start}`;
|
|
153
|
-
} else {
|
|
154
|
-
location = ` L${start}:${end}`;
|
|
155
|
-
}
|
|
155
|
+
location = start === end ? ` L${start}` : ` L${start}:${end}`;
|
|
156
156
|
}
|
|
157
|
-
// Don't show cursor position when nothing is selected
|
|
158
|
-
|
|
159
157
|
lines.push(`📄 ${filename}${location}`);
|
|
160
158
|
}
|
|
161
159
|
|
|
@@ -165,131 +163,107 @@ function updateWidget(): void {
|
|
|
165
163
|
}
|
|
166
164
|
|
|
167
165
|
const widgetKey = lines.join("|");
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (widgetDebounceTimer) {
|
|
171
|
-
clearTimeout(widgetDebounceTimer);
|
|
172
|
-
}
|
|
173
|
-
|
|
166
|
+
if (widgetDebounceTimer) clearTimeout(widgetDebounceTimer);
|
|
167
|
+
|
|
174
168
|
widgetDebounceTimer = setTimeout(() => {
|
|
175
169
|
if (widgetKey !== lastWidgetKey) {
|
|
176
170
|
lastWidgetKey = widgetKey;
|
|
177
171
|
currentCtx!.ui.setWidget("pi-companion", lines);
|
|
178
172
|
}
|
|
179
173
|
widgetDebounceTimer = undefined;
|
|
180
|
-
},
|
|
174
|
+
}, WIDGET_DEBOUNCE_MS);
|
|
181
175
|
}
|
|
182
176
|
|
|
183
|
-
function connectSSE(): void {
|
|
184
|
-
const port =
|
|
177
|
+
async function connectSSE(): Promise<void> {
|
|
178
|
+
const port = await getLivePort();
|
|
185
179
|
if (!port) return;
|
|
186
180
|
|
|
187
|
-
// Close existing connection
|
|
188
181
|
if (sseRequest) {
|
|
189
182
|
sseRequest.destroy();
|
|
190
183
|
sseRequest = undefined;
|
|
191
184
|
}
|
|
192
185
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
for (const line of lines) {
|
|
210
|
-
if (line.startsWith("data: ")) {
|
|
186
|
+
sseRequest = http.get(
|
|
187
|
+
{
|
|
188
|
+
hostname: "127.0.0.1",
|
|
189
|
+
port,
|
|
190
|
+
path: "/context/stream",
|
|
191
|
+
},
|
|
192
|
+
(res) => {
|
|
193
|
+
let buffer = "";
|
|
194
|
+
|
|
195
|
+
res.on("data", (chunk: string) => {
|
|
196
|
+
buffer += chunk;
|
|
197
|
+
const lines = buffer.split("\n");
|
|
198
|
+
buffer = lines.pop() || "";
|
|
199
|
+
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
if (!line.startsWith("data: ")) continue;
|
|
211
202
|
const data = line.slice(6);
|
|
212
|
-
if (data
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
// Ignore parse errors
|
|
219
|
-
}
|
|
203
|
+
if (!data || data === "[object Object]") continue;
|
|
204
|
+
try {
|
|
205
|
+
currentContext = JSON.parse(data);
|
|
206
|
+
updateWidget();
|
|
207
|
+
} catch {
|
|
208
|
+
// Ignore malformed SSE payloads.
|
|
220
209
|
}
|
|
221
210
|
}
|
|
222
|
-
}
|
|
223
|
-
});
|
|
211
|
+
});
|
|
224
212
|
|
|
225
|
-
|
|
226
|
-
scheduleReconnect
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
res.on("error", () => {
|
|
230
|
-
scheduleReconnect();
|
|
231
|
-
});
|
|
232
|
-
});
|
|
213
|
+
res.on("end", scheduleReconnect);
|
|
214
|
+
res.on("error", scheduleReconnect);
|
|
215
|
+
},
|
|
216
|
+
);
|
|
233
217
|
|
|
234
|
-
sseRequest.on("error",
|
|
235
|
-
scheduleReconnect();
|
|
236
|
-
});
|
|
218
|
+
sseRequest.on("error", scheduleReconnect);
|
|
237
219
|
}
|
|
238
220
|
|
|
239
221
|
function scheduleReconnect(): void {
|
|
240
222
|
if (reconnectTimer || !currentCtx) return;
|
|
241
223
|
reconnectTimer = setTimeout(() => {
|
|
242
224
|
reconnectTimer = undefined;
|
|
243
|
-
connectSSE();
|
|
244
|
-
},
|
|
225
|
+
void connectSSE();
|
|
226
|
+
}, RECONNECT_DELAY_MS);
|
|
245
227
|
}
|
|
246
228
|
|
|
247
229
|
export default function (pi: ExtensionAPI) {
|
|
248
|
-
|
|
249
|
-
pi.on("before_agent_start", async (event, ctx) => {
|
|
230
|
+
pi.on("before_agent_start", async () => {
|
|
250
231
|
if (!currentContext) return;
|
|
251
|
-
|
|
232
|
+
|
|
252
233
|
const activeFile = currentContext.workspaceState.openFiles.find((f) => f.isActive);
|
|
253
234
|
if (!activeFile) return;
|
|
254
235
|
|
|
255
236
|
let contextText = "";
|
|
256
|
-
|
|
237
|
+
|
|
257
238
|
if (activeFile.selectedText) {
|
|
258
|
-
// Inject selected text
|
|
259
239
|
contextText = `Current selection in ${activeFile.path}:\n\`\`\`\n${activeFile.selectedText}\n\`\`\``;
|
|
260
240
|
} else if (activeFile.content) {
|
|
261
|
-
// Inject full file content for small files, or first 100 lines
|
|
262
241
|
const lines = activeFile.content.split("\n");
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
contextText = `Current file ${activeFile.path} (first 100 lines):\n\`\`\`\n${lines.slice(0, 100).join("\n")}\n\`\`\``;
|
|
267
|
-
}
|
|
242
|
+
contextText = lines.length <= MAX_INJECTED_LINES
|
|
243
|
+
? `Current file ${activeFile.path}:\n\`\`\`\n${activeFile.content}\n\`\`\``
|
|
244
|
+
: `Current file ${activeFile.path} (first ${MAX_INJECTED_LINES} lines):\n\`\`\`\n${lines.slice(0, MAX_INJECTED_LINES).join("\n")}\n\`\`\``;
|
|
268
245
|
}
|
|
269
246
|
|
|
270
|
-
if (contextText)
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
}
|
|
247
|
+
if (!contextText) return;
|
|
248
|
+
return {
|
|
249
|
+
message: {
|
|
250
|
+
customType: "pi-companion",
|
|
251
|
+
content: contextText,
|
|
252
|
+
display: false,
|
|
253
|
+
},
|
|
254
|
+
};
|
|
279
255
|
});
|
|
280
256
|
|
|
281
257
|
pi.on("session_start", async (_event, ctx) => {
|
|
282
258
|
currentCtx = ctx;
|
|
259
|
+
ensureVSCodeExtensionInstalled(ctx);
|
|
283
260
|
|
|
284
|
-
|
|
285
|
-
installVSCodeExtension(ctx);
|
|
286
|
-
|
|
287
|
-
const port = getPort();
|
|
261
|
+
const port = await getLivePort();
|
|
288
262
|
if (port) {
|
|
289
263
|
ctx.ui.notify("PI Companion: Connected to VS Code", "info");
|
|
290
|
-
connectSSE();
|
|
264
|
+
await connectSSE();
|
|
291
265
|
} else {
|
|
292
|
-
ctx.ui.notify("PI Companion: VS Code
|
|
266
|
+
ctx.ui.notify("PI Companion: VS Code companion server not running. Run 'PI Companion: Start Server' in VS Code.", "warning");
|
|
293
267
|
}
|
|
294
268
|
});
|
|
295
269
|
|
|
@@ -304,7 +278,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
304
278
|
}
|
|
305
279
|
});
|
|
306
280
|
|
|
307
|
-
// Register tools
|
|
308
281
|
pi.registerTool({
|
|
309
282
|
name: "get_ide_context",
|
|
310
283
|
label: "Get IDE Context",
|
|
@@ -338,12 +311,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
338
311
|
return { content: [{ type: "text", text: "No active file" }], details: {} };
|
|
339
312
|
}
|
|
340
313
|
return {
|
|
341
|
-
content: [{
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
314
|
+
content: [{
|
|
315
|
+
type: "text",
|
|
316
|
+
text: JSON.stringify({
|
|
317
|
+
path: activeFile.path,
|
|
318
|
+
cursor: activeFile.cursor,
|
|
319
|
+
selection: activeFile.selectedText,
|
|
320
|
+
content: activeFile.content,
|
|
321
|
+
}, null, 2),
|
|
322
|
+
}],
|
|
347
323
|
details: {},
|
|
348
324
|
};
|
|
349
325
|
},
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"displayName": "PI Companion",
|
|
4
4
|
"description": "Share VS Code context with pi coding agent",
|
|
5
5
|
"repository": "https://github.com/ravshansbox/vscode-pi-companion",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.1.1",
|
|
7
7
|
"publisher": "ravshansbox",
|
|
8
8
|
"keywords": [
|
|
9
9
|
"pi-package"
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"AI",
|
|
16
16
|
"Other"
|
|
17
17
|
],
|
|
18
|
-
"main": "./
|
|
18
|
+
"main": "./dist/extension.js",
|
|
19
19
|
"scripts": {
|
|
20
20
|
"compile": "tsc -p ./",
|
|
21
21
|
"watch": "tsc -watch -p ./",
|
|
@@ -59,10 +59,6 @@
|
|
|
59
59
|
}
|
|
60
60
|
},
|
|
61
61
|
"commands": [
|
|
62
|
-
{
|
|
63
|
-
"command": "pi-companion.run",
|
|
64
|
-
"title": "PI Companion: Run PI"
|
|
65
|
-
},
|
|
66
62
|
{
|
|
67
63
|
"command": "pi-companion.start",
|
|
68
64
|
"title": "PI Companion: Start Server"
|
|
@@ -75,14 +71,6 @@
|
|
|
75
71
|
"command": "pi-companion.status",
|
|
76
72
|
"title": "PI Companion: Show Status"
|
|
77
73
|
}
|
|
78
|
-
],
|
|
79
|
-
"keybindings": [
|
|
80
|
-
{
|
|
81
|
-
"command": "pi-companion.run",
|
|
82
|
-
"key": "cmd+escape",
|
|
83
|
-
"mac": "cmd+escape",
|
|
84
|
-
"when": "editorTextFocus"
|
|
85
|
-
}
|
|
86
74
|
]
|
|
87
75
|
}
|
|
88
76
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
version="${1:-}"
|
|
5
|
+
if [[ -z "$version" ]]; then
|
|
6
|
+
echo "Usage: ./scripts/release.sh <version>"
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
if [[ -z "${VSCE_TOKEN:-}" ]]; then
|
|
11
|
+
echo "VSCE_TOKEN is required"
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
if ! command -v gh >/dev/null 2>&1; then
|
|
16
|
+
echo "gh CLI is required"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
branch=$(git symbolic-ref --short HEAD 2>/dev/null || true)
|
|
21
|
+
if [[ "$branch" != "main" ]]; then
|
|
22
|
+
echo "Release must be run from main"
|
|
23
|
+
exit 1
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [[ -n "$(git status --porcelain)" ]]; then
|
|
27
|
+
echo "Working tree must be clean"
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
npm version "$version" --no-git-tag-version
|
|
32
|
+
npm run compile
|
|
33
|
+
npx vsce publish "$version" --pat "$VSCE_TOKEN" --skip-duplicate --no-git-tag-version
|
|
34
|
+
|
|
35
|
+
git add package.json package-lock.json
|
|
36
|
+
if ! git diff --cached --quiet; then
|
|
37
|
+
git commit -m "$version"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
tag="v$version"
|
|
41
|
+
git tag -f "$tag"
|
|
42
|
+
git push origin main "$tag"
|
|
43
|
+
|
|
44
|
+
gh release view "$tag" --repo ravshansbox/vscode-pi-companion >/dev/null 2>&1 || \
|
|
45
|
+
gh release create "$tag" --repo ravshansbox/vscode-pi-companion --title "$tag" --generate-notes
|
|
46
|
+
|
|
47
|
+
echo "Released $tag"
|
package/src/extension.ts
CHANGED
|
@@ -3,8 +3,6 @@ import { IDEServer } from './ide-server';
|
|
|
3
3
|
|
|
4
4
|
let ideServer: IDEServer | undefined;
|
|
5
5
|
let outputChannel: vscode.OutputChannel;
|
|
6
|
-
let statusBarItem: vscode.StatusBarItem;
|
|
7
|
-
|
|
8
6
|
export function activate(context: vscode.ExtensionContext) {
|
|
9
7
|
outputChannel = vscode.window.createOutputChannel('PI Companion');
|
|
10
8
|
log('Extension activated');
|
|
@@ -19,23 +17,8 @@ export function activate(context: vscode.ExtensionContext) {
|
|
|
19
17
|
log(`Failed to start server: ${err}`);
|
|
20
18
|
});
|
|
21
19
|
|
|
22
|
-
// Create status bar button
|
|
23
|
-
statusBarItem = vscode.window.createStatusBarItem(
|
|
24
|
-
vscode.StatusBarAlignment.Right,
|
|
25
|
-
100
|
|
26
|
-
);
|
|
27
|
-
statusBarItem.text = '$(terminal) Run PI';
|
|
28
|
-
statusBarItem.tooltip = 'Run PI in terminal';
|
|
29
|
-
statusBarItem.command = 'pi-companion.run';
|
|
30
|
-
statusBarItem.show();
|
|
31
|
-
|
|
32
20
|
// Register commands
|
|
33
21
|
context.subscriptions.push(
|
|
34
|
-
statusBarItem,
|
|
35
|
-
|
|
36
|
-
vscode.commands.registerCommand('pi-companion.run', () => {
|
|
37
|
-
runPiInTerminal();
|
|
38
|
-
}),
|
|
39
22
|
|
|
40
23
|
vscode.commands.registerCommand('pi-companion.start', async () => {
|
|
41
24
|
if (ideServer?.isRunning()) {
|
|
@@ -61,21 +44,9 @@ export function activate(context: vscode.ExtensionContext) {
|
|
|
61
44
|
);
|
|
62
45
|
}
|
|
63
46
|
|
|
64
|
-
function runPiInTerminal() {
|
|
65
|
-
// Create new terminal so shell rc files (including direnv hook) are sourced
|
|
66
|
-
const terminal = vscode.window.createTerminal({
|
|
67
|
-
name: 'PI',
|
|
68
|
-
isTransient: true
|
|
69
|
-
});
|
|
70
|
-
terminal.show();
|
|
71
|
-
// Ensure direnv is loaded, then run pi
|
|
72
|
-
terminal.sendText('eval "$(direnv export json 2>/dev/null)" 2>/dev/null; pi');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
47
|
export function deactivate() {
|
|
76
48
|
log('Extension deactivated');
|
|
77
49
|
ideServer?.stop();
|
|
78
|
-
statusBarItem?.dispose();
|
|
79
50
|
}
|
|
80
51
|
|
|
81
52
|
function log(message: string) {
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
name: Publish to VS Code Marketplace & npm
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
publish-vscode:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
permissions:
|
|
11
|
-
contents: write
|
|
12
|
-
|
|
13
|
-
steps:
|
|
14
|
-
- uses: actions/checkout@v4
|
|
15
|
-
with:
|
|
16
|
-
persist-credentials: true
|
|
17
|
-
|
|
18
|
-
- uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: '20'
|
|
21
|
-
cache: 'npm'
|
|
22
|
-
|
|
23
|
-
- name: Install dependencies
|
|
24
|
-
run: npm ci
|
|
25
|
-
|
|
26
|
-
- name: Install vsce
|
|
27
|
-
run: npm install -g @vscode/vsce
|
|
28
|
-
|
|
29
|
-
- name: Publish to VS Code Marketplace
|
|
30
|
-
env:
|
|
31
|
-
VSCE_TOKEN: ${{ secrets.VSCE_TOKEN }}
|
|
32
|
-
run: |
|
|
33
|
-
git config --local user.name "github-actions"
|
|
34
|
-
git config --local user.email "github-actions@github.com"
|
|
35
|
-
|
|
36
|
-
# Package and publish with version bump
|
|
37
|
-
npx vsce package
|
|
38
|
-
vsce publish patch --pat $VSCE_TOKEN --skip-duplicate
|
|
39
|
-
|
|
40
|
-
# Push the tag created by vsce
|
|
41
|
-
git push --follow-tags origin main
|
|
42
|
-
|
|
43
|
-
publish-npm:
|
|
44
|
-
runs-on: ubuntu-latest
|
|
45
|
-
needs: publish-vscode
|
|
46
|
-
if: startsWith(github.ref, 'refs/tags/v')
|
|
47
|
-
permissions:
|
|
48
|
-
id-token: write # Required for OIDC Trusted Publishing
|
|
49
|
-
|
|
50
|
-
steps:
|
|
51
|
-
- uses: actions/checkout@v4
|
|
52
|
-
|
|
53
|
-
- uses: actions/setup-node@v4
|
|
54
|
-
with:
|
|
55
|
-
node-version: '20'
|
|
56
|
-
cache: 'npm'
|
|
57
|
-
|
|
58
|
-
- name: Install dependencies
|
|
59
|
-
run: npm ci
|
|
60
|
-
|
|
61
|
-
- name: Publish to npm
|
|
62
|
-
run: npm publish --access public --skip-duplicate
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
name: Release
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [main]
|
|
6
|
-
|
|
7
|
-
permissions:
|
|
8
|
-
contents: write
|
|
9
|
-
pull-requests: write
|
|
10
|
-
|
|
11
|
-
jobs:
|
|
12
|
-
release:
|
|
13
|
-
runs-on: ubuntu-latest
|
|
14
|
-
steps:
|
|
15
|
-
- uses: googleapis/release-please-action@v4
|
|
16
|
-
with:
|
|
17
|
-
release-type: node
|
|
18
|
-
package-name: vscode-pi-companion
|
|
19
|
-
target-branch: main
|
package/.vscode/launch.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": "0.2.0",
|
|
3
|
-
"configurations": [
|
|
4
|
-
{
|
|
5
|
-
"name": "Run Extension",
|
|
6
|
-
"type": "extensionHost",
|
|
7
|
-
"request": "launch",
|
|
8
|
-
"args": [
|
|
9
|
-
"--extensionDevelopmentPath=${workspaceFolder}"
|
|
10
|
-
],
|
|
11
|
-
"outFiles": [
|
|
12
|
-
"${workspaceFolder}/out/**/*.js"
|
|
13
|
-
],
|
|
14
|
-
"preLaunchTask": "${defaultBuildTask}"
|
|
15
|
-
}
|
|
16
|
-
]
|
|
17
|
-
}
|
package/.vscode/tasks.json
DELETED
package/screenshot.png
DELETED
|
Binary file
|
package/tsconfig.json
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2020"],
|
|
6
|
-
"outDir": "./out",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"moduleResolution": "node"
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"],
|
|
15
|
-
"exclude": ["node_modules", "out"]
|
|
16
|
-
}
|