replen 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,21 @@
1
1
  # replen
2
2
 
3
- **Make your AI coding tools aware of the wider OSS ecosystem.** One command, no API keys.
3
+ **Make your AI coding tools aware of everything your code depends on, implements, and builds on.** One command, no API keys.
4
4
 
5
5
  ```bash
6
6
  npx replen
7
7
  ```
8
8
 
9
- Claude Code, Codex, Cursor already know your code. Replen tells them what else is out there — drop-in libraries, ideas worth porting, dead deps to swap. The match decision happens *inside your AI tool's session* on your subscription tokens. Your code stays on your laptop. Replen never sees it.
9
+ Claude Code, Codex, Cursor already know your code. Replen tells them what's happening *outside* it. The match decision happens *inside your AI tool's session* on your subscription tokens. Your code stays on your laptop. Replen never sees it.
10
+
11
+ Four lenses, all surfaced the same calm way — quietly, in your AI tool's next reply, only when there's something real:
12
+
13
+ - **🔭 OSS repos** — a new library that replaces code you maintain, a pattern worth porting, a dead dep to swap.
14
+ - **📦 Your stack** — a release in a dependency you actually use (`next`, `openai`, `prisma`, `viem`, … matched against your manifest).
15
+ - **📜 Standards you implement** — EIP/ERC, TC39, and Chrome-deprecation changes matched to what your code touches.
16
+ - **🩺 Upstream health** — a dep gone stale/archived, a hot bug others hit in your deps, or an incident on a service you use.
17
+
18
+ One discipline throughout: **silence beats a weak match** — if nothing clears the bar, Replen says nothing.
10
19
 
11
20
  ## What `npx replen` does
12
21
 
package/dist/commands.js CHANGED
@@ -196,6 +196,25 @@ export async function runCheckNew(argv) {
196
196
  console.error("Not signed in. Run `npx replen` first.");
197
197
  process.exit(1);
198
198
  }
199
+ // Hook mode surfaces the repo-scoped SKILL inventory directly — the good data
200
+ // (semantic + dependency / spec / health matches, with the relevance floor +
201
+ // cool-off applied server-side), using the server's pre-formatted footnote.
202
+ // It deliberately skips the legacy hosted `check-new` below, which returns
203
+ // generic, un-scoped, high-star matches that aren't useful to skill-tier
204
+ // users. Output is injected into the agent's opening context by Claude Code's
205
+ // SessionStart-hook stdout injection; the framing nudges the agent to relay
206
+ // it verbatim (final surfacing is still the model's call).
207
+ if (hookMode && !json) {
208
+ const cwdRepo = await detectCwdRepo();
209
+ if (!cwdRepo)
210
+ return; // not in a GitHub repo with a remote — silent
211
+ const inv = await fetchInventoryStatus(cfg, cwdRepo);
212
+ if (inv?.displayText) {
213
+ console.log("[Replen] Relay the following line to the user, verbatim, as a footnote at the very end of your first reply:");
214
+ console.log(inv.displayText);
215
+ }
216
+ return;
217
+ }
199
218
  const query = {};
200
219
  if (repo !== undefined)
201
220
  query.repo = repo;
@@ -482,6 +501,9 @@ async function fetchInventoryStatus(cfg, repo) {
482
501
  count: cands.length,
483
502
  topRepo: top.repo ?? null,
484
503
  topSimilarity: simMatch ? Number(simMatch[1]) : null,
504
+ // The server's pre-formatted, pattern-aware footnote ("By the way — a
505
+ // dependency you use just shipped: …" / "… N candidates queued …").
506
+ displayText: (typeof data.displayText === "string" && data.displayText) ? data.displayText : null,
485
507
  };
486
508
  }
487
509
  catch {
package/dist/mcp-setup.js CHANGED
@@ -22,12 +22,31 @@
22
22
  import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync, chmodSync } from "node:fs";
23
23
  import { homedir } from "node:os";
24
24
  import { join, dirname } from "node:path";
25
+ import { fileURLToPath } from "node:url";
25
26
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
26
27
  import { installSkills } from "./skill-install.js";
27
28
  const SERVER_NAME = "replen";
28
29
  const CLAUDE_CONFIG = join(homedir(), ".claude.json");
30
+ const CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
29
31
  const CODEX_CONFIG = join(homedir(), ".codex", "config.toml");
30
32
  const GEMINI_CONFIG = join(homedir(), ".gemini", "settings.json");
