cluxion-agentplugin-preprocessing 0.3.0__cp311-abi3-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. cluxion_agentplugin_adapters/claude/.claude-plugin/plugin.json +8 -0
  2. cluxion_agentplugin_adapters/claude/skills/preprocess/SKILL.md +33 -0
  3. cluxion_agentplugin_adapters/codex/config-snippet.toml +5 -0
  4. cluxion_agentplugin_docs/cluxion-Docs/README.md +22 -0
  5. cluxion_agentplugin_docs/cluxion-Docs/architecture.md +36 -0
  6. cluxion_agentplugin_docs/cluxion-Docs/harness-logic.md +51 -0
  7. cluxion_agentplugin_docs/cluxion-Docs/honesty-preprocessing.md +40 -0
  8. cluxion_agentplugin_docs/cluxion-Docs/install-and-operations.md +36 -0
  9. cluxion_agentplugin_docs/cluxion-Docs/security.md +27 -0
  10. cluxion_agentplugin_docs/github-profile/README.md +67 -0
  11. cluxion_agentplugin_preprocessing/__init__.py +7 -0
  12. cluxion_agentplugin_preprocessing/cli.py +124 -0
  13. cluxion_agentplugin_preprocessing/hermes_config.py +169 -0
  14. cluxion_agentplugin_preprocessing/plugin.py +230 -0
  15. cluxion_agentplugin_preprocessing/plugin.yaml +13 -0
  16. cluxion_agentplugin_preprocessing/runner.py +346 -0
  17. cluxion_agentplugin_preprocessing/schemas.py +374 -0
  18. cluxion_agentplugin_preprocessing-0.3.0.dist-info/METADATA +123 -0
  19. cluxion_agentplugin_preprocessing-0.3.0.dist-info/RECORD +56 -0
  20. cluxion_agentplugin_preprocessing-0.3.0.dist-info/WHEEL +4 -0
  21. cluxion_agentplugin_preprocessing-0.3.0.dist-info/entry_points.txt +7 -0
  22. cluxion_agentplugin_preprocessing-0.3.0.dist-info/licenses/LICENSE +197 -0
  23. cluxion_queue_native/__init__.py +5 -0
  24. cluxion_queue_native/cluxion_queue_native.pyd +0 -0
  25. cluxion_runtime/__init__.py +16 -0
  26. cluxion_runtime/__main__.py +5 -0
  27. cluxion_runtime/adapters/__init__.py +25 -0
  28. cluxion_runtime/adapters/contract.py +82 -0
  29. cluxion_runtime/adapters/grok_build.py +35 -0
  30. cluxion_runtime/adapters/hermes.py +161 -0
  31. cluxion_runtime/adapters/spec.py +39 -0
  32. cluxion_runtime/bootstrap.py +270 -0
  33. cluxion_runtime/cli.py +381 -0
  34. cluxion_runtime/core/__init__.py +36 -0
  35. cluxion_runtime/core/clarification.py +245 -0
  36. cluxion_runtime/core/context_compress.py +254 -0
  37. cluxion_runtime/core/dispatch_store.py +270 -0
  38. cluxion_runtime/core/harness.py +320 -0
  39. cluxion_runtime/core/intent.py +59 -0
  40. cluxion_runtime/core/ledger.py +191 -0
  41. cluxion_runtime/core/ledger_codec.py +38 -0
  42. cluxion_runtime/core/plan_codec.py +121 -0
  43. cluxion_runtime/core/preprocess.py +499 -0
  44. cluxion_runtime/core/types.py +220 -0
  45. cluxion_runtime/core/work_queue.py +73 -0
  46. cluxion_runtime/guard_daemon_host.py +28 -0
  47. cluxion_runtime/models/__init__.py +15 -0
  48. cluxion_runtime/models/supervisor.py +156 -0
  49. cluxion_runtime/models/vllm_mlx.py +87 -0
  50. cluxion_runtime/resources/__init__.py +7 -0
  51. cluxion_runtime/resources/guard_bridge.py +489 -0
  52. cluxion_runtime/resources/py_queue.py +298 -0
  53. cluxion_runtime/resources/queue_bridge.py +199 -0
  54. cluxion_runtime/resources/rust_bridge.py +116 -0
  55. cluxion_runtime/web/__init__.py +19 -0
  56. cluxion_runtime/web/browser_bridge.py +413 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "cluxion-agentplugin-preprocessing",
