@felipefontoura/paperclip-adapter-hermes-local-plus 0.1.11

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/CHANGELOG.md ADDED
@@ -0,0 +1,61 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.11] - 2026-06-11
9
+
10
+ ### Added
11
+ - Per-agent scratch CWD. Hermes is spawned with `cwd = /paperclip/instances/<inst>/workspaces/<agentId>` instead of the Paperclip server's own `/app` working directory.
12
+ - Honest error logging on the four `try { ... } catch { ... }` blocks the plugin owns. Failures now surface on the Run page with a `[paperclip] WARN ...` line instead of being swallowed silently.
13
+
14
+ ### Changed
15
+ - Default `cwd` no longer inherits from the Paperclip server. The Hermes auto-injection that pulled `/app/AGENTS.md` (the Paperclip contributor guide) into every wake's system prompt is gone.
16
+
17
+ ### Performance
18
+ - **-22.7% input tokens on cold wakes (9 044 → 6 992 measured).** See [`docs/03-cwd-fix-saved-22-percent.md`](./docs/03-cwd-fix-saved-22-percent.md) for the validation report.
19
+
20
+ ## [0.1.10] - 2026-06-11
21
+
22
+ ### Fixed
23
+ - Custom Provider env injection now uses the correct names: `OPENAI_BASE_URL` (not `OPENAI_API_BASE`) plus the `_HERMES_FORCE_<NAME>` escape hatch that bypasses Hermes's provider-env blocklist. End-to-end routing to OpenAI-compatible endpoints (Z.AI, Together AI, Groq, Ollama, vLLM, …) is finally working.
24
+
25
+ ## [0.1.9] - 2026-06-11
26
+
27
+ ### Fixed
28
+ - `${SECRET_NAME}` substitution now resolves from `adapterConfig.env` (the agent-level Environment Variables row in the Paperclip UI) before falling back to `process.env`. Lets one company secret serve dozens of agents.
29
+
30
+ ## [0.1.8] - 2026-06-11
31
+
32
+ ### Added
33
+ - 10 new fields on the Configuration tab: `provider` (dynamic), `customProviderBaseUrl`, `customProviderApiKey` (secret), `customProviderModelOverride`, `customProviderHeaders`, `heartbeatTemplateMode`, `toolsets`, `sessionResume`, `debug`, plus the v0.1.7 `promptTemplate`.
34
+ - `listProviders()` populates the Provider select dynamically from `~/.hermes/config.yaml` (`custom_providers[].name`) + `~/.hermes/auth.json` (`credential_pool`). Always includes `Auto` + `Custom (OpenAI-compatible)`.
35
+ - Custom Provider routing: when `provider = custom`, the plugin passes `--provider openai-api` and injects `OPENAI_BASE_URL` / `OPENAI_API_KEY` env vars from the form fields.
36
+ - `heartbeatTemplateMode` (`auto` / `light` / `full` / `custom`) gives the operator explicit control over which prompt template the plugin selects.
37
+
38
+ ### Changed
39
+ - Default toolsets jump from `terminal,skills` to a powerful set matching what `hermes chat` enables on the host: `terminal,skills,web,browser,file,code_execution,memory,todo,vision,session_search,delegation`. Aluno leigo gets a real Hermes agent by default.
40
+ - Removed the broken `--reasoning-effort` push that the UI's universal `thinkingEffort` field used to generate. Hermes has no such CLI flag — the arg was being silently dropped.
41
+
42
+ ## [0.1.7] - 2026-06-11
43
+
44
+ ### Added
45
+ - `getConfigSchema()` is now exported correctly so the Paperclip server route `GET /api/adapters/<type>/config-schema` returns the plugin's UI form fields. The earlier `agentConfigurationSchema` name was a red herring; nothing read it.
46
+ - `LIGHT_PROMPT_TEMPLATE` for wakes where `AGENTS.md` has substantive content (>200 chars). Reduces a typical focused-task wake from ~18 k to ~5 k input tokens by skipping the heartbeat workflow injection.
47
+ - `promptTemplate` textarea field on the Configuration tab.
48
+
49
+ ## [0.1.6] - 2026-06-11
50
+
51
+ ### Changed
52
+ - `syncSkills()` now fetches `sourcePath` from Paperclip's HTTP API instead of inferring it via string parsing of the runtime snapshot path. Cached for 30 s per company to avoid hammering the server.
53
+
54
+ ## [0.1.5] - 2026-06-11
55
+
56
+ ### Fixed
57
+ - Skill references survive. `syncSkills()` symlinks against the source directory (`/paperclip/instances/.../skills/<bare>`) instead of the runtime snapshot (`__runtime__/<bare>--<hash>`), which strips everything except `SKILL.md` on build. Multi-file skills (Hormozi, Feynman, etc.) now expose `references/`, `assets/`, and friends to Hermes.
58
+
59
+ ## [0.1.0 - 0.1.4]
60
+
61
+ Initial scaffold and early iterations. See git history.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Felipe Fontoura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,206 @@
1
+ # paperclip-adapter-hermes-local-plus
2
+
3
+ **Drop-in replacement for Paperclip's built-in `hermes_local` adapter.**
4
+ Brings the full power of [Hermes Agent](https://hermes-agent.nousresearch.com)
5
+ into the [Paperclip](https://paperclipai.com) UI — for both the non-technical
6
+ operator and the operator who knows their way around a YAML.
7
+
8
+ ```bash
9
+ npm install @felipefontoura/paperclip-adapter-hermes-local-plus
10
+ ```
11
+
12
+ That's it. No fork, no patch, no monkey-patch. The plugin registers under
13
+ the same `type` as the built-in adapter and Paperclip's external-plugin
14
+ loader makes it the active one.
15
+
16
+ ---
17
+
18
+ ## Why this exists
19
+
20
+ Paperclip ships a tiny built-in `hermes_local` adapter that spawns
21
+ `hermes chat -q "<prompt>"` and parses the output. It's a starting
22
+ point. In day-to-day operation, you hit ten things that don't quite work:
23
+
24
+ - Multi-file skills (Hormozi, Feynman, etc.) lose their `references/`
25
+ on the way to Hermes because Paperclip's runtime build strips them.
26
+ - Provider selection lives in `~/.hermes/config.yaml`. Want this agent
27
+ on Anthropic and that one on Z.AI? SSH in, edit YAML, restart.
28
+ - Pluging in a Together AI / Groq / Ollama / OpenRouter? Edit
29
+ `custom_providers:` by hand. Aluno leigo never finds out how.
30
+ - The default heartbeat template kidnaps the agent. The persona you
31
+ carefully wrote in AGENTS.md gets overridden by "list all open
32
+ issues, post a completion comment …".
33
+ - 22% of every wake's token bill is the Paperclip server's own
34
+ `AGENTS.md` accidentally injected by Hermes auto-discovery into the
35
+ system prompt — for every aluno agent that has nothing to do with
36
+ the Paperclip codebase.
37
+ - Cosmetic UI fields that don't do anything ("Thinking effort" — pass-
38
+ through to a CLI flag that Hermes doesn't have).
39
+
40
+ This plugin is the **production-grade** version that fixes all of those
41
+ without touching the upstream Paperclip image and without writing to
42
+ `~/.hermes/config.yaml`.
43
+
44
+ The full list is in `docs/01-configuration-fields-reference.md`.
45
+
46
+ ---
47
+
48
+ ## Highlights
49
+
50
+ | Feature | What it changes |
51
+ | --- | --- |
52
+ | **Skill references survive** | `syncSkills()` symlinks against the source directory, not the runtime snapshot. Every file in `references/`, `assets/`, etc. shows up to Hermes. |
53
+ | **Provider dropdown that tells the truth** | UI Provider select is built dynamically from `~/.hermes/config.yaml` + `auth.json`. Only providers actually configured on this install show up. No 401s from clicking "huggingface" without a key. |
54
+ | **Custom OpenAI-compatible provider, 1 minute** | Pick `Custom` in the Provider select, paste a Base URL, paste a key, type a model id. Plugin injects `OPENAI_BASE_URL` / `OPENAI_API_KEY` plus Hermes's `_HERMES_FORCE_` escape hatch. Routes the wake at Together / Groq / Fireworks / OpenRouter / Ollama / vLLM / anything OpenAI-shaped. |
55
+ | **One company secret, N agents** | Custom Provider API Key field accepts `${TOGETHER_API_KEY}` references resolved from the agent-level Environment Variables row. Rotate a key once; every agent picks up the change. |
56
+ | **Heartbeat template that doesn't hijack** | When `AGENTS.md` has substantive content, a light prompt template kicks in. The persona drives, not the operational workflow. 18 k → 5 k input tokens on a focused-task wake we measured. |
57
+ | **22% token cost reduction shipped** | Per-agent scratch CWD stops Hermes from auto-injecting Paperclip's own contributor guide as "Project Context". Verified live: 9 044 → 6 992 input tokens. |
58
+ | **Honest error surfaces** | No silent `catch { }` blocks for failure paths the plugin owns. Wrong JSON in Extra Headers? Surface in the Run page. Provider auto-detection fell back? Logged. |
59
+ | **Configuration fields the upstream adapter doesn't expose** | `getConfigSchema()` returns 10 fields the Paperclip UI renders natively — Provider, Custom Provider URL/Key/Model/Headers, Heartbeat template mode, Prompt template, Toolsets, Session resume, Debug. |
60
+
61
+ ---
62
+
63
+ ## How to install
64
+
65
+ ### Option 1: as a Paperclip external plugin
66
+
67
+ The Paperclip server reads `/paperclip/adapter-plugins.json`. Add this
68
+ plugin and restart the service:
69
+
70
+ ```bash
71
+ # Inside the paperclip container
72
+ cd /paperclip/adapter-plugins
73
+ mkdir -p hermes-local-plus-0.1.11
74
+ cd hermes-local-plus-0.1.11
75
+ npm pack @felipefontoura/paperclip-adapter-hermes-local-plus@0.1.11
76
+ tar xzf felipefontoura-paperclip-adapter-hermes-local-plus-0.1.11.tgz --strip-components=1
77
+
78
+ # Register
79
+ node -e '
80
+ const fs = require("fs");
81
+ const p = "/paperclip/adapter-plugins.json";
82
+ const cur = JSON.parse(fs.readFileSync(p, "utf8")).filter(x => x.type !== "hermes_local");
83
+ cur.push({
84
+ packageName: "/paperclip/adapter-plugins/hermes-local-plus-0.1.11",
85
+ localPath: "/paperclip/adapter-plugins/hermes-local-plus-0.1.11",
86
+ version: "0.1.11",
87
+ type: "hermes_local",
88
+ installedAt: new Date().toISOString(),
89
+ });
90
+ fs.writeFileSync(p, JSON.stringify(cur, null, 2));
91
+ '
92
+
93
+ # Restart paperclip
94
+ docker service update --force paperclip_paperclip
95
+ ```
96
+
97
+ After the restart Paperclip's UI shows `Hermes Agent (local)` as an
98
+ adapter type. Create an agent, hit `Run Heartbeat`, done.
99
+
100
+ ### Option 2: via bento (unattended install)
101
+
102
+ If your Paperclip lives on a [bento](https://github.com/felipefontoura/bento)-managed
103
+ host, `bento install paperclip` does everything above. See
104
+ `docs/04-bento-unattended-install.md` for the full breakdown.
105
+
106
+ ---
107
+
108
+ ## Configuration in the Paperclip UI
109
+
110
+ `Configuration` tab on every agent of type `Hermes Agent (local)`:
111
+
112
+ ```
113
+ Permissions & Configuration
114
+ ├── Command hermes
115
+ ├── Model Default ▾ (dynamic from config.yaml)
116
+ ├── Provider Auto ▾ (dynamic from config.yaml + auth.json)
117
+ ├── Custom Provider — Base URL https://api.together.xyz/v1
118
+ ├── Custom Provider — API Key ${TOGETHER_API_KEY}
119
+ ├── Custom Provider — Model meta-llama/Llama-3.1-70B-Instruct
120
+ ├── Custom Provider — Extra headers {"X-Org-Id": "abc"}
121
+ ├── Heartbeat template mode Auto ▾
122
+ ├── Prompt template (advanced) (textarea, supports {{variables}})
123
+ ├── Toolsets terminal,skills,web,browser,...
124
+ ├── Session resume Auto ▾
125
+ └── Debug ⚫ off
126
+ ```
127
+
128
+ Each field is documented in `docs/01-configuration-fields-reference.md`
129
+ with the exact CLI flag / env var it maps to and the trade-off you're
130
+ buying.
131
+
132
+ ---
133
+
134
+ ## Architecture
135
+
136
+ ```
137
+ ┌────────────────────────────────────────────────────────┐
138
+ │ Paperclip server (Node) │
139
+ │ ↳ external adapter plugin: hermes-local-plus │
140
+ │ ├── getConfigSchema() drives the UI form │
141
+ │ ├── listModels() Model select │
142
+ │ ├── listProviders() Provider select │
143
+ │ ├── execute(ctx, cfg) spawns Hermes subprocess │
144
+ │ └── syncSkills(ctx, …) symlinks source dirs │
145
+ └──────────────────────┬─────────────────────────────────┘
146
+ │ subprocess
147
+
148
+ ┌────────────────────────────────────────────────────────┐
149
+ │ /usr/local/bin/hermes-paperclip │
150
+ │ ↳ wraps /opt/hermes/bin/hermes │
151
+ │ ↳ reads ~/.hermes/config.yaml + auth.json + .env │
152
+ │ ↳ runs `chat` with args+env from the plugin │
153
+ └────────────────────────────────────────────────────────┘
154
+ ```
155
+
156
+ The plugin owns translation: UI form fields → CLI args + env vars.
157
+ Hermes owns execution. `config.yaml` belongs to the operator alone —
158
+ the plugin reads it to populate dropdowns, but never writes.
159
+
160
+ ---
161
+
162
+ ## Documentation
163
+
164
+ | Doc | Read when |
165
+ | --- | --- |
166
+ | [`docs/01-configuration-fields-reference.md`](./docs/01-configuration-fields-reference.md) | You're configuring an agent and want to know what a field does, what the trade-off is, and an example. |
167
+ | [`docs/02-why-wakes-cost-tokens.md`](./docs/02-why-wakes-cost-tokens.md) | You're surprised by your bill. Empirical breakdown of where Hermes input tokens go, what's prunable, what isn't. |
168
+ | [`docs/03-cwd-fix-saved-22-percent.md`](./docs/03-cwd-fix-saved-22-percent.md) | You want a template for measuring + validating a plugin optimization. |
169
+ | [`docs/04-bento-unattended-install.md`](./docs/04-bento-unattended-install.md) | You operate a bento-managed host and want the unattended install path. |
170
+
171
+ ---
172
+
173
+ ## Compatibility
174
+
175
+ Tested against:
176
+
177
+ - Paperclip server: `ghcr.io/paperclipai/paperclip:latest` (built-in
178
+ external-adapter loader required).
179
+ - Hermes Agent: `nousresearch/hermes-agent:latest`. Verified routes:
180
+ `chat`, `sessions export`, `--resume`, `--continue`. Custom Provider
181
+ routing tested with Z.AI's OpenAI-compatible endpoint.
182
+ - Providers exercised end-to-end: Anthropic (`anthropic`), OpenAI
183
+ (`openai-codex`), Z.AI (`zai-coding-plan`), OpenRouter (`openrouter`),
184
+ any OpenAI-compatible via the Custom Provider form.
185
+ - Docker Swarm with Paperclip running as a single replica. Plugin is
186
+ stateless — multi-replica is a function of the underlying Paperclip
187
+ install, not us.
188
+
189
+ ---
190
+
191
+ ## Status
192
+
193
+ `v0.1.x` — production-deployed at the maintainer's install. Stable for
194
+ the use cases listed above. Breaking changes still permitted between
195
+ minor versions while the API settles. Pin a specific version in your
196
+ `adapter-plugins.json` entry.
197
+
198
+ ---
199
+
200
+ ## License
201
+
202
+ MIT. See [`LICENSE`](./LICENSE).
203
+
204
+ Built on top of [`hermes-paperclip-adapter`](https://github.com/NousResearch/hermes-agent)
205
+ (Nous Research) — same shape, different opinion about what the operator
206
+ actually needs.
@@ -0,0 +1,90 @@
1
+ import { CreateConfigValues } from '@paperclipai/adapter-utils';
2
+
3
+ /**
4
+ * Build adapter configuration from UI form values.
5
+ *
6
+ * Translates Paperclip's CreateConfigValues into the adapterConfig
7
+ * object stored in the agent record.
8
+ *
9
+ * NOTE: Provider resolution happens at runtime in execute.ts, not here.
10
+ * The UI may or may not pass a provider field. If it does, we persist it
11
+ * as the user's explicit override. If not, execute.ts will detect it from
12
+ * ~/.hermes/config.yaml at runtime.
13
+ */
14
+
15
+ /**
16
+ * Build a Hermes Agent adapter config from the Paperclip UI form values.
17
+ */
18
+ declare function buildHermesConfig(v: CreateConfigValues): Record<string, unknown>;
19
+ /**
20
+ * v0.1.7 — `getConfigSchema()` for the Paperclip Configuration tab.
21
+ *
22
+ * Paperclip's UI calls `GET /api/adapters/<type>/config-schema`, which in
23
+ * turn calls `adapter.getConfigSchema()` on the registered adapter object
24
+ * (see `/app/server/dist/routes/adapters.js:498`). The function MUST be
25
+ * named exactly that on the adapter — `agentConfigurationSchema` was a
26
+ * red herring; it isn't read by anything.
27
+ *
28
+ * Verified shape (mirrors `@paperclipai/adapter-cursor-cloud`):
29
+ *
30
+ * {
31
+ * fields: [{
32
+ * key: string, // adapterConfig key the field writes to
33
+ * label: string, // UI label
34
+ * type: "text" | "select" | "toggle", // observed values
35
+ * required?: boolean,
36
+ * default?: string | boolean, // NOT "defaultValue"
37
+ * hint?: string, // NOT "description"
38
+ * options?: [{ value, label }] // only for type: "select"
39
+ * }]
40
+ * }
41
+ *
42
+ * Aluno-leigo note: a "textarea" type was NOT observed in any built-in
43
+ * adapter. We use plain "text" — even multi-line content survives there,
44
+ * just gets rendered in a single-line input. Trade-off accepted: real
45
+ * UI for the field, ugly for long content. Advanced users that paste
46
+ * multi-line templates can still set them via `PATCH adapterConfig`.
47
+ */
48
+ declare function getConfigSchema(): Promise<{
49
+ fields: ({
50
+ key: string;
51
+ label: string;
52
+ type: "select";
53
+ default: string;
54
+ options: {
55
+ value: string;
56
+ label: string;
57
+ }[];
58
+ hint: string;
59
+ } | {
60
+ key: string;
61
+ label: string;
62
+ type: "text";
63
+ default: string;
64
+ hint: string;
65
+ options?: undefined;
66
+ } | {
67
+ key: string;
68
+ label: string;
69
+ type: "secret";
70
+ default: string;
71
+ hint: string;
72
+ options?: undefined;
73
+ } | {
74
+ key: string;
75
+ label: string;
76
+ type: "textarea";
77
+ default: string;
78
+ hint: string;
79
+ options?: undefined;
80
+ } | {
81
+ key: string;
82
+ label: string;
83
+ type: "toggle";
84
+ default: boolean;
85
+ hint: string;
86
+ options?: undefined;
87
+ })[];
88
+ }>;
89
+
90
+ export { buildHermesConfig as b, getConfigSchema as g };
@@ -0,0 +1,49 @@
1
+ import * as _paperclipai_adapter_utils from '@paperclipai/adapter-utils';
2
+ import { execute, testEnvironment, listSkills as listHermesSkills, syncSkills as syncHermesSkills, detectModel, listModels } from './server/index.js';
3
+ export { sessionCodec } from './server/index.js';
4
+ import { g as getConfigSchema } from './build-config-BVrymwyU.js';
5
+
6
+ declare const type = "hermes_local";
7
+ declare const label = "Hermes Agent";
8
+ declare const category: "local";
9
+ /**
10
+ * Models list — always empty at build time. The combobox is populated at
11
+ * runtime by `listModels()` (server/index.ts), which inspects Hermes'
12
+ * actual config + credential pool on disk. Hardcoding would force every
13
+ * install to ship Felipe-specific provider names.
14
+ */
15
+ declare const models: never[];
16
+ declare const agentConfigurationDoc: {
17
+ summary: string;
18
+ steps: string[];
19
+ };
20
+ /**
21
+ * Paperclip plugin-loader contract — must export createServerAdapter()
22
+ * returning a ServerAdapterModule. Capabilities here are what Patch #1
23
+ * in paperclip-hermes-patch-bundle.md added by hand to the core
24
+ * registry.js — embedding them in the plugin module means no bind mount.
25
+ */
26
+ declare function createServerAdapter(): {
27
+ type: string;
28
+ label: string;
29
+ category: "local";
30
+ execute: typeof execute;
31
+ testEnvironment: typeof testEnvironment;
32
+ sessionCodec: _paperclipai_adapter_utils.AdapterSessionCodec;
33
+ listSkills: typeof listHermesSkills;
34
+ syncSkills: typeof syncHermesSkills;
35
+ detectModel: typeof detectModel;
36
+ listModels: typeof listModels;
37
+ models: never[];
38
+ agentConfigurationDoc: {
39
+ summary: string;
40
+ steps: string[];
41
+ };
42
+ getConfigSchema: typeof getConfigSchema;
43
+ supportsInstructionsBundle: boolean;
44
+ instructionsPathKey: string;
45
+ supportsLocalAgentJwt: boolean;
46
+ requiresMaterializedRuntimeSkills: boolean;
47
+ };
48
+
49
+ export { agentConfigurationDoc, category, createServerAdapter, detectModel, execute, label, listHermesSkills as listSkills, models, syncHermesSkills as syncSkills, testEnvironment, type };