replen 0.2.0 → 0.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # replen
2
2
 
3
- **Smart AI Development workflows.** One-command setup for [replen](https://replen.dev) - the AI that asks *"can we do this better?"* on your codebase, every morning.
3
+ **Smarter AI Development workflows.** One-command setup for [replen](https://replen.dev) - the AI that asks *"can we do this better?"* on your codebase. Calm cadence: 1-3 actionable matches a month, not a daily feed.
4
4
 
5
5
  ```bash
6
6
  npx replen
@@ -24,6 +24,23 @@ npx replen mcp setup # re-wire MCP using saved auth
24
24
  npx replen logout # forget saved auth (token stays valid; rotate on /settings to revoke)
25
25
  ```
26
26
 
27
+ ## Plain-shell usage (no Claude Code / Codex needed)
28
+
29
+ ```bash
30
+ npx replen run # trigger a pipeline run
31
+ npx replen progress # tail the run live; exits when done
32
+ npx replen feed # show recent matches (default 2 days)
33
+ npx replen watch # keep a terminal open — rings the bell when
34
+ # a new match lands. Default poll 5min.
35
+ npx replen search <query> # full-text search past matches
36
+ npx replen starred # starred matches + handoff PR status
37
+ npx replen handoff <matchId> # open the handoff PR for a starred match
38
+ ```
39
+
40
+ `watch` is the calm-utility companion: leave it running in a `tmux` pane and forget about it. The first poll establishes a baseline; existing matches don't ring. Pair with `--no-bell` for a silent log feed, or `--json` to pipe into anything.
41
+
42
+ Every data command accepts `--json` for scripting.
43
+
27
44
  ## Self-host
28
45
 
29
46
  Pointing at your own replen instance:
package/dist/commands.js CHANGED
@@ -75,7 +75,16 @@ export async function runProgress(argv) {
75
75
  }
76
76
  if (!status.inFlight) {
77
77
  if (!json) {
78
- console.log(`— done · ${status.matches ?? 0} matches · run #${status.runId ?? "?"}`);
78
+ const quota = parseQuotaReason(status.pausedReason ?? null);
79
+ if (quota) {
80
+ console.log("");
81
+ console.log(`✗ ${quota === "primary" ? "Primary" : "Sensitive"} LLM is out of credits.`);
82
+ console.log(" Top up your API key's balance, or rotate to a different provider on /settings.");
83
+ process.exitCode = 1;
84
+ }
85
+ else {
86
+ console.log(`— done · ${status.matches ?? 0} matches · run #${status.runId ?? "?"}`);
87
+ }
79
88
  }
80
89
  return;
81
90
  }
@@ -160,6 +169,67 @@ export async function runStarred(argv) {
160
169
  handleApiError(e);
161
170
  }
162
171
  }
172
+ // Watch for new matches in the background. Polls /api/mcp/today, diffs against
173
+ // the matchIds seen on the previous poll, prints anything new, and rings the
174
+ // terminal bell (\x07). First poll establishes a baseline — existing matches
175
+ // don't ring. Default interval 5 minutes (the pipeline runs daily by default,
176
+ // so polling tighter is pure overhead).
177
+ export async function runWatch(argv) {
178
+ const cfg = await loadConfigOrExit();
179
+ const json = hasFlag(argv, "--json");
180
+ const noBell = hasFlag(argv, "--no-bell");
181
+ const intervalSec = Math.max(30, Number(getFlag(argv, "--interval") ?? 300));
182
+ const days = Number(getFlag(argv, "--days") ?? 2);
183
+ const project = getFlag(argv, "--project");
184
+ const relevance = getFlag(argv, "--relevance") ?? "high,medium";
185
+ const seen = new Set();
186
+ let firstPass = true;
187
+ if (!json) {
188
+ const target = project ? ` · project=${project}` : "";
189
+ console.log(`Watching for new ${relevance} matches${target} · poll every ${intervalSec}s · Ctrl-C to stop.`);
190
+ }
191
+ while (true) {
192
+ let r;
193
+ try {
194
+ r = await apiGet(cfg, "/api/mcp/today", {
195
+ days,
196
+ relevance,
197
+ project,
198
+ });
199
+ }
200
+ catch (e) {
201
+ // Don't kill the watcher on a transient network blip — just log + retry.
202
+ console.error(`✗ ${e.message ?? String(e)} (retrying in ${intervalSec}s)`);
203
+ await sleep(intervalSec * 1000);
204
+ continue;
205
+ }
206
+ const fresh = [];
207
+ for (const m of r.matches) {
208
+ if (!seen.has(m.matchId)) {
209
+ if (!firstPass)
210
+ fresh.push(m);
211
+ seen.add(m.matchId);
212
+ }
213
+ }
214
+ if (json) {
215
+ // Emit one JSON line per poll, regardless of new/no-new — easy to pipe.
216
+ console.log(JSON.stringify({ ts: new Date().toISOString(), new: fresh.length, matches: fresh }));
217
+ }
218
+ else if (fresh.length > 0) {
219
+ const stamp = new Date().toISOString().slice(11, 19);
220
+ console.log(`\n[${stamp}] ${fresh.length} new:`);
221
+ for (const m of fresh)
222
+ renderMatchLine(m);
223
+ if (!noBell)
224
+ process.stdout.write("\x07");
225
+ }
226
+ else if (firstPass) {
227
+ console.log(`Baseline: ${seen.size} match(es) already in feed; will alert on anything new.`);
228
+ }
229
+ firstPass = false;
230
+ await sleep(intervalSec * 1000);
231
+ }
232
+ }
163
233
  // Open a handoff PR for a starred match in the matched project's own repo.
164
234
  export async function runHandoff(argv) {
165
235
  const cfg = await loadConfigOrExit();
@@ -197,6 +267,15 @@ function renderMatchLine(m, opts = {}) {
197
267
  const repo = m.repo ?? "(no repo)";
198
268
  console.log(` ${star} #${m.matchId} ${repo} → ${m.project} · ${m.relevance}${score}${stars}${lic}${handoff}`);
199
269
  }
270
+ function parseQuotaReason(reason) {
271
+ if (!reason)
272
+ return null;
273
+ if (reason.startsWith("llm-quota:primary"))
274
+ return "primary";
275
+ if (reason.startsWith("llm-quota:sensitive"))
276
+ return "sensitive";
277
+ return null;
278
+ }
200
279
  function marker(kind) {
201
280
  switch (kind) {
202
281
  case "match":
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import { runInit } from "./init.js";
3
3
  import { setupMcp } from "./mcp-setup.js";
4
4
  import { readConfig, configPath } from "./config.js";
5
5
  import { runProjectInit } from "./project-init.js";
6
- import { runFeed, runHandoff, runProgress, runRun, runSearch, runStarred } from "./commands.js";
7
- const HELP = `replen: Smart AI Development workflows
6
+ import { runFeed, runHandoff, runProgress, runRun, runSearch, runStarred, runWatch } from "./commands.js";
7
+ const HELP = `replen: Smarter AI Development workflows
8
8
 
9
9
  Usage:
10
10
  npx replen Sign up / sign in + wire MCP into Claude Code
@@ -21,6 +21,12 @@ Use replen from a plain shell (no Claude Code / Codex needed):
21
21
  npx replen feed [--days N] Today's matches (default 2 days)
22
22
  [--project SLUG] Limit to one project
23
23
  [--relevance high,medium]
24
+ npx replen watch Long-running poll: rings the terminal bell
25
+ [--interval SEC] when a new match lands (default 300s).
26
+ [--days N] First poll establishes baseline; existing
27
+ [--project SLUG] matches don't ring. Ctrl-C to stop.
28
+ [--relevance high,medium]
29
+ [--no-bell]
24
30
  npx replen search <query> Full-text search across past matches
25
31
  npx replen starred Starred matches + handoff PR status
26
32
  npx replen handoff <matchId> Open the handoff PR for a starred match
@@ -88,6 +94,8 @@ async function main() {
88
94
  return runProgress(argv);
89
95
  if (cmd === "feed")
90
96
  return runFeed(argv);
97
+ if (cmd === "watch")
98
+ return runWatch(argv);
91
99
  if (cmd === "search")
92
100
  return runSearch(argv);
93
101
  if (cmd === "starred")
package/dist/init.js CHANGED
@@ -55,7 +55,15 @@ function waitForCallback(port, expectedState) {
55
55
  res.end("state mismatch");
56
56
  return;
57
57
  }
58
- res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
58
+ // Referrer-Policy: no-referrer means anything rendered in SUCCESS_HTML
59
+ // (even if a future maintainer adds an external <img>, <link>, or fetch)
60
+ // cannot leak the callback URL — which carries the exchange code in
61
+ // its query string — via an outbound Referer header.
62
+ res.writeHead(200, {
63
+ "content-type": "text/html; charset=utf-8",
64
+ "referrer-policy": "no-referrer",
65
+ "cache-control": "no-store",
66
+ });
59
67
  res.end(SUCCESS_HTML);
60
68
  // Give the response a tick to flush before we shut down the listener.
61
69
  setTimeout(() => server.close(), 100);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "replen",
3
- "version": "0.2.0",
4
- "description": "Smart AI Development workflows. The AI that asks 'can we do this better?' - replen reads your codebase against the live ecosystem every morning, surfaces drop-in libraries, ideas to port, and patterns to learn from. A proactive layer for your AI coding workflow. One-command setup: opens a browser to sign in, then wires the MCP server into Claude Code / Codex.",
3
+ "version": "0.3.0",
4
+ "description": "Smarter AI Development workflows. The AI that asks 'can we do this better?' - replen reads your codebase against the live ecosystem and surfaces drop-in libraries, ideas to port, and patterns to learn from. Calm cadence: 1-3 actionable matches a month. One-command setup: opens a browser to sign in, then wires the MCP server into Claude Code / Codex.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "replen": "dist/index.js"