alpiecode 0.8.2__tar.gz → 0.9.1__tar.gz
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.
- {alpiecode-0.8.2 → alpiecode-0.9.1}/PKG-INFO +1 -1
- {alpiecode-0.8.2 → alpiecode-0.9.1}/pyproject.toml +5 -1
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/alpiecode.egg-info/PKG-INFO +1 -1
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/alpiecode.egg-info/SOURCES.txt +4 -1
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/agent.py +36 -14
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/cli.py +5 -0
- alpiecode-0.9.1/src/codeagent/client.py +131 -0
- alpiecode-0.9.1/src/codeagent/extension/alpiecode.vsix +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/orchestrator.py +14 -3
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/server.py +10 -0
- alpiecode-0.9.1/src/codeagent/vscode_installer.py +122 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/README.md +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/setup.cfg +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/alpiecode.egg-info/dependency_links.txt +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/alpiecode.egg-info/entry_points.txt +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/alpiecode.egg-info/requires.txt +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/alpiecode.egg-info/top_level.txt +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/__init__.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/backends/__init__.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/backends/base.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/backends/local_backend.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/backends/openai_backend.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/compaction.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/config.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/context.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/executor.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/github.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/guardian.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/local_model.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/media.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/memory.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/prompt.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/session.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/tools.py +0 -0
- {alpiecode-0.8.2 → alpiecode-0.9.1}/src/codeagent/updater.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "alpiecode"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.9.1"
|
|
8
8
|
description = "AlpieCode — Autonomous AI Coding Agent CLI powered by local 169Pi GGUF VLM"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
dependencies = [
|
|
@@ -32,3 +32,7 @@ codeagent = "codeagent.cli:main"
|
|
|
32
32
|
|
|
33
33
|
[tool.setuptools.packages.find]
|
|
34
34
|
where = ["src"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.package-data]
|
|
37
|
+
codeagent = ["extension/*.vsix"]
|
|
38
|
+
|
|
@@ -9,6 +9,7 @@ src/alpiecode.egg-info/top_level.txt
|
|
|
9
9
|
src/codeagent/__init__.py
|
|
10
10
|
src/codeagent/agent.py
|
|
11
11
|
src/codeagent/cli.py
|
|
12
|
+
src/codeagent/client.py
|
|
12
13
|
src/codeagent/compaction.py
|
|
13
14
|
src/codeagent/config.py
|
|
14
15
|
src/codeagent/context.py
|
|
@@ -24,7 +25,9 @@ src/codeagent/server.py
|
|
|
24
25
|
src/codeagent/session.py
|
|
25
26
|
src/codeagent/tools.py
|
|
26
27
|
src/codeagent/updater.py
|
|
28
|
+
src/codeagent/vscode_installer.py
|
|
27
29
|
src/codeagent/backends/__init__.py
|
|
28
30
|
src/codeagent/backends/base.py
|
|
29
31
|
src/codeagent/backends/local_backend.py
|
|
30
|
-
src/codeagent/backends/openai_backend.py
|
|
32
|
+
src/codeagent/backends/openai_backend.py
|
|
33
|
+
src/codeagent/extension/alpiecode.vsix
|
|
@@ -149,28 +149,50 @@ def run_agent(
|
|
|
149
149
|
video_path: str = None,
|
|
150
150
|
url: str = None,
|
|
151
151
|
github_repo: str = None,
|
|
152
|
+
server_url: str = None,
|
|
152
153
|
) -> list:
|
|
153
154
|
"""Run non-interactive agent task with Rich presentation."""
|
|
154
155
|
workdir = workdir.resolve()
|
|
155
156
|
_ensure_git(workdir)
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
# CLI-as-Client check: auto-detect if local/remote server is running
|
|
159
|
+
from .client import AlpieCodeClient
|
|
160
|
+
target_server = server_url or getattr(cfg, "server_url", None)
|
|
161
|
+
if not target_server:
|
|
162
|
+
# Check default local server
|
|
163
|
+
local_client = AlpieCodeClient("http://127.0.0.1:7169")
|
|
164
|
+
if local_client.health().get("status") == "online":
|
|
165
|
+
target_server = "http://127.0.0.1:7169"
|
|
166
|
+
|
|
167
|
+
if target_server:
|
|
168
|
+
client = AlpieCodeClient(target_server)
|
|
169
|
+
event_stream = client.stream_chat(
|
|
170
|
+
task=task,
|
|
171
|
+
workdir=str(workdir),
|
|
172
|
+
image_path=image_path,
|
|
173
|
+
video_path=video_path,
|
|
174
|
+
url=url,
|
|
175
|
+
github_repo=github_repo,
|
|
176
|
+
)
|
|
177
|
+
else:
|
|
178
|
+
backend = resolve_backend(cfg)
|
|
179
|
+
orchestrator = AgentOrchestrator(backend)
|
|
180
|
+
session_mgr = SessionManager()
|
|
181
|
+
session = session_mgr.create_session(workdir, max_tokens=cfg.n_ctx if not backend.is_available else 262_144)
|
|
182
|
+
event_stream = orchestrator.run_task(
|
|
183
|
+
session=session,
|
|
184
|
+
task=task,
|
|
185
|
+
cfg=cfg,
|
|
186
|
+
image_path=image_path,
|
|
187
|
+
video_path=video_path,
|
|
188
|
+
url=url,
|
|
189
|
+
github_repo=github_repo,
|
|
190
|
+
)
|
|
161
191
|
|
|
162
192
|
_checkpoint(workdir, "checkpoint: start")
|
|
163
193
|
|
|
164
194
|
current_turn = 0
|
|
165
|
-
for event in
|
|
166
|
-
session=session,
|
|
167
|
-
task=task,
|
|
168
|
-
cfg=cfg,
|
|
169
|
-
image_path=image_path,
|
|
170
|
-
video_path=video_path,
|
|
171
|
-
url=url,
|
|
172
|
-
github_repo=github_repo,
|
|
173
|
-
):
|
|
195
|
+
for event in event_stream:
|
|
174
196
|
if event.type == "start" and verbose:
|
|
175
197
|
data = event.data
|
|
176
198
|
if HAS_RICH:
|
|
@@ -254,7 +276,7 @@ def run_agent(
|
|
|
254
276
|
elif event.type == "max_turns_reached" and verbose:
|
|
255
277
|
console.print(f"\n⚠️ Max turns ({event.data['max_turns']}) reached without completion.", style="bold yellow")
|
|
256
278
|
|
|
257
|
-
return session.context.messages
|
|
279
|
+
return session.context.messages if "session" in locals() else []
|
|
258
280
|
|
|
259
281
|
|
|
260
282
|
def run_chat(workdir: Path, cfg: Config, verbose: bool = True) -> None:
|
|
@@ -119,6 +119,11 @@ def main():
|
|
|
119
119
|
|
|
120
120
|
if args.command == "serve":
|
|
121
121
|
_show_banner()
|
|
122
|
+
try:
|
|
123
|
+
from .vscode_installer import ensure_vscode_extension
|
|
124
|
+
ensure_vscode_extension()
|
|
125
|
+
except Exception as e:
|
|
126
|
+
pass
|
|
122
127
|
from .server import run_server
|
|
123
128
|
run_server(host=args.host, port=args.port)
|
|
124
129
|
return
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Client SDK for AlpieCode API server.
|
|
3
|
+
|
|
4
|
+
Provides a clean, typed interface for connecting to an AlpieCode server (`alpiecode serve`),
|
|
5
|
+
streaming SSE events, managing sessions, and canceling tasks.
|
|
6
|
+
Used by the CLI client mode and future IDE/remote Python integrations.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import urllib.parse
|
|
11
|
+
import urllib.request
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, Iterator, Optional
|
|
15
|
+
|
|
16
|
+
from .orchestrator import AgentEvent
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AlpieCodeClient:
|
|
20
|
+
"""Client for interacting with a running AlpieCode API server."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, base_url: str = "http://127.0.0.1:7169", timeout: float = 120.0):
|
|
23
|
+
self.base_url = base_url.rstrip("/")
|
|
24
|
+
self.timeout = timeout
|
|
25
|
+
|
|
26
|
+
def health(self) -> Dict[str, Any]:
|
|
27
|
+
"""Check server health and backend status."""
|
|
28
|
+
req = urllib.request.Request(f"{self.base_url}/health")
|
|
29
|
+
try:
|
|
30
|
+
with urllib.request.urlopen(req, timeout=3.0) as resp:
|
|
31
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
32
|
+
except Exception as e:
|
|
33
|
+
return {"status": "offline", "error": str(e)}
|
|
34
|
+
|
|
35
|
+
def metrics(self) -> Dict[str, Any]:
|
|
36
|
+
"""Get server observability metrics."""
|
|
37
|
+
req = urllib.request.Request(f"{self.base_url}/metrics")
|
|
38
|
+
try:
|
|
39
|
+
with urllib.request.urlopen(req, timeout=3.0) as resp:
|
|
40
|
+
return json.loads(resp.read().decode("utf-8"))
|
|
41
|
+
except Exception as e:
|
|
42
|
+
return {"error": str(e)}
|
|
43
|
+
|
|
44
|
+
def list_sessions(self) -> list:
|
|
45
|
+
"""List active server sessions."""
|
|
46
|
+
req = urllib.request.Request(f"{self.base_url}/sessions")
|
|
47
|
+
try:
|
|
48
|
+
with urllib.request.urlopen(req, timeout=5.0) as resp:
|
|
49
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
50
|
+
return data.get("sessions", [])
|
|
51
|
+
except Exception:
|
|
52
|
+
return []
|
|
53
|
+
|
|
54
|
+
def cancel_task(self, session_id: str) -> bool:
|
|
55
|
+
"""Request cancellation of an in-progress task session."""
|
|
56
|
+
req = urllib.request.Request(
|
|
57
|
+
f"{self.base_url}/cancel/{session_id}",
|
|
58
|
+
method="POST",
|
|
59
|
+
headers={"Content-Type": "application/json"},
|
|
60
|
+
)
|
|
61
|
+
try:
|
|
62
|
+
with urllib.request.urlopen(req, timeout=5.0) as resp:
|
|
63
|
+
return resp.status == 200
|
|
64
|
+
except Exception:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
def stream_chat(
|
|
68
|
+
self,
|
|
69
|
+
task: str,
|
|
70
|
+
workdir: str = ".",
|
|
71
|
+
session_id: Optional[str] = None,
|
|
72
|
+
image_path: Optional[str] = None,
|
|
73
|
+
video_path: Optional[str] = None,
|
|
74
|
+
url: Optional[str] = None,
|
|
75
|
+
github_repo: Optional[str] = None,
|
|
76
|
+
) -> Iterator[AgentEvent]:
|
|
77
|
+
"""
|
|
78
|
+
Stream agent events from POST /chat using Server-Sent Events (SSE).
|
|
79
|
+
Yields AgentEvent instances.
|
|
80
|
+
"""
|
|
81
|
+
endpoint = f"{self.base_url}/chat"
|
|
82
|
+
payload = json.dumps({
|
|
83
|
+
"task": task,
|
|
84
|
+
"workdir": str(workdir),
|
|
85
|
+
"session_id": session_id,
|
|
86
|
+
"image": image_path,
|
|
87
|
+
"video": video_path,
|
|
88
|
+
"url": url,
|
|
89
|
+
"github": github_repo,
|
|
90
|
+
}).encode("utf-8")
|
|
91
|
+
|
|
92
|
+
req = urllib.request.Request(
|
|
93
|
+
endpoint,
|
|
94
|
+
data=payload,
|
|
95
|
+
headers={
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
"Accept": "text/event-stream",
|
|
98
|
+
},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
103
|
+
current_event_type = "message"
|
|
104
|
+
current_data_str = ""
|
|
105
|
+
|
|
106
|
+
for raw_line in resp:
|
|
107
|
+
line = raw_line.decode("utf-8").strip()
|
|
108
|
+
if not line:
|
|
109
|
+
if current_data_str:
|
|
110
|
+
try:
|
|
111
|
+
event_data = json.loads(current_data_str)
|
|
112
|
+
except Exception:
|
|
113
|
+
event_data = {"content": current_data_str}
|
|
114
|
+
yield AgentEvent(type=current_event_type, data=event_data)
|
|
115
|
+
current_event_type = "message"
|
|
116
|
+
current_data_str = ""
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
if line.startswith("event:"):
|
|
120
|
+
current_event_type = line[6:].strip()
|
|
121
|
+
elif line.startswith("data:"):
|
|
122
|
+
current_data_str = line[5:].strip()
|
|
123
|
+
|
|
124
|
+
if current_data_str:
|
|
125
|
+
try:
|
|
126
|
+
event_data = json.loads(current_data_str)
|
|
127
|
+
except Exception:
|
|
128
|
+
event_data = {"content": current_data_str}
|
|
129
|
+
yield AgentEvent(type=current_event_type, data=event_data)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
yield AgentEvent(type="error", data={"error": f"Server connection failed: {e}"})
|
|
Binary file
|
|
@@ -28,9 +28,13 @@ class AgentEvent:
|
|
|
28
28
|
data: Dict[str, Any]
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
def resolve_backend(cfg: Config) -> InferenceBackend:
|
|
32
|
-
"""Resolve online vs offline backend based on server reachability.
|
|
33
|
-
|
|
31
|
+
def resolve_backend(cfg: Config, timeout: float = 2.0) -> InferenceBackend:
|
|
32
|
+
"""Resolve online vs offline backend based on server reachability.
|
|
33
|
+
|
|
34
|
+
Uses a generous timeout at startup (2s default) to avoid false negatives
|
|
35
|
+
when the remote API is slow to respond (e.g. Azure VM cold start).
|
|
36
|
+
"""
|
|
37
|
+
if is_server_reachable(cfg.base_url, timeout=timeout):
|
|
34
38
|
return OpenAIBackend(cfg)
|
|
35
39
|
return LocalBackend(cfg)
|
|
36
40
|
|
|
@@ -59,6 +63,13 @@ class AgentOrchestrator:
|
|
|
59
63
|
"""
|
|
60
64
|
Run full agent task loop for a session. Yields AgentEvents.
|
|
61
65
|
"""
|
|
66
|
+
# Dynamic backend re-check: if currently on LocalBackend but remote
|
|
67
|
+
# API is now reachable, switch to OnlineBackend automatically.
|
|
68
|
+
# This handles the case where the server started offline but the
|
|
69
|
+
# remote API came online later (e.g. VM cold start, network hiccup).
|
|
70
|
+
if isinstance(self.backend, LocalBackend) and is_server_reachable(cfg.base_url, timeout=1.5):
|
|
71
|
+
self.backend = OpenAIBackend(cfg)
|
|
72
|
+
|
|
62
73
|
is_offline = not self.backend.is_available or isinstance(self.backend, LocalBackend)
|
|
63
74
|
session.is_offline = is_offline
|
|
64
75
|
|
|
@@ -13,6 +13,7 @@ from typing import Any, Dict, Optional
|
|
|
13
13
|
|
|
14
14
|
try:
|
|
15
15
|
from fastapi import FastAPI, HTTPException, Request, Response
|
|
16
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
16
17
|
from fastapi.responses import JSONResponse, StreamingResponse
|
|
17
18
|
HAS_FASTAPI = True
|
|
18
19
|
except ImportError:
|
|
@@ -53,6 +54,15 @@ if HAS_FASTAPI:
|
|
|
53
54
|
lifespan=lifespan,
|
|
54
55
|
)
|
|
55
56
|
|
|
57
|
+
# CORS — allow VS Code extensions, web UIs, and external clients
|
|
58
|
+
app.add_middleware(
|
|
59
|
+
CORSMiddleware,
|
|
60
|
+
allow_origins=["*"],
|
|
61
|
+
allow_credentials=True,
|
|
62
|
+
allow_methods=["*"],
|
|
63
|
+
allow_headers=["*"],
|
|
64
|
+
)
|
|
65
|
+
|
|
56
66
|
@app.get("/health")
|
|
57
67
|
async def health_check():
|
|
58
68
|
"""Health check and engine status."""
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""
|
|
2
|
+
VS Code Extension Auto-Installer for AlpieCode.
|
|
3
|
+
|
|
4
|
+
Detects if VS Code is available, checks if the AlpieCode extension is installed,
|
|
5
|
+
and prompts/auto-installs the bundled .vsix if missing.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import shutil
|
|
10
|
+
import subprocess
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_bundled_vsix_path() -> Path:
|
|
17
|
+
"""Return path to bundled .vsix inside the codeagent package."""
|
|
18
|
+
pkg_dir = Path(__file__).parent
|
|
19
|
+
return pkg_dir / "extension" / "alpiecode.vsix"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def find_code_cli() -> Optional[str]:
|
|
23
|
+
"""Find the VS Code CLI executable across Windows, WSL, Linux, and macOS."""
|
|
24
|
+
for cmd in ("code", "code.cmd", "code.exe"):
|
|
25
|
+
found = shutil.which(cmd)
|
|
26
|
+
if found:
|
|
27
|
+
return found
|
|
28
|
+
|
|
29
|
+
# Check common WSL / Windows paths if not found directly in PATH
|
|
30
|
+
wsl_code_paths = [
|
|
31
|
+
"/mnt/c/Users/*/AppData/Local/Programs/Microsoft VS Code/bin/code",
|
|
32
|
+
"/usr/local/bin/code",
|
|
33
|
+
"/usr/bin/code",
|
|
34
|
+
]
|
|
35
|
+
import glob
|
|
36
|
+
for pattern in wsl_code_paths:
|
|
37
|
+
matches = glob.glob(pattern)
|
|
38
|
+
if matches:
|
|
39
|
+
return matches[0]
|
|
40
|
+
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def is_extension_installed() -> bool:
|
|
45
|
+
"""Check if 'alpiecode' extension is already installed in VS Code."""
|
|
46
|
+
code_bin = find_code_cli()
|
|
47
|
+
if not code_bin:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
res = subprocess.run(
|
|
52
|
+
[code_bin, "--list-extensions"],
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
timeout=8,
|
|
56
|
+
)
|
|
57
|
+
if res.returncode == 0:
|
|
58
|
+
installed = res.stdout.lower()
|
|
59
|
+
return "alpiecode" in installed
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def ensure_vscode_extension(auto_confirm: bool = False, quiet: bool = False) -> bool:
|
|
67
|
+
"""
|
|
68
|
+
Check if VS Code extension is installed. If missing:
|
|
69
|
+
- Asks user: 'Would you like to install the AlpieCode VS Code extension? [Y/n]'
|
|
70
|
+
- If Yes (or auto_confirm=True): installs bundled .vsix via `code --install-extension`
|
|
71
|
+
- Returns True if installed or successfully installed, False otherwise.
|
|
72
|
+
"""
|
|
73
|
+
if is_extension_installed():
|
|
74
|
+
if not quiet:
|
|
75
|
+
print("✅ AlpieCode VS Code extension is installed.")
|
|
76
|
+
return True
|
|
77
|
+
|
|
78
|
+
code_bin = find_code_cli()
|
|
79
|
+
if not code_bin:
|
|
80
|
+
if not quiet:
|
|
81
|
+
print("ℹ️ VS Code CLI ('code') not detected on PATH. Skipping extension check.")
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
vsix_path = get_bundled_vsix_path()
|
|
85
|
+
if not vsix_path.exists():
|
|
86
|
+
if not quiet:
|
|
87
|
+
print(f"ℹ️ Bundled extension VSIX not found at {vsix_path}")
|
|
88
|
+
return False
|
|
89
|
+
|
|
90
|
+
if not quiet:
|
|
91
|
+
print("\n💡 VS Code detected, but the AlpieCode extension is not installed.")
|
|
92
|
+
|
|
93
|
+
if not auto_confirm:
|
|
94
|
+
try:
|
|
95
|
+
prompt_text = " Would you like to install the AlpieCode VS Code extension now? [Y/n]: "
|
|
96
|
+
choice = input(prompt_text).strip().lower()
|
|
97
|
+
if choice and choice not in ("y", "yes"):
|
|
98
|
+
if not quiet:
|
|
99
|
+
print(" Skipped VS Code extension installation.\n")
|
|
100
|
+
return False
|
|
101
|
+
except (KeyboardInterrupt, EOFError):
|
|
102
|
+
print()
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
print(f"📦 Installing AlpieCode VS Code extension ({vsix_path.name})...")
|
|
106
|
+
try:
|
|
107
|
+
res = subprocess.run(
|
|
108
|
+
[code_bin, "--install-extension", str(vsix_path), "--force"],
|
|
109
|
+
capture_output=True,
|
|
110
|
+
text=True,
|
|
111
|
+
timeout=30,
|
|
112
|
+
)
|
|
113
|
+
if res.returncode == 0:
|
|
114
|
+
print("✅ AlpieCode VS Code extension installed successfully!")
|
|
115
|
+
print(" 👉 Reload VS Code (`Ctrl+Shift+P` → `Developer: Reload Window`) to start chatting!\n")
|
|
116
|
+
return True
|
|
117
|
+
else:
|
|
118
|
+
print(f"⚠️ Could not install VS Code extension: {res.stderr.strip()}\n")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f"⚠️ Extension installation error: {e}\n")
|
|
121
|
+
|
|
122
|
+
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|