altergo 0.1.0__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.
- altergo-0.1.0/LICENSE +21 -0
- altergo-0.1.0/PKG-INFO +142 -0
- altergo-0.1.0/README.md +113 -0
- altergo-0.1.0/altergo.egg-info/PKG-INFO +142 -0
- altergo-0.1.0/altergo.egg-info/SOURCES.txt +9 -0
- altergo-0.1.0/altergo.egg-info/dependency_links.txt +1 -0
- altergo-0.1.0/altergo.egg-info/entry_points.txt +2 -0
- altergo-0.1.0/altergo.egg-info/top_level.txt +1 -0
- altergo-0.1.0/altergo.py +433 -0
- altergo-0.1.0/pyproject.toml +44 -0
- altergo-0.1.0/setup.cfg +4 -0
altergo-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 thepixelabs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
altergo-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: altergo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Your other Claude — switch Claude Code identities without losing a thought
|
|
5
|
+
Author: thepixelabs
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://altergo.pixelabs.net
|
|
8
|
+
Project-URL: Repository, https://github.com/thepixelabs/altergo
|
|
9
|
+
Project-URL: Issues, https://github.com/thepixelabs/altergo/issues
|
|
10
|
+
Keywords: claude,claude-code,multi-account,session-manager,cli
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Environment :: Console :: Curses
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# altergo
|
|
31
|
+
|
|
32
|
+
> Your other Claude — switch Claude Code identities without losing a thought.
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/altergo/)
|
|
35
|
+
[](https://python.org)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://github.com/thepixelabs/altergo/actions/workflows/ci.yml)
|
|
38
|
+
|
|
39
|
+
<!-- TODO: Add demo GIF here -->
|
|
40
|
+
|
|
41
|
+
## What is this?
|
|
42
|
+
|
|
43
|
+
If you have multiple Claude Code subscriptions (personal + work, two orgs, etc.), switching between them means losing access to your session history. **Altergo** fixes that — it shares session data via symlinks while keeping credentials separate, so you can pick up any conversation from either account.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install altergo
|
|
49
|
+
altergo --setup
|
|
50
|
+
altergo
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **Interactive session picker** — curses TUI with arrow keys, j/k, page up/down
|
|
56
|
+
- **Session preview** — see project name, last modified, size, and last message
|
|
57
|
+
- **Zero dependencies** — Python standard library only
|
|
58
|
+
- **Cross-platform** — macOS and Linux
|
|
59
|
+
- **Automatic setup/teardown** — one command to configure, one to undo
|
|
60
|
+
|
|
61
|
+
## How it works
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
~/.claude/ ← Your primary Claude Code account
|
|
65
|
+
├── .credentials.json ← Primary credentials (untouched)
|
|
66
|
+
├── projects/ ← Session files
|
|
67
|
+
├── settings.json
|
|
68
|
+
└── ...
|
|
69
|
+
|
|
70
|
+
~/.altergo/ ← Your alt account
|
|
71
|
+
├── .claude/
|
|
72
|
+
│ ├── .credentials.json ← Alt credentials (separate)
|
|
73
|
+
│ ├── projects/ → symlink to ~/.claude/projects/
|
|
74
|
+
│ ├── settings.json → symlink
|
|
75
|
+
│ └── ... ← All session dirs are symlinked
|
|
76
|
+
└── ...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Both accounts see the same sessions. Only credentials stay separate.
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
altergo Interactive session picker
|
|
85
|
+
altergo new Start a new session with alt credentials
|
|
86
|
+
altergo --resume <id> Resume a specific session
|
|
87
|
+
altergo --list List all sessions
|
|
88
|
+
altergo --setup First-time setup (alt home + symlinks)
|
|
89
|
+
altergo --teardown Undo setup
|
|
90
|
+
altergo --version Show version
|
|
91
|
+
altergo --help Show help
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Keyboard shortcuts (interactive picker)
|
|
95
|
+
|
|
96
|
+
| Key | Action |
|
|
97
|
+
|-----|--------|
|
|
98
|
+
| `↑` / `k` | Move up |
|
|
99
|
+
| `↓` / `j` | Move down |
|
|
100
|
+
| `PgUp` / `PgDn` | Page scroll |
|
|
101
|
+
| `g` / `G` | Jump to top / bottom |
|
|
102
|
+
| `Enter` | Resume session |
|
|
103
|
+
| `q` / `Esc` | Quit |
|
|
104
|
+
|
|
105
|
+
## Install
|
|
106
|
+
|
|
107
|
+
### pip (recommended)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install altergo
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Homebrew
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
brew install thepixelabs/tap/altergo
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Manual
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
curl -o ~/.local/bin/altergo https://raw.githubusercontent.com/thepixelabs/altergo/main/altergo.py
|
|
123
|
+
chmod +x ~/.local/bin/altergo
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
- Python 3.9+
|
|
129
|
+
- [Claude Code](https://claude.ai/code) CLI installed
|
|
130
|
+
- macOS or Linux
|
|
131
|
+
|
|
132
|
+
## Contributing
|
|
133
|
+
|
|
134
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
135
|
+
|
|
136
|
+
## Migrating from claude100-resume
|
|
137
|
+
|
|
138
|
+
If you used the previous `claude100-resume` tool with `~/claude100-home/`, your credentials and alias will not be picked up automatically. See [docs/migration.md](docs/migration.md) for step-by-step instructions.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
[MIT](LICENSE)
|
altergo-0.1.0/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# altergo
|
|
2
|
+
|
|
3
|
+
> Your other Claude — switch Claude Code identities without losing a thought.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/altergo/)
|
|
6
|
+
[](https://python.org)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://github.com/thepixelabs/altergo/actions/workflows/ci.yml)
|
|
9
|
+
|
|
10
|
+
<!-- TODO: Add demo GIF here -->
|
|
11
|
+
|
|
12
|
+
## What is this?
|
|
13
|
+
|
|
14
|
+
If you have multiple Claude Code subscriptions (personal + work, two orgs, etc.), switching between them means losing access to your session history. **Altergo** fixes that — it shares session data via symlinks while keeping credentials separate, so you can pick up any conversation from either account.
|
|
15
|
+
|
|
16
|
+
## Quick start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install altergo
|
|
20
|
+
altergo --setup
|
|
21
|
+
altergo
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- **Interactive session picker** — curses TUI with arrow keys, j/k, page up/down
|
|
27
|
+
- **Session preview** — see project name, last modified, size, and last message
|
|
28
|
+
- **Zero dependencies** — Python standard library only
|
|
29
|
+
- **Cross-platform** — macOS and Linux
|
|
30
|
+
- **Automatic setup/teardown** — one command to configure, one to undo
|
|
31
|
+
|
|
32
|
+
## How it works
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
~/.claude/ ← Your primary Claude Code account
|
|
36
|
+
├── .credentials.json ← Primary credentials (untouched)
|
|
37
|
+
├── projects/ ← Session files
|
|
38
|
+
├── settings.json
|
|
39
|
+
└── ...
|
|
40
|
+
|
|
41
|
+
~/.altergo/ ← Your alt account
|
|
42
|
+
├── .claude/
|
|
43
|
+
│ ├── .credentials.json ← Alt credentials (separate)
|
|
44
|
+
│ ├── projects/ → symlink to ~/.claude/projects/
|
|
45
|
+
│ ├── settings.json → symlink
|
|
46
|
+
│ └── ... ← All session dirs are symlinked
|
|
47
|
+
└── ...
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Both accounts see the same sessions. Only credentials stay separate.
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
altergo Interactive session picker
|
|
56
|
+
altergo new Start a new session with alt credentials
|
|
57
|
+
altergo --resume <id> Resume a specific session
|
|
58
|
+
altergo --list List all sessions
|
|
59
|
+
altergo --setup First-time setup (alt home + symlinks)
|
|
60
|
+
altergo --teardown Undo setup
|
|
61
|
+
altergo --version Show version
|
|
62
|
+
altergo --help Show help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Keyboard shortcuts (interactive picker)
|
|
66
|
+
|
|
67
|
+
| Key | Action |
|
|
68
|
+
|-----|--------|
|
|
69
|
+
| `↑` / `k` | Move up |
|
|
70
|
+
| `↓` / `j` | Move down |
|
|
71
|
+
| `PgUp` / `PgDn` | Page scroll |
|
|
72
|
+
| `g` / `G` | Jump to top / bottom |
|
|
73
|
+
| `Enter` | Resume session |
|
|
74
|
+
| `q` / `Esc` | Quit |
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
### pip (recommended)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
pip install altergo
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Homebrew
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
brew install thepixelabs/tap/altergo
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Manual
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
curl -o ~/.local/bin/altergo https://raw.githubusercontent.com/thepixelabs/altergo/main/altergo.py
|
|
94
|
+
chmod +x ~/.local/bin/altergo
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Requirements
|
|
98
|
+
|
|
99
|
+
- Python 3.9+
|
|
100
|
+
- [Claude Code](https://claude.ai/code) CLI installed
|
|
101
|
+
- macOS or Linux
|
|
102
|
+
|
|
103
|
+
## Contributing
|
|
104
|
+
|
|
105
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
106
|
+
|
|
107
|
+
## Migrating from claude100-resume
|
|
108
|
+
|
|
109
|
+
If you used the previous `claude100-resume` tool with `~/claude100-home/`, your credentials and alias will not be picked up automatically. See [docs/migration.md](docs/migration.md) for step-by-step instructions.
|
|
110
|
+
|
|
111
|
+
## License
|
|
112
|
+
|
|
113
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: altergo
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Your other Claude — switch Claude Code identities without losing a thought
|
|
5
|
+
Author: thepixelabs
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://altergo.pixelabs.net
|
|
8
|
+
Project-URL: Repository, https://github.com/thepixelabs/altergo
|
|
9
|
+
Project-URL: Issues, https://github.com/thepixelabs/altergo/issues
|
|
10
|
+
Keywords: claude,claude-code,multi-account,session-manager,cli
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Environment :: Console :: Curses
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# altergo
|
|
31
|
+
|
|
32
|
+
> Your other Claude — switch Claude Code identities without losing a thought.
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/altergo/)
|
|
35
|
+
[](https://python.org)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
[](https://github.com/thepixelabs/altergo/actions/workflows/ci.yml)
|
|
38
|
+
|
|
39
|
+
<!-- TODO: Add demo GIF here -->
|
|
40
|
+
|
|
41
|
+
## What is this?
|
|
42
|
+
|
|
43
|
+
If you have multiple Claude Code subscriptions (personal + work, two orgs, etc.), switching between them means losing access to your session history. **Altergo** fixes that — it shares session data via symlinks while keeping credentials separate, so you can pick up any conversation from either account.
|
|
44
|
+
|
|
45
|
+
## Quick start
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install altergo
|
|
49
|
+
altergo --setup
|
|
50
|
+
altergo
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Features
|
|
54
|
+
|
|
55
|
+
- **Interactive session picker** — curses TUI with arrow keys, j/k, page up/down
|
|
56
|
+
- **Session preview** — see project name, last modified, size, and last message
|
|
57
|
+
- **Zero dependencies** — Python standard library only
|
|
58
|
+
- **Cross-platform** — macOS and Linux
|
|
59
|
+
- **Automatic setup/teardown** — one command to configure, one to undo
|
|
60
|
+
|
|
61
|
+
## How it works
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
~/.claude/ ← Your primary Claude Code account
|
|
65
|
+
├── .credentials.json ← Primary credentials (untouched)
|
|
66
|
+
├── projects/ ← Session files
|
|
67
|
+
├── settings.json
|
|
68
|
+
└── ...
|
|
69
|
+
|
|
70
|
+
~/.altergo/ ← Your alt account
|
|
71
|
+
├── .claude/
|
|
72
|
+
│ ├── .credentials.json ← Alt credentials (separate)
|
|
73
|
+
│ ├── projects/ → symlink to ~/.claude/projects/
|
|
74
|
+
│ ├── settings.json → symlink
|
|
75
|
+
│ └── ... ← All session dirs are symlinked
|
|
76
|
+
└── ...
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Both accounts see the same sessions. Only credentials stay separate.
|
|
80
|
+
|
|
81
|
+
## Usage
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
altergo Interactive session picker
|
|
85
|
+
altergo new Start a new session with alt credentials
|
|
86
|
+
altergo --resume <id> Resume a specific session
|
|
87
|
+
altergo --list List all sessions
|
|
88
|
+
altergo --setup First-time setup (alt home + symlinks)
|
|
89
|
+
altergo --teardown Undo setup
|
|
90
|
+
altergo --version Show version
|
|
91
|
+
altergo --help Show help
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Keyboard shortcuts (interactive picker)
|
|
95
|
+
|
|
96
|
+
| Key | Action |
|
|
97
|
+
|-----|--------|
|
|
98
|
+
| `↑` / `k` | Move up |
|
|
99
|
+
| `↓` / `j` | Move down |
|
|
100
|
+
| `PgUp` / `PgDn` | Page scroll |
|
|
101
|
+
| `g` / `G` | Jump to top / bottom |
|
|
102
|
+
| `Enter` | Resume session |
|
|
103
|
+
| `q` / `Esc` | Quit |
|
|
104
|
+
|
|
105
|
+
## Install
|
|
106
|
+
|
|
107
|
+
### pip (recommended)
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install altergo
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Homebrew
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
brew install thepixelabs/tap/altergo
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Manual
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
curl -o ~/.local/bin/altergo https://raw.githubusercontent.com/thepixelabs/altergo/main/altergo.py
|
|
123
|
+
chmod +x ~/.local/bin/altergo
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Requirements
|
|
127
|
+
|
|
128
|
+
- Python 3.9+
|
|
129
|
+
- [Claude Code](https://claude.ai/code) CLI installed
|
|
130
|
+
- macOS or Linux
|
|
131
|
+
|
|
132
|
+
## Contributing
|
|
133
|
+
|
|
134
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
135
|
+
|
|
136
|
+
## Migrating from claude100-resume
|
|
137
|
+
|
|
138
|
+
If you used the previous `claude100-resume` tool with `~/claude100-home/`, your credentials and alias will not be picked up automatically. See [docs/migration.md](docs/migration.md) for step-by-step instructions.
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
altergo
|
altergo-0.1.0/altergo.py
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Altergo — Your other Claude.
|
|
4
|
+
|
|
5
|
+
Multi-account session manager for Claude Code. Switch between Claude Code
|
|
6
|
+
identities without losing a thought. Uses symlinks to share session data
|
|
7
|
+
and a separate HOME for alt account credentials.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
altergo [claude flags...] Launch claude with alt credentials (pass-through)
|
|
11
|
+
altergo --resume Pick a session interactively (↑/↓/j/k, Enter, q)
|
|
12
|
+
altergo --resume <id> Resume a specific session directly
|
|
13
|
+
altergo --list List recent sessions
|
|
14
|
+
altergo --setup First-time setup (alt home, symlinks)
|
|
15
|
+
altergo --teardown Remove symlinks and undo setup
|
|
16
|
+
altergo --version Show version
|
|
17
|
+
altergo -h, --help Show this help
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
altergo Start a new session (same as: claude)
|
|
21
|
+
altergo --resume Open session picker
|
|
22
|
+
altergo --dangerously-skip-permissions
|
|
23
|
+
Pass any claude flag straight through
|
|
24
|
+
|
|
25
|
+
Navigation (session picker):
|
|
26
|
+
↑/k Move up PgUp/PgDn Page scroll
|
|
27
|
+
↓/j Move down g/G Jump to top/bottom
|
|
28
|
+
Enter Resume session q/Esc Quit
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__version__ = "0.1.0"
|
|
32
|
+
|
|
33
|
+
import curses
|
|
34
|
+
import os
|
|
35
|
+
import pwd
|
|
36
|
+
import re
|
|
37
|
+
import sys
|
|
38
|
+
import subprocess
|
|
39
|
+
import json
|
|
40
|
+
from datetime import datetime
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
|
|
43
|
+
# --- Config ---
|
|
44
|
+
|
|
45
|
+
# Resolve the real home even if HOME is overridden (e.g., running as altergo)
|
|
46
|
+
_pw_home = Path(pwd.getpwuid(os.getuid()).pw_dir)
|
|
47
|
+
if not _pw_home.exists():
|
|
48
|
+
_pw_home = Path(os.environ["HOME"])
|
|
49
|
+
|
|
50
|
+
MAIN_HOME = _pw_home
|
|
51
|
+
ALT_HOME = MAIN_HOME / ".altergo"
|
|
52
|
+
MAIN_CLAUDE = MAIN_HOME / ".claude"
|
|
53
|
+
ALT_CLAUDE = ALT_HOME / ".claude"
|
|
54
|
+
|
|
55
|
+
# Directories to symlink (shared between main and alt)
|
|
56
|
+
SYMLINK_DIRS = [
|
|
57
|
+
"projects",
|
|
58
|
+
"tasks",
|
|
59
|
+
"session-env",
|
|
60
|
+
"file-history",
|
|
61
|
+
"shell-snapshots",
|
|
62
|
+
"agents",
|
|
63
|
+
"plans",
|
|
64
|
+
"cache",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
# Files to symlink
|
|
68
|
+
SYMLINK_FILES = [
|
|
69
|
+
"settings.json",
|
|
70
|
+
"CLAUDE.md",
|
|
71
|
+
"keybindings.json",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# --- Setup / Teardown ---
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def do_setup():
|
|
78
|
+
print("=== Altergo — Setup ===\n")
|
|
79
|
+
|
|
80
|
+
# 1. Create alt home
|
|
81
|
+
if not ALT_HOME.exists():
|
|
82
|
+
ALT_HOME.mkdir(parents=True)
|
|
83
|
+
print(f"Created alt home: {ALT_HOME}")
|
|
84
|
+
else:
|
|
85
|
+
print(f" Alt home exists: {ALT_HOME}")
|
|
86
|
+
|
|
87
|
+
# Ensure alt .claude dir exists
|
|
88
|
+
ALT_CLAUDE.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
# 2. Symlink directories
|
|
91
|
+
for name in SYMLINK_DIRS:
|
|
92
|
+
src = MAIN_CLAUDE / name
|
|
93
|
+
dst = ALT_CLAUDE / name
|
|
94
|
+
|
|
95
|
+
if not src.exists():
|
|
96
|
+
print(f" Skip {name}/ (not found in main)")
|
|
97
|
+
continue
|
|
98
|
+
|
|
99
|
+
if dst.is_symlink():
|
|
100
|
+
target = dst.resolve()
|
|
101
|
+
if target == src.resolve():
|
|
102
|
+
print(f" {name}/ already symlinked")
|
|
103
|
+
else:
|
|
104
|
+
print(f" Warning: {name}/ symlinked to {target} (expected {src})")
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
if dst.exists():
|
|
108
|
+
print(f" Warning: {name}/ exists as real dir — remove it first to symlink")
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
dst.symlink_to(src)
|
|
112
|
+
print(f" Symlinked {name}/")
|
|
113
|
+
|
|
114
|
+
# 3. Symlink files
|
|
115
|
+
for name in SYMLINK_FILES:
|
|
116
|
+
src = MAIN_CLAUDE / name
|
|
117
|
+
dst = ALT_CLAUDE / name
|
|
118
|
+
|
|
119
|
+
if not src.exists():
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
if dst.is_symlink():
|
|
123
|
+
print(f" {name} already symlinked")
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
if dst.exists():
|
|
127
|
+
dst.unlink()
|
|
128
|
+
|
|
129
|
+
dst.symlink_to(src)
|
|
130
|
+
print(f" Symlinked {name}")
|
|
131
|
+
|
|
132
|
+
# 4. Check credentials
|
|
133
|
+
creds = ALT_CLAUDE / ".credentials.json"
|
|
134
|
+
print()
|
|
135
|
+
if creds.exists():
|
|
136
|
+
print(" Alt account credentials found")
|
|
137
|
+
else:
|
|
138
|
+
print(" No alt account credentials found.")
|
|
139
|
+
print(" Run 'altergo new' to authenticate with your alt account.\n")
|
|
140
|
+
|
|
141
|
+
print("\nSetup complete!\n")
|
|
142
|
+
print("Usage:")
|
|
143
|
+
print(" altergo new Start a new session with alt credentials")
|
|
144
|
+
print(" altergo Interactive session picker")
|
|
145
|
+
print(" altergo --resume <session-id> Resume directly")
|
|
146
|
+
print(" altergo --list List all sessions")
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def do_teardown():
|
|
150
|
+
print("=== Altergo — Teardown ===\n")
|
|
151
|
+
|
|
152
|
+
for name in SYMLINK_DIRS:
|
|
153
|
+
dst = ALT_CLAUDE / name
|
|
154
|
+
if dst.is_symlink():
|
|
155
|
+
dst.unlink()
|
|
156
|
+
print(f" Removed symlink: {name}/")
|
|
157
|
+
|
|
158
|
+
for name in SYMLINK_FILES:
|
|
159
|
+
dst = ALT_CLAUDE / name
|
|
160
|
+
if dst.is_symlink():
|
|
161
|
+
dst.unlink()
|
|
162
|
+
print(f" Removed symlink: {name}")
|
|
163
|
+
|
|
164
|
+
print("\nTeardown complete. Alt home and credentials left intact.")
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# --- Session Discovery ---
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def get_sessions():
|
|
171
|
+
"""Find all sessions across all projects, return sorted by modification time."""
|
|
172
|
+
sessions = []
|
|
173
|
+
projects_dir = MAIN_CLAUDE / "projects"
|
|
174
|
+
|
|
175
|
+
if not projects_dir.exists():
|
|
176
|
+
return sessions
|
|
177
|
+
|
|
178
|
+
for project_dir in projects_dir.iterdir():
|
|
179
|
+
if not project_dir.is_dir():
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
project_name = project_dir.name
|
|
183
|
+
|
|
184
|
+
for f in project_dir.iterdir():
|
|
185
|
+
if f.suffix != ".jsonl" or f.parent.name == "subagents":
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
session_id = f.stem
|
|
189
|
+
mod_time = f.stat().st_mtime
|
|
190
|
+
mod_dt = datetime.fromtimestamp(mod_time)
|
|
191
|
+
size_mb = f.stat().st_size / (1024 * 1024)
|
|
192
|
+
|
|
193
|
+
# Try to extract last user message as a preview
|
|
194
|
+
preview = get_session_preview(f)
|
|
195
|
+
|
|
196
|
+
sessions.append({
|
|
197
|
+
"id": session_id,
|
|
198
|
+
"project": project_name,
|
|
199
|
+
"modified": mod_dt,
|
|
200
|
+
"size_mb": size_mb,
|
|
201
|
+
"path": f,
|
|
202
|
+
"preview": preview,
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
sessions.sort(key=lambda s: s["modified"], reverse=True)
|
|
206
|
+
return sessions
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def get_session_preview(jsonl_path):
|
|
210
|
+
"""Read the last few user messages from a session file for preview."""
|
|
211
|
+
try:
|
|
212
|
+
last_msg = ""
|
|
213
|
+
with open(jsonl_path, "rb") as f:
|
|
214
|
+
# Read last 8KB to find recent messages
|
|
215
|
+
f.seek(0, 2)
|
|
216
|
+
size = f.tell()
|
|
217
|
+
f.seek(max(0, size - 8192))
|
|
218
|
+
tail = f.read().decode("utf-8", errors="replace")
|
|
219
|
+
|
|
220
|
+
for line in tail.strip().split("\n"):
|
|
221
|
+
try:
|
|
222
|
+
obj = json.loads(line)
|
|
223
|
+
if obj.get("type") == "human" and isinstance(obj.get("message"), dict):
|
|
224
|
+
content = obj["message"].get("content", "")
|
|
225
|
+
if isinstance(content, str) and content.strip():
|
|
226
|
+
last_msg = content.strip()
|
|
227
|
+
elif isinstance(content, list):
|
|
228
|
+
for block in content:
|
|
229
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
230
|
+
last_msg = block["text"].strip()
|
|
231
|
+
except (json.JSONDecodeError, KeyError):
|
|
232
|
+
continue
|
|
233
|
+
|
|
234
|
+
return last_msg[:80] if last_msg else ""
|
|
235
|
+
except Exception:
|
|
236
|
+
return ""
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def format_project_name(encoded):
|
|
240
|
+
"""Convert encoded project path back to readable name."""
|
|
241
|
+
# -Users-netz-Documents-git-dispatch → dispatch
|
|
242
|
+
parts = encoded.strip("-").split("-")
|
|
243
|
+
# Return last meaningful part
|
|
244
|
+
return parts[-1] if parts else encoded
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# --- Interactive Menu ---
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def interactive_picker(sessions):
|
|
251
|
+
"""Arrow-key driven session picker using curses."""
|
|
252
|
+
if not sessions:
|
|
253
|
+
print("No sessions found.")
|
|
254
|
+
sys.exit(1)
|
|
255
|
+
|
|
256
|
+
selected = curses.wrapper(_draw_picker, sessions)
|
|
257
|
+
return selected
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _draw_picker(stdscr, sessions):
|
|
261
|
+
curses.curs_set(0)
|
|
262
|
+
curses.use_default_colors()
|
|
263
|
+
|
|
264
|
+
# Init color pairs
|
|
265
|
+
curses.init_pair(1, curses.COLOR_BLACK, curses.COLOR_CYAN) # selected
|
|
266
|
+
curses.init_pair(2, curses.COLOR_CYAN, -1) # header
|
|
267
|
+
curses.init_pair(3, curses.COLOR_YELLOW, -1) # project name
|
|
268
|
+
curses.init_pair(4, curses.COLOR_WHITE, -1) # session id
|
|
269
|
+
curses.init_pair(5, curses.COLOR_GREEN, -1) # time
|
|
270
|
+
|
|
271
|
+
current = 0
|
|
272
|
+
scroll_offset = 0
|
|
273
|
+
|
|
274
|
+
while True:
|
|
275
|
+
stdscr.clear()
|
|
276
|
+
max_y, max_x = stdscr.getmaxyx()
|
|
277
|
+
|
|
278
|
+
# Header
|
|
279
|
+
header = " Altergo — Pick a session (↑/↓ navigate, Enter select, q quit)"
|
|
280
|
+
stdscr.attron(curses.color_pair(2) | curses.A_BOLD)
|
|
281
|
+
stdscr.addnstr(0, 0, header.ljust(max_x), max_x - 1)
|
|
282
|
+
stdscr.attroff(curses.color_pair(2) | curses.A_BOLD)
|
|
283
|
+
|
|
284
|
+
# Column headers
|
|
285
|
+
col_header = f" {'Project':<20} {'Modified':<18} {'Size':>6} {'Last message'}"
|
|
286
|
+
stdscr.attron(curses.A_DIM)
|
|
287
|
+
stdscr.addnstr(2, 0, col_header[:max_x - 1], max_x - 1)
|
|
288
|
+
stdscr.attroff(curses.A_DIM)
|
|
289
|
+
|
|
290
|
+
# Visible area
|
|
291
|
+
visible_rows = max_y - 5 # header + col header + footer + padding
|
|
292
|
+
if visible_rows < 1:
|
|
293
|
+
visible_rows = 1
|
|
294
|
+
|
|
295
|
+
# Adjust scroll
|
|
296
|
+
if current < scroll_offset:
|
|
297
|
+
scroll_offset = current
|
|
298
|
+
elif current >= scroll_offset + visible_rows:
|
|
299
|
+
scroll_offset = current - visible_rows + 1
|
|
300
|
+
|
|
301
|
+
# Draw sessions
|
|
302
|
+
for i in range(visible_rows):
|
|
303
|
+
idx = scroll_offset + i
|
|
304
|
+
if idx >= len(sessions):
|
|
305
|
+
break
|
|
306
|
+
|
|
307
|
+
s = sessions[idx]
|
|
308
|
+
row = i + 3 # after header rows
|
|
309
|
+
|
|
310
|
+
project = format_project_name(s["project"])
|
|
311
|
+
modified = s["modified"].strftime("%Y-%m-%d %H:%M")
|
|
312
|
+
size = f"{s['size_mb']:.1f}MB"
|
|
313
|
+
preview = s["preview"]
|
|
314
|
+
|
|
315
|
+
# Truncate preview to fit
|
|
316
|
+
preview_width = max(0, max_x - 50)
|
|
317
|
+
if len(preview) > preview_width:
|
|
318
|
+
preview = preview[:preview_width - 1] + "…"
|
|
319
|
+
|
|
320
|
+
line = f" {project:<20} {modified:<18} {size:>6} {preview}"
|
|
321
|
+
|
|
322
|
+
if idx == current:
|
|
323
|
+
stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
|
|
324
|
+
stdscr.addnstr(row, 0, f"▸ {line[2:]}"[:max_x - 1].ljust(max_x - 1), max_x - 1)
|
|
325
|
+
stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
|
|
326
|
+
else:
|
|
327
|
+
stdscr.addnstr(row, 0, line[:max_x - 1], max_x - 1)
|
|
328
|
+
|
|
329
|
+
# Footer — show session ID of current selection
|
|
330
|
+
footer_row = max_y - 2
|
|
331
|
+
if current < len(sessions):
|
|
332
|
+
sid = sessions[current]["id"]
|
|
333
|
+
footer = f" Session: {sid}"
|
|
334
|
+
stdscr.attron(curses.A_DIM)
|
|
335
|
+
stdscr.addnstr(footer_row, 0, footer[:max_x - 1], max_x - 1)
|
|
336
|
+
stdscr.attroff(curses.A_DIM)
|
|
337
|
+
|
|
338
|
+
count_info = f" {current + 1}/{len(sessions)} sessions"
|
|
339
|
+
stdscr.attron(curses.A_DIM)
|
|
340
|
+
stdscr.addnstr(footer_row + 1, 0, count_info[:max_x - 1], max_x - 1)
|
|
341
|
+
stdscr.attroff(curses.A_DIM)
|
|
342
|
+
|
|
343
|
+
stdscr.refresh()
|
|
344
|
+
|
|
345
|
+
# Input
|
|
346
|
+
key = stdscr.getch()
|
|
347
|
+
|
|
348
|
+
if key == curses.KEY_UP or key == ord("k"):
|
|
349
|
+
current = max(0, current - 1)
|
|
350
|
+
elif key == curses.KEY_DOWN or key == ord("j"):
|
|
351
|
+
current = min(len(sessions) - 1, current + 1)
|
|
352
|
+
elif key == curses.KEY_PPAGE: # Page Up
|
|
353
|
+
current = max(0, current - visible_rows)
|
|
354
|
+
elif key == curses.KEY_NPAGE: # Page Down
|
|
355
|
+
current = min(len(sessions) - 1, current + visible_rows)
|
|
356
|
+
elif key == ord("g"): # Home
|
|
357
|
+
current = 0
|
|
358
|
+
elif key == ord("G"): # End
|
|
359
|
+
current = len(sessions) - 1
|
|
360
|
+
elif key in (curses.KEY_ENTER, 10, 13):
|
|
361
|
+
return sessions[current]
|
|
362
|
+
elif key in (ord("q"), 27): # q or Escape
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
# --- Launch ---
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def launch_claude(args=None):
|
|
370
|
+
"""Launch claude with alt HOME, passing args through unchanged."""
|
|
371
|
+
env = os.environ.copy()
|
|
372
|
+
env["HOME"] = str(ALT_HOME)
|
|
373
|
+
cmd = ["claude"] + (args or [])
|
|
374
|
+
os.execvpe("claude", cmd, env)
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# --- Main ---
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def main():
|
|
381
|
+
args = sys.argv[1:]
|
|
382
|
+
|
|
383
|
+
# ── Altergo-owned commands (not passed to claude) ──────────────────────────
|
|
384
|
+
|
|
385
|
+
if args and args[0] in ("-h", "--help"):
|
|
386
|
+
print(__doc__)
|
|
387
|
+
sys.exit(0)
|
|
388
|
+
|
|
389
|
+
if args and args[0] == "--version":
|
|
390
|
+
print(f"altergo {__version__}")
|
|
391
|
+
sys.exit(0)
|
|
392
|
+
|
|
393
|
+
if args and args[0] == "--setup":
|
|
394
|
+
do_setup()
|
|
395
|
+
sys.exit(0)
|
|
396
|
+
|
|
397
|
+
if args and args[0] == "--teardown":
|
|
398
|
+
do_teardown()
|
|
399
|
+
sys.exit(0)
|
|
400
|
+
|
|
401
|
+
if args and args[0] == "--list":
|
|
402
|
+
sessions = get_sessions()
|
|
403
|
+
if not sessions:
|
|
404
|
+
print("No sessions found.")
|
|
405
|
+
sys.exit(0)
|
|
406
|
+
print(f"{'Project':<20} {'Modified':<18} {'Size':>6} Session ID")
|
|
407
|
+
print("-" * 80)
|
|
408
|
+
for s in sessions[:30]:
|
|
409
|
+
project = format_project_name(s["project"])
|
|
410
|
+
modified = s["modified"].strftime("%Y-%m-%d %H:%M")
|
|
411
|
+
size = f"{s['size_mb']:.1f}MB"
|
|
412
|
+
print(f"{project:<20} {modified:<18} {size:>6} {s['id']}")
|
|
413
|
+
sys.exit(0)
|
|
414
|
+
|
|
415
|
+
# --resume with no ID → open interactive picker
|
|
416
|
+
if args and args[0] == "--resume" and len(args) == 1:
|
|
417
|
+
sessions = get_sessions()
|
|
418
|
+
selected = interactive_picker(sessions)
|
|
419
|
+
if selected:
|
|
420
|
+
launch_claude(["--resume", selected["id"]])
|
|
421
|
+
else:
|
|
422
|
+
print("Cancelled.")
|
|
423
|
+
sys.exit(0)
|
|
424
|
+
|
|
425
|
+
# ── Everything else → pass straight through to claude with alt HOME ────────
|
|
426
|
+
# altergo → claude
|
|
427
|
+
# altergo --resume x → claude --resume x
|
|
428
|
+
# altergo --dangerously-skip-permissions → claude --dangerously-skip-permissions
|
|
429
|
+
launch_claude(args)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
if __name__ == "__main__":
|
|
433
|
+
main()
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "altergo"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Your other Claude — switch Claude Code identities without losing a thought"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [{ name = "thepixelabs" }]
|
|
13
|
+
keywords = ["claude", "claude-code", "multi-account", "session-manager", "cli"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Environment :: Console :: Curses",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Operating System :: MacOS",
|
|
20
|
+
"Operating System :: POSIX :: Linux",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
"Topic :: Utilities",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
altergo = "altergo:main"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://altergo.pixelabs.net"
|
|
36
|
+
Repository = "https://github.com/thepixelabs/altergo"
|
|
37
|
+
Issues = "https://github.com/thepixelabs/altergo/issues"
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
target-version = "py39"
|
|
41
|
+
line-length = 120
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint]
|
|
44
|
+
select = ["E", "F", "W", "I"]
|
altergo-0.1.0/setup.cfg
ADDED