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.
Files changed (90) hide show
  1. sessionfs-0.1.0/.gitignore +37 -0
  2. sessionfs-0.1.0/PKG-INFO +213 -0
  3. sessionfs-0.1.0/README.md +155 -0
  4. sessionfs-0.1.0/pyproject.toml +95 -0
  5. sessionfs-0.1.0/src/sessionfs/__init__.py +3 -0
  6. sessionfs-0.1.0/src/sessionfs/audit.py +153 -0
  7. sessionfs-0.1.0/src/sessionfs/cli/__init__.py +1 -0
  8. sessionfs-0.1.0/src/sessionfs/cli/cmd_admin.py +60 -0
  9. sessionfs-0.1.0/src/sessionfs/cli/cmd_cloud.py +522 -0
  10. sessionfs-0.1.0/src/sessionfs/cli/cmd_config.py +117 -0
  11. sessionfs-0.1.0/src/sessionfs/cli/cmd_daemon.py +161 -0
  12. sessionfs-0.1.0/src/sessionfs/cli/cmd_io.py +137 -0
  13. sessionfs-0.1.0/src/sessionfs/cli/cmd_ops.py +230 -0
  14. sessionfs-0.1.0/src/sessionfs/cli/cmd_sessions.py +321 -0
  15. sessionfs-0.1.0/src/sessionfs/cli/common.py +84 -0
  16. sessionfs-0.1.0/src/sessionfs/cli/cost.py +77 -0
  17. sessionfs-0.1.0/src/sessionfs/cli/main.py +58 -0
  18. sessionfs-0.1.0/src/sessionfs/cli/sfs_to_cc.py +459 -0
  19. sessionfs-0.1.0/src/sessionfs/cli/sfs_to_md.py +125 -0
  20. sessionfs-0.1.0/src/sessionfs/cli/titles.py +159 -0
  21. sessionfs-0.1.0/src/sessionfs/converters/__init__.py +0 -0
  22. sessionfs-0.1.0/src/sessionfs/converters/codex_injector.py +162 -0
  23. sessionfs-0.1.0/src/sessionfs/converters/cursor_to_sfs.py +324 -0
  24. sessionfs-0.1.0/src/sessionfs/converters/gemini_injector.py +92 -0
  25. sessionfs-0.1.0/src/sessionfs/converters/gemini_to_sfs.py +269 -0
  26. sessionfs-0.1.0/src/sessionfs/converters/sfs_to_codex.py +383 -0
  27. sessionfs-0.1.0/src/sessionfs/converters/sfs_to_gemini.py +204 -0
  28. sessionfs-0.1.0/src/sessionfs/daemon/__init__.py +0 -0
  29. sessionfs-0.1.0/src/sessionfs/daemon/config.py +137 -0
  30. sessionfs-0.1.0/src/sessionfs/daemon/main.py +393 -0
  31. sessionfs-0.1.0/src/sessionfs/daemon/status.py +66 -0
  32. sessionfs-0.1.0/src/sessionfs/security/__init__.py +0 -0
  33. sessionfs-0.1.0/src/sessionfs/security/secrets.py +223 -0
  34. sessionfs-0.1.0/src/sessionfs/server/__init__.py +1 -0
  35. sessionfs-0.1.0/src/sessionfs/server/app.py +95 -0
  36. sessionfs-0.1.0/src/sessionfs/server/auth/__init__.py +1 -0
  37. sessionfs-0.1.0/src/sessionfs/server/auth/dependencies.py +70 -0
  38. sessionfs-0.1.0/src/sessionfs/server/auth/keys.py +24 -0
  39. sessionfs-0.1.0/src/sessionfs/server/auth/rate_limit.py +36 -0
  40. sessionfs-0.1.0/src/sessionfs/server/config.py +28 -0
  41. sessionfs-0.1.0/src/sessionfs/server/db/__init__.py +1 -0
  42. sessionfs-0.1.0/src/sessionfs/server/db/engine.py +40 -0
  43. sessionfs-0.1.0/src/sessionfs/server/db/migrations/env.py +70 -0
  44. sessionfs-0.1.0/src/sessionfs/server/db/migrations/versions/001_initial_schema.py +78 -0
  45. sessionfs-0.1.0/src/sessionfs/server/db/models.py +77 -0
  46. sessionfs-0.1.0/src/sessionfs/server/errors.py +48 -0
  47. sessionfs-0.1.0/src/sessionfs/server/routes/__init__.py +1 -0
  48. sessionfs-0.1.0/src/sessionfs/server/routes/auth.py +132 -0
  49. sessionfs-0.1.0/src/sessionfs/server/routes/health.py +12 -0
  50. sessionfs-0.1.0/src/sessionfs/server/routes/sessions.py +822 -0
  51. sessionfs-0.1.0/src/sessionfs/server/schemas/__init__.py +1 -0
  52. sessionfs-0.1.0/src/sessionfs/server/schemas/auth.py +37 -0
  53. sessionfs-0.1.0/src/sessionfs/server/schemas/errors.py +17 -0
  54. sessionfs-0.1.0/src/sessionfs/server/schemas/sessions.py +104 -0
  55. sessionfs-0.1.0/src/sessionfs/server/storage/__init__.py +1 -0
  56. sessionfs-0.1.0/src/sessionfs/server/storage/base.py +15 -0
  57. sessionfs-0.1.0/src/sessionfs/server/storage/local.py +41 -0
  58. sessionfs-0.1.0/src/sessionfs/server/storage/s3.py +49 -0
  59. sessionfs-0.1.0/src/sessionfs/session_id.py +32 -0
  60. sessionfs-0.1.0/src/sessionfs/spec/__init__.py +0 -0
  61. sessionfs-0.1.0/src/sessionfs/spec/convert_cc.py +634 -0
  62. sessionfs-0.1.0/src/sessionfs/spec/examples/complete/manifest.json +33 -0
  63. sessionfs-0.1.0/src/sessionfs/spec/examples/complete/messages.jsonl +8 -0
  64. sessionfs-0.1.0/src/sessionfs/spec/examples/complete/tools.json +9 -0
  65. sessionfs-0.1.0/src/sessionfs/spec/examples/complete/workspace.json +33 -0
  66. sessionfs-0.1.0/src/sessionfs/spec/examples/minimal/manifest.json +28 -0
  67. sessionfs-0.1.0/src/sessionfs/spec/examples/minimal/messages.jsonl +3 -0
  68. sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/manifest.json +33 -0
  69. sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/messages.jsonl +7 -0
  70. sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/tools.json +7 -0
  71. sessionfs-0.1.0/src/sessionfs/spec/examples/subagent/workspace.json +24 -0
  72. sessionfs-0.1.0/src/sessionfs/spec/schemas/manifest.schema.json +287 -0
  73. sessionfs-0.1.0/src/sessionfs/spec/schemas/message.schema.json +291 -0
  74. sessionfs-0.1.0/src/sessionfs/spec/schemas/tools.schema.json +138 -0
  75. sessionfs-0.1.0/src/sessionfs/spec/schemas/workspace.schema.json +152 -0
  76. sessionfs-0.1.0/src/sessionfs/spec/validate.py +353 -0
  77. sessionfs-0.1.0/src/sessionfs/store/__init__.py +0 -0
  78. sessionfs-0.1.0/src/sessionfs/store/index.py +230 -0
  79. sessionfs-0.1.0/src/sessionfs/store/local.py +147 -0
  80. sessionfs-0.1.0/src/sessionfs/sync/__init__.py +1 -0
  81. sessionfs-0.1.0/src/sessionfs/sync/archive.py +73 -0
  82. sessionfs-0.1.0/src/sessionfs/sync/client.py +313 -0
  83. sessionfs-0.1.0/src/sessionfs/utils/__init__.py +0 -0
  84. sessionfs-0.1.0/src/sessionfs/utils/title_utils.py +207 -0
  85. sessionfs-0.1.0/src/sessionfs/watchers/__init__.py +0 -0
  86. sessionfs-0.1.0/src/sessionfs/watchers/base.py +71 -0
  87. sessionfs-0.1.0/src/sessionfs/watchers/claude_code.py +641 -0
  88. sessionfs-0.1.0/src/sessionfs/watchers/codex.py +573 -0
  89. sessionfs-0.1.0/src/sessionfs/watchers/cursor.py +202 -0
  90. 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
@@ -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,3 @@
1
+ """SessionFS — Portable AI coding sessions."""
2
+
3
+ __version__ = "0.1.0"
@@ -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."""