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