scootcli 0.1.0__py3-none-any.whl

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.
Files changed (71) hide show
  1. scootcli/__init__.py +4 -0
  2. scootcli/__main__.py +9 -0
  3. scootcli/activity.py +26 -0
  4. scootcli/agent.py +350 -0
  5. scootcli/approvals.py +167 -0
  6. scootcli/auth.py +59 -0
  7. scootcli/cli.py +276 -0
  8. scootcli/clipboard.py +89 -0
  9. scootcli/commands/__init__.py +56 -0
  10. scootcli/commands/approve.py +38 -0
  11. scootcli/commands/auth.py +107 -0
  12. scootcli/commands/base.py +31 -0
  13. scootcli/commands/compact.py +40 -0
  14. scootcli/commands/copy.py +23 -0
  15. scootcli/commands/exit.py +14 -0
  16. scootcli/commands/forget.py +28 -0
  17. scootcli/commands/help.py +29 -0
  18. scootcli/commands/init.py +50 -0
  19. scootcli/commands/logo.py +51 -0
  20. scootcli/commands/model.py +61 -0
  21. scootcli/commands/panel.py +28 -0
  22. scootcli/commands/reset.py +20 -0
  23. scootcli/commands/resume.py +31 -0
  24. scootcli/commands/save.py +29 -0
  25. scootcli/commands/sessions.py +42 -0
  26. scootcli/commands/status.py +59 -0
  27. scootcli/commands/verbosity.py +57 -0
  28. scootcli/commands/worktree.py +64 -0
  29. scootcli/commands/yolo.py +20 -0
  30. scootcli/config.py +241 -0
  31. scootcli/context.py +82 -0
  32. scootcli/credentials.py +79 -0
  33. scootcli/errors.py +87 -0
  34. scootcli/images.py +169 -0
  35. scootcli/keys.py +119 -0
  36. scootcli/lineeditor.py +577 -0
  37. scootcli/logo.py +116 -0
  38. scootcli/models.py +120 -0
  39. scootcli/panel.py +263 -0
  40. scootcli/preferences.py +87 -0
  41. scootcli/presets.py +38 -0
  42. scootcli/project.py +94 -0
  43. scootcli/prompts.py +100 -0
  44. scootcli/providers/__init__.py +20 -0
  45. scootcli/providers/base.py +370 -0
  46. scootcli/providers/openai_chat.py +142 -0
  47. scootcli/providers/openai_responses.py +248 -0
  48. scootcli/providers/registry.py +173 -0
  49. scootcli/rendering.py +86 -0
  50. scootcli/repl.py +801 -0
  51. scootcli/sessions.py +186 -0
  52. scootcli/status.py +71 -0
  53. scootcli/tools/__init__.py +68 -0
  54. scootcli/tools/base.py +152 -0
  55. scootcli/tools/edit_file.py +72 -0
  56. scootcli/tools/list_dir.py +47 -0
  57. scootcli/tools/read_file.py +56 -0
  58. scootcli/tools/run_shell.py +73 -0
  59. scootcli/tools/search.py +170 -0
  60. scootcli/tools/update_plan.py +104 -0
  61. scootcli/tools/write_file.py +61 -0
  62. scootcli/transport.py +312 -0
  63. scootcli/vision.py +167 -0
  64. scootcli/workspace.py +105 -0
  65. scootcli/worktree.py +114 -0
  66. scootcli-0.1.0.dist-info/METADATA +238 -0
  67. scootcli-0.1.0.dist-info/RECORD +71 -0
  68. scootcli-0.1.0.dist-info/WHEEL +5 -0
  69. scootcli-0.1.0.dist-info/entry_points.txt +2 -0
  70. scootcli-0.1.0.dist-info/licenses/LICENSE +21 -0
  71. scootcli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,28 @@
