claude-smart 0.2.48 → 0.2.50
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 +2 -2
- package/README.md +11 -40
- package/bin/claude-smart.js +393 -55
- package/package.json +3 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.coverage +0 -0
- package/plugin/README.md +4 -3
- package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
- package/plugin/dashboard/app/dashboard/page.tsx +6 -1
- package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
- package/plugin/dashboard/app/preferences/page.tsx +32 -35
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
- package/plugin/dashboard/app/sessions/page.tsx +2 -0
- package/plugin/dashboard/app/skills/page.tsx +65 -50
- package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
- package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
- package/plugin/dashboard/components/common/host-badge.tsx +118 -0
- package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
- package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
- package/plugin/dashboard/components/common/page-header.tsx +3 -3
- package/plugin/dashboard/lib/config-file.ts +5 -1
- package/plugin/dashboard/lib/host-attribution.ts +62 -0
- package/plugin/dashboard/lib/session-reader.ts +40 -2
- package/plugin/dashboard/lib/types.ts +7 -1
- package/plugin/opencode/dist/server.mjs +60 -2
- package/plugin/opencode/server.mts +64 -2
- package/plugin/pyproject.toml +2 -2
- package/plugin/scripts/_lib.sh +255 -7
- package/plugin/scripts/backend-python-runner.py +46 -0
- package/plugin/scripts/backend-service.sh +766 -123
- package/plugin/scripts/codex-hook.js +79 -229
- package/plugin/scripts/dashboard-open.sh +6 -4
- package/plugin/scripts/dashboard-service.sh +118 -137
- package/plugin/scripts/ensure-plugin-root.sh +106 -8
- package/plugin/scripts/hook_entry.sh +3 -0
- package/plugin/scripts/opencode-claude-compat.js +8 -1
- package/plugin/scripts/smart-install.sh +116 -21
- package/plugin/src/README.md +1 -1
- package/plugin/src/claude_smart/cli.py +93 -29
- package/plugin/src/claude_smart/context_inject.py +3 -0
- package/plugin/src/claude_smart/env_config.py +56 -11
- package/plugin/src/claude_smart/events/post_tool.py +2 -1
- package/plugin/src/claude_smart/events/session_end.py +2 -1
- package/plugin/src/claude_smart/events/stop.py +3 -0
- package/plugin/src/claude_smart/events/user_prompt.py +2 -1
- package/plugin/src/claude_smart/internal_call.py +5 -2
- package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
- package/plugin/src/claude_smart/publish.py +59 -7
- package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
- package/plugin/src/claude_smart/runtime.py +15 -6
- package/plugin/src/claude_smart/state.py +211 -52
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +13 -0
- package/plugin/vendor/reflexio/reflexio/README.md +7 -3
- package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
- package/plugin/vendor/reflexio/reflexio/cli/README.md +3 -0
- package/plugin/vendor/reflexio/reflexio/client/client.py +166 -9
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
- package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +195 -25
- package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
- package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/api.py +79 -5
- package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
- package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +63 -6
- package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +1 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +199 -2
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
- package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +63 -2
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
- package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
- package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/scheduler.py +142 -0
- package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +262 -0
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
- package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +1 -3
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +640 -80
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
- package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
- package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +166 -66
- package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
- package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +190 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +414 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +8 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +86 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +59 -7
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +8 -3
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +57 -12
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +197 -12
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +70 -11
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +17 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_commit_scope.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +269 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +82 -1
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +74 -2
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
- package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
- package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
- package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
- package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
package/bin/claude-smart.js
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* CLIs. Both Claude Code and Codex install from the bundled marketplace in
|
|
5
5
|
* this npm package: Claude Code registers the package root as a local
|
|
6
6
|
* marketplace, and Codex copies the bundled plugin into its own marketplace
|
|
7
|
-
* wrapper.
|
|
8
|
-
*
|
|
7
|
+
* wrapper. The npm path reads setup/bootstrap config from ~/.reflexio/.env and
|
|
8
|
+
* seeds runtime ~/.claude-smart/.env with local-provider defaults so reflexio
|
|
9
|
+
* can route generation through local tools with no API key.
|
|
9
10
|
* Managed/read-only/global setup is handled by `npx claude-smart setup`,
|
|
10
11
|
* which writes ~/.reflexio/.env before running this installer.
|
|
11
12
|
*
|
|
@@ -44,15 +45,24 @@ const CODEX_MARKETPLACE_DISPLAY_NAME = "ReflexioAI";
|
|
|
44
45
|
const CODEX_PLUGIN_ID = `claude-smart@${CODEX_MARKETPLACE_NAME}`;
|
|
45
46
|
const OPENCODE_BARE_PLUGIN_SPEC = "claude-smart";
|
|
46
47
|
const OPENCODE_CONFIG_NAMES = ["opencode.json", "opencode.jsonc"];
|
|
47
|
-
const
|
|
48
|
+
const REFLEXIO_SETUP_ENV_PATH = join(homedir(), ".reflexio", ".env");
|
|
49
|
+
const CLAUDE_SMART_ENV_PATH = join(homedir(), ".claude-smart", ".env");
|
|
48
50
|
const MANAGED_REFLEXIO_URL = "https://www.reflexio.ai/";
|
|
49
51
|
const MANAGED_SETUP_ENV = "CLAUDE_SMART_MANAGED_SETUP";
|
|
50
52
|
const CLAUDE_SMART_READ_ONLY_ENV = "CLAUDE_SMART_READ_ONLY";
|
|
51
53
|
const CLAUDE_SMART_USE_LOCAL_CLI_ENV = "CLAUDE_SMART_USE_LOCAL_CLI";
|
|
52
54
|
const CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV = "CLAUDE_SMART_USE_LOCAL_EMBEDDING";
|
|
55
|
+
const CLAUDE_SMART_HOST_ENV = "CLAUDE_SMART_HOST";
|
|
56
|
+
const CLAUDE_SMART_OPENCODE_PATH_ENV = "CLAUDE_SMART_OPENCODE_PATH";
|
|
53
57
|
const REFLEXIO_USER_ID_ENV = "REFLEXIO_USER_ID";
|
|
58
|
+
const HOST_CLAUDE_CODE = "claude-code";
|
|
59
|
+
const HOST_CODEX = "codex";
|
|
60
|
+
const HOST_OPENCODE = "opencode";
|
|
61
|
+
const SUPPORTED_HOSTS = [HOST_CLAUDE_CODE, HOST_CODEX, HOST_OPENCODE];
|
|
62
|
+
const DEFAULT_CLAUDE_SMART_HOST = HOST_CLAUDE_CODE;
|
|
54
63
|
const REFLEXIO_DIR = join(homedir(), ".reflexio");
|
|
55
64
|
const CLAUDE_SMART_STATE_DIR = join(homedir(), ".claude-smart");
|
|
65
|
+
const INSTALL_FAILURE_MARKER = join(CLAUDE_SMART_STATE_DIR, "install-failed");
|
|
56
66
|
const OPENCODE_LOCAL_PACKAGE_DIR = join(CLAUDE_SMART_STATE_DIR, "opencode", "claude-smart");
|
|
57
67
|
const OPENCODE_PACKAGE_LOCK_TIMEOUT_MS = 120_000;
|
|
58
68
|
const OPENCODE_PACKAGE_LOCK_STALE_MS = 10 * 60_000;
|
|
@@ -96,7 +106,7 @@ const CODEX_REQUIRED_FILES = [
|
|
|
96
106
|
"plugin/scripts/_codex_env.sh",
|
|
97
107
|
];
|
|
98
108
|
const CODEX_CLI_TIMEOUT_MS = 30_000;
|
|
99
|
-
const PLUGIN_SERVICE_TIMEOUT_MS =
|
|
109
|
+
const PLUGIN_SERVICE_TIMEOUT_MS = 45_000;
|
|
100
110
|
const COPYTREE_IGNORE_NAMES = new Set([
|
|
101
111
|
"__pycache__",
|
|
102
112
|
".venv",
|
|
@@ -108,7 +118,7 @@ const COPYTREE_IGNORE_NAMES = new Set([
|
|
|
108
118
|
]);
|
|
109
119
|
const LOCAL_DEFAULT_ENV_ENTRIES = [
|
|
110
120
|
[
|
|
111
|
-
"# Route reflexio generation through the local
|
|
121
|
+
"# Route reflexio generation through the configured local host CLI",
|
|
112
122
|
CLAUDE_SMART_USE_LOCAL_CLI_ENV,
|
|
113
123
|
"1",
|
|
114
124
|
],
|
|
@@ -118,13 +128,17 @@ const LOCAL_DEFAULT_ENV_ENTRIES = [
|
|
|
118
128
|
"1",
|
|
119
129
|
],
|
|
120
130
|
[null, CLAUDE_SMART_READ_ONLY_ENV, "0"],
|
|
131
|
+
[null, CLAUDE_SMART_HOST_ENV, DEFAULT_CLAUDE_SMART_HOST],
|
|
121
132
|
];
|
|
133
|
+
const ENV_OVERRIDABLE_LOCAL_DEFAULT_KEYS = new Set([
|
|
134
|
+
CLAUDE_SMART_USE_LOCAL_CLI_ENV,
|
|
135
|
+
CLAUDE_SMART_USE_LOCAL_EMBEDDING_ENV,
|
|
136
|
+
]);
|
|
122
137
|
const LOCAL_MODE_PRUNE_KEYS = new Set([
|
|
123
138
|
"REFLEXIO_URL",
|
|
124
139
|
"REFLEXIO_API_KEY",
|
|
125
140
|
REFLEXIO_USER_ID_ENV,
|
|
126
141
|
]);
|
|
127
|
-
|
|
128
142
|
function shouldCopyPath(src) {
|
|
129
143
|
const base = src.split(/[\\/]/).pop() || "";
|
|
130
144
|
if (COPYTREE_IGNORE_NAMES.has(base)) return false;
|
|
@@ -254,14 +268,25 @@ function escapeEnvValue(value) {
|
|
|
254
268
|
return String(value).replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
255
269
|
}
|
|
256
270
|
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
271
|
+
function resolveLocalEnvDefault(key, fallback, installHost) {
|
|
272
|
+
if (key === CLAUDE_SMART_HOST_ENV) return installHost;
|
|
273
|
+
if (ENV_OVERRIDABLE_LOCAL_DEFAULT_KEYS.has(key)) {
|
|
274
|
+
const explicit = (process.env[key] || "").trim();
|
|
275
|
+
if (explicit) return explicit;
|
|
276
|
+
}
|
|
277
|
+
return fallback;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function ensureLocalEnvFile(path, installHost) {
|
|
281
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
282
|
+
const existing = existsSync(path)
|
|
283
|
+
? readFileSync(path, "utf8")
|
|
261
284
|
: "";
|
|
262
285
|
const present = new Set();
|
|
263
286
|
const keptLines = [];
|
|
264
287
|
let pruned = false;
|
|
288
|
+
let changed = false;
|
|
289
|
+
let hostWritten = false;
|
|
265
290
|
for (const line of existing.split(/\r?\n/)) {
|
|
266
291
|
const parsed = parseEnvLine(line);
|
|
267
292
|
if (parsed) {
|
|
@@ -269,6 +294,18 @@ function ensureLocalReflexioEnv() {
|
|
|
269
294
|
pruned = true;
|
|
270
295
|
continue;
|
|
271
296
|
}
|
|
297
|
+
if (parsed.key === CLAUDE_SMART_HOST_ENV) {
|
|
298
|
+
present.add(parsed.key);
|
|
299
|
+
if (!hostWritten) {
|
|
300
|
+
const replacement = `${CLAUDE_SMART_HOST_ENV}=${escapeEnvValue(installHost)}`;
|
|
301
|
+
keptLines.push(replacement);
|
|
302
|
+
hostWritten = true;
|
|
303
|
+
if (line !== replacement || parsed.value !== installHost) changed = true;
|
|
304
|
+
} else {
|
|
305
|
+
changed = true;
|
|
306
|
+
}
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
272
309
|
present.add(parsed.key);
|
|
273
310
|
}
|
|
274
311
|
keptLines.push(line);
|
|
@@ -278,29 +315,62 @@ function ensureLocalReflexioEnv() {
|
|
|
278
315
|
const added = [];
|
|
279
316
|
for (const [comment, key, value] of LOCAL_DEFAULT_ENV_ENTRIES) {
|
|
280
317
|
if (present.has(key)) continue;
|
|
318
|
+
const effectiveValue = resolveLocalEnvDefault(key, value, installHost);
|
|
281
319
|
if (comment) additions.push(comment);
|
|
282
320
|
if (key === CLAUDE_SMART_READ_ONLY_ENV) {
|
|
283
|
-
additions.push(`${key}="${escapeEnvValue(
|
|
321
|
+
additions.push(`${key}="${escapeEnvValue(effectiveValue)}"`);
|
|
284
322
|
} else {
|
|
285
|
-
additions.push(`${key}=${escapeEnvValue(
|
|
323
|
+
additions.push(`${key}=${escapeEnvValue(effectiveValue)}`);
|
|
286
324
|
}
|
|
287
325
|
added.push(key);
|
|
288
326
|
}
|
|
289
327
|
|
|
290
|
-
if (additions.length > 0 || pruned) {
|
|
328
|
+
if (additions.length > 0 || pruned || changed) {
|
|
291
329
|
let content = keptLines.join("\n").replace(/\n*$/, "");
|
|
292
330
|
if (additions.length > 0) {
|
|
293
331
|
const prefix = content ? "\n" : "";
|
|
294
332
|
content = content + prefix + additions.join("\n");
|
|
295
333
|
}
|
|
296
|
-
writeFileSync(
|
|
297
|
-
} else if (!existsSync(
|
|
298
|
-
writeFileSync(
|
|
334
|
+
writeFileSync(path, content ? `${content}\n` : "");
|
|
335
|
+
} else if (!existsSync(path)) {
|
|
336
|
+
writeFileSync(path, "");
|
|
337
|
+
}
|
|
338
|
+
chmodSync(path, 0o600);
|
|
339
|
+
return added;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function setEnvVars(path, values) {
|
|
343
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
344
|
+
const existing = existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
345
|
+
const seen = new Set();
|
|
346
|
+
const out = [];
|
|
347
|
+
for (const line of existing ? existing.split(/\r?\n/) : []) {
|
|
348
|
+
const parsed = parseEnvLine(line);
|
|
349
|
+
if (!parsed || !(parsed.key in values)) {
|
|
350
|
+
out.push(line);
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
out.push(`${parsed.key}="${escapeEnvValue(values[parsed.key])}"`);
|
|
354
|
+
seen.add(parsed.key);
|
|
299
355
|
}
|
|
300
|
-
|
|
356
|
+
const added = [];
|
|
357
|
+
for (const [key, value] of Object.entries(values)) {
|
|
358
|
+
if (seen.has(key)) continue;
|
|
359
|
+
out.push(`${key}="${escapeEnvValue(value)}"`);
|
|
360
|
+
added.push(key);
|
|
361
|
+
}
|
|
362
|
+
const content = out.join("\n").replace(/\n*$/, "");
|
|
363
|
+
writeFileSync(path, content ? `${content}\n` : "");
|
|
364
|
+
chmodSync(path, 0o600);
|
|
301
365
|
return added;
|
|
302
366
|
}
|
|
303
367
|
|
|
368
|
+
function ensureLocalInstallEnvDefaults(installHost = DEFAULT_CLAUDE_SMART_HOST) {
|
|
369
|
+
const added = ensureLocalEnvFile(REFLEXIO_SETUP_ENV_PATH, installHost);
|
|
370
|
+
const runtimeAdded = ensureLocalEnvFile(CLAUDE_SMART_ENV_PATH, installHost);
|
|
371
|
+
return Array.from(new Set([...added, ...runtimeAdded]));
|
|
372
|
+
}
|
|
373
|
+
|
|
304
374
|
function maskSecret(value) {
|
|
305
375
|
if (!value) return "";
|
|
306
376
|
if (value.length <= 8) return "*".repeat(value.length);
|
|
@@ -308,12 +378,12 @@ function maskSecret(value) {
|
|
|
308
378
|
return `${prefix}****${value.slice(-4)}`;
|
|
309
379
|
}
|
|
310
380
|
|
|
311
|
-
function loadReflexioSetupEnv() {
|
|
381
|
+
function loadReflexioSetupEnv(installHost = DEFAULT_CLAUDE_SMART_HOST) {
|
|
312
382
|
let readOnlyValue = "";
|
|
313
383
|
let fileApiKey = "";
|
|
314
384
|
let fileUrl = "";
|
|
315
|
-
if (existsSync(
|
|
316
|
-
const text = readFileSync(
|
|
385
|
+
if (existsSync(REFLEXIO_SETUP_ENV_PATH)) {
|
|
386
|
+
const text = readFileSync(REFLEXIO_SETUP_ENV_PATH, "utf8");
|
|
317
387
|
for (const line of text.split(/\r?\n/)) {
|
|
318
388
|
const parsed = parseEnvLine(line);
|
|
319
389
|
if (!parsed) continue;
|
|
@@ -341,9 +411,9 @@ function loadReflexioSetupEnv() {
|
|
|
341
411
|
delete process.env.REFLEXIO_API_KEY;
|
|
342
412
|
delete process.env[REFLEXIO_USER_ID_ENV];
|
|
343
413
|
delete process.env[MANAGED_SETUP_ENV];
|
|
344
|
-
const added =
|
|
414
|
+
const added = ensureLocalInstallEnvDefaults(installHost);
|
|
345
415
|
if (added.length > 0) {
|
|
346
|
-
process.stdout.write(`Seeded ${
|
|
416
|
+
process.stdout.write(`Seeded ${REFLEXIO_SETUP_ENV_PATH} with ${added.join(", ")}.\n`);
|
|
347
417
|
}
|
|
348
418
|
}
|
|
349
419
|
const readOnly = ["1", "true", "yes", "on"].includes(
|
|
@@ -352,8 +422,8 @@ function loadReflexioSetupEnv() {
|
|
|
352
422
|
return { readOnly };
|
|
353
423
|
}
|
|
354
424
|
|
|
355
|
-
function configureReflexioSetup() {
|
|
356
|
-
return loadReflexioSetupEnv();
|
|
425
|
+
function configureReflexioSetup(installHost = DEFAULT_CLAUDE_SMART_HOST) {
|
|
426
|
+
return loadReflexioSetupEnv(installHost);
|
|
357
427
|
}
|
|
358
428
|
|
|
359
429
|
function stripJsonc(text) {
|
|
@@ -529,7 +599,7 @@ function hasExtractionProvider() {
|
|
|
529
599
|
if ((process.env.REFLEXIO_API_KEY || "").trim()) return true;
|
|
530
600
|
const cliPath = (process.env.CLAUDE_SMART_CLI_PATH || "").trim();
|
|
531
601
|
if (cliPath && isExecutableFile(cliPath)) return true;
|
|
532
|
-
return hasCli("claude") || hasCli("codex") ||
|
|
602
|
+
return hasCli("claude") || hasCli("codex") || Boolean(resolveOpenCodePath());
|
|
533
603
|
}
|
|
534
604
|
|
|
535
605
|
function isExecutableFile(path) {
|
|
@@ -550,6 +620,94 @@ function extractionProviderError() {
|
|
|
550
620
|
);
|
|
551
621
|
}
|
|
552
622
|
|
|
623
|
+
function hasOpenCodeCli() {
|
|
624
|
+
return Boolean(resolveOpenCodePath());
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function resolveOpenCodePath() {
|
|
628
|
+
const opencodePath = (process.env[CLAUDE_SMART_OPENCODE_PATH_ENV] || "").trim();
|
|
629
|
+
if (opencodePath && isExecutableFile(opencodePath)) return opencodePath;
|
|
630
|
+
return resolveCommand(isWindows() ? ["opencode.cmd", "opencode.exe", "opencode"] : ["opencode"]);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function persistOpenCodePath() {
|
|
634
|
+
const resolved = resolveOpenCodePath();
|
|
635
|
+
if (!resolved) return [];
|
|
636
|
+
process.env[CLAUDE_SMART_OPENCODE_PATH_ENV] = resolved;
|
|
637
|
+
return setEnvVars(CLAUDE_SMART_ENV_PATH, { [CLAUDE_SMART_OPENCODE_PATH_ENV]: resolved });
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const WINDOWS_SYSTEM_BASH_SUFFIXES = [
|
|
641
|
+
"\\windows\\system32\\bash.exe",
|
|
642
|
+
"\\windows\\sysnative\\bash.exe",
|
|
643
|
+
"\\windows\\syswow64\\bash.exe",
|
|
644
|
+
];
|
|
645
|
+
|
|
646
|
+
function windowsPathText(path) {
|
|
647
|
+
return path.replace(/\//g, "\\").toLowerCase();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function isWindowsSystemBash(path) {
|
|
651
|
+
const normalized = windowsPathText(path);
|
|
652
|
+
return WINDOWS_SYSTEM_BASH_SUFFIXES.some((suffix) => normalized.endsWith(suffix));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function pathCommandCandidates(names) {
|
|
656
|
+
// Return every PATH match so Windows can skip System32 bash and still find Git Bash.
|
|
657
|
+
const delimiter = isWindows() ? ";" : ":";
|
|
658
|
+
const pathParts = (process.env.PATH || "").split(delimiter).filter(Boolean);
|
|
659
|
+
const candidates = [];
|
|
660
|
+
for (const dir of pathParts) {
|
|
661
|
+
for (const name of names) {
|
|
662
|
+
const candidate = join(dir, name);
|
|
663
|
+
if (existsSync(candidate)) candidates.push(candidate);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
return candidates;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function firstUsableBash(candidates) {
|
|
670
|
+
for (const candidate of candidates) {
|
|
671
|
+
const resolved = existsSync(candidate) ? candidate : resolveCommand([candidate]);
|
|
672
|
+
if (resolved && !isWindowsSystemBash(resolved)) return resolved;
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function resolveUsableBash() {
|
|
678
|
+
if (!isWindows()) return resolveCommand(["bash"]);
|
|
679
|
+
const sources = [];
|
|
680
|
+
const bashEnv = (process.env.BASH || "").trim();
|
|
681
|
+
if (bashEnv) sources.push([bashEnv]);
|
|
682
|
+
sources.push([
|
|
683
|
+
"C:\\Program Files\\Git\\bin\\bash.exe",
|
|
684
|
+
"C:\\Program Files (x86)\\Git\\bin\\bash.exe",
|
|
685
|
+
]);
|
|
686
|
+
sources.push(pathCommandCandidates(["bash.exe", "bash"]));
|
|
687
|
+
sources.push(["bash.exe", "bash"]);
|
|
688
|
+
for (const source of sources) {
|
|
689
|
+
const resolved = firstUsableBash(source);
|
|
690
|
+
if (resolved) return resolved;
|
|
691
|
+
}
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function opencodePrerequisiteError() {
|
|
696
|
+
if (!hasOpenCodeCli()) {
|
|
697
|
+
return (
|
|
698
|
+
"error: OpenCode CLI not found on PATH. Install OpenCode first, " +
|
|
699
|
+
"or set CLAUDE_SMART_OPENCODE_PATH to the OpenCode executable.\n"
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
if (isWindows() && !resolveUsableBash()) {
|
|
703
|
+
return (
|
|
704
|
+
"error: Git Bash is required for claude-smart OpenCode support on Windows. " +
|
|
705
|
+
"Install Git for Windows and ensure bash.exe is on PATH, or run OpenCode from WSL.\n"
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
return null;
|
|
709
|
+
}
|
|
710
|
+
|
|
553
711
|
function findClaudeCodePluginRoot() {
|
|
554
712
|
const cacheRoot = join(homedir(), ".claude", "plugins", "cache", CODEX_MARKETPLACE_NAME, "claude-smart");
|
|
555
713
|
const candidates = [];
|
|
@@ -658,6 +816,66 @@ function fileSha256(path) {
|
|
|
658
816
|
return crypto.createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
659
817
|
}
|
|
660
818
|
|
|
819
|
+
function claudeCodeCacheRoot() {
|
|
820
|
+
return join(homedir(), ".claude", "plugins", "cache", CODEX_MARKETPLACE_NAME, "claude-smart");
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Version of the plugin this npm package ships. The Claude Code cache is keyed
|
|
824
|
+
// by this version, and each version vendors a different Reflexio — so the
|
|
825
|
+
// vendor payload may only ever be compared against the cache dir for *this*
|
|
826
|
+
// version. Comparing against some other version's cache reports a spurious
|
|
827
|
+
// mismatch (its vendor legitimately differs) and would force a pointless
|
|
828
|
+
// reinstall.
|
|
829
|
+
function claudeCodePackageVersion(sourceRoot = join(PACKAGE_ROOT, "plugin")) {
|
|
830
|
+
try {
|
|
831
|
+
const manifest = JSON.parse(
|
|
832
|
+
readFileSync(join(sourceRoot, ".claude-plugin", "plugin.json"), "utf8"),
|
|
833
|
+
);
|
|
834
|
+
if (typeof manifest.version === "string" && manifest.version) return manifest.version;
|
|
835
|
+
} catch {
|
|
836
|
+
// Fall through to pyproject.toml.
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
const match = readFileSync(join(sourceRoot, "pyproject.toml"), "utf8").match(
|
|
840
|
+
/^version\s*=\s*"([^"]+)"/m,
|
|
841
|
+
);
|
|
842
|
+
if (match) return match[1];
|
|
843
|
+
} catch {
|
|
844
|
+
// No readable version.
|
|
845
|
+
}
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function claudeCodeVendorPayloadMismatch(
|
|
850
|
+
pluginRoot,
|
|
851
|
+
sourceRoot = join(PACKAGE_ROOT, "plugin"),
|
|
852
|
+
) {
|
|
853
|
+
// A sentinel pair, not a full tree hash: it catches the failure this exists
|
|
854
|
+
// for (a cache with no vendor bundle at all, or one from a different build),
|
|
855
|
+
// not a vendor tree that is individually corrupt past these two files.
|
|
856
|
+
const vendorFiles = [
|
|
857
|
+
"vendor/reflexio/pyproject.toml",
|
|
858
|
+
"vendor/reflexio/reflexio/__init__.py",
|
|
859
|
+
];
|
|
860
|
+
// A git checkout gitignores the generated vendor bundle, so a source tree
|
|
861
|
+
// without one is a dev install, not a broken cache. Nothing to compare.
|
|
862
|
+
const sourceHasVendor = vendorFiles.some((rel) =>
|
|
863
|
+
existsSync(join(sourceRoot, rel)),
|
|
864
|
+
);
|
|
865
|
+
if (!sourceHasVendor) return null;
|
|
866
|
+
|
|
867
|
+
for (const rel of vendorFiles) {
|
|
868
|
+
const source = join(sourceRoot, rel);
|
|
869
|
+
const cached = join(pluginRoot, rel);
|
|
870
|
+
if (!existsSync(source)) {
|
|
871
|
+
throw new Error(`installed claude-smart package is missing ${rel}`);
|
|
872
|
+
}
|
|
873
|
+
if (!existsSync(cached)) return rel;
|
|
874
|
+
if (fileSha256(source) !== fileSha256(cached)) return rel;
|
|
875
|
+
}
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
878
|
+
|
|
661
879
|
function verifyOpenCodePluginPackage(packageRoot) {
|
|
662
880
|
const sourceScript = join(PACKAGE_ROOT, "plugin", "scripts", "smart-install.sh");
|
|
663
881
|
const copiedScript = join(packageRoot, "plugin", "scripts", "smart-install.sh");
|
|
@@ -789,11 +1007,49 @@ async function bootstrapClaudeCodeInstall() {
|
|
|
789
1007
|
if (code !== 0) {
|
|
790
1008
|
throw new Error(`smart-install.sh failed in ${pluginRoot}`);
|
|
791
1009
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1010
|
+
throwIfInstallFailureMarker();
|
|
1011
|
+
return pluginRoot;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
async function refreshClaudeCodeCacheIfVendorMissing() {
|
|
1015
|
+
const version = claudeCodePackageVersion();
|
|
1016
|
+
if (!version) return null;
|
|
1017
|
+
// Only ever inspect and repair the cache dir for the version we are
|
|
1018
|
+
// installing. Other version dirs coexist in the cache and are not ours to
|
|
1019
|
+
// judge — `findClaudeCodePluginRoot()` returns the highest one, which is a
|
|
1020
|
+
// different plugin build whenever this package is a downgrade.
|
|
1021
|
+
const pluginRoot = join(claudeCodeCacheRoot(), version);
|
|
1022
|
+
if (!existsSync(pluginRoot)) return null;
|
|
1023
|
+
|
|
1024
|
+
const mismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
|
|
1025
|
+
if (!mismatch) return pluginRoot;
|
|
1026
|
+
|
|
1027
|
+
process.stderr.write(
|
|
1028
|
+
`warning: cached claude-smart plugin is missing or has stale ${mismatch}; reinstalling from the bundled npm package.\n`,
|
|
1029
|
+
);
|
|
1030
|
+
let code = await runClaude(["plugin", "uninstall", PLUGIN_SPEC], {
|
|
1031
|
+
spinnerLabel: "Removing stale claude-smart cache…",
|
|
1032
|
+
});
|
|
1033
|
+
if (code !== 0) {
|
|
1034
|
+
throw new Error(`could not remove stale ${PLUGIN_SPEC} cache (exit ${code})`);
|
|
1035
|
+
}
|
|
1036
|
+
code = await runClaude(["plugin", "install", PLUGIN_SPEC], {
|
|
1037
|
+
spinnerLabel: "Refreshing claude-smart cache…",
|
|
1038
|
+
});
|
|
1039
|
+
if (code !== 0) {
|
|
1040
|
+
throw new Error(`could not reinstall ${PLUGIN_SPEC} from the bundled package (exit ${code})`);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
if (!existsSync(pluginRoot)) {
|
|
1044
|
+
throw new Error(`Claude Code did not recreate the claude-smart plugin cache at ${pluginRoot}`);
|
|
1045
|
+
}
|
|
1046
|
+
const remainingMismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
|
|
1047
|
+
if (remainingMismatch) {
|
|
1048
|
+
throw new Error(
|
|
1049
|
+
`refreshed claude-smart cache still does not match the bundled npm package: ${remainingMismatch}`,
|
|
1050
|
+
);
|
|
796
1051
|
}
|
|
1052
|
+
process.stdout.write("Refreshed stale claude-smart plugin cache from the bundled npm package.\n");
|
|
797
1053
|
return pluginRoot;
|
|
798
1054
|
}
|
|
799
1055
|
|
|
@@ -870,8 +1126,14 @@ function runSilentStatus(command, args, options = {}) {
|
|
|
870
1126
|
function runPluginService(pluginRoot, scriptName, subcommand, envOverrides = {}) {
|
|
871
1127
|
const script = join(pluginRoot, "scripts", scriptName);
|
|
872
1128
|
if (!existsSync(script)) return false;
|
|
873
|
-
const bash =
|
|
874
|
-
if (!bash)
|
|
1129
|
+
const bash = resolveUsableBash();
|
|
1130
|
+
if (!bash) {
|
|
1131
|
+
const reason = isWindows()
|
|
1132
|
+
? "Git Bash is required for claude-smart services on Windows. Install Git for Windows and ensure bash.exe is on PATH, or run from WSL"
|
|
1133
|
+
: "bash is required but was not found on PATH";
|
|
1134
|
+
process.stderr.write(`warning: ${scriptName} ${subcommand} ${reason}; continuing.\n`);
|
|
1135
|
+
return false;
|
|
1136
|
+
}
|
|
875
1137
|
const result = spawnSync(bash, [script, subcommand], {
|
|
876
1138
|
cwd: pluginRoot,
|
|
877
1139
|
env: { ...runtimeEnv(), ...envOverrides },
|
|
@@ -1224,24 +1486,84 @@ function patchCodexHooksForNode(pluginRoot, nodePath) {
|
|
|
1224
1486
|
}
|
|
1225
1487
|
|
|
1226
1488
|
function ensurePluginRoot(pluginRoot) {
|
|
1227
|
-
const reflexioDir = dirname(
|
|
1228
|
-
const
|
|
1489
|
+
const reflexioDir = dirname(REFLEXIO_SETUP_ENV_PATH);
|
|
1490
|
+
const pluginRootLink = join(reflexioDir, "plugin-root");
|
|
1229
1491
|
mkdirSync(reflexioDir, { recursive: true });
|
|
1230
|
-
|
|
1492
|
+
let pathNotReplaceable = false;
|
|
1231
1493
|
try {
|
|
1232
|
-
|
|
1494
|
+
const existing = lstatSync(pluginRootLink);
|
|
1495
|
+
if (existing.isSymbolicLink() || existing.isFile()) {
|
|
1496
|
+
rmSync(pluginRootLink, { recursive: true, force: true });
|
|
1497
|
+
} else {
|
|
1498
|
+
pathNotReplaceable = true;
|
|
1499
|
+
}
|
|
1500
|
+
} catch (err) {
|
|
1501
|
+
if (!err || err.code !== "ENOENT") pathNotReplaceable = true;
|
|
1502
|
+
}
|
|
1503
|
+
if (pathNotReplaceable) {
|
|
1504
|
+
writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
try {
|
|
1508
|
+
symlinkSync(pluginRoot, pluginRootLink, isWindows() ? "junction" : "dir");
|
|
1509
|
+
writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
|
|
1233
1510
|
} catch {
|
|
1234
1511
|
writeFileSync(join(reflexioDir, "plugin-root.txt"), `${pluginRoot}\n`);
|
|
1235
1512
|
}
|
|
1236
1513
|
}
|
|
1237
1514
|
|
|
1515
|
+
function pluginPythonPath(pluginRoot) {
|
|
1516
|
+
// Python venvs created by uv use Scripts/python.exe on Windows across x64/arm64.
|
|
1517
|
+
return isWindows()
|
|
1518
|
+
? join(pluginRoot, ".venv", "Scripts", "python.exe")
|
|
1519
|
+
: join(pluginRoot, ".venv", "bin", "python");
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
function installFailureReason() {
|
|
1523
|
+
// smart-install.sh owns the install-failed marker format; Node surfaces the
|
|
1524
|
+
// first line as the actionable reason and leaves fingerprint handling to shell.
|
|
1525
|
+
const text = readFileSync(INSTALL_FAILURE_MARKER, "utf8");
|
|
1526
|
+
const first = text.split(/\r?\n/, 1)[0].trim();
|
|
1527
|
+
return first || "unknown error";
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function throwIfInstallFailureMarker() {
|
|
1531
|
+
if (existsSync(INSTALL_FAILURE_MARKER)) throw new Error(installFailureReason());
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function verifyWindowsLocalEmbeddingRuntime(pluginRoot, env) {
|
|
1535
|
+
if (!isWindows()) return;
|
|
1536
|
+
// Reflexio owns local embeddings, but this installer is the first place a
|
|
1537
|
+
// user sees whether the prepared Reflexio runtime can import onnxruntime.
|
|
1538
|
+
const script = join(pluginRoot, "scripts", "smart-install.sh");
|
|
1539
|
+
const bash = resolveUsableBash();
|
|
1540
|
+
if (!bash) {
|
|
1541
|
+
throw new Error(
|
|
1542
|
+
"Git Bash is required for claude-smart dependency checks on Windows. Install Git for Windows and ensure bash.exe is on PATH, or run from WSL.",
|
|
1543
|
+
);
|
|
1544
|
+
}
|
|
1545
|
+
const result = spawnSync(bash, [script, "verify-windows-embedding"], {
|
|
1546
|
+
cwd: pluginRoot,
|
|
1547
|
+
env,
|
|
1548
|
+
encoding: "utf8",
|
|
1549
|
+
windowsHide: true,
|
|
1550
|
+
});
|
|
1551
|
+
if (result.error || result.signal || result.status !== 0) {
|
|
1552
|
+
const detail = result.error
|
|
1553
|
+
? result.error.message
|
|
1554
|
+
: result.signal
|
|
1555
|
+
? `terminated by ${result.signal}`
|
|
1556
|
+
: (result.stderr || "").trim() || `exited with status ${result.status}`;
|
|
1557
|
+
throw new Error(`Windows local embedding preflight failed: ${detail}`);
|
|
1558
|
+
}
|
|
1559
|
+
throwIfInstallFailureMarker();
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1238
1562
|
async function installVendoredReflexio(pluginRoot, uv, env) {
|
|
1239
1563
|
const vendorRoot = join(pluginRoot, "vendor", "reflexio");
|
|
1240
1564
|
if (!existsSync(join(vendorRoot, "pyproject.toml"))) return;
|
|
1241
1565
|
|
|
1242
|
-
const pythonPath =
|
|
1243
|
-
? join(pluginRoot, ".venv", "Scripts", "python.exe")
|
|
1244
|
-
: join(pluginRoot, ".venv", "bin", "python");
|
|
1566
|
+
const pythonPath = pluginPythonPath(pluginRoot);
|
|
1245
1567
|
if (!existsSync(pythonPath)) {
|
|
1246
1568
|
throw new Error(`plugin Python was not created by uv sync: ${pythonPath}`);
|
|
1247
1569
|
}
|
|
@@ -1321,6 +1643,7 @@ async function syncPluginPythonEnv(pluginRoot, uv, env) {
|
|
|
1321
1643
|
async function bootstrapPluginRuntime(pluginRoot, options = {}) {
|
|
1322
1644
|
assertSupportedRuntimePlatform();
|
|
1323
1645
|
process.stdout.write("Preparing claude-smart runtime for hooks...\n");
|
|
1646
|
+
rmSync(INSTALL_FAILURE_MARKER, { force: true });
|
|
1324
1647
|
const nodeRuntime = await ensurePrivateNode();
|
|
1325
1648
|
if (options.patchCodexHooks !== false) {
|
|
1326
1649
|
patchCodexHooksForNode(pluginRoot, nodeRuntime.node);
|
|
@@ -1341,6 +1664,7 @@ async function bootstrapPluginRuntime(pluginRoot, options = {}) {
|
|
|
1341
1664
|
}
|
|
1342
1665
|
await syncPluginPythonEnv(pluginRoot, uv, env);
|
|
1343
1666
|
await installVendoredReflexio(pluginRoot, uv, env);
|
|
1667
|
+
verifyWindowsLocalEmbeddingRuntime(pluginRoot, env);
|
|
1344
1668
|
|
|
1345
1669
|
const dashboardDir = join(pluginRoot, "dashboard");
|
|
1346
1670
|
if (existsSync(dashboardDir)) {
|
|
@@ -1368,7 +1692,7 @@ function printHelp() {
|
|
|
1368
1692
|
"Claude Code install:",
|
|
1369
1693
|
" 1. claude plugin marketplace add <this package>",
|
|
1370
1694
|
` 2. claude plugin install ${PLUGIN_SPEC}`,
|
|
1371
|
-
" 3. Reads
|
|
1695
|
+
" 3. Reads setup/bootstrap config when managed/read-only setup was configured.",
|
|
1372
1696
|
"",
|
|
1373
1697
|
"Codex install:",
|
|
1374
1698
|
` 1. Copies the bundled marketplace to ${CODEX_MARKETPLACE_DIR}`,
|
|
@@ -1400,14 +1724,14 @@ function printHelp() {
|
|
|
1400
1724
|
|
|
1401
1725
|
function parseHost(args) {
|
|
1402
1726
|
const idx = args.indexOf("--host");
|
|
1403
|
-
if (idx === -1) return
|
|
1727
|
+
if (idx === -1) return DEFAULT_CLAUDE_SMART_HOST;
|
|
1404
1728
|
const value = args[idx + 1];
|
|
1405
1729
|
if (!value) {
|
|
1406
|
-
process.stderr.write(
|
|
1730
|
+
process.stderr.write(`error: --host requires a value: ${SUPPORTED_HOSTS.join(", ")}\n`);
|
|
1407
1731
|
process.exit(1);
|
|
1408
1732
|
}
|
|
1409
|
-
if (value
|
|
1410
|
-
process.stderr.write(
|
|
1733
|
+
if (!SUPPORTED_HOSTS.includes(value)) {
|
|
1734
|
+
process.stderr.write(`error: --host must be ${SUPPORTED_HOSTS.join(", ")}\n`);
|
|
1411
1735
|
process.exit(1);
|
|
1412
1736
|
}
|
|
1413
1737
|
return value;
|
|
@@ -1822,11 +2146,11 @@ function findCodexPluginRoot() {
|
|
|
1822
2146
|
}
|
|
1823
2147
|
|
|
1824
2148
|
async function runUpdate(args) {
|
|
1825
|
-
if (parseHost(args) ===
|
|
2149
|
+
if (parseHost(args) === HOST_CODEX) {
|
|
1826
2150
|
await runUpdateCodex(args);
|
|
1827
2151
|
return;
|
|
1828
2152
|
}
|
|
1829
|
-
if (parseHost(args) ===
|
|
2153
|
+
if (parseHost(args) === HOST_OPENCODE) {
|
|
1830
2154
|
await runUpdateOpenCode(args);
|
|
1831
2155
|
return;
|
|
1832
2156
|
}
|
|
@@ -1854,11 +2178,11 @@ async function runUpdateOpenCode(args) {
|
|
|
1854
2178
|
}
|
|
1855
2179
|
|
|
1856
2180
|
async function runUninstall(args) {
|
|
1857
|
-
if (parseHost(args) ===
|
|
2181
|
+
if (parseHost(args) === HOST_CODEX) {
|
|
1858
2182
|
await runUninstallCodex();
|
|
1859
2183
|
return;
|
|
1860
2184
|
}
|
|
1861
|
-
if (parseHost(args) ===
|
|
2185
|
+
if (parseHost(args) === HOST_OPENCODE) {
|
|
1862
2186
|
await runUninstallOpenCode(args);
|
|
1863
2187
|
return;
|
|
1864
2188
|
}
|
|
@@ -1908,11 +2232,11 @@ async function runSetup(args) {
|
|
|
1908
2232
|
}
|
|
1909
2233
|
|
|
1910
2234
|
async function runInstall(args, options = {}) {
|
|
1911
|
-
if (parseHost(args) ===
|
|
2235
|
+
if (parseHost(args) === HOST_CODEX) {
|
|
1912
2236
|
await runInstallCodex(args);
|
|
1913
2237
|
return;
|
|
1914
2238
|
}
|
|
1915
|
-
if (parseHost(args) ===
|
|
2239
|
+
if (parseHost(args) === HOST_OPENCODE) {
|
|
1916
2240
|
await runInstallOpenCode(args);
|
|
1917
2241
|
return;
|
|
1918
2242
|
}
|
|
@@ -1926,7 +2250,7 @@ async function runInstall(args, options = {}) {
|
|
|
1926
2250
|
}
|
|
1927
2251
|
|
|
1928
2252
|
const source = PACKAGE_ROOT;
|
|
1929
|
-
const setup = configureReflexioSetup();
|
|
2253
|
+
const setup = configureReflexioSetup(HOST_CLAUDE_CODE);
|
|
1930
2254
|
const readOnly = setup.readOnly;
|
|
1931
2255
|
|
|
1932
2256
|
const steps = [
|
|
@@ -1959,6 +2283,7 @@ async function runInstall(args, options = {}) {
|
|
|
1959
2283
|
}
|
|
1960
2284
|
|
|
1961
2285
|
try {
|
|
2286
|
+
await refreshClaudeCodeCacheIfVendorMissing();
|
|
1962
2287
|
const pluginRoot = await bootstrapClaudeCodeInstall();
|
|
1963
2288
|
restorePublishHooksFromSource(pluginRoot);
|
|
1964
2289
|
if (readOnly) {
|
|
@@ -1966,7 +2291,7 @@ async function runInstall(args, options = {}) {
|
|
|
1966
2291
|
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
1967
2292
|
}
|
|
1968
2293
|
process.stdout.write(`Prepared claude-smart runtime at ${pluginRoot}.\n`);
|
|
1969
|
-
if (startBackendService(pluginRoot,
|
|
2294
|
+
if (startBackendService(pluginRoot, HOST_CLAUDE_CODE)) {
|
|
1970
2295
|
process.stdout.write("Started claude-smart backend service.\n");
|
|
1971
2296
|
}
|
|
1972
2297
|
if (refreshDashboardService(pluginRoot)) {
|
|
@@ -1998,7 +2323,7 @@ async function runInstallCodex(args) {
|
|
|
1998
2323
|
process.stderr.write("error: 'codex' CLI not found on PATH. Install Codex first.\n");
|
|
1999
2324
|
process.exit(1);
|
|
2000
2325
|
}
|
|
2001
|
-
const setup = configureReflexioSetup();
|
|
2326
|
+
const setup = configureReflexioSetup(HOST_CODEX);
|
|
2002
2327
|
const readOnly = setup.readOnly;
|
|
2003
2328
|
|
|
2004
2329
|
const marketplaceRoot = copyCodexMarketplace();
|
|
@@ -2049,7 +2374,7 @@ async function runInstallCodex(args) {
|
|
|
2049
2374
|
if (readOnly) {
|
|
2050
2375
|
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
2051
2376
|
}
|
|
2052
|
-
if (startBackendService(cacheDir,
|
|
2377
|
+
if (startBackendService(cacheDir, HOST_CODEX)) {
|
|
2053
2378
|
process.stdout.write("Started claude-smart backend service.\n");
|
|
2054
2379
|
}
|
|
2055
2380
|
if (refreshDashboardService(cacheDir)) {
|
|
@@ -2099,8 +2424,14 @@ async function runInstallCodex(args) {
|
|
|
2099
2424
|
}
|
|
2100
2425
|
|
|
2101
2426
|
async function runInstallOpenCode(args) {
|
|
2102
|
-
const
|
|
2427
|
+
const prerequisiteError = opencodePrerequisiteError();
|
|
2428
|
+
if (prerequisiteError) {
|
|
2429
|
+
process.stderr.write(prerequisiteError);
|
|
2430
|
+
process.exit(1);
|
|
2431
|
+
}
|
|
2432
|
+
const setup = configureReflexioSetup(HOST_OPENCODE);
|
|
2103
2433
|
const readOnly = setup.readOnly;
|
|
2434
|
+
persistOpenCodePath();
|
|
2104
2435
|
if (!hasExtractionProvider()) {
|
|
2105
2436
|
process.stderr.write(extractionProviderError());
|
|
2106
2437
|
process.exit(1);
|
|
@@ -2139,7 +2470,7 @@ async function runInstallOpenCode(args) {
|
|
|
2139
2470
|
if (readOnly) {
|
|
2140
2471
|
process.stdout.write("Installed read-only hook manifest; publish interactions hooks are disabled.\n");
|
|
2141
2472
|
}
|
|
2142
|
-
if (startBackendService(pluginRoot,
|
|
2473
|
+
if (startBackendService(pluginRoot, HOST_OPENCODE)) {
|
|
2143
2474
|
process.stdout.write("Started claude-smart backend service.\n");
|
|
2144
2475
|
}
|
|
2145
2476
|
if (refreshDashboardService(pluginRoot)) {
|
|
@@ -2264,6 +2595,8 @@ if (require.main === module) {
|
|
|
2264
2595
|
module.exports = {
|
|
2265
2596
|
assertSupportedRuntimePlatform,
|
|
2266
2597
|
bootstrapPluginRuntime,
|
|
2598
|
+
claudeCodePackageVersion,
|
|
2599
|
+
claudeCodeVendorPayloadMismatch,
|
|
2267
2600
|
codexMarketplacePluginRoot,
|
|
2268
2601
|
copyCodexMarketplace,
|
|
2269
2602
|
ensurePrivateNode,
|
|
@@ -2274,7 +2607,12 @@ module.exports = {
|
|
|
2274
2607
|
opencodeLocalPluginSpec,
|
|
2275
2608
|
installOpenCodePluginPackage,
|
|
2276
2609
|
patchOpenCodePluginConfig,
|
|
2610
|
+
parseHost,
|
|
2277
2611
|
hasExtractionProvider,
|
|
2612
|
+
hasOpenCodeCli,
|
|
2613
|
+
persistOpenCodePath,
|
|
2614
|
+
resolveOpenCodePath,
|
|
2615
|
+
opencodePrerequisiteError,
|
|
2278
2616
|
platformSupportError,
|
|
2279
2617
|
prunePublishHooksForReadOnly,
|
|
2280
2618
|
restorePublishHooksFromSource,
|