@zhijiewang/openharness 2.0.0 → 2.3.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 (235) hide show
  1. package/README.md +4 -4
  2. package/dist/DeferredTool.js +3 -1
  3. package/dist/Tool.d.ts +1 -1
  4. package/dist/agents/roles.js +58 -62
  5. package/dist/commands/cybergotchi.d.ts +1 -1
  6. package/dist/commands/cybergotchi.js +30 -30
  7. package/dist/commands/index.js +360 -122
  8. package/dist/components/App.d.ts +1 -1
  9. package/dist/components/App.js +6 -6
  10. package/dist/components/CompanionFooter.d.ts +1 -1
  11. package/dist/components/CompanionFooter.js +6 -8
  12. package/dist/components/CybergotchiBubble.js +5 -5
  13. package/dist/components/CybergotchiPanel.d.ts +1 -1
  14. package/dist/components/CybergotchiPanel.js +7 -7
  15. package/dist/components/CybergotchiPanelConnected.js +2 -2
  16. package/dist/components/CybergotchiSetup.js +26 -24
  17. package/dist/components/CybergotchiSprite.d.ts +1 -1
  18. package/dist/components/CybergotchiSprite.js +8 -12
  19. package/dist/components/DiffView.d.ts +1 -1
  20. package/dist/components/DiffView.js +10 -10
  21. package/dist/components/ErrorBoundary.d.ts +1 -1
  22. package/dist/components/ErrorBoundary.js +1 -1
  23. package/dist/components/InitWizard.js +65 -33
  24. package/dist/components/Markdown.js +2 -4
  25. package/dist/components/Messages.js +4 -4
  26. package/dist/components/PermissionPrompt.d.ts +1 -1
  27. package/dist/components/PermissionPrompt.js +15 -17
  28. package/dist/components/REPL.d.ts +1 -1
  29. package/dist/components/REPL.js +74 -49
  30. package/dist/components/Spinner.js +2 -2
  31. package/dist/components/TextInput.js +35 -29
  32. package/dist/components/ToolCallDisplay.js +3 -5
  33. package/dist/cybergotchi/bones.d.ts +1 -1
  34. package/dist/cybergotchi/bones.js +8 -8
  35. package/dist/cybergotchi/config.d.ts +2 -2
  36. package/dist/cybergotchi/config.js +13 -13
  37. package/dist/cybergotchi/events.d.ts +5 -5
  38. package/dist/cybergotchi/events.js +7 -7
  39. package/dist/cybergotchi/needs.d.ts +2 -2
  40. package/dist/cybergotchi/needs.js +7 -9
  41. package/dist/cybergotchi/personality.d.ts +2 -2
  42. package/dist/cybergotchi/personality.js +2 -2
  43. package/dist/cybergotchi/species.d.ts +1 -1
  44. package/dist/cybergotchi/species.js +145 -217
  45. package/dist/cybergotchi/speech.d.ts +2 -2
  46. package/dist/cybergotchi/speech.js +43 -43
  47. package/dist/cybergotchi/types.d.ts +4 -4
  48. package/dist/cybergotchi/types.js +26 -26
  49. package/dist/cybergotchi/useCybergotchi.d.ts +1 -1
  50. package/dist/cybergotchi/useCybergotchi.js +29 -25
  51. package/dist/git/index.js +11 -9
  52. package/dist/harness/checkpoints.js +29 -21
  53. package/dist/harness/config.d.ts +12 -2
  54. package/dist/harness/config.js +15 -9
  55. package/dist/harness/context-warning.d.ts +1 -1
  56. package/dist/harness/context-warning.js +1 -1
  57. package/dist/harness/cost.js +1 -1
  58. package/dist/harness/credentials.js +13 -13
  59. package/dist/harness/hooks.js +7 -5
  60. package/dist/harness/keybindings.js +20 -18
  61. package/dist/harness/marketplace.d.ts +3 -3
  62. package/dist/harness/marketplace.js +55 -42
  63. package/dist/harness/memory.d.ts +23 -5
  64. package/dist/harness/memory.js +142 -41
  65. package/dist/harness/onboarding.js +30 -10
  66. package/dist/harness/plugins.d.ts +9 -1
  67. package/dist/harness/plugins.js +54 -30
  68. package/dist/harness/rules.js +12 -7
  69. package/dist/harness/sandbox.d.ts +34 -0
  70. package/dist/harness/sandbox.js +104 -0
  71. package/dist/harness/session-db.d.ts +55 -0
  72. package/dist/harness/session-db.js +165 -0
  73. package/dist/harness/session.d.ts +1 -1
  74. package/dist/harness/session.js +34 -15
  75. package/dist/harness/store.d.ts +3 -3
  76. package/dist/harness/store.js +6 -4
  77. package/dist/harness/submit-handler.d.ts +4 -4
  78. package/dist/harness/submit-handler.js +57 -21
  79. package/dist/harness/telemetry.d.ts +1 -1
  80. package/dist/harness/telemetry.js +23 -19
  81. package/dist/harness/traces.d.ts +2 -2
  82. package/dist/harness/traces.js +44 -33
  83. package/dist/harness/verification.d.ts +1 -1
  84. package/dist/harness/verification.js +50 -44
  85. package/dist/lsp/client.js +44 -40
  86. package/dist/main.js +100 -59
  87. package/dist/mcp/DeferredMcpTool.d.ts +4 -4
  88. package/dist/mcp/DeferredMcpTool.js +9 -5
  89. package/dist/mcp/McpTool.d.ts +4 -4
  90. package/dist/mcp/McpTool.js +8 -4
  91. package/dist/mcp/client.d.ts +2 -2
  92. package/dist/mcp/client.js +21 -21
  93. package/dist/mcp/loader.d.ts +1 -1
  94. package/dist/mcp/loader.js +17 -12
  95. package/dist/mcp/registry.d.ts +3 -3
  96. package/dist/mcp/registry.js +97 -97
  97. package/dist/mcp/schema.d.ts +1 -1
  98. package/dist/mcp/schema.js +16 -16
  99. package/dist/mcp/server.d.ts +1 -1
  100. package/dist/mcp/server.js +21 -21
  101. package/dist/mcp/types.d.ts +3 -3
  102. package/dist/providers/anthropic.d.ts +2 -2
  103. package/dist/providers/anthropic.js +10 -9
  104. package/dist/providers/base.d.ts +1 -1
  105. package/dist/providers/index.js +10 -3
  106. package/dist/providers/llamacpp.d.ts +2 -2
  107. package/dist/providers/llamacpp.js +1 -3
  108. package/dist/providers/ollama.d.ts +2 -2
  109. package/dist/providers/ollama.js +3 -4
  110. package/dist/providers/openai.d.ts +2 -2
  111. package/dist/providers/openai.js +3 -5
  112. package/dist/providers/openrouter.d.ts +2 -2
  113. package/dist/providers/router.d.ts +1 -1
  114. package/dist/providers/router.js +7 -7
  115. package/dist/query/compress.d.ts +2 -2
  116. package/dist/query/compress.js +22 -21
  117. package/dist/query/context-manager.d.ts +2 -2
  118. package/dist/query/context-manager.js +8 -11
  119. package/dist/query/errors.js +1 -1
  120. package/dist/query/index.d.ts +1 -1
  121. package/dist/query/index.js +30 -22
  122. package/dist/query/tools.js +15 -12
  123. package/dist/query/types.d.ts +1 -1
  124. package/dist/query.d.ts +1 -1
  125. package/dist/query.js +1 -1
  126. package/dist/remote/auth.d.ts +2 -2
  127. package/dist/remote/auth.js +8 -8
  128. package/dist/remote/server.d.ts +3 -3
  129. package/dist/remote/server.js +60 -60
  130. package/dist/renderer/cells.js +9 -9
  131. package/dist/renderer/colors.js +24 -6
  132. package/dist/renderer/diff.d.ts +2 -2
  133. package/dist/renderer/diff.js +27 -19
  134. package/dist/renderer/differ.d.ts +1 -1
  135. package/dist/renderer/differ.js +9 -9
  136. package/dist/renderer/image.js +19 -19
  137. package/dist/renderer/index.d.ts +6 -6
  138. package/dist/renderer/index.js +163 -93
  139. package/dist/renderer/input.js +66 -48
  140. package/dist/renderer/layout.d.ts +6 -6
  141. package/dist/renderer/layout.js +163 -124
  142. package/dist/renderer/markdown.d.ts +2 -2
  143. package/dist/renderer/markdown.js +173 -54
  144. package/dist/renderer/session-browser.d.ts +2 -2
  145. package/dist/renderer/session-browser.js +19 -21
  146. package/dist/repl.d.ts +5 -5
  147. package/dist/repl.js +300 -198
  148. package/dist/sdk/index.d.ts +8 -7
  149. package/dist/sdk/index.js +59 -42
  150. package/dist/services/AgentDispatcher.d.ts +3 -3
  151. package/dist/services/AgentDispatcher.js +33 -29
  152. package/dist/services/CronExecutor.d.ts +4 -4
  153. package/dist/services/CronExecutor.js +12 -8
  154. package/dist/services/EvaluatorLoop.d.ts +3 -3
  155. package/dist/services/EvaluatorLoop.js +29 -21
  156. package/dist/services/MetaHarness.d.ts +1 -1
  157. package/dist/services/MetaHarness.js +41 -33
  158. package/dist/services/PipelineExecutor.d.ts +1 -1
  159. package/dist/services/PipelineExecutor.js +23 -25
  160. package/dist/services/SkillExtractor.d.ts +43 -0
  161. package/dist/services/SkillExtractor.js +143 -0
  162. package/dist/services/StreamingToolExecutor.d.ts +2 -2
  163. package/dist/services/StreamingToolExecutor.js +11 -7
  164. package/dist/services/a2a.d.ts +8 -8
  165. package/dist/services/a2a.js +44 -34
  166. package/dist/services/agent-messaging.d.ts +33 -15
  167. package/dist/services/agent-messaging.js +65 -13
  168. package/dist/services/cron.js +16 -16
  169. package/dist/tools/AgentTool/index.d.ts +5 -2
  170. package/dist/tools/AgentTool/index.js +35 -15
  171. package/dist/tools/AskUserTool/index.js +1 -1
  172. package/dist/tools/BashTool/index.d.ts +2 -2
  173. package/dist/tools/BashTool/index.js +18 -10
  174. package/dist/tools/CronTool/index.d.ts +2 -2
  175. package/dist/tools/CronTool/index.js +30 -12
  176. package/dist/tools/DiagnosticsTool/index.js +28 -22
  177. package/dist/tools/EnterPlanModeTool/index.js +93 -14
  178. package/dist/tools/EnterWorktreeTool/index.js +7 -3
  179. package/dist/tools/ExitPlanModeTool/index.d.ts +22 -1
  180. package/dist/tools/ExitPlanModeTool/index.js +20 -5
  181. package/dist/tools/ExitWorktreeTool/index.js +11 -4
  182. package/dist/tools/FileEditTool/index.js +3 -5
  183. package/dist/tools/FileReadTool/index.js +16 -10
  184. package/dist/tools/FileWriteTool/index.js +2 -2
  185. package/dist/tools/GlobTool/index.js +5 -9
  186. package/dist/tools/GrepTool/index.d.ts +2 -2
  187. package/dist/tools/GrepTool/index.js +14 -9
  188. package/dist/tools/ImageReadTool/index.js +2 -2
  189. package/dist/tools/KillProcessTool/index.js +11 -7
  190. package/dist/tools/LSTool/index.js +3 -3
  191. package/dist/tools/MemoryTool/index.d.ts +11 -11
  192. package/dist/tools/MemoryTool/index.js +28 -14
  193. package/dist/tools/MonitorTool/index.d.ts +2 -2
  194. package/dist/tools/MonitorTool/index.js +24 -19
  195. package/dist/tools/MultiEditTool/index.js +9 -5
  196. package/dist/tools/NotebookEditTool/index.js +3 -3
  197. package/dist/tools/ParallelAgentTool/index.d.ts +4 -4
  198. package/dist/tools/ParallelAgentTool/index.js +12 -6
  199. package/dist/tools/PipelineTool/index.d.ts +4 -4
  200. package/dist/tools/PipelineTool/index.js +3 -3
  201. package/dist/tools/PowerShellTool/index.js +10 -6
  202. package/dist/tools/RemoteTriggerTool/index.js +8 -4
  203. package/dist/tools/ScheduleWakeupTool/index.d.ts +42 -0
  204. package/dist/tools/ScheduleWakeupTool/index.js +115 -0
  205. package/dist/tools/SendMessageTool/index.js +25 -7
  206. package/dist/tools/SessionSearchTool/index.d.ts +15 -0
  207. package/dist/tools/SessionSearchTool/index.js +36 -0
  208. package/dist/tools/SkillTool/index.d.ts +3 -0
  209. package/dist/tools/SkillTool/index.js +39 -9
  210. package/dist/tools/TaskCreateTool/index.d.ts +2 -2
  211. package/dist/tools/TaskCreateTool/index.js +2 -2
  212. package/dist/tools/TaskGetTool/index.js +2 -2
  213. package/dist/tools/TaskListTool/index.js +3 -5
  214. package/dist/tools/TaskOutputTool/index.js +2 -2
  215. package/dist/tools/TaskStopTool/index.js +3 -3
  216. package/dist/tools/TaskUpdateTool/index.d.ts +4 -4
  217. package/dist/tools/TaskUpdateTool/index.js +2 -2
  218. package/dist/tools/ToolSearchTool/index.js +9 -6
  219. package/dist/tools/WebFetchTool/index.js +1 -1
  220. package/dist/tools/WebSearchTool/index.js +2 -6
  221. package/dist/tools.js +31 -30
  222. package/dist/types/permissions.js +15 -9
  223. package/dist/utils/bash-safety.d.ts +1 -1
  224. package/dist/utils/bash-safety.js +64 -54
  225. package/dist/utils/diff-algorithm.d.ts +3 -3
  226. package/dist/utils/diff-algorithm.js +7 -7
  227. package/dist/utils/fs.js +3 -3
  228. package/dist/utils/safe-env.js +1 -1
  229. package/dist/utils/theme-data.d.ts +1 -1
  230. package/dist/utils/theme-data.js +1 -1
  231. package/dist/utils/theme.d.ts +1 -1
  232. package/dist/utils/theme.js +1 -1
  233. package/dist/utils/tool-summary.d.ts +1 -1
  234. package/dist/utils/tool-summary.js +27 -9
  235. package/package.json +10 -3
