claude-smart 0.2.23 → 0.2.25

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 (113) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/README.md +76 -28
  3. package/bin/claude-smart.js +355 -11
  4. package/package.json +11 -1
  5. package/plugin/.claude-plugin/plugin.json +17 -0
  6. package/plugin/.codex-plugin/plugin.json +35 -0
  7. package/plugin/LICENSE +202 -0
  8. package/plugin/README.md +37 -0
  9. package/plugin/bin/cs-cite +77 -0
  10. package/plugin/commands/clear-all.md +8 -0
  11. package/plugin/commands/dashboard.md +8 -0
  12. package/plugin/commands/learn.md +12 -0
  13. package/plugin/commands/restart.md +8 -0
  14. package/plugin/commands/show.md +8 -0
  15. package/plugin/dashboard/AGENTS.md +6 -0
  16. package/plugin/dashboard/app/api/claude-settings/route.ts +19 -0
  17. package/plugin/dashboard/app/api/config/route.ts +16 -0
  18. package/plugin/dashboard/app/api/health/route.ts +10 -0
  19. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +63 -0
  20. package/plugin/dashboard/app/api/sessions/[id]/route.ts +28 -0
  21. package/plugin/dashboard/app/api/sessions/route.ts +14 -0
  22. package/plugin/dashboard/app/configure/env/page.tsx +318 -0
  23. package/plugin/dashboard/app/configure/layout.tsx +47 -0
  24. package/plugin/dashboard/app/configure/page.tsx +5 -0
  25. package/plugin/dashboard/app/configure/server/page.tsx +258 -0
  26. package/plugin/dashboard/app/dashboard/page.tsx +227 -0
  27. package/plugin/dashboard/app/globals.css +129 -0
  28. package/plugin/dashboard/app/icon.png +0 -0
  29. package/plugin/dashboard/app/layout.tsx +40 -0
  30. package/plugin/dashboard/app/page.tsx +5 -0
  31. package/plugin/dashboard/app/preferences/[id]/page.tsx +531 -0
  32. package/plugin/dashboard/app/preferences/page.tsx +126 -0
  33. package/plugin/dashboard/app/providers.tsx +12 -0
  34. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +321 -0
  35. package/plugin/dashboard/app/sessions/page.tsx +186 -0
  36. package/plugin/dashboard/app/skills/page.tsx +362 -0
  37. package/plugin/dashboard/app/skills/project/[id]/page.tsx +597 -0
  38. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +830 -0
  39. package/plugin/dashboard/components/common/delete-all-button.tsx +45 -0
  40. package/plugin/dashboard/components/common/empty-state.tsx +34 -0
  41. package/plugin/dashboard/components/common/learnings-badge.tsx +34 -0
  42. package/plugin/dashboard/components/common/page-header.tsx +34 -0
  43. package/plugin/dashboard/components/common/page-tabs.tsx +115 -0
  44. package/plugin/dashboard/components/common/stat-card.tsx +38 -0
  45. package/plugin/dashboard/components/layout/nav-items.ts +22 -0
  46. package/plugin/dashboard/components/layout/sidebar.tsx +45 -0
  47. package/plugin/dashboard/components/layout/top-bar.tsx +64 -0
  48. package/plugin/dashboard/components/stall-banner.tsx +53 -0
  49. package/plugin/dashboard/components/ui/badge.tsx +52 -0
  50. package/plugin/dashboard/components/ui/button.tsx +60 -0
  51. package/plugin/dashboard/components/ui/collapsible.tsx +21 -0
  52. package/plugin/dashboard/components/ui/input.tsx +20 -0
  53. package/plugin/dashboard/components/ui/label.tsx +20 -0
  54. package/plugin/dashboard/components/ui/scroll-area.tsx +55 -0
  55. package/plugin/dashboard/components/ui/select.tsx +201 -0
  56. package/plugin/dashboard/components/ui/separator.tsx +25 -0
  57. package/plugin/dashboard/components/ui/sheet.tsx +135 -0
  58. package/plugin/dashboard/components/ui/switch.tsx +32 -0
  59. package/plugin/dashboard/components.json +25 -0
  60. package/plugin/dashboard/eslint.config.mjs +16 -0
  61. package/plugin/dashboard/hooks/use-settings.tsx +88 -0
  62. package/plugin/dashboard/hooks/use-stall-state.ts +59 -0
  63. package/plugin/dashboard/lib/claude-settings-file.ts +114 -0
  64. package/plugin/dashboard/lib/config-file.ts +131 -0
  65. package/plugin/dashboard/lib/format.ts +58 -0
  66. package/plugin/dashboard/lib/reflexio-client.ts +238 -0
  67. package/plugin/dashboard/lib/reflexio-url.ts +17 -0
  68. package/plugin/dashboard/lib/session-reader.ts +245 -0
  69. package/plugin/dashboard/lib/status.ts +24 -0
  70. package/plugin/dashboard/lib/types.ts +145 -0
  71. package/plugin/dashboard/lib/utils.ts +6 -0
  72. package/plugin/dashboard/next.config.ts +7 -0
  73. package/plugin/dashboard/package-lock.json +10275 -0
  74. package/plugin/dashboard/package.json +37 -0
  75. package/plugin/dashboard/postcss.config.mjs +7 -0
  76. package/plugin/dashboard/public/claude-smart-icon.png +0 -0
  77. package/plugin/dashboard/tsconfig.json +34 -0
  78. package/plugin/hooks/codex-hooks.json +67 -0
  79. package/plugin/hooks/hooks.json +111 -0
  80. package/plugin/pyproject.toml +49 -0
  81. package/plugin/scripts/_codex_env.sh +27 -0
  82. package/plugin/scripts/_lib.sh +325 -0
  83. package/plugin/scripts/backend-service.sh +208 -0
  84. package/plugin/scripts/cli.sh +40 -0
  85. package/plugin/scripts/dashboard-build.sh +139 -0
  86. package/plugin/scripts/dashboard-open.sh +107 -0
  87. package/plugin/scripts/dashboard-service.sh +195 -0
  88. package/plugin/scripts/ensure-plugin-root.sh +84 -0
  89. package/plugin/scripts/hook_entry.sh +70 -0
  90. package/plugin/scripts/smart-install.sh +411 -0
  91. package/plugin/src/claude_smart/__init__.py +3 -0
  92. package/plugin/src/claude_smart/cli.py +1342 -0
  93. package/plugin/src/claude_smart/context_format.py +277 -0
  94. package/plugin/src/claude_smart/context_inject.py +92 -0
  95. package/plugin/src/claude_smart/cs_cite.py +236 -0
  96. package/plugin/src/claude_smart/events/__init__.py +1 -0
  97. package/plugin/src/claude_smart/events/post_tool.py +148 -0
  98. package/plugin/src/claude_smart/events/pre_tool.py +52 -0
  99. package/plugin/src/claude_smart/events/session_end.py +20 -0
  100. package/plugin/src/claude_smart/events/session_start.py +119 -0
  101. package/plugin/src/claude_smart/events/stop.py +393 -0
  102. package/plugin/src/claude_smart/events/user_prompt.py +73 -0
  103. package/plugin/src/claude_smart/hook.py +114 -0
  104. package/plugin/src/claude_smart/ids.py +56 -0
  105. package/plugin/src/claude_smart/internal_call.py +89 -0
  106. package/plugin/src/claude_smart/optimizer_assistant.py +203 -0
  107. package/plugin/src/claude_smart/publish.py +71 -0
  108. package/plugin/src/claude_smart/query_compose.py +51 -0
  109. package/plugin/src/claude_smart/reflexio_adapter.py +403 -0
  110. package/plugin/src/claude_smart/runtime.py +52 -0
  111. package/plugin/src/claude_smart/stall_banner.py +61 -0
  112. package/plugin/src/claude_smart/state.py +276 -0
  113. package/plugin/uv.lock +3720 -0
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "reflexioai",
3
+ "interface": {
4
+ "displayName": "ReflexioAI"
5
+ },
6
+ "plugins": [
7
+ {
8
+ "name": "claude-smart",
9
+ "source": {
10
+ "source": "local",
11
+ "path": "./plugin"
12
+ },
13
+ "policy": {
14
+ "installation": "AVAILABLE",
15
+ "authentication": "ON_INSTALL"
16
+ },
17
+ "category": "Productivity"
18
+ }
19
+ ]
20
+ }
package/README.md CHANGED
@@ -6,14 +6,14 @@
6
6
  claude-smart
