aweshelf 0.1.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.
Files changed (35) hide show
  1. aweshelf-0.1.1/PKG-INFO +108 -0
  2. aweshelf-0.1.1/README.md +87 -0
  3. aweshelf-0.1.1/pyproject.toml +47 -0
  4. aweshelf-0.1.1/setup.cfg +4 -0
  5. aweshelf-0.1.1/src/aweshelf/__init__.py +3 -0
  6. aweshelf-0.1.1/src/aweshelf/cli.py +55 -0
  7. aweshelf-0.1.1/src/aweshelf/commands/__init__.py +0 -0
  8. aweshelf-0.1.1/src/aweshelf/commands/bookmark.py +127 -0
  9. aweshelf-0.1.1/src/aweshelf/commands/browse.py +20 -0
  10. aweshelf-0.1.1/src/aweshelf/commands/list.py +75 -0
  11. aweshelf-0.1.1/src/aweshelf/commands/resume.py +42 -0
  12. aweshelf-0.1.1/src/aweshelf/commands/show.py +68 -0
  13. aweshelf-0.1.1/src/aweshelf/lib/__init__.py +0 -0
  14. aweshelf-0.1.1/src/aweshelf/lib/aweswitch.py +74 -0
  15. aweshelf-0.1.1/src/aweshelf/lib/discovery.py +77 -0
  16. aweshelf-0.1.1/src/aweshelf/lib/resume.py +10 -0
  17. aweshelf-0.1.1/src/aweshelf/lib/resume_target.py +87 -0
  18. aweshelf-0.1.1/src/aweshelf/lib/session.py +178 -0
  19. aweshelf-0.1.1/src/aweshelf/lib/store.py +126 -0
  20. aweshelf-0.1.1/src/aweshelf/tui/__init__.py +0 -0
  21. aweshelf-0.1.1/src/aweshelf/tui/app.py +234 -0
  22. aweshelf-0.1.1/src/aweshelf/types.py +32 -0
  23. aweshelf-0.1.1/src/aweshelf.egg-info/PKG-INFO +108 -0
  24. aweshelf-0.1.1/src/aweshelf.egg-info/SOURCES.txt +33 -0
  25. aweshelf-0.1.1/src/aweshelf.egg-info/dependency_links.txt +1 -0
  26. aweshelf-0.1.1/src/aweshelf.egg-info/entry_points.txt +2 -0
  27. aweshelf-0.1.1/src/aweshelf.egg-info/requires.txt +6 -0
  28. aweshelf-0.1.1/src/aweshelf.egg-info/top_level.txt +1 -0
  29. aweshelf-0.1.1/tests/test_aweswitch.py +148 -0
  30. aweshelf-0.1.1/tests/test_browse.py +49 -0
  31. aweshelf-0.1.1/tests/test_cli.py +197 -0
  32. aweshelf-0.1.1/tests/test_discovery.py +168 -0
  33. aweshelf-0.1.1/tests/test_resume.py +76 -0
  34. aweshelf-0.1.1/tests/test_session.py +89 -0
  35. aweshelf-0.1.1/tests/test_store.py +174 -0
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: aweshelf
3
+ Version: 0.1.1
4
+ Summary: Bookmark, categorize, and restore AI coding sessions with aweswitch profiles.
5
+ Author: Peng
6
+ License-Expression: MPL-2.0
7
+ Keywords: ai,agent,claude,codex,bookmark,session,aweswitch
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Topic :: Utilities
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ Requires-Dist: click>=8.1
17
+ Requires-Dist: textual>=0.40
18
+ Provides-Extra: dev
19
+ Requires-Dist: pytest; extra == "dev"
20
+ Requires-Dist: ruff; extra == "dev"
21
+
22
+ <div align="center">
23
+ <h1>aweshelf: Session Bookmark Manager</h1>
24
+ <p><strong>Bookmark, categorize, and restore AI coding sessions with aweswitch profiles.</strong></p>
25
+ <p>A lightweight CLI-first tool for Claude Code and Codex session management.</p>
26
+ <p>
27
+ <strong>English</strong> ·
28
+ <a href="./README_cn.md">简体中文</a>
29
+ </p>
30
+ <p>
31
+ <img src="https://img.shields.io/badge/version-0.1.1-7C3AED?style=flat-square" alt="Version">
32
+ <img src="https://img.shields.io/badge/python-%E2%89%A53.10-0EA5E9?style=flat-square" alt="Python">
33
+ </p>
34
+ <p>
35
+ <img src="https://img.shields.io/badge/status-alpha-c96a3d?style=flat-square" alt="Status">
36
+ <img src="https://img.shields.io/badge/install-pip-22C55E?style=flat-square" alt="pip install">
37
+ <img src="https://img.shields.io/badge/platform-terminal-334155?style=flat-square" alt="Platform">
38
+ <img src="https://img.shields.io/github/stars/Webioinfo01/aweshelf?style=flat-square" alt="GitHub stars">
39
+ </p>
40
+ </div>
41
+
42
+ > Bookmark, categorize, and restore AI coding sessions with aweswitch profiles.
43
+
44
+ aweshelf lets you save your favorite Claude Code and Codex sessions, tag them with categories, and restore them instantly — including the aweswitch profile (API endpoint, model, token) that was active when you bookmarked.
45
+
46
+ ## Install
47
+
48
+ ```bash
49
+ pip install aweshelf
50
+ ```
51
+
52
+ ## Quick Start
53
+
54
+ ```bash
55
+ # Bookmark the current project's most recent session
56
+ aweshelf bookmark
57
+
58
+ # List all bookmarks
59
+ aweshelf list
60
+
61
+ # Resume a bookmark
62
+ aweshelf resume aweshelf_0001
63
+
64
+ # Browse interactively
65
+ aweshelf browse
66
+ ```
67
+
68
+ ## Config
69
+
70
+ Bookmarks are stored at `~/.config/aweshelf/bookmarks.json`. Override with `AWESHELF_CONFIG` env var.
71
+
72
+ ```json
73
+ {
74
+ "version": 1,
75
+ "bookmarks": [
76
+ {
77
+ "id": "aweshelf_0001",
78
+ "provider": "claude",
79
+ "session_id": "550e8400-...",
80
+ "title": "Fix auth middleware bug",
81
+ "category": "backend",
82
+ "project_path": "/Users/peng/Desktop/Project/my-app",
83
+ "aweswitch_profile": "cc-glm",
84
+ "bookmarked_at": "2026-05-20T14:00:00Z"
85
+ }
86
+ ]
87
+ }
88
+ ```
89
+
90
+ ## Commands
91
+
92
+ ```bash
93
+ aweshelf bookmark [SESSION_ID] [-t TITLE] [-c CATEGORY] [--profile PROFILE]
94
+ aweshelf list [-c CATEGORY] [-p PROVIDER]
95
+ aweshelf search QUERY
96
+ aweshelf recent [-n COUNT]
97
+ aweshelf show BOOKMARK_ID [--json]
98
+ aweshelf edit BOOKMARK_ID [-t TITLE] [-c CATEGORY] [--profile PROFILE]
99
+ aweshelf rm BOOKMARK_ID [--force]
100
+ aweshelf resume BOOKMARK_ID [--profile PROFILE] [--raw] [--dry-run]
101
+ aweshelf browse
102
+ ```
103
+
104
+ ## Development
105
+
106
+ ```bash
107
+ python -m pytest tests/
108
+ ```
@@ -0,0 +1,87 @@
1
+ <div align="center">
2
+ <h1>aweshelf: Session Bookmark Manager</h1>
3
+ <p><strong>Bookmark, categorize, and restore AI coding sessions with aweswitch profiles.</strong></p>
4
+ <p>A lightweight CLI-first tool for Claude Code and Codex session management.</p>
5
+ <p>
6
+ <strong>English</strong> ·
7
+ <a href="./README_cn.md">简体中文</a>
8
+ </p>
9
+ <p>
10
+ <img src="https://img.shields.io/badge/version-0.1.1-7C3AED?style=flat-square" alt="Version">
11
+ <img src="https://img.shields.io/badge/python-%E2%89%A53.10-0EA5E9?style=flat-square" alt="Python">
12
+ </p>
13
+ <p>
14
+ <img src="https://img.shields.io/badge/status-alpha-c96a3d?style=flat-square" alt="Status">
15
+ <img src="https://img.shields.io/badge/install-pip-22C55E?style=flat-square" alt="pip install">
16
+ <img src="https://img.shields.io/badge/platform-terminal-334155?style=flat-square" alt="Platform">
17
+ <img src="https://img.shields.io/github/stars/Webioinfo01/aweshelf?style=flat-square" alt="GitHub stars">
18
+ </p>
19
+ </div>
20
+
21
+ > Bookmark, categorize, and restore AI coding sessions with aweswitch profiles.
22
+
23
+ aweshelf lets you save your favorite Claude Code and Codex sessions, tag them with categories, and restore them instantly — including the aweswitch profile (API endpoint, model, token) that was active when you bookmarked.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install aweshelf
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ```bash
34
+ # Bookmark the current project's most recent session
35
+ aweshelf bookmark
36
+
37
+ # List all bookmarks
38
+ aweshelf list
39
+
40
+ # Resume a bookmark
41
+ aweshelf resume aweshelf_0001
42
+
43
+ # Browse interactively
44
+ aweshelf browse
45
+ ```
46
+
47
+ ## Config
48
+
49
+ Bookmarks are stored at `~/.config/aweshelf/bookmarks.json`. Override with `AWESHELF_CONFIG` env var.
50
+
51
+ ```json
52
+ {
53
+ "version": 1,
54
+ "bookmarks": [
55
+ {
56
+ "id": "aweshelf_0001",
57
+ "provider": "claude",
58
+ "session_id": "550e8400-...",
59
+ "title": "Fix auth middleware bug",
60
+ "category": "backend",
61
+ "project_path": "/Users/peng/Desktop/Project/my-app",
62
+ "aweswitch_profile": "cc-glm",
63
+ "bookmarked_at": "2026-05-20T14:00:00Z"
64
+ }
65
+ ]
66
+ }
67
+ ```
68
+
69
+ ## Commands
70
+
71
+ ```bash
72
+ aweshelf bookmark [SESSION_ID] [-t TITLE] [-c CATEGORY] [--profile PROFILE]
73
+ aweshelf list [-c CATEGORY] [-p PROVIDER]
74
+ aweshelf search QUERY
75
+ aweshelf recent [-n COUNT]
76
+ aweshelf show BOOKMARK_ID [--json]
77
+ aweshelf edit BOOKMARK_ID [-t TITLE] [-c CATEGORY] [--profile PROFILE]
78
+ aweshelf rm BOOKMARK_ID [--force]
79
+ aweshelf resume BOOKMARK_ID [--profile PROFILE] [--raw] [--dry-run]
80
+ aweshelf browse
81
+ ```
82
+
83
+ ## Development
84
+
85
+ ```bash
86
+ python -m pytest tests/
87
+ ```
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "aweshelf"
7
+ version = "0.1.1"
8
+ description = "Bookmark, categorize, and restore AI coding sessions with aweswitch profiles."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = ["click>=8.1", "textual>=0.40"]
12
+ authors = [
13
+ { name = "Peng" }
14
+ ]
15
+ license = "MPL-2.0"
16
+ keywords = ["ai", "agent", "claude", "codex", "bookmark", "session", "aweswitch"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Environment :: Console",
20
+ "Intended Audience :: Developers",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3 :: Only",
23
+ "Topic :: Utilities"
24
+ ]
25
+
26
+ [project.optional-dependencies]
27
+ dev = ["pytest", "ruff"]
28
+
29
+ [project.scripts]
30
+ aweshelf = "aweshelf.cli:main"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.ruff]
36
+ target-version = "py310"
37
+ line-length = 100
38
+
39
+ [tool.ruff.lint]
40
+ select = ["E", "F", "W", "I", "UP", "B", "SIM"]
41
+ ignore = ["E501"]
42
+
43
+ [tool.ruff.lint.isort]
44
+ known-first-party = ["aweshelf"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """aweshelf package."""
2
+
3
+ __version__ = "0.1.1"
@@ -0,0 +1,55 @@
1
+ """aweshelf CLI entry point."""
2
+
3
+ import click
4
+
5
+ from aweshelf import __version__
6
+ from aweshelf.commands.bookmark import bookmark_command
7
+ from aweshelf.commands.list import list_command, search_command, recent_command
8
+ from aweshelf.commands.show import show_command, edit_command, rm_command
9
+ from aweshelf.commands.resume import resume_command
10
+ from aweshelf.commands.browse import browse_command
11
+
12
+
13
+ @click.group(
14
+ name="aweshelf",
15
+ context_settings={"help_option_names": ["-h", "--help"]},
16
+ help="Bookmark, categorize, and restore AI coding sessions.",
17
+ )
18
+ @click.version_option(__version__, "-v", "--version", message="%(version)s")
19
+ def cli():
20
+ pass
21
+
22
+
23
+ cli.add_command(bookmark_command)
24
+ cli.add_command(list_command)
25
+ cli.add_command(search_command)
26
+ cli.add_command(recent_command)
27
+ cli.add_command(show_command)
28
+ cli.add_command(edit_command)
29
+ cli.add_command(rm_command)
30
+ cli.add_command(resume_command)
31
+ cli.add_command(browse_command)
32
+
33
+
34
+ @cli.command("help")
35
+ @click.argument("command_name", required=False)
36
+ @click.pass_context
37
+ def help_command(ctx, command_name):
38
+ """Display help for command."""
39
+ if command_name is None:
40
+ click.echo(ctx.parent.get_help())
41
+ return
42
+
43
+ command = cli.get_command(ctx, command_name)
44
+ if command is None or command.hidden:
45
+ raise click.ClickException(f"unknown command '{command_name}'")
46
+ with command.make_context(command_name, [], parent=ctx.parent, resilient_parsing=True) as command_ctx:
47
+ click.echo(command.get_help(command_ctx))
48
+
49
+
50
+ def main(argv=None):
51
+ return cli.main(args=argv, prog_name="aweshelf")
52
+
53
+
54
+ if __name__ == "__main__":
55
+ raise SystemExit(main())
File without changes
@@ -0,0 +1,127 @@
1
+ """Bookmark command."""
2
+
3
+ import click
4
+
5
+ from aweshelf.types import Bookmark
6
+ from aweshelf.lib.store import add_bookmark, list_categories, bookmark_path
7
+ from aweshelf.lib.discovery import find_project_sessions, find_recent_session
8
+ from aweshelf.lib.session import parse_session_meta
9
+ from aweshelf.lib.aweswitch import detect_profile, load_aweswitch_config
10
+
11
+ DEFAULT_LIST_LIMIT = 10
12
+
13
+
14
+ def pick_session(sessions: list[dict], limit: int = DEFAULT_LIST_LIMIT) -> dict:
15
+ """Let user pick a session from a numbered list."""
16
+ shown = sessions[:limit]
17
+ total = len(sessions)
18
+
19
+ if total > limit:
20
+ click.echo(f"Sessions in current project ({total} total, showing {limit} \u2014 use --verbose for all):\n")
21
+ else:
22
+ click.echo(f"Sessions in current project ({total} total):\n")
23
+
24
+ for i, s in enumerate(shown, 1):
25
+ title = s.get("title", "Untitled")
26
+ provider = s.get("provider", "?")
27
+ sid = s.get("session_id", "?")
28
+ click.echo(f" {i:>3}. [{provider}] {title}")
29
+ click.echo(f" {sid}")
30
+
31
+ if total > limit:
32
+ click.echo(f"\n ... and {total - limit} more")
33
+ click.echo()
34
+
35
+ while True:
36
+ choice = click.prompt("Pick a session (number)", type=int)
37
+ if 1 <= choice <= len(shown):
38
+ return sessions[choice - 1]
39
+ click.echo(f"Please enter 1-{len(shown)}")
40
+
41
+
42
+ def run_bookmark(
43
+ session_id: str | None = None,
44
+ title: str | None = None,
45
+ category: str | None = None,
46
+ profile: str | None = None,
47
+ interactive: bool = True,
48
+ verbose: bool = False,
49
+ ) -> Bookmark:
50
+ path = bookmark_path()
51
+
52
+ if session_id is None and interactive:
53
+ sessions = find_project_sessions()
54
+ if not sessions:
55
+ raise SystemExit("aweshelf: no session found in current project")
56
+ limit = len(sessions) if verbose else DEFAULT_LIST_LIMIT
57
+ session = pick_session(sessions, limit)
58
+ session_id = session["session_id"]
59
+ source_path = session.get("source_path", "")
60
+ provider = session.get("provider", "claude")
61
+ auto_title = session.get("title", "")
62
+ project_path = session.get("project_path", "")
63
+ elif session_id is None:
64
+ session = find_recent_session()
65
+ if session is None:
66
+ raise SystemExit("aweshelf: no session found in current project")
67
+ session_id = session["session_id"]
68
+ source_path = session.get("source_path", "")
69
+ provider = session.get("provider", "claude")
70
+ auto_title = session.get("title", "")
71
+ project_path = session.get("project_path", "")
72
+ else:
73
+ source_path = ""
74
+ provider = "claude"
75
+ auto_title = ""
76
+ project_path = ""
77
+
78
+ if title is None:
79
+ title = auto_title or "Untitled session"
80
+
81
+ if interactive and category is None:
82
+ cats = list_categories(path)
83
+ click.echo(f"\nTitle: {title}")
84
+ if cats:
85
+ click.echo(f"Existing categories: {', '.join(cats)}")
86
+ cat_input = click.prompt("Category", default="", show_default=False)
87
+ category = cat_input if cat_input else ""
88
+
89
+ if category is None:
90
+ category = ""
91
+
92
+ if profile is None and source_path:
93
+ try:
94
+ meta = parse_session_meta(source_path)
95
+ config = load_aweswitch_config()
96
+ if config:
97
+ profile = detect_profile({"ANTHROPIC_BASE_URL": "", "ANTHROPIC_MODEL": meta.get("model", "")})
98
+ except Exception:
99
+ pass
100
+
101
+ bookmark = Bookmark(
102
+ id="",
103
+ provider=provider,
104
+ session_id=session_id,
105
+ title=title,
106
+ category=category,
107
+ project_path=project_path,
108
+ aweswitch_profile=profile,
109
+ )
110
+
111
+ bookmark = add_bookmark(bookmark, path)
112
+ return bookmark
113
+
114
+
115
+ @click.command("bookmark")
116
+ @click.argument("session_id", required=False)
117
+ @click.option("-t", "--title", default=None, help="Bookmark title.")
118
+ @click.option("-c", "--category", default=None, help="Category for the bookmark.")
119
+ @click.option("--profile", default=None, help="aweswitch profile to use.")
120
+ @click.option("--verbose", is_flag=True, help="Show all sessions (no limit).")
121
+ def bookmark_command(session_id, title, category, profile, verbose):
122
+ """Bookmark a session for quick access."""
123
+ try:
124
+ b = run_bookmark(session_id, title, category, profile, interactive=True, verbose=verbose)
125
+ except ValueError as exc:
126
+ raise click.ClickException(str(exc)) from exc
127
+ click.echo(f"\nBookmarked {b.id} — {b.title}")
@@ -0,0 +1,20 @@
1
+ """Browse command — TUI entry point."""
2
+
3
+ import click
4
+
5
+ from aweshelf.lib.resume_target import execute_resume
6
+
7
+
8
+ @click.command("browse")
9
+ def browse_command():
10
+ """Browse bookmarks interactively."""
11
+ try:
12
+ from aweshelf.tui.app import BookmarkBrowser
13
+ except ImportError as exc:
14
+ raise click.ClickException("textual is required for browse. Install with: pip install textual") from exc
15
+
16
+ app = BookmarkBrowser()
17
+ result = app.run()
18
+
19
+ if result is not None:
20
+ execute_resume(result)
@@ -0,0 +1,75 @@
1
+ """List, search, recent commands."""
2
+
3
+ import click
4
+
5
+ from aweshelf.lib.store import load_bookmarks
6
+
7
+
8
+ def format_table(bookmarks: list) -> str:
9
+ if not bookmarks:
10
+ return "No bookmarks found."
11
+
12
+ headers = ["ID", "PROVIDER", "TITLE", "CATEGORY", "PROFILE"]
13
+ rows = []
14
+ for b in bookmarks:
15
+ rows.append([
16
+ b.id,
17
+ b.provider,
18
+ b.title[:40] + ("..." if len(b.title) > 40 else ""),
19
+ b.category or "-",
20
+ b.aweswitch_profile or "-",
21
+ ])
22
+
23
+ widths = [len(h) for h in headers]
24
+ for row in rows:
25
+ for i, cell in enumerate(row):
26
+ widths[i] = max(widths[i], len(cell))
27
+
28
+ lines = []
29
+ header_line = " ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
30
+ lines.append(header_line)
31
+ lines.append(" ".join("-" * w for w in widths))
32
+ for row in rows:
33
+ line = " ".join(cell.ljust(widths[i]) for i, cell in enumerate(row))
34
+ lines.append(line)
35
+
36
+ return "\n".join(lines)
37
+
38
+
39
+ @click.command("list")
40
+ @click.option("-c", "--category", default=None, help="Filter by category.")
41
+ @click.option("-p", "--provider", default=None, help="Filter by provider.")
42
+ def list_command(category, provider):
43
+ """List all bookmarks."""
44
+ bookmarks = load_bookmarks()
45
+ if category:
46
+ bookmarks = [b for b in bookmarks if b.category == category]
47
+ if provider:
48
+ bookmarks = [b for b in bookmarks if b.provider == provider]
49
+ click.echo(format_table(bookmarks))
50
+
51
+
52
+ @click.command("search")
53
+ @click.argument("query")
54
+ def search_command(query):
55
+ """Search bookmarks by title, category, session ID, project, or profile."""
56
+ bookmarks = load_bookmarks()
57
+ query_lower = query.lower()
58
+ results = [
59
+ b for b in bookmarks
60
+ if query_lower in b.title.lower()
61
+ or query_lower in b.category.lower()
62
+ or query_lower in b.session_id.lower()
63
+ or query_lower in b.project_path.lower()
64
+ or (b.aweswitch_profile and query_lower in b.aweswitch_profile.lower())
65
+ ]
66
+ click.echo(format_table(results))
67
+
68
+
69
+ @click.command("recent")
70
+ @click.option("-n", "--count", default=10, help="Number of recent bookmarks.")
71
+ def recent_command(count):
72
+ """Show recently bookmarked sessions."""
73
+ bookmarks = load_bookmarks()
74
+ bookmarks.sort(key=lambda b: b.bookmarked_at, reverse=True)
75
+ click.echo(format_table(bookmarks[:count]))
@@ -0,0 +1,42 @@
1
+ """Resume command."""
2
+
3
+ import click
4
+
5
+ from aweshelf.lib.resume_target import (
6
+ ResumeError,
7
+ build_resume_target,
8
+ format_resume_target,
9
+ run_resume_target,
10
+ )
11
+ from aweshelf.lib.store import find_bookmark
12
+
13
+
14
+ @click.command("resume")
15
+ @click.argument("bookmark_id")
16
+ @click.option("--profile", default=None, help="Override aweswitch profile.")
17
+ @click.option("--raw", is_flag=True, help="Skip aweswitch, use claude/codex directly.")
18
+ @click.option("--dry-run", is_flag=True, help="Print the resume command without running it.")
19
+ def resume_command(bookmark_id, profile, raw, dry_run):
20
+ """Resume a bookmarked session."""
21
+ b = find_bookmark(bookmark_id)
22
+ if b is None:
23
+ raise click.ClickException(f"bookmark not found: {bookmark_id}")
24
+
25
+ try:
26
+ target = build_resume_target(b, profile_override=profile, raw=raw)
27
+ except ResumeError as exc:
28
+ raise click.ClickException(str(exc)) from exc
29
+ if target.warning:
30
+ click.echo(f"Warning: {target.warning}", err=True)
31
+ click.echo(f"Resuming {b.id} — {b.title}")
32
+ click.echo(f" $ {format_resume_target(target)}")
33
+
34
+ if dry_run:
35
+ return
36
+
37
+ try:
38
+ run_resume_target(target)
39
+ except FileNotFoundError as exc:
40
+ raise click.ClickException(f"command not found: {target.argv[0]}") from exc
41
+ except OSError as exc:
42
+ raise click.ClickException(f"failed to run {target.argv[0]}: {exc}") from exc
@@ -0,0 +1,68 @@
1
+ """Show, edit, rm commands."""
2
+
3
+ import json
4
+
5
+ import click
6
+
7
+ from aweshelf.lib.store import find_bookmark, update_bookmark, remove_bookmark
8
+
9
+
10
+ @click.command("show")
11
+ @click.argument("bookmark_id")
12
+ @click.option("--json", "as_json", is_flag=True, help="Output as raw JSON.")
13
+ def show_command(bookmark_id, as_json):
14
+ """Show bookmark details."""
15
+ b = find_bookmark(bookmark_id)
16
+ if b is None:
17
+ raise click.ClickException(f"bookmark not found: {bookmark_id}")
18
+ if as_json:
19
+ click.echo(json.dumps(b.to_dict(), indent=2, ensure_ascii=False))
20
+ else:
21
+ click.echo(f"ID: {b.id}")
22
+ click.echo(f"Provider: {b.provider}")
23
+ click.echo(f"Session ID: {b.session_id}")
24
+ click.echo(f"Title: {b.title}")
25
+ click.echo(f"Category: {b.category or '-'}")
26
+ click.echo(f"Project: {b.project_path or '-'}")
27
+ click.echo(f"aweswitch Profile: {b.aweswitch_profile or '-'}")
28
+ click.echo(f"Bookmarked at: {b.bookmarked_at}")
29
+
30
+
31
+ @click.command("edit")
32
+ @click.argument("bookmark_id")
33
+ @click.option("-t", "--title", default=None, help="New title.")
34
+ @click.option("-c", "--category", default=None, help="New category.")
35
+ @click.option("--profile", default=None, help="New aweswitch profile.")
36
+ def edit_command(bookmark_id, title, category, profile):
37
+ """Edit a bookmark's metadata."""
38
+ fields = {}
39
+ if title is not None:
40
+ fields["title"] = title
41
+ if category is not None:
42
+ fields["category"] = category
43
+ if profile is not None:
44
+ fields["aweswitch_profile"] = profile
45
+
46
+ if not fields:
47
+ raise click.ClickException("nothing to edit; use -t, -c, or --profile")
48
+
49
+ b = update_bookmark(bookmark_id, **fields)
50
+ if b is None:
51
+ raise click.ClickException(f"bookmark not found: {bookmark_id}")
52
+ click.echo(f"Updated {b.id}")
53
+
54
+
55
+ @click.command("rm")
56
+ @click.argument("bookmark_id")
57
+ @click.option("--force", is_flag=True, help="Skip confirmation.")
58
+ def rm_command(bookmark_id, force):
59
+ """Remove a bookmark."""
60
+ b = find_bookmark(bookmark_id)
61
+ if b is None:
62
+ raise click.ClickException(f"bookmark not found: {bookmark_id}")
63
+
64
+ if not force:
65
+ click.confirm(f"Remove bookmark {b.id} ({b.title})?", abort=True)
66
+
67
+ remove_bookmark(bookmark_id)
68
+ click.echo(f"Removed {bookmark_id}")
File without changes