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