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