1
+ """/forget [<id>|all] — delete saved session(s) for this workspace root."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color, eprint
6
+ from . import register
7
+ from .base import SlashCommand
8
+
9
+
10
+ def _run(session, args: str):
11
+ from .. import sessions
12
+
13
+ arg = args.strip()
14
+ if arg == "all":
15
+ n = sessions.delete_all(str(session.config.root))
16
+ print(color(f"forgot {n} saved session(s) for this directory.", "gray"))
17
+ return
18
+ if not arg:
19
+ eprint(color("usage: /forget <id>|all (see /sessions).", "yellow"))
20
+ return
21
+ if sessions.delete(arg):
22
+ print(color(f"forgot session {arg}.", "gray"))
23
+ else:
24
+ eprint(color(f"no session with id '{arg}'.", "yellow"))
25
+
26
+
27
+ register(SlashCommand("forget", "delete saved session(s)", _run, usage="<id>|all"))
28
+
@@ -0,0 +1,29 @@
1
+ """/help — list available slash-commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color
6
+ from . import all_commands, register
7
+ from .base import SlashCommand
8
+
9
+
10
+ def _run(session, args: str):
11
+ if getattr(getattr(session, "config", None), "logo", True):
12
+ from ..logo import mascot
13
+
14
+ for row in mascot("idle", "micro"):
15
+ print(color(row, "cyan"))
16
+ print(color("slash-commands:", "bold"))
17
+ for name, cmd in all_commands().items():
18
+ usage = f" {cmd.usage}" if cmd.usage else ""
19
+ print(f" {color('/' + name, 'cyan')}{usage} {color('— ' + cmd.summary, 'gray')}")
20
+ print(color(" (or just type a prompt · esc to interrupt · ctrl-c to quit)", "gray"))
21
+ print(color(" keys: ↑/↓ recall history · ↓ on a draft clears it (↑ brings it back)", "gray"))
22
+ print(color(" feed detail: /verbosity full|compact|quiet (collapse tool calls / hide reasoning)",
23
+ "gray"))
24
+ print(color(" copy the last reply with /c or Ctrl-S (⌘C can't be captured by a terminal app)",
25
+ "gray"))
26
+
27
+
28
+ register(SlashCommand("help", "show this help", _run))
29
+
@@ -0,0 +1,50 @@
1
+ """/init — learn the project and write AGENTS.md (auto-loaded into the system prompt)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..activity import activity
6
+ from ..errors import ScootError, Interrupted
7
+ from ..keys import read_key
8
+ from ..project import generate_agents_md
9
+ from ..rendering import color, eprint
10
+ from . import register
11
+ from .base import SlashCommand
12
+
13
+
14
+ def _run(session, args: str):
15
+ root = session.config.root
16
+ target = root / "AGENTS.md"
17
+
18
+ if target.exists():
19
+ print(color("AGENTS.md already exists — overwrite? [y/N] ", "yellow"), end="", flush=True)
20
+ key = read_key()
21
+ print(key)
22
+ if key != "y":
23
+ print(color("cancelled.", "gray"))
24
+ return
25
+
26
+ try:
27
+ with activity("scanning project & writing AGENTS.md…") as cancel:
28
+ content = generate_agents_md(session, cancel)
29
+ except Interrupted:
30
+ print(color("⏹ interrupted", "yellow"))
31
+ return
32
+ except ScootError as exc:
33
+ eprint(color(f"/init failed: {exc}", "red"))
34
+ return
35
+
36
+ if not content:
37
+ eprint(color("model returned no content.", "yellow"))
38
+ return
39
+ try:
40
+ target.write_text(content, "utf-8")
41
+ except OSError as exc:
42
+ eprint(color(f"could not write AGENTS.md: {exc}", "red"))
43
+ return
44
+ print(color(
45
+ f"wrote {target} ({len(content.splitlines())} lines). "
46
+ "It will load into context automatically on future turns.", "gray"))
47
+
48
+
49
+ register(SlashCommand("init", "learn the project → AGENTS.md", _run))
50
+
@@ -0,0 +1,51 @@
1
+ """/logo — show the mascot, or turn it on/off (persisted across launches).
2
+
3
+ ``off`` hides the launch banner art and the face in the status bar; the transcript label is unaffected
4
+ (see ``--no-emoji`` / ``SCOOT_EMOJI`` for that).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from .. import logo, preferences
10
+ from ..rendering import color, eprint
11
+ from . import register
12
+ from .base import SlashCommand
13
+
14
+ _ON = ("on", "true", "1", "yes", "show")
15
+ _OFF = ("off", "false", "0", "no", "hide")
16
+
17
+
18
+ def _run(session, args: str):
19
+ want = (args or "").strip().lower()
20
+ cfg = session.config
21
+ if not want:
22
+ for row in logo.mascot("idle", "lean"):
23
+ print(color(row, "cyan"))
24
+ state = "on" if getattr(cfg, "logo", True) else "off"
25
+ print(color(f"logo: {state} (usage: /logo on|off · also --no-logo / SCOOT_LOGO=0)", "gray"))
26
+ return
27
+ if want in _ON:
28
+ on = True
29
+ elif want in _OFF:
30
+ on = False
31
+ else:
32
+ eprint(color(f"unknown option '{want}'. use: /logo on|off", "yellow"))
33
+ return
34
+ preferences.set_logo(on)
35
+ try:
36
+ session.config = cfg.override(logo=on) # Config is frozen: swap in a copy for the live session
37
+ except Exception:
38
+ pass
39
+ bar = getattr(session, "status_bar", None)
40
+ if bar is not None:
41
+ from ..auth import account_label, auth_source
42
+ from ..panel import build_status_text
43
+
44
+ try:
45
+ bar.render(build_status_text(session, account_label() or auth_source()))
46
+ except Exception:
47
+ pass
48
+ print(color(f"logo {'on' if on else 'off'} (saved; the banner follows on the next launch).", "gray"))
49
+
50
+
51
+ register(SlashCommand("logo", "show the mascot, or /logo on|off to toggle it", _run, usage="[on|off]"))
@@ -0,0 +1,61 @@
1
+ """/model — list available models or switch the active one ('auto' by default).
2
+
3
+ Models are ``provider/model`` ids (``openai/gpt-5.3-codex``, ``ollama/llama3.2``); a bare name means
4
+ the default provider. A switch made here is **persisted** (``~/.config/scoot/preferences.json``) so the
5
+ next launch reuses it. An explicit ``--model`` flag or ``SCOOT_MODEL`` still overrides it for that run.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from ..preferences import set_model
11
+ from ..providers.base import qualify, split_model_id
12
+ from ..rendering import color
13
+ from . import register
14
+ from .base import SlashCommand
15
+
16
+
17
+ def _run(session, args: str):
18
+ arg = args.strip()
19
+ available = session.available_models()
20
+
21
+ if not arg:
22
+ print(color(f"models (preference: {session.model} → active: {session.active_model})", "bold"))
23
+ current = ""
24
+ for mid in available:
25
+ head, _ = split_model_id(mid)
26
+ if head != current:
27
+ current = head
28
+ print(color(f" {head}", "bold"))
29
+ mark = color(" ← active", "green") if mid == session.active_model else ""
30
+ print(" " + color(mid, "cyan") + mark)
31
+ errors = getattr(getattr(session, "provider", None), "list_errors", None) or {}
32
+ for name, err in sorted(errors.items()):
33
+ print(color(f" {name}: unavailable ({err})", "yellow"))
34
+ print(color(" use: /model <provider/model> · /model default · /model auto (pick per task)", "gray"))
35
+ return
36
+
37
+ if arg in ("auto", "default"):
38
+ session.model = arg
39
+ session.active_model = session.resolved_model()
40
+ set_model(arg)
41
+ what = "picked per task from the live list" if arg == "auto" else f"the provider's preferred model, now {session.active_model}"
42
+ print(color(f"model preference set to {arg} ({what}); saved for next launch.", "gray"))
43
+ return
44
+
45
+ if arg not in available:
46
+ head, _ = split_model_id(arg)
47
+ if head is None:
48
+ try:
49
+ arg = qualify(session.provider.default_name, arg)
50
+ except Exception:
51
+ pass
52
+ if arg not in available:
53
+ print(color(f"warning: '{arg}' is not in the models list; setting it anyway.", "yellow"))
54
+ session.model = arg
55
+ session.active_model = arg
56
+ set_model(arg)
57
+ print(color(f"model set to {arg}; saved for next launch.", "gray"))
58
+
59
+
60
+ register(SlashCommand("model", "list or switch model (default|auto|provider/model)", _run, usage="[provider/model|default|auto]"))
61
+
@@ -0,0 +1,28 @@
1
+ """/panel — toggle the persistent bottom status bar on/off."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color, eprint
6
+ from . import register
7
+ from .base import SlashCommand
8
+
9
+
10
+ def _run(session, args: str):
11
+ bar = getattr(session, "status_bar", None)
12
+ if bar is None:
13
+ eprint(color("status bar is not available in this context.", "yellow"))
14
+ return
15
+ on = bar.toggle()
16
+ if on:
17
+ from ..auth import account_label, auth_source
18
+ from ..panel import build_status_text
19
+
20
+ try:
21
+ bar.render(build_status_text(session, account_label() or auth_source()))
22
+ except Exception:
23
+ pass
24
+ print(color(f"panel {'on' if on else 'off'}.", "gray"))
25
+
26
+
27
+ register(SlashCommand("panel", "toggle the bottom status bar", _run))
28
+
@@ -0,0 +1,20 @@
1
+ """/reset — clear the conversation history and context (keeps any loaded project context)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color
6
+ from . import register
7
+ from .base import SlashCommand
8
+
9
+
10
+ def _run(session, args: str):
11
+ session.reset()
12
+ redraw = getattr(session, "redraw_home", None)
13
+ if callable(redraw):
14
+ redraw("fresh session — conversation + context cleared.")
15
+ else:
16
+ print(color("conversation + context cleared — fresh session.", "gray"))
17
+
18
+
19
+ register(SlashCommand("reset", "clear the conversation + context", _run))
20
+
@@ -0,0 +1,31 @@
1
+ """/resume [<id>] — reload a saved conversation (defaults to the latest for this directory)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color, eprint
6
+ from . import register
7
+ from .base import SlashCommand
8
+
9
+
10
+ def _run(session, args: str):
11
+ from .. import sessions
12
+
13
+ ident = args.strip()
14
+ if ident:
15
+ record = sessions.load(ident)
16
+ if record is None:
17
+ eprint(color(f"no session with id '{ident}' (try /sessions).", "yellow"))
18
+ return
19
+ else:
20
+ record = sessions.latest_for_root(str(session.config.root))
21
+ if record is None:
22
+ print(color("no saved sessions for this directory.", "gray"))
23
+ return
24
+
25
+ session.apply_record(record)
26
+ print(color(f"resumed session {record.id} · {len(session.messages)} messages "
27
+ f"· model {session.active_model}.", "gray"))
28
+
29
+
30
+ register(SlashCommand("resume", "resume a saved session", _run, usage="[id]"))
31
+
@@ -0,0 +1,29 @@
1
+ """/save <file> — dump the current transcript to a file."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ from ..rendering import color, eprint
8
+ from . import register
9
+ from .base import SlashCommand
10
+
11
+
12
+ def _run(session, args: str):
13
+ target = args.strip() or "scoot-transcript.md"
14
+ path = Path(target).expanduser()
15
+ lines = [f"# scoot transcript\n\n_model: {session.resolved_model()}_\n"]
16
+ for msg in session.messages:
17
+ role = msg.get("role", "?")
18
+ content = msg.get("content", "")
19
+ lines.append(f"\n## {role}\n\n{content}\n")
20
+ try:
21
+ path.write_text("".join(lines))
22
+ except OSError as exc:
23
+ eprint(color(f"could not write {path}: {exc}", "red"))
24
+ return
25
+ print(color(f"saved transcript → {path}", "gray"))
26
+
27
+
28
+ register(SlashCommand("save", "save the transcript", _run, usage="<file>"))
29
+
@@ -0,0 +1,42 @@
1
+ """/sessions — list saved sessions for this workspace root (resume with /resume <id>)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+
7
+ from ..rendering import color
8
+ from . import register
9
+ from .base import SlashCommand
10
+
11
+
12
+ def _ago(ts: float) -> str:
13
+ if not ts:
14
+ return "?"
15
+ secs = max(0, int(time.time() - ts))
16
+ if secs < 60:
17
+ return f"{secs}s ago"
18
+ if secs < 3600:
19
+ return f"{secs // 60}m ago"
20
+ if secs < 86400:
21
+ return f"{secs // 3600}h ago"
22
+ return f"{secs // 86400}d ago"
23
+
24
+
25
+ def _run(session, args: str):
26
+ from .. import sessions
27
+
28
+ records = sessions.list_sessions(str(session.config.root))
29
+ if not records:
30
+ print(color("no saved sessions for this directory.", "gray"))
31
+ return
32
+ print(color("saved sessions (this directory):", "bold"))
33
+ for rec in records:
34
+ marker = color(" ← current", "green") if rec.id == session.id else ""
35
+ print(f" {color(rec.id, 'cyan')} {color(_ago(rec.updated), 'gray'):<20} "
36
+ f"{rec.turns} turns · {rec.active_model or rec.model}{marker}")
37
+ print(f" {color(rec.first_prompt(), 'gray')}")
38
+ print(color("resume with: /resume <id>", "gray"))
39
+
40
+
41
+ register(SlashCommand("sessions", "list saved sessions", _run))
42
+
@@ -0,0 +1,59 @@
1
+ """/status — show current model, token usage, root, and session info."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color
6
+ from . import register
7
+ from .base import SlashCommand
8
+
9
+
10
+ def _run(session, args: str):
11
+ cfg = session.config
12
+ try:
13
+ provider_row = session.provider.default_name
14
+ from ..auth import key_source
15
+
16
+ src = key_source(session.provider.spec)
17
+ provider_row += f" (key: {src})" if src else ("" if not session.provider.spec.key_required
18
+ else " (no key)")
19
+ except Exception:
20
+ provider_row = getattr(cfg, "provider", "") or "?"
21
+ rows = [
22
+ ("provider", provider_row),
23
+ ("model", session.active_model + (" (auto)" if session.model.lower() == "auto" else "")),
24
+ ("effort", getattr(cfg, "effort", "medium")),
25
+ ("env files", ", ".join(getattr(cfg, "env_files", ()) or ()) or "none"),
26
+ ("root", str(cfg.root)),
27
+ ("approval", session.approval_mode),
28
+ ("streaming", "on" if getattr(cfg, "stream", True) else "off"),
29
+ ("panel", "on" if getattr(session, "status_bar", None) and session.status_bar.enabled
30
+ else "off"),
31
+ ("input dock", "on" if getattr(session, "dock", False) else "off"),
32
+ ("resume", getattr(cfg, "resume", "hint")),
33
+ ("workspace context", "on" if getattr(cfg, "workspace_context", True) else "off"),
34
+ ("feed verbosity", getattr(getattr(session, "ui", None), "verbosity",
35
+ getattr(cfg, "verbosity", "full"))),
36
+ ("max steps", str(cfg.max_steps)),
37
+ ("turns", str(len(session.messages))),
38
+ ("tokens (session)", f"prompt={session.total_prompt} completion={session.total_completion}"),
39
+ ("tokens (last)", f"prompt={session.last_usage.get('prompt_tokens', '?')} "
40
+ f"completion={session.last_usage.get('completion_tokens', '?')}"),
41
+ ]
42
+ print(color("status:", "bold"))
43
+ for label, value in rows:
44
+ print(f" {color(label + ':', 'gray'):<28} {value}")
45
+ trusted = getattr(session, "trusted_tools", None)
46
+ if trusted:
47
+ print(f" {color('trusted tools:', 'gray'):<28} {', '.join(sorted(trusted))}")
48
+ if getattr(session, "worktree", None) is not None:
49
+ print(f" {color('worktree:', 'gray'):<28} {session.worktree.branch}")
50
+ plan = getattr(session, "plan", None)
51
+ if plan:
52
+ done = sum(1 for s in plan if s.get("status") == "completed")
53
+ current = next((s.get("step", "") for s in plan if s.get("status") == "in_progress"), "")
54
+ detail = f"{done}/{len(plan)}" + (f" · now: {current}" if current else "")
55
+ print(f" {color('plan:', 'gray'):<28} {detail}")
56
+
57
+
58
+ register(SlashCommand("status", "show model + token usage", _run))
59
+
@@ -0,0 +1,57 @@
1
+ """/verbosity — regulate how much of a turn's "thinking" shows in the feed.
2
+
3
+ Levels (see :class:`scootcli.repl.ReplUI`):
4
+ * ``full`` — reasoning narration shown; every tool-in-use line kept in the feed (a run log).
5
+ * ``compact`` — reasoning narration shown; tool-in-use is one transient line above the spinner,
6
+ removed once the tool finishes (nothing accumulates).
7
+ * ``quiet`` — reasoning narration suppressed; tools render like ``compact``.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from ..rendering import color, eprint
13
+ from . import register
14
+ from .base import SlashCommand
15
+
16
+ _LEVELS = ("full", "compact", "quiet")
17
+ _ALIASES = {"default": "full", "collapse": "compact", "collapsed": "compact",
18
+ "off": "quiet", "min": "quiet", "minimal": "quiet"}
19
+ _HELP = {
20
+ "full": "show reasoning + keep every tool call in the feed",
21
+ "compact": "show reasoning; collapse tool calls to one transient line above Thinking…",
22
+ "quiet": "hide reasoning; collapse tool calls",
23
+ }
24
+
25
+
26
+ def _run(session, args: str):
27
+ ui = getattr(session, "ui", None)
28
+ if ui is None:
29
+ eprint(color("verbosity is not available in this context.", "yellow"))
30
+ return
31
+
32
+ want = (args or "").strip().lower()
33
+ if not want:
34
+ cur = getattr(ui, "verbosity", "full")
35
+ print(color(f"feed verbosity: {cur}", "bold"))
36
+ for lvl in _LEVELS:
37
+ mark = color("●", "cyan") if lvl == cur else color("○", "gray")
38
+ print(f" {mark} {color(lvl, 'bold')} {color('— ' + _HELP[lvl], 'gray')}")
39
+ print(color(" usage: /verbosity full|compact|quiet", "gray"))
40
+ return
41
+
42
+ level = _ALIASES.get(want, want)
43
+ if level not in _LEVELS:
44
+ eprint(color(f"unknown level '{want}'. use: full | compact | quiet", "yellow"))
45
+ return
46
+ ui.verbosity = level
47
+ try:
48
+ session.config.verbosity = level # keep config in sync (surfaced in /status)
49
+ except Exception:
50
+ pass
51
+ print(color(f"feed verbosity → {level} ({_HELP[level]})", "gray"))
52
+
53
+
54
+ register(SlashCommand(
55
+ "verbosity", "regulate feed detail (full|compact|quiet)", _run,
56
+ usage="[full|compact|quiet]",
57
+ ))
@@ -0,0 +1,64 @@
1
+ """/worktree — isolate autonomous work in a throwaway git worktree, then review & merge (M5.1b)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color, eprint
6
+ from .. import worktree as wt
7
+ from . import register
8
+ from .base import SlashCommand
9
+
10
+ _ACTIONS = ("merge", "keep", "discard")
11
+
12
+
13
+ def _start(session):
14
+ if session.worktree is not None:
15
+ print(color(f"already in worktree {session.worktree.branch}.", "yellow"))
16
+ return
17
+ root = session.config.root
18
+ if not wt.is_git_repo(root):
19
+ print(color("not a git repo — worktree isolation unavailable here.", "yellow"))
20
+ print(color(" tip: use /yolo in place, or `git init` first.", "gray"))
21
+ return
22
+ try:
23
+ w = wt.start(root)
24
+ except Exception as exc: # git failure
25
+ eprint(color(f"could not create worktree: {exc}", "red"))
26
+ return
27
+ session.worktree = w
28
+ session.config = session.config.override(root=str(w.path))
29
+ print(color(f"⚙ isolated in worktree {w.branch}", "cyan"))
30
+ print(color(f" root → {w.path}", "gray"))
31
+ print(color(" work freely (try /yolo); then /worktree merge|keep|discard", "gray"))
32
+
33
+
34
+ def _finish(session, action):
35
+ w = session.worktree
36
+ if w is None:
37
+ print(color("not in a worktree.", "yellow"))
38
+ return
39
+ msg = wt.finish(w, action)
40
+ session.config = session.config.override(root=str(w.original_root))
41
+ session.worktree = None
42
+ print(color(f"{msg}", "gray"))
43
+ print(color(f" root → {w.original_root}", "gray"))
44
+
45
+
46
+ def _run(session, args: str):
47
+ sub = (args.strip() or "start").split()[0].lower()
48
+ if sub in ("start", "on"):
49
+ _start(session)
50
+ elif sub == "diff":
51
+ if session.worktree is None:
52
+ print(color("not in a worktree.", "yellow"))
53
+ return
54
+ d = wt.diff(session.worktree)
55
+ print(d or color("(no changes yet)", "gray"))
56
+ elif sub in _ACTIONS:
57
+ _finish(session, sub)
58
+ else:
59
+ print(color(f"usage: /worktree start|diff|{'|'.join(_ACTIONS)}", "gray"))
60
+
61
+
62
+ register(SlashCommand("worktree", "isolate work in a git worktree, then merge/keep/discard",
63
+ _run, usage="start|diff|merge|keep|discard"))
64
+
@@ -0,0 +1,20 @@
1
+ """/yolo — shortcut to switch the session into yolo approval mode (auto-approve all)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from ..rendering import color
6
+ from . import register
7
+ from .base import SlashCommand
8
+
9
+
10
+ def _run(session, args: str):
11
+ session.approval_mode = "yolo"
12
+ print(color("⚡ yolo mode ON — tool calls run without asking.", "yellow"))
13
+ print(color(" (destructive shell commands are still confirmed; /approve always to revert)", "gray"))
14
+ if args.strip().lower() in ("worktree", "wt", "--worktree"):
15
+ from . import get
16
+ get("worktree").handler(session, "start")
17
+
18
+
19
+ register(SlashCommand("yolo", "auto-approve all tool calls this session", _run, usage="[worktree]"))
20
+