repowise 0.1.41 → 0.1.43

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