@oharness/engine 0.1.0

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 (256) hide show
  1. package/dist/agent/agent.d.ts +196 -0
  2. package/dist/agent/agent.js +758 -0
  3. package/dist/agent/agent.js.map +1 -0
  4. package/dist/agent/compaction.d.ts +68 -0
  5. package/dist/agent/compaction.js +179 -0
  6. package/dist/agent/compaction.js.map +1 -0
  7. package/dist/agent/hooks.d.ts +77 -0
  8. package/dist/agent/hooks.js +152 -0
  9. package/dist/agent/hooks.js.map +1 -0
  10. package/dist/agent/memory.d.ts +45 -0
  11. package/dist/agent/memory.js +147 -0
  12. package/dist/agent/memory.js.map +1 -0
  13. package/dist/agent/overflow.d.ts +35 -0
  14. package/dist/agent/overflow.js +69 -0
  15. package/dist/agent/overflow.js.map +1 -0
  16. package/dist/agent/permissions.d.ts +181 -0
  17. package/dist/agent/permissions.js +289 -0
  18. package/dist/agent/permissions.js.map +1 -0
  19. package/dist/agent/prompt.d.ts +25 -0
  20. package/dist/agent/prompt.js +63 -0
  21. package/dist/agent/prompt.js.map +1 -0
  22. package/dist/agent/session.d.ts +108 -0
  23. package/dist/agent/session.js +231 -0
  24. package/dist/agent/session.js.map +1 -0
  25. package/dist/agent/settings.d.ts +37 -0
  26. package/dist/agent/settings.js +113 -0
  27. package/dist/agent/settings.js.map +1 -0
  28. package/dist/agent/skills.d.ts +58 -0
  29. package/dist/agent/skills.js +225 -0
  30. package/dist/agent/skills.js.map +1 -0
  31. package/dist/agent/store.d.ts +308 -0
  32. package/dist/agent/store.js +596 -0
  33. package/dist/agent/store.js.map +1 -0
  34. package/dist/agent/subagent.d.ts +44 -0
  35. package/dist/agent/subagent.js +138 -0
  36. package/dist/agent/subagent.js.map +1 -0
  37. package/dist/agent/titles.d.ts +8 -0
  38. package/dist/agent/titles.js +23 -0
  39. package/dist/agent/titles.js.map +1 -0
  40. package/dist/agent/workspace.d.ts +60 -0
  41. package/dist/agent/workspace.js +156 -0
  42. package/dist/agent/workspace.js.map +1 -0
  43. package/dist/artifacts/publish.d.ts +94 -0
  44. package/dist/artifacts/publish.js +104 -0
  45. package/dist/artifacts/publish.js.map +1 -0
  46. package/dist/artifacts/store.d.ts +324 -0
  47. package/dist/artifacts/store.js +500 -0
  48. package/dist/artifacts/store.js.map +1 -0
  49. package/dist/auth/index.d.ts +13 -0
  50. package/dist/auth/index.js +10 -0
  51. package/dist/auth/index.js.map +1 -0
  52. package/dist/auth/manager.d.ts +83 -0
  53. package/dist/auth/manager.js +266 -0
  54. package/dist/auth/manager.js.map +1 -0
  55. package/dist/auth/oauth.d.ts +83 -0
  56. package/dist/auth/oauth.js +327 -0
  57. package/dist/auth/oauth.js.map +1 -0
  58. package/dist/auth/providers/account.d.ts +59 -0
  59. package/dist/auth/providers/account.js +282 -0
  60. package/dist/auth/providers/account.js.map +1 -0
  61. package/dist/auth/providers/anthropic.d.ts +24 -0
  62. package/dist/auth/providers/anthropic.js +209 -0
  63. package/dist/auth/providers/anthropic.js.map +1 -0
  64. package/dist/auth/providers/generic.d.ts +57 -0
  65. package/dist/auth/providers/generic.js +155 -0
  66. package/dist/auth/providers/generic.js.map +1 -0
  67. package/dist/auth/providers/official.d.ts +3 -0
  68. package/dist/auth/providers/official.js +90 -0
  69. package/dist/auth/providers/official.js.map +1 -0
  70. package/dist/auth/providers/ottoport.d.ts +3 -0
  71. package/dist/auth/providers/ottoport.js +60 -0
  72. package/dist/auth/providers/ottoport.js.map +1 -0
  73. package/dist/auth/store.d.ts +22 -0
  74. package/dist/auth/store.js +138 -0
  75. package/dist/auth/store.js.map +1 -0
  76. package/dist/auth/types.d.ts +167 -0
  77. package/dist/auth/types.js +16 -0
  78. package/dist/auth/types.js.map +1 -0
  79. package/dist/config.d.ts +197 -0
  80. package/dist/config.js +195 -0
  81. package/dist/config.js.map +1 -0
  82. package/dist/core/errors.d.ts +46 -0
  83. package/dist/core/errors.js +83 -0
  84. package/dist/core/errors.js.map +1 -0
  85. package/dist/core/tokens.d.ts +12 -0
  86. package/dist/core/tokens.js +56 -0
  87. package/dist/core/tokens.js.map +1 -0
  88. package/dist/core/types.d.ts +91 -0
  89. package/dist/core/types.js +49 -0
  90. package/dist/core/types.js.map +1 -0
  91. package/dist/harness.d.ts +302 -0
  92. package/dist/harness.js +695 -0
  93. package/dist/harness.js.map +1 -0
  94. package/dist/index.d.ts +32 -0
  95. package/dist/index.js +21 -0
  96. package/dist/index.js.map +1 -0
  97. package/dist/mcp/client.d.ts +55 -0
  98. package/dist/mcp/client.js +199 -0
  99. package/dist/mcp/client.js.map +1 -0
  100. package/dist/mcp/index.d.ts +13 -0
  101. package/dist/mcp/index.js +8 -0
  102. package/dist/mcp/index.js.map +1 -0
  103. package/dist/mcp/manager.d.ts +77 -0
  104. package/dist/mcp/manager.js +325 -0
  105. package/dist/mcp/manager.js.map +1 -0
  106. package/dist/mcp/oauth.d.ts +63 -0
  107. package/dist/mcp/oauth.js +302 -0
  108. package/dist/mcp/oauth.js.map +1 -0
  109. package/dist/mcp/protocol.d.ts +130 -0
  110. package/dist/mcp/protocol.js +14 -0
  111. package/dist/mcp/protocol.js.map +1 -0
  112. package/dist/mcp/registry.d.ts +52 -0
  113. package/dist/mcp/registry.js +100 -0
  114. package/dist/mcp/registry.js.map +1 -0
  115. package/dist/mcp/registry.json +293 -0
  116. package/dist/mcp/secrets.d.ts +20 -0
  117. package/dist/mcp/secrets.js +75 -0
  118. package/dist/mcp/secrets.js.map +1 -0
  119. package/dist/mcp/transport.d.ts +51 -0
  120. package/dist/mcp/transport.js +158 -0
  121. package/dist/mcp/transport.js.map +1 -0
  122. package/dist/paths.d.ts +37 -0
  123. package/dist/paths.js +63 -0
  124. package/dist/paths.js.map +1 -0
  125. package/dist/plugins.d.ts +55 -0
  126. package/dist/plugins.js +201 -0
  127. package/dist/plugins.js.map +1 -0
  128. package/dist/providers/builtin.d.ts +23 -0
  129. package/dist/providers/builtin.js +141 -0
  130. package/dist/providers/builtin.js.map +1 -0
  131. package/dist/providers/catalog.d.ts +129 -0
  132. package/dist/providers/catalog.js +254 -0
  133. package/dist/providers/catalog.js.map +1 -0
  134. package/dist/providers/catalog.json +472 -0
  135. package/dist/providers/discovery.d.ts +16 -0
  136. package/dist/providers/discovery.js +161 -0
  137. package/dist/providers/discovery.js.map +1 -0
  138. package/dist/providers/http.d.ts +24 -0
  139. package/dist/providers/http.js +139 -0
  140. package/dist/providers/http.js.map +1 -0
  141. package/dist/providers/index.d.ts +10 -0
  142. package/dist/providers/index.js +8 -0
  143. package/dist/providers/index.js.map +1 -0
  144. package/dist/providers/piai.d.ts +124 -0
  145. package/dist/providers/piai.js +551 -0
  146. package/dist/providers/piai.js.map +1 -0
  147. package/dist/providers/registry.d.ts +65 -0
  148. package/dist/providers/registry.js +195 -0
  149. package/dist/providers/registry.js.map +1 -0
  150. package/dist/providers/resilient.d.ts +46 -0
  151. package/dist/providers/resilient.js +117 -0
  152. package/dist/providers/resilient.js.map +1 -0
  153. package/dist/providers/types.d.ts +182 -0
  154. package/dist/providers/types.js +50 -0
  155. package/dist/providers/types.js.map +1 -0
  156. package/dist/schedule/cron.d.ts +35 -0
  157. package/dist/schedule/cron.js +125 -0
  158. package/dist/schedule/cron.js.map +1 -0
  159. package/dist/schedule/scheduler.d.ts +87 -0
  160. package/dist/schedule/scheduler.js +237 -0
  161. package/dist/schedule/scheduler.js.map +1 -0
  162. package/dist/storage/kv.d.ts +56 -0
  163. package/dist/storage/kv.js +173 -0
  164. package/dist/storage/kv.js.map +1 -0
  165. package/dist/storage/migrate.d.ts +11 -0
  166. package/dist/storage/migrate.js +93 -0
  167. package/dist/storage/migrate.js.map +1 -0
  168. package/dist/storage/project.d.ts +9 -0
  169. package/dist/storage/project.js +57 -0
  170. package/dist/storage/project.js.map +1 -0
  171. package/dist/storage/records.d.ts +32 -0
  172. package/dist/storage/records.js +48 -0
  173. package/dist/storage/records.js.map +1 -0
  174. package/dist/sync/client.d.ts +79 -0
  175. package/dist/sync/client.js +118 -0
  176. package/dist/sync/client.js.map +1 -0
  177. package/dist/sync/mirror.d.ts +85 -0
  178. package/dist/sync/mirror.js +389 -0
  179. package/dist/sync/mirror.js.map +1 -0
  180. package/dist/sync/protocol.d.ts +67 -0
  181. package/dist/sync/protocol.js +16 -0
  182. package/dist/sync/protocol.js.map +1 -0
  183. package/dist/sync/settings.d.ts +56 -0
  184. package/dist/sync/settings.js +60 -0
  185. package/dist/sync/settings.js.map +1 -0
  186. package/dist/tools/builtin/artifact.d.ts +16 -0
  187. package/dist/tools/builtin/artifact.js +134 -0
  188. package/dist/tools/builtin/artifact.js.map +1 -0
  189. package/dist/tools/builtin/bash.d.ts +3 -0
  190. package/dist/tools/builtin/bash.js +142 -0
  191. package/dist/tools/builtin/bash.js.map +1 -0
  192. package/dist/tools/builtin/fs.d.ts +19 -0
  193. package/dist/tools/builtin/fs.js +280 -0
  194. package/dist/tools/builtin/fs.js.map +1 -0
  195. package/dist/tools/builtin/index.d.ts +22 -0
  196. package/dist/tools/builtin/index.js +42 -0
  197. package/dist/tools/builtin/index.js.map +1 -0
  198. package/dist/tools/builtin/mcp-resource.d.ts +3 -0
  199. package/dist/tools/builtin/mcp-resource.js +220 -0
  200. package/dist/tools/builtin/mcp-resource.js.map +1 -0
  201. package/dist/tools/builtin/memory.d.ts +10 -0
  202. package/dist/tools/builtin/memory.js +76 -0
  203. package/dist/tools/builtin/memory.js.map +1 -0
  204. package/dist/tools/builtin/recall.d.ts +13 -0
  205. package/dist/tools/builtin/recall.js +163 -0
  206. package/dist/tools/builtin/recall.js.map +1 -0
  207. package/dist/tools/builtin/search.d.ts +3 -0
  208. package/dist/tools/builtin/search.js +240 -0
  209. package/dist/tools/builtin/search.js.map +1 -0
  210. package/dist/tools/builtin/skill.d.ts +10 -0
  211. package/dist/tools/builtin/skill.js +46 -0
  212. package/dist/tools/builtin/skill.js.map +1 -0
  213. package/dist/tools/builtin/task.d.ts +9 -0
  214. package/dist/tools/builtin/task.js +68 -0
  215. package/dist/tools/builtin/task.js.map +1 -0
  216. package/dist/tools/builtin/todo.d.ts +20 -0
  217. package/dist/tools/builtin/todo.js +63 -0
  218. package/dist/tools/builtin/todo.js.map +1 -0
  219. package/dist/tools/define.d.ts +19 -0
  220. package/dist/tools/define.js +39 -0
  221. package/dist/tools/define.js.map +1 -0
  222. package/dist/tools/index.d.ts +7 -0
  223. package/dist/tools/index.js +5 -0
  224. package/dist/tools/index.js.map +1 -0
  225. package/dist/tools/registry.d.ts +39 -0
  226. package/dist/tools/registry.js +101 -0
  227. package/dist/tools/registry.js.map +1 -0
  228. package/dist/tools/types.d.ts +112 -0
  229. package/dist/tools/types.js +22 -0
  230. package/dist/tools/types.js.map +1 -0
  231. package/dist/util/id.d.ts +9 -0
  232. package/dist/util/id.js +19 -0
  233. package/dist/util/id.js.map +1 -0
  234. package/dist/util/json.d.ts +25 -0
  235. package/dist/util/json.js +100 -0
  236. package/dist/util/json.js.map +1 -0
  237. package/dist/util/jwt.d.ts +74 -0
  238. package/dist/util/jwt.js +177 -0
  239. package/dist/util/jwt.js.map +1 -0
  240. package/dist/util/logger.d.ts +13 -0
  241. package/dist/util/logger.js +59 -0
  242. package/dist/util/logger.js.map +1 -0
  243. package/dist/util/queue.d.ts +26 -0
  244. package/dist/util/queue.js +106 -0
  245. package/dist/util/queue.js.map +1 -0
  246. package/dist/util/schema.d.ts +17 -0
  247. package/dist/util/schema.js +98 -0
  248. package/dist/util/schema.js.map +1 -0
  249. package/dist/util/shell.d.ts +20 -0
  250. package/dist/util/shell.js +24 -0
  251. package/dist/util/shell.js.map +1 -0
  252. package/official.json +4 -0
  253. package/package.json +38 -0
  254. package/skills/artifact-authoring/SKILL.md +81 -0
  255. package/skills/canvas/SKILL.md +85 -0
  256. package/skills/skill-authoring/SKILL.md +85 -0
