claude-smart 0.2.40 → 0.2.42
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/.claude-plugin/marketplace.json +17 -0
- package/README.md +1 -1
- package/bin/claude-smart.js +101 -53
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/dashboard/next.config.ts +9 -1
- package/plugin/hooks/codex-hooks.json +0 -5
- package/plugin/hooks/hooks.json +0 -10
- package/plugin/pyproject.toml +1 -1
- package/plugin/scripts/backend-service.sh +50 -20
- package/plugin/scripts/cli.sh +28 -1
- package/plugin/scripts/codex-hook.js +7 -38
- package/plugin/scripts/dashboard-service.sh +5 -6
- package/plugin/scripts/ensure-plugin-root.sh +7 -14
- package/plugin/scripts/hook_entry.sh +13 -28
- package/plugin/scripts/smart-install.sh +28 -3
- package/plugin/src/claude_smart/cli.py +72 -38
- package/plugin/uv.lock +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reflexioai",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Yi Lu"
|
|
5
|
+
},
|
|
6
|
+
"metadata": {
|
|
7
|
+
"description": "claude-smart — self-improving Claude Code plugin via reflexio"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "claude-smart",
|
|
12
|
+
"version": "0.2.42",
|
|
13
|
+
"source": "./plugin",
|
|
14
|
+
"description": "Learns user corrections and project playbooks via reflexio — uses Claude Code itself as the LLM backend (no external API key required)"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
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.42-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">
|
package/bin/claude-smart.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* npx claude-smart install — thin wrapper around the native host plugin
|
|
4
|
-
* CLIs.
|
|
5
|
-
*
|
|
6
|
-
* and
|
|
7
|
-
*
|
|
8
|
-
* with no API key.
|
|
9
|
-
* `npx claude-smart setup`,
|
|
10
|
-
* installer.
|
|
4
|
+
* CLIs. Both Claude Code and Codex install from the bundled marketplace in
|
|
5
|
+
* this npm package: Claude Code registers the package root as a local
|
|
6
|
+
* marketplace, and Codex copies the bundled plugin into its own marketplace
|
|
7
|
+
* wrapper. Both paths seed ~/.reflexio/.env with the two local-provider flags
|
|
8
|
+
* so reflexio can route generation through local tools with no API key.
|
|
9
|
+
* Managed/read-only/global setup is handled by `npx claude-smart setup`,
|
|
10
|
+
* which writes ~/.reflexio/.env before running this installer.
|
|
11
11
|
*
|
|
12
12
|
* Keep this file dependency-free — it runs via `npx` with no install step.
|
|
13
13
|
*/
|
|
@@ -33,7 +33,6 @@ const https = require("https");
|
|
|
33
33
|
const { arch, homedir, platform, release, tmpdir } = require("os");
|
|
34
34
|
const { dirname, join } = require("path");
|
|
35
35
|
|
|
36
|
-
const DEFAULT_MARKETPLACE_SOURCE = "ReflexioAI/claude-smart";
|
|
37
36
|
const PLUGIN_SPEC = "claude-smart@reflexioai";
|
|
38
37
|
const CODEX_MARKETPLACE_NAME = "reflexioai";
|
|
39
38
|
const CODEX_MARKETPLACE_DISPLAY_NAME = "ReflexioAI";
|
|
@@ -358,7 +357,7 @@ function findClaudeCodePluginRoot() {
|
|
|
358
357
|
}
|
|
359
358
|
}
|
|
360
359
|
} catch {
|
|
361
|
-
// Fall through to marketplace
|
|
360
|
+
// Fall through to marketplace fallback.
|
|
362
361
|
}
|
|
363
362
|
candidates.sort((a, b) => {
|
|
364
363
|
const versionCompare = compareSemverLikePathNames(b, a);
|
|
@@ -369,9 +368,13 @@ function findClaudeCodePluginRoot() {
|
|
|
369
368
|
return 0;
|
|
370
369
|
}
|
|
371
370
|
});
|
|
371
|
+
// PACKAGE_ROOT/plugin is intentionally NOT a fallback: under `npx` it
|
|
372
|
+
// points at /root/.npm/_npx/<hash>/.../plugin which npm may prune between
|
|
373
|
+
// invocations, breaking the editable claude_smart install (.pth dangles,
|
|
374
|
+
// venv survives) and confusing every later `uv sync`. Require `claude
|
|
375
|
+
// plugin install` to have populated the cache dir instead, and fail loud.
|
|
372
376
|
const fallbacks = [
|
|
373
377
|
join(homedir(), ".claude", "plugins", "marketplaces", CODEX_MARKETPLACE_NAME, "plugin"),
|
|
374
|
-
join(PACKAGE_ROOT, "plugin"),
|
|
375
378
|
];
|
|
376
379
|
for (const candidate of [...candidates, ...fallbacks]) {
|
|
377
380
|
if (
|
|
@@ -429,7 +432,19 @@ function forcePluginRoot(pluginRoot) {
|
|
|
429
432
|
async function bootstrapClaudeCodeInstall() {
|
|
430
433
|
const pluginRoot = findClaudeCodePluginRoot();
|
|
431
434
|
if (!pluginRoot) {
|
|
432
|
-
|
|
435
|
+
const cacheRoot = join(
|
|
436
|
+
homedir(),
|
|
437
|
+
".claude",
|
|
438
|
+
"plugins",
|
|
439
|
+
"cache",
|
|
440
|
+
CODEX_MARKETPLACE_NAME,
|
|
441
|
+
"claude-smart",
|
|
442
|
+
);
|
|
443
|
+
throw new Error(
|
|
444
|
+
`claude plugin install did not populate ${cacheRoot}. ` +
|
|
445
|
+
"Open Claude Code, run /plugins, verify claude-smart is installed " +
|
|
446
|
+
"from the ReflexioAI marketplace, then rerun `npx claude-smart install`.",
|
|
447
|
+
);
|
|
433
448
|
}
|
|
434
449
|
forcePluginRoot(pluginRoot);
|
|
435
450
|
const bash = resolveCommand(isWindows() ? ["bash.exe", "bash"] : ["bash"]);
|
|
@@ -508,14 +523,14 @@ function runChecked(command, args, options = {}) {
|
|
|
508
523
|
});
|
|
509
524
|
}
|
|
510
525
|
|
|
511
|
-
function runPluginService(pluginRoot, scriptName, subcommand) {
|
|
526
|
+
function runPluginService(pluginRoot, scriptName, subcommand, envOverrides = {}) {
|
|
512
527
|
const script = join(pluginRoot, "scripts", scriptName);
|
|
513
528
|
if (!existsSync(script)) return false;
|
|
514
529
|
const bash = resolveCommand(isWindows() ? ["bash.exe", "bash"] : ["bash"]);
|
|
515
530
|
if (!bash) return false;
|
|
516
531
|
const result = spawnSync(bash, [script, subcommand], {
|
|
517
532
|
cwd: pluginRoot,
|
|
518
|
-
env: runtimeEnv(),
|
|
533
|
+
env: { ...runtimeEnv(), ...envOverrides },
|
|
519
534
|
stdio: "ignore",
|
|
520
535
|
windowsHide: true,
|
|
521
536
|
timeout: PLUGIN_SERVICE_TIMEOUT_MS,
|
|
@@ -542,6 +557,12 @@ function refreshDashboardService(pluginRoot) {
|
|
|
542
557
|
return runPluginService(pluginRoot, "dashboard-service.sh", "start");
|
|
543
558
|
}
|
|
544
559
|
|
|
560
|
+
function startBackendService(pluginRoot, host) {
|
|
561
|
+
return runPluginService(pluginRoot, "backend-service.sh", "start", {
|
|
562
|
+
CLAUDE_SMART_HOST: host,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
545
566
|
function stopClaudeSmartServices(pluginRoot) {
|
|
546
567
|
runPluginService(pluginRoot, "dashboard-service.sh", "stop");
|
|
547
568
|
runPluginService(pluginRoot, "backend-service.sh", "stop");
|
|
@@ -834,9 +855,7 @@ function patchCodexHooksForNode(pluginRoot, nodePath) {
|
|
|
834
855
|
const runner = join(pluginRoot, "scripts", "codex-hook.js");
|
|
835
856
|
const command = (...args) => [nodePath, runner, ...args].map(quoteCommandPart).join(" ");
|
|
836
857
|
// Dispatch by command content rather than index — entries can be added or
|
|
837
|
-
// reordered
|
|
838
|
-
// breaking the patch. Entries that must run as bash (smart-install.sh)
|
|
839
|
-
// are left untouched.
|
|
858
|
+
// reordered without breaking the patch.
|
|
840
859
|
const patchOne = (original) => {
|
|
841
860
|
if (typeof original !== "string") return original;
|
|
842
861
|
if (original.includes("smart-install.sh")) return original;
|
|
@@ -956,13 +975,12 @@ function printHelp() {
|
|
|
956
975
|
"Usage:",
|
|
957
976
|
" npx claude-smart install Install the plugin into Claude Code",
|
|
958
977
|
" npx claude-smart install --host codex Register the plugin marketplace for Codex",
|
|
959
|
-
" npx claude-smart install --source <owner/repo> Override the marketplace source",
|
|
960
978
|
" npx claude-smart setup Configure managed/read-only/global setup",
|
|
961
979
|
" npx claude-smart uninstall --host codex Remove the Codex marketplace registration",
|
|
962
980
|
" npx claude-smart --help Show this help",
|
|
963
981
|
"",
|
|
964
982
|
"Claude Code install:",
|
|
965
|
-
" 1. claude plugin marketplace add <
|
|
983
|
+
" 1. claude plugin marketplace add <this package>",
|
|
966
984
|
` 2. claude plugin install ${PLUGIN_SPEC}`,
|
|
967
985
|
" 3. Reads ~/.reflexio/.env when managed/read-only setup was configured.",
|
|
968
986
|
"",
|
|
@@ -976,7 +994,8 @@ function printHelp() {
|
|
|
976
994
|
" 7. Restart Codex.",
|
|
977
995
|
"",
|
|
978
996
|
"Update:",
|
|
979
|
-
" npx claude-smart update
|
|
997
|
+
" npx claude-smart update Reinstall Claude Code support from this package",
|
|
998
|
+
" npx claude-smart update --host codex Reinstall Codex support from this package",
|
|
980
999
|
" npx claude-smart setup Configure managed/read-only/global setup",
|
|
981
1000
|
"",
|
|
982
1001
|
"Uninstall:",
|
|
@@ -986,17 +1005,6 @@ function printHelp() {
|
|
|
986
1005
|
);
|
|
987
1006
|
}
|
|
988
1007
|
|
|
989
|
-
function parseSource(args) {
|
|
990
|
-
const idx = args.indexOf("--source");
|
|
991
|
-
if (idx === -1) return DEFAULT_MARKETPLACE_SOURCE;
|
|
992
|
-
const value = args[idx + 1];
|
|
993
|
-
if (!value) {
|
|
994
|
-
process.stderr.write("error: --source requires a value (e.g. owner/repo)\n");
|
|
995
|
-
process.exit(1);
|
|
996
|
-
}
|
|
997
|
-
return value;
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
1008
|
function parseHost(args) {
|
|
1001
1009
|
const idx = args.indexOf("--host");
|
|
1002
1010
|
if (idx === -1) return "claude-code";
|
|
@@ -1392,33 +1400,53 @@ function installCodexPluginCache(pluginRoot) {
|
|
|
1392
1400
|
return cacheDir;
|
|
1393
1401
|
}
|
|
1394
1402
|
|
|
1395
|
-
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
+
function findCodexPluginRoot() {
|
|
1404
|
+
const candidates = [];
|
|
1405
|
+
try {
|
|
1406
|
+
for (const entry of readdirSync(CODEX_PLUGIN_CACHE_DIR, { withFileTypes: true })) {
|
|
1407
|
+
if (!entry.isDirectory()) continue;
|
|
1408
|
+
const candidate = join(CODEX_PLUGIN_CACHE_DIR, entry.name);
|
|
1409
|
+
if (
|
|
1410
|
+
existsSync(join(candidate, "pyproject.toml")) &&
|
|
1411
|
+
existsSync(join(candidate, "scripts", "smart-install.sh"))
|
|
1412
|
+
) {
|
|
1413
|
+
candidates.push(candidate);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
} catch {
|
|
1417
|
+
// No Codex cache yet.
|
|
1403
1418
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1419
|
+
candidates.sort((a, b) => {
|
|
1420
|
+
const versionCompare = compareSemverLikePathNames(b, a);
|
|
1421
|
+
if (versionCompare !== 0) return versionCompare;
|
|
1422
|
+
try {
|
|
1423
|
+
return statSync(b).mtimeMs - statSync(a).mtimeMs;
|
|
1424
|
+
} catch {
|
|
1425
|
+
return 0;
|
|
1426
|
+
}
|
|
1407
1427
|
});
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1428
|
+
return candidates[0] || null;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
async function runUpdate(args) {
|
|
1432
|
+
if (parseHost(args) === "codex") {
|
|
1433
|
+
await runUpdateCodex(args);
|
|
1434
|
+
return;
|
|
1411
1435
|
}
|
|
1412
1436
|
|
|
1413
|
-
process.stdout.write("\nclaude-smart updated. Restart Claude Code to apply.\n");
|
|
1414
1437
|
const pluginRoot = findClaudeCodePluginRoot();
|
|
1415
1438
|
if (pluginRoot) {
|
|
1416
|
-
|
|
1417
|
-
if (setup.readOnly) {
|
|
1418
|
-
prunePublishHooksForReadOnly(pluginRoot);
|
|
1419
|
-
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1420
|
-
}
|
|
1439
|
+
stopClaudeSmartServices(pluginRoot);
|
|
1421
1440
|
}
|
|
1441
|
+
process.stdout.write("Updating claude-smart by reinstalling from this package...\n");
|
|
1442
|
+
await runInstall(args, { retryInstallAfterUninstall: true });
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
async function runUpdateCodex(args) {
|
|
1446
|
+
const pluginRoot = findCodexPluginRoot() || join(PACKAGE_ROOT, "plugin");
|
|
1447
|
+
stopClaudeSmartServices(pluginRoot);
|
|
1448
|
+
process.stdout.write("Updating claude-smart Codex support by reinstalling from this package...\n");
|
|
1449
|
+
await runInstallCodex(args);
|
|
1422
1450
|
}
|
|
1423
1451
|
|
|
1424
1452
|
async function runUninstall(args) {
|
|
@@ -1471,7 +1499,7 @@ async function runSetup(args) {
|
|
|
1471
1499
|
if (code !== 0) process.exit(code);
|
|
1472
1500
|
}
|
|
1473
1501
|
|
|
1474
|
-
async function runInstall(args) {
|
|
1502
|
+
async function runInstall(args, options = {}) {
|
|
1475
1503
|
if (parseHost(args) === "codex") {
|
|
1476
1504
|
await runInstallCodex(args);
|
|
1477
1505
|
return;
|
|
@@ -1485,7 +1513,7 @@ async function runInstall(args) {
|
|
|
1485
1513
|
process.exit(1);
|
|
1486
1514
|
}
|
|
1487
1515
|
|
|
1488
|
-
const source =
|
|
1516
|
+
const source = PACKAGE_ROOT;
|
|
1489
1517
|
const setup = configureReflexioSetup();
|
|
1490
1518
|
const readOnly = setup.readOnly;
|
|
1491
1519
|
|
|
@@ -1495,7 +1523,21 @@ async function runInstall(args) {
|
|
|
1495
1523
|
];
|
|
1496
1524
|
|
|
1497
1525
|
for (const step of steps) {
|
|
1498
|
-
|
|
1526
|
+
let code = await runClaude(step.args, { spinnerLabel: step.label });
|
|
1527
|
+
if (
|
|
1528
|
+
code !== 0 &&
|
|
1529
|
+
options.retryInstallAfterUninstall &&
|
|
1530
|
+
step.args[0] === "plugin" &&
|
|
1531
|
+
step.args[1] === "install"
|
|
1532
|
+
) {
|
|
1533
|
+
process.stderr.write(
|
|
1534
|
+
`warning: \`claude ${step.args.join(" ")}\` failed (exit ${code}); retrying after uninstalling ${PLUGIN_SPEC}.\n`,
|
|
1535
|
+
);
|
|
1536
|
+
await runClaude(["plugin", "uninstall", PLUGIN_SPEC], {
|
|
1537
|
+
spinnerLabel: "Removing existing claude-smart install…",
|
|
1538
|
+
});
|
|
1539
|
+
code = await runClaude(step.args, { spinnerLabel: step.label });
|
|
1540
|
+
}
|
|
1499
1541
|
if (code !== 0) {
|
|
1500
1542
|
process.stderr.write(
|
|
1501
1543
|
`error: \`claude ${step.args.join(" ")}\` failed (exit ${code})\n`,
|
|
@@ -1512,6 +1554,9 @@ async function runInstall(args) {
|
|
|
1512
1554
|
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1513
1555
|
}
|
|
1514
1556
|
process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
|
|
1557
|
+
if (startBackendService(pluginRoot, "claude-code")) {
|
|
1558
|
+
process.stdout.write("Started claude-smart backend service.\n");
|
|
1559
|
+
}
|
|
1515
1560
|
if (refreshDashboardService(pluginRoot)) {
|
|
1516
1561
|
process.stdout.write("Refreshed claude-smart dashboard service.\n");
|
|
1517
1562
|
}
|
|
@@ -1592,6 +1637,9 @@ async function runInstallCodex(args) {
|
|
|
1592
1637
|
if (readOnly) {
|
|
1593
1638
|
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1594
1639
|
}
|
|
1640
|
+
if (startBackendService(cacheDir, "codex")) {
|
|
1641
|
+
process.stdout.write("Started claude-smart backend service.\n");
|
|
1642
|
+
}
|
|
1595
1643
|
if (refreshDashboardService(cacheDir)) {
|
|
1596
1644
|
process.stdout.write("Refreshed claude-smart dashboard service.\n");
|
|
1597
1645
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-smart",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.42",
|
|
4
4
|
"description": "Self-improving Claude Code plugin — learns from corrections via reflexio",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"bin",
|
|
30
30
|
"scripts/setup-claude-smart.sh",
|
|
31
31
|
".agents/plugins/marketplace.json",
|
|
32
|
+
".claude-plugin/marketplace.json",
|
|
32
33
|
"plugin",
|
|
33
34
|
"!plugin/**/__pycache__",
|
|
34
35
|
"!plugin/**/*.py[cod]",
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
|
+
import path from "path";
|
|
2
3
|
|
|
4
|
+
// Pin Turbopack's workspace root to this dashboard directory. When the
|
|
5
|
+
// plugin is installed via npx (e.g. ~/.npm/_npx/<hash>/node_modules/...),
|
|
6
|
+
// Next would otherwise detect the npx parent's package-lock.json as the
|
|
7
|
+
// workspace root and panic in `next build` ("Failed to write app endpoint
|
|
8
|
+
// /page"). Pinning the root avoids that misdetection.
|
|
3
9
|
const nextConfig: NextConfig = {
|
|
4
|
-
|
|
10
|
+
turbopack: {
|
|
11
|
+
root: path.resolve(__dirname),
|
|
12
|
+
},
|
|
5
13
|
};
|
|
6
14
|
|
|
7
15
|
export default nextConfig;
|
|
@@ -5,11 +5,6 @@
|
|
|
5
5
|
{
|
|
6
6
|
"matcher": "startup|resume",
|
|
7
7
|
"hooks": [
|
|
8
|
-
{
|
|
9
|
-
"type": "command",
|
|
10
|
-
"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/smart-install.sh\" || true",
|
|
11
|
-
"timeout": 300
|
|
12
|
-
},
|
|
13
8
|
{
|
|
14
9
|
"type": "command",
|
|
15
10
|
"command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/ensure-plugin-root.sh\" \"$_R\" || true",
|
package/plugin/hooks/hooks.json
CHANGED
|
@@ -14,16 +14,6 @@
|
|
|
14
14
|
}
|
|
15
15
|
],
|
|
16
16
|
"SessionStart": [
|
|
17
|
-
{
|
|
18
|
-
"matcher": "startup|clear|compact|resume",
|
|
19
|
-
"hooks": [
|
|
20
|
-
{
|
|
21
|
-
"type": "command",
|
|
22
|
-
"command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/smart-install.sh\"",
|
|
23
|
-
"timeout": 300
|
|
24
|
-
}
|
|
25
|
-
]
|
|
26
|
-
},
|
|
27
17
|
{
|
|
28
18
|
"matcher": "startup|clear|compact|resume",
|
|
29
19
|
"hooks": [
|
package/plugin/pyproject.toml
CHANGED
|
@@ -138,20 +138,27 @@ port_occupied() {
|
|
|
138
138
|
(echo >"/dev/tcp/127.0.0.1/$PORT") 2>/dev/null
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
# Reap
|
|
142
|
-
#
|
|
143
|
-
#
|
|
144
|
-
#
|
|
141
|
+
# Reap listeners still holding the given port after the PID file kill
|
|
142
|
+
# when their command line matches one of the supplied patterns. Filters
|
|
143
|
+
# by cmdline so we don't knock over an
|
|
144
|
+
# unrelated service a user has bound there — symmetric with start's
|
|
145
|
+
# refusal to stomp on a foreign listener. Silent on failure.
|
|
145
146
|
reap_port_listeners() {
|
|
147
|
+
port="${1:-$PORT}"
|
|
148
|
+
shift || true
|
|
149
|
+
[ "$#" -eq 0 ] && return 0
|
|
146
150
|
command -v lsof >/dev/null 2>&1 || return 0
|
|
147
|
-
candidates=$(lsof -ti:"$
|
|
151
|
+
candidates=$(lsof -ti:"$port" 2>/dev/null) || candidates=""
|
|
148
152
|
[ -z "$candidates" ] && return 0
|
|
149
153
|
ours=""
|
|
150
154
|
for pid in $candidates; do
|
|
151
155
|
cmdline=$(ps -p "$pid" -o command= 2>/dev/null || true)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
156
|
+
for pattern in "$@"; do
|
|
157
|
+
if [[ "$cmdline" == $pattern ]]; then
|
|
158
|
+
ours="$ours $pid"
|
|
159
|
+
break
|
|
160
|
+
fi
|
|
161
|
+
done
|
|
155
162
|
done
|
|
156
163
|
[ -z "$ours" ] && return 0
|
|
157
164
|
# shellcheck disable=SC2086
|
|
@@ -166,16 +173,33 @@ reap_port_listeners() {
|
|
|
166
173
|
kill -KILL $remaining 2>/dev/null || true
|
|
167
174
|
}
|
|
168
175
|
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
|
|
176
|
+
# Describe what (if anything) is currently listening on $1. Returns
|
|
177
|
+
# "<command> (pid <pid>)" or empty if the port is free or lsof is
|
|
178
|
+
# unavailable. Used to make port-conflict log lines diagnosable.
|
|
179
|
+
port_holder() {
|
|
180
|
+
command -v lsof >/dev/null 2>&1 || return 0
|
|
181
|
+
lsof -i:"$1" -sTCP:LISTEN -P -n 2>/dev/null \
|
|
182
|
+
| awk 'NR==2 {print $1" (pid "$2")"; exit}'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# Full shutdown: kill the recorded process group (if any) then sweep
|
|
186
|
+
# both the backend port and the embedding-service port for surviving
|
|
187
|
+
# reflexio listeners. Used by both `stop` and the opt-in `session-end`
|
|
188
|
+
# path so a stale/missing PID file doesn't produce a silent no-op, and
|
|
189
|
+
# so a stale embedding service on EMBEDDING_PORT doesn't block the next
|
|
190
|
+
# fresh boot (e.g. when codex-hook.js falls back to 8072 for the main
|
|
191
|
+
# backend because 8071 is held by another app).
|
|
173
192
|
full_stop() {
|
|
174
193
|
if [ -f "$PID_FILE" ]; then
|
|
175
194
|
kill_group "$(cat "$PID_FILE" 2>/dev/null)"
|
|
176
195
|
rm -f "$PID_FILE"
|
|
177
196
|
fi
|
|
178
|
-
reap_port_listeners
|
|
197
|
+
reap_port_listeners "$PORT" '*reflexio*' '*uvicorn*'
|
|
198
|
+
if [ -n "${EMBEDDING_PORT:-}" ] && [ "$EMBEDDING_PORT" != "$PORT" ]; then
|
|
199
|
+
reap_port_listeners "$EMBEDDING_PORT" \
|
|
200
|
+
'*reflexio.server.llm.embedding_service:app*' \
|
|
201
|
+
'*reflexio*embedding_service*'
|
|
202
|
+
fi
|
|
179
203
|
}
|
|
180
204
|
|
|
181
205
|
case "$CMD" in
|
|
@@ -195,19 +219,25 @@ case "$CMD" in
|
|
|
195
219
|
if is_our_backend_running; then emit_ok; exit 0; fi
|
|
196
220
|
if port_occupied; then
|
|
197
221
|
# Something answered the TCP probe but /health didn't — don't
|
|
198
|
-
# start a second uvicorn on top of it.
|
|
199
|
-
|
|
222
|
+
# start a second uvicorn on top of it. Surface the holder so the
|
|
223
|
+
# user knows which process to quit (common case: editor/dev tool
|
|
224
|
+
# squatting 8071) instead of silently failing.
|
|
225
|
+
holder="$(port_holder "$PORT" 2>/dev/null || true)"
|
|
226
|
+
msg="[claude-smart] backend: port $PORT held by another process${holder:+ ($holder)}; skipping start"
|
|
227
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "$msg"
|
|
228
|
+
echo "$msg" >&2
|
|
229
|
+
echo "Free port $PORT (or stop the process above) and run /claude-smart:restart again." >&2
|
|
200
230
|
emit_ok; exit 0
|
|
201
231
|
fi
|
|
202
232
|
if ! command -v uv >/dev/null 2>&1; then
|
|
203
233
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
204
|
-
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH;
|
|
205
|
-
CLAUDE_SMART_BOOTSTRAPPING=1
|
|
206
|
-
|
|
207
|
-
|
|
234
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; starting installer in background"
|
|
235
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
236
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
237
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
208
238
|
fi
|
|
209
239
|
if ! command -v uv >/dev/null 2>&1; then
|
|
210
|
-
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH
|
|
240
|
+
claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; installer recovery scheduled; skipping"
|
|
211
241
|
emit_ok; exit 0
|
|
212
242
|
fi
|
|
213
243
|
fi
|
package/plugin/scripts/cli.sh
CHANGED
|
@@ -45,7 +45,7 @@ if ! command -v uv >/dev/null 2>&1; then
|
|
|
45
45
|
fi
|
|
46
46
|
if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
47
47
|
echo "claude-smart: 'uv' not found — bootstrapping dependencies (~1-3 min on first install)..." >&2
|
|
48
|
-
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh"
|
|
48
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >/dev/null
|
|
49
49
|
claude_smart_prepend_astral_bins
|
|
50
50
|
claude_smart_prepend_node_bins
|
|
51
51
|
fi
|
|
@@ -63,4 +63,31 @@ if ! command -v uv >/dev/null 2>&1; then
|
|
|
63
63
|
fi
|
|
64
64
|
fi
|
|
65
65
|
|
|
66
|
+
# Slash commands run synchronously and surface stderr directly to the user,
|
|
67
|
+
# so a bare `python -m claude_smart.cli` ModuleNotFoundError leaks to the
|
|
68
|
+
# transcript when the install is mid-bootstrap (e.g. SessionStart's
|
|
69
|
+
# background smart-install.sh hasn't finished yet). Mirror hook_entry.sh's
|
|
70
|
+
# ensure_hook_package_importable check: run smart-install.sh inline once,
|
|
71
|
+
# then re-check, then surface either the install-failed marker reason or a
|
|
72
|
+
# clear "still bootstrapping" message instead of letting Python crash.
|
|
73
|
+
if ! claude_smart_python_imports "$PLUGIN_ROOT" claude_smart.cli; then
|
|
74
|
+
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] \
|
|
75
|
+
&& [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
76
|
+
echo "claude-smart: finishing install (~1-3 min on first install)..." >&2
|
|
77
|
+
CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >/dev/null || true
|
|
78
|
+
fi
|
|
79
|
+
if ! claude_smart_python_imports "$PLUGIN_ROOT" claude_smart.cli; then
|
|
80
|
+
if [ -f "$FAILURE_MARKER" ]; then
|
|
81
|
+
msg="$(head -n 1 "$FAILURE_MARKER" 2>/dev/null || echo "")"
|
|
82
|
+
[ -n "$msg" ] || msg="unknown error"
|
|
83
|
+
echo "claude-smart is not installed correctly: $msg" >&2
|
|
84
|
+
echo "Re-run the plugin's Setup (restart Claude Code) or fix the underlying issue and delete $FAILURE_MARKER to retry." >&2
|
|
85
|
+
else
|
|
86
|
+
echo "claude-smart: claude_smart package is not importable in $PLUGIN_ROOT/.venv." >&2
|
|
87
|
+
echo "Run $PLUGIN_ROOT/scripts/smart-install.sh manually to diagnose." >&2
|
|
88
|
+
fi
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
|
|
66
93
|
exec uv run --project "$PLUGIN_ROOT" --no-sync --quiet python -m claude_smart.cli "$@"
|
|
@@ -302,33 +302,6 @@ function detached(command, args, options = {}) {
|
|
|
302
302
|
return child.pid;
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
function runInstaller(root, reason) {
|
|
306
|
-
if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
|
|
307
|
-
const script = path.join(root, "scripts", "smart-install.sh");
|
|
308
|
-
if (!fs.existsSync(script)) return false;
|
|
309
|
-
const bash = bashPath();
|
|
310
|
-
if (!bash) return false;
|
|
311
|
-
appendLog("backend.log", `[claude-smart] ${reason}; running installer`);
|
|
312
|
-
const result = spawnSync(bash, [script], {
|
|
313
|
-
cwd: root,
|
|
314
|
-
env: {
|
|
315
|
-
...process.env,
|
|
316
|
-
CLAUDE_SMART_BOOTSTRAPPING: "1",
|
|
317
|
-
},
|
|
318
|
-
encoding: "utf8",
|
|
319
|
-
maxBuffer: 20 * 1024 * 1024,
|
|
320
|
-
windowsHide: true,
|
|
321
|
-
});
|
|
322
|
-
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
323
|
-
if (output) {
|
|
324
|
-
ensureDir(STATE_DIR);
|
|
325
|
-
fs.appendFileSync(path.join(STATE_DIR, "install.log"), `${output}\n`);
|
|
326
|
-
trimLog(path.join(STATE_DIR, "install.log"));
|
|
327
|
-
}
|
|
328
|
-
prependRuntimePath();
|
|
329
|
-
return result.status === 0;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
305
|
function startInstallerDetached(root, reason) {
|
|
333
306
|
if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
|
|
334
307
|
const script = path.join(root, "scripts", "smart-install.sh");
|
|
@@ -410,11 +383,11 @@ async function startBackend(root) {
|
|
|
410
383
|
}
|
|
411
384
|
const uv = uvPath();
|
|
412
385
|
if (!uv) {
|
|
413
|
-
|
|
386
|
+
startInstallerDetached(root, "backend: uv not on PATH");
|
|
414
387
|
}
|
|
415
388
|
const readyUv = uvPath();
|
|
416
389
|
if (!readyUv) {
|
|
417
|
-
appendLog("backend.log", "[claude-smart] backend: uv not on PATH
|
|
390
|
+
appendLog("backend.log", "[claude-smart] backend: uv not on PATH; installer recovery scheduled; skipping");
|
|
418
391
|
emitOk();
|
|
419
392
|
return;
|
|
420
393
|
}
|
|
@@ -478,11 +451,11 @@ async function startDashboard(root) {
|
|
|
478
451
|
}
|
|
479
452
|
const npm = npmPath();
|
|
480
453
|
if (!npm) {
|
|
481
|
-
|
|
454
|
+
startInstallerDetached(root, "dashboard: npm not on PATH");
|
|
482
455
|
}
|
|
483
456
|
const readyNpm = npmPath();
|
|
484
457
|
if (!readyNpm) {
|
|
485
|
-
appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH
|
|
458
|
+
appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH; installer recovery scheduled; skipping");
|
|
486
459
|
emitOk();
|
|
487
460
|
return;
|
|
488
461
|
}
|
|
@@ -512,14 +485,10 @@ function runHook(root, event) {
|
|
|
512
485
|
trimLog(path.join(STATE_DIR, "backend.log"));
|
|
513
486
|
let uv = uvPath();
|
|
514
487
|
if (!uv) {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
uv = uvPath();
|
|
518
|
-
} else {
|
|
519
|
-
startInstallerDetached(root, "hook: uv not on PATH");
|
|
520
|
-
}
|
|
488
|
+
startInstallerDetached(root, "hook: uv not on PATH");
|
|
489
|
+
uv = uvPath();
|
|
521
490
|
if (!uv) {
|
|
522
|
-
appendLog("backend.log", "[claude-smart] hook: uv not on PATH
|
|
491
|
+
appendLog("backend.log", "[claude-smart] hook: uv not on PATH; installer recovery scheduled; skipping");
|
|
523
492
|
emitHookOk();
|
|
524
493
|
return 0;
|
|
525
494
|
}
|
|
@@ -190,14 +190,13 @@ case "$CMD" in
|
|
|
190
190
|
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
191
191
|
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
192
192
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
193
|
-
echo "[claude-smart] dashboard: npm is not on PATH;
|
|
194
|
-
CLAUDE_SMART_BOOTSTRAPPING=1
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
NPM_BIN=$(claude_smart_resolve_npm || true)
|
|
193
|
+
echo "[claude-smart] dashboard: npm is not on PATH; starting installer in background" >>"$LOG_FILE"
|
|
194
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
195
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
196
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
198
197
|
fi
|
|
199
198
|
if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
|
|
200
|
-
reason="npm is not on PATH
|
|
199
|
+
reason="npm is not on PATH; installer recovery scheduled; dashboard cannot start yet"
|
|
201
200
|
echo "[claude-smart] dashboard: $reason; skipping" >>"$LOG_FILE"
|
|
202
201
|
claude_smart_write_dashboard_unavailable "$reason"
|
|
203
202
|
emit_ok; exit 0
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Maintain ~/.reflexio/plugin-root as a symlink to the active plugin
|
|
3
|
-
# install dir so slash commands can reference one short path
|
|
4
|
-
# of whether the user is on the remote marketplace (reflexioai) or the
|
|
5
|
-
# local-dev marketplace (reflexioai-local).
|
|
3
|
+
# install dir so slash commands can reference one short path.
|
|
6
4
|
#
|
|
7
5
|
# Usage: ensure-plugin-root.sh <target-dir> [--force]
|
|
8
|
-
# --force overwrite any existing link
|
|
6
|
+
# --force overwrite any existing link
|
|
9
7
|
# default self-heal only if the link is missing or points at an
|
|
10
8
|
# invalid target
|
|
11
9
|
set -eu
|
|
@@ -34,8 +32,7 @@ fi
|
|
|
34
32
|
|
|
35
33
|
# Opt-in: when CLAUDE_SMART_PLUGIN_ROOT_FOLLOW_SESSION=1 (set in the
|
|
36
34
|
# environment or in ~/.reflexio/.env), always relink to $TARGET so the
|
|
37
|
-
# symlink tracks the currently loaded plugin.
|
|
38
|
-
# a pinned local-dev link across sessions that load the remote plugin.
|
|
35
|
+
# symlink tracks the currently loaded plugin.
|
|
39
36
|
FOLLOW="${CLAUDE_SMART_PLUGIN_ROOT_FOLLOW_SESSION:-}"
|
|
40
37
|
if [ -z "$FOLLOW" ] && [ -f "$HOME/.reflexio/.env" ]; then
|
|
41
38
|
FOLLOW="$(grep -E '^CLAUDE_SMART_PLUGIN_ROOT_FOLLOW_SESSION=' "$HOME/.reflexio/.env" \
|
|
@@ -52,11 +49,9 @@ fi
|
|
|
52
49
|
|
|
53
50
|
# Cache-tracking: if the link currently resolves to a path under the
|
|
54
51
|
# managed plugin cache (~/.claude/plugins/cache/ or ~/.codex/plugins/cache/),
|
|
55
|
-
# always retarget it to
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
# fresh. Links pointing outside the cache (e.g., a user's local-dev
|
|
59
|
-
# checkout) are left alone here and handled by the self-heal below.
|
|
52
|
+
# always retarget it to $TARGET. Plugin updates leave old version
|
|
53
|
+
# directories behind, so a valid pyproject.toml at the stale target is
|
|
54
|
+
# not proof the link is fresh.
|
|
60
55
|
if [ -L "$LINK" ]; then
|
|
61
56
|
# Literal target string, not realpath: we compare against what was written by ln -s.
|
|
62
57
|
CURRENT="$(readlink "$LINK" 2>/dev/null || true)"
|
|
@@ -74,9 +69,7 @@ if [ -L "$LINK" ]; then
|
|
|
74
69
|
fi
|
|
75
70
|
|
|
76
71
|
# Self-heal path: only rewrite the link if it's missing or its target is
|
|
77
|
-
# gone/invalid.
|
|
78
|
-
# setup-local-dev.sh, so SessionStart hooks on the local install don't
|
|
79
|
-
# clobber the user's repo-pointing link.
|
|
72
|
+
# gone/invalid.
|
|
80
73
|
if [ -f "$LINK/pyproject.toml" ]; then
|
|
81
74
|
exit 0
|
|
82
75
|
fi
|
|
@@ -85,29 +85,18 @@ PY
|
|
|
85
85
|
fi
|
|
86
86
|
|
|
87
87
|
if ! command -v uv >/dev/null 2>&1; then
|
|
88
|
-
# Self-heal from skipped
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
# dependency setup.
|
|
88
|
+
# Self-heal from skipped setup without blocking the first prompt. The
|
|
89
|
+
# installer and Setup hook own dependency bootstrap; hooks only schedule
|
|
90
|
+
# recovery and return so SessionStart stays lightweight.
|
|
92
91
|
if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" = "1" ]; then
|
|
93
92
|
claude_smart_emit_continue
|
|
94
93
|
exit 0
|
|
95
94
|
fi
|
|
96
95
|
if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
|
|
97
96
|
mkdir -p "$STATE_DIR"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
claude_smart_prepend_node_bins
|
|
102
|
-
if command -v uv >/dev/null 2>&1; then
|
|
103
|
-
bash "$HERE/backend-service.sh" start >/dev/null 2>&1 || true
|
|
104
|
-
bash "$HERE/dashboard-service.sh" start >/dev/null 2>&1 || true
|
|
105
|
-
fi
|
|
106
|
-
else
|
|
107
|
-
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
108
|
-
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
109
|
-
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
110
|
-
fi
|
|
97
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
98
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
99
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
111
100
|
fi
|
|
112
101
|
if ! command -v uv >/dev/null 2>&1; then
|
|
113
102
|
claude_smart_emit_continue
|
|
@@ -128,17 +117,13 @@ ensure_hook_package_importable() {
|
|
|
128
117
|
fi
|
|
129
118
|
|
|
130
119
|
mkdir -p "$STATE_DIR"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
139
|
-
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
140
|
-
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
141
|
-
fi
|
|
120
|
+
{
|
|
121
|
+
printf '%s\n' "[claude-smart] hook: claude_smart.hook is not importable; retrying install in background"
|
|
122
|
+
date 2>/dev/null || true
|
|
123
|
+
} >>"$STATE_DIR/install.log" 2>&1
|
|
124
|
+
claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
|
|
125
|
+
bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
|
|
126
|
+
>>"$STATE_DIR/install.log" 2>&1 || true
|
|
142
127
|
|
|
143
128
|
claude_smart_python_imports "$PLUGIN_ROOT" claude_smart.hook
|
|
144
129
|
}
|
|
@@ -118,6 +118,13 @@ install_complete() {
|
|
|
118
118
|
return 0
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
start_backend_service() {
|
|
122
|
+
if [ -x "$HERE/backend-service.sh" ]; then
|
|
123
|
+
echo "[claude-smart] starting backend service in background" >&2
|
|
124
|
+
bash "$HERE/backend-service.sh" start >/dev/null 2>&1 || true
|
|
125
|
+
fi
|
|
126
|
+
}
|
|
127
|
+
|
|
121
128
|
install_vendored_reflexio() {
|
|
122
129
|
local VENDORED_REFLEXIO PLUGIN_PYTHON
|
|
123
130
|
|
|
@@ -391,6 +398,7 @@ fi
|
|
|
391
398
|
preflight_supported_runtime_platform
|
|
392
399
|
|
|
393
400
|
if install_complete; then
|
|
401
|
+
start_backend_service
|
|
394
402
|
claude_smart_emit_continue
|
|
395
403
|
exit 0
|
|
396
404
|
fi
|
|
@@ -438,6 +446,22 @@ if ! command -v uv >/dev/null 2>&1; then
|
|
|
438
446
|
fi
|
|
439
447
|
|
|
440
448
|
cd "$PLUGIN_ROOT"
|
|
449
|
+
# Self-heal a corrupt .venv before uv sync. uv refuses to reuse a venv that
|
|
450
|
+
# exists but has no python executable (e.g. partial cleanup of an npm/npx
|
|
451
|
+
# tree, an interrupted earlier install, or the underlying interpreter being
|
|
452
|
+
# uninstalled) — it errors with "not a valid Python environment" instead of
|
|
453
|
+
# rebuilding. Pre-clearing lets uv recreate it cleanly.
|
|
454
|
+
if [ -d "$PLUGIN_ROOT/.venv" ]; then
|
|
455
|
+
if claude_smart_is_windows; then
|
|
456
|
+
plugin_python_path="$PLUGIN_ROOT/.venv/Scripts/python.exe"
|
|
457
|
+
else
|
|
458
|
+
plugin_python_path="$PLUGIN_ROOT/.venv/bin/python"
|
|
459
|
+
fi
|
|
460
|
+
if [ ! -x "$plugin_python_path" ]; then
|
|
461
|
+
echo "[claude-smart] .venv at $PLUGIN_ROOT/.venv has no python executable; recreating" >&2
|
|
462
|
+
rm -rf "$PLUGIN_ROOT/.venv"
|
|
463
|
+
fi
|
|
464
|
+
fi
|
|
441
465
|
echo "[claude-smart] running uv sync..." >&2
|
|
442
466
|
if ! uv sync --locked --python 3.12 --quiet >&2; then
|
|
443
467
|
write_failure "uv sync failed in $PLUGIN_ROOT — run 'uv sync --locked --python 3.12' there to diagnose"
|
|
@@ -618,12 +642,13 @@ elif [ -d "$DASHBOARD_DIR" ]; then
|
|
|
618
642
|
fi
|
|
619
643
|
|
|
620
644
|
# Point ~/.reflexio/plugin-root at this install so slash commands can
|
|
621
|
-
# reference one stable short path regardless of which
|
|
622
|
-
# (reflexioai or reflexioai-local) loaded us.
|
|
645
|
+
# reference one stable short path regardless of which install loaded us.
|
|
623
646
|
if ! bash "$HERE/ensure-plugin-root.sh" "$PLUGIN_ROOT"; then
|
|
624
647
|
echo "[claude-smart] WARNING: failed to set ~/.reflexio/plugin-root symlink — slash commands may not resolve" >&2
|
|
625
648
|
fi
|
|
626
649
|
|
|
650
|
+
start_backend_service
|
|
651
|
+
|
|
627
652
|
write_success_marker
|
|
628
|
-
echo "[claude-smart] install complete. Backend
|
|
653
|
+
echo "[claude-smart] install complete. Backend started; dashboard auto-starts on session start." >&2
|
|
629
654
|
claude_smart_emit_continue
|
|
@@ -38,7 +38,6 @@ from claude_smart.reflexio_adapter import Adapter
|
|
|
38
38
|
|
|
39
39
|
_REFLEXIO_ENV_PATH = env_config.REFLEXIO_ENV_PATH
|
|
40
40
|
_MANAGED_REFLEXIO_URL = env_config.MANAGED_REFLEXIO_URL
|
|
41
|
-
_DEFAULT_MARKETPLACE_SOURCE = "ReflexioAI/claude-smart"
|
|
42
41
|
_PLUGIN_SPEC = "claude-smart@reflexioai"
|
|
43
42
|
_CODEX_MARKETPLACE_NAME = "reflexioai"
|
|
44
43
|
_CODEX_MARKETPLACE_DISPLAY_NAME = "ReflexioAI"
|
|
@@ -803,6 +802,23 @@ def _install_codex_plugin_cache(plugin_root: Path) -> tuple[bool, str]:
|
|
|
803
802
|
return True, f"installed Codex plugin cache at {cache_dir}"
|
|
804
803
|
|
|
805
804
|
|
|
805
|
+
def _find_codex_plugin_root() -> Path | None:
|
|
806
|
+
"""Locate the latest installed Codex plugin cache root."""
|
|
807
|
+
candidates = (
|
|
808
|
+
[
|
|
809
|
+
child
|
|
810
|
+
for child in _CODEX_PLUGIN_CACHE_DIR.iterdir()
|
|
811
|
+
if child.is_dir()
|
|
812
|
+
and (child / "pyproject.toml").is_file()
|
|
813
|
+
and (child / "scripts" / "smart-install.sh").is_file()
|
|
814
|
+
]
|
|
815
|
+
if _CODEX_PLUGIN_CACHE_DIR.is_dir()
|
|
816
|
+
else []
|
|
817
|
+
)
|
|
818
|
+
candidates.sort(key=_installed_plugin_sort_key, reverse=True)
|
|
819
|
+
return candidates[0] if candidates else None
|
|
820
|
+
|
|
821
|
+
|
|
806
822
|
def _cleanup_codex_install_state() -> bool:
|
|
807
823
|
"""Remove Codex's local install artifacts while preserving shared learning data.
|
|
808
824
|
|
|
@@ -1055,13 +1071,13 @@ def cmd_install_codex(args: argparse.Namespace) -> int:
|
|
|
1055
1071
|
def cmd_install(args: argparse.Namespace) -> int:
|
|
1056
1072
|
"""Install claude-smart into Claude Code via the native plugin CLI.
|
|
1057
1073
|
|
|
1058
|
-
Runs ``claude plugin marketplace add``
|
|
1059
|
-
|
|
1060
|
-
|
|
1074
|
+
Runs ``claude plugin marketplace add`` (pointed at the bundled marketplace
|
|
1075
|
+
at ``_REPO_ROOT``) followed by ``claude plugin install``, then appends the
|
|
1076
|
+
local-provider flags to ``~/.reflexio/.env`` so reflexio can route
|
|
1077
|
+
generation through the local Claude Code CLI.
|
|
1061
1078
|
|
|
1062
1079
|
Args:
|
|
1063
|
-
args (argparse.Namespace): Parsed CLI args.
|
|
1064
|
-
marketplace ref (``owner/repo`` on GitHub, or a local directory).
|
|
1080
|
+
args (argparse.Namespace): Parsed CLI args.
|
|
1065
1081
|
|
|
1066
1082
|
Returns:
|
|
1067
1083
|
int: 0 on success, non-zero if the ``claude`` CLI is missing or fails.
|
|
@@ -1077,13 +1093,27 @@ def cmd_install(args: argparse.Namespace) -> int:
|
|
|
1077
1093
|
return 1
|
|
1078
1094
|
read_only = _configure_reflexio_setup()
|
|
1079
1095
|
|
|
1096
|
+
refresh_existing = bool(getattr(args, "refresh_existing", False))
|
|
1080
1097
|
for cmd in (
|
|
1081
|
-
["claude", "plugin", "marketplace", "add",
|
|
1098
|
+
["claude", "plugin", "marketplace", "add", str(_REPO_ROOT)],
|
|
1082
1099
|
["claude", "plugin", "install", _PLUGIN_SPEC],
|
|
1083
1100
|
):
|
|
1084
1101
|
try:
|
|
1085
1102
|
subprocess.run(cmd, check=True)
|
|
1086
1103
|
except subprocess.CalledProcessError as exc:
|
|
1104
|
+
if refresh_existing and cmd[1:3] == ["plugin", "install"]:
|
|
1105
|
+
sys.stderr.write(
|
|
1106
|
+
f"warning: {' '.join(cmd)} failed (exit {exc.returncode}); "
|
|
1107
|
+
f"retrying after uninstalling {_PLUGIN_SPEC}\n"
|
|
1108
|
+
)
|
|
1109
|
+
subprocess.run(
|
|
1110
|
+
["claude", "plugin", "uninstall", _PLUGIN_SPEC], check=False
|
|
1111
|
+
)
|
|
1112
|
+
try:
|
|
1113
|
+
subprocess.run(cmd, check=True)
|
|
1114
|
+
continue
|
|
1115
|
+
except subprocess.CalledProcessError as retry_exc:
|
|
1116
|
+
exc = retry_exc
|
|
1087
1117
|
sys.stderr.write(f"error: {' '.join(cmd)} failed (exit {exc.returncode})\n")
|
|
1088
1118
|
return exc.returncode or 1
|
|
1089
1119
|
|
|
@@ -1112,41 +1142,44 @@ def cmd_install(args: argparse.Namespace) -> int:
|
|
|
1112
1142
|
|
|
1113
1143
|
|
|
1114
1144
|
def cmd_update(args: argparse.Namespace) -> int:
|
|
1115
|
-
"""Update claude-smart
|
|
1145
|
+
"""Update claude-smart by reinstalling from this package.
|
|
1116
1146
|
|
|
1117
|
-
|
|
1147
|
+
The simplified install path registers the bundled package root as the
|
|
1148
|
+
marketplace, so update is a stop + reinstall instead of
|
|
1149
|
+
``claude plugin update``.
|
|
1118
1150
|
|
|
1119
1151
|
Args:
|
|
1120
|
-
args (argparse.Namespace): Parsed CLI args
|
|
1152
|
+
args (argparse.Namespace): Parsed CLI args.
|
|
1121
1153
|
|
|
1122
1154
|
Returns:
|
|
1123
|
-
int: 0 on success, non-zero if the
|
|
1155
|
+
int: 0 on success, non-zero if the host CLI is missing or install fails.
|
|
1124
1156
|
"""
|
|
1125
|
-
if
|
|
1126
|
-
|
|
1127
|
-
"error: 'claude' CLI not found on PATH. "
|
|
1128
|
-
"Install Claude Code first: https://claude.com/claude-code\n"
|
|
1129
|
-
)
|
|
1130
|
-
return 1
|
|
1157
|
+
if getattr(args, "host", "claude-code") == "codex":
|
|
1158
|
+
return cmd_update_codex(args)
|
|
1131
1159
|
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
return exc.returncode or 1
|
|
1160
|
+
_run_service(_DASHBOARD_SCRIPT, "stop")
|
|
1161
|
+
_run_service(_BACKEND_SCRIPT, "stop")
|
|
1162
|
+
sys.stdout.write("Updating claude-smart by reinstalling from this package...\n")
|
|
1163
|
+
install_args = argparse.Namespace(**vars(args), refresh_existing=True)
|
|
1164
|
+
install_args.host = "claude-code"
|
|
1165
|
+
return cmd_install(install_args)
|
|
1139
1166
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1167
|
+
|
|
1168
|
+
def cmd_update_codex(_args: argparse.Namespace) -> int:
|
|
1169
|
+
"""Update Codex support by refreshing its marketplace/cache install."""
|
|
1170
|
+
plugin_root = _find_codex_plugin_root()
|
|
1142
1171
|
if plugin_root is not None:
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1172
|
+
_run_service(plugin_root / "scripts" / "dashboard-service.sh", "stop")
|
|
1173
|
+
_run_service(plugin_root / "scripts" / "backend-service.sh", "stop")
|
|
1174
|
+
else:
|
|
1175
|
+
_run_service(_DASHBOARD_SCRIPT, "stop")
|
|
1176
|
+
_run_service(_BACKEND_SCRIPT, "stop")
|
|
1177
|
+
sys.stdout.write(
|
|
1178
|
+
"Updating claude-smart Codex support by reinstalling from this package...\n"
|
|
1179
|
+
)
|
|
1180
|
+
install_args = argparse.Namespace(**vars(_args))
|
|
1181
|
+
install_args.host = "codex"
|
|
1182
|
+
return cmd_install_codex(install_args)
|
|
1150
1183
|
|
|
1151
1184
|
|
|
1152
1185
|
def cmd_uninstall(_args: argparse.Namespace) -> int:
|
|
@@ -1952,14 +1985,15 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
1952
1985
|
default="claude-code",
|
|
1953
1986
|
help="Install target host",
|
|
1954
1987
|
)
|
|
1955
|
-
inst.add_argument(
|
|
1956
|
-
"--source",
|
|
1957
|
-
default=_DEFAULT_MARKETPLACE_SOURCE,
|
|
1958
|
-
help="Marketplace ref — GitHub owner/repo, or a local directory path",
|
|
1959
|
-
)
|
|
1960
1988
|
inst.set_defaults(func=cmd_install)
|
|
1961
1989
|
|
|
1962
1990
|
upd = sub.add_parser("update", help="Update claude-smart to the latest version")
|
|
1991
|
+
upd.add_argument(
|
|
1992
|
+
"--host",
|
|
1993
|
+
choices=("claude-code", "codex"),
|
|
1994
|
+
default="claude-code",
|
|
1995
|
+
help="Update target host",
|
|
1996
|
+
)
|
|
1963
1997
|
upd.set_defaults(func=cmd_update)
|
|
1964
1998
|
|
|
1965
1999
|
uni = sub.add_parser("uninstall", help="Remove claude-smart")
|