@@ -6,23 +6,23 @@
6
6
  * Not bulletproof (key derivation is deterministic from machine info),
7
7
  * but far better than plaintext YAML.
8
8
  */
9
- import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from 'node:crypto';
10
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
11
- import { join } from 'node:path';
12
- import { homedir, hostname, userInfo } from 'node:os';
13
- const CRED_DIR = join(homedir(), '.oh');
14
- const CRED_PATH = join(CRED_DIR, 'credentials.enc');
15
- const ALGORITHM = 'aes-256-gcm';
9
+ import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "node:crypto";
10
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { homedir, hostname, userInfo } from "node:os";
12
+ import { join } from "node:path";
13
+ const CRED_DIR = join(homedir(), ".oh");
14
+ const CRED_PATH = join(CRED_DIR, "credentials.enc");
15
+ const ALGORITHM = "aes-256-gcm";
16
16
  /** Derive an encryption key from machine identity */
17
17
  function deriveKey() {
18
18
  const identity = `${hostname()}-${userInfo().username}-openharness`;
19
- return scryptSync(identity, 'oh-credential-salt', 32);
19
+ return scryptSync(identity, "oh-credential-salt", 32);
20
20
  }
21
21
  function encrypt(data) {
22
22
  const key = deriveKey();
23
23
  const iv = randomBytes(12);
24
24
  const cipher = createCipheriv(ALGORITHM, key, iv);
25
- const encrypted = Buffer.concat([cipher.update(data, 'utf-8'), cipher.final()]);
25
+ const encrypted = Buffer.concat([cipher.update(data, "utf-8"), cipher.final()]);
26
26
  const tag = cipher.getAuthTag();
27
27
  // Format: [iv (12)] [tag (16)] [encrypted data]
28
28
  return Buffer.concat([iv, tag, encrypted]);
@@ -34,7 +34,7 @@ function decrypt(data) {
34
34
  const encrypted = data.subarray(28);
35
35
  const decipher = createDecipheriv(ALGORITHM, key, iv);
36
36
  decipher.setAuthTag(tag);
37
- return decipher.update(encrypted, undefined, 'utf-8') + decipher.final('utf-8');
37
+ return decipher.update(encrypted, undefined, "utf-8") + decipher.final("utf-8");
38
38
  }
39
39
  function loadStore() {
40
40
  if (!existsSync(CRED_PATH))
@@ -82,9 +82,9 @@ export function listCredentials() {
82
82
  export function resolveApiKey(provider, configApiKey) {
83
83
  // Environment variable names by provider
84
84
  const envVarMap = {
85
- anthropic: 'ANTHROPIC_API_KEY',
86
- openai: 'OPENAI_API_KEY',
87
- openrouter: 'OPENROUTER_API_KEY',
85
+ anthropic: "ANTHROPIC_API_KEY",
86
+ openai: "OPENAI_API_KEY",
87
+ openrouter: "OPENROUTER_API_KEY",
88
88
  };
89
89
  const envVar = envVarMap[provider];
90
90
  if (envVar && process.env[envVar])
@@ -103,14 +103,14 @@ async function runHttpHook(url, event, ctx, timeoutMs = 10_000) {
103
103
  try {
104
104
  const body = JSON.stringify({ event, ...ctx });
105
105
  const res = await fetch(url, {
106
- method: 'POST',
107
- headers: { 'Content-Type': 'application/json' },
106
+ method: "POST",
107
+ headers: { "Content-Type": "application/json" },
108
108
  body,
109
109
  signal: AbortSignal.timeout(timeoutMs),
110
110
  });
111
111
  if (!res.ok)
112
112
  return false;
113
- const data = await res.json();
113
+ const data = (await res.json());
114
114
  return data.allowed !== false;
115
115
  }
116
116
  catch {
@@ -118,7 +118,7 @@ async function runHttpHook(url, event, ctx, timeoutMs = 10_000) {
118
118
  }
119
119
  }
120
120
  /** Run a prompt hook. Uses LLM to make a yes/no decision. */
121
- async function runPromptHook(promptText, ctx) {
121
+ async function runPromptHook(_promptText, _ctx) {
122
122
  // Prompt hooks require a provider — skip if not available
123
123
  // This is a lightweight check; full LLM call would need provider injection
124
124
  // For now, prompt hooks evaluate the prompt text as a simple template
@@ -177,7 +177,9 @@ export function emitHook(event, ctx = {}) {
177
177
  for (const def of defs) {
178
178
  if (!matchesHook(def, ctx))
179
179
  continue;
180
- executeHookDef(def, event, ctx).catch(() => { });
180
+ executeHookDef(def, event, ctx).catch(() => {
181
+ /* fire-and-forget: non-preToolUse hooks must not block the agent loop */
182
+ });
181
183
  }
182
184
  return true;
183
185
  }
@@ -10,10 +10,10 @@
10
10
  *
11
11
  * Supports single keys and two-key chord sequences (e.g., "ctrl+k ctrl+d").
12
12
  */
13
- import { readFileSync, existsSync } from 'node:fs';
14
- import { join } from 'node:path';
15
- import { homedir } from 'node:os';
16
- const KEYBINDINGS_PATH = join(homedir(), '.oh', 'keybindings.json');
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { homedir } from "node:os";
15
+ import { join } from "node:path";
16
+ const KEYBINDINGS_PATH = join(homedir(), ".oh", "keybindings.json");
17
17
  let cachedBindings = null;
18
18
  /** Load keybindings from ~/.oh/keybindings.json */
19
19
  export function loadKeybindings() {
@@ -24,37 +24,39 @@ export function loadKeybindings() {
24
24
  return cachedBindings;
25
25
  }
26
26
  try {
27
- const raw = readFileSync(KEYBINDINGS_PATH, 'utf-8');
27
+ const raw = readFileSync(KEYBINDINGS_PATH, "utf-8");
28
28
  const parsed = JSON.parse(raw);
29
29
  if (Array.isArray(parsed)) {
30
30
  cachedBindings = parsed;
31
31
  return cachedBindings;
32
32
  }
33
33
  }
34
- catch { /* ignore parse errors */ }
34
+ catch {
35
+ /* ignore parse errors */
36
+ }
35
37
  cachedBindings = defaultKeybindings();
36
38
  return cachedBindings;
37
39
  }
38
40
  /** Default keybindings */
39
41
  function defaultKeybindings() {
40
42
  return [
41
- { key: 'ctrl+d', action: '/diff' },
42
- { key: 'ctrl+l', action: '/clear' },
43
- { key: 'ctrl+u', action: '/undo' },
44
- { key: 'ctrl+s', action: '/status' },
45
- { key: 'ctrl+k ctrl+c', action: '/cost' },
46
- { key: 'ctrl+k ctrl+f', action: '/fast' },
47
- { key: 'ctrl+k ctrl+l', action: '/log' },
43
+ { key: "ctrl+d", action: "/diff" },
44
+ { key: "ctrl+l", action: "/clear" },
45
+ { key: "ctrl+u", action: "/undo" },
46
+ { key: "ctrl+s", action: "/status" },
47
+ { key: "ctrl+k ctrl+c", action: "/cost" },
48
+ { key: "ctrl+k ctrl+f", action: "/fast" },
49
+ { key: "ctrl+k ctrl+l", action: "/log" },
48
50
  ];
49
51
  }
50
52
  /** Parse a key string like "ctrl+s" into components */
51
53
  function parseKeyString(keyStr) {
52
- const parts = keyStr.toLowerCase().split('+');
54
+ const parts = keyStr.toLowerCase().split("+");
53
55
  return {
54
- ctrl: parts.includes('ctrl'),
55
- alt: parts.includes('alt'),
56
- shift: parts.includes('shift'),
57
- key: parts.filter(p => p !== 'ctrl' && p !== 'alt' && p !== 'shift')[0] ?? '',
56
+ ctrl: parts.includes("ctrl"),
57
+ alt: parts.includes("alt"),
58
+ shift: parts.includes("shift"),
59
+ key: parts.filter((p) => p !== "ctrl" && p !== "alt" && p !== "shift")[0] ?? "",
58
60
  };
59
61
  }
60
62
  /** Check if an Ink key event matches a parsed key */
@@ -15,13 +15,13 @@ export type MarketplaceEntry = {
15
15
  keywords?: string[];
16
16
  };
17
17
  export type MarketplaceSource = {
18
- type: 'github';
18
+ type: "github";
19
19
  repo: string;
20
20
  } | {
21
- type: 'npm';
21
+ type: "npm";
22
22
  package: string;
23
23
  } | {
24
- type: 'url';
24
+ type: "url";
25
25
  url: string;
26
26
  };
27
27
  export type Marketplace = {
@@ -6,13 +6,13 @@
6
6
  *
7
7
  * Inspired by Claude Code's marketplace model.
8
8
  */
9
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, rmSync } from 'node:fs';
10
- import { join, basename } from 'node:path';
11
- import { homedir } from 'node:os';
12
- import { execSync } from 'node:child_process';
13
- const MARKETPLACE_DIR = join(homedir(), '.oh', 'marketplaces');
14
- const PLUGIN_CACHE_DIR = join(homedir(), '.oh', 'plugins', 'cache');
15
- const INSTALLED_PLUGINS_FILE = join(homedir(), '.oh', 'plugins', 'installed.json');
9
+ import { execSync } from "node:child_process";
10
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11
+ import { homedir } from "node:os";
12
+ import { basename, join } from "node:path";
13
+ const MARKETPLACE_DIR = join(homedir(), ".oh", "marketplaces");
14
+ const PLUGIN_CACHE_DIR = join(homedir(), ".oh", "plugins", "cache");
15
+ const INSTALLED_PLUGINS_FILE = join(homedir(), ".oh", "plugins", "installed.json");
16
16
  // ── Marketplace Management ──
17
17
  /** Add a marketplace from a URL, GitHub repo, or local path */
18
18
  export function addMarketplace(nameOrUrl) {
@@ -20,30 +20,30 @@ export function addMarketplace(nameOrUrl) {
20
20
  // Fetch marketplace.json
21
21
  let data;
22
22
  let marketplaceName;
23
- if (nameOrUrl.startsWith('http')) {
23
+ if (nameOrUrl.startsWith("http")) {
24
24
  // URL
25
25
  try {
26
- data = execSync(`curl -sL "${nameOrUrl}/marketplace.json"`, { encoding: 'utf-8', timeout: 10_000 });
26
+ data = execSync(`curl -sL "${nameOrUrl}/marketplace.json"`, { encoding: "utf-8", timeout: 10_000 });
27
27
  marketplaceName = new URL(nameOrUrl).hostname;
28
28
  }
29
29
  catch {
30
30
  return null;
31
31
  }
32
32
  }
33
- else if (nameOrUrl.includes('/') && !nameOrUrl.startsWith('.')) {
33
+ else if (nameOrUrl.includes("/") && !nameOrUrl.startsWith(".")) {
34
34
  // GitHub repo (owner/repo format)
35
35
  try {
36
36
  const url = `https://raw.githubusercontent.com/${nameOrUrl}/main/marketplace.json`;
37
- data = execSync(`curl -sL "${url}"`, { encoding: 'utf-8', timeout: 10_000 });
38
- marketplaceName = nameOrUrl.replace('/', '-');
37
+ data = execSync(`curl -sL "${url}"`, { encoding: "utf-8", timeout: 10_000 });
38
+ marketplaceName = nameOrUrl.replace("/", "-");
39
39
  }
40
40
  catch {
41
41
  return null;
42
42
  }
43
43
  }
44
- else if (existsSync(join(nameOrUrl, 'marketplace.json'))) {
44
+ else if (existsSync(join(nameOrUrl, "marketplace.json"))) {
45
45
  // Local path
46
- data = readFileSync(join(nameOrUrl, 'marketplace.json'), 'utf-8');
46
+ data = readFileSync(join(nameOrUrl, "marketplace.json"), "utf-8");
47
47
  marketplaceName = basename(nameOrUrl);
48
48
  }
49
49
  else {
@@ -79,10 +79,10 @@ export function listMarketplaces() {
79
79
  if (!existsSync(MARKETPLACE_DIR))
80
80
  return [];
81
81
  return readdirSync(MARKETPLACE_DIR)
82
- .filter(f => f.endsWith('.json'))
83
- .map(f => {
82
+ .filter((f) => f.endsWith(".json"))
83
+ .map((f) => {
84
84
  try {
85
- return JSON.parse(readFileSync(join(MARKETPLACE_DIR, f), 'utf-8'));
85
+ return JSON.parse(readFileSync(join(MARKETPLACE_DIR, f), "utf-8"));
86
86
  }
87
87
  catch {
88
88
  return null;
@@ -98,7 +98,7 @@ export function searchMarketplace(query) {
98
98
  for (const plugin of mp.plugins) {
99
99
  if (plugin.name.toLowerCase().includes(q) ||
100
100
  plugin.description.toLowerCase().includes(q) ||
101
- plugin.keywords?.some(k => k.toLowerCase().includes(q))) {
101
+ plugin.keywords?.some((k) => k.toLowerCase().includes(q))) {
102
102
  results.push({ ...plugin, marketplace: mp.name });
103
103
  }
104
104
  }
@@ -111,11 +111,11 @@ export function installPlugin(pluginName, marketplaceName) {
111
111
  // Find the plugin in marketplaces
112
112
  const marketplaces = listMarketplaces();
113
113
  let entry = null;
114
- let fromMarketplace = '';
114
+ let fromMarketplace = "";
115
115
  for (const mp of marketplaces) {
116
116
  if (marketplaceName && mp.name !== marketplaceName)
117
117
  continue;
118
- const found = mp.plugins.find(p => p.name === pluginName);
118
+ const found = mp.plugins.find((p) => p.name === pluginName);
119
119
  if (found) {
120
120
  entry = found;
121
121
  fromMarketplace = mp.name;
@@ -129,24 +129,33 @@ export function installPlugin(pluginName, marketplaceName) {
129
129
  mkdirSync(cacheDir, { recursive: true });
130
130
  try {
131
131
  switch (entry.source.type) {
132
- case 'github': {
132
+ case "github": {
133
133
  // Clone the repo to cache
134
- execSync(`git clone --depth 1 "https://github.com/${entry.source.repo}.git" "${cacheDir}"`, { stdio: 'pipe', timeout: 30_000 });
134
+ execSync(`git clone --depth 1 "https://github.com/${entry.source.repo}.git" "${cacheDir}"`, {
135
+ stdio: "pipe",
136
+ timeout: 30_000,
137
+ });
135
138
  break;
136
139
  }
137
- case 'npm': {
140
+ case "npm": {
138
141
  // Install npm package to cache
139
- execSync(`npm pack "${entry.source.package}" --pack-destination "${cacheDir}"`, { stdio: 'pipe', timeout: 30_000 });
142
+ execSync(`npm pack "${entry.source.package}" --pack-destination "${cacheDir}"`, {
143
+ stdio: "pipe",
144
+ timeout: 30_000,
145
+ });
140
146
  // Extract the tarball
141
- const tgz = readdirSync(cacheDir).find(f => f.endsWith('.tgz'));
147
+ const tgz = readdirSync(cacheDir).find((f) => f.endsWith(".tgz"));
142
148
  if (tgz) {
143
- execSync(`tar xzf "${join(cacheDir, tgz)}" -C "${cacheDir}" --strip-components=1`, { stdio: 'pipe' });
149
+ execSync(`tar xzf "${join(cacheDir, tgz)}" -C "${cacheDir}" --strip-components=1`, { stdio: "pipe" });
144
150
  }
145
151
  break;
146
152
  }
147
- case 'url': {
148
- execSync(`curl -sL "${entry.source.url}" -o "${join(cacheDir, 'plugin.tar.gz')}"`, { stdio: 'pipe', timeout: 30_000 });
149
- execSync(`tar xzf "${join(cacheDir, 'plugin.tar.gz')}" -C "${cacheDir}"`, { stdio: 'pipe' });
153
+ case "url": {
154
+ execSync(`curl -sL "${entry.source.url}" -o "${join(cacheDir, "plugin.tar.gz")}"`, {
155
+ stdio: "pipe",
156
+ timeout: 30_000,
157
+ });
158
+ execSync(`tar xzf "${join(cacheDir, "plugin.tar.gz")}" -C "${cacheDir}"`, { stdio: "pipe" });
150
159
  break;
151
160
  }
152
161
  }
@@ -156,7 +165,9 @@ export function installPlugin(pluginName, marketplaceName) {
156
165
  try {
157
166
  rmSync(cacheDir, { recursive: true });
158
167
  }
159
- catch { /* ignore */ }
168
+ catch {
169
+ /* ignore */
170
+ }
160
171
  return null;
161
172
  }
162
173
  // Record installation
@@ -173,16 +184,18 @@ export function installPlugin(pluginName, marketplaceName) {
173
184
  /** Uninstall a plugin */
174
185
  export function uninstallPlugin(name) {
175
186
  const installed = getInstalledPlugins();
176
- const plugin = installed.find(p => p.name === name);
187
+ const plugin = installed.find((p) => p.name === name);
177
188
  if (!plugin)
178
189
  return false;
179
190
  // Remove from cache
180
191
  try {
181
192
  rmSync(plugin.cachePath, { recursive: true });
182
193
  }
183
- catch { /* ignore */ }
194
+ catch {
195
+ /* ignore */
196
+ }
184
197
  // Remove from installed list
185
- const remaining = installed.filter(p => p.name !== name);
198
+ const remaining = installed.filter((p) => p.name !== name);
186
199
  saveInstalledPluginList(remaining);
187
200
  return true;
188
201
  }
@@ -191,7 +204,7 @@ export function getInstalledPlugins() {
191
204
  if (!existsSync(INSTALLED_PLUGINS_FILE))
192
205
  return [];
193
206
  try {
194
- return JSON.parse(readFileSync(INSTALLED_PLUGINS_FILE, 'utf-8'));
207
+ return JSON.parse(readFileSync(INSTALLED_PLUGINS_FILE, "utf-8"));
195
208
  }
196
209
  catch {
197
210
  return [];
@@ -200,7 +213,7 @@ export function getInstalledPlugins() {
200
213
  function saveInstalledPlugin(plugin) {
201
214
  const installed = getInstalledPlugins();
202
215
  // Replace existing version
203
- const idx = installed.findIndex(p => p.name === plugin.name);
216
+ const idx = installed.findIndex((p) => p.name === plugin.name);
204
217
  if (idx >= 0)
205
218
  installed[idx] = plugin;
206
219
  else
@@ -208,7 +221,7 @@ function saveInstalledPlugin(plugin) {
208
221
  saveInstalledPluginList(installed);
209
222
  }
210
223
  function saveInstalledPluginList(plugins) {
211
- const dir = join(homedir(), '.oh', 'plugins');
224
+ const dir = join(homedir(), ".oh", "plugins");
212
225
  mkdirSync(dir, { recursive: true });
213
226
  writeFileSync(INSTALLED_PLUGINS_FILE, JSON.stringify(plugins, null, 2));
214
227
  }
@@ -216,27 +229,27 @@ function saveInstalledPluginList(plugins) {
216
229
  /** Format marketplace entries for display */
217
230
  export function formatMarketplaceSearch(results) {
218
231
  if (results.length === 0)
219
- return 'No plugins found.';
232
+ return "No plugins found.";
220
233
  const lines = [`Found ${results.length} plugin(s):\n`];
221
234
  for (const r of results) {
222
235
  lines.push(` ${r.name}@${r.version} [${r.marketplace}]`);
223
236
  lines.push(` ${r.description}`);
224
237
  if (r.author)
225
238
  lines.push(` by ${r.author}`);
226
- lines.push('');
239
+ lines.push("");
227
240
  }
228
- lines.push('Install with: /plugin install <name>');
229
- return lines.join('\n');
241
+ lines.push("Install with: /plugin install <name>");
242
+ return lines.join("\n");
230
243
  }
231
244
  /** Format installed plugins for display */
232
245
  export function formatInstalledPlugins(plugins) {
233
246
  if (plugins.length === 0)
234
- return 'No plugins installed from marketplaces.';
247
+ return "No plugins installed from marketplaces.";
235
248
  const lines = [`Installed Plugins (${plugins.length}):\n`];
236
249
  for (const p of plugins) {
237
250
  const age = Math.round((Date.now() - p.installedAt) / (1000 * 60 * 60 * 24));
238
251
  lines.push(` ${p.name}@${p.version} [${p.marketplace}] ${age}d ago`);
239
252
  }
240
- return lines.join('\n');
253
+ return lines.join("\n");
241
254
  }
242
255
  //# sourceMappingURL=marketplace.js.map
@@ -7,11 +7,17 @@
7
7
  * The system detects learnable patterns from assistant responses and saves them
8
8
  * without user intervention.
9
9
  */
10
- import type { Message } from '../types/message.js';
11
- import type { Provider } from '../providers/base.js';
10
+ import type { Provider } from "../providers/base.js";
11
+ import type { Message } from "../types/message.js";
12
+ /**
13
+ * Memory types — supports both legacy and Claude Code-compatible names.
14
+ * Legacy: convention, preference, project, debugging
15
+ * New: user, feedback, project, reference
16
+ */
17
+ export type MemoryType = "convention" | "preference" | "project" | "debugging" | "user" | "feedback" | "reference";
12
18
  export type MemoryEntry = {
13
19
  name: string;
14
- type: 'convention' | 'preference' | 'project' | 'debugging';
20
+ type: MemoryType;
15
21
  description: string;
16
22
  content: string;
17
23
  filePath: string;
@@ -25,7 +31,13 @@ export declare function loadMemories(): MemoryEntry[];
25
31
  /** Build a system prompt section from loaded memories */
26
32
  export declare function memoriesToPrompt(memories: MemoryEntry[]): string;
27
33
  /** Save a memory entry to the project memory directory */
28
- export declare function saveMemory(name: string, type: MemoryEntry['type'], description: string, content: string, global?: boolean): string;
34
+ export declare function saveMemory(name: string, type: MemoryType, description: string, content: string, global?: boolean): string;
35
+ /**
36
+ * Update or create MEMORY.md index file in the given memory directory.
37
+ * The index is always loaded into context, providing instant awareness of all stored memories.
38
+ * Each entry is a one-liner pointer to the individual memory file (~200 line cap).
39
+ */
40
+ export declare function updateMemoryIndex(dir?: string): void;
29
41
  /** Mark a memory as accessed — updates lastAccessed and accessCount in the file */
30
42
  export declare function touchMemory(entry: MemoryEntry): void;
31
43
  /** Boost a memory's relevance score (capped at 1.0) */
@@ -53,13 +65,19 @@ export type ConsolidationResult = {
53
65
  * persist updated relevance scores. Designed to run on session end.
54
66
  */
55
67
  export declare function consolidateMemories(): ConsolidationResult;
68
+ /** Load the user profile from .oh/memory/USER.md */
69
+ export declare function loadUserProfile(): string;
70
+ /** Update the user profile, truncating to max chars */
71
+ export declare function updateUserProfile(content: string): void;
72
+ /** Format user profile for system prompt injection */
73
+ export declare function userProfileToPrompt(): string;
56
74
  /**
57
75
  * Detect if recent assistant messages contain learnable patterns.
58
76
  * Returns structured memories to save, or empty array.
59
77
  */
60
78
  export declare function detectMemories(provider: Provider, recentMessages: Message[], model?: string): Promise<Array<{
61
79
  name: string;
62
- type: MemoryEntry['type'];
80
+ type: MemoryEntry["type"];
63
81
  description: string;
64
82
  content: string;
65
83
  }>>;