@nghyane/arcane 0.1.21 → 0.1.23

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.1.23] - 2026-03-08
6
+
7
+ ### Fixed
8
+
9
+ - Fix Python setup installing packages into system Python instead of managed venv, causing "kernel unavailable" even after install
10
+ - Fix missing `ensurepip` step when managed venv lacks pip
11
+ - Fix misleading error message to point users to `arcane setup python`
12
+
13
+ ## [0.1.22] - 2026-03-08
14
+
15
+ ### Changed
16
+
17
+ - Rename edit operation `replace` to `replace_range` for clarity
18
+
5
19
  ## [0.1.21] - 2026-03-05
6
20
 
7
21
  ### Fixed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@nghyane/arcane",
4
- "version": "0.1.21",
4
+ "version": "0.1.23",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/nghyane/arcane",
7
7
  "author": "Can Bölük",
@@ -160,7 +160,18 @@ async function installPythonPackages(
160
160
  pythonPath: string,
161
161
  uvPath?: string,
162
162
  pipPath?: string,
163
+ usingManagedEnv?: boolean,
163
164
  ): Promise<{ success: boolean; usedManagedEnv: boolean }> {
165
+ const managedPython = managedPythonPath();
166
+ const hasManagedEnv = await Bun.file(managedPython).exists();
167
+
168
+ // If the managed venv already exists, install directly into it.
169
+ // resolvePythonRuntime prefers managed venv over system Python,
170
+ // so installing into system Python would be invisible to the check.
171
+ if (hasManagedEnv || usingManagedEnv) {
172
+ return installIntoManagedEnv(packages, pythonPath, uvPath);
173
+ }
174
+
164
175
  if (uvPath) {
165
176
  console.log(chalk.dim(`Installing via uv: ${packages.join(" ")}`));
166
177
  const result = await $`${uvPath} pip install ${packages}`.nothrow();
@@ -178,24 +189,35 @@ async function installPythonPackages(
178
189
  }
179
190
 
180
191
  console.log(chalk.dim(`Falling back to managed virtual environment: ${MANAGED_PYTHON_ENV}`));
192
+ return installIntoManagedEnv(packages, pythonPath, uvPath);
193
+ }
194
+
195
+ async function installIntoManagedEnv(
196
+ packages: string[],
197
+ pythonPath: string,
198
+ uvPath?: string,
199
+ ): Promise<{ success: boolean; usedManagedEnv: boolean }> {
200
+ console.log(chalk.dim(`Installing into managed environment: ${MANAGED_PYTHON_ENV}`));
181
201
 
182
202
  if (uvPath) {
183
- const createEnv = await $`${uvPath} venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
203
+ // Ensure venv exists (no-op if already created)
204
+ await $`${uvPath} venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
205
+ const result = await $`${uvPath} pip install --python ${MANAGED_PYTHON_ENV} ${packages}`.nothrow();
206
+ return { success: result.exitCode === 0, usedManagedEnv: true };
207
+ }
208
+
209
+ const managedPython = managedPythonPath();
210
+ if (!(await Bun.file(managedPython).exists())) {
211
+ const createEnv = await $`${pythonPath} -m venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
184
212
  if (createEnv.exitCode !== 0) {
185
213
  return { success: false, usedManagedEnv: true };
186
214
  }
187
- const installInManagedEnv = await $`${uvPath} pip install --python ${MANAGED_PYTHON_ENV} ${packages}`.nothrow();
188
- return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
189
215
  }
190
216
 
191
- const createEnv = await $`${pythonPath} -m venv ${MANAGED_PYTHON_ENV}`.quiet().nothrow();
192
- if (createEnv.exitCode !== 0) {
193
- return { success: false, usedManagedEnv: true };
194
- }
195
-
196
- const managedPython = managedPythonPath();
197
- const installInManagedEnv = await $`${managedPython} -m pip install ${packages}`.nothrow();
198
- return { success: installInManagedEnv.exitCode === 0, usedManagedEnv: true };
217
+ // Ensure pip is available in the managed env
218
+ await $`${managedPython} -m ensurepip`.quiet().nothrow();
219
+ const result = await $`${managedPython} -m pip install ${packages}`.nothrow();
220
+ return { success: result.exitCode === 0, usedManagedEnv: true };
199
221
  }
200
222
 
201
223
  /**
@@ -258,7 +280,13 @@ async function handlePythonSetup(flags: { json?: boolean; check?: boolean }): Pr
258
280
  }
259
281
 
260
282
  console.log("");
261
- const install = await installPythonPackages(check.missingPackages, check.pythonPath, check.uvPath, check.pipPath);
283
+ const install = await installPythonPackages(
284
+ check.missingPackages,
285
+ check.pythonPath,
286
+ check.uvPath,
287
+ check.pipPath,
288
+ check.usingManagedEnv,
289
+ );
262
290
 
263
291
  if (!install.success) {
264
292
  console.error(chalk.red(`\n${theme.status.error} Installation failed`));
package/src/ipy/kernel.ts CHANGED
@@ -129,8 +129,7 @@ export async function checkPythonKernelAvailability(cwd: string): Promise<Python
129
129
  return {
130
130
  ok: false,
131
131
  pythonPath: runtime.pythonPath,
132
- reason:
133
- "kernel_gateway (jupyter-kernel-gateway) or ipykernel not installed. Run: python -m pip install jupyter_kernel_gateway ipykernel",
132
+ reason: "kernel_gateway (jupyter-kernel-gateway) or ipykernel not installed. Run: arcane setup python",
134
133
  };
135
134
  } catch (err: unknown) {
136
135
  return { ok: false, reason: err instanceof Error ? err.message : String(err) };
@@ -314,10 +314,10 @@ export class EditTool implements AgentTool<TInput, any, Theme> {
314
314
  });
315
315
  break;
316
316
  }
317
- case "replace": {
317
+ case "replace_range": {
318
318
  const { first, last, content } = edit;
319
319
  anchorEdits.push({
320
- op: "replace",
320
+ op: "replace_range",
321
321
  first: parseTag(first),
322
322
  last: parseTag(last),
323
323
  content: hashlineParseContent(content),
@@ -419,7 +419,7 @@ export class EditTool implements AgentTool<TInput, any, Theme> {
419
419
  case "set":
420
420
  refs.push(edit.tag);
421
421
  break;
422
- case "replace":
422
+ case "replace_range":
423
423
  refs.push(edit.first, edit.last);
424
424
  break;
425
425
  case "append":
@@ -17,7 +17,7 @@ import type { HashMismatch } from "./types";
17
17
  export type LineTag = { line: number; hash: string };
18
18
  export type HashlineEdit =
19
19
  | { op: "set"; tag: LineTag; content: string[] }
20
- | { op: "replace"; first: LineTag; last: LineTag; content: string[] }
20
+ | { op: "replace_range"; first: LineTag; last: LineTag; content: string[] }
21
21
  | { op: "append"; after?: LineTag; content: string[] }
22
22
  | { op: "prepend"; before?: LineTag; content: string[] }
23
23
  | { op: "insert"; after: LineTag; before: LineTag; content: string[] };
@@ -647,7 +647,7 @@ export function applyHashlineEdits(
647
647
  case "set":
648
648
  touched.add(edit.tag.line);
649
649
  break;
650
- case "replace":
650
+ case "replace_range":
651
651
  for (let ln = edit.first.line; ln <= edit.last.line; ln++) touched.add(ln);
652
652
  break;
653
653
  case "append":
@@ -715,7 +715,7 @@ export function applyHashlineEdits(
715
715
  if (!afterValid || !beforeValid) continue;
716
716
  break;
717
717
  }
718
- case "replace": {
718
+ case "replace_range": {
719
719
  if (edit.first.line > edit.last.line) {
720
720
  throw new Error(`Range start line ${edit.first.line} must be <= end line ${edit.last.line}`);
721
721
  }
@@ -740,7 +740,7 @@ export function applyHashlineEdits(
740
740
  case "set":
741
741
  lineKey = `s:${edit.tag.line}`;
742
742
  break;
743
- case "replace":
743
+ case "replace_range":
744
744
  lineKey = `r:${edit.first.line}:${edit.last.line}`;
745
745
  break;
746
746
  case "append":
@@ -783,7 +783,7 @@ export function applyHashlineEdits(
783
783
  sortLine = edit.tag.line;
784
784
  precedence = 0;
785
785
  break;
786
- case "replace":
786
+ case "replace_range":
787
787
  sortLine = edit.last.line;
788
788
  precedence = 0;
789
789
  break;
@@ -850,7 +850,7 @@ export function applyHashlineEdits(
850
850
  trackFirstChanged(edit.tag.line);
851
851
  break;
852
852
  }
853
- case "replace": {
853
+ case "replace_range": {
854
854
  const count = edit.last.line - edit.first.line + 1;
855
855
  const origLines = originalFileLines.slice(edit.first.line - 1, edit.first.line - 1 + count);
856
856
  let stripped = autocorrect
@@ -133,7 +133,7 @@ const hashlinePrependEditSchema = Type.Object(
133
133
 
134
134
  const hashlineRangeEditSchema = Type.Object(
135
135
  {
136
- op: Type.Literal("replace"),
136
+ op: Type.Literal("replace_range"),
137
137
  first: hashlineTagFormat("first line"),
138
138
  last: hashlineTagFormat("last line"),
139
139
  content: hashlineReplaceContentFormat("Replacement"),