memhook 0.3.0 → 0.4.1

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 (89) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +30 -7
  3. package/dist/bin/memhook.d.ts +0 -1
  4. package/dist/bin/memhook.js +52 -2
  5. package/dist/src/ansi.d.ts +0 -1
  6. package/dist/src/ansi.js +0 -1
  7. package/dist/src/backup.d.ts +9 -0
  8. package/dist/src/backup.js +16 -0
  9. package/dist/src/cache.d.ts +0 -1
  10. package/dist/src/cache.js +0 -1
  11. package/dist/src/catalog.d.ts +0 -1
  12. package/dist/src/catalog.js +0 -1
  13. package/dist/src/config.d.ts +11 -1
  14. package/dist/src/config.js +6 -1
  15. package/dist/src/configFile.d.ts +6 -1
  16. package/dist/src/configFile.js +0 -1
  17. package/dist/src/index.d.ts +2 -1
  18. package/dist/src/index.js +7 -1
  19. package/dist/src/init.d.ts +3 -3
  20. package/dist/src/init.js +54 -11
  21. package/dist/src/install.d.ts +0 -1
  22. package/dist/src/install.js +0 -1
  23. package/dist/src/preFilter.d.ts +0 -1
  24. package/dist/src/preFilter.js +0 -1
  25. package/dist/src/providers/anthropic.d.ts +0 -1
  26. package/dist/src/providers/anthropic.js +0 -1
  27. package/dist/src/providers/factory.d.ts +0 -1
  28. package/dist/src/providers/factory.js +0 -1
  29. package/dist/src/providers/http.d.ts +0 -1
  30. package/dist/src/providers/http.js +0 -1
  31. package/dist/src/providers/ollama.d.ts +0 -1
  32. package/dist/src/providers/ollama.js +0 -1
  33. package/dist/src/providers/openai.d.ts +0 -1
  34. package/dist/src/providers/openai.js +0 -1
  35. package/dist/src/providers/types.d.ts +0 -1
  36. package/dist/src/providers/types.js +0 -1
  37. package/dist/src/router.d.ts +22 -1
  38. package/dist/src/router.js +105 -12
  39. package/dist/src/skills.d.ts +67 -0
  40. package/dist/src/skills.js +72 -0
  41. package/dist/src/skillsCmd.d.ts +50 -0
  42. package/dist/src/skillsCmd.js +272 -0
  43. package/dist/src/tail.d.ts +0 -1
  44. package/dist/src/tail.js +7 -4
  45. package/dist/src/version.d.ts +1 -2
  46. package/dist/src/version.js +1 -2
  47. package/package.json +5 -2
  48. package/skills/curate/SKILL.md +181 -0
  49. package/skills/curate/reference.md +105 -0
  50. package/skills/relay/SKILL.md +162 -0
  51. package/skills/wrap/SKILL.md +173 -0
  52. package/dist/bin/memhook.d.ts.map +0 -1
  53. package/dist/bin/memhook.js.map +0 -1
  54. package/dist/src/ansi.d.ts.map +0 -1
  55. package/dist/src/ansi.js.map +0 -1
  56. package/dist/src/cache.d.ts.map +0 -1
  57. package/dist/src/cache.js.map +0 -1
  58. package/dist/src/catalog.d.ts.map +0 -1
  59. package/dist/src/catalog.js.map +0 -1
  60. package/dist/src/config.d.ts.map +0 -1
  61. package/dist/src/config.js.map +0 -1
  62. package/dist/src/configFile.d.ts.map +0 -1
  63. package/dist/src/configFile.js.map +0 -1
  64. package/dist/src/index.d.ts.map +0 -1
  65. package/dist/src/index.js.map +0 -1
  66. package/dist/src/init.d.ts.map +0 -1
  67. package/dist/src/init.js.map +0 -1
  68. package/dist/src/install.d.ts.map +0 -1
  69. package/dist/src/install.js.map +0 -1
  70. package/dist/src/preFilter.d.ts.map +0 -1
  71. package/dist/src/preFilter.js.map +0 -1
  72. package/dist/src/providers/anthropic.d.ts.map +0 -1
  73. package/dist/src/providers/anthropic.js.map +0 -1
  74. package/dist/src/providers/factory.d.ts.map +0 -1
  75. package/dist/src/providers/factory.js.map +0 -1
  76. package/dist/src/providers/http.d.ts.map +0 -1
  77. package/dist/src/providers/http.js.map +0 -1
  78. package/dist/src/providers/ollama.d.ts.map +0 -1
  79. package/dist/src/providers/ollama.js.map +0 -1
  80. package/dist/src/providers/openai.d.ts.map +0 -1
  81. package/dist/src/providers/openai.js.map +0 -1
  82. package/dist/src/providers/types.d.ts.map +0 -1
  83. package/dist/src/providers/types.js.map +0 -1
  84. package/dist/src/router.d.ts.map +0 -1
  85. package/dist/src/router.js.map +0 -1
  86. package/dist/src/tail.d.ts.map +0 -1
  87. package/dist/src/tail.js.map +0 -1
  88. package/dist/src/version.d.ts.map +0 -1
  89. package/dist/src/version.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,20 @@ All notable changes to memhook are documented here.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.4.1](https://github.com/utilia-ai-wox/memhook/compare/v0.4.0...v0.4.1) (2026-06-02)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * harden parsing, packaging, and CI; expand test coverage ([#34](https://github.com/utilia-ai-wox/memhook/issues/34)) ([9c4fd7f](https://github.com/utilia-ai-wox/memhook/commit/9c4fd7f463504d25eb669d26528228e92cd8a4a9))
14
+
15
+ ## [0.4.0](https://github.com/utilia-ai-wox/memhook/compare/v0.3.0...v0.4.0) (2026-06-02)
16
+
17
+
18
+ ### Features
19
+
20
+ * companion skills (/wrap /curate /relay) + installer + nudge ([#32](https://github.com/utilia-ai-wox/memhook/issues/32)) ([3ede75d](https://github.com/utilia-ai-wox/memhook/commit/3ede75dd94cd5383a54dd0540b34b308e649a696))
21
+
8
22
  ## [0.3.0](https://github.com/utilia-ai-wox/memhook/compare/v0.2.2...v0.3.0) (2026-06-02)
9
23
 
10
24
 
package/README.md CHANGED
@@ -19,10 +19,6 @@ files for each prompt and injects them as `additionalContext`.
19
19
 
20
20
  </div>
21
21
 
22
- <!-- TODO(demo): record an asciinema of `tail -f ~/.claude/logs/memhook.log`
23
- while prompting Claude Code — it shows the router picking files live.
24
- Embed: [![asciicast](https://asciinema.org/a/XXXXX.svg)](https://asciinema.org/a/XXXXX) -->
25
-
26
22
  ## ✨ Features
27
23
 
28
24
  - 🎯 **Relevant-only injection** — a cheap model picks the 0–5 memory files that matter for _this_ prompt.
@@ -33,6 +29,7 @@ files for each prompt and injects them as `additionalContext`.
33
29
  - 🪶 **One dependency** — `yaml`, with zero sub-deps.
34
30
  - ⚡ **Cached & pre-filtered** — an LRU cache + a trivial-prompt skip keep latency near zero.
35
31
  - 🧰 **One-command setup** — `memhook init` wires the hooks (with backup); `memhook tail` shows routing live.
32
+ - 🧩 **Companion skills** — optional `/wrap`, `/curate`, `/relay` to capture, tidy, and hand off your memory.
36
33
 
37
34
  ## 🤔 Why
38
35
 
@@ -237,6 +234,32 @@ jq -c 'select((.ts | fromdateiso8601) > (now - 7*86400)) | .status' \
237
234
  | `api_no_content` | API returned 200 but no text |
238
235
  | `parse_invalid` | Response wasn't a valid JSON array |
239
236
 
237
+ ## 🧩 Companion skills
238
+
239
+ Routing only works well when your memory stays healthy. memhook ships three
240
+ optional Claude Code skills for that — install them with one command:
241
+
242
+ ```bash
243
+ memhook skills install # copy them into ~/.claude/skills
244
+ memhook skills list # show install status
245
+ memhook skills uninstall # remove them (backs up any edits first)
246
+ ```
247
+
248
+ `memhook init` also offers to install them.
249
+
250
+ | Skill | What it does |
251
+ | --------- | ---------------------------------------------------------------------------------------------------------------------------- |
252
+ | `/wrap` | End-of-session wrap-up — captures the session's lessons into memory + a dated journal entry. Proposes; never writes unasked. |
253
+ | `/curate` | Memory hygiene — dedupes, fixes the `MEMORY.md` index, splits oversized files, then rebuilds the catalog. |
254
+ | `/relay` | Generates a self-contained prompt to resume work in a fresh session. Read-only. |
255
+
256
+ They're standalone skills, so you invoke them directly as `/wrap`, `/curate`,
257
+ `/relay`. They are user-invoked only — Claude won't trigger them on its own.
258
+
259
+ When your catalog grows large, memhook also adds a one-line reminder to run
260
+ `/curate` (a local-only `systemMessage`, 7-day cooldown — toggle with
261
+ `MEMHOOK_CURATE_NUDGE`).
262
+
240
263
  ## 🛡️ Fail-soft
241
264
 
242
265
  memhook never blocks Claude Code. On any error — missing key, network
@@ -247,9 +270,9 @@ model**, just without injected memories for that turn.
247
270
  ## 🗺️ Roadmap
248
271
 
249
272
  - `v0.2` ✅ — YAML config file, OpenAI provider, Ollama local provider (published on npm)
250
- - `v0.3` — `memhook init` / `memhook uninstall` setup wizard + zero-dep live monitor (`memhook tail`)
251
- - `v0.4` — Companion skills (`/wrap`, `/curate`, `/relay`)
252
- - `v1.0` — API frozen, cross-platform validated, listed on awesome-lists
273
+ - `v0.3` — `memhook init` / `memhook uninstall` setup wizard + zero-dep live monitor (`memhook tail`)
274
+ - `v0.4` — Companion skills (`/wrap`, `/curate`, `/relay`) + `memhook skills` installer + `/curate` nudge
275
+ - `v1.0` — API frozen, cross-platform validated, polished docs
253
276
 
254
277
  ## 🤝 Contributing
255
278
 
@@ -18,4 +18,3 @@
18
18
  * "SessionStart": [{ "hooks": [{ "type": "command", "command": "memhook build-catalog" }] }]
19
19
  */
20
20
  export {};
21
- //# sourceMappingURL=memhook.d.ts.map
@@ -22,8 +22,11 @@ import { buildCatalog } from "../src/catalog.js";
22
22
  import { loadConfig } from "../src/config.js";
23
23
  import { runInit, runUninstall } from "../src/init.js";
24
24
  import { runTail } from "../src/tail.js";
25
+ import { runSkills } from "../src/skillsCmd.js";
26
+ import { isCompanionSkill } from "../src/skills.js";
25
27
  import { MEMHOOK_VERSION as VERSION } from "../src/version.js";
26
28
  const PROVIDERS = ["anthropic", "openai", "ollama"];
29
+ const SKILLS_SUBCOMMANDS = ["install", "uninstall", "list"];
27
30
  async function main() {
28
31
  const cmd = process.argv[2] ?? "help";
29
32
  const args = process.argv.slice(3);
@@ -43,6 +46,9 @@ async function main() {
43
46
  case "tail":
44
47
  process.exitCode = await cmdTail(args);
45
48
  break;
49
+ case "skills":
50
+ process.exitCode = await cmdSkills(args);
51
+ break;
46
52
  case "version":
47
53
  case "--version":
48
54
  case "-v":
@@ -98,6 +104,11 @@ async function cmdInit(args) {
98
104
  }
99
105
  provider = flags["provider"];
100
106
  }
107
+ let skills;
108
+ if (flags["no-skills"] === true)
109
+ skills = false;
110
+ else if (flags["skills"] === true)
111
+ skills = true;
101
112
  return runInit({
102
113
  yes: flags["yes"] === true,
103
114
  dryRun: flags["dry-run"] === true,
@@ -107,6 +118,30 @@ async function cmdInit(args) {
107
118
  bin: strFlag(flags["bin"]) ?? "memhook",
108
119
  settingsPath: strFlag(flags["settings"]),
109
120
  noCatalog: flags["no-catalog"] === true,
121
+ skills,
122
+ });
123
+ }
124
+ async function cmdSkills(args) {
125
+ const { flags, positionals } = parseArgs(args, BOOL_SKILLS);
126
+ const sub = positionals[0] ?? "list";
127
+ if (!SKILLS_SUBCOMMANDS.includes(sub)) {
128
+ process.stderr.write(`memhook skills: unknown subcommand "${sub}" (install | uninstall | list)\n`);
129
+ return 1;
130
+ }
131
+ const names = [];
132
+ for (const n of positionals.slice(1)) {
133
+ if (!isCompanionSkill(n)) {
134
+ process.stderr.write(`memhook skills: unknown skill "${n}" (wrap | curate | relay)\n`);
135
+ return 1;
136
+ }
137
+ names.push(n);
138
+ }
139
+ return runSkills({
140
+ subcommand: sub,
141
+ names: names.length > 0 ? names : undefined,
142
+ yes: flags["yes"] === true,
143
+ dryRun: flags["dry-run"] === true,
144
+ force: flags["force"] === true,
110
145
  });
111
146
  }
112
147
  async function cmdUninstall(args) {
@@ -137,9 +172,10 @@ async function cmdTail(args) {
137
172
  });
138
173
  }
139
174
  // ── tiny flag parser ─────────────────────────────────────────────────────────
140
- const BOOL_INIT = new Set(["yes", "dry-run", "no-catalog"]);
175
+ const BOOL_INIT = new Set(["yes", "dry-run", "no-catalog", "skills", "no-skills"]);
141
176
  const BOOL_UNINSTALL = new Set(["yes", "dry-run", "purge"]);
142
177
  const BOOL_TAIL = new Set(["no-follow"]);
178
+ const BOOL_SKILLS = new Set(["yes", "dry-run", "force"]);
143
179
  const SHORT = { "-y": "--yes", "-n": "--lines" };
144
180
  function strFlag(v) {
145
181
  return typeof v === "string" ? v : undefined;
@@ -203,6 +239,7 @@ COMMANDS
203
239
  init Wire memhook into ~/.claude/settings.json (with backup)
204
240
  uninstall Remove memhook's hooks from ~/.claude/settings.json
205
241
  tail Pretty live view of the routing log (status, latency, memories)
242
+ skills Install/uninstall/list companion skills (/wrap /curate /relay)
206
243
  version Print version
207
244
  help Show this message
208
245
 
@@ -213,6 +250,8 @@ init OPTIONS
213
250
  --bin <name> command written into settings.json (default: memhook)
214
251
  --settings <path> settings file to patch (default: ~/.claude/settings.json)
215
252
  --no-catalog skip the initial catalog build
253
+ --skills install companion skills (default: ask / yes in --yes mode)
254
+ --no-skills skip installing companion skills
216
255
  --dry-run print the plan, write nothing
217
256
  -y, --yes non-interactive (accept defaults / flags)
218
257
 
@@ -228,6 +267,14 @@ tail OPTIONS
228
267
  --status <a,b> only show these statuses (e.g. ok,cache_hit)
229
268
  --file <path> log file to read (default: $MEMHOOK_LOG_PATH)
230
269
 
270
+ skills SUBCOMMANDS
271
+ install [names…] copy /wrap /curate /relay into ~/.claude/skills (default: all)
272
+ uninstall [names…] remove the bundled companion skills (backs up first)
273
+ list show each skill's install status
274
+ --force overwrite a skill that differs from shipped (backs up first)
275
+ --dry-run print the plan, write nothing
276
+ -y, --yes non-interactive (accept defaults)
277
+
231
278
  ENV VARS
232
279
  MEMHOOK_ENABLED toggle (default: true)
233
280
  MEMHOOK_PROVIDER anthropic | openai | ollama (default: anthropic)
@@ -241,6 +288,10 @@ ENV VARS
241
288
  MEMHOOK_TIMEOUT_MS request timeout (default: 8000; ollama: 30000)
242
289
  MEMHOOK_DISABLE_CACHE=true skip local LRU cache
243
290
  MEMHOOK_DISABLE_PREFILTER=true skip trivial-prompt skip
291
+ MEMHOOK_CURATE_NUDGE /curate-nudge toggle (default: true)
292
+ MEMHOOK_CURATE_NUDGE_TOKENS catalog-token threshold to nudge (default: 15000)
293
+ MEMHOOK_CURATE_NUDGE_FILES memory-file-count threshold to nudge (default: 250)
294
+ MEMHOOK_CURATE_NUDGE_COOLDOWN_DAYS min days between nudges (default: 7)
244
295
  NO_COLOR / MEMHOOK_NO_COLOR disable colour in init/tail output
245
296
  MEMHOOK_DEBUG=true print errors to stderr (default: silent fail-soft)
246
297
 
@@ -255,4 +306,3 @@ main().catch((err) => {
255
306
  process.stderr.write(`memhook fatal: ${String(err)}\n`);
256
307
  process.exit(1);
257
308
  });
258
- //# sourceMappingURL=memhook.js.map
@@ -68,4 +68,3 @@ export declare function padEnd(s: string, width: number): string;
68
68
  /** Truncate to `max` chars, appending `…` when cut. Callers pass plain text. */
69
69
  export declare function truncate(s: string, max: number): string;
70
70
  export {};
71
- //# sourceMappingURL=ansi.d.ts.map
package/dist/src/ansi.js CHANGED
@@ -97,4 +97,3 @@ export function truncate(s, max) {
97
97
  return s.slice(0, max);
98
98
  return s.slice(0, max - 1) + "…";
99
99
  }
100
- //# sourceMappingURL=ansi.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Tiny shared backup helpers for the interactive commands (init / uninstall /
3
+ * skills). Extracted so both `init.ts` and `skillsCmd.ts` can use them without
4
+ * importing each other (which would create a module cycle).
5
+ */
6
+ /** A backup path next to `path`, stamped so successive runs never collide. */
7
+ export declare function backupPath(path: string, stamp: string): string;
8
+ /** A filesystem-safe ISO-ish timestamp for backup filenames. */
9
+ export declare function stampNow(): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Tiny shared backup helpers for the interactive commands (init / uninstall /
3
+ * skills). Extracted so both `init.ts` and `skillsCmd.ts` can use them without
4
+ * importing each other (which would create a module cycle).
5
+ */
6
+ /** A backup path next to `path`, stamped so successive runs never collide. */
7
+ export function backupPath(path, stamp) {
8
+ return `${path}.bak-${stamp}`;
9
+ }
10
+ /** A filesystem-safe ISO-ish timestamp for backup filenames. */
11
+ export function stampNow() {
12
+ return new Date()
13
+ .toISOString()
14
+ .replace(/[:.]/g, "-")
15
+ .replace(/-(\d{3})Z$/, "Z");
16
+ }
@@ -27,4 +27,3 @@ export declare class LocalCache {
27
27
  put(key: string, value: string): void;
28
28
  evictStale(): number;
29
29
  }
30
- //# sourceMappingURL=cache.d.ts.map
package/dist/src/cache.js CHANGED
@@ -84,4 +84,3 @@ export class LocalCache {
84
84
  return removed;
85
85
  }
86
86
  }
87
- //# sourceMappingURL=cache.js.map
@@ -17,4 +17,3 @@ export declare function buildCatalog(opts: CatalogBuildOptions): {
17
17
  lines: number;
18
18
  bytes: number;
19
19
  };
20
- //# sourceMappingURL=catalog.d.ts.map
@@ -149,4 +149,3 @@ function extractDescription(file) {
149
149
  return h1[1].slice(0, 200).replace(/\s+/g, " ");
150
150
  return "";
151
151
  }
152
- //# sourceMappingURL=catalog.js.map
@@ -54,7 +54,17 @@ export interface MemhookConfig {
54
54
  logging: {
55
55
  jsonlPath: string;
56
56
  };
57
+ /**
58
+ * Optional proactive nudge: when the memory catalog grows past a threshold,
59
+ * the router attaches a one-line `systemMessage` suggesting `/curate`. This
60
+ * is local-only (no outbound call) and best-effort (never affects fail-soft).
61
+ */
62
+ curateNudge: {
63
+ enabled: boolean;
64
+ thresholdTokens: number;
65
+ thresholdFiles: number;
66
+ cooldownDays: number;
67
+ };
57
68
  scriptVersion: string;
58
69
  }
59
70
  export declare function loadConfig(env?: NodeJS.ProcessEnv): MemhookConfig;
60
- //# sourceMappingURL=config.d.ts.map
@@ -166,7 +166,12 @@ export function loadConfig(env = process.env) {
166
166
  logging: {
167
167
  jsonlPath: str("MEMHOOK_LOG_PATH", yaml?.logging?.jsonlPath, join(home, ".claude", "logs", "memhook.log")),
168
168
  },
169
+ curateNudge: {
170
+ enabled: bool("MEMHOOK_CURATE_NUDGE", yaml?.curateNudge?.enabled, true),
171
+ thresholdTokens: num("MEMHOOK_CURATE_NUDGE_TOKENS", yaml?.curateNudge?.thresholdTokens, 15000),
172
+ thresholdFiles: num("MEMHOOK_CURATE_NUDGE_FILES", yaml?.curateNudge?.thresholdFiles, 250),
173
+ cooldownDays: num("MEMHOOK_CURATE_NUDGE_COOLDOWN_DAYS", yaml?.curateNudge?.cooldownDays, 7),
174
+ },
169
175
  scriptVersion: MEMHOOK_VERSION,
170
176
  };
171
177
  }
172
- //# sourceMappingURL=config.js.map
@@ -48,7 +48,12 @@ export interface RawConfigFile {
48
48
  logging?: {
49
49
  jsonlPath?: string;
50
50
  };
51
+ curateNudge?: {
52
+ enabled?: boolean;
53
+ thresholdTokens?: number;
54
+ thresholdFiles?: number;
55
+ cooldownDays?: number;
56
+ };
51
57
  }
52
58
  export declare function resolveConfigPath(env: NodeJS.ProcessEnv): string;
53
59
  export declare function loadYamlConfig(env: NodeJS.ProcessEnv): RawConfigFile | null;
54
- //# sourceMappingURL=configFile.d.ts.map
@@ -48,4 +48,3 @@ export function loadYamlConfig(env) {
48
48
  return null;
49
49
  return parsed;
50
50
  }
51
- //# sourceMappingURL=configFile.js.map
@@ -15,10 +15,11 @@ export { MEMHOOK_VERSION } from "./version.js";
15
15
  export { createProvider } from "./providers/factory.js";
16
16
  export { addHooks, removeHooks, memhookSubcommand, MEMHOOK_HOOKS, type Settings, type HookEvent, type AddResult, type RemoveResult, } from "./install.js";
17
17
  export { runInit, runUninstall, buildConfigObject, backupPath, type InitOptions, type UninstallOptions, } from "./init.js";
18
+ export { COMPANION_SKILLS, SKILL_FILES, isCompanionSkill, diffSkill, planInstall, planUninstall, type CompanionSkill, type SkillSources, type InstalledFiles, type SkillStatus, type InstallAction, type SkillInstallPlan, type SkillUninstallPlan, } from "./skills.js";
19
+ export { runSkills, installCompanionSkills, bundledSkillsDir, type RunSkillsOptions, type SkillsSubcommand, type InstallSkillsOptions, type SkillInstallResult, } from "./skillsCmd.js";
18
20
  export { runTail, parseLogLine, formatRow, formatHeader, formatFooter, summarize, emptyStats, accumulate, tailLines, type LogRow, type Stats, type TailOptions, } from "./tail.js";
19
21
  export { makeAnsi, colorEnabled, visibleWidth, type Ansi, type AnsiOptions } from "./ansi.js";
20
22
  export { AnthropicProvider, type AnthropicProviderOptions } from "./providers/anthropic.js";
21
23
  export { OpenAIProvider } from "./providers/openai.js";
22
24
  export { OllamaProvider } from "./providers/ollama.js";
23
25
  export type { Provider, ProviderConfig, SelectionRequest, SelectionResponse, UsageBreakdown, } from "./providers/types.js";
24
- //# sourceMappingURL=index.d.ts.map
package/dist/src/index.js CHANGED
@@ -13,11 +13,17 @@ export { LocalCache } from "./cache.js";
13
13
  export { PreFilter } from "./preFilter.js";
14
14
  export { MEMHOOK_VERSION } from "./version.js";
15
15
  export { createProvider } from "./providers/factory.js";
16
+ // ── Internal building blocks (NOT part of the semver-stable surface) ─────────
17
+ // The install/init/skills/tail/ansi re-exports below back the CLI and tests.
18
+ // They are exposed for power users but may change between 0.x minor releases;
19
+ // the stable embedding API is the core above (route, loadConfig, buildCatalog,
20
+ // LocalCache, PreFilter, MEMHOOK_VERSION) plus the provider exports.
16
21
  export { addHooks, removeHooks, memhookSubcommand, MEMHOOK_HOOKS, } from "./install.js";
17
22
  export { runInit, runUninstall, buildConfigObject, backupPath, } from "./init.js";
23
+ export { COMPANION_SKILLS, SKILL_FILES, isCompanionSkill, diffSkill, planInstall, planUninstall, } from "./skills.js";
24
+ export { runSkills, installCompanionSkills, bundledSkillsDir, } from "./skillsCmd.js";
18
25
  export { runTail, parseLogLine, formatRow, formatHeader, formatFooter, summarize, emptyStats, accumulate, tailLines, } from "./tail.js";
19
26
  export { makeAnsi, colorEnabled, visibleWidth } from "./ansi.js";
20
27
  export { AnthropicProvider } from "./providers/anthropic.js";
21
28
  export { OpenAIProvider } from "./providers/openai.js";
22
29
  export { OllamaProvider } from "./providers/ollama.js";
23
- //# sourceMappingURL=index.js.map
@@ -23,6 +23,8 @@ export interface InitOptions {
23
23
  bin: string;
24
24
  settingsPath?: string | undefined;
25
25
  noCatalog?: boolean | undefined;
26
+ /** Tri-state: undefined = ask (interactive) or default-install; true/false force it. */
27
+ skills?: boolean | undefined;
26
28
  }
27
29
  export interface UninstallOptions {
28
30
  yes: boolean;
@@ -30,8 +32,7 @@ export interface UninstallOptions {
30
32
  settingsPath?: string | undefined;
31
33
  purge?: boolean | undefined;
32
34
  }
33
- /** A backup path next to `path`, stamped so successive runs never collide. */
34
- export declare function backupPath(path: string, stamp: string): string;
35
+ export { backupPath } from "./backup.js";
35
36
  /**
36
37
  * Build the minimal YAML config object for the chosen provider — only keys that
37
38
  * differ from the built-in defaults are emitted, so the file stays small and
@@ -44,4 +45,3 @@ export declare function buildConfigObject(opts: {
44
45
  }): Record<string, unknown> | null;
45
46
  export declare function runInit(opts: InitOptions, env?: NodeJS.ProcessEnv): Promise<number>;
46
47
  export declare function runUninstall(opts: UninstallOptions, env?: NodeJS.ProcessEnv): Promise<number>;
47
- //# sourceMappingURL=init.d.ts.map
package/dist/src/init.js CHANGED
@@ -22,22 +22,17 @@ import { addHooks, removeHooks } from "./install.js";
22
22
  import { buildCatalog } from "./catalog.js";
23
23
  import { loadConfig } from "./config.js";
24
24
  import { makeAnsi } from "./ansi.js";
25
+ import { backupPath, stampNow } from "./backup.js";
26
+ import { installCompanionSkills } from "./skillsCmd.js";
25
27
  const PROVIDERS = ["anthropic", "openai", "ollama"];
26
28
  const DEFAULT_KEY_ENV = {
27
29
  anthropic: "ANTHROPIC_API_KEY",
28
30
  openai: "OPENAI_API_KEY",
29
31
  ollama: undefined,
30
32
  };
31
- /** A backup path next to `path`, stamped so successive runs never collide. */
32
- export function backupPath(path, stamp) {
33
- return `${path}.bak-${stamp}`;
34
- }
35
- function stampNow() {
36
- return new Date()
37
- .toISOString()
38
- .replace(/[:.]/g, "-")
39
- .replace(/-(\d{3})Z$/, "Z");
40
- }
33
+ // `backupPath` / `stampNow` live in ./backup.js (shared with skillsCmd.ts);
34
+ // re-exported here so existing importers (index.ts, tests) keep working.
35
+ export { backupPath } from "./backup.js";
41
36
  function defaultSettingsPath() {
42
37
  return join(homedir(), ".claude", "settings.json");
43
38
  }
@@ -165,6 +160,9 @@ export async function runInit(opts, env = process.env) {
165
160
  }
166
161
  if (!opts.noCatalog)
167
162
  io.out(` ${ansi.green("+")} build catalog`);
163
+ if (opts.skills !== false) {
164
+ io.out(` ${ansi.green("+")} companion skills ${ansi.dim("(/wrap /curate /relay)")}`);
165
+ }
168
166
  // 4. API-key heads-up (never blocks; just warns).
169
167
  if (provider !== "ollama" && apiKeyEnv && !env[apiKeyEnv]) {
170
168
  io.out(`\n ${ansi.yellow("!")} ${apiKeyEnv} is not set in this shell — ` +
@@ -218,6 +216,51 @@ export async function runInit(opts, env = process.env) {
218
216
  io.out(ansi.yellow("! ") + "catalog build skipped (run `memhook build-catalog` later)");
219
217
  }
220
218
  }
219
+ // 8. Companion skills (/wrap, /curate, /relay) — opt-in, non-clobbering. The
220
+ // install only writes absent skills (never overwrites a user edit without
221
+ // --force), so default-on in --yes mode is safe; interactive mode asks.
222
+ let wantSkills = opts.skills;
223
+ if (wantSkills === undefined) {
224
+ if (interactive) {
225
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
226
+ try {
227
+ const a = (await rl.question(`\nInstall companion skills ${ansi.dim("(/wrap /curate /relay)")} ${ansi.dim("[Y/n]")} `))
228
+ .trim()
229
+ .toLowerCase();
230
+ wantSkills = !(a === "n" || a === "no");
231
+ }
232
+ finally {
233
+ rl.close();
234
+ }
235
+ }
236
+ else {
237
+ wantSkills = true;
238
+ }
239
+ }
240
+ if (wantSkills) {
241
+ try {
242
+ const results = installCompanionSkills({});
243
+ const applied = results.filter((r) => r.applied).map((r) => `/${r.plan.name}`);
244
+ const blocked = results
245
+ .filter((r) => r.plan.action === "blocked")
246
+ .map((r) => `/${r.plan.name}`);
247
+ if (applied.length > 0) {
248
+ io.out(`${ansi.green("✓")} skills ${applied.join(" ")} → ${join(homedir(), ".claude", "skills")}`);
249
+ }
250
+ else if (blocked.length === 0) {
251
+ io.out(`${ansi.dim("·")} companion skills already installed ${ansi.dim("(skip)")}`);
252
+ }
253
+ if (blocked.length > 0) {
254
+ io.out(`${ansi.yellow("!")} ${blocked.join(" ")} differ from shipped — left as-is ` +
255
+ ansi.dim("(memhook skills install --force to update)"));
256
+ }
257
+ }
258
+ catch (err) {
259
+ io.out(ansi.yellow("! ") + "skill install skipped (run `memhook skills install` later)");
260
+ if (env["MEMHOOK_DEBUG"] === "true")
261
+ process.stderr.write(`${String(err)}\n`);
262
+ }
263
+ }
221
264
  io.out(`\n${ansi.green("Done.")} Restart Claude Code, then watch it live with ` +
222
265
  ansi.bold("memhook tail") +
223
266
  ".");
@@ -276,8 +319,8 @@ export async function runUninstall(opts, env = process.env) {
276
319
  for (const target of [config.cache.dir, config.logging.jsonlPath]) {
277
320
  io.out(ansi.dim(` (left in place: ${target} — remove manually if desired)`));
278
321
  }
322
+ io.out(ansi.dim(" (companion skills, if installed: remove with `memhook skills uninstall`)"));
279
323
  }
280
324
  io.out(`\n${ansi.green("Done.")} Restart Claude Code to drop the hooks.`);
281
325
  return 0;
282
326
  }
283
- //# sourceMappingURL=init.js.map
@@ -84,4 +84,3 @@ export declare function addHooks(input: unknown, bin?: string): AddResult;
84
84
  * group list becomes empty — are pruned so no dangling shells are left behind.
85
85
  */
86
86
  export declare function removeHooks(input: unknown): RemoveResult;
87
- //# sourceMappingURL=install.d.ts.map
@@ -121,4 +121,3 @@ export function removeHooks(input) {
121
121
  }
122
122
  return { settings, removed, removedEvents };
123
123
  }
124
- //# sourceMappingURL=install.js.map
@@ -13,4 +13,3 @@ export declare class PreFilter {
13
13
  constructor(filePath: string | undefined, defaults: string[]);
14
14
  isTrivial(prompt: string): boolean;
15
15
  }
16
- //# sourceMappingURL=preFilter.d.ts.map
@@ -37,4 +37,3 @@ export class PreFilter {
37
37
  function normalise(input) {
38
38
  return input.replace(/[\s\p{P}]/gu, "").toLowerCase();
39
39
  }
40
- //# sourceMappingURL=preFilter.js.map
@@ -30,4 +30,3 @@ export declare class AnthropicProvider implements Provider {
30
30
  constructor(config: ProviderConfig, options?: AnthropicProviderOptions);
31
31
  select(req: SelectionRequest): Promise<SelectionResponse>;
32
32
  }
33
- //# sourceMappingURL=anthropic.d.ts.map
@@ -95,4 +95,3 @@ function extractUsage(json) {
95
95
  cacheReadTokens: num("cache_read_input_tokens"),
96
96
  };
97
97
  }
98
- //# sourceMappingURL=anthropic.js.map
@@ -12,4 +12,3 @@
12
12
  import type { Provider } from "./types.js";
13
13
  import type { MemhookConfig } from "../config.js";
14
14
  export declare function createProvider(cfg: MemhookConfig, apiKey: string | undefined): Provider;
15
- //# sourceMappingURL=factory.d.ts.map
@@ -34,4 +34,3 @@ export function createProvider(cfg, apiKey) {
34
34
  }
35
35
  }
36
36
  }
37
- //# sourceMappingURL=factory.js.map
@@ -31,4 +31,3 @@ export interface RawHttpResult {
31
31
  latencyMs: number;
32
32
  }
33
33
  export declare function postJsonWithRetry(opts: PostJsonOptions): Promise<RawHttpResult>;
34
- //# sourceMappingURL=http.d.ts.map
@@ -57,4 +57,3 @@ export async function postJsonWithRetry(opts) {
57
57
  function sleep(ms) {
58
58
  return new Promise((resolve) => setTimeout(resolve, ms));
59
59
  }
60
- //# sourceMappingURL=http.js.map
@@ -27,4 +27,3 @@ export declare class OllamaProvider implements Provider {
27
27
  constructor(config: ProviderConfig);
28
28
  select(req: SelectionRequest): Promise<SelectionResponse>;
29
29
  }
30
- //# sourceMappingURL=ollama.d.ts.map
@@ -86,4 +86,3 @@ function extractUsage(json) {
86
86
  cacheReadTokens: 0,
87
87
  };
88
88
  }
89
- //# sourceMappingURL=ollama.js.map
@@ -28,4 +28,3 @@ export declare class OpenAIProvider implements Provider {
28
28
  constructor(config: ProviderConfig);
29
29
  select(req: SelectionRequest): Promise<SelectionResponse>;
30
30
  }
31
- //# sourceMappingURL=openai.d.ts.map
@@ -91,4 +91,3 @@ function extractUsage(json) {
91
91
  cacheReadTokens: cachedTokens,
92
92
  };
93
93
  }
