repowise 0.1.32 → 0.1.34

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.
@@ -1,102 +1,2860 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from 'node:fs';
3
- import { fileURLToPath } from 'node:url';
4
- import { dirname, join } from 'node:path';
5
- import { Command } from 'commander';
6
- import { setStagingMode } from '../src/lib/env.js';
7
- import { showWelcome } from '../src/lib/welcome.js';
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
- const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
11
- const program = new Command();
12
- program
13
- .name('repowise')
14
- .description('AI-optimized codebase context generator')
15
- .version(pkg.version)
16
- .option('--staging', 'Use the staging environment')
17
- .hook('preAction', async () => {
18
- if (program.opts()['staging']) {
19
- setStagingMode(true);
20
- }
21
- await showWelcome(pkg.version);
22
- });
23
- program
24
- .command('create')
25
- .description('Create context for a repository')
26
- .option('--context-storage <mode>', 'Context storage mode: git or server (skips prompt)')
27
- .action(async (options) => {
28
- const { create } = await import('../src/commands/create.js');
29
- const validModes = ['git', 'server'];
30
- const mode = options.contextStorage;
31
- await create({
32
- contextStorage: mode && validModes.includes(mode) ? mode : undefined,
2
+ #!/usr/bin/env node
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __esm = (fn, res) => function __init() {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ };
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+
13
+ // src/lib/env.ts
14
+ function setStagingMode(value) {
15
+ staging = value;
16
+ }
17
+ function getEnvConfig() {
18
+ return staging ? STAGING : PRODUCTION;
19
+ }
20
+ var staging, PRODUCTION, STAGING;
21
+ var init_env = __esm({
22
+ "src/lib/env.ts"() {
23
+ "use strict";
24
+ staging = false;
25
+ PRODUCTION = {
26
+ apiUrl: "https://api.repowise.ai",
27
+ cognitoDomain: "auth.repowise.ai",
28
+ cognitoClientId: "",
29
+ // TODO: set after production Cognito deploy
30
+ cognitoRegion: "us-east-1",
31
+ customDomain: true
32
+ };
33
+ STAGING = {
34
+ apiUrl: "https://staging-api.repowise.ai",
35
+ cognitoDomain: "auth-staging.repowise.ai",
36
+ cognitoClientId: "7h0l0dhjcb1v5erer0gaclv0q6",
37
+ cognitoRegion: "us-east-1",
38
+ customDomain: true
39
+ };
40
+ }
41
+ });
42
+
43
+ // src/lib/config.ts
44
+ import { readFile, writeFile, mkdir } from "fs/promises";
45
+ import { homedir } from "os";
46
+ import { join } from "path";
47
+ async function getConfig() {
48
+ try {
49
+ const data = await readFile(CONFIG_PATH, "utf-8");
50
+ return JSON.parse(data);
51
+ } catch {
52
+ return {};
53
+ }
54
+ }
55
+ async function saveConfig(config2) {
56
+ await mkdir(CONFIG_DIR, { recursive: true });
57
+ await writeFile(CONFIG_PATH, JSON.stringify(config2, null, 2));
58
+ }
59
+ var CONFIG_DIR, CONFIG_PATH;
60
+ var init_config = __esm({
61
+ "src/lib/config.ts"() {
62
+ "use strict";
63
+ CONFIG_DIR = join(homedir(), ".repowise");
64
+ CONFIG_PATH = join(CONFIG_DIR, "config.json");
65
+ }
66
+ });
67
+
68
+ // src/lib/auth.ts
69
+ import { createHash, randomBytes } from "crypto";
70
+ import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod, unlink } from "fs/promises";
71
+ import http from "http";
72
+ import { homedir as homedir2 } from "os";
73
+ import { join as join2 } from "path";
74
+ function getCognitoConfig() {
75
+ const env = getEnvConfig();
76
+ return {
77
+ domain: process.env["REPOWISE_COGNITO_DOMAIN"] ?? env.cognitoDomain,
78
+ clientId: process.env["REPOWISE_COGNITO_CLIENT_ID"] ?? env.cognitoClientId,
79
+ region: process.env["REPOWISE_COGNITO_REGION"] ?? env.cognitoRegion,
80
+ customDomain: env.customDomain
81
+ };
82
+ }
83
+ function getCognitoBaseUrl() {
84
+ const { domain, region, customDomain } = getCognitoConfig();
85
+ return customDomain ? `https://${domain}` : `https://${domain}.auth.${region}.amazoncognito.com`;
86
+ }
87
+ function generateCodeVerifier() {
88
+ return randomBytes(32).toString("base64url");
89
+ }
90
+ function generateCodeChallenge(verifier) {
91
+ return createHash("sha256").update(verifier).digest("base64url");
92
+ }
93
+ function generateState() {
94
+ return randomBytes(32).toString("hex");
95
+ }
96
+ function getAuthorizeUrl(codeChallenge, state) {
97
+ const { clientId } = getCognitoConfig();
98
+ if (!clientId) {
99
+ throw new Error(
100
+ "Missing REPOWISE_COGNITO_CLIENT_ID environment variable. Configure it before running login."
101
+ );
102
+ }
103
+ const params = new URLSearchParams({
104
+ response_type: "code",
105
+ client_id: clientId,
106
+ redirect_uri: `http://localhost:${CLI_CALLBACK_PORT}/callback`,
107
+ code_challenge: codeChallenge,
108
+ code_challenge_method: "S256",
109
+ scope: "openid email profile",
110
+ state
111
+ });
112
+ return `${getCognitoBaseUrl()}/oauth2/authorize?${params.toString()}`;
113
+ }
114
+ function getTokenUrl() {
115
+ return `${getCognitoBaseUrl()}/oauth2/token`;
116
+ }
117
+ function startCallbackServer() {
118
+ return new Promise((resolve, reject) => {
119
+ const server = http.createServer((req, res) => {
120
+ const url = new URL(req.url, `http://localhost:${CLI_CALLBACK_PORT}`);
121
+ if (url.pathname !== "/callback") {
122
+ res.writeHead(404);
123
+ res.end();
124
+ return;
125
+ }
126
+ const code = url.searchParams.get("code");
127
+ const state = url.searchParams.get("state");
128
+ const error = url.searchParams.get("error");
129
+ if (error) {
130
+ res.writeHead(200, { "Content-Type": "text/html" });
131
+ res.end(
132
+ callbackPage(
133
+ "Authentication Failed",
134
+ "Something went wrong. Please close this tab and try again.",
135
+ true
136
+ )
137
+ );
138
+ server.close();
139
+ reject(new Error(`Authentication error: ${error}`));
140
+ return;
141
+ }
142
+ if (!code || !state) {
143
+ res.writeHead(400, { "Content-Type": "text/html" });
144
+ res.end(
145
+ callbackPage(
146
+ "Missing Parameters",
147
+ "The callback was missing required data. Please close this tab and try again.",
148
+ true
149
+ )
150
+ );
151
+ server.close();
152
+ reject(new Error("Missing code or state in callback"));
153
+ return;
154
+ }
155
+ res.writeHead(200, { "Content-Type": "text/html" });
156
+ res.end(
157
+ callbackPage(
158
+ "Authentication Successful",
159
+ "You can close this tab and return to the terminal.",
160
+ false
161
+ )
162
+ );
163
+ server.close();
164
+ resolve({ code, state });
165
+ });
166
+ server.listen(CLI_CALLBACK_PORT, "127.0.0.1");
167
+ server.on("error", (err) => {
168
+ if (err.code === "EADDRINUSE") {
169
+ reject(
170
+ new Error(
171
+ `Port ${CLI_CALLBACK_PORT} is already in use. Close the conflicting process and try again.`
172
+ )
173
+ );
174
+ } else {
175
+ reject(err);
176
+ }
177
+ });
178
+ const timeout = setTimeout(() => {
179
+ server.close();
180
+ reject(new Error("Authentication timed out. Please try again."));
181
+ }, CALLBACK_TIMEOUT_MS);
182
+ server.on("close", () => clearTimeout(timeout));
183
+ });
184
+ }
185
+ async function exchangeCodeForTokens(code, codeVerifier) {
186
+ const response = await fetch(getTokenUrl(), {
187
+ method: "POST",
188
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
189
+ body: new URLSearchParams({
190
+ grant_type: "authorization_code",
191
+ client_id: getCognitoConfig().clientId,
192
+ redirect_uri: `http://localhost:${CLI_CALLBACK_PORT}/callback`,
193
+ code,
194
+ code_verifier: codeVerifier
195
+ })
196
+ });
197
+ if (!response.ok) {
198
+ const text = await response.text();
199
+ throw new Error(`Token exchange failed: ${response.status} ${text}`);
200
+ }
201
+ const data = await response.json();
202
+ return {
203
+ accessToken: data.access_token,
204
+ refreshToken: data.refresh_token,
205
+ idToken: data.id_token,
206
+ expiresAt: Date.now() + data.expires_in * 1e3
207
+ };
208
+ }
209
+ async function refreshTokens(refreshToken) {
210
+ const response = await fetch(getTokenUrl(), {
211
+ method: "POST",
212
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
213
+ body: new URLSearchParams({
214
+ grant_type: "refresh_token",
215
+ client_id: getCognitoConfig().clientId,
216
+ refresh_token: refreshToken
217
+ })
218
+ });
219
+ if (!response.ok) {
220
+ throw new Error(`Token refresh failed: ${response.status}`);
221
+ }
222
+ const data = await response.json();
223
+ return {
224
+ accessToken: data.access_token,
225
+ refreshToken,
226
+ // Cognito does not return a new refresh token
227
+ idToken: data.id_token,
228
+ expiresAt: Date.now() + data.expires_in * 1e3
229
+ };
230
+ }
231
+ async function getStoredCredentials() {
232
+ try {
233
+ const data = await readFile2(CREDENTIALS_PATH, "utf-8");
234
+ return JSON.parse(data);
235
+ } catch (err) {
236
+ if (err.code === "ENOENT" || err instanceof SyntaxError) {
237
+ return null;
238
+ }
239
+ throw err;
240
+ }
241
+ }
242
+ async function storeCredentials(credentials) {
243
+ await mkdir2(CONFIG_DIR2, { recursive: true, mode: 448 });
244
+ await writeFile2(CREDENTIALS_PATH, JSON.stringify(credentials, null, 2));
245
+ await chmod(CREDENTIALS_PATH, 384);
246
+ }
247
+ async function clearCredentials() {
248
+ try {
249
+ await unlink(CREDENTIALS_PATH);
250
+ } catch (err) {
251
+ if (err.code !== "ENOENT") throw err;
252
+ }
253
+ }
254
+ async function getValidCredentials() {
255
+ const creds = await getStoredCredentials();
256
+ if (!creds) return null;
257
+ if (Date.now() > creds.expiresAt - 5 * 60 * 1e3) {
258
+ try {
259
+ const refreshed = await refreshTokens(creds.refreshToken);
260
+ await storeCredentials(refreshed);
261
+ return refreshed;
262
+ } catch {
263
+ await clearCredentials();
264
+ return null;
265
+ }
266
+ }
267
+ return creds;
268
+ }
269
+ async function performLogin() {
270
+ const codeVerifier = generateCodeVerifier();
271
+ const codeChallenge = generateCodeChallenge(codeVerifier);
272
+ const state = generateState();
273
+ const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
274
+ const callbackPromise = startCallbackServer();
275
+ try {
276
+ const open = (await import("open")).default;
277
+ await open(authorizeUrl);
278
+ } catch {
279
+ console.log(`
280
+ Open this URL in your browser to authenticate:
281
+ `);
282
+ console.log(authorizeUrl);
283
+ }
284
+ const { code, state: returnedState } = await callbackPromise;
285
+ if (returnedState !== state) {
286
+ throw new Error("State mismatch \u2014 possible CSRF attack. Please try again.");
287
+ }
288
+ const credentials = await exchangeCodeForTokens(code, codeVerifier);
289
+ await storeCredentials(credentials);
290
+ return credentials;
291
+ }
292
+ function callbackPage(title, message, isError) {
293
+ const icon = isError ? '<svg width="48" height="48" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="#ef4444" stroke-width="2"/><path stroke="#ef4444" stroke-width="2" stroke-linecap="round" d="M15 9l-6 6M9 9l6 6"/></svg>' : '<svg width="48" height="48" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke="#10b981" stroke-width="2"/><path stroke="#10b981" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" d="M8 12l3 3 5-5"/></svg>';
294
+ return `<!DOCTYPE html>
295
+ <html lang="en">
296
+ <head>
297
+ <meta charset="UTF-8">
298
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
299
+ <title>${title} \u2014 RepoWise</title>
300
+ <link rel="icon" href="https://staging.repowise.ai/favicon.svg" type="image/svg+xml">
301
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
302
+ <style>
303
+ * { margin: 0; padding: 0; box-sizing: border-box; }
304
+ body { font-family: 'Inter', system-ui, sans-serif; background: #0a0b14; color: #e4e4e7; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
305
+ .card { text-align: center; max-width: 440px; padding: 48px 40px; }
306
+ .logo { margin-bottom: 32px; }
307
+ .logo svg { height: 48px; width: auto; }
308
+ .icon { margin-bottom: 20px; }
309
+ h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; color: ${isError ? "#ef4444" : "#e4e4e7"}; }
310
+ p { font-size: 15px; color: #a1a1aa; line-height: 1.5; }
311
+ </style>
312
+ </head>
313
+ <body>
314
+ <div class="card">
315
+ <div class="logo">
316
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 50" height="48">
317
+ <text x="0" y="38" font-family="Inter, system-ui, sans-serif" font-weight="700" font-size="36" fill="#e4e4e7">Repo<tspan fill="#6c5ce7">Wise</tspan></text>
318
+ </svg>
319
+ </div>
320
+ <div class="icon">${icon}</div>
321
+ <h1>${title}</h1>
322
+ <p>${message}</p>
323
+ </div>
324
+ </body>
325
+ </html>`;
326
+ }
327
+ function decodeIdToken(idToken) {
328
+ try {
329
+ const parts = idToken.split(".");
330
+ if (parts.length < 2) return { email: "unknown" };
331
+ const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
332
+ return { email: payload.email ?? "unknown", tenantId: payload["custom:tenant_id"] };
333
+ } catch {
334
+ return { email: "unknown" };
335
+ }
336
+ }
337
+ var CONFIG_DIR2, CREDENTIALS_PATH, CLI_CALLBACK_PORT, CALLBACK_TIMEOUT_MS;
338
+ var init_auth = __esm({
339
+ "src/lib/auth.ts"() {
340
+ "use strict";
341
+ init_env();
342
+ CONFIG_DIR2 = join2(homedir2(), ".repowise");
343
+ CREDENTIALS_PATH = join2(CONFIG_DIR2, "credentials.json");
344
+ CLI_CALLBACK_PORT = 19876;
345
+ CALLBACK_TIMEOUT_MS = 12e4;
346
+ }
347
+ });
348
+
349
+ // src/lib/api.ts
350
+ function getApiUrl() {
351
+ return process.env["REPOWISE_API_URL"] ?? getEnvConfig().apiUrl;
352
+ }
353
+ async function apiRequest(path, options) {
354
+ const credentials = await getValidCredentials();
355
+ if (!credentials) {
356
+ throw new Error("Not logged in. Run `repowise login` first.");
357
+ }
358
+ const response = await fetch(`${getApiUrl()}${path}`, {
359
+ ...options,
360
+ headers: {
361
+ "Content-Type": "application/json",
362
+ Authorization: `Bearer ${credentials.accessToken}`,
363
+ ...options?.headers
364
+ }
365
+ });
366
+ if (response.status === 401) {
367
+ await clearCredentials();
368
+ throw new Error("Session expired. Run `repowise login` again.");
369
+ }
370
+ if (!response.ok) {
371
+ let message = `Request failed with status ${response.status}`;
372
+ try {
373
+ const body = await response.json();
374
+ if (body.error?.message) message = body.error.message;
375
+ } catch {
376
+ }
377
+ throw new Error(message);
378
+ }
379
+ const json = await response.json();
380
+ return json.data;
381
+ }
382
+ var init_api = __esm({
383
+ "src/lib/api.ts"() {
384
+ "use strict";
385
+ init_auth();
386
+ init_env();
387
+ }
388
+ });
389
+
390
+ // src/lib/prompts.ts
391
+ import { checkbox, confirm } from "@inquirer/prompts";
392
+ import chalk2 from "chalk";
393
+ async function selectAiTools() {
394
+ const choices = [
395
+ { name: "Cursor", value: "cursor" },
396
+ { name: "Claude Code", value: "claude-code" },
397
+ { name: "GitHub Copilot", value: "copilot" },
398
+ { name: "Windsurf", value: "windsurf" },
399
+ { name: "Cline", value: "cline" },
400
+ { name: "Codex", value: "codex" },
401
+ { name: "Roo Code", value: "roo-code" },
402
+ { name: "Other (manual setup)", value: "other" }
403
+ ];
404
+ while (true) {
405
+ console.log(chalk2.dim(" Use Space to select, Enter to continue.\n"));
406
+ const selected = await checkbox({
407
+ message: chalk2.bold("Which AI tools do you use?"),
408
+ choices
409
+ });
410
+ if (selected.length === 0) {
411
+ const goBack = await confirm({
412
+ message: "No tools selected. Go back and choose?",
413
+ default: true
414
+ });
415
+ if (goBack) continue;
416
+ }
417
+ const hasOther = selected.includes("other");
418
+ const tools = selected.filter((s) => s !== "other");
419
+ return { tools, hasOther };
420
+ }
421
+ }
422
+ var init_prompts = __esm({
423
+ "src/lib/prompts.ts"() {
424
+ "use strict";
425
+ }
426
+ });
427
+
428
+ // src/lib/ai-tools.ts
429
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3, readdir } from "fs/promises";
430
+ import { join as join3, dirname } from "path";
431
+ function sanitizeRepoName(name) {
432
+ return name.replace(/[<>[\]`()|\\]/g, "");
433
+ }
434
+ function fileDescriptionFromName(fileName) {
435
+ return fileName.replace(/\.md$/, "").split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
436
+ }
437
+ function generateReference(tool, repoName, contextFolder, contextFiles) {
438
+ const config2 = AI_TOOL_CONFIG[tool];
439
+ const safeName = sanitizeRepoName(repoName);
440
+ const fileLines = contextFiles.map((f) => {
441
+ const desc = fileDescriptionFromName(f.fileName);
442
+ const isOverview = f.fileName === "project-overview.md";
443
+ return { path: f.relativePath, desc: isOverview ? `${desc} (full index of all files)` : desc };
444
+ });
445
+ const hasFiles = fileLines.length > 0;
446
+ if (config2.format === "markdown") {
447
+ const lines2 = [
448
+ config2.markerStart,
449
+ "",
450
+ `## Project Context \u2014 ${safeName}`,
451
+ "",
452
+ `This repository has AI-optimized context files generated by RepoWise.`,
453
+ `Before making changes, read the relevant context files in \`${contextFolder}/\` to understand the project's architecture, coding patterns, conventions, and domain knowledge.`,
454
+ "",
455
+ `**Start here:** \`${contextFolder}/project-overview.md\` \u2014 the routing document that describes every context file and when to read it.`,
456
+ ""
457
+ ];
458
+ if (hasFiles) {
459
+ lines2.push(
460
+ `**Core context files:**`,
461
+ "",
462
+ ...fileLines.map((f) => `- \`${f.path}\` \u2014 ${f.desc}`),
463
+ "",
464
+ `> Additional context files may exist beyond this list. Check \`project-overview.md\` for the complete index.`
465
+ );
466
+ }
467
+ lines2.push("", config2.markerEnd);
468
+ return lines2.join("\n");
469
+ }
470
+ const lines = [
471
+ config2.markerStart,
472
+ `# Project Context \u2014 ${safeName}`,
473
+ "#",
474
+ `# This repository has AI-optimized context files generated by RepoWise.`,
475
+ `# Before making changes, read the relevant context files in ${contextFolder}/`,
476
+ `# to understand the project's architecture, coding patterns, conventions, and domain knowledge.`,
477
+ "#",
478
+ `# Start here: ${contextFolder}/project-overview.md`,
479
+ `# The routing document that describes every context file and when to read it.`
480
+ ];
481
+ if (hasFiles) {
482
+ lines.push(
483
+ "#",
484
+ `# Core context files:`,
485
+ ...fileLines.map((f) => `# ${f.path} \u2014 ${f.desc}`),
486
+ "#",
487
+ "# Additional context files may exist beyond this list.",
488
+ "# Check project-overview.md for the complete index."
489
+ );
490
+ }
491
+ lines.push(config2.markerEnd);
492
+ return lines.join("\n");
493
+ }
494
+ async function updateToolConfig(repoRoot, tool, repoName, contextFolder, contextFiles) {
495
+ const config2 = AI_TOOL_CONFIG[tool];
496
+ const fullPath = join3(repoRoot, config2.filePath);
497
+ const dir = dirname(fullPath);
498
+ if (dir !== repoRoot) {
499
+ await mkdir3(dir, { recursive: true });
500
+ }
501
+ const referenceBlock = generateReference(tool, repoName, contextFolder, contextFiles);
502
+ let existing = "";
503
+ let created = true;
504
+ try {
505
+ existing = await readFile3(fullPath, "utf-8");
506
+ created = false;
507
+ } catch (err) {
508
+ if (err.code !== "ENOENT") throw err;
509
+ }
510
+ const startIdx = existing.indexOf(config2.markerStart);
511
+ const endIdx = existing.indexOf(config2.markerEnd);
512
+ let content;
513
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
514
+ const before = existing.slice(0, startIdx);
515
+ const after = existing.slice(endIdx + config2.markerEnd.length);
516
+ content = before + referenceBlock + after;
517
+ } else {
518
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n\n" : existing.length > 0 ? "\n" : "";
519
+ content = existing + separator + referenceBlock + "\n";
520
+ }
521
+ await writeFile3(fullPath, content, "utf-8");
522
+ return { created };
523
+ }
524
+ async function scanLocalContextFiles(repoRoot, contextFolder) {
525
+ const folderPath = join3(repoRoot, contextFolder);
526
+ try {
527
+ const entries = await readdir(folderPath, { withFileTypes: true });
528
+ return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => ({
529
+ fileName: e.name,
530
+ relativePath: `${contextFolder}/${e.name}`
531
+ })).sort((a, b) => a.fileName.localeCompare(b.fileName));
532
+ } catch (err) {
533
+ if (err.code === "ENOENT") return [];
534
+ throw err;
535
+ }
536
+ }
537
+ var AI_TOOL_CONFIG, SUPPORTED_TOOLS;
538
+ var init_ai_tools = __esm({
539
+ "src/lib/ai-tools.ts"() {
540
+ "use strict";
541
+ AI_TOOL_CONFIG = {
542
+ cursor: {
543
+ label: "Cursor",
544
+ fileName: ".cursorrules",
545
+ filePath: ".cursorrules",
546
+ markerStart: "# --- repowise-start ---",
547
+ markerEnd: "# --- repowise-end ---",
548
+ format: "plain-text"
549
+ },
550
+ "claude-code": {
551
+ label: "Claude Code",
552
+ fileName: "CLAUDE.md",
553
+ filePath: "CLAUDE.md",
554
+ markerStart: "<!-- repowise-start -->",
555
+ markerEnd: "<!-- repowise-end -->",
556
+ format: "markdown"
557
+ },
558
+ copilot: {
559
+ label: "GitHub Copilot",
560
+ fileName: "copilot-instructions.md",
561
+ filePath: ".github/copilot-instructions.md",
562
+ markerStart: "<!-- repowise-start -->",
563
+ markerEnd: "<!-- repowise-end -->",
564
+ format: "markdown"
565
+ },
566
+ windsurf: {
567
+ label: "Windsurf",
568
+ fileName: ".windsurfrules",
569
+ filePath: ".windsurfrules",
570
+ markerStart: "# --- repowise-start ---",
571
+ markerEnd: "# --- repowise-end ---",
572
+ format: "plain-text"
573
+ },
574
+ cline: {
575
+ label: "Cline",
576
+ fileName: ".clinerules",
577
+ filePath: ".clinerules",
578
+ markerStart: "# --- repowise-start ---",
579
+ markerEnd: "# --- repowise-end ---",
580
+ format: "plain-text"
581
+ },
582
+ codex: {
583
+ label: "Codex",
584
+ fileName: "AGENTS.md",
585
+ filePath: "AGENTS.md",
586
+ markerStart: "<!-- repowise-start -->",
587
+ markerEnd: "<!-- repowise-end -->",
588
+ format: "markdown"
589
+ },
590
+ "roo-code": {
591
+ label: "Roo Code",
592
+ fileName: "rules.md",
593
+ filePath: ".roo/rules.md",
594
+ markerStart: "<!-- repowise-start -->",
595
+ markerEnd: "<!-- repowise-end -->",
596
+ format: "markdown"
597
+ }
598
+ };
599
+ SUPPORTED_TOOLS = Object.keys(AI_TOOL_CONFIG);
600
+ }
601
+ });
602
+
603
+ // src/lib/interview-handler.ts
604
+ import chalk3 from "chalk";
605
+ import { input } from "@inquirer/prompts";
606
+ async function handleInterview(syncId, questionId, questionText, questionContext, estimatedQuestions) {
607
+ questionCounter++;
608
+ if (questionCounter === 1) {
609
+ console.log("");
610
+ console.log(chalk3.cyan.bold(" \u2500\u2500 Interview \u2500\u2500"));
611
+ console.log(chalk3.dim(" Help us understand your project better. Answer a few short"));
612
+ console.log(
613
+ chalk3.dim(
614
+ ` questions so we can generate more relevant context files (up to ${MAX_QUESTIONS}).`
615
+ )
616
+ );
617
+ }
618
+ const total = Math.min(estimatedQuestions ?? MAX_QUESTIONS, MAX_QUESTIONS);
619
+ console.log("");
620
+ console.log(chalk3.cyan.bold(` Question ${questionCounter}/${total}`));
621
+ if (questionContext) {
622
+ console.log(chalk3.dim(` ${questionContext}`));
623
+ }
624
+ console.log(` ${questionText}`);
625
+ console.log(chalk3.dim(' (Enter to skip \xB7 "done" to finish early)'));
626
+ let answer;
627
+ try {
628
+ answer = await Promise.race([
629
+ input({
630
+ message: chalk3.cyan(">"),
631
+ theme: { prefix: " " }
632
+ }),
633
+ new Promise(
634
+ (_, reject) => setTimeout(() => reject(new Error("INTERVIEW_TIMEOUT")), INTERVIEW_TIMEOUT_MS)
635
+ )
636
+ ]);
637
+ } catch (err) {
638
+ if (err instanceof Error && err.message === "INTERVIEW_TIMEOUT") {
639
+ console.log(chalk3.yellow(" Timed out \u2014 auto-skipping this question."));
640
+ answer = "skip";
641
+ } else {
642
+ throw err;
643
+ }
644
+ }
645
+ const trimmed = answer.trim();
646
+ let action;
647
+ let answerText = "";
648
+ if (trimmed.toLowerCase() === "done") {
649
+ action = "done";
650
+ } else if (trimmed === "" || trimmed.toLowerCase() === "skip") {
651
+ action = "skip";
652
+ } else {
653
+ action = "answer";
654
+ answerText = trimmed;
655
+ }
656
+ if (questionCounter >= MAX_QUESTIONS && action !== "done") {
657
+ action = "done";
658
+ console.log(chalk3.green(" Thanks for your answers! Wrapping up the interview."));
659
+ }
660
+ try {
661
+ await apiRequest(`/v1/sync/${syncId}/answer`, {
662
+ method: "POST",
663
+ body: JSON.stringify({ questionId, answerText, action })
664
+ });
665
+ } catch (err) {
666
+ const message = err instanceof Error ? err.message : String(err);
667
+ if (message.includes("not awaiting input") || message.includes("expired")) {
668
+ console.log(chalk3.dim(" Pipeline has already moved on \u2014 continuing."));
669
+ return;
670
+ }
671
+ try {
672
+ await new Promise((r) => setTimeout(r, 1e3));
673
+ await apiRequest(`/v1/sync/${syncId}/answer`, {
674
+ method: "POST",
675
+ body: JSON.stringify({ questionId, answerText, action })
676
+ });
677
+ } catch {
678
+ console.log(chalk3.yellow(" Could not submit answer \u2014 pipeline will continue."));
679
+ return;
680
+ }
681
+ }
682
+ if (action === "done") {
683
+ console.log(chalk3.dim(" Interview ended early."));
684
+ } else if (action === "skip") {
685
+ console.log(chalk3.dim(" Skipped."));
686
+ } else {
687
+ console.log(chalk3.dim(" Answer recorded."));
688
+ }
689
+ console.log("");
690
+ }
691
+ var INTERVIEW_TIMEOUT_MS, MAX_QUESTIONS, questionCounter;
692
+ var init_interview_handler = __esm({
693
+ "src/lib/interview-handler.ts"() {
694
+ "use strict";
695
+ init_api();
696
+ INTERVIEW_TIMEOUT_MS = 5 * 60 * 1e3;
697
+ MAX_QUESTIONS = 10;
698
+ questionCounter = 0;
699
+ }
700
+ });
701
+
702
+ // src/lib/progress-renderer.ts
703
+ import chalk4 from "chalk";
704
+ function computeOverallProgress(syncResult) {
705
+ const stepNumber = syncResult.stepNumber ?? 1;
706
+ const totalSteps = syncResult.totalSteps ?? 6;
707
+ const stepPct = syncResult.progressPercentage ?? 0;
708
+ return Math.min(100, Math.round(((stepNumber - 1) * 100 + stepPct) / totalSteps));
709
+ }
710
+ var CORE_FILES, FILE_DESCRIPTIONS, ALL_PERSONAS, PERSONA_LABELS, ProgressRenderer;
711
+ var init_progress_renderer = __esm({
712
+ "src/lib/progress-renderer.ts"() {
713
+ "use strict";
714
+ CORE_FILES = /* @__PURE__ */ new Set([
715
+ "project-overview.md",
716
+ "architecture.md",
717
+ "data-models.md",
718
+ "api-contracts.md",
719
+ "coding-patterns.md"
720
+ ]);
721
+ FILE_DESCRIPTIONS = {
722
+ // Core
723
+ "project-overview.md": "Project overview & file index",
724
+ "architecture.md": "System design & components",
725
+ "data-models.md": "Schemas, entities & relationships",
726
+ "api-contracts.md": "API endpoints & contracts",
727
+ "coding-patterns.md": "Code conventions & patterns",
728
+ // Tailored
729
+ "domain-knowledge.md": "Business domain & terminology",
730
+ "testing-strategy.md": "Test frameworks & coverage",
731
+ "deployment-workflows.md": "CI/CD & release process",
732
+ "state-management.md": "State & caching patterns",
733
+ "performance-optimization.md": "Performance & optimization",
734
+ "accessibility-patterns.md": "Accessibility & ARIA patterns",
735
+ "tech-stack.md": "Technology inventory & versions",
736
+ "tribal-knowledge.md": "Team knowledge & conventions",
737
+ "development-setup.md": "Dev environment setup",
738
+ "ui-patterns.md": "UI components & design system",
739
+ "ux-patterns.md": "UX interactions & feedback",
740
+ "user-flows.md": "User journeys & navigation",
741
+ "security-patterns.md": "Auth & security patterns",
742
+ "error-handling.md": "Error handling & recovery",
743
+ "integration-patterns.md": "External integrations & APIs",
744
+ "configuration.md": "Config & environment settings"
745
+ };
746
+ ALL_PERSONAS = ["pm", "architect", "dev", "analyst", "tea", "ux", "sm", "techWriter"];
747
+ PERSONA_LABELS = {
748
+ pm: "Product Manager",
749
+ architect: "Architect",
750
+ dev: "Developer",
751
+ analyst: "Business Analyst",
752
+ tea: "Test Architect",
753
+ ux: "UX Designer",
754
+ sm: "Scrum Master",
755
+ techWriter: "Tech Writer"
756
+ };
757
+ ProgressRenderer = class {
758
+ privacyShieldShown = false;
759
+ discoveryShown = false;
760
+ scanSummaryShown = false;
761
+ validationShown = false;
762
+ lastValidationSnapshot = "";
763
+ lastValidationLineCount = 0;
764
+ pushShown = false;
765
+ fileStatusHeaderShown = false;
766
+ lastFileStatusSnapshot = "";
767
+ lastFileStatusLineCount = 0;
768
+ renderPrivacyShield(enabled, spinner) {
769
+ if (this.privacyShieldShown) return;
770
+ this.privacyShieldShown = true;
771
+ spinner.stop();
772
+ console.log("");
773
+ console.log(chalk4.cyan.bold(" \u2500\u2500 Privacy Shield \u2500\u2500"));
774
+ if (enabled) {
775
+ console.log(` ${chalk4.green("\u2713")} Privacy Shield active`);
776
+ console.log(` ${chalk4.green("\u2713")} Private connection established`);
777
+ } else {
778
+ console.log(` ${chalk4.yellow("\u2139")} Privacy Shield not in current plan`);
779
+ console.log(chalk4.dim(" Shield your data from the open internet."));
780
+ }
781
+ console.log("");
782
+ spinner.start();
783
+ }
784
+ renderDiscovery(result, spinner) {
785
+ if (this.discoveryShown) return;
786
+ this.discoveryShown = true;
787
+ spinner.stop();
788
+ console.log("");
789
+ console.log(chalk4.cyan.bold(" \u2500\u2500 Repository Discovery \u2500\u2500"));
790
+ if (result.languages.length > 0) {
791
+ const langs = result.languages.slice(0, 5).map((l) => `${l.name} (${Math.round(l.percentage)}%)`).join(", ");
792
+ console.log(` ${chalk4.dim("Languages:")} ${langs}`);
793
+ }
794
+ if (result.frameworks.length > 0) {
795
+ console.log(
796
+ ` ${chalk4.dim("Frameworks:")} ${result.frameworks.map((f) => f.name).join(", ")}`
797
+ );
798
+ }
799
+ console.log(
800
+ ` ${chalk4.dim("Structure:")} ${result.structureType} ${chalk4.dim(`(${result.fileCount} files)`)}`
801
+ );
802
+ if (result.existingDocs.length > 0) {
803
+ console.log(` ${chalk4.dim("Existing docs:")} ${result.existingDocs.join(", ")}`);
804
+ }
805
+ if (result.fileTree && result.fileTree.length > 0) {
806
+ this.renderTree(result.fileTree);
807
+ }
808
+ console.log("");
809
+ spinner.start();
810
+ }
811
+ renderTree(entries) {
812
+ console.log("");
813
+ console.log(chalk4.cyan.bold(" \u2500\u2500 Project Structure \u2500\u2500"));
814
+ const root = { name: "", type: "tree", children: /* @__PURE__ */ new Map() };
815
+ for (const entry of entries) {
816
+ const parts = entry.path.split("/");
817
+ let current = root;
818
+ for (let i = 0; i < parts.length; i++) {
819
+ const part = parts[i];
820
+ if (!current.children.has(part)) {
821
+ const isLast = i === parts.length - 1;
822
+ current.children.set(part, {
823
+ name: part,
824
+ type: isLast ? entry.type : "tree",
825
+ children: /* @__PURE__ */ new Map()
826
+ });
827
+ }
828
+ current = current.children.get(part);
829
+ }
830
+ }
831
+ const printNode = (node, prefix, isLast) => {
832
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
833
+ const display = node.type === "tree" ? chalk4.bold.dim(`${node.name}/`) : node.name;
834
+ console.log(` ${prefix}${connector}${display}`);
835
+ const sorted = [...node.children.values()].sort((a, b) => {
836
+ if (a.type === "tree" && b.type !== "tree") return -1;
837
+ if (a.type !== "tree" && b.type === "tree") return 1;
838
+ return a.name.localeCompare(b.name);
839
+ });
840
+ const childPrefix = prefix + (isLast ? " " : "\u2502 ");
841
+ sorted.forEach((child, idx) => {
842
+ printNode(child, childPrefix, idx === sorted.length - 1);
843
+ });
844
+ };
845
+ const topLevel = [...root.children.values()].sort((a, b) => {
846
+ if (a.type === "tree" && b.type !== "tree") return -1;
847
+ if (a.type !== "tree" && b.type === "tree") return 1;
848
+ return a.name.localeCompare(b.name);
849
+ });
850
+ topLevel.forEach((child, idx) => {
851
+ printNode(child, "", idx === topLevel.length - 1);
852
+ });
853
+ }
854
+ renderScanSummary(summary, spinner) {
855
+ if (this.scanSummaryShown) return;
856
+ this.scanSummaryShown = true;
857
+ spinner.stop();
858
+ console.log(
859
+ chalk4.dim(
860
+ ` Scan complete: ${summary.totalFiles} files, ${summary.totalFunctions} functions, ${summary.totalClasses} classes, ${summary.totalEndpoints} endpoints`
861
+ )
862
+ );
863
+ console.log("");
864
+ spinner.start();
865
+ }
866
+ renderValidation(progress, spinner) {
867
+ const resultMap = new Map(progress.personaResults.map((r) => [r.persona, r.score]));
868
+ const isComplete = progress.status === "complete";
869
+ if (isComplete && this.validationShown) return;
870
+ const snapshot = `${progress.round}:${progress.status}:${progress.personaResults.map((r) => `${r.persona}:${r.score}`).join(",")}`;
871
+ if (snapshot === this.lastValidationSnapshot) return;
872
+ this.lastValidationSnapshot = snapshot;
873
+ if (isComplete) this.validationShown = true;
874
+ spinner.stop();
875
+ if (this.lastValidationLineCount > 0) {
876
+ process.stdout.write(`\x1B[${this.lastValidationLineCount}A`);
877
+ }
878
+ const lines = [];
879
+ const title = isComplete ? "Validation Results" : "Validation";
880
+ lines.push(chalk4.cyan.bold(` \u2500\u2500 ${title} \u2500\u2500`));
881
+ if (!isComplete) {
882
+ lines.push(
883
+ chalk4.dim(
884
+ ` ${ALL_PERSONAS.length} AI reviewers checking context quality \u2014 issues are auto-fixed.`
885
+ )
886
+ );
887
+ }
888
+ const passCount = progress.personaResults.filter((r) => r.score === "PASS").length;
889
+ if (isComplete) {
890
+ const roundInfo = progress.round > 1 ? ` (${progress.round} rounds)` : "";
891
+ lines.push(chalk4.dim(` ${passCount}/${ALL_PERSONAS.length} PASS${roundInfo}`));
892
+ } else if (progress.personaResults.length > 0) {
893
+ const statusSuffix = progress.status === "regenerating" ? chalk4.dim(" \u2014 improving files based on feedback") : "";
894
+ lines.push(
895
+ ` Round ${progress.round}/${progress.maxRounds}: ${passCount}/${ALL_PERSONAS.length} passed${statusSuffix}`
896
+ );
897
+ } else {
898
+ lines.push(chalk4.dim(` Round ${progress.round}/${progress.maxRounds}: validating...`));
899
+ }
900
+ for (const persona of ALL_PERSONAS) {
901
+ const label = PERSONA_LABELS[persona] ?? persona;
902
+ const score = resultMap.get(persona);
903
+ if (score) {
904
+ const icon = score === "PASS" ? chalk4.green("\u2713") : chalk4.red("\u2717");
905
+ const scoreColor = score === "PASS" ? chalk4.green : score === "PARTIAL" ? chalk4.yellow : chalk4.red;
906
+ const fixingSuffix = progress.status === "regenerating" && score !== "PASS" ? chalk4.dim(" \u2192 fixing...") : "";
907
+ lines.push(` ${icon} ${label}: ${scoreColor(score)}${fixingSuffix}`);
908
+ } else {
909
+ lines.push(` ${chalk4.dim("\u25CB")} ${chalk4.dim(label)}`);
910
+ }
911
+ }
912
+ if (isComplete && passCount < ALL_PERSONAS.length) {
913
+ lines.push(chalk4.yellow(" \u26A0 Continuing with best-effort context"));
914
+ }
915
+ lines.push("");
916
+ for (const line of lines) {
917
+ process.stdout.write(`\x1B[2K${line}
918
+ `);
919
+ }
920
+ for (let i = lines.length; i < this.lastValidationLineCount; i++) {
921
+ process.stdout.write("\x1B[2K\n");
922
+ }
923
+ this.lastValidationLineCount = lines.length;
924
+ spinner.start();
925
+ }
926
+ renderFileStatuses(fileStatuses, spinner) {
927
+ const snapshot = fileStatuses.map((f) => `${f.fileName}:${f.status}`).join(",");
928
+ if (snapshot === this.lastFileStatusSnapshot) return;
929
+ this.lastFileStatusSnapshot = snapshot;
930
+ const completedCount = fileStatuses.filter((f) => f.status === "completed").length;
931
+ const totalCount = fileStatuses.length;
932
+ spinner.stop();
933
+ if (!this.fileStatusHeaderShown) {
934
+ this.fileStatusHeaderShown = true;
935
+ console.log("");
936
+ console.log(chalk4.cyan.bold(" \u2500\u2500 RepoWise Context Generation \u2500\u2500"));
937
+ console.log(chalk4.dim(" Building AI-optimized context files from your codebase."));
938
+ }
939
+ if (this.lastFileStatusLineCount > 0) {
940
+ process.stdout.write(`\x1B[${this.lastFileStatusLineCount}A`);
941
+ }
942
+ const coreFiles = [];
943
+ const tailoredFiles = [];
944
+ for (const file of fileStatuses) {
945
+ const baseName = file.fileName.split("/").pop() ?? file.fileName;
946
+ if (CORE_FILES.has(baseName)) {
947
+ coreFiles.push(file);
948
+ } else {
949
+ tailoredFiles.push(file);
950
+ }
951
+ }
952
+ const maxCoreLen = coreFiles.reduce((m, f) => Math.max(m, f.fileName.length), 0);
953
+ const maxTailoredLen = tailoredFiles.reduce((m, f) => Math.max(m, f.fileName.length), 0);
954
+ const formatFileLine = (file, padLen) => {
955
+ const baseName = file.fileName.split("/").pop() ?? file.fileName;
956
+ const desc = FILE_DESCRIPTIONS[baseName] ?? baseName.replace(/\.md$/, "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
957
+ const padded = file.fileName.padEnd(padLen);
958
+ switch (file.status) {
959
+ case "completed":
960
+ return ` ${chalk4.green("\u2713")} ${padded} ${chalk4.dim(`\u2014 ${desc}`)}`;
961
+ case "generating":
962
+ return ` ${chalk4.cyan("\u27F3")} ${padded} ${chalk4.dim(`\u2014 ${desc}`)}`;
963
+ case "failed":
964
+ return ` ${chalk4.red("\u2717")} ${padded} ${chalk4.dim(`\u2014 ${desc}`)}`;
965
+ case "pending":
966
+ return ` ${chalk4.dim("\u25CB")} ${chalk4.dim(`${padded} \u2014 ${desc}`)}`;
967
+ }
968
+ };
969
+ const lines = [];
970
+ lines.push(chalk4.dim(` Generated ${completedCount}/${totalCount} files`));
971
+ lines.push("");
972
+ lines.push(` ${chalk4.bold("Core")}`);
973
+ for (const file of coreFiles) {
974
+ lines.push(formatFileLine(file, maxCoreLen));
975
+ }
976
+ if (tailoredFiles.length > 0) {
977
+ lines.push("");
978
+ lines.push(` ${chalk4.bold("Tailored")}`);
979
+ for (const file of tailoredFiles) {
980
+ lines.push(formatFileLine(file, maxTailoredLen));
981
+ }
982
+ }
983
+ lines.push("");
984
+ for (const line of lines) {
985
+ process.stdout.write(`\x1B[2K${line}
986
+ `);
987
+ }
988
+ for (let i = lines.length; i < this.lastFileStatusLineCount; i++) {
989
+ process.stdout.write("\x1B[2K\n");
990
+ }
991
+ this.lastFileStatusLineCount = lines.length;
992
+ spinner.start();
993
+ }
994
+ renderPush(contextStorage, spinner) {
995
+ if (this.pushShown) return;
996
+ this.pushShown = true;
997
+ spinner.stop();
998
+ console.log("");
999
+ if (contextStorage === "server") {
1000
+ console.log(chalk4.cyan.bold(" \u2500\u2500 Uploading Context \u2500\u2500"));
1001
+ console.log(
1002
+ ` ${chalk4.dim("Encrypting and uploading context files to RepoWise servers...")}`
1003
+ );
1004
+ } else {
1005
+ console.log(chalk4.cyan.bold(" \u2500\u2500 Pushing to Repository \u2500\u2500"));
1006
+ console.log(` ${chalk4.dim("Committing context files to your repository...")}`);
1007
+ }
1008
+ console.log("");
1009
+ spinner.start();
1010
+ }
1011
+ getSpinnerText(syncResult) {
1012
+ const stepLabel = syncResult.stepLabel ?? syncResult.currentStep ?? "Processing";
1013
+ const overallPct = computeOverallProgress(syncResult);
1014
+ let progressText = stepLabel;
1015
+ if (syncResult.scanProgress && !syncResult.scanProgress.summary) {
1016
+ progressText = `Scanning batch ${syncResult.scanProgress.currentBatch}/${syncResult.scanProgress.totalBatches}`;
1017
+ } else if (syncResult.generationProgress) {
1018
+ const gp = syncResult.generationProgress;
1019
+ if (gp.fileStatuses && gp.fileStatuses.length > 0) {
1020
+ const completed = gp.fileStatuses.filter((f) => f.status === "completed").length;
1021
+ const total = gp.fileStatuses.length;
1022
+ const generating = gp.fileStatuses.find((f) => f.status === "generating");
1023
+ if (completed === total) {
1024
+ progressText = stepLabel;
1025
+ } else if (generating) {
1026
+ progressText = `Generating ${generating.fileName} (${completed}/${total})`;
1027
+ } else {
1028
+ progressText = `Generating (${completed}/${total})`;
1029
+ }
1030
+ } else {
1031
+ progressText = `Generating ${gp.currentFileName} (${gp.currentFile}/${gp.totalFiles})`;
1032
+ }
1033
+ } else if (syncResult.validationProgress && syncResult.validationProgress.status !== "complete") {
1034
+ const vp = syncResult.validationProgress;
1035
+ if (vp.status === "regenerating") {
1036
+ progressText = `Improving files based on feedback (round ${vp.round})`;
1037
+ } else {
1038
+ progressText = `Validation round ${vp.round}/${vp.maxRounds}`;
1039
+ }
1040
+ }
1041
+ return `${progressText}... ${chalk4.dim(`(${overallPct}%)`)}`;
1042
+ }
1043
+ update(syncResult, spinner) {
1044
+ if (syncResult.privacyShieldEnabled !== void 0) {
1045
+ this.renderPrivacyShield(syncResult.privacyShieldEnabled, spinner);
1046
+ }
1047
+ if (syncResult.discoveryResult) {
1048
+ this.renderDiscovery(syncResult.discoveryResult, spinner);
1049
+ }
1050
+ if (syncResult.scanProgress?.summary && syncResult.scanProgress.summary.totalFiles > 0) {
1051
+ this.renderScanSummary(syncResult.scanProgress.summary, spinner);
1052
+ }
1053
+ if (syncResult.generationProgress?.fileStatuses && syncResult.generationProgress.fileStatuses.length > 0) {
1054
+ this.renderFileStatuses(syncResult.generationProgress.fileStatuses, spinner);
1055
+ }
1056
+ if (syncResult.validationProgress) {
1057
+ this.renderValidation(syncResult.validationProgress, spinner);
1058
+ }
1059
+ if (syncResult.currentStep === "push-context") {
1060
+ this.renderPush(syncResult.contextStorage, spinner);
1061
+ }
1062
+ spinner.text = this.getSpinnerText(syncResult);
1063
+ }
1064
+ };
1065
+ }
1066
+ });
1067
+
1068
+ // ../listener/dist/service-installer.js
1069
+ var service_installer_exports = {};
1070
+ __export(service_installer_exports, {
1071
+ install: () => install,
1072
+ isInstalled: () => isInstalled,
1073
+ uninstall: () => uninstall
1074
+ });
1075
+ import { execFile } from "child_process";
1076
+ import { writeFile as writeFile4, mkdir as mkdir4, unlink as unlink2 } from "fs/promises";
1077
+ import { homedir as homedir3 } from "os";
1078
+ import { join as join4 } from "path";
1079
+ import { createRequire } from "module";
1080
+ import { fileURLToPath } from "url";
1081
+ function resolveListenerCommand() {
1082
+ try {
1083
+ const require2 = createRequire(import.meta.url);
1084
+ const mainPath = require2.resolve("@repowise/listener/main");
1085
+ return { script: mainPath, args: [] };
1086
+ } catch {
1087
+ const bundlePath = fileURLToPath(import.meta.url);
1088
+ return { script: bundlePath, args: ["__listener"] };
1089
+ }
1090
+ }
1091
+ function exec(cmd, args) {
1092
+ return new Promise((resolve, reject) => {
1093
+ execFile(cmd, args, (err, stdout) => {
1094
+ if (err) {
1095
+ reject(err);
1096
+ return;
1097
+ }
1098
+ resolve(String(stdout ?? ""));
33
1099
  });
1100
+ });
1101
+ }
1102
+ function plistPath() {
1103
+ return join4(homedir3(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
1104
+ }
1105
+ function logDir() {
1106
+ return join4(homedir3(), ".repowise", "logs");
1107
+ }
1108
+ function buildPlist() {
1109
+ const cmd = resolveListenerCommand();
1110
+ const logs = logDir();
1111
+ const programArgs = [process.execPath, cmd.script, ...cmd.args].map((a) => ` <string>${a}</string>`).join("\n");
1112
+ return `<?xml version="1.0" encoding="UTF-8"?>
1113
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
1114
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1115
+ <plist version="1.0">
1116
+ <dict>
1117
+ <key>Label</key>
1118
+ <string>${PLIST_LABEL}</string>
1119
+ <key>ProgramArguments</key>
1120
+ <array>
1121
+ ${programArgs}
1122
+ </array>
1123
+ <key>RunAtLoad</key>
1124
+ <true/>
1125
+ <key>KeepAlive</key>
1126
+ <true/>
1127
+ <key>StandardOutPath</key>
1128
+ <string>${join4(logs, "listener-stdout.log")}</string>
1129
+ <key>StandardErrorPath</key>
1130
+ <string>${join4(logs, "listener-stderr.log")}</string>
1131
+ <key>ProcessType</key>
1132
+ <string>Background</string>
1133
+ </dict>
1134
+ </plist>`;
1135
+ }
1136
+ async function darwinInstall() {
1137
+ await mkdir4(logDir(), { recursive: true });
1138
+ await mkdir4(join4(homedir3(), "Library", "LaunchAgents"), { recursive: true });
1139
+ await writeFile4(plistPath(), buildPlist());
1140
+ await exec("launchctl", ["load", plistPath()]);
1141
+ }
1142
+ async function darwinUninstall() {
1143
+ try {
1144
+ await exec("launchctl", ["unload", plistPath()]);
1145
+ } catch {
1146
+ }
1147
+ try {
1148
+ await unlink2(plistPath());
1149
+ } catch {
1150
+ }
1151
+ }
1152
+ async function darwinIsInstalled() {
1153
+ try {
1154
+ const stdout = await exec("launchctl", ["list"]);
1155
+ return stdout.includes(PLIST_LABEL);
1156
+ } catch {
1157
+ return false;
1158
+ }
1159
+ }
1160
+ function unitPath() {
1161
+ return join4(homedir3(), ".config", "systemd", "user", `${SYSTEMD_SERVICE}.service`);
1162
+ }
1163
+ function buildUnit() {
1164
+ const cmd = resolveListenerCommand();
1165
+ const execStart = [process.execPath, cmd.script, ...cmd.args].join(" ");
1166
+ const logs = logDir();
1167
+ return `[Unit]
1168
+ Description=RepoWise Listener
1169
+ After=network-online.target
1170
+ Wants=network-online.target
1171
+
1172
+ [Service]
1173
+ Type=simple
1174
+ ExecStart=${execStart}
1175
+ Restart=on-failure
1176
+ RestartSec=10
1177
+ StandardOutput=append:${join4(logs, "listener-stdout.log")}
1178
+ StandardError=append:${join4(logs, "listener-stderr.log")}
1179
+
1180
+ [Install]
1181
+ WantedBy=default.target`;
1182
+ }
1183
+ async function linuxInstall() {
1184
+ await mkdir4(logDir(), { recursive: true });
1185
+ await mkdir4(join4(homedir3(), ".config", "systemd", "user"), { recursive: true });
1186
+ await writeFile4(unitPath(), buildUnit());
1187
+ await exec("systemctl", ["--user", "daemon-reload"]);
1188
+ await exec("systemctl", ["--user", "enable", SYSTEMD_SERVICE]);
1189
+ await exec("systemctl", ["--user", "start", SYSTEMD_SERVICE]);
1190
+ }
1191
+ async function linuxUninstall() {
1192
+ try {
1193
+ await exec("systemctl", ["--user", "stop", SYSTEMD_SERVICE]);
1194
+ } catch {
1195
+ }
1196
+ try {
1197
+ await exec("systemctl", ["--user", "disable", SYSTEMD_SERVICE]);
1198
+ } catch {
1199
+ }
1200
+ try {
1201
+ await unlink2(unitPath());
1202
+ } catch {
1203
+ }
1204
+ try {
1205
+ await exec("systemctl", ["--user", "daemon-reload"]);
1206
+ } catch {
1207
+ }
1208
+ }
1209
+ async function linuxIsInstalled() {
1210
+ try {
1211
+ const stdout = await exec("systemctl", ["--user", "is-enabled", SYSTEMD_SERVICE]);
1212
+ return stdout.trim() === "enabled";
1213
+ } catch {
1214
+ return false;
1215
+ }
1216
+ }
1217
+ async function win32Install() {
1218
+ await mkdir4(logDir(), { recursive: true });
1219
+ const cmd = resolveListenerCommand();
1220
+ const taskCmd = [process.execPath, cmd.script, ...cmd.args].map((a) => `"${a}"`).join(" ");
1221
+ await exec("schtasks", [
1222
+ "/create",
1223
+ "/tn",
1224
+ TASK_NAME,
1225
+ "/tr",
1226
+ taskCmd,
1227
+ "/sc",
1228
+ "onlogon",
1229
+ "/ru",
1230
+ process.env.USERNAME ?? "",
1231
+ "/f"
1232
+ ]);
1233
+ await exec("schtasks", ["/run", "/tn", TASK_NAME]);
1234
+ }
1235
+ async function win32Uninstall() {
1236
+ try {
1237
+ await exec("schtasks", ["/end", "/tn", TASK_NAME]);
1238
+ } catch {
1239
+ }
1240
+ try {
1241
+ await exec("schtasks", ["/delete", "/tn", TASK_NAME, "/f"]);
1242
+ } catch {
1243
+ }
1244
+ }
1245
+ async function win32IsInstalled() {
1246
+ try {
1247
+ await exec("schtasks", ["/query", "/tn", TASK_NAME]);
1248
+ return true;
1249
+ } catch {
1250
+ return false;
1251
+ }
1252
+ }
1253
+ async function install() {
1254
+ switch (process.platform) {
1255
+ case "darwin":
1256
+ await darwinInstall();
1257
+ break;
1258
+ case "linux":
1259
+ await linuxInstall();
1260
+ break;
1261
+ case "win32":
1262
+ await win32Install();
1263
+ break;
1264
+ default:
1265
+ throw new Error(`Unsupported platform: ${process.platform}`);
1266
+ }
1267
+ }
1268
+ async function uninstall() {
1269
+ switch (process.platform) {
1270
+ case "darwin":
1271
+ await darwinUninstall();
1272
+ break;
1273
+ case "linux":
1274
+ await linuxUninstall();
1275
+ break;
1276
+ case "win32":
1277
+ await win32Uninstall();
1278
+ break;
1279
+ default:
1280
+ throw new Error(`Unsupported platform: ${process.platform}`);
1281
+ }
1282
+ }
1283
+ async function isInstalled() {
1284
+ switch (process.platform) {
1285
+ case "darwin":
1286
+ return darwinIsInstalled();
1287
+ case "linux":
1288
+ return linuxIsInstalled();
1289
+ case "win32":
1290
+ return win32IsInstalled();
1291
+ default:
1292
+ return false;
1293
+ }
1294
+ }
1295
+ var PLIST_LABEL, SYSTEMD_SERVICE, TASK_NAME;
1296
+ var init_service_installer = __esm({
1297
+ "../listener/dist/service-installer.js"() {
1298
+ "use strict";
1299
+ PLIST_LABEL = "com.repowise.listener";
1300
+ SYSTEMD_SERVICE = "repowise-listener";
1301
+ TASK_NAME = "RepoWise Listener";
1302
+ }
1303
+ });
1304
+
1305
+ // ../listener/dist/process-manager.js
1306
+ var process_manager_exports = {};
1307
+ __export(process_manager_exports, {
1308
+ getStatus: () => getStatus,
1309
+ isRunning: () => isRunning,
1310
+ startBackground: () => startBackground,
1311
+ stopProcess: () => stopProcess
1312
+ });
1313
+ import { spawn } from "child_process";
1314
+ import { openSync, closeSync } from "fs";
1315
+ import { readFile as readFile4, writeFile as writeFile5, mkdir as mkdir5, unlink as unlink3 } from "fs/promises";
1316
+ import { homedir as homedir4 } from "os";
1317
+ import { join as join5 } from "path";
1318
+ import { createRequire as createRequire2 } from "module";
1319
+ import { fileURLToPath as fileURLToPath2 } from "url";
1320
+ function resolveListenerCommand2() {
1321
+ try {
1322
+ const require2 = createRequire2(import.meta.url);
1323
+ const mainPath = require2.resolve("@repowise/listener/main");
1324
+ return { script: mainPath, args: [] };
1325
+ } catch {
1326
+ const bundlePath = fileURLToPath2(import.meta.url);
1327
+ return { script: bundlePath, args: ["__listener"] };
1328
+ }
1329
+ }
1330
+ async function readPid() {
1331
+ try {
1332
+ const content = await readFile4(PID_PATH, "utf-8");
1333
+ const pid = parseInt(content.trim(), 10);
1334
+ return Number.isNaN(pid) ? null : pid;
1335
+ } catch (err) {
1336
+ if (err.code === "ENOENT")
1337
+ return null;
1338
+ throw err;
1339
+ }
1340
+ }
1341
+ function isAlive(pid) {
1342
+ try {
1343
+ process.kill(pid, 0);
1344
+ return true;
1345
+ } catch {
1346
+ return false;
1347
+ }
1348
+ }
1349
+ async function startBackground() {
1350
+ await mkdir5(LOG_DIR, { recursive: true });
1351
+ const cmd = resolveListenerCommand2();
1352
+ const stdoutFd = openSync(join5(LOG_DIR, "listener-stdout.log"), "a");
1353
+ const stderrFd = openSync(join5(LOG_DIR, "listener-stderr.log"), "a");
1354
+ const child = spawn(process.execPath, [cmd.script, ...cmd.args], {
1355
+ detached: true,
1356
+ stdio: ["ignore", stdoutFd, stderrFd],
1357
+ cwd: homedir4(),
1358
+ env: { ...process.env }
1359
+ });
1360
+ child.unref();
1361
+ closeSync(stdoutFd);
1362
+ closeSync(stderrFd);
1363
+ const pid = child.pid;
1364
+ if (!pid)
1365
+ throw new Error("Failed to spawn listener process");
1366
+ await writeFile5(PID_PATH, String(pid));
1367
+ return pid;
1368
+ }
1369
+ async function stopProcess() {
1370
+ const pid = await readPid();
1371
+ if (pid === null)
1372
+ return;
1373
+ if (!isAlive(pid)) {
1374
+ await removePidFile();
1375
+ return;
1376
+ }
1377
+ try {
1378
+ process.kill(pid, "SIGTERM");
1379
+ } catch {
1380
+ }
1381
+ const deadline = Date.now() + 5e3;
1382
+ while (Date.now() < deadline && isAlive(pid)) {
1383
+ await new Promise((r) => setTimeout(r, 200));
1384
+ }
1385
+ if (isAlive(pid)) {
1386
+ try {
1387
+ process.kill(pid, "SIGKILL");
1388
+ } catch {
1389
+ }
1390
+ }
1391
+ await removePidFile();
1392
+ }
1393
+ async function isRunning() {
1394
+ const pid = await readPid();
1395
+ if (pid === null)
1396
+ return false;
1397
+ return isAlive(pid);
1398
+ }
1399
+ async function getStatus() {
1400
+ const pid = await readPid();
1401
+ if (pid === null)
1402
+ return { running: false, pid: null };
1403
+ const alive = isAlive(pid);
1404
+ return { running: alive, pid: alive ? pid : null };
1405
+ }
1406
+ async function removePidFile() {
1407
+ try {
1408
+ await unlink3(PID_PATH);
1409
+ } catch {
1410
+ }
1411
+ }
1412
+ var REPOWISE_DIR, PID_PATH, LOG_DIR;
1413
+ var init_process_manager = __esm({
1414
+ "../listener/dist/process-manager.js"() {
1415
+ "use strict";
1416
+ REPOWISE_DIR = join5(homedir4(), ".repowise");
1417
+ PID_PATH = join5(REPOWISE_DIR, "listener.pid");
1418
+ LOG_DIR = join5(REPOWISE_DIR, "logs");
1419
+ }
1420
+ });
1421
+
1422
+ // src/commands/create.ts
1423
+ var create_exports = {};
1424
+ __export(create_exports, {
1425
+ create: () => create
34
1426
  });
35
- program
36
- .command('login')
37
- .description('Authenticate with RepoWise')
38
- .option('--no-browser', 'Print login URL instead of opening browser')
39
- .action(async (options) => {
40
- const { login } = await import('../src/commands/login.js');
41
- await login(options);
42
- });
43
- program
44
- .command('logout')
45
- .description('Sign out of RepoWise')
46
- .action(async () => {
47
- const { logout } = await import('../src/commands/logout.js');
48
- await logout();
49
- });
50
- program
51
- .command('status')
52
- .description('Show current status')
53
- .action(async () => {
54
- const { status } = await import('../src/commands/status.js');
55
- await status();
56
- });
57
- program
58
- .command('sync')
59
- .description('Trigger a manual sync')
60
- .action(async () => {
61
- const { sync } = await import('../src/commands/sync.js');
62
- sync();
63
- });
64
- program
65
- .command('listen')
66
- .description('Start the context listener')
67
- .option('--install', 'Install auto-start service')
68
- .option('--uninstall', 'Remove auto-start service')
69
- .action(async (options) => {
70
- const { listen } = await import('../src/commands/listen.js');
71
- await listen(options);
72
- });
73
- program
74
- .command('start')
75
- .description('Start the listener as a background process')
76
- .action(async () => {
77
- const { start } = await import('../src/commands/start.js');
78
- await start();
79
- });
80
- program
81
- .command('stop')
82
- .description('Stop the running listener process')
83
- .action(async () => {
84
- const { stop } = await import('../src/commands/stop.js');
85
- await stop();
86
- });
87
- program
88
- .command('config')
89
- .description('Manage configuration')
90
- .action(async () => {
91
- const { config } = await import('../src/commands/config.js');
92
- await config();
93
- });
94
- // Handle internal listener mode (used when spawning listener from bundled CLI)
95
- if (process.argv[2] === '__listener') {
96
- const { startListener } = await import('@repowise/listener/main');
97
- await startListener();
98
- }
99
- else {
100
- program.parse();
101
- }
102
- //# sourceMappingURL=repowise.js.map
1427
+ import { execSync } from "child_process";
1428
+ import { mkdirSync, writeFileSync } from "fs";
1429
+ import { join as join6 } from "path";
1430
+ import chalk5 from "chalk";
1431
+ import ora from "ora";
1432
+ import { select } from "@inquirer/prompts";
1433
+ function detectRepoRoot() {
1434
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
1435
+ }
1436
+ function detectRepoName(repoRoot) {
1437
+ try {
1438
+ const remoteUrl = execSync("git remote get-url origin", {
1439
+ encoding: "utf-8",
1440
+ cwd: repoRoot
1441
+ }).trim();
1442
+ const match = remoteUrl.match(/\/([^/]+?)(?:\.git)?$/);
1443
+ if (match?.[1]) return match[1];
1444
+ } catch {
1445
+ }
1446
+ return repoRoot.split("/").pop() ?? "unknown";
1447
+ }
1448
+ function formatElapsed(ms) {
1449
+ const totalSeconds = Math.round(ms / 1e3);
1450
+ const minutes = Math.floor(totalSeconds / 60);
1451
+ const seconds = totalSeconds % 60;
1452
+ if (minutes === 0) return `${seconds}s`;
1453
+ return seconds > 0 ? `${minutes}m ${seconds}s` : `${minutes}m`;
1454
+ }
1455
+ async function create(options = {}) {
1456
+ const startTime = Date.now();
1457
+ const spinner = ora("Checking authentication...").start();
1458
+ try {
1459
+ let credentials = await getValidCredentials();
1460
+ if (!credentials) {
1461
+ spinner.info(chalk5.yellow("Not logged in. Opening browser to authenticate..."));
1462
+ credentials = await performLogin();
1463
+ const { email } = decodeIdToken(credentials.idToken);
1464
+ spinner.succeed(chalk5.green(`Authenticated as ${chalk5.bold(email)}`));
1465
+ } else {
1466
+ spinner.succeed("Authenticated");
1467
+ }
1468
+ let repoId;
1469
+ let repoName;
1470
+ let repoRoot;
1471
+ spinner.start("Checking for pending repository...");
1472
+ try {
1473
+ const pending = await apiRequest("/v1/onboarding/pending");
1474
+ if (pending?.repoId) {
1475
+ repoId = pending.repoId;
1476
+ repoName = pending.repoName;
1477
+ spinner.succeed(`Found pending repository: ${chalk5.bold(repoName)}`);
1478
+ apiRequest("/v1/onboarding/pending", { method: "DELETE" }).catch(() => {
1479
+ });
1480
+ }
1481
+ } catch {
1482
+ }
1483
+ if (!repoId) {
1484
+ spinner.text = "Detecting repository...";
1485
+ try {
1486
+ repoRoot = detectRepoRoot();
1487
+ repoName = detectRepoName(repoRoot);
1488
+ spinner.succeed(`Repository: ${chalk5.bold(repoName)}`);
1489
+ } catch {
1490
+ spinner.fail(
1491
+ chalk5.red(
1492
+ "Not in a git repository. Run this command from your repo directory, or select a repo on the dashboard first."
1493
+ )
1494
+ );
1495
+ process.exitCode = 1;
1496
+ return;
1497
+ }
1498
+ try {
1499
+ const repos = await apiRequest("/v1/repos");
1500
+ const match = repos.find((r) => r.name === repoName || r.fullName.endsWith(`/${repoName}`));
1501
+ if (match) {
1502
+ repoId = match.repoId;
1503
+ }
1504
+ } catch {
1505
+ }
1506
+ } else {
1507
+ try {
1508
+ repoRoot = detectRepoRoot();
1509
+ } catch {
1510
+ }
1511
+ }
1512
+ if (!repoId) {
1513
+ spinner.fail(
1514
+ chalk5.red(
1515
+ "Could not find this repository in your RepoWise account. Connect it on the dashboard first."
1516
+ )
1517
+ );
1518
+ process.exitCode = 1;
1519
+ return;
1520
+ }
1521
+ const { tools, hasOther } = await selectAiTools();
1522
+ if (hasOther) {
1523
+ console.log(
1524
+ chalk5.cyan(
1525
+ "\nFor AI tools not listed, context files still work with any tool that reads the filesystem.\nRequest support for your tool at: https://dashboard.repowise.ai/support/ai-tools"
1526
+ )
1527
+ );
1528
+ }
1529
+ if (tools.length === 0 && !hasOther) {
1530
+ console.log(
1531
+ chalk5.yellow(
1532
+ "\nNo AI tools selected. You can configure them later with `repowise config`."
1533
+ )
1534
+ );
1535
+ }
1536
+ let contextStorage = options.contextStorage ?? "git";
1537
+ if (!options.contextStorage) {
1538
+ console.log("");
1539
+ console.log(chalk5.bold("How should we deliver your context files?"));
1540
+ console.log(
1541
+ chalk5.dim("RepoWise generates markdown files that help AI tools understand your project \u2014")
1542
+ );
1543
+ console.log(
1544
+ chalk5.dim("architecture docs, API references, patterns. No secrets or source code.")
1545
+ );
1546
+ console.log("");
1547
+ contextStorage = await select({
1548
+ message: "Context storage",
1549
+ choices: [
1550
+ { name: "Push to your git repo (recommended)", value: "git" },
1551
+ { name: "Store encrypted on RepoWise servers", value: "server" }
1552
+ ]
1553
+ });
1554
+ if (contextStorage === "server") {
1555
+ console.log(
1556
+ chalk5.yellow(
1557
+ "\n Warning: Server storage opts out of zero-retention. Files are encrypted (AES-256/KMS)"
1558
+ )
1559
+ );
1560
+ console.log(chalk5.yellow(" and deletable anytime from your dashboard or CLI.\n"));
1561
+ }
1562
+ } else if (options.contextStorage === "server") {
1563
+ console.log(chalk5.yellow("Using server-side encrypted storage (--context-storage=server)"));
1564
+ }
1565
+ spinner.start("Starting context generation pipeline...");
1566
+ let syncId;
1567
+ try {
1568
+ const triggerResult = await apiRequest(`/v1/repos/${repoId}/sync`, {
1569
+ method: "POST",
1570
+ body: JSON.stringify({ scanType: "full", contextStorage })
1571
+ });
1572
+ syncId = triggerResult.syncId;
1573
+ } catch (triggerErr) {
1574
+ const msg = triggerErr instanceof Error ? triggerErr.message : "";
1575
+ if (!msg.toLowerCase().includes("already running")) {
1576
+ throw triggerErr;
1577
+ }
1578
+ spinner.text = "Resuming existing pipeline...";
1579
+ const syncs = await apiRequest(
1580
+ `/v1/repos/${repoId}/syncs?limit=1`
1581
+ );
1582
+ const active = syncs.items.find(
1583
+ (s) => s.status === "in_progress" || s.status === "awaiting_input"
1584
+ );
1585
+ if (!active) {
1586
+ throw new Error("Could not find active sync to resume. Please try again.");
1587
+ }
1588
+ syncId = active.syncId;
1589
+ spinner.info(chalk5.cyan("Resuming existing pipeline..."));
1590
+ spinner.start();
1591
+ }
1592
+ let pollAttempts = 0;
1593
+ const progressRenderer = new ProgressRenderer();
1594
+ while (true) {
1595
+ if (++pollAttempts > MAX_POLL_ATTEMPTS) {
1596
+ spinner.fail(chalk5.red("Pipeline timed out. Check dashboard for status."));
1597
+ process.exitCode = 1;
1598
+ return;
1599
+ }
1600
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
1601
+ const syncResult = await apiRequest(`/v1/sync/${syncId}/status`);
1602
+ progressRenderer.update(syncResult, spinner);
1603
+ if (syncResult.status === "awaiting_input" && syncResult.questionId && syncResult.questionText) {
1604
+ spinner.stop();
1605
+ await handleInterview(
1606
+ syncId,
1607
+ syncResult.questionId,
1608
+ syncResult.questionText,
1609
+ syncResult.questionContext ?? void 0,
1610
+ syncResult.discoveryResult?.estimatedInterviewQuestions
1611
+ );
1612
+ spinner.start("Resuming pipeline...");
1613
+ continue;
1614
+ }
1615
+ if (syncResult.status === "completed") {
1616
+ const generatedFiles = syncResult.filesGenerated ?? [];
1617
+ const fileCount = generatedFiles.length;
1618
+ if (fileCount > 0) {
1619
+ const coreCount = generatedFiles.filter(
1620
+ (f) => CORE_FILES.has(f.split("/").pop() ?? f)
1621
+ ).length;
1622
+ const tailoredCount = fileCount - coreCount;
1623
+ spinner.succeed(
1624
+ `Context generation complete \u2014 ${coreCount} core + ${tailoredCount} tailored files`
1625
+ );
1626
+ } else {
1627
+ spinner.warn(chalk5.yellow("Pipeline completed but no context files were generated."));
1628
+ console.log(
1629
+ chalk5.yellow(
1630
+ " This may be due to AI throttling or a parsing issue. Try running `repowise create` again."
1631
+ )
1632
+ );
1633
+ }
1634
+ break;
1635
+ }
1636
+ if (syncResult.status === "failed") {
1637
+ spinner.fail(chalk5.red(`Pipeline failed: ${syncResult.error ?? "Unknown error"}`));
1638
+ process.exitCode = 1;
1639
+ return;
1640
+ }
1641
+ }
1642
+ if (contextStorage === "server" && repoRoot) {
1643
+ spinner.start("Downloading context files from server...");
1644
+ try {
1645
+ const listResult = await apiRequest(`/v1/repos/${repoId}/context`);
1646
+ const files = listResult.data?.files ?? listResult.files ?? [];
1647
+ if (files.length > 0) {
1648
+ const contextDir = join6(repoRoot, DEFAULT_CONTEXT_FOLDER);
1649
+ mkdirSync(contextDir, { recursive: true });
1650
+ for (const file of files) {
1651
+ const urlResult = await apiRequest(`/v1/repos/${repoId}/context/${file.fileName}`);
1652
+ const presignedUrl = urlResult.data?.url ?? urlResult.url;
1653
+ const response = await fetch(presignedUrl);
1654
+ if (response.ok) {
1655
+ const content = await response.text();
1656
+ writeFileSync(join6(contextDir, file.fileName), content, "utf-8");
1657
+ }
1658
+ }
1659
+ spinner.succeed(`Context files downloaded to ./${DEFAULT_CONTEXT_FOLDER}/`);
1660
+ } else {
1661
+ spinner.warn("No context files found on server");
1662
+ }
1663
+ } catch (err) {
1664
+ const msg = err instanceof Error ? err.message : "Unknown error";
1665
+ spinner.warn(
1666
+ chalk5.yellow(
1667
+ `Cannot reach RepoWise servers to download context: ${msg}
1668
+ Files are stored on our servers (not in git). Retry when online.`
1669
+ )
1670
+ );
1671
+ }
1672
+ } else if (repoRoot) {
1673
+ spinner.start("Pulling latest changes...");
1674
+ try {
1675
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
1676
+ cwd: repoRoot,
1677
+ encoding: "utf-8",
1678
+ stdio: "pipe"
1679
+ }).trim();
1680
+ execSync(`git pull origin ${branch}`, { cwd: repoRoot, encoding: "utf-8", stdio: "pipe" });
1681
+ spinner.succeed("Pulled latest changes");
1682
+ } catch (err) {
1683
+ const stderr = err instanceof Error && "stderr" in err ? String(err.stderr).trim() : "";
1684
+ const reason = stderr || (err instanceof Error ? err.message : "Unknown error");
1685
+ spinner.warn(
1686
+ chalk5.yellow(
1687
+ `Could not pull latest changes: ${reason}
1688
+ You may need to run \`git pull\` manually.`
1689
+ )
1690
+ );
1691
+ }
1692
+ }
1693
+ const contextFolder = DEFAULT_CONTEXT_FOLDER;
1694
+ let contextFiles = [];
1695
+ if (repoRoot) {
1696
+ contextFiles = await scanLocalContextFiles(repoRoot, contextFolder);
1697
+ }
1698
+ if (contextFiles.length === 0) {
1699
+ console.log(
1700
+ chalk5.yellow(
1701
+ ` No context files found in ${contextFolder}/. You may need to run \`git pull\` manually or re-run \`repowise create\`.`
1702
+ )
1703
+ );
1704
+ }
1705
+ if (tools.length > 0 && repoRoot) {
1706
+ spinner.start("Configuring AI tools...");
1707
+ const results = [];
1708
+ for (const tool of tools) {
1709
+ const { created: wasCreated } = await updateToolConfig(
1710
+ repoRoot,
1711
+ tool,
1712
+ repoName,
1713
+ contextFolder,
1714
+ contextFiles
1715
+ );
1716
+ const config2 = AI_TOOL_CONFIG[tool];
1717
+ const action = wasCreated ? "Created" : "Updated";
1718
+ results.push(` ${action} ${config2.filePath}`);
1719
+ }
1720
+ spinner.succeed("AI tools configured");
1721
+ console.log(chalk5.dim(results.join("\n")));
1722
+ }
1723
+ const existingConfig = await getConfig();
1724
+ const existingRepos = existingConfig.repos ?? [];
1725
+ const updatedRepos = existingRepos.filter((r) => r.repoId !== repoId);
1726
+ if (repoRoot) {
1727
+ updatedRepos.push({ repoId, localPath: repoRoot });
1728
+ }
1729
+ await saveConfig({
1730
+ ...existingConfig,
1731
+ aiTools: tools,
1732
+ contextFolder,
1733
+ repos: updatedRepos
1734
+ });
1735
+ let listenerRunning = false;
1736
+ try {
1737
+ const { install: install2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
1738
+ const { startBackground: startBackground2 } = await Promise.resolve().then(() => (init_process_manager(), process_manager_exports));
1739
+ await install2();
1740
+ await startBackground2();
1741
+ listenerRunning = true;
1742
+ } catch {
1743
+ console.log(
1744
+ chalk5.yellow(
1745
+ "Warning: Could not start listener automatically. Run the following to enable it:"
1746
+ )
1747
+ );
1748
+ console.log(chalk5.yellow(` $ repowise listen --install`));
1749
+ }
1750
+ const elapsed = formatElapsed(Date.now() - startTime);
1751
+ console.log("");
1752
+ console.log(chalk5.green.bold(" All done! Setup complete!"));
1753
+ console.log(
1754
+ chalk5.green(
1755
+ ` Your AI tools now have access to project context for ${chalk5.bold(repoName)}.`
1756
+ )
1757
+ );
1758
+ if (listenerRunning) {
1759
+ console.log("");
1760
+ console.log(chalk5.cyan(" The RepoWise listener is running in the background \u2014"));
1761
+ console.log(chalk5.cyan(" your context will stay in sync automatically."));
1762
+ console.log(chalk5.cyan(" Go back to coding, we've got it from here!"));
1763
+ }
1764
+ console.log("");
1765
+ console.log(
1766
+ chalk5.cyan(
1767
+ ' Head back to the dashboard and click "Complete Onboarding" to explore your RepoWise dashboard!'
1768
+ )
1769
+ );
1770
+ console.log(chalk5.dim(`
1771
+ Total time: ${elapsed}`));
1772
+ } catch (err) {
1773
+ const message = err instanceof Error ? err.message : "Create failed";
1774
+ spinner.fail(chalk5.red(message));
1775
+ process.exitCode = 1;
1776
+ }
1777
+ }
1778
+ var POLL_INTERVAL_MS, MAX_POLL_ATTEMPTS, DEFAULT_CONTEXT_FOLDER;
1779
+ var init_create = __esm({
1780
+ "src/commands/create.ts"() {
1781
+ "use strict";
1782
+ init_auth();
1783
+ init_api();
1784
+ init_prompts();
1785
+ init_ai_tools();
1786
+ init_config();
1787
+ init_interview_handler();
1788
+ init_progress_renderer();
1789
+ POLL_INTERVAL_MS = 3e3;
1790
+ MAX_POLL_ATTEMPTS = 600;
1791
+ DEFAULT_CONTEXT_FOLDER = "repowise-context";
1792
+ }
1793
+ });
1794
+
1795
+ // src/commands/login.ts
1796
+ var login_exports = {};
1797
+ __export(login_exports, {
1798
+ login: () => login
1799
+ });
1800
+ import chalk6 from "chalk";
1801
+ import ora2 from "ora";
1802
+ async function login(options = {}) {
1803
+ const spinner = ora2("Preparing login...").start();
1804
+ try {
1805
+ const codeVerifier = generateCodeVerifier();
1806
+ const codeChallenge = generateCodeChallenge(codeVerifier);
1807
+ const state = generateState();
1808
+ const authorizeUrl = getAuthorizeUrl(codeChallenge, state);
1809
+ const callbackPromise = startCallbackServer();
1810
+ if (options.browser === false) {
1811
+ spinner.stop();
1812
+ console.log(`
1813
+ Open this URL in your browser to authenticate:
1814
+ `);
1815
+ console.log(chalk6.cyan(authorizeUrl));
1816
+ console.log(`
1817
+ Waiting for authentication...`);
1818
+ } else {
1819
+ spinner.text = "Opening browser for authentication...";
1820
+ try {
1821
+ const open = (await import("open")).default;
1822
+ await open(authorizeUrl);
1823
+ spinner.text = "Waiting for authentication in browser...";
1824
+ } catch {
1825
+ spinner.stop();
1826
+ console.log(`
1827
+ Could not open browser automatically. Open this URL:
1828
+ `);
1829
+ console.log(chalk6.cyan(authorizeUrl));
1830
+ console.log(`
1831
+ Waiting for authentication...`);
1832
+ }
1833
+ }
1834
+ const { code, state: returnedState } = await callbackPromise;
1835
+ if (returnedState !== state) {
1836
+ throw new Error("State mismatch \u2014 possible CSRF attack. Please try again.");
1837
+ }
1838
+ spinner.start("Exchanging authorization code...");
1839
+ const credentials = await exchangeCodeForTokens(code, codeVerifier);
1840
+ await storeCredentials(credentials);
1841
+ const { email } = decodeIdToken(credentials.idToken);
1842
+ spinner.succeed(chalk6.green(`Logged in as ${chalk6.bold(email)}`));
1843
+ } catch (err) {
1844
+ const message = err instanceof Error ? err.message : "Login failed";
1845
+ spinner.fail(chalk6.red(message));
1846
+ process.exitCode = 1;
1847
+ }
1848
+ }
1849
+ var init_login = __esm({
1850
+ "src/commands/login.ts"() {
1851
+ "use strict";
1852
+ init_auth();
1853
+ }
1854
+ });
1855
+
1856
+ // src/commands/logout.ts
1857
+ var logout_exports = {};
1858
+ __export(logout_exports, {
1859
+ logout: () => logout
1860
+ });
1861
+ import chalk7 from "chalk";
1862
+ async function logout() {
1863
+ const creds = await getStoredCredentials();
1864
+ if (!creds) {
1865
+ console.log(chalk7.yellow("Not logged in."));
1866
+ return;
1867
+ }
1868
+ await clearCredentials();
1869
+ console.log(chalk7.green("Logged out successfully."));
1870
+ }
1871
+ var init_logout = __esm({
1872
+ "src/commands/logout.ts"() {
1873
+ "use strict";
1874
+ init_auth();
1875
+ }
1876
+ });
1877
+
1878
+ // src/commands/status.ts
1879
+ var status_exports = {};
1880
+ __export(status_exports, {
1881
+ status: () => status
1882
+ });
1883
+ import { readFile as readFile5 } from "fs/promises";
1884
+ import { homedir as homedir5 } from "os";
1885
+ import { join as join7 } from "path";
1886
+ async function status() {
1887
+ let state = null;
1888
+ try {
1889
+ const data = await readFile5(STATE_PATH, "utf-8");
1890
+ state = JSON.parse(data);
1891
+ } catch {
1892
+ }
1893
+ let processRunning = false;
1894
+ let pid = null;
1895
+ try {
1896
+ const { getStatus: getStatus2 } = await Promise.resolve().then(() => (init_process_manager(), process_manager_exports));
1897
+ const processStatus = await getStatus2();
1898
+ processRunning = processStatus.running;
1899
+ pid = processStatus.pid;
1900
+ } catch {
1901
+ }
1902
+ let serviceInstalled = false;
1903
+ try {
1904
+ const { isInstalled: isInstalled2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
1905
+ serviceInstalled = await isInstalled2();
1906
+ } catch {
1907
+ }
1908
+ console.log("RepoWise Status");
1909
+ console.log("===============");
1910
+ if (processRunning) {
1911
+ console.log(`Listener: running (PID: ${pid})`);
1912
+ } else {
1913
+ console.log("Listener: stopped");
1914
+ }
1915
+ if (serviceInstalled) {
1916
+ console.log("Auto-start: enabled");
1917
+ } else {
1918
+ console.log("Auto-start: disabled");
1919
+ }
1920
+ console.log("");
1921
+ if (!state || Object.keys(state.repos).length === 0) {
1922
+ console.log("No sync history. Run `repowise listen` to start syncing.");
1923
+ return;
1924
+ }
1925
+ console.log("Watched Repos:");
1926
+ for (const [repoId, repoState] of Object.entries(state.repos)) {
1927
+ const syncTime = repoState.lastSyncTimestamp ? new Date(repoState.lastSyncTimestamp).toLocaleString() : "never";
1928
+ const commit = repoState.lastSyncCommitSha ? repoState.lastSyncCommitSha.slice(0, 7) : "none";
1929
+ console.log(` ${repoId}: last sync ${syncTime} (commit: ${commit})`);
1930
+ }
1931
+ }
1932
+ var STATE_PATH;
1933
+ var init_status = __esm({
1934
+ "src/commands/status.ts"() {
1935
+ "use strict";
1936
+ STATE_PATH = join7(homedir5(), ".repowise", "listener-state.json");
1937
+ }
1938
+ });
1939
+
1940
+ // src/commands/sync.ts
1941
+ var sync_exports = {};
1942
+ __export(sync_exports, {
1943
+ sync: () => sync
1944
+ });
1945
+ function sync() {
1946
+ console.log("Sync command - not yet implemented");
1947
+ }
1948
+ var init_sync = __esm({
1949
+ "src/commands/sync.ts"() {
1950
+ "use strict";
1951
+ }
1952
+ });
1953
+
1954
+ // ../listener/dist/lib/config.js
1955
+ import { readFile as readFile6 } from "fs/promises";
1956
+ import { homedir as homedir6 } from "os";
1957
+ import { join as join8 } from "path";
1958
+ async function getListenerConfig() {
1959
+ const apiUrl = process.env["REPOWISE_API_URL"] ?? DEFAULT_API_URL;
1960
+ try {
1961
+ const data = await readFile6(CONFIG_PATH2, "utf-8");
1962
+ const raw = JSON.parse(data);
1963
+ const validRepos = (raw.repos ?? []).filter((r) => typeof r === "object" && r !== null && typeof r.repoId === "string" && typeof r.localPath === "string");
1964
+ return {
1965
+ apiUrl: raw.apiUrl ?? apiUrl,
1966
+ repos: validRepos
1967
+ };
1968
+ } catch {
1969
+ return { apiUrl, repos: [] };
1970
+ }
1971
+ }
1972
+ var CONFIG_DIR3, CONFIG_PATH2, DEFAULT_API_URL;
1973
+ var init_config2 = __esm({
1974
+ "../listener/dist/lib/config.js"() {
1975
+ "use strict";
1976
+ CONFIG_DIR3 = join8(homedir6(), ".repowise");
1977
+ CONFIG_PATH2 = join8(CONFIG_DIR3, "config.json");
1978
+ DEFAULT_API_URL = "https://api.repowise.ai";
1979
+ }
1980
+ });
1981
+
1982
+ // ../listener/dist/lib/state.js
1983
+ import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir6, chmod as chmod2 } from "fs/promises";
1984
+ import { homedir as homedir7 } from "os";
1985
+ import { join as join9 } from "path";
1986
+ function emptyState() {
1987
+ return { repos: {} };
1988
+ }
1989
+ async function loadState() {
1990
+ try {
1991
+ const data = await readFile7(STATE_PATH2, "utf-8");
1992
+ return JSON.parse(data);
1993
+ } catch (err) {
1994
+ if (err.code === "ENOENT" || err instanceof SyntaxError) {
1995
+ return emptyState();
1996
+ }
1997
+ throw err;
1998
+ }
1999
+ }
2000
+ async function saveState(state) {
2001
+ await mkdir6(CONFIG_DIR4, { recursive: true, mode: 448 });
2002
+ await writeFile6(STATE_PATH2, JSON.stringify(state, null, 2));
2003
+ await chmod2(STATE_PATH2, 384);
2004
+ }
2005
+ var CONFIG_DIR4, STATE_PATH2;
2006
+ var init_state = __esm({
2007
+ "../listener/dist/lib/state.js"() {
2008
+ "use strict";
2009
+ CONFIG_DIR4 = join9(homedir7(), ".repowise");
2010
+ STATE_PATH2 = join9(CONFIG_DIR4, "listener-state.json");
2011
+ }
2012
+ });
2013
+
2014
+ // ../listener/dist/lib/auth.js
2015
+ import { readFile as readFile8, writeFile as writeFile7, mkdir as mkdir7, chmod as chmod3 } from "fs/promises";
2016
+ import { homedir as homedir8 } from "os";
2017
+ import { join as join10 } from "path";
2018
+ function getCognitoConfig2() {
2019
+ return {
2020
+ domain: process.env["REPOWISE_COGNITO_DOMAIN"] ?? "auth-repowise-dev",
2021
+ clientId: process.env["REPOWISE_COGNITO_CLIENT_ID"] ?? "",
2022
+ region: process.env["REPOWISE_COGNITO_REGION"] ?? "us-east-1"
2023
+ };
2024
+ }
2025
+ function getTokenUrl2() {
2026
+ const { domain, region } = getCognitoConfig2();
2027
+ return `https://${domain}.auth.${region}.amazoncognito.com/oauth2/token`;
2028
+ }
2029
+ async function refreshTokens2(refreshToken) {
2030
+ const response = await fetch(getTokenUrl2(), {
2031
+ method: "POST",
2032
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
2033
+ body: new URLSearchParams({
2034
+ grant_type: "refresh_token",
2035
+ client_id: getCognitoConfig2().clientId,
2036
+ refresh_token: refreshToken
2037
+ })
2038
+ });
2039
+ if (!response.ok) {
2040
+ throw new Error(`Token refresh failed: ${response.status}`);
2041
+ }
2042
+ const data = await response.json();
2043
+ return {
2044
+ accessToken: data.access_token,
2045
+ refreshToken,
2046
+ idToken: data.id_token,
2047
+ expiresAt: Date.now() + data.expires_in * 1e3
2048
+ };
2049
+ }
2050
+ async function getStoredCredentials2() {
2051
+ try {
2052
+ const data = await readFile8(CREDENTIALS_PATH2, "utf-8");
2053
+ return JSON.parse(data);
2054
+ } catch (err) {
2055
+ if (err.code === "ENOENT" || err instanceof SyntaxError) {
2056
+ return null;
2057
+ }
2058
+ throw err;
2059
+ }
2060
+ }
2061
+ async function storeCredentials2(credentials) {
2062
+ await mkdir7(CONFIG_DIR5, { recursive: true, mode: 448 });
2063
+ await writeFile7(CREDENTIALS_PATH2, JSON.stringify(credentials, null, 2));
2064
+ await chmod3(CREDENTIALS_PATH2, 384);
2065
+ }
2066
+ async function getValidCredentials2() {
2067
+ const creds = await getStoredCredentials2();
2068
+ if (!creds)
2069
+ return null;
2070
+ if (Date.now() > creds.expiresAt - 5 * 60 * 1e3) {
2071
+ try {
2072
+ const refreshed = await refreshTokens2(creds.refreshToken);
2073
+ await storeCredentials2(refreshed);
2074
+ return refreshed;
2075
+ } catch {
2076
+ return null;
2077
+ }
2078
+ }
2079
+ return creds;
2080
+ }
2081
+ var CONFIG_DIR5, CREDENTIALS_PATH2;
2082
+ var init_auth2 = __esm({
2083
+ "../listener/dist/lib/auth.js"() {
2084
+ "use strict";
2085
+ CONFIG_DIR5 = join10(homedir8(), ".repowise");
2086
+ CREDENTIALS_PATH2 = join10(CONFIG_DIR5, "credentials.json");
2087
+ }
2088
+ });
2089
+
2090
+ // ../listener/dist/poll-client.js
2091
+ var POLL_TIMEOUT_MS, AuthError, PollClient;
2092
+ var init_poll_client = __esm({
2093
+ "../listener/dist/poll-client.js"() {
2094
+ "use strict";
2095
+ init_auth2();
2096
+ POLL_TIMEOUT_MS = 3e4;
2097
+ AuthError = class extends Error {
2098
+ constructor(message) {
2099
+ super(message);
2100
+ this.name = "AuthError";
2101
+ }
2102
+ };
2103
+ PollClient = class {
2104
+ apiUrl;
2105
+ constructor(apiUrl) {
2106
+ this.apiUrl = apiUrl;
2107
+ }
2108
+ async poll(repoIds, since) {
2109
+ const credentials = await getValidCredentials2();
2110
+ if (!credentials) {
2111
+ throw new AuthError("Not logged in. Run `repowise login` first.");
2112
+ }
2113
+ const params = new URLSearchParams({
2114
+ repoIds: repoIds.join(","),
2115
+ since
2116
+ });
2117
+ const controller = new AbortController();
2118
+ const timeoutId = setTimeout(() => controller.abort(), POLL_TIMEOUT_MS);
2119
+ try {
2120
+ const response = await fetch(`${this.apiUrl}/v1/listeners/poll?${params.toString()}`, {
2121
+ headers: {
2122
+ Authorization: `Bearer ${credentials.accessToken}`,
2123
+ "Content-Type": "application/json"
2124
+ },
2125
+ signal: controller.signal
2126
+ });
2127
+ if (response.status === 401) {
2128
+ throw new AuthError("Session expired. Run `repowise login` again.");
2129
+ }
2130
+ if (!response.ok) {
2131
+ throw new Error(`Poll request failed: ${response.status}`);
2132
+ }
2133
+ const json = await response.json();
2134
+ return json.data;
2135
+ } finally {
2136
+ clearTimeout(timeoutId);
2137
+ }
2138
+ }
2139
+ };
2140
+ }
2141
+ });
2142
+
2143
+ // ../listener/dist/reconnection.js
2144
+ var DEFAULT_CONFIG, BackoffCalculator;
2145
+ var init_reconnection = __esm({
2146
+ "../listener/dist/reconnection.js"() {
2147
+ "use strict";
2148
+ DEFAULT_CONFIG = {
2149
+ initialDelay: 1e3,
2150
+ maxDelay: 6e4,
2151
+ jitterMax: 1e3
2152
+ };
2153
+ BackoffCalculator = class {
2154
+ config;
2155
+ attempt = 0;
2156
+ constructor(config2 = {}) {
2157
+ this.config = { ...DEFAULT_CONFIG, ...config2 };
2158
+ }
2159
+ nextDelay() {
2160
+ const baseDelay = Math.min(this.config.initialDelay * Math.pow(2, this.attempt), this.config.maxDelay);
2161
+ const jitter = Math.random() * this.config.jitterMax;
2162
+ this.attempt++;
2163
+ return baseDelay + jitter;
2164
+ }
2165
+ reset() {
2166
+ this.attempt = 0;
2167
+ }
2168
+ getAttempt() {
2169
+ return this.attempt;
2170
+ }
2171
+ };
2172
+ }
2173
+ });
2174
+
2175
+ // ../listener/dist/notification.js
2176
+ import notifier from "node-notifier";
2177
+ function notifyConnectionLost() {
2178
+ try {
2179
+ notify({
2180
+ title: TITLE,
2181
+ message: "Connection lost \u2014 will sync when back online"
2182
+ });
2183
+ } catch {
2184
+ }
2185
+ }
2186
+ function notifyBackOnline(updateCount) {
2187
+ try {
2188
+ notify({
2189
+ title: TITLE,
2190
+ message: `Back online \u2014 ${updateCount} updates synced`
2191
+ });
2192
+ } catch {
2193
+ }
2194
+ }
2195
+ function notifyContextUpdated(repoId, fileCount) {
2196
+ try {
2197
+ notify({
2198
+ title: TITLE,
2199
+ message: `Context updated for ${repoId} \u2014 ${fileCount} files`
2200
+ });
2201
+ } catch {
2202
+ }
2203
+ }
2204
+ var TITLE, notify;
2205
+ var init_notification = __esm({
2206
+ "../listener/dist/notification.js"() {
2207
+ "use strict";
2208
+ TITLE = "RepoWise";
2209
+ notify = notifier.notify.bind(notifier);
2210
+ }
2211
+ });
2212
+
2213
+ // ../listener/dist/file-writer.js
2214
+ import { access } from "fs/promises";
2215
+ import { join as join11 } from "path";
2216
+ async function verifyContextFolder(localPath) {
2217
+ try {
2218
+ await access(join11(localPath, "repowise-context"));
2219
+ return true;
2220
+ } catch {
2221
+ return false;
2222
+ }
2223
+ }
2224
+ var init_file_writer = __esm({
2225
+ "../listener/dist/file-writer.js"() {
2226
+ "use strict";
2227
+ }
2228
+ });
2229
+
2230
+ // ../listener/dist/context-fetcher.js
2231
+ import { execFile as execFile2 } from "child_process";
2232
+ import { mkdir as mkdir8, writeFile as writeFile8 } from "fs/promises";
2233
+ import { join as join12 } from "path";
2234
+ import { promisify } from "util";
2235
+ async function fetchContextUpdates(localPath) {
2236
+ try {
2237
+ const { stdout: beforeSha } = await execFileAsync("git", [
2238
+ "-C",
2239
+ localPath,
2240
+ "rev-parse",
2241
+ "HEAD"
2242
+ ]);
2243
+ const before = beforeSha.trim();
2244
+ await execFileAsync("git", ["-C", localPath, "pull", "--ff-only"]);
2245
+ const { stdout: afterSha } = await execFileAsync("git", ["-C", localPath, "rev-parse", "HEAD"]);
2246
+ const after = afterSha.trim();
2247
+ if (before === after) {
2248
+ return { success: true, updatedFiles: [] };
2249
+ }
2250
+ const { stdout } = await execFileAsync("git", [
2251
+ "-C",
2252
+ localPath,
2253
+ "diff",
2254
+ "--name-only",
2255
+ before,
2256
+ after,
2257
+ "--",
2258
+ "repowise-context/"
2259
+ ]);
2260
+ const updatedFiles = stdout.trim().split("\n").filter(Boolean);
2261
+ const contextExists = await verifyContextFolder(localPath);
2262
+ if (!contextExists && updatedFiles.length > 0) {
2263
+ console.warn(`Warning: repowise-context/ folder not found in ${localPath} after pull`);
2264
+ }
2265
+ return { success: true, updatedFiles };
2266
+ } catch (err) {
2267
+ const message = err instanceof Error ? err.message : "Unknown error";
2268
+ console.error(`Git pull failed for ${localPath}: ${message}`);
2269
+ return { success: false, updatedFiles: [] };
2270
+ }
2271
+ }
2272
+ async function fetchContextFromServer(repoId, localPath, apiUrl, authToken) {
2273
+ try {
2274
+ const headers = { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" };
2275
+ const listRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context`, { headers });
2276
+ if (!listRes.ok) {
2277
+ console.error(`Context list failed (${listRes.status}) for repo ${repoId}`);
2278
+ return { success: false, updatedFiles: [] };
2279
+ }
2280
+ const listData = await listRes.json();
2281
+ const files = listData.data?.files ?? [];
2282
+ if (files.length === 0) {
2283
+ return { success: true, updatedFiles: [] };
2284
+ }
2285
+ const contextDir = join12(localPath, "repowise-context");
2286
+ await mkdir8(contextDir, { recursive: true });
2287
+ const updatedFiles = [];
2288
+ for (const file of files) {
2289
+ const urlRes = await fetch(`${apiUrl}/v1/repos/${repoId}/context/${file.fileName}`, {
2290
+ headers
2291
+ });
2292
+ if (!urlRes.ok)
2293
+ continue;
2294
+ const urlData = await urlRes.json();
2295
+ const presignedUrl = urlData.data?.url;
2296
+ if (!presignedUrl)
2297
+ continue;
2298
+ const contentRes = await fetch(presignedUrl);
2299
+ if (!contentRes.ok)
2300
+ continue;
2301
+ const content = await contentRes.text();
2302
+ await writeFile8(join12(contextDir, file.fileName), content, "utf-8");
2303
+ updatedFiles.push(file.fileName);
2304
+ }
2305
+ return { success: true, updatedFiles };
2306
+ } catch (err) {
2307
+ const message = err instanceof Error ? err.message : "Unknown error";
2308
+ console.error(`Server context fetch failed for ${repoId}: ${message}`);
2309
+ return { success: false, updatedFiles: [] };
2310
+ }
2311
+ }
2312
+ var execFileAsync;
2313
+ var init_context_fetcher = __esm({
2314
+ "../listener/dist/context-fetcher.js"() {
2315
+ "use strict";
2316
+ init_file_writer();
2317
+ execFileAsync = promisify(execFile2);
2318
+ }
2319
+ });
2320
+
2321
+ // ../listener/dist/main.js
2322
+ var main_exports = {};
2323
+ __export(main_exports, {
2324
+ startListener: () => startListener,
2325
+ stop: () => stop
2326
+ });
2327
+ import { readFile as readFile9, writeFile as writeFile9, unlink as unlink4, mkdir as mkdir9 } from "fs/promises";
2328
+ import { homedir as homedir9 } from "os";
2329
+ import { join as join13 } from "path";
2330
+ async function writePidFile() {
2331
+ await mkdir9(join13(homedir9(), ".repowise"), { recursive: true });
2332
+ await writeFile9(PID_PATH2, String(process.pid));
2333
+ }
2334
+ async function removePidFile2() {
2335
+ try {
2336
+ await unlink4(PID_PATH2);
2337
+ } catch {
2338
+ }
2339
+ }
2340
+ async function handleStalePid() {
2341
+ try {
2342
+ const content = await readFile9(PID_PATH2, "utf-8");
2343
+ const pid = parseInt(content.trim(), 10);
2344
+ if (!Number.isNaN(pid) && pid !== process.pid) {
2345
+ try {
2346
+ process.kill(pid, 0);
2347
+ console.error(`Listener already running (PID: ${pid}). Stop it first with \`repowise stop\`.`);
2348
+ process.exitCode = 1;
2349
+ return true;
2350
+ } catch {
2351
+ }
2352
+ }
2353
+ } catch {
2354
+ }
2355
+ return false;
2356
+ }
2357
+ function stop() {
2358
+ running = false;
2359
+ if (sleepResolve) {
2360
+ sleepResolve();
2361
+ sleepResolve = null;
2362
+ }
2363
+ }
2364
+ function sleep(ms) {
2365
+ return new Promise((resolve) => {
2366
+ sleepResolve = resolve;
2367
+ const timer = setTimeout(() => {
2368
+ sleepResolve = null;
2369
+ resolve();
2370
+ }, ms);
2371
+ if (typeof timer === "object" && "unref" in timer)
2372
+ timer.unref();
2373
+ });
2374
+ }
2375
+ async function processNotifications(notifications, state, repoLocalPaths, apiUrl, authToken) {
2376
+ let updateCount = 0;
2377
+ for (const notif of notifications) {
2378
+ if (notif.type === "sync.completed") {
2379
+ const localPath = repoLocalPaths.get(notif.repoId);
2380
+ if (localPath) {
2381
+ let result;
2382
+ if (notif.contextStorage === "server" && apiUrl && authToken) {
2383
+ result = await fetchContextFromServer(notif.repoId, localPath, apiUrl, authToken);
2384
+ } else {
2385
+ result = await fetchContextUpdates(localPath);
2386
+ }
2387
+ if (result.success) {
2388
+ updateCount++;
2389
+ notifyContextUpdated(notif.repoId, result.updatedFiles.length);
2390
+ }
2391
+ }
2392
+ }
2393
+ state.repos[notif.repoId] = {
2394
+ lastSyncTimestamp: notif.createdAt,
2395
+ lastSyncCommitSha: notif.commitSha
2396
+ };
2397
+ }
2398
+ return updateCount;
2399
+ }
2400
+ async function handleCatchUp(offlineState, pollClient, repoIds, state, repoLocalPaths, apiUrl, authToken) {
2401
+ if (!offlineState.offlineSince)
2402
+ return;
2403
+ const offlineDuration = Date.now() - new Date(offlineState.offlineSince).getTime();
2404
+ if (offlineDuration >= TWENTY_FOUR_HOURS_MS) {
2405
+ let syncCount = 0;
2406
+ for (const [, localPath] of repoLocalPaths) {
2407
+ const result = await fetchContextUpdates(localPath);
2408
+ if (result.success)
2409
+ syncCount++;
2410
+ }
2411
+ notifyBackOnline(syncCount);
2412
+ } else {
2413
+ const sinceTimestamp = offlineState.offlineSince;
2414
+ const response = await pollClient.poll(repoIds, sinceTimestamp);
2415
+ const updateCount = await processNotifications(response.notifications, state, repoLocalPaths, apiUrl, authToken);
2416
+ await saveState(state);
2417
+ notifyBackOnline(updateCount);
2418
+ }
2419
+ }
2420
+ async function startListener() {
2421
+ running = true;
2422
+ const alreadyRunning = await handleStalePid();
2423
+ if (alreadyRunning)
2424
+ return;
2425
+ await writePidFile();
2426
+ const config2 = await getListenerConfig();
2427
+ if (config2.repos.length === 0) {
2428
+ console.error("No repos configured. Add repos to ~/.repowise/config.json");
2429
+ process.exitCode = 1;
2430
+ return;
2431
+ }
2432
+ const credentials = await getValidCredentials2();
2433
+ if (!credentials) {
2434
+ console.error("Not logged in. Run `repowise login` first.");
2435
+ process.exitCode = 1;
2436
+ return;
2437
+ }
2438
+ const state = await loadState();
2439
+ const pollClient = new PollClient(config2.apiUrl);
2440
+ const backoff = new BackoffCalculator();
2441
+ const repoIds = config2.repos.map((r) => r.repoId);
2442
+ const repoLocalPaths = new Map(config2.repos.map((r) => [r.repoId, r.localPath]));
2443
+ const offlineState = {
2444
+ isOffline: false,
2445
+ offlineSince: null,
2446
+ attemptCount: 0
2447
+ };
2448
+ const now = (/* @__PURE__ */ new Date()).toISOString();
2449
+ for (const repoId of repoIds) {
2450
+ if (!state.repos[repoId]) {
2451
+ state.repos[repoId] = { lastSyncTimestamp: now, lastSyncCommitSha: null };
2452
+ }
2453
+ }
2454
+ let pollIntervalMs = 5e3;
2455
+ console.log(`RepoWise Listener started \u2014 watching ${repoIds.length} repo(s)`);
2456
+ const shutdown = async () => {
2457
+ console.log("Shutting down...");
2458
+ stop();
2459
+ await saveState(state);
2460
+ await removePidFile2();
2461
+ };
2462
+ process.on("SIGTERM", () => void shutdown());
2463
+ process.on("SIGINT", () => void shutdown());
2464
+ while (running) {
2465
+ try {
2466
+ const sinceTimestamp = repoIds.reduce((earliest, id) => {
2467
+ const ts = state.repos[id]?.lastSyncTimestamp ?? now;
2468
+ return ts < earliest ? ts : earliest;
2469
+ }, now);
2470
+ const response = await pollClient.poll(repoIds, sinceTimestamp);
2471
+ if (offlineState.isOffline) {
2472
+ await handleCatchUp(offlineState, pollClient, repoIds, state, repoLocalPaths, config2.apiUrl, credentials?.idToken);
2473
+ offlineState.isOffline = false;
2474
+ offlineState.offlineSince = null;
2475
+ offlineState.attemptCount = 0;
2476
+ backoff.reset();
2477
+ } else if (response.notifications.length > 0) {
2478
+ await processNotifications(response.notifications, state, repoLocalPaths, config2.apiUrl, credentials?.idToken);
2479
+ await saveState(state);
2480
+ }
2481
+ pollIntervalMs = response.pollIntervalMs;
2482
+ await sleep(pollIntervalMs);
2483
+ } catch (err) {
2484
+ if (!running)
2485
+ break;
2486
+ if (err instanceof AuthError) {
2487
+ console.error(err.message);
2488
+ process.exitCode = 1;
2489
+ break;
2490
+ }
2491
+ if (!offlineState.isOffline) {
2492
+ offlineState.isOffline = true;
2493
+ offlineState.offlineSince = (/* @__PURE__ */ new Date()).toISOString();
2494
+ offlineState.attemptCount = 0;
2495
+ notifyConnectionLost();
2496
+ }
2497
+ offlineState.attemptCount++;
2498
+ const delay = backoff.nextDelay();
2499
+ const message = err instanceof Error ? err.message : "Unknown error";
2500
+ console.error(`Poll failed (attempt ${offlineState.attemptCount}): ${message}. Retrying in ${Math.round(delay / 1e3)}s`);
2501
+ await sleep(delay);
2502
+ }
2503
+ }
2504
+ await removePidFile2();
2505
+ }
2506
+ var TWENTY_FOUR_HOURS_MS, PID_PATH2, running, sleepResolve, isDirectRun;
2507
+ var init_main = __esm({
2508
+ "../listener/dist/main.js"() {
2509
+ "use strict";
2510
+ init_config2();
2511
+ init_state();
2512
+ init_auth2();
2513
+ init_poll_client();
2514
+ init_reconnection();
2515
+ init_notification();
2516
+ init_context_fetcher();
2517
+ TWENTY_FOUR_HOURS_MS = 24 * 60 * 60 * 1e3;
2518
+ PID_PATH2 = join13(homedir9(), ".repowise", "listener.pid");
2519
+ running = false;
2520
+ sleepResolve = null;
2521
+ isDirectRun = process.argv[1]?.endsWith("main.js") || process.argv[1]?.endsWith("main.ts");
2522
+ if (isDirectRun) {
2523
+ startListener().catch((err) => {
2524
+ console.error("Listener fatal error:", err);
2525
+ process.exitCode = 1;
2526
+ });
2527
+ }
2528
+ }
2529
+ });
2530
+
2531
+ // src/commands/listen.ts
2532
+ var listen_exports = {};
2533
+ __export(listen_exports, {
2534
+ listen: () => listen
2535
+ });
2536
+ async function listen(options) {
2537
+ if (options.install) {
2538
+ try {
2539
+ const { install: install2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
2540
+ await install2();
2541
+ console.log("Auto-start service installed. The listener will start on boot.");
2542
+ } catch (err) {
2543
+ const message = err instanceof Error ? err.message : "Unknown error";
2544
+ console.error(`Failed to install auto-start service: ${message}`);
2545
+ console.error("You can still run the listener manually with `repowise listen`.");
2546
+ process.exitCode = 1;
2547
+ }
2548
+ return;
2549
+ }
2550
+ if (options.uninstall) {
2551
+ try {
2552
+ const { uninstall: uninstall2 } = await Promise.resolve().then(() => (init_service_installer(), service_installer_exports));
2553
+ await uninstall2();
2554
+ console.log("Auto-start service removed.");
2555
+ } catch (err) {
2556
+ const message = err instanceof Error ? err.message : "Unknown error";
2557
+ console.error(`Failed to uninstall auto-start service: ${message}`);
2558
+ process.exitCode = 1;
2559
+ }
2560
+ return;
2561
+ }
2562
+ const credentials = await getValidCredentials();
2563
+ if (!credentials) {
2564
+ console.error("Not logged in. Run `repowise login` first.");
2565
+ process.exitCode = 1;
2566
+ return;
2567
+ }
2568
+ console.log("Starting RepoWise listener...");
2569
+ try {
2570
+ const { startListener: startListener2 } = await Promise.resolve().then(() => (init_main(), main_exports));
2571
+ await startListener2();
2572
+ } catch {
2573
+ console.error("Failed to start listener. Ensure @repowise/listener is installed.");
2574
+ process.exitCode = 1;
2575
+ }
2576
+ }
2577
+ var init_listen = __esm({
2578
+ "src/commands/listen.ts"() {
2579
+ "use strict";
2580
+ init_auth();
2581
+ }
2582
+ });
2583
+
2584
+ // src/commands/start.ts
2585
+ var start_exports = {};
2586
+ __export(start_exports, {
2587
+ start: () => start
2588
+ });
2589
+ async function start() {
2590
+ try {
2591
+ const { isRunning: isRunning2, startBackground: startBackground2 } = await Promise.resolve().then(() => (init_process_manager(), process_manager_exports));
2592
+ if (await isRunning2()) {
2593
+ console.log("Listener is already running.");
2594
+ return;
2595
+ }
2596
+ const pid = await startBackground2();
2597
+ console.log(`Listener started (PID: ${pid}).`);
2598
+ } catch (err) {
2599
+ const message = err instanceof Error ? err.message : "Unknown error";
2600
+ console.error(`Failed to start listener: ${message}`);
2601
+ process.exitCode = 1;
2602
+ }
2603
+ }
2604
+ var init_start = __esm({
2605
+ "src/commands/start.ts"() {
2606
+ "use strict";
2607
+ }
2608
+ });
2609
+
2610
+ // src/commands/stop.ts
2611
+ var stop_exports = {};
2612
+ __export(stop_exports, {
2613
+ stop: () => stop2
2614
+ });
2615
+ async function stop2() {
2616
+ try {
2617
+ const { isRunning: isRunning2, stopProcess: stopProcess2 } = await Promise.resolve().then(() => (init_process_manager(), process_manager_exports));
2618
+ if (!await isRunning2()) {
2619
+ console.log("Listener is not running.");
2620
+ return;
2621
+ }
2622
+ await stopProcess2();
2623
+ console.log("Listener stopped.");
2624
+ } catch (err) {
2625
+ const message = err instanceof Error ? err.message : "Unknown error";
2626
+ console.error(`Failed to stop listener: ${message}`);
2627
+ process.exitCode = 1;
2628
+ }
2629
+ }
2630
+ var init_stop = __esm({
2631
+ "src/commands/stop.ts"() {
2632
+ "use strict";
2633
+ }
2634
+ });
2635
+
2636
+ // src/commands/config.ts
2637
+ var config_exports = {};
2638
+ __export(config_exports, {
2639
+ config: () => config
2640
+ });
2641
+ import chalk8 from "chalk";
2642
+ import ora3 from "ora";
2643
+ import { select as select2 } from "@inquirer/prompts";
2644
+ async function config() {
2645
+ const spinner = ora3("Checking authentication...").start();
2646
+ try {
2647
+ let credentials = await getValidCredentials();
2648
+ if (!credentials) {
2649
+ spinner.info(chalk8.yellow("Not logged in. Opening browser to authenticate..."));
2650
+ credentials = await performLogin();
2651
+ const { email } = decodeIdToken(credentials.idToken);
2652
+ spinner.succeed(chalk8.green(`Authenticated as ${chalk8.bold(email)}`));
2653
+ } else {
2654
+ spinner.succeed("Authenticated");
2655
+ }
2656
+ spinner.start("Loading repositories...");
2657
+ const repos = await apiRequest("/v1/repos");
2658
+ spinner.stop();
2659
+ if (repos.length === 0) {
2660
+ console.log(chalk8.yellow("No repositories connected. Run `repowise create` first."));
2661
+ return;
2662
+ }
2663
+ const repoId = await select2({
2664
+ message: "Select a repository to configure",
2665
+ choices: repos.map((r) => ({
2666
+ name: `${r.fullName} (storage: ${r.contextStorage ?? "git"})`,
2667
+ value: r.repoId
2668
+ }))
2669
+ });
2670
+ const repo = repos.find((r) => r.repoId === repoId);
2671
+ const currentStorage = repo.contextStorage ?? "git";
2672
+ const currentDelivery = repo.deliveryMode ?? "direct";
2673
+ const currentBranch = repo.monitoredBranch ?? repo.defaultBranch;
2674
+ console.log("");
2675
+ console.log(chalk8.bold(`Settings for ${repo.fullName}`));
2676
+ console.log(chalk8.dim(` Context storage: ${currentStorage}`));
2677
+ console.log(chalk8.dim(` Delivery mode: ${currentDelivery}`));
2678
+ console.log(chalk8.dim(` Monitored branch: ${currentBranch}`));
2679
+ console.log("");
2680
+ const setting = await select2({
2681
+ message: "What would you like to change?",
2682
+ choices: [
2683
+ { name: `Context storage (current: ${currentStorage})`, value: "contextStorage" },
2684
+ { name: `Delivery mode (current: ${currentDelivery})`, value: "deliveryMode" },
2685
+ { name: `Monitored branch (current: ${currentBranch})`, value: "monitoredBranch" }
2686
+ ]
2687
+ });
2688
+ const patch = {};
2689
+ if (setting === "contextStorage") {
2690
+ const newStorage = await select2({
2691
+ message: "Context storage",
2692
+ choices: [
2693
+ { name: "Push to your git repo", value: "git" },
2694
+ { name: "Store encrypted on RepoWise servers", value: "server" }
2695
+ ],
2696
+ default: currentStorage
2697
+ });
2698
+ if (newStorage === currentStorage) {
2699
+ console.log(chalk8.dim("No change."));
2700
+ return;
2701
+ }
2702
+ if (newStorage === "server") {
2703
+ console.log(
2704
+ chalk8.yellow(
2705
+ "\n Warning: Server storage opts out of zero-retention. Files are encrypted (AES-256/KMS)"
2706
+ )
2707
+ );
2708
+ console.log(chalk8.yellow(" and deletable anytime from your dashboard or CLI.\n"));
2709
+ }
2710
+ if (newStorage === "git" && currentStorage === "server") {
2711
+ console.log(chalk8.cyan("\n Note: Existing server-stored files will be deleted."));
2712
+ console.log(chalk8.cyan(" A new sync will push context files to your git repo.\n"));
2713
+ }
2714
+ patch.contextStorage = newStorage;
2715
+ } else if (setting === "deliveryMode") {
2716
+ const newMode = await select2({
2717
+ message: "Delivery mode",
2718
+ choices: [
2719
+ { name: "Direct push to branch", value: "direct" },
2720
+ { name: "Create pull request", value: "pr" }
2721
+ ],
2722
+ default: currentDelivery
2723
+ });
2724
+ if (newMode === currentDelivery) {
2725
+ console.log(chalk8.dim("No change."));
2726
+ return;
2727
+ }
2728
+ patch.deliveryMode = newMode;
2729
+ } else if (setting === "monitoredBranch") {
2730
+ const { input: input2 } = await import("@inquirer/prompts");
2731
+ const newBranch = await input2({
2732
+ message: "Monitored branch",
2733
+ default: currentBranch
2734
+ });
2735
+ if (newBranch === currentBranch) {
2736
+ console.log(chalk8.dim("No change."));
2737
+ return;
2738
+ }
2739
+ patch.monitoredBranch = newBranch;
2740
+ }
2741
+ spinner.start("Saving...");
2742
+ await apiRequest(`/v1/repos/${repoId}`, {
2743
+ method: "PATCH",
2744
+ body: JSON.stringify(patch)
2745
+ });
2746
+ spinner.succeed(chalk8.green("Setting updated"));
2747
+ } catch (err) {
2748
+ spinner.stop();
2749
+ const message = err instanceof Error ? err.message : "Config failed";
2750
+ console.error(chalk8.red(message));
2751
+ process.exitCode = 1;
2752
+ }
2753
+ }
2754
+ var init_config3 = __esm({
2755
+ "src/commands/config.ts"() {
2756
+ "use strict";
2757
+ init_auth();
2758
+ init_api();
2759
+ }
2760
+ });
2761
+
2762
+ // bin/repowise.ts
2763
+ init_env();
2764
+ import { readFileSync } from "fs";
2765
+ import { fileURLToPath as fileURLToPath3 } from "url";
2766
+ import { dirname as dirname2, join as join14 } from "path";
2767
+ import { Command } from "commander";
2768
+
2769
+ // src/lib/welcome.ts
2770
+ init_config();
2771
+ import chalk from "chalk";
2772
+ var W = 41;
2773
+ function row(styled, visible) {
2774
+ return chalk.cyan(" \u2502") + styled + " ".repeat(W - visible) + chalk.cyan("\u2502");
2775
+ }
2776
+ async function showWelcome(currentVersion) {
2777
+ try {
2778
+ const config2 = await getConfig();
2779
+ if (config2.lastSeenVersion === currentVersion) return;
2780
+ const isUpgrade = !!config2.lastSeenVersion;
2781
+ const tag = isUpgrade ? "updated" : "installed";
2782
+ const titleText = `RepoWise v${currentVersion}`;
2783
+ const titleStyled = " " + chalk.bold(titleText) + chalk.green(` \u2713 ${tag}`);
2784
+ const titleVisible = 3 + titleText.length + 3 + tag.length;
2785
+ const border = "\u2500".repeat(W);
2786
+ console.log("");
2787
+ console.log(chalk.cyan(` \u256D${border}\u256E`));
2788
+ console.log(row("", 0));
2789
+ console.log(row(titleStyled, titleVisible));
2790
+ console.log(row("", 0));
2791
+ console.log(row(" " + chalk.dim("Get started:"), 15));
2792
+ console.log(row(" $ " + chalk.bold("repowise create"), 22));
2793
+ console.log(row("", 0));
2794
+ console.log(row(" " + chalk.dim("Thank you for using RepoWise!"), 32));
2795
+ console.log(row(" " + chalk.dim("https://repowise.ai"), 22));
2796
+ console.log(row("", 0));
2797
+ console.log(chalk.cyan(` \u2570${border}\u256F`));
2798
+ console.log("");
2799
+ await saveConfig({ ...config2, lastSeenVersion: currentVersion });
2800
+ } catch {
2801
+ }
2802
+ }
2803
+
2804
+ // bin/repowise.ts
2805
+ var __filename = fileURLToPath3(import.meta.url);
2806
+ var __dirname = dirname2(__filename);
2807
+ var pkg = JSON.parse(readFileSync(join14(__dirname, "..", "..", "package.json"), "utf-8"));
2808
+ var program = new Command();
2809
+ program.name("repowise").description("AI-optimized codebase context generator").version(pkg.version).option("--staging", "Use the staging environment").hook("preAction", async () => {
2810
+ if (program.opts()["staging"]) {
2811
+ setStagingMode(true);
2812
+ }
2813
+ await showWelcome(pkg.version);
2814
+ });
2815
+ program.command("create").description("Create context for a repository").option("--context-storage <mode>", "Context storage mode: git or server (skips prompt)").action(async (options) => {
2816
+ const { create: create2 } = await Promise.resolve().then(() => (init_create(), create_exports));
2817
+ const validModes = ["git", "server"];
2818
+ const mode = options.contextStorage;
2819
+ await create2({
2820
+ contextStorage: mode && validModes.includes(mode) ? mode : void 0
2821
+ });
2822
+ });
2823
+ program.command("login").description("Authenticate with RepoWise").option("--no-browser", "Print login URL instead of opening browser").action(async (options) => {
2824
+ const { login: login2 } = await Promise.resolve().then(() => (init_login(), login_exports));
2825
+ await login2(options);
2826
+ });
2827
+ program.command("logout").description("Sign out of RepoWise").action(async () => {
2828
+ const { logout: logout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
2829
+ await logout2();
2830
+ });
2831
+ program.command("status").description("Show current status").action(async () => {
2832
+ const { status: status2 } = await Promise.resolve().then(() => (init_status(), status_exports));
2833
+ await status2();
2834
+ });
2835
+ program.command("sync").description("Trigger a manual sync").action(async () => {
2836
+ const { sync: sync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
2837
+ sync2();
2838
+ });
2839
+ program.command("listen").description("Start the context listener").option("--install", "Install auto-start service").option("--uninstall", "Remove auto-start service").action(async (options) => {
2840
+ const { listen: listen2 } = await Promise.resolve().then(() => (init_listen(), listen_exports));
2841
+ await listen2(options);
2842
+ });
2843
+ program.command("start").description("Start the listener as a background process").action(async () => {
2844
+ const { start: start2 } = await Promise.resolve().then(() => (init_start(), start_exports));
2845
+ await start2();
2846
+ });
2847
+ program.command("stop").description("Stop the running listener process").action(async () => {
2848
+ const { stop: stop3 } = await Promise.resolve().then(() => (init_stop(), stop_exports));
2849
+ await stop3();
2850
+ });
2851
+ program.command("config").description("Manage configuration").action(async () => {
2852
+ const { config: config2 } = await Promise.resolve().then(() => (init_config3(), config_exports));
2853
+ await config2();
2854
+ });
2855
+ if (process.argv[2] === "__listener") {
2856
+ const { startListener: startListener2 } = await Promise.resolve().then(() => (init_main(), main_exports));
2857
+ await startListener2();
2858
+ } else {
2859
+ program.parse();
2860
+ }