claude-smart 0.2.25 → 0.2.26
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 +30 -53
- package/bin/claude-smart.js +266 -9
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +2 -1
- package/plugin/pyproject.toml +1 -1
- package/plugin/skills/claude-smart/SKILL.md +32 -0
- package/plugin/src/claude_smart/cli.py +228 -16
- package/plugin/uv.lock +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
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.
|
|
16
|
+
<img src="https://img.shields.io/badge/version-0.2.26-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">
|
|
@@ -81,66 +81,45 @@ Four ways this changes what your coding assistant can do for you:
|
|
|
81
81
|
### Claude Code
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
claude
|
|
85
|
-
claude plugin install claude-smart@reflexioai
|
|
84
|
+
npx claude-smart install # or: uvx claude-smart install
|
|
86
85
|
```
|
|
87
86
|
|
|
88
|
-
|
|
89
|
-
private Node.js/npm runtime under `~/.claude-smart/` when they are missing, so
|
|
90
|
-
you do not need to install Python or Node globally for the plugin/dashboard.
|
|
87
|
+
Then restart Claude Code.
|
|
91
88
|
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
Requires Node.js (for `npx`) or uv (for `uvx`) to already exist.
|
|
90
|
+
|
|
91
|
+
Alternatively, install via Claude Code's plugin marketplace:
|
|
94
92
|
|
|
95
93
|
```bash
|
|
96
|
-
|
|
94
|
+
claude plugin marketplace add ReflexioAI/claude-smart
|
|
95
|
+
claude plugin install claude-smart@reflexioai
|
|
97
96
|
```
|
|
98
97
|
|
|
99
|
-
|
|
98
|
+
The plugin Setup hook installs its own `uv`, Python 3.12 environment, and
|
|
99
|
+
private Node.js/npm runtime under `~/.claude-smart/` when they are missing, so
|
|
100
|
+
you do not need to install Python or Node globally for the plugin/dashboard.
|
|
100
101
|
|
|
101
102
|
To uninstall:
|
|
102
103
|
|
|
103
104
|
```bash
|
|
104
|
-
claude
|
|
105
|
+
npx claude-smart uninstall # or: uvx claude-smart uninstall
|
|
105
106
|
```
|
|
106
107
|
|
|
107
|
-
Or, if
|
|
108
|
+
Or, if installed via the plugin marketplace:
|
|
108
109
|
|
|
109
110
|
```bash
|
|
110
|
-
|
|
111
|
+
claude plugin uninstall claude-smart@reflexioai
|
|
111
112
|
```
|
|
112
113
|
|
|
113
114
|
### Codex
|
|
114
115
|
|
|
115
|
-
You need the `codex` CLI on `PATH` and Node.js available for `npx`:
|
|
116
|
-
|
|
117
116
|
```bash
|
|
118
117
|
npx claude-smart install --host codex
|
|
119
118
|
```
|
|
120
119
|
|
|
121
|
-
|
|
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.
|
|
120
|
+
Then fully quit and reopen Codex so hooks reload.
|
|
143
121
|
|
|
122
|
+
Requires the `codex` CLI on `PATH` and Node.js (for `npx`).
|
|
144
123
|
|
|
145
124
|
To uninstall:
|
|
146
125
|
|
|
@@ -148,11 +127,10 @@ To uninstall:
|
|
|
148
127
|
npx claude-smart uninstall --host codex
|
|
149
128
|
```
|
|
150
129
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
130
|
+
Restart Codex after uninstalling. Learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared with Claude Code, so you can switch between hosts without losing skills or preferences.
|
|
131
|
+
|
|
132
|
+
Developing the plugin itself? See [DEVELOPER.md](./DEVELOPER.md#developing-locally) for what the installer does, manual toggles via `/plugins`, and clone-based development.
|
|
154
133
|
|
|
155
|
-
Developing the plugin itself? See [DEVELOPER.md](./DEVELOPER.md#developing-locally).
|
|
156
134
|
> **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.
|
|
157
135
|
|
|
158
136
|
---
|
|
@@ -200,9 +178,7 @@ Under the hood: hooks watch your turns, tool calls, and assistant replies, auto-
|
|
|
200
178
|
```
|
|
201
179
|
|
|
202
180
|
That signals a preference (`p…`) or skill (`s…`) materially shaped the reply.
|
|
203
|
-
|
|
204
|
-
link back to dashboard entries. Open the interaction's detail page in the
|
|
205
|
-
[dashboard](#dashboard) to see the exact cited item.
|
|
181
|
+
Open the session's detail page in the [dashboard](http://localhost:3001) to see the exact cited item.
|
|
206
182
|
|
|
207
183
|
See [ARCHITECTURE.md](./ARCHITECTURE.md) for hooks, data flow, and reflexio details.
|
|
208
184
|
|
|
@@ -211,16 +187,17 @@ See [ARCHITECTURE.md](./ARCHITECTURE.md) for hooks, data flow, and reflexio deta
|
|
|
211
187
|
## Commands
|
|
212
188
|
|
|
213
189
|
Claude Code installs these as plugin slash commands. Codex does not currently
|
|
214
|
-
support
|
|
215
|
-
|
|
190
|
+
support plugin-provided slash commands, so claude-smart ships a Codex skill that
|
|
191
|
+
maps requests like "claude-smart show" or "run claude-smart learn" to the
|
|
192
|
+
equivalent action.
|
|
216
193
|
|
|
217
|
-
| Claude Code | Codex
|
|
194
|
+
| Claude Code | Codex request | What it does |
|
|
218
195
|
| --- | --- | --- |
|
|
219
|
-
| `/claude-smart:dashboard` | `
|
|
220
|
-
| `/claude-smart:show` | `
|
|
221
|
-
| `/claude-smart:learn [note]` | `
|
|
222
|
-
| `/claude-smart:restart` | `
|
|
223
|
-
| `/claude-smart:clear-all` | `
|
|
196
|
+
| `/claude-smart:dashboard` | `open claude-smart dashboard` | Open the dashboard in your browser, auto-starting the reflexio backend and dashboard services if they aren't already running. |
|
|
197
|
+
| `/claude-smart:show` | `claude-smart show` | Print current project-specific skills, shared skills, and the current project's preferences so you can audit learned state manually. |
|
|
198
|
+
| `/claude-smart:learn [note]` | `claude-smart learn with 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. |
|
|
199
|
+
| `/claude-smart:restart` | `restart claude-smart` | Restart the reflexio backend and dashboard to pick up new changes (e.g. after upgrading the plugin or editing local reflexio code). |
|
|
200
|
+
| `/claude-smart:clear-all` | `clear all claude-smart learnings` | **Destructive.** Delete *all* reflexio interactions, preferences, and skills. Use when you want to wipe learned state and start fresh. |
|
|
224
201
|
|
|
225
202
|
---
|
|
226
203
|
|
|
@@ -235,7 +212,7 @@ Advanced users can tune claude-smart via environment variables — see [DEVELOPE
|
|
|
235
212
|
| `~/.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`. |
|
|
236
213
|
| `~/.reflexio/.env` | Provider config — `CLAUDE_SMART_USE_LOCAL_CLI`, `CLAUDE_SMART_USE_LOCAL_EMBEDDING`, any optional API keys. |
|
|
237
214
|
| `.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. |
|
|
238
|
-
| `~/.codex/config.toml` | Codex feature flags,
|
|
215
|
+
| `~/.codex/config.toml` | Codex plugin state, hook feature flags, and per-hook trust entries after `claude-smart install --host codex`. |
|
|
239
216
|
| `~/.codex/plugins/cache/reflexioai/claude-smart/<version>/` | Codex's cached install of the `claude-smart` plugin from the `ReflexioAI` marketplace. |
|
|
240
217
|
| `~/.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. |
|
|
241
218
|
| `~/.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. |
|
package/bin/claude-smart.js
CHANGED
|
@@ -205,9 +205,10 @@ function printHelp() {
|
|
|
205
205
|
"Codex install:",
|
|
206
206
|
` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
|
|
207
207
|
" 2. codex plugin marketplace add <copied marketplace>",
|
|
208
|
-
" 3. codex features enable plugin_hooks",
|
|
208
|
+
" 3. codex features enable hooks && codex features enable plugin_hooks",
|
|
209
209
|
" 4. Installs claude-smart into Codex's plugin cache and enables it",
|
|
210
|
-
" 5.
|
|
210
|
+
" 5. Trusts and enables claude-smart hook entries in ~/.codex/config.toml",
|
|
211
|
+
" 6. Restart Codex.",
|
|
211
212
|
"",
|
|
212
213
|
"Update:",
|
|
213
214
|
" npx claude-smart update Update to the latest version",
|
|
@@ -359,6 +360,227 @@ function setCodexPluginEnabled() {
|
|
|
359
360
|
writeFileSync(CODEX_CONFIG_PATH, next);
|
|
360
361
|
}
|
|
361
362
|
|
|
363
|
+
function tomlDottedQuoted(name) {
|
|
364
|
+
return `"${name.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function setTomlFeature(feature, value) {
|
|
368
|
+
// Minimal port of `_set_toml_feature` in plugin/src/claude_smart/cli.py:
|
|
369
|
+
// ensures `[features]\n<feature> = <bool>\n` is present in
|
|
370
|
+
// ~/.codex/config.toml, replacing any prior value for the same key.
|
|
371
|
+
const desired = `${feature} = ${value ? "true" : "false"}`;
|
|
372
|
+
const sectionRe = /^\s*\[([^\]]+)\]\s*(?:#.*)?$/;
|
|
373
|
+
const featureRe = new RegExp(`^\\s*${feature.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=`);
|
|
374
|
+
const text = existsSync(CODEX_CONFIG_PATH)
|
|
375
|
+
? readFileSync(CODEX_CONFIG_PATH, "utf8")
|
|
376
|
+
: "";
|
|
377
|
+
const lines = text.split("\n");
|
|
378
|
+
let inFeatures = false;
|
|
379
|
+
let featuresIdx = null;
|
|
380
|
+
let insertIdx = null;
|
|
381
|
+
let changed = false;
|
|
382
|
+
const out = [];
|
|
383
|
+
for (const line of lines) {
|
|
384
|
+
const sectionMatch = line.match(sectionRe);
|
|
385
|
+
if (sectionMatch) {
|
|
386
|
+
if (inFeatures && insertIdx === null) insertIdx = out.length;
|
|
387
|
+
inFeatures = sectionMatch[1].trim() === "features";
|
|
388
|
+
if (inFeatures) featuresIdx = out.length;
|
|
389
|
+
out.push(line);
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (inFeatures && featureRe.test(line)) {
|
|
393
|
+
out.push(desired);
|
|
394
|
+
changed = changed || line !== desired;
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
out.push(line);
|
|
398
|
+
}
|
|
399
|
+
if (featuresIdx === null) {
|
|
400
|
+
if (out.length && out[out.length - 1].trim()) out.push("");
|
|
401
|
+
out.push("[features]", desired);
|
|
402
|
+
changed = true;
|
|
403
|
+
} else {
|
|
404
|
+
const sectionEnd = insertIdx !== null ? insertIdx : out.length;
|
|
405
|
+
let hasFeature = false;
|
|
406
|
+
for (let i = featuresIdx + 1; i < sectionEnd; i++) {
|
|
407
|
+
if (featureRe.test(out[i])) { hasFeature = true; break; }
|
|
408
|
+
}
|
|
409
|
+
if (!hasFeature) {
|
|
410
|
+
const idx = insertIdx !== null ? insertIdx : out.length;
|
|
411
|
+
out.splice(idx, 0, desired);
|
|
412
|
+
changed = true;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (!changed && text.endsWith("\n")) return true;
|
|
416
|
+
mkdirSync(dirname(CODEX_CONFIG_PATH), { recursive: true });
|
|
417
|
+
let payload = out.join("\n");
|
|
418
|
+
if (!payload.endsWith("\n")) payload += "\n";
|
|
419
|
+
writeFileSync(CODEX_CONFIG_PATH, payload);
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function setCodexHookStates(states) {
|
|
424
|
+
const entries = Object.entries(states);
|
|
425
|
+
if (entries.length === 0) return false;
|
|
426
|
+
removeTomlSections(CODEX_CONFIG_PATH, {
|
|
427
|
+
exact: new Set(),
|
|
428
|
+
prefixes: [`hooks.state."${CODEX_PLUGIN_ID}:`],
|
|
429
|
+
});
|
|
430
|
+
const existing = existsSync(CODEX_CONFIG_PATH)
|
|
431
|
+
? readFileSync(CODEX_CONFIG_PATH, "utf8")
|
|
432
|
+
: "";
|
|
433
|
+
let next = existing;
|
|
434
|
+
if (next && !next.endsWith("\n")) next += "\n";
|
|
435
|
+
if (!next.includes("[hooks.state]")) {
|
|
436
|
+
if (next.trim()) next += "\n";
|
|
437
|
+
next += "[hooks.state]\n";
|
|
438
|
+
}
|
|
439
|
+
if (next.trim()) next += "\n";
|
|
440
|
+
for (const [key, currentHash] of entries.sort(([a], [b]) => a.localeCompare(b))) {
|
|
441
|
+
next += `[hooks.state.${tomlDottedQuoted(key)}]\n`;
|
|
442
|
+
next += "enabled = true\n";
|
|
443
|
+
next += `trusted_hash = "${currentHash}"\n\n`;
|
|
444
|
+
}
|
|
445
|
+
mkdirSync(dirname(CODEX_CONFIG_PATH), { recursive: true });
|
|
446
|
+
writeFileSync(CODEX_CONFIG_PATH, next.trimEnd() + "\n");
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function createCodexAppServerClient(child) {
|
|
451
|
+
// A single long-lived stdout listener that demultiplexes JSON-RPC responses
|
|
452
|
+
// by id. Avoids losing messages between sequential requests.
|
|
453
|
+
const pending = new Map();
|
|
454
|
+
let buffer = "";
|
|
455
|
+
let exited = false;
|
|
456
|
+
|
|
457
|
+
const onData = (chunk) => {
|
|
458
|
+
buffer += chunk.toString();
|
|
459
|
+
let newline;
|
|
460
|
+
while ((newline = buffer.indexOf("\n")) >= 0) {
|
|
461
|
+
const line = buffer.slice(0, newline);
|
|
462
|
+
buffer = buffer.slice(newline + 1);
|
|
463
|
+
if (!line.trim()) continue;
|
|
464
|
+
let message;
|
|
465
|
+
try {
|
|
466
|
+
message = JSON.parse(line);
|
|
467
|
+
} catch {
|
|
468
|
+
continue;
|
|
469
|
+
}
|
|
470
|
+
const entry = pending.get(message.id);
|
|
471
|
+
if (!entry) continue;
|
|
472
|
+
pending.delete(message.id);
|
|
473
|
+
clearTimeout(entry.timer);
|
|
474
|
+
if (message.error) {
|
|
475
|
+
entry.reject(new Error(JSON.stringify(message.error)));
|
|
476
|
+
} else {
|
|
477
|
+
entry.resolve(message);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
const onExit = () => {
|
|
482
|
+
exited = true;
|
|
483
|
+
for (const entry of pending.values()) {
|
|
484
|
+
clearTimeout(entry.timer);
|
|
485
|
+
entry.reject(new Error("Codex app-server exited before responding"));
|
|
486
|
+
}
|
|
487
|
+
pending.clear();
|
|
488
|
+
};
|
|
489
|
+
child.stdout.on("data", onData);
|
|
490
|
+
child.on("exit", onExit);
|
|
491
|
+
|
|
492
|
+
return {
|
|
493
|
+
request(id, method, params, timeoutMs) {
|
|
494
|
+
return new Promise((resolve, reject) => {
|
|
495
|
+
if (exited) {
|
|
496
|
+
reject(new Error("Codex app-server exited before responding"));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const timer = setTimeout(() => {
|
|
500
|
+
pending.delete(id);
|
|
501
|
+
reject(new Error(`Codex app-server ${method} timed out`));
|
|
502
|
+
}, timeoutMs);
|
|
503
|
+
pending.set(id, { resolve, reject, timer });
|
|
504
|
+
child.stdin.write(JSON.stringify({ id, method, params }) + "\n");
|
|
505
|
+
});
|
|
506
|
+
},
|
|
507
|
+
notify(method, params) {
|
|
508
|
+
if (exited) return;
|
|
509
|
+
child.stdin.write(JSON.stringify({ method, params }) + "\n");
|
|
510
|
+
},
|
|
511
|
+
close() {
|
|
512
|
+
child.stdout.off("data", onData);
|
|
513
|
+
child.off("exit", onExit);
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function listCodexPluginHooks(cwd) {
|
|
519
|
+
const child = spawn("codex", ["app-server", "--listen", "stdio://"], {
|
|
520
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
521
|
+
});
|
|
522
|
+
const client = createCodexAppServerClient(child);
|
|
523
|
+
try {
|
|
524
|
+
await client.request(
|
|
525
|
+
1,
|
|
526
|
+
"initialize",
|
|
527
|
+
{
|
|
528
|
+
clientInfo: {
|
|
529
|
+
name: "claude_smart_installer",
|
|
530
|
+
title: "claude-smart installer",
|
|
531
|
+
version: "0.0.0",
|
|
532
|
+
},
|
|
533
|
+
capabilities: { experimentalApi: true },
|
|
534
|
+
},
|
|
535
|
+
CODEX_CLI_TIMEOUT_MS,
|
|
536
|
+
);
|
|
537
|
+
client.notify("initialized", {});
|
|
538
|
+
const response = await client.request(
|
|
539
|
+
2,
|
|
540
|
+
"hooks/list",
|
|
541
|
+
{ cwds: [cwd] },
|
|
542
|
+
CODEX_CLI_TIMEOUT_MS,
|
|
543
|
+
);
|
|
544
|
+
const hooks = response.result?.data?.[0]?.hooks;
|
|
545
|
+
if (!Array.isArray(hooks)) {
|
|
546
|
+
throw new Error("Codex app-server hook metadata was malformed");
|
|
547
|
+
}
|
|
548
|
+
return hooks.filter(
|
|
549
|
+
(hook) =>
|
|
550
|
+
hook &&
|
|
551
|
+
(hook.pluginId === CODEX_PLUGIN_ID ||
|
|
552
|
+
String(hook.key || "").startsWith(`${CODEX_PLUGIN_ID}:`)),
|
|
553
|
+
);
|
|
554
|
+
} finally {
|
|
555
|
+
client.close();
|
|
556
|
+
child.stdin.destroy();
|
|
557
|
+
child.stdout.destroy();
|
|
558
|
+
child.kill("SIGTERM");
|
|
559
|
+
child.unref();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
async function trustCodexPluginHooks(cwd) {
|
|
564
|
+
const hooks = await listCodexPluginHooks(cwd);
|
|
565
|
+
const states = {};
|
|
566
|
+
for (const hook of hooks) {
|
|
567
|
+
if (
|
|
568
|
+
typeof hook.key === "string" &&
|
|
569
|
+
hook.key.startsWith(`${CODEX_PLUGIN_ID}:`) &&
|
|
570
|
+
typeof hook.currentHash === "string"
|
|
571
|
+
) {
|
|
572
|
+
states[hook.key] = hook.currentHash;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (Object.keys(states).length === 0) {
|
|
576
|
+
throw new Error("Codex did not report trust hashes for claude-smart hooks");
|
|
577
|
+
}
|
|
578
|
+
if (!setCodexHookStates(states)) {
|
|
579
|
+
throw new Error(`could not write claude-smart hook trust state to ${CODEX_CONFIG_PATH}`);
|
|
580
|
+
}
|
|
581
|
+
return Object.keys(states).length;
|
|
582
|
+
}
|
|
583
|
+
|
|
362
584
|
function codexPluginVersion(pluginRoot) {
|
|
363
585
|
try {
|
|
364
586
|
const manifest = JSON.parse(
|
|
@@ -515,13 +737,26 @@ async function runInstallCodex() {
|
|
|
515
737
|
process.exit(code);
|
|
516
738
|
}
|
|
517
739
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
740
|
+
for (const feature of ["hooks", "plugin_hooks"]) {
|
|
741
|
+
code = await runCodex(["features", "enable", feature]);
|
|
742
|
+
if (code !== 0) {
|
|
743
|
+
// Older Codex builds may not recognize the `hooks` feature name; fall
|
|
744
|
+
// through to writing the flag directly under [features] in config.toml.
|
|
745
|
+
try {
|
|
746
|
+
setTomlFeature(feature, true);
|
|
747
|
+
process.stdout.write(`Enabled Codex ${feature} via ${CODEX_CONFIG_PATH}.\n`);
|
|
748
|
+
} catch (err) {
|
|
749
|
+
process.stderr.write(
|
|
750
|
+
`error: could not enable Codex ${feature} feature: ${err && err.message ? err.message : err}\n`,
|
|
751
|
+
);
|
|
752
|
+
process.exit(code);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
522
755
|
}
|
|
523
756
|
|
|
524
757
|
let cacheDir = null;
|
|
758
|
+
let trustedHookCount = 0;
|
|
759
|
+
let trustError = null;
|
|
525
760
|
try {
|
|
526
761
|
cacheDir = installCodexPluginCache(join(marketplaceRoot, CODEX_MARKETPLACE_PLUGIN_PATH));
|
|
527
762
|
process.stdout.write(`Installed Codex plugin cache at ${cacheDir}.\n`);
|
|
@@ -530,9 +765,31 @@ async function runInstallCodex() {
|
|
|
530
765
|
`error: automatic Codex plugin install failed: ${err && err.message ? err.message : err}\n`,
|
|
531
766
|
);
|
|
532
767
|
process.stderr.write(
|
|
533
|
-
`Open Codex, run /plugins,
|
|
768
|
+
`Open Codex, run /plugins, install claude-smart from the ${CODEX_MARKETPLACE_DISPLAY_NAME} marketplace, and restart Codex.\n`,
|
|
769
|
+
);
|
|
770
|
+
process.exit(1);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
774
|
+
try {
|
|
775
|
+
trustedHookCount = await trustCodexPluginHooks(process.cwd());
|
|
776
|
+
trustError = null;
|
|
777
|
+
break;
|
|
778
|
+
} catch (err) {
|
|
779
|
+
trustError = err;
|
|
780
|
+
if (attempt === 0) await new Promise((r) => setTimeout(r, 500));
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (trustError) {
|
|
784
|
+
process.stderr.write(
|
|
785
|
+
`warning: ${trustError && trustError.message ? trustError.message : trustError}\n`,
|
|
786
|
+
);
|
|
787
|
+
process.stderr.write(
|
|
788
|
+
`Fully quit and reopen Codex in this repo, run /hooks, trust the claude-smart hooks, and restart Codex.\n`,
|
|
534
789
|
);
|
|
535
790
|
process.exit(1);
|
|
791
|
+
} else {
|
|
792
|
+
process.stdout.write(`Trusted and enabled ${trustedHookCount} claude-smart Codex hooks.\n`);
|
|
536
793
|
}
|
|
537
794
|
|
|
538
795
|
const added = seedReflexioEnv();
|
|
@@ -544,7 +801,7 @@ async function runInstallCodex() {
|
|
|
544
801
|
[
|
|
545
802
|
"",
|
|
546
803
|
"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.`,
|
|
804
|
+
`Restart Codex so the installed plugin and trusted hooks reload. /plugins should show claude-smart as installed from the ${CODEX_MARKETPLACE_DISPLAY_NAME} marketplace.`,
|
|
548
805
|
"Local data is shared with Claude Code under ~/.reflexio/ and ~/.claude-smart/.",
|
|
549
806
|
"",
|
|
550
807
|
].join("\n"),
|
|
@@ -570,7 +827,7 @@ async function runUninstallCodex() {
|
|
|
570
827
|
[
|
|
571
828
|
"",
|
|
572
829
|
"claude-smart Codex plugin and marketplace state removed. Restart Codex to apply.",
|
|
573
|
-
"Codex's global
|
|
830
|
+
"Codex's global hook feature flags and local data under ~/.reflexio/ and ~/.claude-smart/ were left in place.",
|
|
574
831
|
"",
|
|
575
832
|
].join("\n"),
|
|
576
833
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-smart",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
4
4
|
"description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Yi Lu"
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"playbook",
|
|
18
18
|
"learning"
|
|
19
19
|
],
|
|
20
|
+
"skills": "./skills/",
|
|
20
21
|
"hooks": "./hooks/codex-hooks.json",
|
|
21
22
|
"interface": {
|
|
22
23
|
"displayName": "claude-smart",
|
package/plugin/pyproject.toml
CHANGED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: claude-smart
|
|
3
|
+
description: Codex-only — use when the user asks to run claude-smart commands in Codex, including claude-smart show, learn, restart, dashboard, clear-all, or slash-like requests such as /claude-smart:learn. In Claude Code the native plugin slash commands handle these; do not invoke this skill there.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# claude-smart Commands In Codex
|
|
7
|
+
|
|
8
|
+
Codex does not currently support plugin-provided slash commands. When the user
|
|
9
|
+
asks for a claude-smart command, run the equivalent shell command through the
|
|
10
|
+
active plugin root. This skill is Codex-specific; under Claude Code the native
|
|
11
|
+
`/claude-smart:*` slash commands already exist, so do not fall back to the
|
|
12
|
+
shell commands there.
|
|
13
|
+
|
|
14
|
+
## Command Map
|
|
15
|
+
|
|
16
|
+
- Dashboard: `bash ~/.reflexio/plugin-root/scripts/dashboard-open.sh`
|
|
17
|
+
- Show learned state: `bash ~/.reflexio/plugin-root/scripts/cli.sh show`
|
|
18
|
+
- Learn from the latest turn: `bash ~/.reflexio/plugin-root/scripts/cli.sh learn`
|
|
19
|
+
- Learn with a note: `bash ~/.reflexio/plugin-root/scripts/cli.sh learn --note "<note>"`
|
|
20
|
+
- Restart backend/dashboard: `bash ~/.reflexio/plugin-root/scripts/cli.sh restart`
|
|
21
|
+
- Clear all learned state: `bash ~/.reflexio/plugin-root/scripts/cli.sh clear-all --yes`
|
|
22
|
+
|
|
23
|
+
## Behavior
|
|
24
|
+
|
|
25
|
+
1. Treat slash-like prompts such as `/claude-smart:show` as requests to run the
|
|
26
|
+
matching shell command above.
|
|
27
|
+
2. For `learn`, preserve any user-provided note exactly as the note text.
|
|
28
|
+
3. For `clear-all`, require explicit confirmation before running it because it
|
|
29
|
+
deletes all reflexio interactions, preferences, and skills.
|
|
30
|
+
4. If `~/.reflexio/plugin-root` is missing or broken, tell the user to restart
|
|
31
|
+
Codex after installing claude-smart, then rerun the command.
|
|
32
|
+
5. After running a command, summarize the important output concisely.
|
|
@@ -25,6 +25,7 @@ import argparse
|
|
|
25
25
|
import json
|
|
26
26
|
import os
|
|
27
27
|
import re
|
|
28
|
+
import select
|
|
28
29
|
import shutil
|
|
29
30
|
import subprocess
|
|
30
31
|
import sys
|
|
@@ -337,6 +338,191 @@ def _set_codex_plugin_enabled(path: Path) -> bool:
|
|
|
337
338
|
return True
|
|
338
339
|
|
|
339
340
|
|
|
341
|
+
def _toml_dotted_quoted(name: str) -> str:
|
|
342
|
+
"""Quote a TOML bare-key segment whose name may contain ``"`` or ``\\``."""
|
|
343
|
+
return '"' + name.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def _set_codex_hook_states(path: Path, states: dict[str, str]) -> bool:
|
|
347
|
+
"""Trust and enable Codex hook state entries for the given hook hashes."""
|
|
348
|
+
if not states:
|
|
349
|
+
return False
|
|
350
|
+
if not _remove_toml_sections(
|
|
351
|
+
path,
|
|
352
|
+
exact=set(),
|
|
353
|
+
prefixes=(f'hooks.state."{_CODEX_PLUGIN_ID}:',),
|
|
354
|
+
):
|
|
355
|
+
return False
|
|
356
|
+
try:
|
|
357
|
+
text = path.read_text() if path.exists() else ""
|
|
358
|
+
except OSError:
|
|
359
|
+
return False
|
|
360
|
+
if text and not text.endswith("\n"):
|
|
361
|
+
text += "\n"
|
|
362
|
+
if "[hooks.state]" not in text:
|
|
363
|
+
if text.strip():
|
|
364
|
+
text += "\n"
|
|
365
|
+
text += "[hooks.state]\n"
|
|
366
|
+
if text.strip():
|
|
367
|
+
text += "\n"
|
|
368
|
+
for key, current_hash in sorted(states.items()):
|
|
369
|
+
text += (
|
|
370
|
+
f"[hooks.state.{_toml_dotted_quoted(key)}]\n"
|
|
371
|
+
"enabled = true\n"
|
|
372
|
+
f'trusted_hash = "{current_hash}"\n\n'
|
|
373
|
+
)
|
|
374
|
+
try:
|
|
375
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
376
|
+
path.write_text(text.rstrip() + "\n")
|
|
377
|
+
except OSError:
|
|
378
|
+
return False
|
|
379
|
+
return True
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _read_codex_app_server_response(
|
|
383
|
+
proc: subprocess.Popen[str], response_id: int, deadline: float
|
|
384
|
+
) -> dict[str, object]:
|
|
385
|
+
"""Read JSON-RPC lines from ``proc.stdout`` until ``response_id`` arrives.
|
|
386
|
+
|
|
387
|
+
Skips unrelated notifications and non-JSON output. Raises ``RuntimeError``
|
|
388
|
+
if the child exits, ``TimeoutError`` once ``deadline`` is reached, or
|
|
389
|
+
re-raises Codex's own error payload as ``RuntimeError``.
|
|
390
|
+
"""
|
|
391
|
+
if proc.stdout is None:
|
|
392
|
+
raise RuntimeError("Codex app-server stdout pipe is not available")
|
|
393
|
+
while time.monotonic() < deadline:
|
|
394
|
+
ready, _, _ = select.select([proc.stdout], [], [], 0.2)
|
|
395
|
+
if not ready:
|
|
396
|
+
if proc.poll() is not None:
|
|
397
|
+
raise RuntimeError("Codex app-server exited before responding")
|
|
398
|
+
continue
|
|
399
|
+
line = proc.stdout.readline()
|
|
400
|
+
if not line:
|
|
401
|
+
continue
|
|
402
|
+
try:
|
|
403
|
+
message = json.loads(line)
|
|
404
|
+
except json.JSONDecodeError:
|
|
405
|
+
continue
|
|
406
|
+
if message.get("id") == response_id:
|
|
407
|
+
if "error" in message:
|
|
408
|
+
raise RuntimeError(str(message["error"]))
|
|
409
|
+
return message
|
|
410
|
+
raise TimeoutError("Codex app-server did not respond in time")
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _codex_app_server_request(
|
|
414
|
+
proc: subprocess.Popen[str],
|
|
415
|
+
response_id: int,
|
|
416
|
+
method: str,
|
|
417
|
+
params: dict[str, object],
|
|
418
|
+
deadline: float,
|
|
419
|
+
) -> dict[str, object]:
|
|
420
|
+
"""Send one JSON-RPC request to the Codex app-server and await the response."""
|
|
421
|
+
if proc.stdin is None:
|
|
422
|
+
raise RuntimeError("Codex app-server stdin pipe is not available")
|
|
423
|
+
proc.stdin.write(
|
|
424
|
+
json.dumps({"id": response_id, "method": method, "params": params})
|
|
425
|
+
)
|
|
426
|
+
proc.stdin.write("\n")
|
|
427
|
+
proc.stdin.flush()
|
|
428
|
+
return _read_codex_app_server_response(proc, response_id, deadline)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _list_codex_plugin_hooks(cwd: Path) -> tuple[bool, list[dict[str, object]], str]:
|
|
432
|
+
"""Ask Codex for discovered hook metadata, including current trust hashes."""
|
|
433
|
+
try:
|
|
434
|
+
popen = subprocess.Popen(
|
|
435
|
+
["codex", "app-server", "--listen", "stdio://"],
|
|
436
|
+
stdin=subprocess.PIPE,
|
|
437
|
+
stdout=subprocess.PIPE,
|
|
438
|
+
stderr=subprocess.DEVNULL,
|
|
439
|
+
text=True,
|
|
440
|
+
)
|
|
441
|
+
except OSError as exc:
|
|
442
|
+
return False, [], f"could not start Codex app-server: {exc}"
|
|
443
|
+
|
|
444
|
+
proc = popen
|
|
445
|
+
try:
|
|
446
|
+
deadline = time.monotonic() + _CODEX_CLI_TIMEOUT_SECONDS
|
|
447
|
+
_codex_app_server_request(
|
|
448
|
+
proc,
|
|
449
|
+
1,
|
|
450
|
+
"initialize",
|
|
451
|
+
{
|
|
452
|
+
"clientInfo": {
|
|
453
|
+
"name": "claude_smart_installer",
|
|
454
|
+
"title": "claude-smart installer",
|
|
455
|
+
"version": "0.0.0",
|
|
456
|
+
},
|
|
457
|
+
"capabilities": {"experimentalApi": True},
|
|
458
|
+
},
|
|
459
|
+
deadline,
|
|
460
|
+
)
|
|
461
|
+
if proc.stdin is None:
|
|
462
|
+
return False, [], "Codex app-server stdin pipe is not available"
|
|
463
|
+
proc.stdin.write(json.dumps({"method": "initialized", "params": {}}) + "\n")
|
|
464
|
+
proc.stdin.flush()
|
|
465
|
+
response = _codex_app_server_request(
|
|
466
|
+
proc,
|
|
467
|
+
2,
|
|
468
|
+
"hooks/list",
|
|
469
|
+
{"cwds": [str(cwd)]},
|
|
470
|
+
deadline,
|
|
471
|
+
)
|
|
472
|
+
except (OSError, RuntimeError, TimeoutError) as exc:
|
|
473
|
+
return False, [], str(exc)
|
|
474
|
+
finally:
|
|
475
|
+
proc.terminate()
|
|
476
|
+
try:
|
|
477
|
+
proc.wait(timeout=2)
|
|
478
|
+
except subprocess.TimeoutExpired:
|
|
479
|
+
proc.kill()
|
|
480
|
+
proc.wait(timeout=2)
|
|
481
|
+
|
|
482
|
+
result = response.get("result")
|
|
483
|
+
data = result.get("data") if isinstance(result, dict) else None
|
|
484
|
+
if not isinstance(data, list) or not data:
|
|
485
|
+
return False, [], "Codex app-server returned no hook metadata"
|
|
486
|
+
hooks = data[0].get("hooks") if isinstance(data[0], dict) else None
|
|
487
|
+
if not isinstance(hooks, list):
|
|
488
|
+
return False, [], "Codex app-server hook metadata was malformed"
|
|
489
|
+
plugin_hooks = [
|
|
490
|
+
hook
|
|
491
|
+
for hook in hooks
|
|
492
|
+
if isinstance(hook, dict)
|
|
493
|
+
and (
|
|
494
|
+
hook.get("pluginId") == _CODEX_PLUGIN_ID
|
|
495
|
+
or str(hook.get("key", "")).startswith(f"{_CODEX_PLUGIN_ID}:")
|
|
496
|
+
)
|
|
497
|
+
]
|
|
498
|
+
return True, plugin_hooks, f"found {len(plugin_hooks)} claude-smart hooks"
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _trust_codex_plugin_hooks(cwd: Path) -> tuple[bool, str]:
|
|
502
|
+
"""Seed Codex per-hook trust state for the installed claude-smart plugin."""
|
|
503
|
+
ok, hooks, message = _list_codex_plugin_hooks(cwd)
|
|
504
|
+
if not ok:
|
|
505
|
+
return False, message
|
|
506
|
+
states: dict[str, str] = {}
|
|
507
|
+
for hook in hooks:
|
|
508
|
+
key = hook.get("key")
|
|
509
|
+
current_hash = hook.get("currentHash")
|
|
510
|
+
if (
|
|
511
|
+
isinstance(key, str)
|
|
512
|
+
and key.startswith(f"{_CODEX_PLUGIN_ID}:")
|
|
513
|
+
and isinstance(current_hash, str)
|
|
514
|
+
):
|
|
515
|
+
states[key] = current_hash
|
|
516
|
+
if not states:
|
|
517
|
+
return False, "Codex did not report trust hashes for claude-smart hooks"
|
|
518
|
+
if not _set_codex_hook_states(_CODEX_CONFIG_PATH, states):
|
|
519
|
+
return (
|
|
520
|
+
False,
|
|
521
|
+
f"could not write claude-smart hook trust state to {_CODEX_CONFIG_PATH}",
|
|
522
|
+
)
|
|
523
|
+
return True, f"trusted and enabled {len(states)} claude-smart Codex hooks"
|
|
524
|
+
|
|
525
|
+
|
|
340
526
|
def _codex_plugin_version(plugin_root: Path) -> str | None:
|
|
341
527
|
try:
|
|
342
528
|
manifest = json.loads(
|
|
@@ -401,21 +587,28 @@ def _cleanup_codex_install_state() -> bool:
|
|
|
401
587
|
|
|
402
588
|
|
|
403
589
|
def _enable_codex_plugin_hooks() -> tuple[bool, str]:
|
|
404
|
-
"""Enable Codex
|
|
590
|
+
"""Enable Codex hook feature flags, falling back to editing config.toml.
|
|
405
591
|
|
|
406
592
|
Returns:
|
|
407
593
|
tuple[bool, str]: ``(success, message)``. The message describes
|
|
408
|
-
|
|
409
|
-
|
|
594
|
+
either the successful path taken (CLI vs. direct config write)
|
|
595
|
+
or the failure mode.
|
|
410
596
|
"""
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
597
|
+
messages: list[str] = []
|
|
598
|
+
for feature in ("hooks", "plugin_hooks"):
|
|
599
|
+
result = _run_codex(["features", "enable", feature])
|
|
600
|
+
if result.returncode == 0:
|
|
601
|
+
messages.append(f"codex features enable {feature}")
|
|
602
|
+
continue
|
|
603
|
+
if _set_toml_feature(_CODEX_CONFIG_PATH, feature, True):
|
|
604
|
+
messages.append(f"set {feature} = true in {_CODEX_CONFIG_PATH}")
|
|
605
|
+
continue
|
|
606
|
+
detail = (
|
|
607
|
+
result.stderr or result.stdout or "could not update Codex config"
|
|
608
|
+
).strip()
|
|
609
|
+
prior = f" (succeeded earlier: {'; '.join(messages)})" if messages else ""
|
|
610
|
+
return False, f"{feature}: {detail}{prior}"
|
|
611
|
+
return True, "; ".join(messages)
|
|
419
612
|
|
|
420
613
|
|
|
421
614
|
def _register_codex_marketplace(root: Path) -> tuple[bool, str]:
|
|
@@ -501,22 +694,41 @@ def cmd_install_codex(_args: argparse.Namespace) -> int:
|
|
|
501
694
|
|
|
502
695
|
installed = False
|
|
503
696
|
install_msg = "marketplace registration failed"
|
|
697
|
+
trusted = False
|
|
698
|
+
trust_msg = "plugin was not installed"
|
|
504
699
|
if registered:
|
|
505
700
|
installed, install_msg = _install_codex_plugin_cache(
|
|
506
701
|
marketplace_root / _CODEX_LOCAL_PLUGIN_PATH
|
|
507
702
|
)
|
|
508
703
|
if installed:
|
|
509
704
|
sys.stdout.write(f"{install_msg}.\n")
|
|
705
|
+
trusted, trust_msg = _trust_codex_plugin_hooks(Path.cwd())
|
|
706
|
+
if not trusted:
|
|
707
|
+
# The app-server can race against the just-installed cache.
|
|
708
|
+
# Retry once before giving up; the manual /hooks recovery is
|
|
709
|
+
# available either way.
|
|
710
|
+
time.sleep(0.5)
|
|
711
|
+
trusted, trust_msg = _trust_codex_plugin_hooks(Path.cwd())
|
|
712
|
+
if trusted:
|
|
713
|
+
sys.stdout.write(f"{trust_msg}.\n")
|
|
714
|
+
else:
|
|
715
|
+
sys.stderr.write(f"warning: {trust_msg}\n")
|
|
510
716
|
else:
|
|
511
717
|
sys.stderr.write(f"warning: {install_msg}\n")
|
|
512
718
|
|
|
513
|
-
if registered and installed:
|
|
719
|
+
if registered and installed and trusted:
|
|
514
720
|
sys.stdout.write(
|
|
515
721
|
"\nclaude-smart Codex support is installed.\n"
|
|
516
|
-
"Restart Codex so the installed plugin and hooks reload. /plugins should "
|
|
722
|
+
"Restart Codex so the installed plugin and trusted hooks reload. /plugins should "
|
|
517
723
|
f"show claude-smart as installed from the {_CODEX_MARKETPLACE_DISPLAY_NAME} marketplace. "
|
|
518
724
|
"Uninstall removes the plugin cache and marketplace registration but leaves "
|
|
519
|
-
"shared claude-smart data and Codex's global
|
|
725
|
+
"shared claude-smart data and Codex's global hook feature flags intact.\n"
|
|
726
|
+
)
|
|
727
|
+
elif registered and installed:
|
|
728
|
+
sys.stdout.write(
|
|
729
|
+
"\nclaude-smart Codex support is installed, but hook trust could not be completed.\n"
|
|
730
|
+
"Fully quit and reopen Codex in this repo, run /hooks, trust the claude-smart hooks, "
|
|
731
|
+
"and restart Codex so hooks reload.\n"
|
|
520
732
|
)
|
|
521
733
|
elif registered:
|
|
522
734
|
sys.stdout.write(
|
|
@@ -531,7 +743,7 @@ def cmd_install_codex(_args: argparse.Namespace) -> int:
|
|
|
531
743
|
"then fully quit and reopen Codex, run /plugins, install claude-smart from the "
|
|
532
744
|
f"{_CODEX_MARKETPLACE_DISPLAY_NAME} marketplace, and restart Codex so hooks reload.\n"
|
|
533
745
|
)
|
|
534
|
-
return 0 if hooks_ok and registered and installed else 1
|
|
746
|
+
return 0 if hooks_ok and registered and installed and trusted else 1
|
|
535
747
|
|
|
536
748
|
|
|
537
749
|
def cmd_install(args: argparse.Namespace) -> int:
|
|
@@ -674,7 +886,7 @@ def cmd_uninstall_codex(_args: argparse.Namespace) -> int:
|
|
|
674
886
|
sys.stderr.write(f"warning: could not update {_CODEX_CONFIG_PATH}\n")
|
|
675
887
|
sys.stdout.write(
|
|
676
888
|
"claude-smart Codex plugin and marketplace state removed. Restart Codex to apply. "
|
|
677
|
-
"Codex's global
|
|
889
|
+
"Codex's global hook feature flags and local data under ~/.reflexio "
|
|
678
890
|
"and ~/.claude-smart were left in place.\n"
|
|
679
891
|
)
|
|
680
892
|
return 0
|