94
- //# sourceMappingURL=openai.js.map
@@ -45,4 +45,3 @@ export interface Provider {
45
45
  readonly name: string;
46
46
  select(req: SelectionRequest): Promise<SelectionResponse>;
47
47
  }
48
- //# sourceMappingURL=types.d.ts.map
@@ -15,4 +15,3 @@
15
15
  * `createProvider()` in `factory.ts`.
16
16
  */
17
17
  export {};
18
- //# sourceMappingURL=types.js.map
@@ -18,6 +18,7 @@
18
18
  * Fail-soft: every error path falls back to empty additionalContext.
19
19
  * Never blocks Claude Code.
20
20
  */
21
+ import { type MemhookConfig } from "./config.js";
21
22
  export interface HookInput {
22
23
  prompt: string;
23
24
  cwd?: string;
@@ -27,6 +28,26 @@ export interface HookOutput {
27
28
  hookEventName: "UserPromptSubmit";
28
29
  additionalContext: string;
29
30
  };
31
+ /**
32
+ * Optional, additive (v0.4). A one-line warning shown to the user — used only
33
+ * for the `/curate` nudge when the catalog grows large. Documented Claude Code
34
+ * field; absent on every turn the nudge does not fire, so the existing output
35
+ * shape is unchanged for existing consumers (docs/SPECIFICATION.md §10.2).
36
+ */
37
+ systemMessage?: string;
30
38
  }
31
39
  export declare function route(stdinJson: string, env?: NodeJS.ProcessEnv): Promise<HookOutput>;
32
- //# sourceMappingURL=router.d.ts.map
40
+ /**
41
+ * Proactive `/curate` nudge. Returns a one-line `systemMessage` when the memory
42
+ * catalog has grown past a threshold and the cooldown has elapsed, else
43
+ * undefined. Local-only: it reads the already-loaded catalog length, counts
44
+ * memory files, and stamps a local cooldown file — NO outbound call. The whole
45
+ * body is wrapped so any failure yields no nudge (fail-soft is never affected).
46
+ *
47
+ * Cost: within the cooldown it pays one tiny stamp read and returns; only once
48
+ * the cooldown elapses does it count files (a readdir per memory dir, the same
49
+ * order of I/O the router already does in `readSelected`).
50
+ *
51
+ * Exported for direct unit testing.
52
+ */
53
+ export declare function maybeCurateNudge(config: MemhookConfig, catalogContent: string, now: number): string | undefined;