sessionfs 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.
- sessionfs-0.1.0/.gitignore +37 -0
- sessionfs-0.1.0/PKG-INFO +213 -0
- sessionfs-0.1.0/README.md +155 -0
- sessionfs-0.1.0/pyproject.toml +95 -0
- sessionfs-0.1.0/src/sessionfs/__init__.py +3 -0
- sessionfs-0.1.0/src/sessionfs/audit.py +153 -0
- sessionfs-0.1.0/src/sessionfs/cli/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/cli/cmd_admin.py +60 -0
- sessionfs-0.1.0/src/sessionfs/cli/cmd_cloud.py +522 -0
- sessionfs-0.1.0/src/sessionfs/cli/cmd_config.py +117 -0
- sessionfs-0.1.0/src/sessionfs/cli/cmd_daemon.py +161 -0
- sessionfs-0.1.0/src/sessionfs/cli/cmd_io.py +137 -0
- sessionfs-0.1.0/src/sessionfs/cli/cmd_ops.py +230 -0
- sessionfs-0.1.0/src/sessionfs/cli/cmd_sessions.py +321 -0
- sessionfs-0.1.0/src/sessionfs/cli/common.py +84 -0
- sessionfs-0.1.0/src/sessionfs/cli/cost.py +77 -0
- sessionfs-0.1.0/src/sessionfs/cli/main.py +58 -0
- sessionfs-0.1.0/src/sessionfs/cli/sfs_to_cc.py +459 -0
- sessionfs-0.1.0/src/sessionfs/cli/sfs_to_md.py +125 -0
- sessionfs-0.1.0/src/sessionfs/cli/titles.py +159 -0
- sessionfs-0.1.0/src/sessionfs/converters/__init__.py +0 -0
- sessionfs-0.1.0/src/sessionfs/converters/codex_injector.py +162 -0
- sessionfs-0.1.0/src/sessionfs/converters/cursor_to_sfs.py +324 -0
- sessionfs-0.1.0/src/sessionfs/converters/gemini_injector.py +92 -0
- sessionfs-0.1.0/src/sessionfs/converters/gemini_to_sfs.py +269 -0
- sessionfs-0.1.0/src/sessionfs/converters/sfs_to_codex.py +383 -0
- sessionfs-0.1.0/src/sessionfs/converters/sfs_to_gemini.py +204 -0
- sessionfs-0.1.0/src/sessionfs/daemon/__init__.py +0 -0
- sessionfs-0.1.0/src/sessionfs/daemon/config.py +137 -0
- sessionfs-0.1.0/src/sessionfs/daemon/main.py +393 -0
- sessionfs-0.1.0/src/sessionfs/daemon/status.py +66 -0
- sessionfs-0.1.0/src/sessionfs/security/__init__.py +0 -0
- sessionfs-0.1.0/src/sessionfs/security/secrets.py +223 -0
- sessionfs-0.1.0/src/sessionfs/server/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/server/app.py +95 -0
- sessionfs-0.1.0/src/sessionfs/server/auth/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/server/auth/dependencies.py +70 -0
- sessionfs-0.1.0/src/sessionfs/server/auth/keys.py +24 -0
- sessionfs-0.1.0/src/sessionfs/server/auth/rate_limit.py +36 -0
- sessionfs-0.1.0/src/sessionfs/server/config.py +28 -0
- sessionfs-0.1.0/src/sessionfs/server/db/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/server/db/engine.py +40 -0
- sessionfs-0.1.0/src/sessionfs/server/db/migrations/env.py +70 -0
- sessionfs-0.1.0/src/sessionfs/server/db/migrations/versions/001_initial_schema.py +78 -0
- sessionfs-0.1.0/src/sessionfs/server/db/models.py +77 -0
- sessionfs-0.1.0/src/sessionfs/server/errors.py +48 -0
- sessionfs-0.1.0/src/sessionfs/server/routes/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/server/routes/auth.py +132 -0
- sessionfs-0.1.0/src/sessionfs/server/routes/health.py +12 -0
- sessionfs-0.1.0/src/sessionfs/server/routes/sessions.py +822 -0
- sessionfs-0.1.0/src/sessionfs/server/schemas/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/server/schemas/auth.py +37 -0
- sessionfs-0.1.0/src/sessionfs/server/schemas/errors.py +17 -0
- sessionfs-0.1.0/src/sessionfs/server/schemas/sessions.py +104 -0
- sessionfs-0.1.0/src/sessionfs/server/storage/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/server/storage/base.py +15 -0
- sessionfs-0.1.0/src/sessionfs/server/storage/local.py +41 -0
- sessionfs-0.1.0/src/sessionfs/server/storage/s3.py +49 -0
- sessionfs-0.1.0/src/sessionfs/session_id.py +32 -0
- sessionfs-0.1.0/src/sessionfs/spec/__init__.py +0 -0
- sessionfs-0.1.0/src/sessionfs/spec/convert_cc.py +634 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/complete/manifest.json +33 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/complete/messages.jsonl +8 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/complete/tools.json +9 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/complete/workspace.json +33 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/minimal/manifest.json +28 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/minimal/messages.jsonl +3 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/manifest.json +33 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/messages.jsonl +7 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/tools.json +7 -0
- sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/workspace.json +24 -0
- sessionfs-0.1.0/src/sessionfs/spec/schemas/manifest.schema.json +287 -0
- sessionfs-0.1.0/src/sessionfs/spec/schemas/message.schema.json +291 -0
- sessionfs-0.1.0/src/sessionfs/spec/schemas/tools.schema.json +138 -0
- sessionfs-0.1.0/src/sessionfs/spec/schemas/workspace.schema.json +152 -0
- sessionfs-0.1.0/src/sessionfs/spec/validate.py +353 -0
- sessionfs-0.1.0/src/sessionfs/store/__init__.py +0 -0
- sessionfs-0.1.0/src/sessionfs/store/index.py +230 -0
- sessionfs-0.1.0/src/sessionfs/store/local.py +147 -0
- sessionfs-0.1.0/src/sessionfs/sync/__init__.py +1 -0
- sessionfs-0.1.0/src/sessionfs/sync/archive.py +73 -0
- sessionfs-0.1.0/src/sessionfs/sync/client.py +313 -0
- sessionfs-0.1.0/src/sessionfs/utils/__init__.py +0 -0
- sessionfs-0.1.0/src/sessionfs/utils/title_utils.py +207 -0
- sessionfs-0.1.0/src/sessionfs/watchers/__init__.py +0 -0
- sessionfs-0.1.0/src/sessionfs/watchers/base.py +71 -0
- sessionfs-0.1.0/src/sessionfs/watchers/claude_code.py +641 -0
- sessionfs-0.1.0/src/sessionfs/watchers/codex.py +573 -0
- sessionfs-0.1.0/src/sessionfs/watchers/cursor.py +202 -0
- sessionfs-0.1.0/src/sessionfs/watchers/gemini.py +206 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# Node
|
|
12
|
+
node_modules/
|
|
13
|
+
.next/
|
|
14
|
+
|
|
15
|
+
# IDE
|
|
16
|
+
.vscode/
|
|
17
|
+
.idea/
|
|
18
|
+
*.swp
|
|
19
|
+
*.swo
|
|
20
|
+
|
|
21
|
+
# OS
|
|
22
|
+
.DS_Store
|
|
23
|
+
Thumbs.db
|
|
24
|
+
|
|
25
|
+
# Environment
|
|
26
|
+
.env
|
|
27
|
+
.env.local
|
|
28
|
+
.env.*.local
|
|
29
|
+
|
|
30
|
+
# SessionFS local data (never commit user sessions)
|
|
31
|
+
.sessionfs/
|
|
32
|
+
|
|
33
|
+
# Internal — never commit to main
|
|
34
|
+
docs/security/
|
|
35
|
+
src/spikes/
|
|
36
|
+
.agents/
|
|
37
|
+
DOGFOOD.md
|
sessionfs-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sessionfs
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Capture, sync, and resume AI coding sessions across Claude Code, Codex, Gemini CLI, and Cursor.
|
|
5
|
+
Project-URL: Homepage, https://sessionfs.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/sessionfs/sessionfs
|
|
7
|
+
Project-URL: Documentation, https://sessionfs.dev/docs
|
|
8
|
+
Project-URL: Issues, https://github.com/sessionfs/sessionfs/issues
|
|
9
|
+
Author: SessionFS Contributors
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: agents,ai,claude,codex,cursor,gemini,sessions,sync
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Software Development
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: httpx<1.0,>=0.27
|
|
26
|
+
Requires-Dist: jsonschema<5.0,>=4.20
|
|
27
|
+
Requires-Dist: pydantic<3.0,>=2.0
|
|
28
|
+
Requires-Dist: rich<14.0,>=13.0
|
|
29
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
30
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
31
|
+
Requires-Dist: watchdog<6.0,>=4.0
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: aiosqlite<1.0,>=0.20; extra == 'dev'
|
|
34
|
+
Requires-Dist: alembic<2.0,>=1.13; extra == 'dev'
|
|
35
|
+
Requires-Dist: asyncpg<1.0,>=0.29; extra == 'dev'
|
|
36
|
+
Requires-Dist: boto3<2.0,>=1.34; extra == 'dev'
|
|
37
|
+
Requires-Dist: build<2.0,>=1.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: fastapi<1.0,>=0.110; extra == 'dev'
|
|
39
|
+
Requires-Dist: httpx<1.0,>=0.27; extra == 'dev'
|
|
40
|
+
Requires-Dist: mypy<2.0,>=1.8; extra == 'dev'
|
|
41
|
+
Requires-Dist: pydantic-settings<3.0,>=2.0; extra == 'dev'
|
|
42
|
+
Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == 'dev'
|
|
43
|
+
Requires-Dist: pytest<9.0,>=8.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: python-multipart>=0.0.9; extra == 'dev'
|
|
45
|
+
Requires-Dist: ruff<1.0,>=0.3; extra == 'dev'
|
|
46
|
+
Requires-Dist: sqlalchemy[asyncio]<3.0,>=2.0; extra == 'dev'
|
|
47
|
+
Requires-Dist: uvicorn[standard]<1.0,>=0.27; extra == 'dev'
|
|
48
|
+
Provides-Extra: server
|
|
49
|
+
Requires-Dist: alembic<2.0,>=1.13; extra == 'server'
|
|
50
|
+
Requires-Dist: asyncpg<1.0,>=0.29; extra == 'server'
|
|
51
|
+
Requires-Dist: boto3<2.0,>=1.34; extra == 'server'
|
|
52
|
+
Requires-Dist: fastapi<1.0,>=0.110; extra == 'server'
|
|
53
|
+
Requires-Dist: pydantic-settings<3.0,>=2.0; extra == 'server'
|
|
54
|
+
Requires-Dist: python-multipart>=0.0.9; extra == 'server'
|
|
55
|
+
Requires-Dist: sqlalchemy[asyncio]<3.0,>=2.0; extra == 'server'
|
|
56
|
+
Requires-Dist: uvicorn[standard]<1.0,>=0.27; extra == 'server'
|
|
57
|
+
Description-Content-Type: text/markdown
|
|
58
|
+
|
|
59
|
+
# SessionFS
|
|
60
|
+
|
|
61
|
+
**Stop re-prompting. Start resuming.**
|
|
62
|
+
|
|
63
|
+
SessionFS captures your AI coding sessions and makes them portable across tools and teammates.
|
|
64
|
+
|
|
65
|
+
Start a session in Claude Code, resume it in Codex. Push a session to the cloud, your teammate pulls it with full context — conversation history, workspace state, tool configs, and token usage. No copy-pasting. No re-explaining.
|
|
66
|
+
|
|
67
|
+
## Supported Tools
|
|
68
|
+
|
|
69
|
+
| Tool | Capture | Resume |
|
|
70
|
+
|------|---------|--------|
|
|
71
|
+
| Claude Code | Yes | Yes |
|
|
72
|
+
| Codex CLI | Yes | Yes |
|
|
73
|
+
| Gemini CLI | Yes | Yes |
|
|
74
|
+
| Cursor IDE | Yes | Capture-only |
|
|
75
|
+
|
|
76
|
+
## Quick Install
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install sessionfs
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Requires Python 3.10+. Installs two commands: `sfs` (CLI) and `sfsd` (daemon).
|
|
83
|
+
|
|
84
|
+
## Quick Start
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Start the daemon — it watches your tools automatically
|
|
88
|
+
sfs daemon start
|
|
89
|
+
|
|
90
|
+
# Use Claude Code, Codex, Gemini, or Cursor normally
|
|
91
|
+
# Sessions are captured in the background
|
|
92
|
+
|
|
93
|
+
# List captured sessions across all tools
|
|
94
|
+
sfs list
|
|
95
|
+
|
|
96
|
+
# Resume a Claude Code session in Codex
|
|
97
|
+
sfs resume ses_abc123 --in codex
|
|
98
|
+
|
|
99
|
+
# Or hand it off to a teammate
|
|
100
|
+
sfs push ses_abc123
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
See the full [Quickstart Guide](docs/quickstart.md) for detailed steps.
|
|
104
|
+
|
|
105
|
+
## How It Works
|
|
106
|
+
|
|
107
|
+
The `sfsd` daemon uses filesystem events (fsevents on macOS, inotify on Linux) to watch native AI tool session storage. When it detects new or updated sessions, it converts them into the `.sfs` format — a portable directory containing `manifest.json`, `messages.jsonl`, `workspace.json`, and `tools.json`.
|
|
108
|
+
|
|
109
|
+
Each tool has its own watcher:
|
|
110
|
+
- **Claude Code** — watches `~/.claude/projects/` JSONL files
|
|
111
|
+
- **Codex CLI** — watches `~/.codex/sessions/` rollout files, reads SQLite index
|
|
112
|
+
- **Gemini CLI** — watches `~/.gemini/tmp/*/chats/` JSON sessions
|
|
113
|
+
- **Cursor IDE** — reads `state.vscdb` SQLite database (capture-only, no write-back)
|
|
114
|
+
|
|
115
|
+
Sessions are indexed locally for fast browsing via the CLI. Cloud sync is opt-in; the daemon defaults to local-only.
|
|
116
|
+
|
|
117
|
+
## Commands
|
|
118
|
+
|
|
119
|
+
| Command | Description |
|
|
120
|
+
|---------|-------------|
|
|
121
|
+
| `sfs list` | List captured sessions with filtering and sorting |
|
|
122
|
+
| `sfs show <id>` | Show session details, messages, and cost estimates |
|
|
123
|
+
| `sfs resume <id> [--in TOOL]` | Resume a session in any supported tool |
|
|
124
|
+
| `sfs fork <id>` | Fork a session into a new independent session |
|
|
125
|
+
| `sfs checkpoint <id>` | Create a named checkpoint of a session |
|
|
126
|
+
| `sfs export <id>` | Export as `.sfs`, markdown, or Claude Code format |
|
|
127
|
+
| `sfs import` | Import sessions from any supported tool |
|
|
128
|
+
| `sfs push <id>` | Push a session to the cloud |
|
|
129
|
+
| `sfs pull <id>` | Pull a session from the cloud |
|
|
130
|
+
| `sfs daemon start\|stop\|status\|logs` | Manage the background daemon |
|
|
131
|
+
| `sfs config show\|set` | Manage configuration |
|
|
132
|
+
| `sfs admin reindex` | Re-extract metadata for all cloud sessions |
|
|
133
|
+
|
|
134
|
+
See the full [CLI Reference](docs/cli-reference.md) for options and examples.
|
|
135
|
+
|
|
136
|
+
## Cross-Tool Resume
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
# Start in Claude Code, resume in Codex
|
|
140
|
+
sfs resume ses_abc123 --in codex
|
|
141
|
+
|
|
142
|
+
# Start in Gemini, resume in Claude Code
|
|
143
|
+
sfs resume ses_def456 --in claude-code
|
|
144
|
+
|
|
145
|
+
# Cursor sessions can be resumed in any other tool
|
|
146
|
+
sfs resume ses_ghi789 --in gemini
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
SessionFS converts between native formats automatically — message roles, tool calls, thinking blocks, and workspace state are mapped across tools.
|
|
150
|
+
|
|
151
|
+
## Cloud Sync (Optional)
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
# Create an account
|
|
155
|
+
sfs auth signup --url https://api.sessionfs.dev
|
|
156
|
+
|
|
157
|
+
# Push a session
|
|
158
|
+
sfs push <session_id>
|
|
159
|
+
|
|
160
|
+
# Pull on another machine
|
|
161
|
+
sfs pull <session_id>
|
|
162
|
+
sfs resume <session_id>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
See the [Sync Guide](docs/sync-guide.md) for setup, conflict handling, and self-hosted options.
|
|
166
|
+
|
|
167
|
+
## Self-Hosted Server
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
docker compose up -d
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Starts the SessionFS API server, PostgreSQL, and web dashboard. See the [Sync Guide](docs/sync-guide.md#self-hosted) for full configuration.
|
|
174
|
+
|
|
175
|
+
## Web Dashboard
|
|
176
|
+
|
|
177
|
+
A browser-based interface for browsing and managing synced sessions. Accessible at `http://localhost:8000` when running the self-hosted server.
|
|
178
|
+
|
|
179
|
+
## Session Format
|
|
180
|
+
|
|
181
|
+
Sessions are stored as `.sfs` directories:
|
|
182
|
+
- `manifest.json` — identity, provenance, model info, stats
|
|
183
|
+
- `messages.jsonl` — conversation history with content blocks
|
|
184
|
+
- `workspace.json` — git state, files, environment
|
|
185
|
+
- `tools.json` — tool definitions and shell context
|
|
186
|
+
|
|
187
|
+
All file paths are relative to workspace root. Sessions are append-only — conflict resolution appends both sides rather than merging.
|
|
188
|
+
|
|
189
|
+
## Status
|
|
190
|
+
|
|
191
|
+
**v0.1.0 — Public Beta.** 429 tests passing.
|
|
192
|
+
|
|
193
|
+
What works today:
|
|
194
|
+
- Four-tool session capture (Claude Code, Codex, Gemini, Cursor)
|
|
195
|
+
- Cross-tool resume between Claude Code, Codex, and Gemini
|
|
196
|
+
- Browse, inspect, export, fork, and checkpoint sessions
|
|
197
|
+
- Cloud sync with push/pull and ETag conflict detection
|
|
198
|
+
- Self-hosted API server with auth, PostgreSQL, and S3/local storage
|
|
199
|
+
- Web dashboard for session management
|
|
200
|
+
- 12 security controls including secret detection, path traversal protection, and audit logging
|
|
201
|
+
|
|
202
|
+
On the roadmap:
|
|
203
|
+
- Team handoff workflows with notifications
|
|
204
|
+
- VS Code extension
|
|
205
|
+
- Additional tool watchers
|
|
206
|
+
|
|
207
|
+
## Contributing
|
|
208
|
+
|
|
209
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and PR guidelines.
|
|
210
|
+
|
|
211
|
+
## License
|
|
212
|
+
|
|
213
|
+
Apache 2.0
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# SessionFS
|
|
2
|
+
|
|
3
|
+
**Stop re-prompting. Start resuming.**
|
|
4
|
+
|
|
5
|
+
SessionFS captures your AI coding sessions and makes them portable across tools and teammates.
|
|
6
|
+
|
|
7
|
+
Start a session in Claude Code, resume it in Codex. Push a session to the cloud, your teammate pulls it with full context — conversation history, workspace state, tool configs, and token usage. No copy-pasting. No re-explaining.
|
|
8
|
+
|
|
9
|
+
## Supported Tools
|
|
10
|
+
|
|
11
|
+
| Tool | Capture | Resume |
|
|
12
|
+
|------|---------|--------|
|
|
13
|
+
| Claude Code | Yes | Yes |
|
|
14
|
+
| Codex CLI | Yes | Yes |
|
|
15
|
+
| Gemini CLI | Yes | Yes |
|
|
16
|
+
| Cursor IDE | Yes | Capture-only |
|
|
17
|
+
|
|
18
|
+
## Quick Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install sessionfs
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Requires Python 3.10+. Installs two commands: `sfs` (CLI) and `sfsd` (daemon).
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Start the daemon — it watches your tools automatically
|
|
30
|
+
sfs daemon start
|
|
31
|
+
|
|
32
|
+
# Use Claude Code, Codex, Gemini, or Cursor normally
|
|
33
|
+
# Sessions are captured in the background
|
|
34
|
+
|
|
35
|
+
# List captured sessions across all tools
|
|
36
|
+
sfs list
|
|
37
|
+
|
|
38
|
+
# Resume a Claude Code session in Codex
|
|
39
|
+
sfs resume ses_abc123 --in codex
|
|
40
|
+
|
|
41
|
+
# Or hand it off to a teammate
|
|
42
|
+
sfs push ses_abc123
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
See the full [Quickstart Guide](docs/quickstart.md) for detailed steps.
|
|
46
|
+
|
|
47
|
+
## How It Works
|
|
48
|
+
|
|
49
|
+
The `sfsd` daemon uses filesystem events (fsevents on macOS, inotify on Linux) to watch native AI tool session storage. When it detects new or updated sessions, it converts them into the `.sfs` format — a portable directory containing `manifest.json`, `messages.jsonl`, `workspace.json`, and `tools.json`.
|
|
50
|
+
|
|
51
|
+
Each tool has its own watcher:
|
|
52
|
+
- **Claude Code** — watches `~/.claude/projects/` JSONL files
|
|
53
|
+
- **Codex CLI** — watches `~/.codex/sessions/` rollout files, reads SQLite index
|
|
54
|
+
- **Gemini CLI** — watches `~/.gemini/tmp/*/chats/` JSON sessions
|
|
55
|
+
- **Cursor IDE** — reads `state.vscdb` SQLite database (capture-only, no write-back)
|
|
56
|
+
|
|
57
|
+
Sessions are indexed locally for fast browsing via the CLI. Cloud sync is opt-in; the daemon defaults to local-only.
|
|
58
|
+
|
|
59
|
+
## Commands
|
|
60
|
+
|
|
61
|
+
| Command | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `sfs list` | List captured sessions with filtering and sorting |
|
|
64
|
+
| `sfs show <id>` | Show session details, messages, and cost estimates |
|
|
65
|
+
| `sfs resume <id> [--in TOOL]` | Resume a session in any supported tool |
|
|
66
|
+
| `sfs fork <id>` | Fork a session into a new independent session |
|
|
67
|
+
| `sfs checkpoint <id>` | Create a named checkpoint of a session |
|
|
68
|
+
| `sfs export <id>` | Export as `.sfs`, markdown, or Claude Code format |
|
|
69
|
+
| `sfs import` | Import sessions from any supported tool |
|
|
70
|
+
| `sfs push <id>` | Push a session to the cloud |
|
|
71
|
+
| `sfs pull <id>` | Pull a session from the cloud |
|
|
72
|
+
| `sfs daemon start\|stop\|status\|logs` | Manage the background daemon |
|
|
73
|
+
| `sfs config show\|set` | Manage configuration |
|
|
74
|
+
| `sfs admin reindex` | Re-extract metadata for all cloud sessions |
|
|
75
|
+
|
|
76
|
+
See the full [CLI Reference](docs/cli-reference.md) for options and examples.
|
|
77
|
+
|
|
78
|
+
## Cross-Tool Resume
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Start in Claude Code, resume in Codex
|
|
82
|
+
sfs resume ses_abc123 --in codex
|
|
83
|
+
|
|
84
|
+
# Start in Gemini, resume in Claude Code
|
|
85
|
+
sfs resume ses_def456 --in claude-code
|
|
86
|
+
|
|
87
|
+
# Cursor sessions can be resumed in any other tool
|
|
88
|
+
sfs resume ses_ghi789 --in gemini
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
SessionFS converts between native formats automatically — message roles, tool calls, thinking blocks, and workspace state are mapped across tools.
|
|
92
|
+
|
|
93
|
+
## Cloud Sync (Optional)
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Create an account
|
|
97
|
+
sfs auth signup --url https://api.sessionfs.dev
|
|
98
|
+
|
|
99
|
+
# Push a session
|
|
100
|
+
sfs push <session_id>
|
|
101
|
+
|
|
102
|
+
# Pull on another machine
|
|
103
|
+
sfs pull <session_id>
|
|
104
|
+
sfs resume <session_id>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
See the [Sync Guide](docs/sync-guide.md) for setup, conflict handling, and self-hosted options.
|
|
108
|
+
|
|
109
|
+
## Self-Hosted Server
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
docker compose up -d
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Starts the SessionFS API server, PostgreSQL, and web dashboard. See the [Sync Guide](docs/sync-guide.md#self-hosted) for full configuration.
|
|
116
|
+
|
|
117
|
+
## Web Dashboard
|
|
118
|
+
|
|
119
|
+
A browser-based interface for browsing and managing synced sessions. Accessible at `http://localhost:8000` when running the self-hosted server.
|
|
120
|
+
|
|
121
|
+
## Session Format
|
|
122
|
+
|
|
123
|
+
Sessions are stored as `.sfs` directories:
|
|
124
|
+
- `manifest.json` — identity, provenance, model info, stats
|
|
125
|
+
- `messages.jsonl` — conversation history with content blocks
|
|
126
|
+
- `workspace.json` — git state, files, environment
|
|
127
|
+
- `tools.json` — tool definitions and shell context
|
|
128
|
+
|
|
129
|
+
All file paths are relative to workspace root. Sessions are append-only — conflict resolution appends both sides rather than merging.
|
|
130
|
+
|
|
131
|
+
## Status
|
|
132
|
+
|
|
133
|
+
**v0.1.0 — Public Beta.** 429 tests passing.
|
|
134
|
+
|
|
135
|
+
What works today:
|
|
136
|
+
- Four-tool session capture (Claude Code, Codex, Gemini, Cursor)
|
|
137
|
+
- Cross-tool resume between Claude Code, Codex, and Gemini
|
|
138
|
+
- Browse, inspect, export, fork, and checkpoint sessions
|
|
139
|
+
- Cloud sync with push/pull and ETag conflict detection
|
|
140
|
+
- Self-hosted API server with auth, PostgreSQL, and S3/local storage
|
|
141
|
+
- Web dashboard for session management
|
|
142
|
+
- 12 security controls including secret detection, path traversal protection, and audit logging
|
|
143
|
+
|
|
144
|
+
On the roadmap:
|
|
145
|
+
- Team handoff workflows with notifications
|
|
146
|
+
- VS Code extension
|
|
147
|
+
- Additional tool watchers
|
|
148
|
+
|
|
149
|
+
## Contributing
|
|
150
|
+
|
|
151
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, testing, and PR guidelines.
|
|
152
|
+
|
|
153
|
+
## License
|
|
154
|
+
|
|
155
|
+
Apache 2.0
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sessionfs"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Capture, sync, and resume AI coding sessions across Claude Code, Codex, Gemini CLI, and Cursor."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = {text = "Apache-2.0"}
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "SessionFS Contributors" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["ai", "agents", "sessions", "sync", "claude", "codex", "gemini", "cursor"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: Apache Software License",
|
|
21
|
+
"Operating System :: MacOS",
|
|
22
|
+
"Operating System :: POSIX :: Linux",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Programming Language :: Python :: 3.10",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Topic :: Software Development",
|
|
28
|
+
"Typing :: Typed",
|
|
29
|
+
]
|
|
30
|
+
dependencies = [
|
|
31
|
+
"pydantic>=2.0,<3.0",
|
|
32
|
+
"jsonschema>=4.20,<5.0",
|
|
33
|
+
"watchdog>=4.0,<6.0",
|
|
34
|
+
"typer>=0.12,<1.0",
|
|
35
|
+
"rich>=13.0,<14.0",
|
|
36
|
+
"httpx>=0.27,<1.0",
|
|
37
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[project.urls]
|
|
41
|
+
Homepage = "https://sessionfs.dev"
|
|
42
|
+
Repository = "https://github.com/sessionfs/sessionfs"
|
|
43
|
+
Documentation = "https://sessionfs.dev/docs"
|
|
44
|
+
Issues = "https://github.com/sessionfs/sessionfs/issues"
|
|
45
|
+
|
|
46
|
+
[project.optional-dependencies]
|
|
47
|
+
server = [
|
|
48
|
+
"fastapi>=0.110,<1.0",
|
|
49
|
+
"uvicorn[standard]>=0.27,<1.0",
|
|
50
|
+
"sqlalchemy[asyncio]>=2.0,<3.0",
|
|
51
|
+
"asyncpg>=0.29,<1.0",
|
|
52
|
+
"alembic>=1.13,<2.0",
|
|
53
|
+
"python-multipart>=0.0.9",
|
|
54
|
+
"boto3>=1.34,<2.0",
|
|
55
|
+
"pydantic-settings>=2.0,<3.0",
|
|
56
|
+
]
|
|
57
|
+
dev = [
|
|
58
|
+
"sessionfs[server]",
|
|
59
|
+
"pytest>=8.0,<9.0",
|
|
60
|
+
"pytest-asyncio>=0.23,<1.0",
|
|
61
|
+
"httpx>=0.27,<1.0",
|
|
62
|
+
"aiosqlite>=0.20,<1.0",
|
|
63
|
+
"ruff>=0.3,<1.0",
|
|
64
|
+
"mypy>=1.8,<2.0",
|
|
65
|
+
"build>=1.0,<2.0",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[project.scripts]
|
|
69
|
+
sfs = "sessionfs.cli.main:cli_main"
|
|
70
|
+
sfsd = "sessionfs.daemon.main:cli_main"
|
|
71
|
+
|
|
72
|
+
[tool.hatch.build.targets.wheel]
|
|
73
|
+
packages = ["src/sessionfs"]
|
|
74
|
+
|
|
75
|
+
[tool.hatch.build]
|
|
76
|
+
include = [
|
|
77
|
+
"src/sessionfs/**/*.py",
|
|
78
|
+
"src/sessionfs/spec/schemas/*.json",
|
|
79
|
+
"src/sessionfs/spec/examples/**/*",
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
[tool.pytest.ini_options]
|
|
83
|
+
testpaths = ["tests"]
|
|
84
|
+
pythonpath = ["src"]
|
|
85
|
+
asyncio_mode = "auto"
|
|
86
|
+
|
|
87
|
+
[tool.ruff]
|
|
88
|
+
target-version = "py310"
|
|
89
|
+
line-length = 100
|
|
90
|
+
|
|
91
|
+
[tool.mypy]
|
|
92
|
+
python_version = "3.10"
|
|
93
|
+
warn_return_any = true
|
|
94
|
+
warn_unused_configs = true
|
|
95
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""M9: Audit logging module.
|
|
2
|
+
|
|
3
|
+
Provides structured audit logging to both local file (~/.sessionfs/audit.log)
|
|
4
|
+
and the server audit_events table. All significant events are logged in JSON
|
|
5
|
+
lines format.
|
|
6
|
+
|
|
7
|
+
IMPORTANT: Never log session content, blob data, or API key values.
|
|
8
|
+
Log metadata only.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
import os
|
|
16
|
+
import stat
|
|
17
|
+
from datetime import datetime, timezone
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger("sessionfs.audit")
|
|
22
|
+
|
|
23
|
+
# Event types that can be logged
|
|
24
|
+
EVENT_TYPES = frozenset({
|
|
25
|
+
"session_captured",
|
|
26
|
+
"session_synced",
|
|
27
|
+
"session_pulled",
|
|
28
|
+
"session_resumed",
|
|
29
|
+
"session_exported",
|
|
30
|
+
"session_handoff",
|
|
31
|
+
"session_deleted",
|
|
32
|
+
"session_forked",
|
|
33
|
+
"session_checkpoint_created",
|
|
34
|
+
"api_key_created",
|
|
35
|
+
"api_key_revoked",
|
|
36
|
+
"auth_failed",
|
|
37
|
+
"auth_success",
|
|
38
|
+
"sync_conflict",
|
|
39
|
+
"sync_error",
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class AuditLogger:
|
|
44
|
+
"""Writes audit events to a JSON lines file."""
|
|
45
|
+
|
|
46
|
+
def __init__(self, audit_log_path: Path | None = None) -> None:
|
|
47
|
+
if audit_log_path is None:
|
|
48
|
+
audit_log_path = Path.home() / ".sessionfs" / "audit.log"
|
|
49
|
+
self._path = audit_log_path
|
|
50
|
+
|
|
51
|
+
def _ensure_file(self) -> None:
|
|
52
|
+
"""Ensure the audit log file exists with correct permissions."""
|
|
53
|
+
self._path.parent.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
if not self._path.exists():
|
|
55
|
+
self._path.touch()
|
|
56
|
+
os.chmod(self._path, stat.S_IRUSR | stat.S_IWUSR) # 0o600
|
|
57
|
+
|
|
58
|
+
def log(
|
|
59
|
+
self,
|
|
60
|
+
event_type: str,
|
|
61
|
+
*,
|
|
62
|
+
user_id: str | None = None,
|
|
63
|
+
session_id: str | None = None,
|
|
64
|
+
details: dict[str, Any] | None = None,
|
|
65
|
+
source_ip: str | None = None,
|
|
66
|
+
) -> None:
|
|
67
|
+
"""Write an audit event to the log file.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
event_type: One of the EVENT_TYPES constants.
|
|
71
|
+
user_id: User who performed the action (None for daemon events).
|
|
72
|
+
session_id: Session involved (if applicable).
|
|
73
|
+
details: Additional context (never include secret values).
|
|
74
|
+
source_ip: Client IP (server-side events only).
|
|
75
|
+
"""
|
|
76
|
+
entry = {
|
|
77
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
78
|
+
"event_type": event_type,
|
|
79
|
+
}
|
|
80
|
+
if user_id is not None:
|
|
81
|
+
entry["user_id"] = user_id
|
|
82
|
+
if session_id is not None:
|
|
83
|
+
entry["session_id"] = session_id
|
|
84
|
+
if details:
|
|
85
|
+
entry["details"] = details
|
|
86
|
+
if source_ip:
|
|
87
|
+
entry["source_ip"] = source_ip
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
self._ensure_file()
|
|
91
|
+
with open(self._path, "a", encoding="utf-8") as f:
|
|
92
|
+
f.write(json.dumps(entry, separators=(",", ":")) + "\n")
|
|
93
|
+
except OSError as e:
|
|
94
|
+
logger.error("Failed to write audit log: %s", e)
|
|
95
|
+
|
|
96
|
+
def read_events(
|
|
97
|
+
self,
|
|
98
|
+
event_type: str | None = None,
|
|
99
|
+
session_id: str | None = None,
|
|
100
|
+
limit: int = 100,
|
|
101
|
+
) -> list[dict[str, Any]]:
|
|
102
|
+
"""Read audit events from the log file with optional filters."""
|
|
103
|
+
if not self._path.exists():
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
events: list[dict[str, Any]] = []
|
|
107
|
+
with open(self._path, "r", encoding="utf-8") as f:
|
|
108
|
+
for line in f:
|
|
109
|
+
line = line.strip()
|
|
110
|
+
if not line:
|
|
111
|
+
continue
|
|
112
|
+
try:
|
|
113
|
+
event = json.loads(line)
|
|
114
|
+
except json.JSONDecodeError:
|
|
115
|
+
continue
|
|
116
|
+
if event_type and event.get("event_type") != event_type:
|
|
117
|
+
continue
|
|
118
|
+
if session_id and event.get("session_id") != session_id:
|
|
119
|
+
continue
|
|
120
|
+
events.append(event)
|
|
121
|
+
|
|
122
|
+
# Return most recent first, limited
|
|
123
|
+
return events[-limit:][::-1]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
# Module-level default instance
|
|
127
|
+
_default_logger: AuditLogger | None = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def get_audit_logger(audit_log_path: Path | None = None) -> AuditLogger:
|
|
131
|
+
"""Get or create the default AuditLogger instance."""
|
|
132
|
+
global _default_logger
|
|
133
|
+
if _default_logger is None or audit_log_path is not None:
|
|
134
|
+
_default_logger = AuditLogger(audit_log_path)
|
|
135
|
+
return _default_logger
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def audit_event(
|
|
139
|
+
event_type: str,
|
|
140
|
+
*,
|
|
141
|
+
user_id: str | None = None,
|
|
142
|
+
session_id: str | None = None,
|
|
143
|
+
details: dict[str, Any] | None = None,
|
|
144
|
+
source_ip: str | None = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
"""Convenience function to log an audit event using the default logger."""
|
|
147
|
+
get_audit_logger().log(
|
|
148
|
+
event_type,
|
|
149
|
+
user_id=user_id,
|
|
150
|
+
session_id=session_id,
|
|
151
|
+
details=details,
|
|
152
|
+
source_ip=source_ip,
|
|
153
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""SessionFS CLI."""
|