7
7
  </h1>
8
8
 
9
- <h4 align="center">The <a href="https://claude.com/claude-code" target="_blank">Claude Code</a> plugin that makes Claude Code self-improve as you use it so Claude Code stops repeating mistakes and gets better every session.</h4>
9
+ <h4 align="center">A local learning plugin for <a href="https://claude.com/claude-code" target="_blank">Claude Code</a> and Codex that turns corrections into durable rules your coding assistant follows in future sessions.</h4>
10
10
 
11
11
  <p align="center">
12
12
  <a href="LICENSE">
13
13
  <img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
14
14
  </a>
15
15
  <a href="plugin/pyproject.toml">
16
- <img src="https://img.shields.io/badge/version-0.2.23-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.25-green.svg" alt="Version">
17
17
  </a>
18
18
  <a href="plugin/pyproject.toml">
19
19
  <img src="https://img.shields.io/badge/python-%3E%3D3.12-brightgreen.svg" alt="Python">
@@ -22,7 +22,7 @@
22
22
  <img src="https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg" alt="Node">
23
23
  </a>
24
24
  <a href="#quick-start">
25
- <img src="https://img.shields.io/badge/llm-claude%20code%20cli-purple.svg" alt="LLM">
25
+ <img src="https://img.shields.io/badge/hosts-Claude%20Code%20%2B%20Codex-purple.svg" alt="Hosts">
26
26
  </a>
27
27
  <a href="https://discord.gg/Jbft3jPn">
28
28
  <img src="https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white" alt="Discord">
@@ -32,7 +32,7 @@
32
32
  <p align="center">
33
33
  <a href="#quick-start">Quick Start</a> •
34
34
  <a href="#how-it-works">How It Works</a> •
35
- <a href="#slash-commands">Slash Commands</a> •
35
+ <a href="#commands">Commands</a> •
36
36
  <a href="#dashboard">Dashboard</a> •
37
37
  <a href="#configuration">Configuration</a> •
38
38
  <a href="TROUBLESHOOTING.md">Troubleshooting</a> •
@@ -40,7 +40,7 @@
40
40
  </p>
41
41
 
42
42
  <p align="center">
43
- It learns both corrections and successful execution patterns—so Claude Code avoids repeating mistakes and reuses what works. Project-specific skills capture repo-local rules, and shared skills roll up durable patterns for reuse across projects.
43
+ It learns both corrections and successful execution patterns—so your coding assistant avoids repeating mistakes and reuses what works. Project-specific skills capture repo-local rules, and shared skills roll up durable patterns for reuse across projects.
44
44
  </p>
45
45
 
46
46
  <p align="center">
@@ -55,7 +55,7 @@ Most memory solutions are still mostly informative—Claude remembers what happe
55
55
 
