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 +49 -5
- package/dist/config.js +25 -0
- package/dist/index.js +85 -0
- package/dist/init.js +114 -0
- package/dist/mcp-setup.js +52 -0
- package/package.json +31 -5
- package/index.js +0 -5
package/README.md
CHANGED
|
@@ -1,9 +1,53 @@
|
|
|
1
1
|
# replen
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npx replen
|
|
7
|
+
```
|
|
8
8
|
|
|
9
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
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
|
-
"
|
|
9
|
-
|
|
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);
|