woclaw-codex 0.1.0

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.md ADDED
@@ -0,0 +1,72 @@
1
+ # WoClaw Codex CLI Integration
2
+
3
+ Connect OpenAI Codex CLI sessions to a WoClaw Hub for **shared context across sessions and agents**.
4
+
5
+ ```
6
+ pip install aiohttp websockets # required by hook scripts
7
+ python3 install.py # one-command install
8
+ ```
9
+
10
+ ## What It Does
11
+
12
+ - **SessionStart Hook**: When Codex starts, loads shared project context from WoClaw Hub and injects it as developer context
13
+ - **Stop Hook**: When Codex ends, saves a transcript summary back to WoClaw Hub so future sessions can pick up where you left off
14
+
15
+ ## Requirements
16
+
17
+ - Python 3.8+
18
+ - `aiohttp` or standard `urllib` (stdlib, no extra deps needed for REST)
19
+ - WoClaw Hub running at `ws://vm153:8082` / `http://vm153:8083`
20
+
21
+ ## Quick Install
22
+
23
+ ```bash
24
+ # Clone WoClaw repo (if you have it)
25
+ cd packages/codex-woclaw
26
+
27
+ # Install hooks (one command)
28
+ python3 install.py
29
+ ```
30
+
31
+ This will:
32
+ 1. Copy `session_start.py` and `stop.py` to `~/.codex/hooks/`
33
+ 2. Create `~/.codex/hooks.json` with WoClaw hook configuration
34
+ 3. Enable `codex_hooks = true` in `~/.codex/config.toml`
35
+
36
+ Then start a Codex session — the hook runs automatically.
37
+
38
+ ## Uninstall
39
+
40
+ ```bash
41
+ python3 install.py --uninstall
42
+ ```
43
+
44
+ ## Environment Variables
45
+
46
+ | Variable | Default | Description |
47
+ |----------|---------|-------------|
48
+ | `WOCLAW_HUB_URL` | `http://vm153:8083` | Hub REST API URL |
49
+ | `WOCLAW_TOKEN` | `WoClaw2026` | Hub auth token |
50
+ | `WOCLAW_KEY` | `codex:context` | Memory key for context |
51
+
52
+ ## How It Works
53
+
54
+ The Codex CLI hooks system (`~/.codex/hooks.json`) fires Python scripts at key lifecycle events:
55
+
56
+ 1. **SessionStart** → `session_start.py` reads `WOCLAW_KEY` from WoClaw Hub REST API → injects as `additionalContext`
57
+ 2. **Stop** → `stop.py` reads session transcript → writes summary to WoClaw Hub under `WOCLAW_KEY`
58
+
59
+ ## NPM Package
60
+
61
+ Publishing as `woclaw-codex` npm package for easy distribution:
62
+
63
+ ```bash
64
+ cd packages/codex-woclaw
65
+ npm publish --access public
66
+ # → woclaw-codex on npm
67
+ ```
68
+
69
+ After npm install, users get:
70
+ ```
71
+ npx woclaw-codex install # installs hooks
72
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * woclaw-codex CLI
4
+ * Entry point for npm-installed package.
5
+ * Delegates to the Python install script.
6
+ */
7
+
8
+ const { spawn } = require("child_process");
9
+ const path = require("path");
10
+
11
+ const action = process.argv[2] || "install";
12
+
13
+ if (action === "install") {
14
+ console.log("Installing WoClaw Codex CLI hooks...");
15
+ const script = path.join(__dirname, "..", "install.py");
16
+ const child = spawn("python3", [script], { stdio: "inherit" });
17
+ child.on("exit", (code) => process.exit(code || 0));
18
+ } else if (action === "uninstall") {
19
+ console.log("Uninstalling WoClaw Codex CLI hooks...");
20
+ const script = path.join(__dirname, "..", "install.py");
21
+ const child = spawn("python3", [script, "--uninstall"], { stdio: "inherit" });
22
+ child.on("exit", (code) => process.exit(code || 0));
23
+ } else {
24
+ console.error(`Unknown action: ${action}`);
25
+ console.error("Usage: woclaw-codex [install|uninstall]");
26
+ process.exit(1);
27
+ }
package/install.py ADDED
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ WoClaw Codex CLI Installer
4
+
5
+ Configures Codex CLI to use WoClaw hooks for shared context across sessions.
6
+
7
+ Usage:
8
+ python3 install.py [--uninstall]
9
+
10
+ This script:
11
+ 1. Creates ~/.codex/hooks/ directory
12
+ 2. Copies hook scripts to ~/.codex/hooks/
13
+ 3. Creates ~/.codex/hooks.json with WoClaw hook configuration
14
+ 4. Enables hooks in ~/.codex/config.toml
15
+
16
+ Requires: Python 3.8+
17
+ """
18
+
19
+ import os
20
+ import sys
21
+ import json
22
+ import shutil
23
+ import argparse
24
+ from pathlib import Path
25
+
26
+
27
+ HOOKS_DIR = Path.home() / ".codex" / "hooks"
28
+ HOOKS_JSON = Path.home() / ".codex" / "hooks.json"
29
+ CONFIG_TOML = Path.home() / ".codex" / "config.toml"
30
+ SCRIPT_DIR = Path(__file__).parent
31
+
32
+
33
+ def ensure_hooks_dir():
34
+ HOOKS_DIR.mkdir(parents=True, exist_ok=True)
35
+ print(f"Created: {HOOKS_DIR}")
36
+
37
+
38
+ def copy_hooks():
39
+ for script in ["session_start.py", "stop.py"]:
40
+ src = SCRIPT_DIR / script
41
+ dst = HOOKS_DIR / script
42
+ shutil.copy2(src, dst)
43
+ os.chmod(dst, 0o755)
44
+ print(f"Copied: {dst}")
45
+
46
+
47
+ def create_hooks_json():
48
+ hooks_config = {
49
+ "hooks": {
50
+ "SessionStart": [
51
+ {
52
+ "matcher": "startup|resume",
53
+ "hooks": [
54
+ {
55
+ "type": "command",
56
+ "command": f"python3 {HOOKS_DIR / 'session_start.py'}",
57
+ "statusMessage": "Loading shared context from WoClaw"
58
+ }
59
+ ]
60
+ }
61
+ ],
62
+ "Stop": [
63
+ {
64
+ "hooks": [
65
+ {
66
+ "type": "command",
67
+ "command": f"python3 {HOOKS_DIR / 'stop.py'}",
68
+ "timeout": 30,
69
+ "statusMessage": "Saving session to WoClaw Hub"
70
+ }
71
+ ]
72
+ }
73
+ ]
74
+ }
75
+ }
76
+ with open(HOOKS_JSON, "w") as f:
77
+ json.dump(hooks_config, f, indent=2)
78
+ print(f"Created: {HOOKS_JSON}")
79
+
80
+
81
+ def update_config_toml():
82
+ """Enable hooks feature in config.toml."""
83
+ content = ""
84
+ if CONFIG_TOML.exists():
85
+ content = CONFIG_TOML.read_text()
86
+
87
+ if "[features]" in content and "codex_hooks" in content:
88
+ print(f"Hooks already enabled in {CONFIG_TOML}")
89
+ return
90
+
91
+ # Append or create config
92
+ addition = (
93
+ "\n[features]\n"
94
+ "codex_hooks = true\n"
95
+ )
96
+
97
+ if content.strip():
98
+ if not content.rstrip().endswith("\n"):
99
+ addition = "\n" + addition
100
+ content += addition
101
+ else:
102
+ content = addition
103
+
104
+ CONFIG_TOML.parent.mkdir(parents=True, exist_ok=True)
105
+ CONFIG_TOML.write_text(content)
106
+ print(f"Updated: {CONFIG_TOML} (enabled codex_hooks)")
107
+
108
+
109
+ def uninstall():
110
+ """Remove WoClaw hooks from Codex configuration."""
111
+ # Remove hook scripts
112
+ for script in ["session_start.py", "stop.py"]:
113
+ dst = HOOKS_DIR / script
114
+ if dst.exists():
115
+ dst.unlink()
116
+ print(f"Removed: {dst}")
117
+
118
+ # Remove hooks.json
119
+ if HOOKS_JSON.exists():
120
+ try:
121
+ data = json.loads(HOOKS_JSON.read_text())
122
+ if "hooks" in data and (
123
+ "SessionStart" in data["hooks"] or "Stop" in data["hooks"]
124
+ ):
125
+ # Clear WoClaw hooks from the config
126
+ for key in ["SessionStart", "Stop"]:
127
+ if key in data["hooks"]:
128
+ data["hooks"][key] = [
129
+ h for h in data["hooks"][key]
130
+ if not any(
131
+ "woclaw" in str(v).lower()
132
+ for v in (h.get("command") or "")
133
+ )
134
+ ]
135
+ HOOKS_JSON.write_text(json.dumps(data, indent=2))
136
+ print(f"Updated: {HOOKS_JSON} (removed WoClaw hooks)")
137
+ except (json.JSONDecodeError, OSError):
138
+ HOOKS_JSON.unlink()
139
+ print(f"Removed: {HOOKS_JSON}")
140
+
141
+
142
+ def main():
143
+ parser = argparse.ArgumentParser(description="Install WoClaw hooks for Codex CLI")
144
+ parser.add_argument("--uninstall", action="store_true", help="Remove WoClaw hooks")
145
+ args = parser.parse_args()
146
+
147
+ if args.uninstall:
148
+ print("Uninstalling WoClaw Codex hooks...")
149
+ uninstall()
150
+ print("Done.")
151
+ return
152
+
153
+ print("Installing WoClaw Codex CLI hooks...")
154
+ print(f"Hub: {os.environ.get('WOCLAW_HUB_URL', 'http://vm153:8083')}")
155
+ print(f"Token: {'***' + os.environ.get('WOCLAW_TOKEN', 'WoClaw2026')[-4:]}")
156
+ print()
157
+
158
+ ensure_hooks_dir()
159
+ copy_hooks()
160
+ create_hooks_json()
161
+ update_config_toml()
162
+
163
+ print()
164
+ print("WoClaw hooks installed!")
165
+ print()
166
+ print("Next steps:")
167
+ print(" 1. Start a new Codex session: codex")
168
+ print(" 2. On session start, shared context will be loaded from WoClaw Hub")
169
+ print(" 3. On session end, your work will be saved to WoClaw Hub")
170
+ print()
171
+ print("Environment variables (optional):")
172
+ print(" WOCLAW_HUB_URL - Hub REST URL (default: http://vm153:8083)")
173
+ print(" WOCLAW_TOKEN - Auth token (default: WoClaw2026)")
174
+ print(" WOCLAW_KEY - Memory key (default: codex:context)")
175
+
176
+
177
+ if __name__ == "__main__":
178
+ main()
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "woclaw-codex",
3
+ "version": "0.1.0",
4
+ "description": "WoClaw integration for OpenAI Codex CLI — shared context across sessions",
5
+ "keywords": [
6
+ "woclaw",
7
+ "codex",
8
+ "openai",
9
+ "ai-agent",
10
+ "shared-memory",
11
+ "context"
12
+ ],
13
+ "files": [
14
+ "*.py",
15
+ "install.py"
16
+ ],
17
+ "bin": {
18
+ "woclaw-codex": "./bin/cli.js"
19
+ },
20
+ "scripts": {
21
+ "install": "python3 install.py",
22
+ "uninstall": "python3 install.py --uninstall"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "license": "MIT"
28
+ }
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ WoClaw SessionStart Hook for OpenAI Codex CLI
4
+
5
+ Reads shared project context from WoClaw Hub when a Codex session starts.
6
+ Injects the context as additional developer context for the new session.
7
+
8
+ Requires: aiohttp, websockets (or use REST for simplicity)
9
+
10
+ Install:
11
+ python3 session_start.py --install
12
+
13
+ Environment:
14
+ WOCLAW_HUB_URL - Hub REST URL (default: http://vm153:8083)
15
+ WOCLAW_TOKEN - Auth token (default: WoClaw2026)
16
+ WOCLAW_KEY - Memory key to read (default: codex:context)
17
+ """
18
+
19
+ import os
20
+ import sys
21
+ import json
22
+ import urllib.request
23
+ import urllib.error
24
+
25
+
26
+ HUB_URL = os.environ.get("WOCLAW_HUB_URL", "http://vm153:8083")
27
+ TOKEN = os.environ.get("WOCLAW_TOKEN", "WoClaw2026")
28
+ MEMORY_KEY = os.environ.get("WOCLAW_KEY", "codex:context")
29
+
30
+
31
+ def read_memory(key: str) -> str | None:
32
+ """Read a value from WoClaw Hub REST API."""
33
+ url = f"{HUB_URL}/memory/{key}"
34
+ req = urllib.request.Request(
35
+ url,
36
+ headers={"Authorization": f"Bearer {TOKEN}"}
37
+ )
38
+ try:
39
+ with urllib.request.urlopen(req, timeout=5) as resp:
40
+ data = json.loads(resp.read())
41
+ if data.get("exists"):
42
+ return data.get("value")
43
+ except urllib.error.URLError:
44
+ pass
45
+ return None
46
+
47
+
48
+ def main():
49
+ # Read hook input from stdin
50
+ try:
51
+ hook_input = json.load(sys.stdin)
52
+ except (json.JSONDecodeError, EOFError):
53
+ hook_input = {}
54
+
55
+ source = hook_input.get("source", "startup")
56
+ cwd = hook_input.get("cwd", os.getcwd())
57
+
58
+ context = read_memory(MEMORY_KEY)
59
+
60
+ if context:
61
+ # Emit JSON output to inject as additional context
62
+ output = {
63
+ "hookSpecificOutput": {
64
+ "hookEventName": "SessionStart",
65
+ "additionalContext": f"[WoClaw] Shared project context:\n{context}"
66
+ }
67
+ }
68
+ print(json.dumps(output))
69
+ else:
70
+ # No shared context found — just continue silently
71
+ pass
72
+
73
+
74
+ if __name__ == "__main__":
75
+ main()
package/stop.py ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ WoClaw Stop Hook for OpenAI Codex CLI
4
+
5
+ Writes session summary to WoClaw Hub when a Codex session ends.
6
+ Helps preserve discoveries and decisions across sessions.
7
+
8
+ Install:
9
+ python3 stop.py --install
10
+
11
+ Environment:
12
+ WOCLAW_HUB_URL - Hub REST URL (default: http://vm153:8083)
13
+ WOCLAW_TOKEN - Auth token (default: WoClaw2026)
14
+ WOCLAW_KEY - Memory key to write (default: codex:context)
15
+ """
16
+
17
+ import os
18
+ import sys
19
+ import json
20
+ import urllib.request
21
+ import urllib.error
22
+ from datetime import datetime
23
+
24
+
25
+ HUB_URL = os.environ.get("WOCLAW_HUB_URL", "http://vm153:8083")
26
+ TOKEN = os.environ.get("WOCLAW_TOKEN", "WoClaw2026")
27
+ MEMORY_KEY = os.environ.get("WOCLAW_KEY", "codex:context")
28
+
29
+
30
+ def write_memory(key: str, value: str, hostname: str) -> bool:
31
+ """Write a value to WoClaw Hub REST API."""
32
+ url = f"{HUB_URL}/memory"
33
+ payload = json.dumps({
34
+ "key": key,
35
+ "value": value,
36
+ "updatedBy": f"codex:{hostname}",
37
+ "tags": ["codex", "session-summary"]
38
+ }).encode("utf-8")
39
+
40
+ req = urllib.request.Request(
41
+ url,
42
+ data=payload,
43
+ headers={
44
+ "Authorization": f"Bearer {TOKEN}",
45
+ "Content-Type": "application/json"
46
+ },
47
+ method="POST"
48
+ )
49
+ try:
50
+ with urllib.request.urlopen(req, timeout=5) as resp:
51
+ return resp.status == 200
52
+ except urllib.error.URLError:
53
+ return False
54
+
55
+
56
+ def read_transcript(path: str | None, max_lines: int = 80) -> str | None:
57
+ """Read the last lines of the session transcript."""
58
+ if not path:
59
+ return None
60
+ try:
61
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
62
+ lines = f.readlines()
63
+ return "".join(lines[-max_lines:])
64
+ except (OSError, IOError):
65
+ return None
66
+
67
+
68
+ def main():
69
+ # Read hook input from stdin
70
+ try:
71
+ hook_input = json.load(sys.stdin)
72
+ except (json.JSONDecodeError, EOFError):
73
+ hook_input = {}
74
+
75
+ session_id = hook_input.get("session_id", "unknown")
76
+ transcript_path = hook_input.get("transcript_path")
77
+ cwd = hook_input.get("cwd", os.getcwd())
78
+ stop_reason = hook_input.get("stopReason", "unknown")
79
+ hostname = os.uname().nodename
80
+
81
+ # Read transcript for context
82
+ transcript = read_transcript(transcript_path)
83
+
84
+ if transcript:
85
+ # Build a concise session summary
86
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
87
+ summary = (
88
+ f"[{timestamp}] Codex session ended ({stop_reason})\n"
89
+ f"Session: {session_id}\n"
90
+ f"CWD: {cwd}\n"
91
+ f"---Last transcript excerpt---\n"
92
+ f"{transcript[:1000]}"
93
+ )
94
+ success = write_memory(MEMORY_KEY, summary, hostname)
95
+ if success:
96
+ print(f"[WoClaw] Session summary saved to {MEMORY_KEY}", file=sys.stderr)
97
+ else:
98
+ print(f"[WoClaw] Failed to save session summary", file=sys.stderr)
99
+
100
+ # Emit continue response (don't block anything)
101
+ print(json.dumps({"continue": True}))
102
+
103
+
104
+ if __name__ == "__main__":
105
+ main()