@online5880/opensession 0.1.1 โ†’ 0.1.3

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.
package/README.ko.md ADDED
@@ -0,0 +1,133 @@
1
+ # ๐ŸŒ OpenSession
2
+
3
+ **[English](README.md) | [ํ•œ๊ตญ์–ด](README.ko.md)**
4
+
5
+ > **AI ์—์ด์ „ํŠธ ์šด์˜์„ ์œ„ํ•œ ์‹คํ–‰ ์—ฐ์†์„ฑ ๊ณ„์ธต (Execution Continuity Layer)**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@online5880/opensession.svg?style=flat-square)](https://www.npmjs.com/package/@online5880/opensession)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
9
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](#)
10
+ [![Built with Supabase](https://img.shields.io/badge/Backend-Supabase-green.svg?style=flat-square)](https://supabase.com)
11
+
12
+ **OpenSession**์€ AI ์—์ด์ „ํŠธ๊ฐ€ ๋‹ค์–‘ํ•œ ๋„๊ตฌ, ํ™˜๊ฒฝ, ๋„คํŠธ์›Œํฌ ์ƒํƒœ ์‚ฌ์ด์—์„œ ์ž‘์—…์˜ ๋งฅ๋ฝ(Context)๊ณผ ์—ฐ์†์„ฑ(Continuity)์„ ์žƒ์ง€ ์•Š๋„๋ก ๋•๋Š” **์‹คํ–‰ ์—ฐ์†์„ฑ OS**์ž…๋‹ˆ๋‹ค. ํ•œ ๋ฒˆ ์‹œ์ž‘ํ•˜๋ฉด ์–ด๋””์„œ๋“  ์žฌ๊ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
13
+
14
+ ---
15
+
16
+ ## ๐Ÿš€ ์™œ OpenSession ์ธ๊ฐ€์š”?
17
+
18
+ AI ์—์ด์ „ํŠธ์™€ ํ˜‘์—…ํ•  ๋•Œ ๊ฐ€์žฅ ํฐ ๋ฌธ์ œ๋Š” **"๋งฅ๋ฝ์˜ ๋‹จ์ ˆ(Context Fragmentation)"**์ž…๋‹ˆ๋‹ค.
19
+ - ๋กœ์ปฌ์—์„œ ์ž‘์—…ํ•˜๋˜ ์—์ด์ „ํŠธ๋ฅผ ์„œ๋ฒ„๋กœ ์˜ฎ๊ธฐ๋ฉด?
20
+ - ๋„คํŠธ์›Œํฌ ์˜ค๋ฅ˜๋กœ ์„ธ์…˜์ด ๋Š๊ธฐ๋ฉด?
21
+ - ์—ฌ๋Ÿฌ ๋„๊ตฌ๊ฐ€ ๊ฐ์ž์˜ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ฒจ์„œ ์ „์ฒด ํ๋ฆ„์„ ํŒŒ์•…ํ•˜๊ธฐ ํž˜๋“ค๋‹ค๋ฉด?
22
+
23
+ **OpenSession์€ ์ด ๋ชจ๋“  ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค.** ๋‹จ์ผ ์„ธ์…˜ ID๋กœ ๋ชจ๋“  ํ™œ๋™์„ Supabase์— ์˜์†ํ™”ํ•˜๊ณ , CLI/Web/TUI๋ฅผ ํ†ตํ•ด ๋Š๊น€ ์—†์ด ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
24
+
25
+ ---
26
+
27
+ ## โœจ ํ•ต์‹ฌ ๊ธฐ๋Šฅ
28
+
29
+ ### 1. ์˜์†์  ์„ธ์…˜ ๋ชจ๋ธ (Stable Session Model)
30
+ - ์–ด๋–ค ํ™˜๊ฒฝ์—์„œ๋„ ๋™์ผํ•œ `session_id`๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž‘์—…์„ ์ด์–ด๊ฐ‘๋‹ˆ๋‹ค.
31
+ - `start` -> `pause` -> `resume` ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.
32
+
33
+ ### 2. ๊ฐ•๋ ฅํ•œ ์ด๋ฒคํŠธ ํƒ€์ž„๋ผ์ธ (Durable Event Timeline)
34
+ - **์˜๋„ (Intent)**: ๋ฌด์—‡์„ ํ•˜๋ ค๊ณ  ํ•˜๋Š”๊ฐ€?
35
+ - **์•ก์…˜ (Action)**: ์‹ค์ œ ์–ด๋–ค ๋ช…๋ น์„ ์‹คํ–‰ํ–ˆ๋Š”๊ฐ€?
36
+ - **๊ฒฐ๊ณผ๋ฌผ (Artifact)**: ๋ฌด์—‡์„ ๋งŒ๋“ค์–ด๋ƒˆ๋Š”๊ฐ€?
37
+ - ์œ„ ์„ธ ๊ฐ€์ง€ ํ•ต์‹ฌ ์š”์†Œ๋ฅผ ๊ตฌ์กฐํ™”๋œ JSON์œผ๋กœ ๊ธฐ๋กํ•˜์—ฌ ์‹ฌ์ธต ๋ถ„์„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
38
+
39
+ ### 3. ๋‹ค์ค‘ ๋ชจ๋‹ˆํ„ฐ๋ง ์ธํ„ฐํŽ˜์ด์Šค (Multi-Surface Monitoring)
40
+ - **CLI**: ์ง๊ด€์ ์ธ ํ„ฐ๋ฏธ๋„ ๊ธฐ๋ฐ˜ ์ œ์–ด.
41
+ - **WebUI (Viewer)**: ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™•์ธํ•˜๋Š” ๊ณ ํ•ด์ƒ๋„ ํƒ€์ž„๋ผ์ธ ๋Œ€์‹œ๋ณด๋“œ.
42
+ - **TUI (Terminal UI)**: ํ„ฐ๋ฏธ๋„ ์•ˆ์—์„œ ํ‚ค๋ณด๋“œ๋กœ ์กฐ์ž‘ํ•˜๋Š” ๋Œ€ํ™”ํ˜• ๋Œ€์‹œ๋ณด๋“œ.
43
+
44
+ ### 4. ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰ ์‹ ๋ขฐ์„ฑ (Enterprise-Grade Reliability)
45
+ - **๋ฉฑ๋“ฑ์„ฑ(Idempotency)** ๋ณด์žฅ: ๋™์ผํ•œ ์ด๋ฒคํŠธ๊ฐ€ ์ค‘๋ณต ๊ธฐ๋ก๋˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค.
46
+ - **์ง€์ˆ˜ ๋ฐฑ์˜คํ”„(Exponential Backoff)**: ๋ถˆ์•ˆ์ •ํ•œ ๋„คํŠธ์›Œํฌ ํ™˜๊ฒฝ์—์„œ๋„ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค.
47
+
48
+ ---
49
+
50
+ ## ๐Ÿ—บ๏ธ ๋กœ๋“œ๋งต: 3๋‹จ๊ณ„ ์ธํ„ฐํŽ˜์ด์Šค
51
+
52
+ | ๋‹จ๊ณ„ (Phase) | ์ธํ„ฐํŽ˜์ด์Šค | ์ƒํƒœ | ๊ธฐ๋Šฅ ์„ค๋ช… |
53
+ | :--- | :--- | :--- | :--- |
54
+ | **Phase 1** | **CLI ์ฝ”์–ด** | โœ… ์•ˆ์ •๋จ | ์„ธ์…˜ ์ œ์–ด, ๊ธฐ๋ณธ ๋กœ๊น…, ์„ค์ • ๊ด€๋ฆฌ |
55
+ | **Phase 2** | **WebUI ๋ทฐ์–ด** | โœ… ์•ˆ์ •๋จ | ๋‹คํฌ ํ…Œ๋งˆ, ํ†ต๊ณ„(KPI) ๋ฆฌํฌํŠธ, JSON ํŽ˜์ด๋กœ๋“œ ๋ทฐ์–ด |
56
+ | **Phase 3** | **์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ TUI** | โœ… ํ™œ์„ฑํ™” | ๋Œ€ํ™”ํ˜• ์„ธ์…˜ ์„ ํƒ, ์‹ค์‹œ๊ฐ„ ์ด๋ฒคํŠธ ์ŠคํŠธ๋ฆฌ๋ฐ ์ž๋™ ๊ฐฑ์‹  |
57
+
58
+ ---
59
+
60
+ ## ๐Ÿ› ๏ธ ์‹œ์ž‘ํ•˜๊ธฐ
61
+
62
+ ### ์„ค์น˜ ๋ฐ ๋‹จ์ถ•์–ด ์„ค์ • (Alias)
63
+
64
+ ๋น ๋ฅธ ์‹คํ–‰์„ ์œ„ํ•ด ์‰˜ ํ”„๋กœํ•„์— ๋‹จ์ถ•์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜์„ธ์š”:
65
+
66
+ **macOS / Linux (Bash/Zsh):**
67
+ ```bash
68
+ alias opss='npx -y @online5880/opensession'
69
+ ```
70
+
71
+ **Windows (PowerShell):**
72
+ ```powershell
73
+ function opss { npx -y @online5880/opensession @args }
74
+ ```
75
+
76
+ ### 1๋ถ„ ํ€ต ์Šคํƒ€ํŠธ
77
+
78
+ 1. **์ดˆ๊ธฐํ™”**: Supabase URL๊ณผ API ํ‚ค๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
79
+ ```bash
80
+ opss init
81
+ ```
82
+
83
+ 2. **์„ธ์…˜ ์‹œ์ž‘**: ์ƒˆ๋กœ์šด ํ”„๋กœ์ ํŠธ ์„ธ์…˜์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.
84
+ ```bash
85
+ opss start --project-key my-ai-lab --actor mane
86
+ ```
87
+
88
+ 3. **์ด๋ฒคํŠธ ๊ธฐ๋ก**: ์—์ด์ „ํŠธ์˜ ํ™œ๋™์„ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
89
+ ```bash
90
+ opss log --limit 10
91
+ ```
92
+
93
+ 4. **๋Œ€์‹œ๋ณด๋“œ ์‹คํ–‰**: ์›ํ•˜๋Š” ๋ทฐ์–ด๋ฅผ ์„ ํƒํ•ด ๋ชจ๋‹ˆํ„ฐ๋งํ•˜์„ธ์š”.
94
+ ```bash
95
+ opss tui # ํ„ฐ๋ฏธ๋„ ๋Œ€์‹œ๋ณด๋“œ (์ถ”์ฒœ)
96
+ opss viewer # ์›น ๋ธŒ๋ผ์šฐ์ € ๋ทฐ์–ด
97
+ ```
98
+
99
+ ---
100
+
101
+ ## ๐Ÿ“– ๋ช…๋ น์–ด ๊ฐ€์ด๋“œ
102
+
103
+ | ๋ช…๋ น์–ด | ๋‹จ์ถ•์–ด | ์„ค๋ช… |
104
+ | :--- | :--- | :--- |
105
+ | `init` | `setup` | Supabase ์„œ๋ฒ„ ์—ฐ๊ฒฐ ๋ฐ ๋กœ์ปฌ ์„ค์ •์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. |
106
+ | `start` | `st` | ์ƒˆ๋กœ์šด ์„ธ์…˜์„ ์ƒ์„ฑํ•˜๊ณ  ํƒ€์ž„๋ผ์ธ์„ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค. |
107
+ | `resume` | `rs` | ๋ฉฑ๋“ฑ์„ฑ ๋ณดํ˜ธ๋ฅผ ํ†ตํ•ด ๊ธฐ์กด ์„ธ์…˜์„ ์žฌ๊ฐœํ•ฉ๋‹ˆ๋‹ค. |
108
+ | `tui` | - | **(New)** ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ„ฐ๋ฏธ๋„ ๋Œ€์‹œ๋ณด๋“œ(TUI)๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. |
109
+ | `viewer` | `vw` | ๋กœ์ปฌ ์›น ๋ทฐ์–ด ์„œ๋ฒ„๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค (๊ธฐ๋ณธ 8787 ํฌํŠธ). |
110
+ | `status` | `ps` | CLI ๋ฒ„์ „ ์ •๋ณด ๋ฐ ํ™œ์„ฑ ์„ธ์…˜ ์ƒํƒœ๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. |
111
+ | `report` | - | 28์ผ๊ฐ„์˜ KPI ํ†ต๊ณ„ ๋ฐ ์ฃผ๊ฐ„ ํŠธ๋ Œ๋“œ ๋ถ„์„ ๋ฆฌํฌํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. |
112
+
113
+ ---
114
+
115
+ ## ๐Ÿ—๏ธ ์•„ํ‚คํ…์ฒ˜
116
+
117
+ OpenSession์€ ์—์ด์ „ํŠธ ๋Ÿฐํƒ€์ž„๊ณผ ์˜๊ตฌ ์Šคํ† ๋ฆฌ์ง€ ์‚ฌ์ด์˜ **์‹ ๋ขฐ์„ฑ ๋†’์€ ์—ฐ๊ฒฐ ๊ณ„์ธต**์œผ๋กœ ์ž‘๋™ํ•ฉ๋‹ˆ๋‹ค.
118
+
119
+ ```mermaid
120
+ graph TD
121
+ Agent[AI Agent / Developer] -->|Log/Control| CLI[OpenSession CLI]
122
+ CLI -->|Persist| DB[(Supabase Cloud)]
123
+ DB -->|Stream| TUI[TUI Dashboard]
124
+ DB -->|Render| Web[Web Viewer]
125
+ ```
126
+
127
+ ---
128
+
129
+ ## ๐Ÿค ๊ธฐ์—ฌํ•˜๊ธฐ (Contributing)
130
+
131
+ ๊ธฐ์—ฌ๋Š” ์–ธ์ œ๋‚˜ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! ๋ฒ„๊ทธ ์ œ๋ณด ๋ฐ ๊ธฐ๋Šฅ ์ œ์•ˆ์€ [GitHub Issues](https://github.com/online5880/opensession/issues)๋ฅผ ์ด์šฉํ•ด ์ฃผ์„ธ์š”.
132
+
133
+ MIT ยฉ [online5880](https://github.com/online5880)
package/README.md CHANGED
@@ -1,32 +1,137 @@
1
- # @online5880/opensession
2
-
3
- MVP CLI for session continuity with Supabase.
4
-
5
- ## Commands
6
-
7
- - `init [--project-key] [--actor]` (ํ”„๋กฌํ”„ํŠธ๋กœ URL/ANON KEY ์ž…๋ ฅ)
8
- - `login --actor`
9
- - `start --project-key [--project-name] [--actor]`
10
- - `resume --session-id [--actor]`
11
- - `status [--project-key]`
12
- - `sync --project <name>`
13
- - `log [--session-id] [--limit]`
14
-
15
- ## Quick start
16
-
17
- ```bash
18
- npm install
19
- node src/cli.js init --project-key demo --actor mane
20
- node src/cli.js sync --project demo
21
- node src/cli.js start --project-key demo
22
- node src/cli.js status
23
- node src/cli.js log
24
- ```
25
-
26
- Apply `sql/schema.sql` in Supabase SQL editor before using the CLI.
27
-
28
- ## npx ์‹คํ–‰
29
-
30
- ```bash
31
- npx @online5880/opensession init
32
- ```
1
+ # ๐ŸŒ OpenSession
2
+
3
+ **[English](README.md) | [ํ•œ๊ตญ์–ด](README.ko.md)**
4
+
5
+ > **The Execution Continuity Layer for AI Agent Operations**
6
+
7
+ [![npm version](https://img.shields.io/npm/v/@online5880/opensession.svg?style=flat-square)](https://www.npmjs.com/package/@online5880/opensession)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square)](https://opensource.org/licenses/MIT)
9
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](#)
10
+ [![Built with Supabase](https://img.shields.io/badge/Backend-Supabase-green.svg?style=flat-square)](https://supabase.com)
11
+
12
+ **OpenSession** is an execution continuity layer designed to help AI agents maintain context and workflow stability across different tools, environments, and network conditions.
13
+
14
+ ---
15
+
16
+ ## ๐Ÿš€ Why OpenSession?
17
+
18
+ The biggest challenge in collaborating with AI agents is **"Context Fragmentation."**
19
+ - What happens if you move an agent from local to a remote server?
20
+ - What if the session drops due to network issues?
21
+ - How do you track the flow across multiple tools with disparate logs?
22
+
23
+ **OpenSession solves this.** By using a single session ID, all activities are persisted to Supabase, allowing you to monitor and resume work seamlessly via CLI, Web, or TUI.
24
+
25
+ ---
26
+
27
+ ## โœจ Key Capabilities
28
+
29
+ ### 1. Stable Session Model
30
+ - Resume work anywhere using the same `session_id`.
31
+ - Native support for `start` -> `pause` -> `resume` workflows.
32
+
33
+ ### 2. Durable Event Timeline
34
+ - **Intent**: What is the goal?
35
+ - **Action**: What command was executed?
36
+ - **Artifact**: What was produced?
37
+ - All elements are recorded in structured JSON for deep analysis.
38
+
39
+ ### 3. Multi-Surface Monitoring
40
+ - **CLI**: Intuitive terminal-based control.
41
+ - **WebUI (Viewer)**: High-resolution timeline dashboard in your browser.
42
+ - **TUI (Terminal UI)**: Keyboard-driven interactive dashboard inside your terminal.
43
+
44
+ ### 4. Enterprise-Grade Reliability
45
+ - **Idempotency**: Prevents duplicate event recording.
46
+ - **Exponential Backoff**: Automatic retries for unstable network scenarios.
47
+
48
+ ---
49
+
50
+ ## ๐Ÿ—บ๏ธ Roadmap: The 3-Layer Interface
51
+
52
+ | Phase | Surface | Status | Features |
53
+ | :--- | :--- | :--- | :--- |
54
+ | **Phase 1** | **CLI Core** | โœ… Stable | Session control, basic logging, config management |
55
+ | **Phase 2** | **WebUI Viewer** | โœ… Stable | Dark theme, KPI reports, JSON payload viewer |
56
+ | **Phase 3** | **Interactive TUI** | โœ… Active | Real-time session switching, live event streaming |
57
+
58
+ ### Installation & Setup
59
+
60
+ #### 1. Alias / Function Setup
61
+ To use the `opss` shorthand, add the following to your shell profile:
62
+
63
+ **macOS / Linux (Bash/Zsh):**
64
+ ```bash
65
+ alias opss='npx -y @online5880/opensession'
66
+ ```
67
+
68
+ **Windows (PowerShell):**
69
+ ```powershell
70
+ function opss { npx -y @online5880/opensession @args }
71
+ ```
72
+
73
+ #### 2. Global Install (Optional)
74
+ ```bash
75
+ npm install -g @online5880/opensession
76
+ ```
77
+
78
+ ---
79
+
80
+ ## ๐Ÿš€ 1-Minute Quickstart
81
+
82
+ 1. **Initialize**: Set up your Supabase URL and API Key.
83
+ ```bash
84
+ opss init
85
+ ```
86
+
87
+ 2. **Start Session**: Begin a new project session.
88
+ ```bash
89
+ opss start --project-key my-ai-lab --actor mane
90
+ ```
91
+
92
+ 3. **Log Events**: Record agent activities.
93
+ ```bash
94
+ opss log --limit 10
95
+ ```
96
+
97
+ 4. **Launch Dashboard**: Choose your preferred view.
98
+ ```bash
99
+ opss tui # Terminal dashboard (Recommended)
100
+ opss viewer # Web browser viewer
101
+ ```
102
+
103
+ ---
104
+
105
+ ## ๐Ÿ“– Command Reference
106
+
107
+ | Command | Alias | Description |
108
+ | :--- | :--- | :--- |
109
+ | `init` | `setup` | Initialize Supabase connection and local config. |
110
+ | `start` | `st` | Create a new session and start the timeline. |
111
+ | `resume` | `rs` | Resume an existing session with idempotency protection. |
112
+ | `tui` | - | **(New)** Launch the interactive Terminal UI dashboard. |
113
+ | `viewer` | `vw` | Run a local read-only web viewer server. |
114
+ | `status` | `ps` | Check CLI version and active session status. |
115
+ | `report` | - | Generate 28-day KPI stats and weekly trend analysis. |
116
+
117
+ ---
118
+
119
+ ## ๐Ÿ—๏ธ Architecture
120
+
121
+ OpenSession acts as a high-reliability bridge between agent runtimes and persistent storage.
122
+
123
+ ```mermaid
124
+ graph TD
125
+ Agent[AI Agent / Developer] -->|Log/Control| CLI[OpenSession CLI]
126
+ CLI -->|Persist| DB[(Supabase Cloud)]
127
+ DB -->|Stream| TUI[TUI Dashboard]
128
+ DB -->|Render| Web[Web Viewer]
129
+ ```
130
+
131
+ ---
132
+
133
+ ## ๐Ÿค Contributing
134
+
135
+ Contributions are welcome! Please use [GitHub Issues](https://github.com/online5880/opensession/issues) for bug reports and feature requests.
136
+
137
+ MIT ยฉ [online5880](https://github.com/online5880)
package/package.json CHANGED
@@ -1,20 +1,23 @@
1
- {
2
- "name": "@online5880/opensession",
3
- "version": "0.1.1",
4
- "description": "Session continuity bridge CLI with Supabase backend",
5
- "type": "module",
6
- "bin": {
7
- "opensession": "src/cli.js"
8
- },
9
- "scripts": {
10
- "start": "node src/cli.js",
11
- "lint": "sh -c 'for f in src/*.js; do node --check \"$f\"; done'"
12
- },
13
- "engines": {
14
- "node": ">=18"
15
- },
16
- "dependencies": {
17
- "@supabase/supabase-js": "^2.52.1",
18
- "commander": "^14.0.1"
19
- }
20
- }
1
+ {
2
+ "name": "@online5880/opensession",
3
+ "version": "0.1.3",
4
+ "description": "Session continuity bridge CLI with Supabase backend",
5
+ "type": "module",
6
+ "bin": {
7
+ "opensession": "src/cli.js",
8
+ "opss": "src/cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/cli.js",
12
+ "lint": "sh -c 'for f in src/*.js; do node --check \"$f\"; done'",
13
+ "test": "node --test test/*.test.js"
14
+ },
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "dependencies": {
19
+ "@supabase/supabase-js": "^2.52.1",
20
+ "blessed": "^0.1.81",
21
+ "commander": "^14.0.1"
22
+ }
23
+ }
@@ -0,0 +1,35 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>OpenSession โ€” Agent Execution Continuity OS</title>
7
+ <meta name="description" content="Start once, resume anywhere. OpenSession is the execution continuity layer for AI agent operations." />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root {--bg:#080b12;--bg-soft:#0f1420;--panel:#121a28;--panel-2:#182234;--text:#e9f1ff;--muted:#99abc9;--line:#22314a;--brand:#64d6ff;--brand-2:#8c7bff;--ok:#37d67a;}
13
+ *{box-sizing:border-box} body{margin:0;font-family:Inter,system-ui,-apple-system,sans-serif;color:var(--text);background:radial-gradient(1200px 700px at 85% -10%,rgba(140,123,255,.22),transparent 60%),radial-gradient(1000px 600px at 10% -10%,rgba(100,214,255,.18),transparent 58%),var(--bg)}
14
+ .container{width:min(1120px,92vw);margin:0 auto}.topbar{display:flex;justify-content:space-between;align-items:center;padding:18px 0;border-bottom:1px solid rgba(255,255,255,.06);position:sticky;top:0;backdrop-filter:blur(10px);background:rgba(8,11,18,.75)}
15
+ .brand{font-weight:800}.brand span{color:var(--brand)}.nav a{color:var(--muted);text-decoration:none;margin-left:18px;font-size:.95rem}.hero{padding:74px 0 34px;text-align:center}
16
+ .eyebrow{color:var(--brand);font-weight:600;letter-spacing:.12em;text-transform:uppercase;font-size:.78rem}h1{margin:14px auto;max-width:900px;font-size:clamp(2rem,5vw,3.8rem);line-height:1.08}.sub{margin:0 auto;max-width:760px;color:var(--muted);font-size:1.08rem}
17
+ .cta{margin-top:28px;display:flex;gap:12px;justify-content:center;flex-wrap:wrap}.btn{padding:12px 18px;border-radius:12px;text-decoration:none;font-weight:600}.btn.primary{color:#06121a;background:linear-gradient(90deg,var(--brand),#7de6ff)}.btn.secondary{color:var(--text);border:1px solid #2a3c59;background:rgba(255,255,255,.02)}
18
+ .metrics{margin-top:28px;display:grid;grid-template-columns:repeat(4,1fr);gap:10px;background:rgba(255,255,255,.02);border:1px solid var(--line);border-radius:14px;padding:10px}.metric{background:linear-gradient(180deg,var(--panel),var(--panel-2));border-radius:10px;padding:14px}.metric .n{font-size:1.1rem;font-weight:800}.metric .l{color:var(--muted);font-size:.85rem;margin-top:4px}
19
+ section{padding:36px 0}h2{margin:0 0 14px;font-size:1.45rem}.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}.card{border:1px solid var(--line);background:linear-gradient(180deg,var(--panel),#111827);border-radius:14px;padding:16px}.card h3{margin:0 0 8px}.card p{margin:0;color:var(--muted);font-size:.93rem}
20
+ .arch{border:1px solid var(--line);border-radius:14px;padding:16px;background:linear-gradient(180deg,#101827,#0d1421)}.layers{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:12px}.layer{border:1px solid #2a3e60;border-radius:10px;padding:12px;background:#0c1320}
21
+ .timeline{border-left:2px solid #2a3b5a;margin-left:6px;padding-left:16px}.event{margin:0 0 14px}.event span{color:var(--muted);display:block;margin-top:4px}
22
+ .code{border:1px solid #2a3f60;border-radius:12px;background:#08111d;overflow:auto;font-family:"JetBrains Mono",monospace;font-size:.9rem;line-height:1.6;padding:14px}.code .ok{color:var(--ok)}.code .cmd{color:#8fd0ff}
23
+ footer{padding:30px 0 42px;color:var(--muted);font-size:.9rem;border-top:1px solid rgba(255,255,255,.06);margin-top:22px}
24
+ @media (max-width:900px){.cards{grid-template-columns:1fr}.layers{grid-template-columns:1fr 1fr}.metrics{grid-template-columns:1fr 1fr}}
25
+ </style>
26
+ </head>
27
+ <body><div class="container"><div class="topbar"><div class="brand">Open<span>Session</span></div><div class="nav"><a href="#features">Features</a><a href="#docs">Docs</a><a href="#updates">What's New</a></div></div>
28
+ <section class="hero"><div class="eyebrow">Agent Execution Continuity OS</div><h1>Start once, resume anywhere.<br/>Keep every agent run connected.</h1><p class="sub">OpenSession keeps intent, actions, and artifacts connected so teams can hand off work without losing context.</p><div class="cta"><a class="btn primary" href="#docs">Quickstart</a><a class="btn secondary" href="https://github.com/online5880/opensession" target="_blank" rel="noreferrer">GitHub</a></div>
29
+ <div class="metrics"><div class="metric"><div class="n">Resume Anywhere</div><div class="l">single session id across environments</div></div><div class="metric"><div class="n">Intent โ†’ Action โ†’ Artifact</div><div class="l">structured execution chain</div></div><div class="metric"><div class="n">Handoff Packet</div><div class="l">3-minute transfer context</div></div><div class="metric"><div class="n">Guardrails</div><div class="l">risk-level automation control</div></div></div></section>
30
+ <section id="features"><h2>Core capabilities</h2><div class="cards"><article class="card"><h3>Resume Anywhere ID</h3><p>Continue the same session from CLI, automation, or another machine using one stable session id.</p></article><article class="card"><h3>Automation Guardrails</h3><p>Classify risky automation paths and enforce approval policies before external side effects.</p></article><article class="card"><h3>Session Health Score</h3><p>Detect likely breakage early with health indicators for interruptions and loops.</p></article></div></section>
31
+ <section><h2>Architecture</h2><div class="arch">OpenSession runs as an execution continuity layer above your agent/runtime stack.<div class="layers"><div class="layer"><b>Surface</b>CLI / WebUI / TUI</div><div class="layer"><b>Core</b>Sessions / Events / Handoff</div><div class="layer"><b>Policy</b>Guardrails / Approvals</div><div class="layer"><b>Storage</b>Supabase + Artifact links</div></div></div></section>
32
+ <section id="updates"><h2>Whatโ€™s New</h2><div class="timeline"><div class="event"><b>v0.1.x โ€” continuity baseline</b><span>init/sync/start/resume/status/log stabilized with Supabase-backed runtime flow.</span></div><div class="event"><b>Viewer/TUI surfaces</b><span>read-only viewer and operational terminal flow added for daily ops visibility.</span></div><div class="event"><b>Reliability hardening</b><span>retry/idempotency and runbook-oriented operations added for unstable runtime scenarios.</span></div></div></section>
33
+ <section id="docs"><h2>Docs ยท One-command start</h2><div class="code"><div><span class="cmd">alias opss='npx -y @online5880/opensession'</span></div><div><span class="cmd">opss init</span> <span class="ok"># set Supabase URL/key</span></div><div><span class="cmd">opss sync --project demo</span></div><div><span class="cmd">opss start --project-key demo --actor mane</span></div><div><span class="cmd">opss status --project demo</span></div><div><span class="cmd">opss log --limit 50</span></div></div></section>
34
+ <footer>OpenSession ยท Execution continuity for AI agent operations ยท <a href="https://github.com/online5880/opensession" target="_blank" rel="noreferrer" style="color:#9fd6ff">GitHub</a></footer></div></body>
35
+ </html>
package/sql/schema.sql CHANGED
@@ -1,27 +1,38 @@
1
- create table if not exists projects (
2
- id uuid primary key default gen_random_uuid(),
3
- project_key text unique not null,
4
- name text not null,
5
- created_at timestamptz not null default now()
6
- );
7
-
8
- create table if not exists sessions (
9
- id uuid primary key default gen_random_uuid(),
10
- project_id uuid not null references projects(id) on delete cascade,
11
- actor text not null,
12
- status text not null default 'active',
13
- started_at timestamptz not null default now(),
14
- ended_at timestamptz
15
- );
16
-
17
- create index if not exists sessions_project_status_idx on sessions(project_id, status);
18
-
19
- create table if not exists session_events (
20
- id uuid primary key default gen_random_uuid(),
21
- session_id uuid not null references sessions(id) on delete cascade,
22
- type text not null,
23
- payload jsonb not null default '{}'::jsonb,
24
- created_at timestamptz not null default now()
25
- );
26
-
27
- create index if not exists session_events_session_created_idx on session_events(session_id, created_at);
1
+ create table if not exists projects (
2
+ id uuid primary key default gen_random_uuid(),
3
+ project_key text unique not null,
4
+ name text not null,
5
+ created_at timestamptz not null default now()
6
+ );
7
+
8
+ create table if not exists sessions (
9
+ id uuid primary key default gen_random_uuid(),
10
+ project_id uuid not null references projects(id) on delete cascade,
11
+ actor text not null,
12
+ status text not null default 'active',
13
+ started_at timestamptz not null default now(),
14
+ ended_at timestamptz
15
+ );
16
+
17
+ create index if not exists sessions_project_status_idx on sessions(project_id, status);
18
+
19
+ create table if not exists session_events (
20
+ id uuid primary key default gen_random_uuid(),
21
+ session_id uuid not null references sessions(id) on delete cascade,
22
+ type text not null,
23
+ idempotency_key text,
24
+ payload jsonb not null default '{}'::jsonb,
25
+ created_at timestamptz not null default now()
26
+ );
27
+
28
+ update session_events
29
+ set idempotency_key = payload->>'idempotencyKey'
30
+ where idempotency_key is null
31
+ and payload ? 'idempotencyKey'
32
+ and nullif(payload->>'idempotencyKey', '') is not null;
33
+
34
+ create index if not exists session_events_session_created_idx on session_events(session_id, created_at);
35
+ create index if not exists session_events_idempotency_idx on session_events(session_id, type, idempotency_key);
36
+ create unique index if not exists session_events_session_type_idempotency_key_uidx
37
+ on session_events(session_id, type, idempotency_key)
38
+ where idempotency_key is not null;
@@ -0,0 +1,197 @@
1
+ const DEFAULT_TIMEOUT_MS = 5000;
2
+
3
+ function withTimeout(ms) {
4
+ return AbortSignal.timeout(Number.isInteger(ms) && ms > 0 ? ms : DEFAULT_TIMEOUT_MS);
5
+ }
6
+
7
+ function normalizeArray(value) {
8
+ if (!Array.isArray(value)) {
9
+ return [];
10
+ }
11
+ return value;
12
+ }
13
+
14
+ function normalizeHeaders(value) {
15
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
16
+ return {};
17
+ }
18
+
19
+ const output = {};
20
+ for (const [key, headerValue] of Object.entries(value)) {
21
+ if (typeof headerValue === 'string' && headerValue.trim().length > 0) {
22
+ output[key] = headerValue;
23
+ }
24
+ }
25
+ return output;
26
+ }
27
+
28
+ function normalizeWebhookTarget(target) {
29
+ if (!target || typeof target !== 'object') {
30
+ return null;
31
+ }
32
+
33
+ const url = typeof target.url === 'string' ? target.url.trim() : '';
34
+ if (!url) {
35
+ return null;
36
+ }
37
+
38
+ const eventTypes = normalizeArray(target.eventTypes).filter((item) => typeof item === 'string' && item.trim().length > 0);
39
+ const source = typeof target.source === 'string' && target.source.trim().length > 0 ? target.source.trim() : null;
40
+
41
+ return {
42
+ type: 'webhook',
43
+ url,
44
+ eventTypes,
45
+ source,
46
+ headers: normalizeHeaders(target.headers),
47
+ timeoutMs: Number.isInteger(target.timeoutMs) && target.timeoutMs > 0 ? target.timeoutMs : DEFAULT_TIMEOUT_MS
48
+ };
49
+ }
50
+
51
+ function normalizeRule(rule) {
52
+ if (!rule || typeof rule !== 'object') {
53
+ return null;
54
+ }
55
+
56
+ const name = typeof rule.name === 'string' && rule.name.trim().length > 0 ? rule.name.trim() : 'unnamed-rule';
57
+ const when = rule.when && typeof rule.when === 'object' && !Array.isArray(rule.when) ? rule.when : {};
58
+ const actions = normalizeArray(rule.actions)
59
+ .map((action) => normalizeWebhookTarget(action))
60
+ .filter(Boolean);
61
+
62
+ if (actions.length === 0) {
63
+ return null;
64
+ }
65
+
66
+ return {
67
+ name,
68
+ when: {
69
+ eventType: typeof when.eventType === 'string' ? when.eventType.trim() : null,
70
+ source: typeof when.source === 'string' ? when.source.trim() : null,
71
+ projectKey: typeof when.projectKey === 'string' ? when.projectKey.trim() : null
72
+ },
73
+ actions
74
+ };
75
+ }
76
+
77
+ function shouldApplyTarget(target, envelope) {
78
+ if (!target) {
79
+ return false;
80
+ }
81
+
82
+ if (target.source && target.source !== envelope.source) {
83
+ return false;
84
+ }
85
+
86
+ if (target.eventTypes.length > 0 && !target.eventTypes.includes(envelope.eventType)) {
87
+ return false;
88
+ }
89
+
90
+ return true;
91
+ }
92
+
93
+ function ruleMatches(rule, envelope) {
94
+ const { when } = rule;
95
+
96
+ if (when.eventType && when.eventType !== envelope.eventType) {
97
+ return false;
98
+ }
99
+
100
+ if (when.source && when.source !== envelope.source) {
101
+ return false;
102
+ }
103
+
104
+ if (when.projectKey && when.projectKey !== envelope.projectKey) {
105
+ return false;
106
+ }
107
+
108
+ return true;
109
+ }
110
+
111
+ async function postJson(url, body, options = {}) {
112
+ const headers = {
113
+ 'Content-Type': 'application/json',
114
+ ...(options.headers ?? {})
115
+ };
116
+
117
+ const response = await fetch(url, {
118
+ method: 'POST',
119
+ headers,
120
+ body: JSON.stringify(body),
121
+ signal: withTimeout(options.timeoutMs)
122
+ });
123
+
124
+ const text = await response.text();
125
+ return {
126
+ ok: response.ok,
127
+ status: response.status,
128
+ body: text.slice(0, 400)
129
+ };
130
+ }
131
+
132
+ export async function dispatchAutomation(envelope, automationConfig = {}) {
133
+ const rules = normalizeArray(automationConfig.rules)
134
+ .map((rule) => normalizeRule(rule))
135
+ .filter(Boolean);
136
+ const directTargets = normalizeArray(automationConfig.webhooks)
137
+ .map((target) => normalizeWebhookTarget(target))
138
+ .filter(Boolean)
139
+ .filter((target) => shouldApplyTarget(target, envelope));
140
+
141
+ const results = [];
142
+
143
+ for (const target of directTargets) {
144
+ try {
145
+ const response = await postJson(target.url, envelope, target);
146
+ results.push({
147
+ channel: 'webhook',
148
+ mode: 'direct',
149
+ destination: target.url,
150
+ ...response
151
+ });
152
+ } catch (error) {
153
+ results.push({
154
+ channel: 'webhook',
155
+ mode: 'direct',
156
+ destination: target.url,
157
+ ok: false,
158
+ status: 0,
159
+ body: error instanceof Error ? error.message : String(error)
160
+ });
161
+ }
162
+ }
163
+
164
+ for (const rule of rules) {
165
+ if (!ruleMatches(rule, envelope)) {
166
+ continue;
167
+ }
168
+
169
+ for (const action of rule.actions) {
170
+ try {
171
+ const response = await postJson(action.url, {
172
+ rule: rule.name,
173
+ event: envelope
174
+ }, action);
175
+ results.push({
176
+ channel: 'webhook',
177
+ mode: 'rule',
178
+ rule: rule.name,
179
+ destination: action.url,
180
+ ...response
181
+ });
182
+ } catch (error) {
183
+ results.push({
184
+ channel: 'webhook',
185
+ mode: 'rule',
186
+ rule: rule.name,
187
+ destination: action.url,
188
+ ok: false,
189
+ status: 0,
190
+ body: error instanceof Error ? error.message : String(error)
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ return results;
197
+ }