replen 0.4.1 → 1.0.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 +42 -20
- package/dist/commands.js +83 -0
- package/dist/discover-projects.js +391 -0
- package/dist/discover-roots.js +169 -0
- package/dist/first-ingest.js +140 -0
- package/dist/index.js +56 -0
- package/dist/init.js +23 -3
- package/dist/inject-instruction.js +327 -0
- package/dist/mcp-setup.js +163 -45
- package/dist/skill-install.js +58 -0
- package/dist/sync-projects.js +195 -0
- package/extras/skills/replen-match/SKILL.md +230 -0
- package/package.json +6 -2
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Layered project-root discovery. Tries cheap inference first, falls back
|
|
2
|
+
// to interactive prompt if nothing works. Result is one or more root dirs
|
|
3
|
+
// that the recursive walker in discover-projects.ts will then scan for
|
|
4
|
+
// git repos.
|
|
5
|
+
//
|
|
6
|
+
// Order (cheapest / most-targeted first):
|
|
7
|
+
// 1. Explicit --root flag(s) → user-specified
|
|
8
|
+
// 2. REPLEN_PROJECT_ROOTS env var → scripted / dotfiles
|
|
9
|
+
// 3. Saved ~/.replen/config.json roots → previously confirmed
|
|
10
|
+
// 4. cwd walk-up → "I'm in a repo right now"
|
|
11
|
+
// 5. ~/.claude.json mining → "I use Claude Code regularly"
|
|
12
|
+
// 6. Hardcoded ~/github, ~/code, ~/projects → conventional layout
|
|
13
|
+
// 7. Interactive prompt → ask the user directly
|
|
14
|
+
//
|
|
15
|
+
// The orchestration in sync-projects.ts tries strategies in order and
|
|
16
|
+
// keeps going until at least one git repo is found, then stops looking.
|
|
17
|
+
import { readFileSync, existsSync, statSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { join, dirname, resolve } from "node:path";
|
|
20
|
+
import { createInterface } from "node:readline/promises";
|
|
21
|
+
import { readConfig } from "./config.js";
|
|
22
|
+
/** From explicit `--root <path>` CLI flag(s), or empty if not passed. */
|
|
23
|
+
export function rootsFromFlag(flagValues) {
|
|
24
|
+
return flagValues.map(expandTilde).filter(existsAndIsDir);
|
|
25
|
+
}
|
|
26
|
+
/** From `REPLEN_PROJECT_ROOTS` env var (colon-separated). */
|
|
27
|
+
export function rootsFromEnv() {
|
|
28
|
+
const env = process.env.REPLEN_PROJECT_ROOTS;
|
|
29
|
+
if (!env)
|
|
30
|
+
return [];
|
|
31
|
+
return env.split(":").map((s) => s.trim()).filter(Boolean).map(expandTilde).filter(existsAndIsDir);
|
|
32
|
+
}
|
|
33
|
+
/** From the user's persisted ~/.replen/config.json `projectRoots`. */
|
|
34
|
+
export async function rootsFromConfig() {
|
|
35
|
+
const cfg = await readConfig();
|
|
36
|
+
if (!cfg?.projectRoots?.length)
|
|
37
|
+
return [];
|
|
38
|
+
return cfg.projectRoots.map(expandTilde).filter(existsAndIsDir);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* If `npx replen` was invoked from inside a git repo (or one of its
|
|
42
|
+
* subdirs), walk up to find the repo root, then return its parent dir
|
|
43
|
+
* as a candidate root. This catches the common "I'm sitting in one of
|
|
44
|
+
* my projects when I install Replen" case.
|
|
45
|
+
*
|
|
46
|
+
* Stops at `homedir()` and at `/` to avoid walking into system dirs.
|
|
47
|
+
*/
|
|
48
|
+
export function rootsFromCwdWalkUp() {
|
|
49
|
+
const cwd = process.cwd();
|
|
50
|
+
const home = homedir();
|
|
51
|
+
let dir = cwd;
|
|
52
|
+
// Walk up until we find a .git/ or hit home/root.
|
|
53
|
+
while (dir !== "/" && dir !== home && dir.length > 1) {
|
|
54
|
+
if (existsSync(join(dir, ".git"))) {
|
|
55
|
+
const parent = dirname(dir);
|
|
56
|
+
// Only suggest the parent if it's still inside home — never offer
|
|
57
|
+
// "/" or anything above $HOME as a scan root.
|
|
58
|
+
if (parent.startsWith(home) && parent !== home)
|
|
59
|
+
return [parent];
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
dir = dirname(dir);
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Read ~/.claude.json (Claude Code's per-user config) and extract the
|
|
68
|
+
* project cwds it tracks. Group by parent dir. Return parents that
|
|
69
|
+
* contain ≥2 Claude Code projects — these are very likely the user's
|
|
70
|
+
* actual repo roots.
|
|
71
|
+
*
|
|
72
|
+
* Why this is high-signal: Claude Code only adds an entry when the user
|
|
73
|
+
* opens it inside a real project, so the cwd list is curated by their
|
|
74
|
+
* actual usage. Far more accurate than guessing dir names.
|
|
75
|
+
*/
|
|
76
|
+
export function rootsFromClaudeJson() {
|
|
77
|
+
const claudePath = join(homedir(), ".claude.json");
|
|
78
|
+
if (!existsSync(claudePath))
|
|
79
|
+
return [];
|
|
80
|
+
let data;
|
|
81
|
+
try {
|
|
82
|
+
data = JSON.parse(readFileSync(claudePath, "utf8"));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
if (!data || typeof data !== "object")
|
|
88
|
+
return [];
|
|
89
|
+
const projects = data.projects;
|
|
90
|
+
if (!projects || typeof projects !== "object")
|
|
91
|
+
return [];
|
|
92
|
+
const parentCounts = new Map();
|
|
93
|
+
for (const path of Object.keys(projects)) {
|
|
94
|
+
if (!path.startsWith("/"))
|
|
95
|
+
continue;
|
|
96
|
+
const parent = dirname(path);
|
|
97
|
+
if (!parent.startsWith(homedir()))
|
|
98
|
+
continue;
|
|
99
|
+
parentCounts.set(parent, (parentCounts.get(parent) ?? 0) + 1);
|
|
100
|
+
}
|
|
101
|
+
return Array.from(parentCounts.entries())
|
|
102
|
+
.filter(([, n]) => n >= 2)
|
|
103
|
+
.sort((a, b) => b[1] - a[1])
|
|
104
|
+
.slice(0, 5)
|
|
105
|
+
.map(([parent]) => parent)
|
|
106
|
+
.filter(existsAndIsDir);
|
|
107
|
+
}
|
|
108
|
+
/** Conventional layout: ~/github, ~/code, ~/projects. */
|
|
109
|
+
export function rootsFromHardcoded() {
|
|
110
|
+
return ["github", "code", "projects", "Code", "Projects", "dev", "src", "work"]
|
|
111
|
+
.map((n) => join(homedir(), n))
|
|
112
|
+
.filter(existsAndIsDir);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Last-resort interactive prompt. Asks the user where they keep their
|
|
116
|
+
* code, with a sensible default. Returns null if the user is in a
|
|
117
|
+
* non-interactive context (stdin not a TTY) or hits Ctrl-C.
|
|
118
|
+
*/
|
|
119
|
+
export async function promptForRoot() {
|
|
120
|
+
if (!process.stdin.isTTY)
|
|
121
|
+
return null;
|
|
122
|
+
const home = homedir();
|
|
123
|
+
// Suggest the most common parent of any folder we know about, falling
|
|
124
|
+
// back to home itself.
|
|
125
|
+
const suggestion = pickPromptSuggestion() ?? home;
|
|
126
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
127
|
+
try {
|
|
128
|
+
const answer = (await rl.question(`\n Where do you keep your code? [${suggestion}]: `)).trim();
|
|
129
|
+
const chosen = answer || suggestion;
|
|
130
|
+
const expanded = expandTilde(chosen);
|
|
131
|
+
if (!existsAndIsDir(expanded)) {
|
|
132
|
+
console.warn(` ✗ Not a directory: ${expanded}`);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return expanded;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
finally {
|
|
141
|
+
rl.close();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
function pickPromptSuggestion() {
|
|
145
|
+
const fromCwd = rootsFromCwdWalkUp();
|
|
146
|
+
if (fromCwd[0])
|
|
147
|
+
return fromCwd[0];
|
|
148
|
+
const fromClaude = rootsFromClaudeJson();
|
|
149
|
+
if (fromClaude[0])
|
|
150
|
+
return fromClaude[0];
|
|
151
|
+
const fromHard = rootsFromHardcoded();
|
|
152
|
+
if (fromHard[0])
|
|
153
|
+
return fromHard[0];
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function expandTilde(p) {
|
|
157
|
+
if (p === "~" || p.startsWith("~/")) {
|
|
158
|
+
return p === "~" ? homedir() : join(homedir(), p.slice(2));
|
|
159
|
+
}
|
|
160
|
+
return resolve(p);
|
|
161
|
+
}
|
|
162
|
+
function existsAndIsDir(p) {
|
|
163
|
+
try {
|
|
164
|
+
return statSync(p).isDirectory();
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// First-time ingest streamer used by `npx replen` after install.
|
|
2
|
+
//
|
|
3
|
+
// Triggers a pipeline run and tails progress until the discovered pool
|
|
4
|
+
// is ready (event signal: `fetch_done` with "Discovered pool" message,
|
|
5
|
+
// or candidates count crosses 0). Then exits cleanly. Stages 1+2 and
|
|
6
|
+
// the scouted-pool fetcher continue server-side in the background;
|
|
7
|
+
// the user can do `npx replen progress` later if they want to watch
|
|
8
|
+
// the rest, or just open Claude Code and the agent will find whatever
|
|
9
|
+
// landed.
|
|
10
|
+
//
|
|
11
|
+
// Why exit early instead of waiting for full pipeline completion: a
|
|
12
|
+
// fresh install with 30+ projects can take 5-10 min for Stage 1+2
|
|
13
|
+
// per-project LLM work to finish. The user shouldn't sit at a blank
|
|
14
|
+
// terminal — they want to know "is there something I can look at now?"
|
|
15
|
+
// and the answer becomes yes once the discovered pool lands (typically
|
|
16
|
+
// 30-60s in).
|
|
17
|
+
//
|
|
18
|
+
// Tolerant of rate-limit and in-flight responses; never fails the
|
|
19
|
+
// install flow on its own — worst case prints a "you can run
|
|
20
|
+
// `npx replen progress` later" hint and returns.
|
|
21
|
+
import { apiGet, apiPost } from "./api.js";
|
|
22
|
+
const POLL_INTERVAL_MS = 2500;
|
|
23
|
+
// Absolute cap on how long we sit in the streamer. Discovered pool
|
|
24
|
+
// usually lands inside 60s but giving generous headroom for slow
|
|
25
|
+
// fetcher tails. If we hit this, we exit with a hint rather than
|
|
26
|
+
// blocking the install flow.
|
|
27
|
+
const MAX_WAIT_MS = 180_000;
|
|
28
|
+
export async function runFirstIngest(cfg) {
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log(" Pulling first batch of candidates from your sources…");
|
|
31
|
+
console.log(" (This takes about a minute. You can `^C` here — ingest continues server-side.)");
|
|
32
|
+
console.log("");
|
|
33
|
+
// Step 1: trigger a run. Tolerant of in-flight / rate-limit.
|
|
34
|
+
let triggered = null;
|
|
35
|
+
try {
|
|
36
|
+
triggered = await apiPost(cfg, "/api/mcp/run-now", {});
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Surface but don't fail install — the cron scheduler will pick
|
|
40
|
+
// up the new projects on its next tick.
|
|
41
|
+
console.log(` · Couldn't trigger an immediate run (${e.message.slice(0, 80)}).`);
|
|
42
|
+
console.log(` The cron scheduler will catch up shortly. Open Claude Code in a tracked repo`);
|
|
43
|
+
console.log(` in a few minutes and the agent will mention any new matches.`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (!triggered.ok && triggered.status !== "in_flight") {
|
|
47
|
+
console.log(` · Run not started: ${triggered.reason ?? triggered.status}`);
|
|
48
|
+
console.log(` The next scheduled run will pick up the new projects.`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Step 2: poll status, print events, exit when discovered pool ready.
|
|
52
|
+
let since = 0;
|
|
53
|
+
let firstTick = true;
|
|
54
|
+
let lastCandidates = 0;
|
|
55
|
+
let startedAt = Date.now();
|
|
56
|
+
let discoveredReadyAnnounced = false;
|
|
57
|
+
while (true) {
|
|
58
|
+
if (Date.now() - startedAt > MAX_WAIT_MS) {
|
|
59
|
+
console.log("");
|
|
60
|
+
console.log(" · Ingest still going — exiting the streamer so you're not blocked.");
|
|
61
|
+
console.log(" Run `npx replen progress` to keep watching, or just open Claude Code.");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
let status;
|
|
65
|
+
try {
|
|
66
|
+
status = await apiGet(cfg, "/api/mcp/status", since ? { since } : undefined);
|
|
67
|
+
}
|
|
68
|
+
catch (e) {
|
|
69
|
+
console.log(` · Lost connection to status endpoint: ${e.message.slice(0, 80)}`);
|
|
70
|
+
console.log(` Ingest still running. Open Claude Code shortly to see matches.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (firstTick) {
|
|
74
|
+
const phase = status.phase ?? "starting";
|
|
75
|
+
console.log(` Run #${status.runId ?? "?"} · ${status.inFlight ? `running · phase=${phase}` : "idle"}`);
|
|
76
|
+
firstTick = false;
|
|
77
|
+
}
|
|
78
|
+
for (const ev of status.events ?? []) {
|
|
79
|
+
console.log(` ${marker(ev.kind)} ${ev.message}`);
|
|
80
|
+
if (ev.id > since)
|
|
81
|
+
since = ev.id;
|
|
82
|
+
// The Phase-1 completion signal: pipeline emits a `fetch_done`
|
|
83
|
+
// event whose message starts with "Discovered pool:". Once we
|
|
84
|
+
// see that, the discovered candidates are persisted and visible
|
|
85
|
+
// to replen_match.
|
|
86
|
+
if (ev.kind === "fetch_done" &&
|
|
87
|
+
ev.message.startsWith("Discovered pool:") &&
|
|
88
|
+
!discoveredReadyAnnounced) {
|
|
89
|
+
discoveredReadyAnnounced = true;
|
|
90
|
+
// Don't exit immediately — let the next poll catch any inline
|
|
91
|
+
// events so the user sees a coherent end state, then bail.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// Fallback signal: if candidates count crossed 0, the discovered
|
|
95
|
+
// pool is functionally ready even if the event ordering didn't
|
|
96
|
+
// emit the expected marker. Belt + suspenders.
|
|
97
|
+
if (!discoveredReadyAnnounced && (status.candidates ?? 0) > lastCandidates && (status.candidates ?? 0) > 0) {
|
|
98
|
+
discoveredReadyAnnounced = true;
|
|
99
|
+
}
|
|
100
|
+
lastCandidates = status.candidates ?? lastCandidates;
|
|
101
|
+
// If the whole pipeline already finished (e.g. existing user with
|
|
102
|
+
// cached vectors — runs end fast), exit cleanly.
|
|
103
|
+
if (!status.inFlight) {
|
|
104
|
+
console.log("");
|
|
105
|
+
console.log(` ✓ Ingest complete · ${status.candidates ?? 0} candidate(s) in inventory.`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// If discovered pool is ready, exit and let the rest run server-
|
|
109
|
+
// side. Stage 1+2 + scouted pool take much longer; no point making
|
|
110
|
+
// the user watch them.
|
|
111
|
+
if (discoveredReadyAnnounced) {
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(` ✓ First candidates ready (${status.candidates ?? "?"} so far).`);
|
|
114
|
+
console.log(` Per-project relevance refinement continues in the background.`);
|
|
115
|
+
console.log(` Run \`npx replen progress\` later to watch, or just open Claude Code.`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
await sleep(POLL_INTERVAL_MS);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function marker(kind) {
|
|
122
|
+
switch (kind) {
|
|
123
|
+
case "fetch_start":
|
|
124
|
+
case "fetch_done":
|
|
125
|
+
return "›";
|
|
126
|
+
case "scan":
|
|
127
|
+
return "·";
|
|
128
|
+
case "match":
|
|
129
|
+
return "✓";
|
|
130
|
+
case "skip":
|
|
131
|
+
return "·";
|
|
132
|
+
case "error":
|
|
133
|
+
return "✗";
|
|
134
|
+
default:
|
|
135
|
+
return "·";
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function sleep(ms) {
|
|
139
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
140
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,15 @@ Usage:
|
|
|
12
12
|
npx replen mcp setup Re-wire MCP using saved auth
|
|
13
13
|
npx replen project-init Print a prompt your AI coding tool uses to draft
|
|
14
14
|
a CLAUDE.md tuned for replen
|
|
15
|
+
npx replen inject [-y] Append the "## Replen integration" section to
|
|
16
|
+
every CLAUDE.md + AGENTS.md (Claude Code +
|
|
17
|
+
Codex) under ~/github/, ~/code/, ~/projects/
|
|
18
|
+
so the agent auto-surfaces matches on session
|
|
19
|
+
start. Idempotent. Asks for consent unless -y.
|
|
20
|
+
npx replen sync-projects Re-scan local repos for new GitHub remotes
|
|
21
|
+
[--root PATH ...] and register them with Replen. Run after
|
|
22
|
+
cloning a new repo, or pass --root to point
|
|
23
|
+
at a non-conventional layout.
|
|
15
24
|
npx replen logout Forget saved auth
|
|
16
25
|
npx replen --help This help
|
|
17
26
|
|
|
@@ -39,6 +48,14 @@ Every data command accepts --json to dump raw JSON for scripting / jq.
|
|
|
39
48
|
|
|
40
49
|
Env:
|
|
41
50
|
REPLEN_BASE Override dashboard URL (default https://app.replen.dev)
|
|
51
|
+
REPLEN_PROJECT_ROOTS Colon-separated list of dirs to scan for git repos.
|
|
52
|
+
Overrides auto-detection. e.g. ~/work:~/src
|
|
53
|
+
|
|
54
|
+
Project discovery (when run without --root and REPLEN_PROJECT_ROOTS unset):
|
|
55
|
+
1. Walks up from the current dir to find a git repo, suggests its parent
|
|
56
|
+
2. Reads ~/.claude.json for tracked Claude Code project paths
|
|
57
|
+
3. Falls back to ~/github, ~/code, ~/projects, ~/dev, ~/src, ~/work
|
|
58
|
+
4. Prompts you interactively if none of the above turn up anything
|
|
42
59
|
|
|
43
60
|
Learn more: https://replen.dev
|
|
44
61
|
`;
|
|
@@ -92,6 +109,27 @@ async function main() {
|
|
|
92
109
|
console.log(`Note: this only clears local auth. The token is still valid until you rotate it on /settings.`);
|
|
93
110
|
return;
|
|
94
111
|
}
|
|
112
|
+
if (cmd === "inject") {
|
|
113
|
+
const { injectInstructions, summariseOutcome } = await import("./inject-instruction.js");
|
|
114
|
+
const yes = argv.includes("--yes") || argv.includes("-y");
|
|
115
|
+
const explicitRoots = collectRootFlags(argv);
|
|
116
|
+
const outcome = await injectInstructions({ yes, explicitRoots });
|
|
117
|
+
const summary = summariseOutcome(outcome);
|
|
118
|
+
if (summary)
|
|
119
|
+
console.log(summary);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (cmd === "sync-projects" || cmd === "sync") {
|
|
123
|
+
const cfg = await readConfig();
|
|
124
|
+
if (!cfg) {
|
|
125
|
+
console.error("Not signed in. Run `npx replen` first.");
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const explicitRoots = collectRootFlags(argv);
|
|
129
|
+
const { syncDiscoveredProjects } = await import("./sync-projects.js");
|
|
130
|
+
await syncDiscoveredProjects({ token: cfg.token, base: cfg.base, explicitRoots });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
95
133
|
if (cmd === "run")
|
|
96
134
|
return runRun(argv);
|
|
97
135
|
if (cmd === "progress")
|
|
@@ -124,6 +162,24 @@ async function main() {
|
|
|
124
162
|
console.error(HELP);
|
|
125
163
|
process.exit(1);
|
|
126
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Parse one or more `--root <path>` (or `--root=<path>`) flags out of
|
|
167
|
+
* the argv tail. Doesn't validate the path — discover-roots.ts handles
|
|
168
|
+
* existence + stat checks downstream.
|
|
169
|
+
*/
|
|
170
|
+
function collectRootFlags(argv) {
|
|
171
|
+
const out = [];
|
|
172
|
+
for (let i = 0; i < argv.length; i++) {
|
|
173
|
+
const arg = argv[i];
|
|
174
|
+
if (arg === "--root" && argv[i + 1]) {
|
|
175
|
+
out.push(argv[++i]);
|
|
176
|
+
}
|
|
177
|
+
else if (arg.startsWith("--root=")) {
|
|
178
|
+
out.push(arg.slice("--root=".length));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
127
183
|
main().catch((e) => {
|
|
128
184
|
console.error(`\n✗ ${e?.message ?? String(e)}`);
|
|
129
185
|
process.exit(1);
|
package/dist/init.js
CHANGED
|
@@ -142,10 +142,30 @@ export async function runInit() {
|
|
|
142
142
|
});
|
|
143
143
|
console.log(` ✓ Saved auth to ${configPath()}`);
|
|
144
144
|
await setupMcp(exchange.token, exchange.base);
|
|
145
|
+
// Phase A: auto-discover the user's local projects, extract tags
|
|
146
|
+
// from manifests, and register them in one shot. Replaces the
|
|
147
|
+
// legacy "paste a GitHub PAT and let us call api.github.com" flow
|
|
148
|
+
// for project discovery.
|
|
145
149
|
console.log("");
|
|
146
|
-
console.log("
|
|
147
|
-
|
|
148
|
-
|
|
150
|
+
console.log(" Scanning your local repos for projects…");
|
|
151
|
+
const { syncDiscoveredProjects } = await import("./sync-projects.js");
|
|
152
|
+
await syncDiscoveredProjects({ token: exchange.token, base: exchange.base });
|
|
153
|
+
// Phase B: trigger the first ingest and stream progress until the
|
|
154
|
+
// discovered pool is ready (~30-60s). Without this, a new user
|
|
155
|
+
// would open Claude Code and find replen_match returning nothing —
|
|
156
|
+
// the server-side cron hasn't run yet for the just-registered
|
|
157
|
+
// projects. Streaming gives them visible activity AND ensures
|
|
158
|
+
// there's something to surface by the time they get to Claude Code.
|
|
159
|
+
const { runFirstIngest } = await import("./first-ingest.js");
|
|
160
|
+
await runFirstIngest({ token: exchange.token, base: exchange.base, savedAt: "" });
|
|
161
|
+
console.log("");
|
|
162
|
+
console.log(" All set. Restart Claude Code and try:");
|
|
163
|
+
console.log(" /replen-match → triage today's candidates against this repo,");
|
|
164
|
+
console.log(" in-session, using your subscription tokens");
|
|
165
|
+
console.log(" (no LLM API keys needed — the agent does the reasoning)");
|
|
166
|
+
console.log("");
|
|
167
|
+
console.log(" Other MCP hosts (Codex / Cursor / Aider):");
|
|
168
|
+
console.log(" \"use replen_match\" — same tool, no slash command");
|
|
149
169
|
console.log("");
|
|
150
170
|
console.log(` Dashboard: ${exchange.base}`);
|
|
151
171
|
console.log("");
|