package/dist/paths.js ADDED
@@ -0,0 +1,63 @@
1
+ import { homedir } from "node:os";
2
+ import path from "node:path";
3
+ /**
4
+ * Where OHarness keeps everything that outlives a project: credentials,
5
+ * sessions, the model catalog, user-level skills, agents and plugins.
6
+ *
7
+ * One resolver, because nine call sites each computing `homedir()` themselves
8
+ * is how `OHARNESS_HOME` ended up honoured by the session store and ignored
9
+ * by the other eight — an escape hatch that only worked for one kind of data
10
+ * is worse than none, since it reads as if the whole thing is redirected.
11
+ *
12
+ * Set `OHARNESS_HOME` to redirect all of it. Developing on the harness
13
+ * itself should always set it (the repo's npm scripts do), so working on the
14
+ * tool never writes into the home directory of the person working on it.
15
+ */
16
+ export function userDir() {
17
+ const override = process.env["OHARNESS_HOME"];
18
+ if (override)
19
+ return path.resolve(override);
20
+ // On Linux, respect the XDG layout when the user has opted into it; falling
21
+ // back to a dotfile in $HOME everywhere else, which is what CLI tools of
22
+ // this shape are expected to do.
23
+ const xdg = process.env["XDG_CONFIG_HOME"];
24
+ if (xdg && process.platform === "linux")
25
+ return path.join(xdg, "oharness");
26
+ return path.join(homedir(), ".oharness");
27
+ }
28
+ /** A path inside the user directory, e.g. `userPath("sessions")`. */
29
+ export function userPath(...segments) {
30
+ return path.join(userDir(), ...segments);
31
+ }
32
+ /**
33
+ * Project-local storage: `.oharness/` in the working directory.
34
+ *
35
+ * Kept separate from the user directory on purpose. Settings, skills and
36
+ * agents exist at both levels and the merge order between them is meaningful,
37
+ * so collapsing them into one path helper would blur a distinction the
38
+ * permission and skill layers depend on.
39
+ */
40
+ export function projectPath(cwd, ...segments) {
41
+ return path.join(cwd, ".oharness", ...segments);
42
+ }
43
+ /**
44
+ * Expand a leading `~` the way a shell would, and leave everything else alone.
45
+ *
46
+ * Lives here rather than in the workspace policy because both the policy and
47
+ * the file tools need it and neither may import the other. A path the agent
48
+ * typed as `~/.oharness/skills` has to become the same string the shell would
49
+ * have produced, or a refusal message ends up naming a path that exists
50
+ * nowhere and the reader learns nothing from it.
51
+ */
52
+ export function expandHome(input) {
53
+ if (input === "~")
54
+ return homedir();
55
+ if (input.startsWith("~/"))
56
+ return path.join(homedir(), input.slice(2));
57
+ return input;
58
+ }
59
+ /** Whether `target` is one of `roots` or sits underneath one. */
60
+ export function within(roots, target) {
61
+ return roots.some((root) => target === root || target.startsWith(root + path.sep));
62
+ }
63
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,OAAO;IACrB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC9C,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAE5C,4EAA4E;IAC5E,yEAAyE;IACzE,iCAAiC;IACjC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC3C,IAAI,GAAG,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IAE3E,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AAC3C,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,QAAQ,CAAC,GAAG,QAAkB;IAC5C,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,CAAC;AAC3C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,GAAG,QAAkB;IAC5D,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,GAAG,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,OAAO,EAAE,CAAC;IACpC,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,MAAM,CAAC,KAAwB,EAAE,MAAc;IAC7D,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,UAAU,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrF,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { HookConfig } from "./agent/hooks.ts";
2
+ import type { SubagentDefinition } from "./agent/subagent.ts";
3
+ import type { McpServerConfig } from "./mcp/client.ts";
4
+ /**
5
+ * A plugin is a declarative bundle of existing extension points: skills,
6
+ * agents, hooks and MCP server declarations. It contains no code of its own —
7
+ * tools still arrive through the host application — but two of those four
8
+ * name things that run, so where a plugin came from decides how much of it is
9
+ * adopted. See `pluginContributions`.
10
+ *
11
+ * Two manifest layouts are read, this project's and Claude Code's, because
12
+ * they describe the same thing and reading both costs about twenty lines.
13
+ */
14
+ export interface PluginManifest {
15
+ name: string;
16
+ description?: string;
17
+ version?: string;
18
+ hooks?: HookConfig;
19
+ mcpServers?: Record<string, McpServerConfig>;
20
+ subagents?: SubagentDefinition[];
21
+ }
22
+ export interface PluginDefinition extends PluginManifest {
23
+ directory: string;
24
+ source: "user" | "project" | "config" | "claude";
25
+ skillDir: string;
26
+ agentDir: string;
27
+ }
28
+ export interface PluginConfig {
29
+ /** Extra directories containing plugin folders, relative to the project. */
30
+ dirs?: string[];
31
+ /** If set, only these plugin names load. */
32
+ enabled?: string[];
33
+ /** Disabled wins over enabled, useful for project-level policy. */
34
+ disabled?: string[];
35
+ /**
36
+ * Read plugins Claude Code has already installed. On by default: they are
37
+ * files on this machine that this user chose to install, and the alternative
38
+ * is an empty list beside a directory full of them. Set false to ignore it.
39
+ */
40
+ claudeCode?: boolean;
41
+ }
42
+ /** Where Claude Code keeps what it has installed; overridable for tests. */
43
+ export declare function claudePluginCache(): string;
44
+ /** Discover plugins in a stable override order: Claude → user → project → config. */
45
+ export declare function discoverPlugins(cwd: string, config?: PluginConfig): Promise<PluginDefinition[]>;
46
+ /** Convert installed plugin declarations into ordinary harness configuration. */
47
+ export declare function pluginContributions(plugins: PluginDefinition[]): {
48
+ skillDirs: string[];
49
+ agentDirs: string[];
50
+ hooks: HookConfig;
51
+ mcpServers: Record<string, McpServerConfig>;
52
+ subagents: SubagentDefinition[];
53
+ };
54
+ /** Lightweight validation keeps plugin manifests dependency-free and safe to ignore when malformed. */
55
+ export declare function parsePluginManifest(value: unknown): PluginManifest | undefined;
@@ -0,0 +1,201 @@
1
+ import { homedir } from "node:os";
2
+ import { userPath } from "./paths.js";
3
+ import { readFile, readdir, stat } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { parseJson } from "./util/json.js";
6
+ const MANIFEST = "oharness.plugin.json";
7
+ /**
8
+ * The other manifest this reads.
9
+ *
10
+ * A plugin here is a manifest plus a `skills/` and an `agents/` directory, and
11
+ * so is a Claude Code plugin — the same idea arrived at twice. Reading both is
12
+ * about twenty lines, and it is the difference between a menu with an
13
+ * ecosystem behind it and a menu that is empty until somebody hand-copies a
14
+ * folder into place. Nothing is imported or converted: the directory stays
15
+ * where it is and is read where it lies.
16
+ */
17
+ const CLAUDE_MANIFEST = path.join(".claude-plugin", "plugin.json");
18
+ /** Its two sidecars, which hold what our manifest keeps inline. */
19
+ const CLAUDE_MCP = ".mcp.json";
20
+ const CLAUDE_HOOKS = path.join("hooks", "hooks.json");
21
+ /** `<marketplace>/<plugin>/<version>/` — three levels down from the cache. */
22
+ const CLAUDE_CACHE_DEPTH = 3;
23
+ /** Where Claude Code keeps what it has installed; overridable for tests. */
24
+ export function claudePluginCache() {
25
+ return process.env["OHARNESS_CLAUDE_PLUGINS"] ?? path.join(homedir(), ".claude", "plugins", "cache");
26
+ }
27
+ /** Discover plugins in a stable override order: Claude → user → project → config. */
28
+ export async function discoverPlugins(cwd, config = {}) {
29
+ const roots = [
30
+ // First, so anything of this project's own overrides it by name.
31
+ ...(config.claudeCode === false
32
+ ? []
33
+ : [
34
+ {
35
+ directory: claudePluginCache(),
36
+ source: "claude",
37
+ depth: CLAUDE_CACHE_DEPTH,
38
+ },
39
+ ]),
40
+ { directory: userPath("plugins"), source: "user" },
41
+ { directory: path.join(cwd, ".oharness", "plugins"), source: "project" },
42
+ ...((config.dirs ?? []).map((dir) => ({
43
+ directory: path.resolve(cwd, dir),
44
+ source: "config",
45
+ }))),
46
+ ];
47
+ // A later layer with the same name deliberately replaces the earlier one.
48
+ const byName = new Map();
49
+ for (const root of roots) {
50
+ for (const plugin of await scanPluginRoot(root.directory, root.source, root.depth ?? 1)) {
51
+ // Within one root the same plugin can be cached at several versions;
52
+ // the newest is the one that was installed, the rest are history.
53
+ const seen = byName.get(plugin.name);
54
+ if (seen && seen.source === plugin.source && newer(seen, plugin))
55
+ continue;
56
+ byName.set(plugin.name, plugin);
57
+ }
58
+ }
59
+ const enabled = config.enabled ? new Set(config.enabled) : undefined;
60
+ const disabled = new Set(config.disabled ?? []);
61
+ return [...byName.values()].filter((plugin) => (!enabled || enabled.has(plugin.name)) && !disabled.has(plugin.name));
62
+ }
63
+ /** Convert installed plugin declarations into ordinary harness configuration. */
64
+ export function pluginContributions(plugins) {
65
+ const out = {
66
+ skillDirs: [],
67
+ agentDirs: [],
68
+ hooks: {},
69
+ mcpServers: {},
70
+ subagents: [],
71
+ };
72
+ for (const plugin of plugins) {
73
+ out.skillDirs.push(plugin.skillDir);
74
+ out.agentDirs.push(plugin.agentDir);
75
+ out.subagents.push(...(plugin.subagents ?? []));
76
+ // Skills and agents are text the agent reads when it decides to. Hooks and
77
+ // MCP servers are not: a hook is a command line that runs on its own
78
+ // schedule, and a server is a process started at launch. Adopting those
79
+ // from a directory the user filled in for a different program would mean
80
+ // this harness executing things nobody pointed at it. They are still
81
+ // reported, so a plugin's servers can be added the ordinary way, with the
82
+ // confirmation and the login that path already has.
83
+ if (plugin.source === "claude")
84
+ continue;
85
+ Object.assign(out.mcpServers, plugin.mcpServers ?? {});
86
+ for (const [event, hooks] of Object.entries(plugin.hooks ?? {})) {
87
+ out.hooks[event] = [...(out.hooks[event] ?? []), ...(hooks ?? [])];
88
+ }
89
+ }
90
+ return out;
91
+ }
92
+ /**
93
+ * Every plugin under a root, to a bounded depth.
94
+ *
95
+ * Depth exists because the two layouts nest differently: ours is one folder
96
+ * per plugin, and Claude Code's cache is marketplace, then plugin, then
97
+ * version. A directory that is itself a plugin is not descended into, so the
98
+ * walk stops at the first thing it recognises rather than picking up whatever
99
+ * a plugin happens to keep in a subfolder.
100
+ */
101
+ async function scanPluginRoot(root, source, depth) {
102
+ let entries;
103
+ try {
104
+ entries = await readdir(root, { withFileTypes: true });
105
+ }
106
+ catch {
107
+ return [];
108
+ }
109
+ const plugins = [];
110
+ for (const entry of entries) {
111
+ if (!entry.isDirectory() && !entry.isSymbolicLink())
112
+ continue;
113
+ // Dot-directories at a scan level are bookkeeping, never plugins.
114
+ if (entry.name.startsWith("."))
115
+ continue;
116
+ const directory = path.join(root, entry.name);
117
+ const plugin = await readPlugin(directory, source);
118
+ if (plugin)
119
+ plugins.push(plugin);
120
+ else if (depth > 1)
121
+ plugins.push(...(await scanPluginRoot(directory, source, depth - 1)));
122
+ }
123
+ return plugins;
124
+ }
125
+ /** One directory, in either layout, or nothing if it is neither. */
126
+ async function readPlugin(directory, source) {
127
+ const own = await readManifest(path.join(directory, MANIFEST));
128
+ if (own) {
129
+ return {
130
+ ...own,
131
+ directory,
132
+ source,
133
+ skillDir: path.join(directory, "skills"),
134
+ agentDir: path.join(directory, "agents"),
135
+ };
136
+ }
137
+ const claude = await readManifest(path.join(directory, CLAUDE_MANIFEST));
138
+ if (!claude)
139
+ return undefined;
140
+ // The same declarations, kept in sidecar files rather than inline.
141
+ const mcp = await readJsonFile(path.join(directory, CLAUDE_MCP));
142
+ const hooks = await readJsonFile(path.join(directory, CLAUDE_HOOKS));
143
+ return {
144
+ ...claude,
145
+ ...(isRecord(mcp?.mcpServers) ? { mcpServers: mcp.mcpServers } : {}),
146
+ ...(isRecord(hooks?.hooks) ? { hooks: hooks.hooks } : {}),
147
+ directory,
148
+ source,
149
+ skillDir: path.join(directory, "skills"),
150
+ agentDir: path.join(directory, "agents"),
151
+ };
152
+ }
153
+ async function readManifest(file) {
154
+ const value = await readJsonFile(file);
155
+ return value === undefined ? undefined : parsePluginManifest(value);
156
+ }
157
+ async function readJsonFile(file) {
158
+ try {
159
+ const info = await stat(file);
160
+ if (!info.isFile())
161
+ return undefined;
162
+ const parsed = parseJson(await readFile(file, "utf8"));
163
+ return parsed.ok ? parsed.value : undefined;
164
+ }
165
+ catch {
166
+ return undefined;
167
+ }
168
+ }
169
+ /** Whether `a` is at least as new as `b`, comparing dotted versions. */
170
+ function newer(a, b) {
171
+ const parts = (v) => (v ?? "0").split(/[.-]/).map((n) => Number.parseInt(n, 10) || 0);
172
+ const left = parts(a.version);
173
+ const right = parts(b.version);
174
+ for (let i = 0; i < Math.max(left.length, right.length); i++) {
175
+ const one = left[i] ?? 0;
176
+ const two = right[i] ?? 0;
177
+ if (one !== two)
178
+ return one > two;
179
+ }
180
+ return true;
181
+ }
182
+ /** Lightweight validation keeps plugin manifests dependency-free and safe to ignore when malformed. */
183
+ export function parsePluginManifest(value) {
184
+ if (!value || typeof value !== "object" || Array.isArray(value))
185
+ return undefined;
186
+ const raw = value;
187
+ if (typeof raw.name !== "string" || raw.name.trim() === "")
188
+ return undefined;
189
+ return {
190
+ name: raw.name.trim(),
191
+ ...(typeof raw.description === "string" ? { description: raw.description } : {}),
192
+ ...(typeof raw.version === "string" ? { version: raw.version } : {}),
193
+ ...(isRecord(raw.hooks) ? { hooks: raw.hooks } : {}),
194
+ ...(isRecord(raw.mcpServers) ? { mcpServers: raw.mcpServers } : {}),
195
+ ...(Array.isArray(raw.subagents) ? { subagents: raw.subagents } : {}),
196
+ };
197
+ }
198
+ function isRecord(value) {
199
+ return !!value && typeof value === "object" && !Array.isArray(value);
200
+ }
201
+ //# sourceMappingURL=plugins.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugins.js","sourceRoot":"","sources":["../src/plugins.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Api, Model as PiModel } from "@earendil-works/pi-ai";
2
+ import type { CatalogProvider } from "./catalog.ts";
3
+ import type { ModelSpec } from "./types.ts";
4
+ export interface SeedOptions {
5
+ /** Entries already stated by a catalog file; these are never replaced. */
6
+ existing?: ModelSpec[];
7
+ }
8
+ /**
9
+ * Model specs for every catalog provider pi-ai has built-in entries for.
10
+ *
11
+ * A failure here is never fatal: the seed is a convenience, and a catalog that
12
+ * loads with fewer models is a far better outcome than a harness that will not
13
+ * start because a dependency reorganised its data.
14
+ */
15
+ export declare function builtinModels(providers: Record<string, CatalogProvider>, opts?: SeedOptions): Promise<ModelSpec[]>;
16
+ /**
17
+ * Translate one pi-ai model into a harness spec.
18
+ *
19
+ * The endpoint deliberately does not come across: pi-ai carries a `baseUrl`
20
+ * per model, but which host a provider talks to is the catalog entry's to
21
+ * decide — that is what makes pointing Kimi at its mainland endpoint work.
22
+ */
23
+ export declare function specFromPiModel(entry: CatalogProvider, model: PiModel<Api>): ModelSpec;
@@ -0,0 +1,141 @@
1
+ import { PI_APIS } from "./piai.js";
2
+ /**
3
+ * Levels pi-ai only offers when the model names them.
4
+ *
5
+ * `thinkingLevelMap` is a patch, not a listing: an absent key means "provider
6
+ * default", and null means unsupported. That reads in opposite directions for
7
+ * the two halves of the scale, since `xhigh` and `max` are opt-in upstream.
8
+ */
9
+ const OPT_IN_EFFORTS = ["xhigh", "max"];
10
+ const DEFAULT_EFFORTS = ["low", "medium", "high"];
11
+ /**
12
+ * Model specs for every catalog provider pi-ai has built-in entries for.
13
+ *
14
+ * A failure here is never fatal: the seed is a convenience, and a catalog that
15
+ * loads with fewer models is a far better outcome than a harness that will not
16
+ * start because a dependency reorganised its data.
17
+ */
18
+ export async function builtinModels(providers, opts = {}) {
19
+ let catalog;
20
+ try {
21
+ // Dynamic, so the ~600KB of bundled model data is read when a catalog is
22
+ // built and not merely because something imported this module.
23
+ catalog = (await import("@earendil-works/pi-ai/providers/all"));
24
+ }
25
+ catch {
26
+ return [];
27
+ }
28
+ let known;
29
+ try {
30
+ known = new Set(catalog.getBuiltinProviders());
31
+ }
32
+ catch {
33
+ return [];
34
+ }
35
+ // Which catalog entry claims each pi-ai provider. Ids mostly match; where
36
+ // they do not, the catalog says so with `piProvider`.
37
+ const claims = new Map();
38
+ for (const entry of Object.values(providers)) {
39
+ const piId = entry.piProvider ?? entry.id;
40
+ if (!known.has(piId))
41
+ continue;
42
+ // An entry naming the provider outright beats one that only shares its id
43
+ // by accident, so a mapping is never stolen by declaration order.
44
+ const held = claims.get(piId);
45
+ if (held && (held.piProvider === piId || held.id === piId))
46
+ continue;
47
+ claims.set(piId, entry);
48
+ }
49
+ const taken = new Set();
50
+ for (const spec of opts.existing ?? []) {
51
+ taken.add(spec.id);
52
+ taken.add(`${spec.provider} ${spec.model}`);
53
+ }
54
+ const seeded = [];
55
+ for (const [piId, entry] of claims) {
56
+ let models;
57
+ try {
58
+ models = catalog.getBuiltinModels(piId);
59
+ }
60
+ catch {
61
+ continue;
62
+ }
63
+ for (const model of models) {
64
+ // A model pi-ai serves over the Responses API cannot be driven over Chat
65
+ // Completions just because the catalog entry points at the same vendor;
66
+ // the request shape differs. Seed only what this entry can actually call.
67
+ if (model.api !== PI_APIS[entry.protocol])
68
+ continue;
69
+ const spec = specFromPiModel(entry, model);
70
+ if (taken.has(spec.id) || taken.has(`${spec.provider} ${spec.model}`))
71
+ continue;
72
+ taken.add(spec.id);
73
+ taken.add(`${spec.provider} ${spec.model}`);
74
+ seeded.push(spec);
75
+ }
76
+ }
77
+ return seeded;
78
+ }
79
+ /**
80
+ * Translate one pi-ai model into a harness spec.
81
+ *
82
+ * The endpoint deliberately does not come across: pi-ai carries a `baseUrl`
83
+ * per model, but which host a provider talks to is the catalog entry's to
84
+ * decide — that is what makes pointing Kimi at its mainland endpoint work.
85
+ */
86
+ export function specFromPiModel(entry, model) {
87
+ const compat = (model.compat ?? {});
88
+ const levels = model.thinkingLevelMap;
89
+ return {
90
+ id: `${entry.id}/${model.id}`,
91
+ provider: entry.id,
92
+ model: model.id,
93
+ displayName: model.name || model.id,
94
+ contextWindow: model.contextWindow,
95
+ maxOutputTokens: model.maxTokens,
96
+ // pi-ai publishes no tool flag, and a chat model that cannot call tools is
97
+ // useless here anyway — the harness's own default assumes the same.
98
+ supportsTools: true,
99
+ supportsImages: model.input.includes("image"),
100
+ supportsParallelToolCalls: true,
101
+ // A quoted cache-read price is the only evidence in this data that the
102
+ // vendor caches at all.
103
+ supportsPromptCache: (model.cost?.cacheRead ?? 0) > 0,
104
+ thinkingMode: thinkingMode(model, compat),
105
+ effortLevels: effortLevels(model, levels),
106
+ supportsSampling: compat["supportsTemperature"] !== false,
107
+ ...price("costPer1MInput", model.cost?.input),
108
+ ...price("costPer1MOutput", model.cost?.output),
109
+ ...price("costPer1MCacheRead", model.cost?.cacheRead),
110
+ ...price("costPer1MCacheWrite", model.cost?.cacheWrite),
111
+ };
112
+ }
113
+ /**
114
+ * pi-ai models are `reasoning: true/false` plus a per-protocol rendering, so
115
+ * "effort" is the honest translation of every reasoning model here. The one
116
+ * distinction worth keeping is the Anthropic quirk the wire layer round-trips:
117
+ * a model that only accepts an adaptive config rejects a budget outright.
118
+ */
119
+ function thinkingMode(model, compat) {
120
+ if (!model.reasoning)
121
+ return "none";
122
+ return compat["forceAdaptiveThinking"] === true ? "adaptive" : "effort";
123
+ }
124
+ function effortLevels(model, levels) {
125
+ if (!model.reasoning)
126
+ return [];
127
+ if (!levels)
128
+ return [...DEFAULT_EFFORTS];
129
+ return [
130
+ ...DEFAULT_EFFORTS.filter((level) => levels[level] !== null),
131
+ ...OPT_IN_EFFORTS.filter((level) => typeof levels[level] === "string"),
132
+ ];
133
+ }
134
+ /**
135
+ * Zero means "not quoted" in this data, and a spec carrying a zero price reads
136
+ * as free everywhere downstream — a worse answer than saying nothing.
137
+ */
138
+ function price(field, value) {
139
+ return value && value > 0 ? { [field]: value } : {};
140
+ }
141
+ //# sourceMappingURL=builtin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builtin.js","sourceRoot":"","sources":["../../src/providers/builtin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ import type { ModelSpec } from "./types.ts";
2
+ /**
3
+ * The model catalog is data, not code.
4
+ *
5
+ * Vendor model ids turn over faster than releases of this package, so baking
6
+ * them into TypeScript guarantees the list is wrong within weeks. Provider
7
+ * facts (endpoint, env var, protocol) are stable and worth shipping; model ids
8
+ * mostly come from `/models refresh`, which asks the vendor directly.
9
+ */
10
+ /**
11
+ * Wire formats a catalog entry may declare.
12
+ *
13
+ * The first three are the historical names and stay as they are — renaming
14
+ * them would invalidate every user catalog in existence. The rest were opened
15
+ * up by moving the wire layer onto pi-ai, which implements them already:
16
+ * `openai-responses` for vendors that have moved off Chat Completions, and the
17
+ * two cloud-signed endpoints that were previously out of reach.
18
+ */
19
+ export type ProtocolId = "anthropic-messages" | "openai-chat" | "gemini" | "openai-responses" | "bedrock" | "vertex";
20
+ export type DiscoveryFormat = "openai" | "openrouter";
21
+ export interface CatalogProvider {
22
+ id: string;
23
+ label: string;
24
+ /** Which wire format this endpoint speaks. */
25
+ protocol: ProtocolId;
26
+ baseUrl: string;
27
+ /** Env vars consulted in order for an API key. */
28
+ envVars: string[];
29
+ headers?: Record<string, string>;
30
+ /** How to read `/models`; absent means the endpoint is not worth calling. */
31
+ discovery?: DiscoveryFormat;
32
+ /**
33
+ * The id this vendor goes by in pi-ai's built-in model catalog, when it is
34
+ * not this entry's own id. Only naming exists to be reconciled here — the
35
+ * endpoint and credentials stay this entry's.
36
+ */
37
+ piProvider?: string;
38
+ /** Some servers reject `stream_options: {include_usage: true}`. */
39
+ includeUsage?: boolean;
40
+ /**
41
+ * Send an `Idempotency-Key` header, one value per request.
42
+ *
43
+ * Not a nicety on the endpoints that ask for it: a metering gateway reserves
44
+ * the worst-case cost before it forwards, and the key is what lets a retry
45
+ * resume that reservation instead of opening a second one. OttoPort rejects
46
+ * a completion without it outright.
47
+ *
48
+ * It cannot be an entry in `headers` — that map is one static value for the
49
+ * life of the process, and a key reused across two different requests is
50
+ * precisely what the header exists to prevent.
51
+ */
52
+ idempotencyKey?: boolean;
53
+ /**
54
+ * A ceiling on `max_tokens`, when the endpoint imposes one below what its
55
+ * models advertise.
56
+ *
57
+ * Gateways cap output to bound what a single request can cost, and the cap
58
+ * is the gateway's rather than the model's — so `/models` does not mention
59
+ * it and discovery lands on its own default, which is higher. The result is
60
+ * a 400 on every request rather than a shorter answer, so the number is
61
+ * stated here where the endpoint is described.
62
+ */
63
+ maxOutputTokens?: number;
64
+ docsUrl?: string;
65
+ /**
66
+ * Surfaced without being searched for.
67
+ *
68
+ * Which vendors are worth showing first is a fact about the market, not
69
+ * about this code, so it lives in the catalog data with everything else that
70
+ * changes on its own schedule.
71
+ */
72
+ popular?: boolean;
73
+ /**
74
+ * The one this project stands behind. At most one entry should carry it —
75
+ * a badge that half the list wears recommends nothing.
76
+ */
77
+ recommended?: boolean;
78
+ /** One line shown under the name, so a chooser is not a list of bare names. */
79
+ description?: string;
80
+ }
81
+ export interface Catalog {
82
+ /** When the bundled model entries were last checked. */
83
+ verified?: string;
84
+ providers: Record<string, CatalogProvider>;
85
+ models: ModelSpec[];
86
+ }
87
+ /**
88
+ * A catalog as read from one file. Fields are optional because a later file
89
+ * is a *patch*: a user catalog that only changes `baseUrl` must not blank out
90
+ * the label and env vars it did not mention.
91
+ */
92
+ export interface CatalogPatch {
93
+ verified?: string;
94
+ providers: Record<string, Partial<CatalogProvider>>;
95
+ models: ModelSpec[];
96
+ }
97
+ /** Where a user's own catalog lives; overrides the bundled one. */
98
+ export declare function userCatalogPath(): string;
99
+ /**
100
+ * Load the catalog: bundled file, then the user's, then any explicit paths.
101
+ * Later sources override earlier ones per provider id and per model id.
102
+ *
103
+ * Under all of them sits pi-ai's built-in model catalog, which is why a
104
+ * provider nobody has written an entry for still has models to offer. It is
105
+ * seeded last but ranked lowest: appended after the stated entries so the
106
+ * order callers see — and the default model, which is the first of them —
107
+ * still comes from what this package verified.
108
+ */
109
+ export declare function loadCatalog(extraPaths?: string[]): Promise<Catalog>;
110
+ /** Parse and finalize a single complete catalog. */
111
+ export declare function parseCatalog(raw: unknown, source?: string): Catalog;
112
+ /**
113
+ * Parse one catalog file into a patch, keeping absent fields absent so a later
114
+ * merge can tell "not mentioned" from "set to empty".
115
+ */
116
+ export declare function parsePatch(raw: unknown, source?: string): CatalogPatch;
117
+ export declare function mergeCatalog(base: Catalog, override: Catalog): Catalog;
118
+ export declare function mergePatch(base: CatalogPatch, override: CatalogPatch): CatalogPatch;
119
+ /**
120
+ * Resolve a merged patch into a usable catalog, dropping providers that are
121
+ * still incomplete after every layer has been applied.
122
+ */
123
+ export declare function finalize(patch: CatalogPatch): Catalog;
124
+ /**
125
+ * Fill in a partial model entry. Defaults are deliberately conservative:
126
+ * claiming a capability the model lacks is a 400 at request time, whereas
127
+ * under-claiming only costs a feature.
128
+ */
129
+ export declare function normalizeModel(entry: unknown): ModelSpec | undefined;