3
+ "version": "0.2.0",
4
+ "description": "Cluxion preprocessing: honesty, clarification, Rust work queue, resource admission.",
5
+ "author": "cluxion",
6
+ "commands": "./commands",
7
+ "skills": "./skills"
8
+ }
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: cluxion-preprocess
3
+ description: Run Cluxion preprocessing before agent work. You (the connected AI) call cluxion tools/CLI. Use for task planning, long work queueing, or unclear user intent. Enforces honesty and clarification before queueing.
4
+ ---
5
+
6
+ # Cluxion Preprocessing — 연결된 AI 지시문
7
+
8
+ 작업 전에 **당신(연결된 AI)** 이 plan·큐 도구를 호출합니다. 플러그인은 JSON 계약만 반환합니다.
9
+
10
+ ## Plan 호출
11
+
12
+ ```bash
13
+ cluxion-runtime plan --surface claude --json-stdin
14
+ ```
15
+
16
+ stdin: `{"prompt": "<user request>", "cwd": "<workspace>"}`
17
+
18
+ Hermes에서는 `cluxion_plan` 도구를 동일 목적으로 사용합니다.
19
+
20
+ ## 규칙
21
+
22
+ 1. `clarification.required`이 true이면 사용자에게 질문하고 작업을 시작하지 말 것.
23
+ 2. context가 부족하면 모른다고 말할 것 — 사실을 지어내지 말 것.
24
+ 3. `queued` 모드: `cluxion_queue_next` → segment 처리 → `cluxion_queue_record` → `cluxion_queue_brief`.
25
+ 4. 세션 맥락 보존이 필요하면 brief를 ForgetForge에 넘길 것: `forgetforge import-brief --source preprocessing --brief "<cluxion_queue_brief output>"` (또는 Hermes `forgetforge_import_brief`).
26
+ 5. `answer_policy.required_checks`를 응답 전에 따를 것.
27
+ 6. 플러그인이 대신 completion을 생성하지 않음 — **당신**이 계약에 맞게 응답할 것.
28
+
29
+ ## 설치 확인
30
+
31
+ ```bash
32
+ cluxion-preprocess check
33
+ ```
@@ -0,0 +1,5 @@
1
+ # Add to ~/.codex/config.toml
2
+ [plugins.cluxion_preprocess]
3
+ enabled = true
4
+ command = ["cluxion-runtime", "plan", "--json-stdin", "--surface", "codex"]
5
+ description = "Cluxion preprocessing: honesty, clarification, Rust queue"
@@ -0,0 +1,22 @@
1
+ # Legacy Docs (cluxion-Docs)
2
+
3
+ > **공개 문서 기준: [`../Docs/`](../Docs/)**
4
+ > 이 폴더는 이전 `hermes-cluxion` 패키징 시기의 **레거시 참고**입니다.
5
+
6
+ ## 현재 방향 (요약)
7
+
8
+ - **연결된 AI**가 `cluxion_plan`·큐 도구를 호출하고 JSON 계약을 따릅니다.
9
+ - **모델·OAuth·provider**는 host agent가 소유합니다. 플러그인은 plan·게이트만 반환합니다.
10
+ - 플러그인 내부에서 **별도 LLM을 호출하지 않습니다.**
11
+
12
+ 사용자·에이전트 연동 설명은 **`Docs/README.md`** 를 따르세요.
13
+
14
+ ## 레거시 목차 (기술 참고)
15
+
16
+ | 문서 | 내용 |
17
+ |------|------|
18
+ | [architecture.md](./architecture.md) | 패키지 경계 (레거시 명칭 포함) |
19
+ | [harness-logic.md](./harness-logic.md) | intent·전처리·큐·resource |
20
+ | [honesty-preprocessing.md](./honesty-preprocessing.md) | answer_policy 계약 |
21
+ | [install-and-operations.md](./install-and-operations.md) | 설치 요약 → `Docs/installation.md` |
22
+ | [security.md](./security.md) | 공개 배포 보안 원칙 |
@@ -0,0 +1,36 @@
1
+ # Architecture (legacy reference)
2
+
3
+ > 최신 문서: [`../Docs/architecture.md`](../Docs/architecture.md)
4
+
5
+ ## 패키지 구성
6
+
7
+ | 패키지 | 역할 |
8
+ |--------|------|
9
+ | `cluxion_agentplugin_preprocessing` | Hermes plugin, CLI, tool schema |
10
+ | `cluxion_runtime` | `cluxion-runtime plan`, harness 엔진 |
11
+ | `rust/cluxion_queue` | SQLite 작업큐 + dispatch (선택) |
12
+
13
+ ## Host vs Cluxion
14
+
15
+ **Host agent (연결된 AI)**
16
+
17
+ - OAuth, provider, **모델 선택**
18
+ - tool 권한, completion, 최종 응답
19
+ - queued segment 처리·synthesis
20
+
21
+ **Cluxion preprocessing**
22
+
23
+ - `WorkItem` 정규화, intent 분류
24
+ - `answer_policy` · `host_execution` 계약
25
+ - 명확화·segment 큐·resource admission
26
+ - **추가 LLM 호출 없음**
27
+
28
+ ## 등록 도구 (Hermes `cluxion` toolset)
29
+
30
+ | Tool | 용도 |
31
+ |------|------|
32
+ | `cluxion_plan` | 전처리·방향·큐·리소스 plan |
33
+ | `cluxion_clarify` | 명확화 질문 |
34
+ | `cluxion_queue_next` / `record` / `brief` | segment 큐 |
35
+
36
+ 모델·provider 변경은 host agent 책임입니다. 전처리 플러그인은 실행 **전** 계약만 제공합니다.
@@ -0,0 +1,51 @@
1
+ # Harness Logic (legacy reference)
2
+
3
+ > 최신 문서: [`../Docs/design.md`](../Docs/design.md), [`../Docs/tools.md`](../Docs/tools.md)
4
+
5
+ ## 1. Adapter Payload
6
+
7
+ ```bash
8
+ cluxion-runtime plan --json-stdin --surface <surface>
9
+ ```
10
+
11
+ 필수: `prompt`
12
+ 선택: `work_id`, `priority`, `cwd`, `metadata`, `expected_ram_mb`, `context_tokens`
13
+
14
+ ## 2. Intent 분류
15
+
16
+ `classify_intent()` — **모델 호출 없음**, 결정론적.
17
+
18
+ - `category`, `operation`, `direction`, `confidence`, `signals`
19
+ - 방향은 host harness (`hermes_harness`, `claude_harness`, …) 또는 `host_managed`
20
+
21
+ ## 3. 전처리 모드
22
+
23
+ | Mode | 용도 |
24
+ |------|------|
25
+ | `simple_answer` | 짧은 일반 질문 |
26
+ | `verification_answer` | 사실 확인 필요 짧은 질문 |
27
+ | `standard` | 코드·테스트·보안·문서 등 실질 작업 |
28
+ | `queued` | 긴 입력 segment 분할 |
29
+ | `needs_clarification` | 사용자 방향 확정 전 |
30
+
31
+ 모든 모드에 `answer_policy` 포함.
32
+
33
+ ## 4. 작업큐
34
+
35
+ `AgentWorkQueue` — priority + FIFO. Rust `cluxion-queue` 또는 Python fallback.
36
+
37
+ ## 5. Host Execution
38
+
39
+ `host_execution` 계약을 **연결된 AI**가 읽고 실행:
40
+
41
+ - `current_turn_direct_answer`
42
+ - `current_turn_verify_then_answer`
43
+ - `single_host_task`
44
+ - `durable_segment_queue` + `cluxion_queue_*`
45
+ - `ask_user_before_queue`
46
+
47
+ ## 6. Resource Admission
48
+
49
+ `collect_resource_snapshot()` + `capacity_decision()` — fail-closed RAM/CPU 게이트.
50
+
51
+ Queued segment content는 out-of-band store에 저장됩니다.
@@ -0,0 +1,40 @@
1
+ # Honesty Preprocessing (legacy reference)
2
+
3
+ > 최신 문서: [`../Docs/design.md`](../Docs/design.md)
4
+
5
+ 플러그인은 모델을 바꾸지 않습니다. **연결된 AI**가 따라야 하는 `answer_policy` 계약을 만듭니다.
6
+
7
+ ## Goals
8
+
9
+ - 모르면 모른다고 말하게 함
10
+ - 확인하지 않은 실행·파일·환경 상태를 사실처럼 말하지 않게 함
11
+ - 확인한 사실 / 추론 / unknown 분리
12
+ - 짧은 질문에 heavy preprocessing 비용 최소화
13
+
14
+ ## Modes
15
+
16
+ | Mode | 동작 |
17
+ |------|------|
18
+ | `simple_answer` | segment·resource snapshot 없음 |
19
+ | `verification_answer` | `verification_required` + `required_checks` |
20
+ | `standard` | segment·resource admission·evidence 기반 응답 |
21
+ | `queued` | checksum·순서 보존 합성 |
22
+
23
+ ## Required Checks (대표)
24
+
25
+ - `verify_current_or_recent_fact`
26
+ - `cite_external_source_or_document`
27
+ - `inspect_runtime_state_before_claiming`
28
+ - `run_requested_check_or_state_not_run`
29
+ - `tie_claims_to_file_diff_or_command_output`
30
+ - `tie_security_claims_to_evidence`
31
+ - `preserve_segment_checksums_in_synthesis`
32
+
33
+ ## Hard Boundaries
34
+
35
+ - 기억·추정만으로 현재 상태 단정 금지
36
+ - 실행하지 않은 테스트 통과 주장 금지
37
+ - 존재하지 않는 파일·URL·버전 날조 금지
38
+ - ambiguous prompt는 확정 표현 자제
39
+
40
+ 검증·실행·응답은 **연결된 AI** 책임입니다.
@@ -0,0 +1,36 @@
1
+ # Install And Operations (legacy reference)
2
+
3
+ > 최신 문서: [`../Docs/installation.md`](../Docs/installation.md)
4
+
5
+ ## 설치
6
+
7
+ ```bash
8
+ pip install cluxion-agentplugin-preprocessing
9
+ cluxion-preprocess check
10
+ cluxion-preprocess enable # Hermes
11
+ ```
12
+
13
+ ## 연결된 AI 연동
14
+
15
+ - Hermes: `cluxion_*` 도구 (plugin enable 후)
16
+ - Claude: `adapters/claude/skills/preprocess/SKILL.md`
17
+ - Codex: `adapters/codex/config-snippet.toml`
18
+ - 공통: `cluxion-runtime plan --surface <hermes|claude|codex|grok_build>`
19
+
20
+ ## Rust 큐 (선택)
21
+
22
+ ```bash
23
+ cargo build --release --manifest-path rust/cluxion_queue/Cargo.toml
24
+ export CLUXION_QUEUE_BIN=/path/to/cluxion-queue
25
+ ```
26
+
27
+ ## 배포 전 점검
28
+
29
+ ```bash
30
+ uv run ruff check .
31
+ uv run pytest
32
+ cluxion-preprocess check
33
+ cluxion-runtime plan --surface hermes --prompt "smoke"
34
+ ```
35
+
36
+ 모델 endpoint·provider 설정은 **host agent 문서**를 참고하세요. 전처리 플러그인은 모델을 소유하지 않습니다.
@@ -0,0 +1,27 @@
1
+ # Security
2
+
3
+ > 공개 배포용 보안 원칙. 연동 설명은 [`../Docs/`](../Docs/) 참고.
4
+
5
+ ## 원칙
6
+
7
+ - API 키·OAuth credential·secret 파일을 저장하지 않습니다.
8
+ - subprocess는 배열 인자만 사용 (`shell=True` 금지).
9
+ - Hermes config 변경 시 timestamp backup 후 atomic replace.
10
+ - 큐·dispatch 데이터는 **사용자 디스크 경로**에만 저장, 원격 전송 없음.
11
+ - host agent venv를 오염시키지 않도록 runtime 의존성은 격리 경로 사용 (코드 내부 정책).
12
+
13
+ ## Host 경계
14
+
15
+ - 모델·provider·인증은 **연결된 AI / host agent** 소유.
16
+ - Cluxion은 plan·게이트 JSON만 반환.
17
+
18
+ ## 배포 전 점검
19
+
20
+ ```bash
21
+ uv run ruff check .
22
+ uv run pytest
23
+ uv run python -m build
24
+ uv run twine check dist/*
25
+ cluxion-preprocess check
26
+ cluxion-runtime plan --surface hermes --prompt "smoke"
27
+ ```
@@ -0,0 +1,67 @@
1
+ # hermes-cluxion
2
+
3
+ `hermes-cluxion` is a lightweight Cluxion harness plugin for Hermes Agent.
4
+
5
+ It keeps Hermes in control of OAuth, provider auth, active model selection, tools, permissions, and final AI completion calls. Cluxion adds deterministic work planning around Hermes: preprocessing, intent routing, honesty contracts, queued segment dispatch, resource admission, and optional local `vllm-mlx` endpoint setup.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ python -m pip install "hermes-cluxion==0.1.9"
11
+ hermes-cluxion enable
12
+ hermes-cluxion check
13
+ ```
14
+
15
+ Confirm the plugin is enabled:
16
+
17
+ ```bash
18
+ hermes tools list
19
+ ```
20
+
21
+ Expected:
22
+
23
+ ```text
24
+ Plugin toolsets (cli):
25
+ ✓ enabled cluxion 🔌 Cluxion
26
+ ```
27
+
28
+ ## Hermes Tools
29
+
30
+ - `cluxion_plan`
31
+ - `cluxion_bootstrap`
32
+ - `cluxion_serve_local`
33
+ - `cluxion_hermes_config`
34
+ - `cluxion_queue_next`
35
+ - `cluxion_queue_record`
36
+ - `cluxion_queue_brief`
37
+
38
+ ## Execution Model
39
+
40
+ - Cloud AI calls stay inside Hermes.
41
+ - Local model calls also stay inside Hermes after switching to a custom local provider.
42
+ - Cluxion returns `host_execution`, `answer_policy`, queue metadata, and resource decisions.
43
+ - Short simple prompts do not pay queue or resource snapshot cost.
44
+ - Short verification prompts get required checks without heavy preprocessing.
45
+ - Long work uses durable queued segment dispatch and final briefing synthesis.
46
+
47
+ ## Local MLX Flow
48
+
49
+ ```bash
50
+ hermes-cluxion bootstrap --upgrade
51
+ hermes-cluxion hermes-config \
52
+ --model mlx-community/Qwen3.6-35B-A3B-OptiQ-4bit \
53
+ --port 23003 \
54
+ --context-length 131072
55
+ cluxion-runtime serve-local \
56
+ --model mlx-community/Qwen3.6-35B-A3B-OptiQ-4bit \
57
+ --port 23003 \
58
+ --upgrade-runtime
59
+ ```
60
+
61
+ Then switch Hermes:
62
+
63
+ ```text
64
+ /model custom:cluxion-local:mlx-community/Qwen3.6-35B-A3B-OptiQ-4bit
65
+ ```
66
+
67
+ Full guide: [README](../../README.md)
@@ -0,0 +1,7 @@
1
+ """Universal Cluxion preprocessing agent plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.3.0"
6
+
7
+ __all__ = ["__version__"]
@@ -0,0 +1,124 @@
1
+ """CLI helpers for the universal Cluxion preprocessing plugin."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from typing import TYPE_CHECKING
9
+
10
+ from cluxion_agentplugin_preprocessing import __version__, hermes_config, runner
11
+ from cluxion_runtime.bootstrap import ensure_local_runtime
12
+
13
+ if TYPE_CHECKING:
14
+ from collections.abc import Sequence
15
+
16
+
17
+ def main(argv: Sequence[str] | None = None) -> int:
18
+ """Run cluxion-preprocess CLI."""
19
+ parser = _parser()
20
+ args = parser.parse_args(argv)
21
+ if args.command == "check":
22
+ return _check()
23
+ if args.command == "bootstrap":
24
+ return _bootstrap(args)
25
+ if args.command == "status":
26
+ return _status(args)
27
+ if args.command == "enable":
28
+ return _enable(args)
29
+ if args.command == "disable":
30
+ return _disable(args)
31
+ if args.command == "hermes-config":
32
+ return _hermes_config(args)
33
+ parser.print_help(sys.stderr)
34
+ return 2
35
+
36
+
37
+ def _parser() -> argparse.ArgumentParser:
38
+ parser = argparse.ArgumentParser(prog="cluxion-preprocess")
39
+ parser.add_argument("--version", action="version", version=f"cluxion-agentplugin-preprocessing {__version__}")
40
+ subparsers = parser.add_subparsers(dest="command")
41
+ subparsers.add_parser("check", help="Check whether cluxion-runtime is visible")
42
+ bootstrap = subparsers.add_parser("bootstrap", help="Install or upgrade local runtime dependencies")
43
+ bootstrap.add_argument("--upgrade", action="store_true")
44
+ bootstrap.add_argument("--dry-run", action="store_true")
45
+ bootstrap.add_argument("--package", action="append", default=None)
46
+ status = subparsers.add_parser("status", help="Show whether Hermes config enables this plugin")
47
+ status.add_argument("--home", default=None)
48
+ enable = subparsers.add_parser("enable", help="Enable the pip-installed plugin in Hermes config")
49
+ enable.add_argument("--home", default=None)
50
+ enable.add_argument("--dry-run", action="store_true")
51
+ disable = subparsers.add_parser("disable", help="Disable the pip-installed plugin in Hermes config")
52
+ disable.add_argument("--home", default=None)
53
+ disable.add_argument("--dry-run", action="store_true")
54
+ config = subparsers.add_parser("hermes-config", help="Render Hermes local endpoint config through cluxion-runtime")
55
+ config.add_argument("--model", required=True)
56
+ config.add_argument("--host", default="127.0.0.1")
57
+ config.add_argument("--port", type=int, default=23003)
58
+ config.add_argument("--context-length", type=int, default=131_072)
59
+ config.add_argument("--provider-key", default="cluxion-local")
60
+ config.add_argument("--display-name", default="Cluxion Local")
61
+ return parser
62
+
63
+
64
+ def _check() -> int:
65
+ from cluxion_runtime.resources.queue_bridge import queue_available
66
+
67
+ payload = {
68
+ "plugin": "cluxion-agentplugin-preprocessing",
69
+ "version": __version__,
70
+ "cluxion_runtime_available": runner.runtime_available(),
71
+ "rust_queue_available": queue_available(),
72
+ }
73
+ print(json.dumps(payload, ensure_ascii=False, sort_keys=True))
74
+ return 0 if payload["cluxion_runtime_available"] else 1
75
+
76
+
77
+ def _bootstrap(args: argparse.Namespace) -> int:
78
+ result = ensure_local_runtime(
79
+ packages=tuple(args.package) if args.package else ("vllm-mlx",),
80
+ upgrade=bool(args.upgrade),
81
+ dry_run=bool(args.dry_run),
82
+ )
83
+ print(json.dumps(result.to_dict(), ensure_ascii=False, sort_keys=True))
84
+ return 0 if result.ok else 1
85
+
86
+
87
+ def _status(args: argparse.Namespace) -> int:
88
+ result = hermes_config.plugin_status(args.home)
89
+ print(json.dumps({"ok": True, **result.to_dict()}, ensure_ascii=False, sort_keys=True))
90
+ return 0 if result.enabled else 1
91
+
92
+
93
+ def _enable(args: argparse.Namespace) -> int:
94
+ result = hermes_config.enable_plugin(args.home, dry_run=bool(args.dry_run))
95
+ print(json.dumps({"ok": True, **result.to_dict()}, ensure_ascii=False, sort_keys=True))
96
+ return 0
97
+
98
+
99
+ def _disable(args: argparse.Namespace) -> int:
100
+ result = hermes_config.disable_plugin(args.home, dry_run=bool(args.dry_run))
101
+ print(json.dumps({"ok": True, **result.to_dict()}, ensure_ascii=False, sort_keys=True))
102
+ return 0
103
+
104
+
105
+ def _hermes_config(args: argparse.Namespace) -> int:
106
+ result = runner.hermes_config(
107
+ {
108
+ "model": str(args.model),
109
+ "host": str(args.host),
110
+ "port": int(args.port),
111
+ "context_length": int(args.context_length),
112
+ "provider_key": str(args.provider_key),
113
+ "display_name": str(args.display_name),
114
+ }
115
+ )
116
+ print(result.to_json())
117
+ return 0 if result.ok else 1
118
+
119
+
120
+ if __name__ == "__main__":
121
+ raise SystemExit(main())
122
+
123
+
124
+ __all__ = ["main"]
@@ -0,0 +1,169 @@
1
+ """Hermes config helpers for pip-installed entry point plugins."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import shutil
7
+ import tempfile
8
+ from dataclasses import dataclass
9
+ from datetime import UTC, datetime
10
+ from pathlib import Path
11
+ from typing import Any
12
+
13
+ import yaml
14
+
15
+ PLUGIN_NAME = "cluxion-agentplugin-preprocessing"
16
+ LEGACY_PLUGIN_NAME = "hermes-cluxion"
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ConfigChange:
21
+ """Result of a Hermes config update."""
22
+
23
+ config_path: Path
24
+ changed: bool
25
+ enabled: bool
26
+ backup_path: Path | None = None
27
+
28
+ def to_dict(self) -> dict[str, object]:
29
+ return {
30
+ "config_path": str(self.config_path),
31
+ "changed": self.changed,
32
+ "enabled": self.enabled,
33
+ "backup_path": str(self.backup_path) if self.backup_path else "",
34
+ }
35
+
36
+
37
+ def plugin_status(home: str | os.PathLike[str] | None = None) -> ConfigChange:
38
+ """Return whether Hermes config enables this plugin."""
39
+ path = config_path(home)
40
+ config = _read_config(path)
41
+ return ConfigChange(path, False, _is_enabled(config))
42
+
43
+
44
+ def enable_plugin(home: str | os.PathLike[str] | None = None, *, dry_run: bool = False) -> ConfigChange:
45
+ """Add hermes-cluxion to plugins.enabled and remove it from plugins.disabled."""
46
+ path = config_path(home)
47
+ config = _read_config(path)
48
+ changed = _set_enabled(config, enabled=True)
49
+ if dry_run or not changed:
50
+ return ConfigChange(path, changed, True)
51
+ backup = _write_config(path, config)
52
+ return ConfigChange(path, changed, True, backup)
53
+
54
+
55
+ def disable_plugin(home: str | os.PathLike[str] | None = None, *, dry_run: bool = False) -> ConfigChange:
56
+ """Move hermes-cluxion from plugins.enabled to plugins.disabled."""
57
+ path = config_path(home)
58
+ config = _read_config(path)
59
+ changed = _set_enabled(config, enabled=False)
60
+ if dry_run or not changed:
61
+ return ConfigChange(path, changed, False)
62
+ backup = _write_config(path, config)
63
+ return ConfigChange(path, changed, False, backup)
64
+
65
+
66
+ def config_path(home: str | os.PathLike[str] | None = None) -> Path:
67
+ """Resolve the Hermes config path without importing Hermes internals."""
68
+ if home is not None:
69
+ hermes_home = Path(home).expanduser()
70
+ else:
71
+ hermes_home = (
72
+ Path(os.environ.get("HERMES_HOME", "")).expanduser()
73
+ if os.environ.get("HERMES_HOME")
74
+ else Path.home() / ".hermes"
75
+ )
76
+ return hermes_home / "config.yaml"
77
+
78
+
79
+ def _read_config(path: Path) -> dict[str, Any]:
80
+ if not path.exists():
81
+ return {}
82
+ raw = path.read_text(encoding="utf-8")
83
+ if not raw.strip():
84
+ return {}
85
+ loaded = yaml.safe_load(raw)
86
+ if loaded is None:
87
+ return {}
88
+ if not isinstance(loaded, dict):
89
+ raise ValueError(f"{path} must contain a YAML mapping")
90
+ return dict(loaded)
91
+
92
+
93
+ def _is_enabled(config: dict[str, Any]) -> bool:
94
+ plugins = config.get("plugins")
95
+ if not isinstance(plugins, dict):
96
+ return False
97
+ enabled = plugins.get("enabled")
98
+ disabled = plugins.get("disabled")
99
+ return (
100
+ (_contains(enabled, PLUGIN_NAME) or _contains(enabled, LEGACY_PLUGIN_NAME))
101
+ and not _contains(disabled, PLUGIN_NAME)
102
+ and not _contains(disabled, LEGACY_PLUGIN_NAME)
103
+ )
104
+
105
+
106
+ def _set_enabled(config: dict[str, Any], *, enabled: bool) -> bool:
107
+ plugins = config.setdefault("plugins", {})
108
+ if not isinstance(plugins, dict):
109
+ raise ValueError("plugins must be a YAML mapping")
110
+ enabled_list = _as_list(plugins.get("enabled"))
111
+ disabled_list = _as_list(plugins.get("disabled"))
112
+
113
+ old_enabled = list(enabled_list)
114
+ old_disabled = list(disabled_list)
115
+ if enabled:
116
+ if PLUGIN_NAME not in enabled_list:
117
+ enabled_list.append(PLUGIN_NAME)
118
+ disabled_list = [item for item in disabled_list if item not in {PLUGIN_NAME, LEGACY_PLUGIN_NAME}]
119
+ else:
120
+ enabled_list = [item for item in enabled_list if item not in {PLUGIN_NAME, LEGACY_PLUGIN_NAME}]
121
+ if PLUGIN_NAME not in disabled_list:
122
+ disabled_list.append(PLUGIN_NAME)
123
+
124
+ plugins["enabled"] = enabled_list
125
+ if disabled_list:
126
+ plugins["disabled"] = disabled_list
127
+ else:
128
+ plugins.pop("disabled", None)
129
+ return enabled_list != old_enabled or disabled_list != old_disabled
130
+
131
+
132
+ def _as_list(value: object) -> list[str]:
133
+ if value is None:
134
+ return []
135
+ if not isinstance(value, list):
136
+ raise ValueError("plugins.enabled/plugins.disabled must be YAML lists")
137
+ items: list[str] = []
138
+ for item in value:
139
+ if not isinstance(item, str):
140
+ raise ValueError("plugins.enabled/plugins.disabled entries must be strings")
141
+ items.append(item)
142
+ return items
143
+
144
+
145
+ def _contains(value: object, item: str) -> bool:
146
+ return isinstance(value, list) and item in value
147
+
148
+
149
+ def _write_config(path: Path, config: dict[str, Any]) -> Path | None:
150
+ path.parent.mkdir(parents=True, exist_ok=True)
151
+ backup = _backup_config(path)
152
+ content = yaml.safe_dump(config, allow_unicode=True, sort_keys=False)
153
+ with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle:
154
+ handle.write(content)
155
+ temp_name = handle.name
156
+ Path(temp_name).replace(path)
157
+ return backup
158
+
159
+
160
+ def _backup_config(path: Path) -> Path | None:
161
+ if not path.exists():
162
+ return None
163
+ stamp = datetime.now(UTC).strftime("%Y%m%dT%H%M%SZ")
164
+ backup = path.with_name(f"{path.name}.bak-{stamp}")
165
+ shutil.copy2(path, backup)
166
+ return backup
167
+
168
+
169
+ __all__ = ["ConfigChange", "config_path", "disable_plugin", "enable_plugin", "plugin_status"]