replen 0.0.1 → 0.1.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.
package/README.md CHANGED
@@ -1,9 +1,53 @@
1
1
  # replen
2
2
 
3
- Placeholder for the [replen](https://replen.dev) toolkit. The actual functionality ships under the scoped packages:
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.
4
4
 
5
- - `@replen/mcp` — MCP server for Claude Code / Codex
6
- - `@replen/cli` — command-line install / setup helper
7
- - (more to follow)
5
+ ```bash
6
+ npx replen
7
+ ```
8
8
 
9
- Watch [the GitHub org](https://github.com/replen) (pending) or [replen.dev](https://replen.dev) for the launch.
9
+ While your AI coding tool waits for prompts, replen reads your code against the ecosystem and surfaces drop-in libraries, ideas to port, and patterns to learn from. A proactive layer for your AI coding workflow.
10
+
11
+ The single command above:
12
+ 1. Opens your browser to sign in / sign up at `app.replen.dev`
13
+ 2. Captures the auth back into the terminal (browser-callback flow, like `gh auth login`)
14
+ 3. Wires the [@replen/mcp](https://www.npmjs.com/package/@replen/mcp) server into your Claude Code / Codex config
15
+
16
+ Same flow that `claude` itself uses for auth.
17
+
18
+ ## Subcommands
19
+
20
+ ```bash
21
+ npx replen # sign in + setup
22
+ npx replen status # show current config
23
+ npx replen mcp setup # re-wire MCP using saved auth
24
+ npx replen logout # forget saved auth (token stays valid; rotate on /settings to revoke)
25
+ ```
26
+
27
+ ## Self-host
28
+
29
+ Pointing at your own replen instance:
30
+
31
+ ```bash
32
+ REPLEN_BASE=https://replen.mydomain.dev npx replen
33
+ ```
34
+
35
+ ## How the auth works
36
+
37
+ 1. CLI generates a random state token and picks a free port (~38xxx)
38
+ 2. Starts a local HTTP server on `127.0.0.1:<port>/callback`
39
+ 3. Opens your browser to `https://app.replen.dev/cli-auth?port=<port>&state=<state>`
40
+ 4. You sign in (Firebase Auth) and click "Authorize CLI on this computer"
41
+ 5. Your browser navigates to `http://127.0.0.1:<port>/callback?token=<ing_...>&state=<state>`
42
+ 6. CLI validates state, saves the token to `~/.replen/config.json` (mode 0600)
43
+ 7. CLI continues and writes the MCP config into `~/.claude.json`
44
+
45
+ The token never transits anything other than your browser ↔ localhost ↔ disk. The replen backend only sees it on subsequent MCP / API requests.
46
+
47
+ ## Revoke
48
+
49
+ Rotate the ingest token on the [/settings](https://app.replen.dev/settings) page. The old one stops working immediately. Then re-run `npx replen` to get a fresh one.
50
+
51
+ ## License
52
+
53
+ MIT — see [LICENSE](./LICENSE).
package/dist/config.js ADDED
@@ -0,0 +1,25 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join, dirname } from "node:path";
4
+ const CONFIG_DIR = join(homedir(), ".replen");
5
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
6
+ export async function readConfig() {
7
+ try {
8
+ const raw = await readFile(CONFIG_FILE, "utf8");
9
+ const parsed = JSON.parse(raw);
10
+ if (typeof parsed?.token === "string" && typeof parsed?.base === "string") {
11
+ return parsed;
12
+ }
13
+ return null;
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ export async function writeConfig(cfg) {
20
+ await mkdir(dirname(CONFIG_FILE), { recursive: true, mode: 0o700 });
21
+ await writeFile(CONFIG_FILE, JSON.stringify(cfg, null, 2) + "\n", { mode: 0o600 });
22
+ }
23
+ export function configPath() {
24
+ return CONFIG_FILE;
25
+ }
package/dist/index.js ADDED
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { runInit } from "./init.js";
3
+ import { setupMcp } from "./mcp-setup.js";
4
+ import { readConfig, configPath } from "./config.js";
5
+ const HELP = `replen — Smart AI Development workflows
6
+
7
+ Usage:
8
+ npx replen Sign up / sign in + wire MCP into Claude Code
9
+ npx replen status Show current config
10
+ npx replen mcp setup Re-wire MCP using saved auth
11
+ npx replen logout Forget saved auth
12
+ npx replen --help This help
13
+
14
+ Env:
15
+ REPLEN_BASE Override dashboard URL (default https://app.replen.dev)
16
+
17
+ Learn more: https://replen.dev
18
+ `;
19
+ async function main() {
20
+ const argv = process.argv.slice(2);
21
+ const cmd = argv[0];
22
+ if (cmd === "--help" || cmd === "-h" || cmd === "help") {
23
+ console.log(HELP);
24
+ return;
25
+ }
26
+ if (cmd === "status") {
27
+ const cfg = await readConfig();
28
+ if (!cfg) {
29
+ console.log(`Not signed in. Run \`npx replen\` to set up.`);
30
+ process.exit(1);
31
+ }
32
+ console.log(`Signed in.`);
33
+ console.log(` Dashboard: ${cfg.base}`);
34
+ console.log(` Token: ${cfg.token.slice(0, 8)}…${cfg.token.slice(-4)}`);
35
+ console.log(` Saved: ${cfg.savedAt}`);
36
+ console.log(` Config: ${configPath()}`);
37
+ return;
38
+ }
39
+ if (cmd === "mcp" && argv[1] === "setup") {
40
+ const cfg = await readConfig();
41
+ if (!cfg) {
42
+ console.error(`Not signed in. Run \`npx replen\` first.`);
43
+ process.exit(1);
44
+ }
45
+ await setupMcp(cfg.token, cfg.base);
46
+ console.log(`\nRestart Claude Code to pick up the change.`);
47
+ return;
48
+ }
49
+ if (cmd === "logout") {
50
+ const { unlink } = await import("node:fs/promises");
51
+ try {
52
+ await unlink(configPath());
53
+ console.log(`Forgot auth at ${configPath()}`);
54
+ }
55
+ catch (e) {
56
+ if (e?.code === "ENOENT") {
57
+ console.log(`No saved auth — nothing to forget.`);
58
+ }
59
+ else {
60
+ throw e;
61
+ }
62
+ }
63
+ console.log(`Note: this only clears local auth. The token is still valid until you rotate it on /settings.`);
64
+ return;
65
+ }
66
+ if (cmd === undefined) {
67
+ // Default: if already signed in, just rerun mcp setup. Otherwise, full flow.
68
+ const cfg = await readConfig();
69
+ if (cfg) {
70
+ console.log(`Already signed in to ${cfg.base}. Re-wiring MCP config…`);
71
+ await setupMcp(cfg.token, cfg.base);
72
+ console.log(`\nDone. Run \`npx replen status\` to inspect.`);
73
+ return;
74
+ }
75
+ await runInit();
76
+ return;
77
+ }
78
+ console.error(`Unknown command: ${cmd}\n`);
79
+ console.error(HELP);
80
+ process.exit(1);
81
+ }
82
+ main().catch((e) => {
83
+ console.error(`\n✗ ${e?.message ?? String(e)}`);
84
+ process.exit(1);
85
+ });
package/dist/init.js ADDED
@@ -0,0 +1,114 @@
1
+ import { createServer } from "node:http";
2
+ import { randomBytes } from "node:crypto";
3
+ import { spawn } from "node:child_process";
4
+ import { platform } from "node:os";
5
+ import { writeConfig, configPath } from "./config.js";
6
+ import { setupMcp } from "./mcp-setup.js";
7
+ // Default web app URL — override with REPLEN_BASE for self-host.
8
+ const DEFAULT_BASE = process.env.REPLEN_BASE || "https://app.replen.dev";
9
+ // Range for the local callback listener. Anything in 1024-65535 works.
10
+ const PORT_MIN = 38000;
11
+ const PORT_MAX = 39000;
12
+ function pickPort() {
13
+ return PORT_MIN + Math.floor(Math.random() * (PORT_MAX - PORT_MIN));
14
+ }
15
+ function openBrowser(url) {
16
+ const cmd = platform() === "darwin" ? "open"
17
+ : platform() === "win32" ? "start"
18
+ : "xdg-open";
19
+ // Detach — we don't care about its exit.
20
+ const proc = spawn(cmd, [url], { stdio: "ignore", detached: true });
21
+ proc.on("error", () => {
22
+ // Fail silently — we print the URL anyway as fallback.
23
+ });
24
+ proc.unref();
25
+ }
26
+ function waitForCallback(port, expectedState) {
27
+ return new Promise((resolve, reject) => {
28
+ const server = createServer((req, res) => {
29
+ const url = new URL(req.url ?? "/", `http://127.0.0.1:${port}`);
30
+ if (url.pathname !== "/callback") {
31
+ res.writeHead(404, { "content-type": "text/plain" });
32
+ res.end("not found");
33
+ return;
34
+ }
35
+ const token = url.searchParams.get("token");
36
+ const state = url.searchParams.get("state");
37
+ const base = url.searchParams.get("base") ?? DEFAULT_BASE;
38
+ if (!token || !state) {
39
+ res.writeHead(400, { "content-type": "text/plain" });
40
+ res.end("missing token/state");
41
+ return;
42
+ }
43
+ if (state !== expectedState) {
44
+ res.writeHead(400, { "content-type": "text/plain" });
45
+ res.end("state mismatch");
46
+ return;
47
+ }
48
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
49
+ res.end(SUCCESS_HTML);
50
+ // Give the response a tick to flush before we shut down the listener.
51
+ setTimeout(() => server.close(), 100);
52
+ resolve({ token, base });
53
+ });
54
+ server.on("error", reject);
55
+ server.listen(port, "127.0.0.1");
56
+ // 5-minute timeout. Browser closed, user wandered off, etc.
57
+ const timeout = setTimeout(() => {
58
+ server.close();
59
+ reject(new Error("Timed out waiting for browser callback (5 min)."));
60
+ }, 5 * 60 * 1000);
61
+ server.on("close", () => clearTimeout(timeout));
62
+ });
63
+ }
64
+ const SUCCESS_HTML = `<!doctype html>
65
+ <html><head><meta charset="utf-8"><title>replen — authorized</title>
66
+ <style>
67
+ body { font: 15px system-ui, -apple-system, sans-serif; max-width: 480px;
68
+ margin: 80px auto; padding: 0 24px; color: #111; line-height: 1.55; }
69
+ h1 { font-size: 22px; margin: 0 0 8px; }
70
+ p { color: #555; margin: 8px 0; }
71
+ .ok { color: #16a34a; font-weight: 600; }
72
+ </style></head>
73
+ <body>
74
+ <h1><span class="ok">✓</span> Authorized</h1>
75
+ <p>The replen CLI is now connected to your account.</p>
76
+ <p>You can close this tab and head back to your terminal — the CLI is finishing setup.</p>
77
+ </body></html>`;
78
+ export async function runInit() {
79
+ const state = randomBytes(32).toString("hex");
80
+ const port = pickPort();
81
+ const base = DEFAULT_BASE;
82
+ const authUrl = `${base}/cli-auth?port=${port}&state=${state}`;
83
+ console.log("");
84
+ console.log(" Opening your browser to sign in to replen…");
85
+ console.log("");
86
+ console.log(` If it doesn't open automatically, visit:`);
87
+ console.log(` ${authUrl}`);
88
+ console.log("");
89
+ console.log(" (Waiting for browser callback on http://127.0.0.1:" + port + "…)");
90
+ console.log("");
91
+ openBrowser(authUrl);
92
+ let result;
93
+ try {
94
+ result = await waitForCallback(port, state);
95
+ }
96
+ catch (e) {
97
+ console.error(" ✗ " + (e?.message ?? String(e)));
98
+ process.exit(1);
99
+ }
100
+ await writeConfig({
101
+ token: result.token,
102
+ base: result.base,
103
+ savedAt: new Date().toISOString(),
104
+ });
105
+ console.log(` ✓ Saved auth to ${configPath()}`);
106
+ await setupMcp(result.token, result.base);
107
+ console.log("");
108
+ console.log(" All set. Restart Claude Code (or Codex) and try:");
109
+ console.log(" /replen-triage — runs the morning triage protocol");
110
+ console.log(" use replen to digest_today — pulls today's matches");
111
+ console.log("");
112
+ console.log(` Dashboard: ${result.base}`);
113
+ console.log("");
114
+ }
@@ -0,0 +1,52 @@
1
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, dirname } from "node:path";
4
+ // Write the @replen/mcp server entry into Claude Code's config. Uses npx so
5
+ // the user doesn't need a separate global install — Claude Code will fetch
6
+ // @replen/mcp on first MCP launch and cache it.
7
+ const SERVER_NAME = "replen";
8
+ const CONFIG_PATH = join(homedir(), ".claude.json");
9
+ function readJson(path) {
10
+ if (!existsSync(path))
11
+ return {};
12
+ const raw = readFileSync(path, "utf8");
13
+ if (!raw.trim())
14
+ return {};
15
+ return JSON.parse(raw);
16
+ }
17
+ function writeJsonAtomic(path, data) {
18
+ mkdirSync(dirname(path), { recursive: true });
19
+ const tmp = `${path}.tmp.${Date.now()}`;
20
+ writeFileSync(tmp, JSON.stringify(data, null, 2));
21
+ renameSync(tmp, path);
22
+ }
23
+ export async function setupMcp(token, base) {
24
+ console.log(` Wiring replen MCP into Claude Code config…`);
25
+ if (existsSync(CONFIG_PATH)) {
26
+ const backup = `${CONFIG_PATH}.bak`;
27
+ writeFileSync(backup, readFileSync(CONFIG_PATH));
28
+ console.log(` (backed up existing config to ${backup})`);
29
+ }
30
+ let config;
31
+ try {
32
+ config = readJson(CONFIG_PATH);
33
+ }
34
+ catch (e) {
35
+ console.error(` ✗ ${CONFIG_PATH} is not valid JSON: ${e.message}`);
36
+ console.error(` Fix the file manually and run \`replen mcp setup\`.`);
37
+ process.exit(1);
38
+ }
39
+ const mcpServers = config.mcpServers ?? {};
40
+ const overwrite = !!mcpServers[SERVER_NAME];
41
+ mcpServers[SERVER_NAME] = {
42
+ type: "stdio",
43
+ command: "npx",
44
+ args: ["-y", "@replen/mcp"],
45
+ env: {
46
+ DIGEST_BASE_URL: base,
47
+ DIGEST_TOKEN: token,
48
+ },
49
+ };
50
+ writeJsonAtomic(CONFIG_PATH, { ...config, mcpServers });
51
+ console.log(` ✓ ${overwrite ? "Updated" : "Added"} "${SERVER_NAME}" in ${CONFIG_PATH}`);
52
+ }
package/package.json CHANGED
@@ -1,10 +1,36 @@
1
1
  {
2
2
  "name": "replen",
3
- "version": "0.0.1",
4
- "description": "Placeholder — the replen toolkit (MCP server, CLI, web dashboard) is being prepared for OSS release. Visit https://replen.dev when live.",
5
- "keywords": ["placeholder", "oss-digest", "mcp", "claude-code", "codex"],
3
+ "version": "0.1.1",
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.",
5
+ "type": "module",
6
+ "bin": {
7
+ "replen": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc && chmod +x dist/index.js",
11
+ "dev": "tsx src/index.ts",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "keywords": ["replen", "oss-discovery", "claude-code", "mcp", "cli"],
6
20
  "homepage": "https://replen.dev",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/replenhq/replen.git",
24
+ "directory": "cli"
25
+ },
26
+ "bugs": "https://github.com/replenhq/replen/issues",
7
27
  "license": "MIT",
8
- "main": "index.js",
9
- "files": ["index.js", "README.md", "LICENSE"]
28
+ "engines": {
29
+ "node": ">=18"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.10.5",
33
+ "tsx": "^4.19.2",
34
+ "typescript": "^5.7.2"
35
+ }
10
36
  }
package/index.js DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env node
2
- // Placeholder for the replen toolkit. The actual packages (CLI, MCP server)
3
- // will ship under the @replen scope.
4
- console.error("This is a placeholder. See https://replen.dev — actual packages: @replen/mcp, @replen/cli (coming soon).");
5
- process.exit(0);