241 ctx, target, editable, reinstall, use_active_venv,
242 allow_worktree, allow_outside_project, json_mode,
244 """Install an Option C project as an editable package, with safety checks.
246 Wraps ``pip install -e <target>`` after verifying:
248 1. The target is an Option C project (pyproject.toml + .octrc.json
250 2. The active venv matches the target's expected venv (refuse with
251 a precise diagnostic otherwise).
252 3. No existing shadow install from a different worktree is about
253 to be silently overwritten.
254 4. The target is not inside a Claude Code worktree (refusable
255 with --allow-worktree).
257 func =
"oct_install_cmd"
260 f
"target={target} editable={editable} reinstall={reinstall} "
261 f
"use_active_venv={use_active_venv} allow_worktree={allow_worktree}",
267 "Error: --editable / -e is required. Non-editable installs "
268 "are out of scope for `oct install`.",
274 target_path = Path(target).resolve()
275 refusals: list[str] = []
276 warnings: list[str] = []
281 f
"target {target_path} is not an Option C project "
282 f
"(missing pyproject.toml + (.octrc.json | .option_c/))"
288 f
"target {target_path} is inside a Claude Code worktree "
289 f
"(.claude/worktrees/...). Pass --allow-worktree to override."
293 cwd = Path.cwd().resolve()
297 if cwd != target_path:
299 f
"CWD {cwd} is not an Option C project root. "
300 f
"Continuing because target {target_path} is."
307 if not use_active_venv:
308 if expected_venv
is None:
310 f
"target has no local venv at {target_path}/.venv "
311 f
"or {target_path}/venv; skipping venv-match check"
313 elif active_venv
is None:
315 f
"no active venv detected; project expects {expected_venv}. "
316 f
"Activate the venv (.venv\\Scripts\\activate or "
317 f
"source .venv/bin/activate), or pass --use-active-venv."
321 same = active_venv.samefile(expected_venv)
323 same = active_venv == expected_venv
326 f
"active venv is at {active_venv}; project expects "
327 f
"{expected_venv}. Activate the right venv, or pass "
328 f
"--use-active-venv to override."
333 shadow_loc: Path |
None =
None
334 if not refusals
and pkg_name:
337 active_venv
if use_active_venv
or expected_venv
is None
340 if shadow_loc
is not None:
343 expected_loc = target_path
344 if not _is_under(shadow_loc, expected_loc):
347 f
"package '{pkg_name}' is already installed "
348 f
"with Location={shadow_loc}, which is NOT under "
349 f
"the install target {target_path}. This would "
350 f
"silently shadow another worktree's install. "
351 f
"Pass --reinstall to overwrite, or activate the "
356 f
"overwriting existing install at {shadow_loc} "
357 f
"(--reinstall acknowledged)"
363 click.echo(json.dumps({
364 "operation":
"install",
365 "target": str(target_path),
368 "warnings": warnings,
371 click.echo(
"oct install preflight FAILED:", err=
True)
373 click.echo(f
" - {r}", err=
True)
375 click.echo(f
" ! {w}", err=
True)
379 if warnings
and not json_mode:
381 click.echo(f
"Warning: {w}", err=
True)
384 pip_python =
_venv_python(active_venv)
if active_venv
else Path(sys.executable)
385 pip_argv = [str(pip_python),
"-m",
"pip",
"install",
"-e", str(target_path)]
386 _dbg(
"INSTALL", func, f
"running: {pip_argv}", 2)
389 proc = subprocess.run(
390 pip_argv, shell=
False, timeout=_PIP_TIMEOUT,
391 text=
True, capture_output=json_mode,
393 except subprocess.TimeoutExpired:
395 f
"pip install timed out after {_PIP_TIMEOUT}s.",
400 except FileNotFoundError
as exc:
401 click.echo(f
"pip executable not found: {exc}", err=
True)
405 if proc.returncode != 0:
407 click.echo(json.dumps({
408 "operation":
"install",
409 "target": str(target_path),
411 "errors": [f
"pip install failed (exit {proc.returncode})"],
412 "stdout": proc.stdout,
"stderr": proc.stderr,
416 f
"pip install failed (exit {proc.returncode}).",
427 click.echo(json.dumps({
428 "operation":
"install",
429 "target": str(target_path),
431 "venv": str(active_venv)
if active_venv
else None,
432 "installed_location": str(new_loc)
if new_loc
else None,
433 "oct_binary": str(oct_path)
if oct_path
else None,
435 "warnings": warnings,
438 click.echo(f
"\noct install: {pkg_name or '<pkg>'} installed.")
440 click.echo(f
" Location: {new_loc}")
442 click.echo(f
" oct binary: {oct_path}")
444 click.echo(f
" venv: {active_venv}")
oct_install_cmd(ctx, target, editable, reinstall, use_active_venv, allow_worktree, allow_outside_project, json_mode)