56
56
  `claude-smart` focuses on learning instead.
57
57
 
58
- Four ways this changes what Claude Code can do for you:
58
+ Four ways this changes what your coding assistant can do for you:
59
59
 
60
60
  - 💡 **Stop repeating the same mistakes:** Produces actionable skills Claude can follow next time; memory only records what happened.
61
61
 
@@ -78,6 +78,8 @@ Four ways this changes what Claude Code can do for you:
78
78
 
79
79
  ## Quick Start
80
80
 
81
+ ### Claude Code
82
+
81
83
  ```bash
82
84
  claude plugin marketplace add ReflexioAI/claude-smart
83
85
  claude plugin install claude-smart@reflexioai
@@ -108,10 +110,50 @@ Or, if you already have Node.js or uv:
108
110
  npx claude-smart uninstall # or: uvx claude-smart uninstall
109
111
  ```
110
112
 
111
- Local data under `~/.reflexio/` and `~/.claude-smart/` is left in place — remove manually if desired.
113
+ ### Codex
114
+
115
+ You need the `codex` CLI on `PATH` and Node.js available for `npx`:
116
+
117
+ ```bash
118
+ npx claude-smart install --host codex
119
+ ```
120
+
121
+ The helper registers the bundled **ReflexioAI** marketplace with Codex, enables
122
+ Codex's `plugin_hooks` feature, installs `claude-smart` into Codex's local
123
+ plugin cache, and enables it in `~/.codex/config.toml`. Hooks are not active in
124
+ already-running Codex windows; after the command finishes:
125
+
126
+ 1. Fully quit and reopen Codex in your project so hooks reload.
127
+ 2. Run `/plugins` only if you want to verify `claude-smart` shows as installed
128
+ from the **ReflexioAI** marketplace.
129
+
130
+ If you install or toggle `claude-smart` manually from `/plugins`, still run
131
+ `npx claude-smart install --host codex` once afterward so `plugin_hooks` is
132
+ enabled and the cache/config are prepared.
133
+
134
+ Do not create a `~/plugins/claude-smart` symlink for a normal `npx` install;
135
+ that symlink is only for plugin development from a cloned checkout.
136
+
137
+ Installing from a clone is only for plugin development; see
138
+ [DEVELOPER.md](./DEVELOPER.md#developing-locally).
139
+
140
+ Codex and Claude Code intentionally share the same `CLAUDE_SMART_*` environment
141
+ variables, `~/.reflexio/` data, `~/.claude-smart/` session buffers, backend,
142
+ dashboard, and learned skills/preferences.
143
+
144
+
145
+ To uninstall:
146
+
147
+ ```bash
148
+ npx claude-smart uninstall --host codex
149
+ ```
150
+
151
+ This removes the Codex marketplace registration, installed plugin config, and
152
+ Codex plugin cache. Local data under `~/.reflexio/` and `~/.claude-smart/` is
153
+ left in place — remove manually if desired. Restart Codex after uninstalling.
112
154
 
113
155
  Developing the plugin itself? See [DEVELOPER.md](./DEVELOPER.md#developing-locally).
114
- > **Not supported:** Claude Code Cowork and claude.ai/code web — they run in a remote sandbox, so the local backend/dashboard and `~/.reflexio/` aren't reachable.
156
+ > **Not supported:** Claude Code Cowork, claude.ai/code web, or remote Codex environments without local plugin hooks — they run outside your local machine, so the local backend/dashboard and `~/.reflexio/` aren't reachable.
115
157
 
116
158
  ---
117
159
 
@@ -124,13 +166,13 @@ Developing the plugin itself? See [DEVELOPER.md](./DEVELOPER.md#developing-local
124
166
  - 🔌 **No external API call** — semantic search runs on an in-process ONNX embedder (all-MiniLM-L6-v2), and all data (preferences, skills, interaction buffers) is stored locally on your machine (`~/.reflexio/` and `~/.claude-smart/`).
125
167
  - 🔎 **Hybrid search** — Skills and preferences are indexed with vector + BM25 search for fast, robust retrieval.
126
168
  - 🧪 **Offline resilience** — If the reflexio backend is down, hooks buffer to disk; the next successful publish drains them.
127
- - 🧰 **Manual correction marker** — `/claude-smart:learn` flags the last turn as a correction so the extractor weights it heavily.
169
+ - 🧰 **Manual correction marker** — `/claude-smart:learn` in Claude Code, or `bash ~/.reflexio/plugin-root/scripts/cli.sh learn` in Codex, flags the last turn as a correction so the extractor weights it heavily.
128
170
 
129
171
  ---
130
172
 
131
173
  ## Dashboard
132
174
 
133
- A web UI for browsing session histories, inspecting preferences, and editing project-specific and shared skills. The dashboard auto-starts alongside the backend, so you can open **http://localhost:3001** directly. Or run `/claude-smart:dashboard` in Claude Code to launch dashboard in browser.
175
+ A web UI for browsing session histories, inspecting preferences, and editing project-specific and shared skills. The dashboard auto-starts alongside the backend, so you can open **http://localhost:3001** directly. Or run `/claude-smart:dashboard` in Claude Code to launch dashboard in browser. In Codex, run `bash ~/.reflexio/plugin-root/scripts/dashboard-open.sh`.
134
176
 
135
177
  <p align="center">
136
178
  <img src="assets/preferences_dashboard.png" alt="Preferences dashboard" width="49%">
@@ -141,7 +183,7 @@ A web UI for browsing session histories, inspecting preferences, and editing pro
141
183
 
142
184
  ## How It Works
143
185
 
144
- claude-smart builds three artifacts as you work and injects the relevant ones into Claude:
186
+ claude-smart builds three artifacts as you work and injects the relevant ones into Claude Code or Codex:
145
187
 
146
188
  - **Preferences** (project-scoped) — how you work in this specific repo (stack, role, small quirks). *e.g.* "uses pnpm, not npm"; "prefers terse answers"; "backend engineer — explain frontend with backend analogues."
147
189
  - **Project-specific skills** — durable rules with triggers and rationales learned from corrections in a project. *e.g.* "always pass `--run` to `npm test` — watch mode hangs CI."
@@ -149,32 +191,36 @@ claude-smart builds three artifacts as you work and injects the relevant ones in
149
191
 
150
192
  Skills clean themselves up: correct the same thing twice and they merge; change your mind and the old one is archived.
151
193
 
152
- Under the hood: hooks watch your turns, tool calls, and Claude's replies, auto-flagging corrections (or anything you flag with `/learn`). At session end (or on `/learn`), [reflexio](https://github.com/ReflexioAI/reflexio) — the self-improving engine that powers claude-smart — extracts preferences and project-specific skills, then rolls durable patterns into shared skills. On each new user prompt, claude-smart searches for matching context and injects only the relevant hits. Run `/show` to audit the current learned state. Everything runs on your machine.
194
+ Under the hood: hooks watch your turns, tool calls, and assistant replies, auto-flagging corrections (or anything you flag with `/claude-smart:learn`). At session end (or on `/claude-smart:learn`), [reflexio](https://github.com/ReflexioAI/reflexio) — the self-improving engine that powers claude-smart — extracts preferences and project-specific skills, then rolls durable patterns into shared skills. On each new user prompt, claude-smart searches for matching context and injects only the relevant hits. Run `/claude-smart:show` or the equivalent CLI command to audit the current learned state. Everything runs on your machine.
153
195
 
154
- **Citations (`cs-cite`).** At the end of a reply, Claude may run:
196
+ **Citations.** At the end of a reply, the assistant may append a short marker:
155
197
 
156
198
  ```
157
- Bash(cs-cite s1-252,p1-5aed)
158
- ⎿ (No output)
159
-
160
- ⏺ ✨ 2 claude-smart learnings applied
199
+ 2 claude-smart learnings applied [cs:s1-252,p1-5aed]
161
200
  ```
162
201
 
163
- That signals a preference (`p…`) or skill (`s…`) materially shaped the reply. Open the interaction's detail page in the [dashboard](#dashboard) to see the exact cited item.
202
+ That signals a preference (`p…`) or skill (`s…`) materially shaped the reply.
203
+ Standalone wrappers like `✨abc123✨` are not claude-smart citations and will not
204
+ link back to dashboard entries. Open the interaction's detail page in the
205
+ [dashboard](#dashboard) to see the exact cited item.
164
206
 
165
207
  See [ARCHITECTURE.md](./ARCHITECTURE.md) for hooks, data flow, and reflexio details.
166
208
 
167
209
  ---
168
210
 
169
- ## Slash Commands
211
+ ## Commands
170
212
 
171
- | Command | What it does |
172
- | --- | --- |
173
- | `/dashboard` | Open the dashboard in your browser, auto-starting the reflexio backend and dashboard services if they aren't already running. |
174
- | `/show` | Print current project-specific skills, shared skills, and the current project's preferences so you can audit learned state manually. |
175
- | `/learn [note]` | Flag the most recent turn as a correction (for cases the automatic heuristic missed) and force reflexio to run extraction *now* on the session's unpublished interactions. The optional note becomes the correction description the extractor sees. |
176
- | `/restart` | Restart the reflexio backend and dashboard to pick up new changes (e.g. after upgrading the plugin or editing local reflexio code). |
177
- | `/clear-all` | **Destructive.** Delete *all* reflexio interactions, preferences, and skills. Use when you want to wipe learned state and start fresh. |
213
+ Claude Code installs these as plugin slash commands. Codex does not currently
214
+ support these plugin-provided slash commands, so run the equivalent shell command
215
+ directly, or ask Codex to run it.
216
+
217
+ | Claude Code | Codex / shell | What it does |
218
+ | --- | --- | --- |
219
+ | `/claude-smart:dashboard` | `bash ~/.reflexio/plugin-root/scripts/dashboard-open.sh` | Open the dashboard in your browser, auto-starting the reflexio backend and dashboard services if they aren't already running. |
220
+ | `/claude-smart:show` | `bash ~/.reflexio/plugin-root/scripts/cli.sh show` | Print current project-specific skills, shared skills, and the current project's preferences so you can audit learned state manually. |
221
+ | `/claude-smart:learn [note]` | `bash ~/.reflexio/plugin-root/scripts/cli.sh learn --note "optional note"` | Flag the most recent turn as a correction (for cases the automatic heuristic missed) and force reflexio to run extraction *now* on the session's unpublished interactions. The optional note becomes the correction description the extractor sees. |
222
+ | `/claude-smart:restart` | `bash ~/.reflexio/plugin-root/scripts/cli.sh restart` | Restart the reflexio backend and dashboard to pick up new changes (e.g. after upgrading the plugin or editing local reflexio code). |
223
+ | `/claude-smart:clear-all` | `bash ~/.reflexio/plugin-root/scripts/cli.sh clear-all --yes` | **Destructive.** Delete *all* reflexio interactions, preferences, and skills. Use when you want to wipe learned state and start fresh. |
178
224
 
179
225
  ---
180
226
 
@@ -189,7 +235,9 @@ Advanced users can tune claude-smart via environment variables — see [DEVELOPE
189
235
  | `~/.reflexio/data/reflexio.db` | Source of truth for learned preferences, skills, interactions, full-text indexes, and embedding tables (plus `.db-shm` / `.db-wal` WAL sidecars). Inspect with `sqlite3`. |
190
236
  | `~/.reflexio/.env` | Provider config — `CLAUDE_SMART_USE_LOCAL_CLI`, `CLAUDE_SMART_USE_LOCAL_EMBEDDING`, any optional API keys. |
191
237
  | `.claude/settings.local.json` or `~/.claude/settings.json` | Claude Code hook environment, such as `CLAUDE_SMART_ENABLE_OPTIMIZER`; use project-local settings for one repo or user settings for all projects. |
192
- | `~/.reflexio/plugin-root` | Self-healed symlink to the active plugin dir (managed by `ensure-plugin-root.sh` — written on install, refreshed each `SessionStart`). Slash commands resolve through it, so don't delete it; if you do, the next session will recreate it. |
238
+ | `~/.codex/config.toml` | Codex feature flags, including `plugin_hooks = true` after `claude-smart install --host codex`. |
239
+ | `~/.codex/plugins/cache/reflexioai/claude-smart/<version>/` | Codex's cached install of the `claude-smart` plugin from the `ReflexioAI` marketplace. |
240
+ | `~/.reflexio/plugin-root` | Self-healed symlink to the active plugin dir (managed by `ensure-plugin-root.sh` — written on install, refreshed each `SessionStart`). Claude Code slash commands and Codex shell-command helpers resolve through it, so don't delete it; if you do, the next session will recreate it. |
193
241
  | `~/.claude-smart/sessions/{session_id}.jsonl` | Per-session buffer. User turns, assistant turns, tool invocations, `{"published_up_to": N}` watermarks. Safe to inspect and safe to delete — everything past the latest watermark has already been written to reflexio's DB. |
194
242
  | `~/.cache/chroma/onnx_models/all-MiniLM-L6-v2/` | Cached ONNX weights (~86 MB, downloaded once). Delete to force a re-download. |
195
243
 
@@ -211,4 +259,4 @@ See the [LICENSE](LICENSE) file for details.
211
259
 
212
260
  ---
213
261
 
214
- **Built on** [reflexio](https://github.com/ReflexioAI/reflexio) · **Runs on** [Claude Code](https://claude.com/claude-code) · **Written in** Python 3.12+
262
+ **Built on** [reflexio](https://github.com/ReflexioAI/reflexio) · **Runs on** [Claude Code](https://claude.com/claude-code) and Codex · **Written in** Python 3.12+
@@ -1,22 +1,75 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * npx claude-smart install — thin wrapper around the native Claude Code
4
- * plugin CLI. Registers the GitHub marketplace, installs the plugin, and
5
- * seeds ~/.reflexio/.env with the two local-provider flags so reflexio
6
- * can route generation through the local `claude` CLI with no API key.
3
+ * npx claude-smart install — thin wrapper around the native host plugin
4
+ * CLIs. For Claude Code it registers the GitHub marketplace and installs the
5
+ * plugin. For Codex it copies the bundled local marketplace, registers it,
6
+ * and enables plugin hooks. Both paths seed ~/.reflexio/.env with the two
7
+ * local-provider flags so reflexio can route generation through local tools
8
+ * with no API key.
7
9
  *
8
10
  * Keep this file dependency-free — it runs via `npx` with no install step.
9
11
  */
10
12
  "use strict";
11
13
 
12
- const { execFileSync, execSync, spawn } = require("child_process");
13
- const { appendFileSync, existsSync, mkdirSync, readFileSync } = require("fs");
14
+ const { execSync, spawn } = require("child_process");
15
+ const {
16
+ appendFileSync,
17
+ cpSync,
18
+ existsSync,
19
+ mkdirSync,
20
+ readFileSync,
21
+ rmSync,
22
+ writeFileSync,
23
+ } = require("fs");
14
24
  const { homedir } = require("os");
15
25
  const { dirname, join } = require("path");
16
26
 
17
27
  const DEFAULT_MARKETPLACE_SOURCE = "ReflexioAI/claude-smart";
18
28
  const PLUGIN_SPEC = "claude-smart@reflexioai";
29
+ const CODEX_MARKETPLACE_NAME = "reflexioai";
30
+ const CODEX_MARKETPLACE_DISPLAY_NAME = "ReflexioAI";
31
+ const CODEX_PLUGIN_ID = `claude-smart@${CODEX_MARKETPLACE_NAME}`;
19
32
  const REFLEXIO_ENV_PATH = join(homedir(), ".reflexio", ".env");
33
+ const CODEX_CONFIG_PATH = join(homedir(), ".codex", "config.toml");
34
+ const PACKAGE_ROOT = dirname(dirname(__filename));
35
+ const CODEX_MARKETPLACE_DIR = join(
36
+ homedir(),
37
+ ".claude",
38
+ "plugins",
39
+ "marketplaces",
40
+ CODEX_MARKETPLACE_NAME,
41
+ );
42
+ const CODEX_MARKETPLACE_PLUGIN_PATH = join("plugins", "claude-smart");
43
+ const CODEX_PLUGIN_CACHE_DIR = join(
44
+ homedir(),
45
+ ".codex",
46
+ "plugins",
47
+ "cache",
48
+ CODEX_MARKETPLACE_NAME,
49
+ "claude-smart",
50
+ );
51
+ const CODEX_REQUIRED_FILES = [
52
+ ".agents/plugins/marketplace.json",
53
+ "plugin/.codex-plugin/plugin.json",
54
+ "plugin/hooks/codex-hooks.json",
55
+ "plugin/scripts/_codex_env.sh",
56
+ ];
57
+ const CODEX_CLI_TIMEOUT_MS = 30_000;
58
+ const COPYTREE_IGNORE_NAMES = new Set([
59
+ "__pycache__",
60
+ ".venv",
61
+ ".pytest_cache",
62
+ ".ruff_cache",
63
+ "node_modules",
64
+ ".next",
65
+ ]);
66
+
67
+ function shouldCopyPath(src) {
68
+ const base = src.split(/[\\/]/).pop() || "";
69
+ if (COPYTREE_IGNORE_NAMES.has(base)) return false;
70
+ if (base.endsWith(".pyc") || base.endsWith(".pyo")) return false;
71
+ return true;
72
+ }
20
73
 
21
74
  function runClaude(args, { spinnerLabel } = {}) {
22
75
  const useSpinner = Boolean(spinnerLabel) && process.stdout.isTTY && !process.env.CI;
@@ -80,7 +133,11 @@ function runClaude(args, { spinnerLabel } = {}) {
80
133
  }
81
134
 
82
135
  function hasClaudeCli() {
83
- const probe = process.platform === "win32" ? "where claude" : "command -v claude";
136
+ return hasCli("claude");
137
+ }
138
+
139
+ function hasCli(name) {
140
+ const probe = process.platform === "win32" ? `where ${name}` : `command -v ${name}`;
84
141
  try {
85
142
  execSync(probe, { stdio: "ignore" });
86
143
  return true;
@@ -89,6 +146,30 @@ function hasClaudeCli() {
89
146
  }
90
147
  }
91
148
 
149
+ function runCodex(args) {
150
+ return new Promise((resolve) => {
151
+ const child = spawn("codex", args, {
152
+ stdio: "inherit",
153
+ timeout: CODEX_CLI_TIMEOUT_MS,
154
+ killSignal: "SIGTERM",
155
+ });
156
+ let timedOut = false;
157
+ child.on("exit", (code, signal) => {
158
+ if (signal === "SIGTERM" && code === null) {
159
+ timedOut = true;
160
+ process.stderr.write(
161
+ `error: codex ${args.join(" ")} timed out after ${CODEX_CLI_TIMEOUT_MS / 1000}s\n`,
162
+ );
163
+ resolve(124);
164
+ return;
165
+ }
166
+ if (timedOut) return;
167
+ resolve(typeof code === "number" ? code : 1);
168
+ });
169
+ child.on("error", () => resolve(1));
170
+ });
171
+ }
172
+
92
173
  function seedReflexioEnv() {
93
174
  mkdirSync(dirname(REFLEXIO_ENV_PATH), { recursive: true });
94
175
  const existing = existsSync(REFLEXIO_ENV_PATH)
@@ -106,19 +187,28 @@ function seedReflexioEnv() {
106
187
  function printHelp() {
107
188
  process.stdout.write(
108
189
  [
109
- "claude-smart — install helper for the Claude Code plugin",
190
+ "claude-smart — install helper for Claude Code and Codex",
110
191
  "",
111
192
  "Usage:",
112
193
  " npx claude-smart install Install the plugin into Claude Code",
194
+ " npx claude-smart install --host codex Register the plugin marketplace for Codex",
113
195
  " npx claude-smart install --source <owner/repo> Override the marketplace source",
196
+ " npx claude-smart uninstall --host codex Remove the Codex marketplace registration",
114
197
  " npx claude-smart --help Show this help",
115
198
  "",
116
- "What it does:",
199
+ "Claude Code install:",
117
200
  " 1. claude plugin marketplace add <source>",
118
201
  ` 2. claude plugin install ${PLUGIN_SPEC}`,
119
202
  " 3. Appends CLAUDE_SMART_USE_LOCAL_CLI=1 and CLAUDE_SMART_USE_LOCAL_EMBEDDING=1",
120
203
  " to ~/.reflexio/.env (idempotent).",
121
204
  "",
205
+ "Codex install:",
206
+ ` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
207
+ " 2. codex plugin marketplace add <copied marketplace>",
208
+ " 3. codex features enable plugin_hooks",
209
+ " 4. Installs claude-smart into Codex's plugin cache and enables it",
210
+ " 5. Restart Codex.",
211
+ "",
122
212
  "Update:",
123
213
  " npx claude-smart update Update to the latest version",
124
214
  "",
@@ -140,6 +230,165 @@ function parseSource(args) {
140
230
  return value;
141
231
  }
142
232
 
233
+ function parseHost(args) {
234
+ const idx = args.indexOf("--host");
235
+ if (idx === -1) return "claude-code";
236
+ const value = args[idx + 1];
237
+ if (!value) {
238
+ process.stderr.write("error: --host requires a value: claude-code or codex\n");
239
+ process.exit(1);
240
+ }
241
+ if (value !== "claude-code" && value !== "codex") {
242
+ process.stderr.write("error: --host must be claude-code or codex\n");
243
+ process.exit(1);
244
+ }
245
+ return value;
246
+ }
247
+
248
+ function copyCodexMarketplace() {
249
+ for (const rel of CODEX_REQUIRED_FILES) {
250
+ const path = join(PACKAGE_ROOT, rel);
251
+ if (!existsSync(path)) {
252
+ process.stderr.write(
253
+ `error: published package is missing ${rel}; reinstall claude-smart or use a newer release\n`,
254
+ );
255
+ process.exit(1);
256
+ }
257
+ }
258
+
259
+ rmSync(CODEX_MARKETPLACE_DIR, { recursive: true, force: true });
260
+ mkdirSync(join(CODEX_MARKETPLACE_DIR, ".agents", "plugins"), { recursive: true });
261
+ mkdirSync(join(CODEX_MARKETPLACE_DIR, "plugins"), { recursive: true });
262
+
263
+ writeFileSync(
264
+ join(CODEX_MARKETPLACE_DIR, ".agents", "plugins", "marketplace.json"),
265
+ JSON.stringify(
266
+ {
267
+ name: CODEX_MARKETPLACE_NAME,
268
+ interface: { displayName: CODEX_MARKETPLACE_DISPLAY_NAME },
269
+ plugins: [
270
+ {
271
+ name: "claude-smart",
272
+ source: {
273
+ source: "local",
274
+ path: `./${CODEX_MARKETPLACE_PLUGIN_PATH}`,
275
+ },
276
+ policy: {
277
+ installation: "AVAILABLE",
278
+ authentication: "ON_INSTALL",
279
+ },
280
+ category: "Productivity",
281
+ },
282
+ ],
283
+ },
284
+ null,
285
+ 2,
286
+ ) + "\n",
287
+ );
288
+
289
+ cpSync(join(PACKAGE_ROOT, "plugin"), join(CODEX_MARKETPLACE_DIR, CODEX_MARKETPLACE_PLUGIN_PATH), {
290
+ recursive: true,
291
+ force: true,
292
+ verbatimSymlinks: false,
293
+ filter: shouldCopyPath,
294
+ });
295
+
296
+ for (const rel of ["README.md", "LICENSE", "package.json"]) {
297
+ const src = join(PACKAGE_ROOT, rel);
298
+ if (existsSync(src)) {
299
+ cpSync(src, join(CODEX_MARKETPLACE_DIR, rel), {
300
+ recursive: true,
301
+ force: true,
302
+ verbatimSymlinks: false,
303
+ });
304
+ }
305
+ }
306
+ return CODEX_MARKETPLACE_DIR;
307
+ }
308
+
309
+ function removeTomlSections(path, { exact, prefixes = [] }) {
310
+ if (!existsSync(path)) return true;
311
+ const text = readFileSync(path, "utf8");
312
+ if (!text) return true;
313
+
314
+ let changed = false;
315
+ let dropping = false;
316
+ const lines = text.split(/(?<=\n)/);
317
+ const kept = [];
318
+ for (const line of lines) {
319
+ const match = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/);
320
+ if (match) {
321
+ const name = match[1].trim();
322
+ dropping = exact.has(name) || prefixes.some((prefix) => name.startsWith(prefix));
323
+ changed = changed || dropping;
324
+ }
325
+ if (!dropping) kept.push(line);
326
+ }
327
+ if (changed) writeFileSync(path, kept.join(""));
328
+ return true;
329
+ }
330
+
331
+ function cleanupCodexInstallState() {
332
+ removeTomlSections(CODEX_CONFIG_PATH, {
333
+ exact: new Set([
334
+ `plugins."${CODEX_PLUGIN_ID}"`,
335
+ `marketplaces.${CODEX_MARKETPLACE_NAME}`,
336
+ ]),
337
+ prefixes: [`hooks.state."${CODEX_PLUGIN_ID}:`],
338
+ });
339
+ rmSync(CODEX_MARKETPLACE_DIR, { recursive: true, force: true });
340
+ rmSync(CODEX_PLUGIN_CACHE_DIR, { recursive: true, force: true });
341
+ try {
342
+ rmSync(dirname(CODEX_PLUGIN_CACHE_DIR), { recursive: false, force: true });
343
+ } catch {
344
+ // Leave the marketplace cache parent if Codex has other entries there.
345
+ }
346
+ }
347
+
348
+ function setCodexPluginEnabled() {
349
+ const sectionName = `plugins."${CODEX_PLUGIN_ID}"`;
350
+ removeTomlSections(CODEX_CONFIG_PATH, { exact: new Set([sectionName]) });
351
+ const existing = existsSync(CODEX_CONFIG_PATH)
352
+ ? readFileSync(CODEX_CONFIG_PATH, "utf8")
353
+ : "";
354
+ let next = existing;
355
+ if (next && !next.endsWith("\n")) next += "\n";
356
+ if (next.trim()) next += "\n";
357
+ next += `[${sectionName}]\nenabled = true\n`;
358
+ mkdirSync(dirname(CODEX_CONFIG_PATH), { recursive: true });
359
+ writeFileSync(CODEX_CONFIG_PATH, next);
360
+ }
361
+
362
+ function codexPluginVersion(pluginRoot) {
363
+ try {
364
+ const manifest = JSON.parse(
365
+ readFileSync(join(pluginRoot, ".codex-plugin", "plugin.json"), "utf8"),
366
+ );
367
+ return typeof manifest.version === "string" && manifest.version
368
+ ? manifest.version
369
+ : null;
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+
375
+ function installCodexPluginCache(pluginRoot) {
376
+ const version = codexPluginVersion(pluginRoot);
377
+ if (!version) {
378
+ throw new Error(`missing version in ${join(pluginRoot, ".codex-plugin", "plugin.json")}`);
379
+ }
380
+ const cacheDir = join(CODEX_PLUGIN_CACHE_DIR, version);
381
+ rmSync(cacheDir, { recursive: true, force: true });
382
+ mkdirSync(dirname(cacheDir), { recursive: true });
383
+ cpSync(pluginRoot, cacheDir, {
384
+ recursive: true,
385
+ force: true,
386
+ verbatimSymlinks: false,
387
+ });
388
+ setCodexPluginEnabled();
389
+ return cacheDir;
390
+ }
391
+
143
392
  async function runUpdate() {
144
393
  if (!hasClaudeCli()) {
145
394
  process.stderr.write(
@@ -160,7 +409,12 @@ async function runUpdate() {
160
409
  process.stdout.write("\nclaude-smart updated. Restart Claude Code to apply.\n");
161
410
  }
162
411
 
163
- async function runUninstall() {
412
+ async function runUninstall(args) {
413
+ if (parseHost(args) === "codex") {
414
+ await runUninstallCodex();
415
+ return;
416
+ }
417
+
164
418
  if (!hasClaudeCli()) {
165
419
  process.stderr.write(
166
420
  "error: 'claude' CLI not found on PATH. " +
@@ -190,6 +444,11 @@ async function runUninstall() {
190
444
  }
191
445
 
192
446
  async function runInstall(args) {
447
+ if (parseHost(args) === "codex") {
448
+ await runInstallCodex();
449
+ return;
450
+ }
451
+
193
452
  if (!hasClaudeCli()) {
194
453
  process.stderr.write(
195
454
  "error: 'claude' CLI not found on PATH. " +
@@ -232,6 +491,91 @@ async function runInstall(args) {
232
491
  );
233
492
  }
234
493
 
494
+ async function runInstallCodex() {
495
+ if (!hasCli("codex")) {
496
+ process.stderr.write("error: 'codex' CLI not found on PATH. Install Codex first.\n");
497
+ process.exit(1);
498
+ }
499
+
500
+ const marketplaceRoot = copyCodexMarketplace();
501
+ process.stdout.write(`Prepared Codex marketplace at ${marketplaceRoot}.\n`);
502
+
503
+ let code = await runCodex(["plugin", "marketplace", "add", marketplaceRoot]);
504
+ if (code !== 0) {
505
+ process.stderr.write(
506
+ `warning: \`codex plugin marketplace add ${marketplaceRoot}\` failed; retrying after removing ${CODEX_MARKETPLACE_NAME}.\n`,
507
+ );
508
+ await runCodex(["plugin", "marketplace", "remove", CODEX_MARKETPLACE_NAME]);
509
+ code = await runCodex(["plugin", "marketplace", "add", marketplaceRoot]);
510
+ }
511
+ if (code !== 0) {
512
+ process.stderr.write(
513
+ `error: could not register Codex marketplace. Run manually: codex plugin marketplace add ${marketplaceRoot}\n`,
514
+ );
515
+ process.exit(code);
516
+ }
517
+
518
+ code = await runCodex(["features", "enable", "plugin_hooks"]);
519
+ if (code !== 0) {
520
+ process.stderr.write("error: could not enable Codex plugin_hooks feature.\n");
521
+ process.exit(code);
522
+ }
523
+
524
+ let cacheDir = null;
525
+ try {
526
+ cacheDir = installCodexPluginCache(join(marketplaceRoot, CODEX_MARKETPLACE_PLUGIN_PATH));
527
+ process.stdout.write(`Installed Codex plugin cache at ${cacheDir}.\n`);
528
+ } catch (err) {
529
+ process.stderr.write(
530
+ `error: automatic Codex plugin install failed: ${err && err.message ? err.message : err}\n`,
531
+ );
532
+ process.stderr.write(
533
+ `Open Codex, run /plugins, and install claude-smart from the ${CODEX_MARKETPLACE_DISPLAY_NAME} marketplace manually.\n`,
534
+ );
535
+ process.exit(1);
536
+ }
537
+
538
+ const added = seedReflexioEnv();
539
+ if (added.length > 0) {
540
+ process.stdout.write(`Seeded ${REFLEXIO_ENV_PATH} with ${added.join(", ")}.\n`);
541
+ }
542
+
543
+ process.stdout.write(
544
+ [
545
+ "",
546
+ "claude-smart Codex support is installed.",
547
+ `Restart Codex so the installed plugin and hooks reload. /plugins should show claude-smart as installed from the ${CODEX_MARKETPLACE_DISPLAY_NAME} marketplace.`,
548
+ "Local data is shared with Claude Code under ~/.reflexio/ and ~/.claude-smart/.",
549
+ "",
550
+ ].join("\n"),
551
+ );
552
+ }
553
+
554
+ async function runUninstallCodex() {
555
+ if (!hasCli("codex")) {
556
+ process.stdout.write("Codex CLI not found; skipping marketplace removal.\n");
557
+ cleanupCodexInstallState();
558
+ return;
559
+ }
560
+
561
+ const code = await runCodex(["plugin", "marketplace", "remove", CODEX_MARKETPLACE_NAME]);
562
+ if (code !== 0) {
563
+ process.stderr.write(
564
+ `warning: Codex marketplace removal failed; remove manually with: codex plugin marketplace remove ${CODEX_MARKETPLACE_NAME}\n`,
565
+ );
566
+ }
567
+ cleanupCodexInstallState();
568
+
569
+ process.stdout.write(
570
+ [
571
+ "",
572
+ "claude-smart Codex plugin and marketplace state removed. Restart Codex to apply.",
573
+ "Codex's global plugin_hooks feature and local data under ~/.reflexio/ and ~/.claude-smart/ were left in place.",
574
+ "",
575
+ ].join("\n"),
576
+ );
577
+ }
578
+
235
579
  async function main() {
236
580
  const args = process.argv.slice(2);
237
581
  const cmd = args[0] || "install";
@@ -252,7 +596,7 @@ async function main() {
252
596
  }
253
597
 
254
598
  if (cmd === "uninstall") {
255
- await runUninstall();
599
+ await runUninstall(args.slice(1));
256
600
  return;
257
601
  }
258
602