claude-smart 0.2.46 → 0.2.48
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 +1 -1
- package/README.md +19 -11
- package/bin/claude-smart.js +290 -68
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/README.md +11 -10
- package/plugin/dashboard/app/layout.tsx +20 -0
- package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +36 -2
- package/plugin/dashboard/package-lock.json +61 -390
- package/plugin/dashboard/package.json +2 -2
- package/plugin/opencode/dist/server.mjs +76 -2
- package/plugin/opencode/server.mts +79 -2
- package/plugin/pyproject.toml +6 -2
- package/plugin/scripts/smart-install.sh +7 -1
- package/plugin/src/claude_smart/cli.py +210 -22
- package/plugin/src/claude_smart/context_format.py +9 -9
- package/plugin/src/claude_smart/cs_cite.py +66 -19
- package/plugin/uv.lock +5 -5
- package/plugin/vendor/reflexio/.env.example +7 -0
- package/plugin/vendor/reflexio/README.md +3 -3
- package/plugin/vendor/reflexio/pyproject.toml +2 -1
- package/plugin/vendor/reflexio/reflexio/README.md +11 -6
- package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +1 -1
- package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +2 -2
- package/plugin/vendor/reflexio/reflexio/cli/utils.py +44 -1
- package/plugin/vendor/reflexio/reflexio/client/client.py +97 -0
- package/plugin/vendor/reflexio/reflexio/lib/_agent_playbook.py +8 -0
- package/plugin/vendor/reflexio/reflexio/lib/_base.py +15 -0
- package/plugin/vendor/reflexio/reflexio/lib/_config.py +23 -18
- package/plugin/vendor/reflexio/reflexio/lib/_generation.py +9 -8
- package/plugin/vendor/reflexio/reflexio/lib/_interactions.py +16 -1
- package/plugin/vendor/reflexio/reflexio/lib/_profiles.py +27 -16
- package/plugin/vendor/reflexio/reflexio/lib/_search.py +27 -5
- package/plugin/vendor/reflexio/reflexio/lib/_user_playbook.py +9 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/__init__.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/enums.py +1 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/governance.py +117 -0
- package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +45 -3
- package/plugin/vendor/reflexio/reflexio/models/config_schema.py +37 -0
- package/plugin/vendor/reflexio/reflexio/server/README.md +38 -9
- package/plugin/vendor/reflexio/reflexio/server/__init__.py +21 -2
- package/plugin/vendor/reflexio/reflexio/server/api.py +274 -3267
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +4 -3
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +16 -1
- package/plugin/vendor/reflexio/reflexio/server/api_endpoints/request_context.py +1 -1
- package/plugin/vendor/reflexio/reflexio/server/{_auth.py → auth.py} +2 -0
- package/plugin/vendor/reflexio/reflexio/server/cache/reflexio_cache.py +62 -36
- package/plugin/vendor/reflexio/reflexio/server/deployment_profile.py +69 -0
- package/plugin/vendor/reflexio/reflexio/server/extensions.py +213 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +424 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_json_extraction.py +249 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_structured_output.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +980 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_types.py +110 -0
- package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +73 -1819
- package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +57 -5
- package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +12 -1
- package/plugin/vendor/reflexio/reflexio/server/middleware.py +244 -0
- package/plugin/vendor/reflexio/reflexio/server/operation_limiter.py +9 -1
- package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.4.0.prompt.md +14 -2
- package/plugin/vendor/reflexio/reflexio/server/rate_limit.py +79 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_common.py +25 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/_metering.py +98 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/braintrust.py +129 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/config.py +210 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +549 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/interactions.py +259 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/playbooks.py +578 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/profiles.py +423 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/provenance.py +345 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/search.py +349 -0
- package/plugin/vendor/reflexio/reflexio/server/routes/system.py +261 -0
- package/plugin/vendor/reflexio/reflexio/server/scheduling.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/README.md +5 -2
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/__init__.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_batch_progress.py +298 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_config_filter.py +152 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +243 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +299 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_status_change.py +273 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +258 -0
- package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +17 -1183
- package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +8 -1
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_scheduler.py +26 -41
- package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +8 -0
- package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +232 -123
- package/plugin/vendor/reflexio/reflexio/server/services/governance/config.py +52 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +378 -0
- package/plugin/vendor/reflexio/reflexio/server/services/governance/subject_refs.py +34 -0
- package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +385 -78
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/README.md +9 -1
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_prompt_processing.py +100 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +121 -525
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_clustering.py +184 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_postprocessing.py +130 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator_prompt_formatting.py +212 -0
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +258 -69
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +9 -9
- package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +3 -2
- package/plugin/vendor/reflexio/reflexio/server/services/publish_learning_worker.py +288 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/recency.py +211 -0
- package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +29 -13
- package/plugin/vendor/reflexio/reflexio/server/services/storage/error.py +4 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +681 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +43 -6
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +10 -1167
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +58 -351
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +49 -19
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +452 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_lineage.py +11 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_playbook.py +1 -2133
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_profiles.py +7 -1126
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_requests.py +73 -33
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_share_links.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_agent_run_store.py +506 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_pending_tool_call_store.py +704 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/agent_run/_run_tool_dependency_store.py +123 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/__init__.py +6 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_fts_vec.py +132 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_audit.py +122 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +465 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_purge.py +387 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_rebuild_hide.py +332 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +511 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +955 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +189 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_optimization.py +247 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_source_linkage.py +145 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +844 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +263 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +896 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_search.py +270 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +50 -9
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +64 -370
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +4 -4
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_playbook.py +0 -909
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_requests.py +2 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_share_links.py +20 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_agent_run_store.py +86 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +195 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_pending_tool_call_store.py +148 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_run_tool_dependency_store.py +38 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_audit.py +29 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_erase_execution.py +30 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_purge.py +74 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_rebuild_hide.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/governance/_subject_barrier.py +49 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/__init__.py +13 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +365 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +124 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_optimization.py +85 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_source_linkage.py +47 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +333 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/__init__.py +9 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +73 -0
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/{_profiles.py → profiles/_profile_store.py} +57 -86
- package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_search.py +32 -0
- package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +153 -12
- package/plugin/vendor/reflexio/reflexio/server/services/playbook/user_detail_stripping.py +0 -84
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"lint": "eslint"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
+
"@tailwindcss/postcss": "^4",
|
|
12
13
|
"@base-ui/react": "^1.3.0",
|
|
13
14
|
"class-variance-authority": "^0.7.1",
|
|
14
15
|
"clsx": "^2.1.1",
|
|
@@ -17,17 +18,16 @@
|
|
|
17
18
|
"next-themes": "^0.4.6",
|
|
18
19
|
"react": "19.2.4",
|
|
19
20
|
"react-dom": "19.2.4",
|
|
21
|
+
"shadcn": "^4.0.8",
|
|
20
22
|
"tailwind-merge": "^3.5.0",
|
|
21
23
|
"tw-animate-css": "^1.4.0"
|
|
22
24
|
},
|
|
23
25
|
"devDependencies": {
|
|
24
|
-
"@tailwindcss/postcss": "^4",
|
|
25
26
|
"@types/node": "^20",
|
|
26
27
|
"@types/react": "^19",
|
|
27
28
|
"@types/react-dom": "^19",
|
|
28
29
|
"eslint": "^9",
|
|
29
30
|
"eslint-config-next": "16.2.6",
|
|
30
|
-
"shadcn": "^4.0.8",
|
|
31
31
|
"tailwindcss": "^4",
|
|
32
32
|
"typescript": "^5"
|
|
33
33
|
},
|
|
@@ -1,11 +1,85 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { AssistantBuffer } from "./assistant-buffer.js";
|
|
5
7
|
import { sessionIDFrom } from "./internal.js";
|
|
6
8
|
import { chatMessagePayload, eventPayload, stopPayload, toolAfterPayload } from "./payload.js";
|
|
7
9
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
|
|
10
|
+
function homeDir() {
|
|
11
|
+
return process.env.HOME || homedir();
|
|
12
|
+
}
|
|
13
|
+
function textFilePluginRoot() {
|
|
14
|
+
try {
|
|
15
|
+
return readFileSync(join(homeDir(), ".reflexio", "plugin-root.txt"), "utf8").trim() || undefined;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isPluginRoot(candidate) {
|
|
22
|
+
return !!candidate
|
|
23
|
+
&& existsSync(join(candidate, "pyproject.toml"))
|
|
24
|
+
&& existsSync(join(candidate, "uv.lock"))
|
|
25
|
+
&& existsSync(join(candidate, "scripts", "hook_entry.sh"));
|
|
26
|
+
}
|
|
27
|
+
function canonicalPluginRoot(candidate) {
|
|
28
|
+
if (!isPluginRoot(candidate))
|
|
29
|
+
return undefined;
|
|
30
|
+
try {
|
|
31
|
+
return realpathSync(candidate);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function pathParts(candidate) {
|
|
38
|
+
return candidate.split(/[\\/]+/).filter(Boolean);
|
|
39
|
+
}
|
|
40
|
+
function isDescendantOrSame(child, parent) {
|
|
41
|
+
const path = relative(parent, child);
|
|
42
|
+
return path === "" || (!path.startsWith("..") && !isAbsolute(path));
|
|
43
|
+
}
|
|
44
|
+
function isReflexioSessionCopy(candidate) {
|
|
45
|
+
try {
|
|
46
|
+
const reflexioRoot = realpathSync(join(homeDir(), ".reflexio"));
|
|
47
|
+
return isDescendantOrSame(candidate, reflexioRoot);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function isTransientPackageRoot(candidate) {
|
|
54
|
+
return pathParts(candidate).includes("_npx") || isReflexioSessionCopy(candidate);
|
|
55
|
+
}
|
|
56
|
+
function resolvePluginRoot() {
|
|
57
|
+
const current = resolve(MODULE_DIR, "../..");
|
|
58
|
+
const explicit = canonicalPluginRoot(process.env.CLAUDE_SMART_PLUGIN_ROOT);
|
|
59
|
+
if (explicit)
|
|
60
|
+
return explicit;
|
|
61
|
+
const currentRoot = canonicalPluginRoot(current);
|
|
62
|
+
if (currentRoot && !isTransientPackageRoot(currentRoot))
|
|
63
|
+
return currentRoot;
|
|
64
|
+
const candidates = [
|
|
65
|
+
join(homeDir(), ".reflexio", "plugin-root"),
|
|
66
|
+
textFilePluginRoot(),
|
|
67
|
+
];
|
|
68
|
+
for (const candidate of candidates) {
|
|
69
|
+
const root = canonicalPluginRoot(candidate);
|
|
70
|
+
if (root)
|
|
71
|
+
return root;
|
|
72
|
+
}
|
|
73
|
+
if (currentRoot)
|
|
74
|
+
return currentRoot;
|
|
75
|
+
try {
|
|
76
|
+
return realpathSync(current);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return current;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const PLUGIN_ROOT = resolvePluginRoot();
|
|
9
83
|
const SCRIPTS_DIR = resolve(PLUGIN_ROOT, "scripts");
|
|
10
84
|
const HOOK_ENTRY = resolve(SCRIPTS_DIR, "hook_entry.sh");
|
|
11
85
|
const BACKEND_SERVICE = resolve(SCRIPTS_DIR, "backend-service.sh");
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process"
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs"
|
|
3
|
+
import { homedir } from "node:os"
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
|
3
5
|
import { fileURLToPath } from "node:url"
|
|
4
6
|
|
|
5
7
|
import { AssistantBuffer } from "./assistant-buffer.js"
|
|
@@ -20,7 +22,82 @@ type EventInput = {
|
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
const MODULE_DIR = dirname(fileURLToPath(import.meta.url))
|
|
23
|
-
|
|
25
|
+
|
|
26
|
+
function homeDir(): string {
|
|
27
|
+
return process.env.HOME || homedir()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function textFilePluginRoot(): string | undefined {
|
|
31
|
+
try {
|
|
32
|
+
return readFileSync(join(homeDir(), ".reflexio", "plugin-root.txt"), "utf8").trim() || undefined
|
|
33
|
+
} catch {
|
|
34
|
+
return undefined
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isPluginRoot(candidate: string | undefined): candidate is string {
|
|
39
|
+
return !!candidate
|
|
40
|
+
&& existsSync(join(candidate, "pyproject.toml"))
|
|
41
|
+
&& existsSync(join(candidate, "uv.lock"))
|
|
42
|
+
&& existsSync(join(candidate, "scripts", "hook_entry.sh"))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function canonicalPluginRoot(candidate: string | undefined): string | undefined {
|
|
46
|
+
if (!isPluginRoot(candidate)) return undefined
|
|
47
|
+
try {
|
|
48
|
+
return realpathSync(candidate)
|
|
49
|
+
} catch {
|
|
50
|
+
return undefined
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function pathParts(candidate: string): string[] {
|
|
55
|
+
return candidate.split(/[\\/]+/).filter(Boolean)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isDescendantOrSame(child: string, parent: string): boolean {
|
|
59
|
+
const path = relative(parent, child)
|
|
60
|
+
return path === "" || (!path.startsWith("..") && !isAbsolute(path))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isReflexioSessionCopy(candidate: string): boolean {
|
|
64
|
+
try {
|
|
65
|
+
const reflexioRoot = realpathSync(join(homeDir(), ".reflexio"))
|
|
66
|
+
return isDescendantOrSame(candidate, reflexioRoot)
|
|
67
|
+
} catch {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isTransientPackageRoot(candidate: string): boolean {
|
|
73
|
+
return pathParts(candidate).includes("_npx") || isReflexioSessionCopy(candidate)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolvePluginRoot(): string {
|
|
77
|
+
const current = resolve(MODULE_DIR, "../..")
|
|
78
|
+
const explicit = canonicalPluginRoot(process.env.CLAUDE_SMART_PLUGIN_ROOT)
|
|
79
|
+
if (explicit) return explicit
|
|
80
|
+
|
|
81
|
+
const currentRoot = canonicalPluginRoot(current)
|
|
82
|
+
if (currentRoot && !isTransientPackageRoot(currentRoot)) return currentRoot
|
|
83
|
+
|
|
84
|
+
const candidates = [
|
|
85
|
+
join(homeDir(), ".reflexio", "plugin-root"),
|
|
86
|
+
textFilePluginRoot(),
|
|
87
|
+
]
|
|
88
|
+
for (const candidate of candidates) {
|
|
89
|
+
const root = canonicalPluginRoot(candidate)
|
|
90
|
+
if (root) return root
|
|
91
|
+
}
|
|
92
|
+
if (currentRoot) return currentRoot
|
|
93
|
+
try {
|
|
94
|
+
return realpathSync(current)
|
|
95
|
+
} catch {
|
|
96
|
+
return current
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const PLUGIN_ROOT = resolvePluginRoot()
|
|
24
101
|
const SCRIPTS_DIR = resolve(PLUGIN_ROOT, "scripts")
|
|
25
102
|
const HOOK_ENTRY = resolve(SCRIPTS_DIR, "hook_entry.sh")
|
|
26
103
|
const BACKEND_SERVICE = resolve(SCRIPTS_DIR, "backend-service.sh")
|
package/plugin/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "claude-smart"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.48"
|
|
4
4
|
description = "Self-improving Claude Code, Codex, and OpenCode plugin that turns corrections into Preferences, Project-specific skills, and Shared skills"
|
|
5
5
|
keywords = [
|
|
6
6
|
"claude",
|
|
@@ -38,9 +38,13 @@ dependencies = [
|
|
|
38
38
|
"sqlite-vec>=0.1.6",
|
|
39
39
|
]
|
|
40
40
|
|
|
41
|
+
# claude-smart is distributed via npm only (see bin/claude-smart.js); it is not
|
|
42
|
+
# published to PyPI, so there is no `claude-smart` console script / uvx entry
|
|
43
|
+
# point. These two scripts are resolved by the runtime from the plugin venv
|
|
44
|
+
# (session_start.py locates claude-smart-optimizer-assistant; the hook is invoked
|
|
45
|
+
# via `python -m claude_smart.hook`), so they stay.
|
|
41
46
|
[project.scripts]
|
|
42
47
|
claude-smart-hook = "claude_smart.hook:main"
|
|
43
|
-
claude-smart = "claude_smart.cli:main"
|
|
44
48
|
claude-smart-optimizer-assistant = "claude_smart.optimizer_assistant:main"
|
|
45
49
|
|
|
46
50
|
[build-system]
|
|
@@ -465,7 +465,13 @@ if [ -d "$PLUGIN_ROOT/.venv" ]; then
|
|
|
465
465
|
fi
|
|
466
466
|
echo "[claude-smart] running uv sync..." >&2
|
|
467
467
|
if ! uv sync --locked --python 3.12 --quiet >&2; then
|
|
468
|
-
|
|
468
|
+
if uv lock --check --python 3.12 >/dev/null 2>&1; then
|
|
469
|
+
write_failure "uv sync failed in $PLUGIN_ROOT — run 'uv sync --locked --python 3.12' there to diagnose"
|
|
470
|
+
fi
|
|
471
|
+
echo "[claude-smart] plugin/uv.lock is out of sync; refreshing local lockfile and retrying uv sync" >&2
|
|
472
|
+
if ! uv lock --python 3.12 >&2 || ! uv sync --python 3.12 --quiet >&2; then
|
|
473
|
+
write_failure "uv sync failed in $PLUGIN_ROOT after refreshing plugin/uv.lock — run 'uv lock --python 3.12 && uv sync --python 3.12' there to diagnose"
|
|
474
|
+
fi
|
|
469
475
|
fi
|
|
470
476
|
# Defend against a stale lockfile silently dropping the project install.
|
|
471
477
|
# When plugin/uv.lock pins a different claude-smart version than pyproject.toml,
|
|
@@ -21,6 +21,10 @@ Exposes the following subcommands:
|
|
|
21
21
|
from __future__ import annotations
|
|
22
22
|
|
|
23
23
|
import argparse
|
|
24
|
+
from collections.abc import Iterable, Iterator
|
|
25
|
+
from contextlib import contextmanager
|
|
26
|
+
from dataclasses import dataclass
|
|
27
|
+
from hashlib import sha256
|
|
24
28
|
import json
|
|
25
29
|
import os
|
|
26
30
|
import re
|
|
@@ -29,11 +33,11 @@ import shutil
|
|
|
29
33
|
import subprocess
|
|
30
34
|
import sys
|
|
31
35
|
import time
|
|
32
|
-
from collections.abc import Iterable
|
|
33
|
-
from dataclasses import dataclass
|
|
34
36
|
from pathlib import Path
|
|
37
|
+
from urllib.parse import urlparse
|
|
38
|
+
from urllib.request import url2pathname
|
|
35
39
|
|
|
36
|
-
from claude_smart import context_format, env_config, ids, publish, state
|
|
40
|
+
from claude_smart import context_format, cs_cite, env_config, ids, publish, state
|
|
37
41
|
from claude_smart.reflexio_adapter import Adapter
|
|
38
42
|
|
|
39
43
|
_REFLEXIO_ENV_PATH = env_config.REFLEXIO_ENV_PATH
|
|
@@ -56,7 +60,7 @@ _CODEX_PLUGIN_CACHE_DIR = (
|
|
|
56
60
|
/ "claude-smart"
|
|
57
61
|
)
|
|
58
62
|
_CODEX_CLI_TIMEOUT_SECONDS = 30
|
|
59
|
-
|
|
63
|
+
_OPENCODE_BARE_PLUGIN_SPEC = "claude-smart"
|
|
60
64
|
_OPENCODE_CONFIG_NAMES = ("opencode.json", "opencode.jsonc")
|
|
61
65
|
_REFLEXIO_UNREACHABLE_MSG = (
|
|
62
66
|
"Failed to reach reflexio. Check ~/.claude-smart/backend.log "
|
|
@@ -72,6 +76,9 @@ _BACKEND_SCRIPT = _SCRIPTS_DIR / "backend-service.sh"
|
|
|
72
76
|
_DASHBOARD_SCRIPT = _SCRIPTS_DIR / "dashboard-service.sh"
|
|
73
77
|
_REFLEXIO_DIR = Path.home() / ".reflexio"
|
|
74
78
|
_STATE_DIR = Path.home() / ".claude-smart"
|
|
79
|
+
_OPENCODE_LOCAL_PACKAGE_DIR = _STATE_DIR / "opencode" / "claude-smart"
|
|
80
|
+
_OPENCODE_PACKAGE_LOCK_TIMEOUT_SECONDS = 120.0
|
|
81
|
+
_OPENCODE_PACKAGE_LOCK_STALE_SECONDS = 10 * 60.0
|
|
75
82
|
_LOCAL_DATA_NOTICE = (
|
|
76
83
|
"Local data was kept so reinstalling claude-smart can reuse your learned "
|
|
77
84
|
"rules, sessions, logs, and local Reflexio data.\n"
|
|
@@ -113,6 +120,7 @@ _COPYTREE_IGNORE = shutil.ignore_patterns(
|
|
|
113
120
|
"*.pyo",
|
|
114
121
|
".pytest_cache",
|
|
115
122
|
".ruff_cache",
|
|
123
|
+
".git",
|
|
116
124
|
".next",
|
|
117
125
|
"node_modules",
|
|
118
126
|
)
|
|
@@ -1080,9 +1088,50 @@ def _opencode_plugin_spec(entry: object) -> str | None:
|
|
|
1080
1088
|
return None
|
|
1081
1089
|
|
|
1082
1090
|
|
|
1091
|
+
def _opencode_local_plugin_spec(
|
|
1092
|
+
package_root: Path = _OPENCODE_LOCAL_PACKAGE_DIR,
|
|
1093
|
+
) -> str:
|
|
1094
|
+
return package_root.resolve(strict=False).as_uri()
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
def _is_default_opencode_package_path(package_path: Path) -> bool:
|
|
1098
|
+
if _same_real_path(package_path, _OPENCODE_LOCAL_PACKAGE_DIR):
|
|
1099
|
+
return True
|
|
1100
|
+
try:
|
|
1101
|
+
return package_path.resolve(strict=False) == _OPENCODE_LOCAL_PACKAGE_DIR.resolve(
|
|
1102
|
+
strict=False
|
|
1103
|
+
)
|
|
1104
|
+
except OSError:
|
|
1105
|
+
return False
|
|
1106
|
+
|
|
1107
|
+
|
|
1108
|
+
def _is_opencode_claude_smart_spec(spec: str | None) -> bool:
|
|
1109
|
+
if not spec:
|
|
1110
|
+
return False
|
|
1111
|
+
if spec == _OPENCODE_BARE_PLUGIN_SPEC or spec.startswith(
|
|
1112
|
+
f"{_OPENCODE_BARE_PLUGIN_SPEC}@"
|
|
1113
|
+
):
|
|
1114
|
+
return True
|
|
1115
|
+
parsed = urlparse(spec)
|
|
1116
|
+
if parsed.scheme != "file":
|
|
1117
|
+
return False
|
|
1118
|
+
package_path = Path(url2pathname(parsed.path))
|
|
1119
|
+
if _is_default_opencode_package_path(package_path):
|
|
1120
|
+
return True
|
|
1121
|
+
try:
|
|
1122
|
+
manifest = json.loads((package_path / "package.json").read_text())
|
|
1123
|
+
if manifest.get("name") == _OPENCODE_BARE_PLUGIN_SPEC:
|
|
1124
|
+
return True
|
|
1125
|
+
except (OSError, json.JSONDecodeError):
|
|
1126
|
+
pass
|
|
1127
|
+
return False
|
|
1128
|
+
|
|
1129
|
+
|
|
1083
1130
|
def _patch_opencode_plugin_config(
|
|
1084
|
-
config_path: Path, *, install: bool
|
|
1131
|
+
config_path: Path, *, install: bool, plugin_spec: str | None = None
|
|
1085
1132
|
) -> tuple[bool, Path]:
|
|
1133
|
+
if install and plugin_spec is None:
|
|
1134
|
+
plugin_spec = _opencode_local_plugin_spec()
|
|
1086
1135
|
data = _read_jsonc_object(config_path)
|
|
1087
1136
|
for field in ("plugins", "plugin"):
|
|
1088
1137
|
value = data.get(field)
|
|
@@ -1100,9 +1149,13 @@ def _patch_opencode_plugin_config(
|
|
|
1100
1149
|
kept = [
|
|
1101
1150
|
item
|
|
1102
1151
|
for item in plugins
|
|
1103
|
-
if _opencode_plugin_spec(item)
|
|
1152
|
+
if not _is_opencode_claude_smart_spec(_opencode_plugin_spec(item))
|
|
1104
1153
|
]
|
|
1105
|
-
|
|
1154
|
+
if install:
|
|
1155
|
+
assert plugin_spec is not None
|
|
1156
|
+
next_plugins = [*kept, plugin_spec]
|
|
1157
|
+
else:
|
|
1158
|
+
next_plugins = kept
|
|
1106
1159
|
if isinstance(current_plugin, list):
|
|
1107
1160
|
changed = ("plugins" in data) or next_plugins != current_plugin
|
|
1108
1161
|
else:
|
|
@@ -1144,6 +1197,102 @@ def _opencode_install_supported_from_this_package() -> bool:
|
|
|
1144
1197
|
return (_SCRIPTS_DIR / "smart-install.sh").is_file()
|
|
1145
1198
|
|
|
1146
1199
|
|
|
1200
|
+
def _file_sha256(path: Path) -> str:
|
|
1201
|
+
return sha256(path.read_bytes()).hexdigest()
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def _same_real_path(left: Path, right: Path) -> bool:
|
|
1205
|
+
try:
|
|
1206
|
+
return left.resolve(strict=True) == right.resolve(strict=True)
|
|
1207
|
+
except OSError:
|
|
1208
|
+
return False
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
def _verify_opencode_plugin_package(package_root: Path) -> None:
|
|
1212
|
+
source_script = _PLUGIN_ROOT / "scripts" / "smart-install.sh"
|
|
1213
|
+
copied_script = package_root / "plugin" / "scripts" / "smart-install.sh"
|
|
1214
|
+
for path in (package_root / "package.json", copied_script):
|
|
1215
|
+
if not path.exists():
|
|
1216
|
+
raise OSError(f"OpenCode local plugin package is missing {path}")
|
|
1217
|
+
if _file_sha256(source_script) != _file_sha256(copied_script):
|
|
1218
|
+
raise OSError(
|
|
1219
|
+
"OpenCode local plugin package does not match the installed claude-smart package"
|
|
1220
|
+
)
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
@contextmanager
|
|
1224
|
+
def _opencode_package_install_lock(package_root: Path) -> Iterator[None]:
|
|
1225
|
+
lock_dir = package_root.parent / ".install.lock"
|
|
1226
|
+
package_root.parent.mkdir(parents=True, exist_ok=True)
|
|
1227
|
+
deadline = time.monotonic() + _OPENCODE_PACKAGE_LOCK_TIMEOUT_SECONDS
|
|
1228
|
+
while True:
|
|
1229
|
+
try:
|
|
1230
|
+
lock_dir.mkdir()
|
|
1231
|
+
break
|
|
1232
|
+
except FileExistsError:
|
|
1233
|
+
try:
|
|
1234
|
+
stale = (
|
|
1235
|
+
time.time() - lock_dir.stat().st_mtime
|
|
1236
|
+
> _OPENCODE_PACKAGE_LOCK_STALE_SECONDS
|
|
1237
|
+
)
|
|
1238
|
+
except OSError:
|
|
1239
|
+
stale = False
|
|
1240
|
+
if stale:
|
|
1241
|
+
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
1242
|
+
continue
|
|
1243
|
+
if time.monotonic() >= deadline:
|
|
1244
|
+
raise OSError(
|
|
1245
|
+
f"timed out waiting for OpenCode package install lock at {lock_dir}"
|
|
1246
|
+
)
|
|
1247
|
+
time.sleep(0.1)
|
|
1248
|
+
try:
|
|
1249
|
+
yield
|
|
1250
|
+
finally:
|
|
1251
|
+
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
def _unique_opencode_package_path(parent: Path, prefix: str) -> Path:
|
|
1255
|
+
return parent / f"{prefix}-{os.getpid()}-{time.monotonic_ns()}"
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
def _replace_opencode_plugin_package(staged_package: Path, package_root: Path) -> None:
|
|
1259
|
+
backup_package = _unique_opencode_package_path(
|
|
1260
|
+
package_root.parent, ".claude-smart-previous"
|
|
1261
|
+
)
|
|
1262
|
+
backup_created = False
|
|
1263
|
+
try:
|
|
1264
|
+
if package_root.exists() or package_root.is_symlink():
|
|
1265
|
+
package_root.rename(backup_package)
|
|
1266
|
+
backup_created = True
|
|
1267
|
+
staged_package.rename(package_root)
|
|
1268
|
+
if backup_created:
|
|
1269
|
+
shutil.rmtree(backup_package, ignore_errors=True)
|
|
1270
|
+
except OSError:
|
|
1271
|
+
if backup_created and not package_root.exists() and backup_package.exists():
|
|
1272
|
+
backup_package.rename(package_root)
|
|
1273
|
+
raise
|
|
1274
|
+
|
|
1275
|
+
|
|
1276
|
+
def _install_opencode_plugin_package() -> Path:
|
|
1277
|
+
package_root = _OPENCODE_LOCAL_PACKAGE_DIR
|
|
1278
|
+
if _same_real_path(_REPO_ROOT, package_root):
|
|
1279
|
+
_verify_opencode_plugin_package(package_root)
|
|
1280
|
+
return package_root
|
|
1281
|
+
with _opencode_package_install_lock(package_root):
|
|
1282
|
+
staged_package = _unique_opencode_package_path(
|
|
1283
|
+
package_root.parent, ".claude-smart-copy"
|
|
1284
|
+
)
|
|
1285
|
+
shutil.rmtree(staged_package, ignore_errors=True)
|
|
1286
|
+
try:
|
|
1287
|
+
shutil.copytree(_REPO_ROOT, staged_package, ignore=_COPYTREE_IGNORE)
|
|
1288
|
+
_verify_opencode_plugin_package(staged_package)
|
|
1289
|
+
_replace_opencode_plugin_package(staged_package, package_root)
|
|
1290
|
+
_verify_opencode_plugin_package(package_root)
|
|
1291
|
+
finally:
|
|
1292
|
+
shutil.rmtree(staged_package, ignore_errors=True)
|
|
1293
|
+
return package_root
|
|
1294
|
+
|
|
1295
|
+
|
|
1147
1296
|
def _bootstrap_opencode_install(read_only: bool) -> tuple[bool, str]:
|
|
1148
1297
|
if not _opencode_install_supported_from_this_package():
|
|
1149
1298
|
return (
|
|
@@ -1152,22 +1301,28 @@ def _bootstrap_opencode_install(read_only: bool) -> tuple[bool, str]:
|
|
|
1152
1301
|
"Run `npx claude-smart install --host opencode`.",
|
|
1153
1302
|
)
|
|
1154
1303
|
try:
|
|
1155
|
-
|
|
1304
|
+
package_root = _install_opencode_plugin_package()
|
|
1305
|
+
except OSError as exc:
|
|
1306
|
+
return False, str(exc)
|
|
1307
|
+
plugin_root = package_root / "plugin"
|
|
1308
|
+
try:
|
|
1309
|
+
_force_plugin_root(plugin_root)
|
|
1156
1310
|
except OSError as exc:
|
|
1157
1311
|
return False, str(exc)
|
|
1158
1312
|
bash = _resolve_bash()
|
|
1159
1313
|
if not bash:
|
|
1160
1314
|
return False, "bash is required to bootstrap claude-smart dependencies"
|
|
1161
|
-
|
|
1315
|
+
scripts_dir = plugin_root / "scripts"
|
|
1316
|
+
result = subprocess.run([bash, str(scripts_dir / "smart-install.sh")], cwd=plugin_root)
|
|
1162
1317
|
if result.returncode != 0:
|
|
1163
|
-
return False, f"smart-install.sh failed in {
|
|
1318
|
+
return False, f"smart-install.sh failed in {plugin_root}"
|
|
1164
1319
|
if _INSTALL_FAILURE_MARKER.is_file():
|
|
1165
1320
|
first_line = _INSTALL_FAILURE_MARKER.read_text().splitlines()
|
|
1166
1321
|
reason = (first_line[0].strip() if first_line else "") or "unknown error"
|
|
1167
1322
|
return False, reason
|
|
1168
1323
|
if read_only:
|
|
1169
|
-
_prune_publish_hooks_for_read_only(
|
|
1170
|
-
return True, str(
|
|
1324
|
+
_prune_publish_hooks_for_read_only(plugin_root)
|
|
1325
|
+
return True, str(package_root)
|
|
1171
1326
|
|
|
1172
1327
|
|
|
1173
1328
|
def cmd_install_opencode(args: argparse.Namespace) -> int:
|
|
@@ -1181,24 +1336,31 @@ def cmd_install_opencode(args: argparse.Namespace) -> int:
|
|
|
1181
1336
|
if not _has_extraction_provider():
|
|
1182
1337
|
sys.stderr.write(_extraction_provider_error())
|
|
1183
1338
|
return 1
|
|
1184
|
-
bootstrapped,
|
|
1339
|
+
bootstrapped, package_root_text = _bootstrap_opencode_install(read_only)
|
|
1185
1340
|
if not bootstrapped:
|
|
1186
1341
|
sys.stderr.write(
|
|
1187
|
-
f"error: claude-smart OpenCode setup failed during dependency bootstrap: {
|
|
1342
|
+
f"error: claude-smart OpenCode setup failed during dependency bootstrap: {package_root_text}\n"
|
|
1188
1343
|
)
|
|
1189
1344
|
return 1
|
|
1345
|
+
package_root = Path(package_root_text)
|
|
1346
|
+
plugin_root = package_root / "plugin"
|
|
1347
|
+
plugin_spec = _opencode_local_plugin_spec(package_root)
|
|
1190
1348
|
config_path = _opencode_config_path(
|
|
1191
1349
|
global_config=bool(getattr(args, "global_config", False))
|
|
1192
1350
|
)
|
|
1193
1351
|
try:
|
|
1194
|
-
changed, config_path = _patch_opencode_plugin_config(
|
|
1352
|
+
changed, config_path = _patch_opencode_plugin_config(
|
|
1353
|
+
config_path,
|
|
1354
|
+
install=True,
|
|
1355
|
+
plugin_spec=plugin_spec,
|
|
1356
|
+
)
|
|
1195
1357
|
except ValueError as exc:
|
|
1196
1358
|
sys.stderr.write(f"error: {exc}\n")
|
|
1197
1359
|
return 1
|
|
1198
1360
|
action = "Updated" if changed else "OpenCode config already includes"
|
|
1199
1361
|
sys.stdout.write(
|
|
1200
|
-
f"{action} `{
|
|
1201
|
-
f"Prepared claude-smart runtime at {
|
|
1362
|
+
f"{action} `{plugin_spec}` in {config_path}.\n"
|
|
1363
|
+
f"Prepared claude-smart runtime at {plugin_root}.\n"
|
|
1202
1364
|
"Restart OpenCode in your project so it loads the plugin.\n"
|
|
1203
1365
|
)
|
|
1204
1366
|
return 0
|
|
@@ -1517,6 +1679,13 @@ def cmd_uninstall_codex(_args: argparse.Namespace) -> int:
|
|
|
1517
1679
|
def cmd_uninstall_opencode(args: argparse.Namespace) -> int:
|
|
1518
1680
|
_run_service(_DASHBOARD_SCRIPT, "stop")
|
|
1519
1681
|
_run_service(_BACKEND_SCRIPT, "stop")
|
|
1682
|
+
local_scripts = _OPENCODE_LOCAL_PACKAGE_DIR / "plugin" / "scripts"
|
|
1683
|
+
for script in (
|
|
1684
|
+
local_scripts / "dashboard-service.sh",
|
|
1685
|
+
local_scripts / "backend-service.sh",
|
|
1686
|
+
):
|
|
1687
|
+
if script.exists():
|
|
1688
|
+
_run_service(script, "stop")
|
|
1520
1689
|
config_path = _opencode_config_path(
|
|
1521
1690
|
global_config=bool(getattr(args, "global_config", False))
|
|
1522
1691
|
)
|
|
@@ -1525,10 +1694,15 @@ def cmd_uninstall_opencode(args: argparse.Namespace) -> int:
|
|
|
1525
1694
|
except ValueError as exc:
|
|
1526
1695
|
sys.stderr.write(f"error: {exc}\n")
|
|
1527
1696
|
return 1
|
|
1697
|
+
shutil.rmtree(_OPENCODE_LOCAL_PACKAGE_DIR, ignore_errors=True)
|
|
1698
|
+
try:
|
|
1699
|
+
_OPENCODE_LOCAL_PACKAGE_DIR.parent.rmdir()
|
|
1700
|
+
except OSError:
|
|
1701
|
+
pass
|
|
1528
1702
|
if changed:
|
|
1529
|
-
sys.stdout.write(f"Removed
|
|
1703
|
+
sys.stdout.write(f"Removed claude-smart OpenCode plugin entries from {config_path}.\n")
|
|
1530
1704
|
else:
|
|
1531
|
-
sys.stdout.write(
|
|
1705
|
+
sys.stdout.write("OpenCode config did not include claude-smart.\n")
|
|
1532
1706
|
sys.stdout.write(
|
|
1533
1707
|
"Restart OpenCode to apply. "
|
|
1534
1708
|
f"{_LOCAL_DATA_NOTICE}"
|
|
@@ -1572,12 +1746,26 @@ def cmd_show(args: argparse.Namespace) -> int:
|
|
|
1572
1746
|
f"First error: {adapter.read_errors[0]}\n"
|
|
1573
1747
|
)
|
|
1574
1748
|
return 1
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
)
|
|
1749
|
+
body = md or f"_No skills or preferences yet for project `{project_id}`._\n"
|
|
1750
|
+
sys.stdout.write(body + _reflexio_show_footer())
|
|
1578
1751
|
return 0
|
|
1579
1752
|
|
|
1580
1753
|
|
|
1754
|
+
def _reflexio_show_footer() -> str:
|
|
1755
|
+
"""Return the markdown attribution footer appended to ``show`` output.
|
|
1756
|
+
|
|
1757
|
+
Reinforces that claude-smart is powered by the open-source reflexio engine
|
|
1758
|
+
and nudges users to star the repo.
|
|
1759
|
+
|
|
1760
|
+
Returns:
|
|
1761
|
+
str: A leading-separator markdown footer ending in a newline.
|
|
1762
|
+
"""
|
|
1763
|
+
return (
|
|
1764
|
+
f"\n---\n⭐ claude-smart is powered by [reflexio]({cs_cite.REFLEXIO_REPO_URL})"
|
|
1765
|
+
f" — if it helps you, [star it on GitHub]({cs_cite.REFLEXIO_REPO_URL}).\n"
|
|
1766
|
+
)
|
|
1767
|
+
|
|
1768
|
+
|
|
1581
1769
|
def cmd_learn(args: argparse.Namespace) -> int:
|
|
1582
1770
|
"""Force reflexio extraction over the active session's interactions.
|
|
1583
1771
|
|
|
@@ -269,7 +269,7 @@ def _compact_citation_instruction(marker_parts: list[str] | None = None) -> str:
|
|
|
269
269
|
gate = cs_cite.WHEN_TO_CITE_COMPACT
|
|
270
270
|
link_style = os.environ.get(_CITATION_LINK_STYLE_ENV, "markdown")
|
|
271
271
|
if link_style == "osc8" and marker_parts:
|
|
272
|
-
marker =
|
|
272
|
+
marker = cs_cite.build_marker(" | ".join(marker_parts), "osc8")
|
|
273
273
|
separator_instruction = (
|
|
274
274
|
" Separate multiple linked memories with the visible ` | ` separator."
|
|
275
275
|
if len(marker_parts) > 1
|
|
@@ -281,12 +281,13 @@ def _compact_citation_instruction(marker_parts: list[str] | None = None) -> str:
|
|
|
281
281
|
)
|
|
282
282
|
if link_style == "osc8":
|
|
283
283
|
return _remoteize_citation_instruction(
|
|
284
|
-
f"{gate} If you do, end with
|
|
285
|
-
"followed by the same linked memory text
|
|
286
|
-
"
|
|
284
|
+
f"{gate} If you do, end with `{cs_cite.MARKER_PREFIX}` "
|
|
285
|
+
"followed by the same linked memory text, then "
|
|
286
|
+
f"`{cs_cite.marker_attribution('osc8')}` linking to the reflexio "
|
|
287
|
+
"repo; keep the links, but do not show the URL."
|
|
287
288
|
)
|
|
288
289
|
if marker_parts:
|
|
289
|
-
marker =
|
|
290
|
+
marker = cs_cite.build_marker(" | ".join(marker_parts), "markdown")
|
|
290
291
|
separator_instruction = (
|
|
291
292
|
" Separate multiple linked memories with the visible ` | ` separator."
|
|
292
293
|
if len(marker_parts) > 1
|
|
@@ -297,9 +298,8 @@ def _compact_citation_instruction(marker_parts: list[str] | None = None) -> str:
|
|
|
297
298
|
f"links: `{marker}`.{separator_instruction}"
|
|
298
299
|
)
|
|
299
300
|
return _remoteize_citation_instruction(
|
|
300
|
-
f"{gate} If you do, end with one final marker like
|
|
301
|
-
"
|
|
302
|
-
"s1-123)` using the shown rule URL."
|
|
301
|
+
f"{gate} If you do, end with one final marker like "
|
|
302
|
+
f"`{cs_cite.MARKDOWN_EXAMPLE_ONE}` using the shown rule URL."
|
|
303
303
|
)
|
|
304
304
|
|
|
305
305
|
|
|
@@ -337,7 +337,7 @@ def _exact_osc8_marker_instruction(entries: list[dict[str, Any]]) -> str:
|
|
|
337
337
|
if not marker_parts:
|
|
338
338
|
return ""
|
|
339
339
|
|
|
340
|
-
marker =
|
|
340
|
+
marker = cs_cite.build_marker(" | ".join(marker_parts), "osc8")
|
|
341
341
|
return (
|
|
342
342
|
"If any listed memory above was used, copy this exact final marker, "
|
|
343
343
|
f"preserving its hidden OSC 8 terminal links: `{marker}`. "
|