33
+ // Read/triage replen MCP tools that are safe to auto-allow so the proactive
34
+ // footnote (replen_match) and in-session triage don't trigger a permission
35
+ // prompt every session. Deliberately EXCLUDES replen_run / replen_handoff /
36
+ // replen_feedback — those trigger pipeline runs / open PRs and should keep
37
+ // prompting for explicit consent.
38
+ const REPLEN_AUTO_ALLOW = [
39
+ "mcp__replen__replen_match",
40
+ "mcp__replen__replen_check_new",
41
+ "mcp__replen__replen_analyze",
42
+ "mcp__replen__replen_state",
43
+ "mcp__replen__replen_record_triage",
44
+ "mcp__replen__replen_today",
45
+ "mcp__replen__replen_search",
46
+ "mcp__replen__replen_starred",
47
+ "mcp__replen__replen_status",
48
+ "mcp__replen__replen_help",
49
+ ];
31
50
  // ============================================================================
32
51
  // Public entry point
33
52
  // ============================================================================
@@ -76,12 +95,37 @@ function setupClaude(token, base) {
76
95
  };
77
96
  const hooks = installSessionStartHook(config.hooks ?? {});
78
97
  writeJsonAtomic(path, { ...config, mcpServers, hooks });
98
+ // Auto-allow the read/triage tools so the proactive footnote doesn't
99
+ // prompt every session. Best-effort: a failure here mustn't fail MCP setup.
100
+ try {
101
+ allowlistClaudeTools();
102
+ }
103
+ catch (e) {
104
+ console.warn(` ⚠ Claude Code allowlist skipped — ${e.message}`);
105
+ }
79
106
  return { ok: true, label: "Claude Code", path, action: existed ? "updated" : "added" };
80
107
  }
81
108
  catch (e) {
82
109
  return { ok: false, label: "Claude Code", path, error: e.message };
83
110
  }
84
111
  }
112
+ // Merge the replen read/triage tools into Claude Code's permission allowlist
113
+ // (~/.claude/settings.json → permissions.allow) so the proactive footnote +
114
+ // in-session triage run without a per-session permission prompt. Non-
115
+ // destructive: preserves existing allow entries and every other setting.
116
+ function allowlistClaudeTools() {
117
+ const path = CLAUDE_SETTINGS;
118
+ mkdirSync(dirname(path), { recursive: true });
119
+ const config = existsSync(path) ? readJson(path) : {};
120
+ const permissions = config.permissions ?? {};
121
+ const existingAllow = Array.isArray(permissions.allow) ? permissions.allow : [];
122
+ const allow = Array.from(new Set([...existingAllow, ...REPLEN_AUTO_ALLOW]));
123
+ if (allow.length === existingAllow.length)
124
+ return; // already fully allowlisted
125
+ backupIfExists(path);
126
+ writeJsonAtomic(path, { ...config, permissions: { ...permissions, allow } });
127
+ console.log(` ✓ Claude Code: allowlisted replen read tools (no more per-session prompts)`);
128
+ }
85
129
  function setupGemini(token, base) {
86
130
  const path = GEMINI_CONFIG;
87
131
  try {
@@ -196,8 +240,27 @@ function backupIfExists(path) {
196
240
  // their current docs). Both rely on the AGENTS.md / GEMINI.md project-
197
241
  // context file being read at session start, which our inject step
198
242
  // covers.
199
- const HOOK_COMMAND = "npx --quiet replen check-new --hook";
200
- const HOOK_MARKER = "replen check-new --hook";
243
+ // Version-PIN the hook command. A bare `npx replen` resolves a LOCAL package
244
+ // named "replen" when one exists in cwd (e.g. the replen repo itself, whose
245
+ // server package is also "replen" and has no bin) → "could not determine
246
+ // executable to run", and the hook silently dies every session there. Pinning
247
+ // to the published version forces npx to the registry (collision-proof) and,
248
+ // because the exact version is cached, avoids the per-session `@latest`
249
+ // registry round-trip. Re-running setup refreshes the pin.
250
+ const HOOK_COMMAND = `npx --quiet replen@${cliVersion()} check-new --hook`;
251
+ // Match on the stable substring so we find/replace our hook regardless of the
252
+ // pinned version in any previously-written command.
253
+ const HOOK_MARKER = "check-new --hook";
254
+ function cliVersion() {
255
+ try {
256
+ const here = dirname(fileURLToPath(import.meta.url)); // cli/dist
257
+ const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
258
+ return typeof pkg.version === "string" && pkg.version ? pkg.version : "latest";
259
+ }
260
+ catch {
261
+ return "latest";
262
+ }
263
+ }
201
264
  function installSessionStartHook(existing) {
202
265
  const sessionStart = (existing.SessionStart ?? []).slice();
203
266
  const ours = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replen",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Make your AI coding tools smarter. One command, no API keys. Replen scouts the OSS firehose against your projects and surfaces drop-in libraries, ideas to port, and dead deps to swap — the match decision happens inside your AI tool's session on your subscription tokens. 1-3 actionable matches a month, by design.",
5
5
  "type": "module",
6
6
  "bin": {