replen 1.0.2 → 1.0.4
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 +11 -2
- package/dist/commands.js +56 -0
- package/dist/mcp-setup.js +22 -2
- package/extras/skills/replen-match/SKILL.md +34 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
# replen
|
|
2
2
|
|
|
3
|
-
**Make your AI coding tools aware of
|
|
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
|
|
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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Plain-shell subcommands. Each one mirrors an MCP tool but renders for the
|
|
2
2
|
// terminal rather than returning JSON to an agent. `--json` flag on every
|
|
3
3
|
// command dumps raw JSON for piping/scripting.
|
|
4
|
+
import { readdirSync, existsSync } from "node:fs";
|
|
4
5
|
import { apiGet, apiPost, loadConfigOrExit } from "./api.js";
|
|
5
6
|
import { configPath } from "./config.js";
|
|
6
7
|
function hasFlag(argv, flag) {
|
|
@@ -196,6 +197,33 @@ export async function runCheckNew(argv) {
|
|
|
196
197
|
console.error("Not signed in. Run `npx replen` first.");
|
|
197
198
|
process.exit(1);
|
|
198
199
|
}
|
|
200
|
+
// Hook mode surfaces the repo-scoped SKILL inventory directly — the good data
|
|
201
|
+
// (semantic + dependency / spec / health matches, with the relevance floor +
|
|
202
|
+
// cool-off applied server-side), using the server's pre-formatted footnote.
|
|
203
|
+
// It deliberately skips the legacy hosted `check-new` below, which returns
|
|
204
|
+
// generic, un-scoped, high-star matches that aren't useful to skill-tier
|
|
205
|
+
// users. Output is injected into the agent's opening context by Claude Code's
|
|
206
|
+
// SessionStart-hook stdout injection; the framing nudges the agent to relay
|
|
207
|
+
// it verbatim (final surfacing is still the model's call).
|
|
208
|
+
if (hookMode && !json) {
|
|
209
|
+
const cwdRepo = await detectCwdRepo();
|
|
210
|
+
if (!cwdRepo) {
|
|
211
|
+
// No git remote → Replen can't scope this project. If the cwd looks like
|
|
212
|
+
// a real project (has source / a manifest), nudge the user to set it up;
|
|
213
|
+
// otherwise stay silent (don't nag in /tmp or a random dir). Surfacing is
|
|
214
|
+
// the model's call, but the explicit framing maximises it.
|
|
215
|
+
if (looksLikeProject()) {
|
|
216
|
+
console.log("[Replen] This project has no git remote, so Replen can't scope or match against it. Mention to the user, briefly, as a footnote: 'By the way — this project isn't set up with Replen yet (no git remote), so I can't surface matches for it. Want me to scope it — init git, create the repo, write the docs, and add tags?'");
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const inv = await fetchInventoryStatus(cfg, cwdRepo);
|
|
221
|
+
if (inv?.displayText) {
|
|
222
|
+
console.log("[Replen] Relay the following line to the user, verbatim, as a footnote at the very end of your first reply:");
|
|
223
|
+
console.log(inv.displayText);
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
199
227
|
const query = {};
|
|
200
228
|
if (repo !== undefined)
|
|
201
229
|
query.repo = repo;
|
|
@@ -455,6 +483,31 @@ async function detectCwdRepo() {
|
|
|
455
483
|
return null;
|
|
456
484
|
}
|
|
457
485
|
}
|
|
486
|
+
// Cheap heuristic: does the cwd look like a real project worth nudging the user
|
|
487
|
+
// to scope with Replen? True when there's a recognised manifest, a src/lib/app
|
|
488
|
+
// dir, or a couple of source files. Keeps the "no git — want to scope?" prompt
|
|
489
|
+
// from firing in /tmp, $HOME, or an empty directory.
|
|
490
|
+
function looksLikeProject() {
|
|
491
|
+
try {
|
|
492
|
+
const manifests = ["package.json", "pyproject.toml", "requirements.txt", "Cargo.toml", "go.mod", "pom.xml", "build.gradle", "Gemfile", "composer.json", "pubspec.yaml"];
|
|
493
|
+
if (manifests.some((m) => existsSync(m)))
|
|
494
|
+
return true;
|
|
495
|
+
if (["src", "lib", "app", "cmd", "pkg"].some((d) => existsSync(d)))
|
|
496
|
+
return true;
|
|
497
|
+
const codeExt = /\.(ts|tsx|js|jsx|py|rs|go|java|rb|php|c|cc|cpp|h|hpp|swift|kt|scala|sol|ex|clj)$/i;
|
|
498
|
+
let codeFiles = 0;
|
|
499
|
+
for (const e of readdirSync(".", { withFileTypes: true })) {
|
|
500
|
+
if (e.isFile() && codeExt.test(e.name))
|
|
501
|
+
codeFiles++;
|
|
502
|
+
if (codeFiles >= 2)
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
catch {
|
|
508
|
+
return false;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
458
511
|
async function fetchInventoryStatus(cfg, repo) {
|
|
459
512
|
// Hook mode is on the session-open critical path; cap latency hard.
|
|
460
513
|
const ctrl = new AbortController();
|
|
@@ -482,6 +535,9 @@ async function fetchInventoryStatus(cfg, repo) {
|
|
|
482
535
|
count: cands.length,
|
|
483
536
|
topRepo: top.repo ?? null,
|
|
484
537
|
topSimilarity: simMatch ? Number(simMatch[1]) : null,
|
|
538
|
+
// The server's pre-formatted, pattern-aware footnote ("By the way — a
|
|
539
|
+
// dependency you use just shipped: …" / "… N candidates queued …").
|
|
540
|
+
displayText: (typeof data.displayText === "string" && data.displayText) ? data.displayText : null,
|
|
485
541
|
};
|
|
486
542
|
}
|
|
487
543
|
catch {
|
package/dist/mcp-setup.js
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
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";
|
|
@@ -239,8 +240,27 @@ function backupIfExists(path) {
|
|
|
239
240
|
// their current docs). Both rely on the AGENTS.md / GEMINI.md project-
|
|
240
241
|
// context file being read at session start, which our inject step
|
|
241
242
|
// covers.
|
|
242
|
-
|
|
243
|
-
|
|
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
|
+
}
|
|
244
264
|
function installSessionStartHook(existing) {
|
|
245
265
|
const sessionStart = (existing.SessionStart ?? []).slice();
|
|
246
266
|
const ours = {
|
|
@@ -181,11 +181,40 @@ fresh candidate shows up at session start via the hook).
|
|
|
181
181
|
**Inventory call returns 401.** User's token expired or got rotated.
|
|
182
182
|
Tell them to run `npx replen` to re-auth.
|
|
183
183
|
|
|
184
|
-
**
|
|
185
|
-
your project list"
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
firehose
|
|
184
|
+
**The project isn't scoped — `scopedTo: null`, a `note` about "repo not in
|
|
185
|
+
your project list", OR the cwd has no git remote.** Replen can only match
|
|
186
|
+
against a repo it has registered, and it scopes by the git remote. When it's
|
|
187
|
+
unscoped you'll get the global trending firehose — which is noise for this
|
|
188
|
+
codebase. **Do NOT triage the firehose** (manufacturing reasons to care about
|
|
189
|
+
random trending repos is exactly what this skill must not do).
|
|
190
|
+
|
|
191
|
+
Instead, **offer to onboard the project.** Lead with ONE line, not the whole
|
|
192
|
+
checklist: *"This project isn't set up with Replen yet, so I can only see the
|
|
193
|
+
global firehose (not matches for your code). Want me to scope it — init git,
|
|
194
|
+
create the repo, write the docs, and add tags? Then Replen can surface things
|
|
195
|
+
that actually fit."* If the user agrees, run this checklist:
|
|
196
|
+
|
|
197
|
+
1. **Git + GitHub.** If there's no git repo, `git init`. Create the GitHub
|
|
198
|
+
repo using the user's existing `gh` auth — ask for owner/name or suggest a
|
|
199
|
+
sensible default from the folder name, and confirm public vs private:
|
|
200
|
+
`gh repo create <owner>/<name> --private --source=. --remote=origin --push`.
|
|
201
|
+
If a repo exists locally but has no remote, just add + push the remote.
|
|
202
|
+
2. **Docs Replen can read.** Replen's scorer reads your `README.md` +
|
|
203
|
+
`CLAUDE.md` to understand the project — that's the difference between
|
|
204
|
+
useful matches and noise. Write a concrete `README.md` (what it is, stack,
|
|
205
|
+
domain) if missing, and a `CLAUDE.md` optimised for Replen (run the
|
|
206
|
+
`/replen-project-init` protocol, or draft the seven sections directly:
|
|
207
|
+
what it is · stack · niche/domain · active areas · constraints/non-goals ·
|
|
208
|
+
anti-patterns · integration preferences). Use the project's real domain
|
|
209
|
+
vocabulary, not abstractions.
|
|
210
|
+
3. **Register + tag.** Add the repo to Replen at **app.replen.dev/projects**
|
|
211
|
+
(toggle it watched), or it auto-registers on the next
|
|
212
|
+
`npx replen sync-projects`. Set **tags** that narrow the firehose to the
|
|
213
|
+
project's domain — derive them from the code (e.g. for a Python CCXT
|
|
214
|
+
trading engine: `crypto, trading, ccxt, python, quant, backtesting`) — at
|
|
215
|
+
/projects.
|
|
216
|
+
4. **Re-run.** Once it has a remote + docs + tags, call `replen_match` again —
|
|
217
|
+
now it scopes to the project and matches against the real code.
|
|
189
218
|
|
|
190
219
|
**Candidate's README is unreachable (WebFetch 404)**. Note it in the
|
|
191
220
|
writeup (`Caveats: README unreachable; verdict based on description
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "replen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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": {
|