@jvittechs/jai1-cli 0.1.96 → 0.1.98
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/cli.js +658 -119
- package/dist/cli.js.map +1 -1
- package/dist/web-chat/README.md +418 -0
- package/dist/web-chat/app.js +1527 -0
- package/dist/web-chat/index.html +324 -0
- package/dist/web-chat/style.css +1882 -0
- package/package.json +4 -2
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ var NetworkError = class extends Jai1Error {
|
|
|
33
33
|
// package.json
|
|
34
34
|
var package_default = {
|
|
35
35
|
name: "@jvittechs/jai1-cli",
|
|
36
|
-
version: "0.1.
|
|
36
|
+
version: "0.1.98",
|
|
37
37
|
description: "A unified CLI tool for JV-IT TECHS developers to manage Jai1 Framework. Please contact TeamAI for usage instructions.",
|
|
38
38
|
type: "module",
|
|
39
39
|
bin: {
|
|
@@ -70,7 +70,8 @@ var package_default = {
|
|
|
70
70
|
"coding"
|
|
71
71
|
],
|
|
72
72
|
scripts: {
|
|
73
|
-
build: "tsup src/cli.ts --dts --format esm --target node22 --out-dir dist --sourcemap",
|
|
73
|
+
build: "tsup src/cli.ts --dts --format esm --target node22 --out-dir dist --sourcemap && npm run copy-web-chat",
|
|
74
|
+
"copy-web-chat": "mkdir -p dist/web-chat && cp -r src/web-chat/* dist/web-chat/",
|
|
74
75
|
dev: "tsx src/cli.ts --help",
|
|
75
76
|
lint: "eslint .",
|
|
76
77
|
test: "vitest run",
|
|
@@ -92,6 +93,7 @@ var package_default = {
|
|
|
92
93
|
"ink-text-input": "^6.0.0",
|
|
93
94
|
marked: "^12.0.0",
|
|
94
95
|
"marked-terminal": "^7.0.0",
|
|
96
|
+
open: "^10.1.0",
|
|
95
97
|
"p-limit": "^5.0.0",
|
|
96
98
|
"p-queue": "^7.4.1",
|
|
97
99
|
"p-retry": "^6.2.0",
|
|
@@ -798,6 +800,9 @@ var IDE_MIGRATION_CONFIGS = {
|
|
|
798
800
|
workflowsPath: null,
|
|
799
801
|
commandsPath: "commands",
|
|
800
802
|
fileExtension: ".mdc",
|
|
803
|
+
// For rules
|
|
804
|
+
workflowsExtension: ".md",
|
|
805
|
+
// For workflows/commands
|
|
801
806
|
generateFrontmatter: (opts) => {
|
|
802
807
|
const lines = ["---"];
|
|
803
808
|
if (opts.description) {
|
|
@@ -1112,21 +1117,24 @@ ${reference}
|
|
|
1112
1117
|
*/
|
|
1113
1118
|
getTargetPath(config, item) {
|
|
1114
1119
|
let contentPath = null;
|
|
1120
|
+
let extension = config.fileExtension;
|
|
1115
1121
|
switch (item.type) {
|
|
1116
1122
|
case "rules":
|
|
1117
1123
|
contentPath = config.rulesPath;
|
|
1118
1124
|
break;
|
|
1119
1125
|
case "workflows":
|
|
1120
1126
|
contentPath = config.workflowsPath ?? config.commandsPath;
|
|
1127
|
+
extension = config.workflowsExtension ?? config.fileExtension;
|
|
1121
1128
|
break;
|
|
1122
1129
|
case "commands":
|
|
1123
1130
|
contentPath = config.commandsPath;
|
|
1131
|
+
extension = config.workflowsExtension ?? config.fileExtension;
|
|
1124
1132
|
break;
|
|
1125
1133
|
}
|
|
1126
1134
|
if (!contentPath) {
|
|
1127
1135
|
return null;
|
|
1128
1136
|
}
|
|
1129
|
-
const filename = `${item.name}${
|
|
1137
|
+
const filename = `${item.name}${extension}`;
|
|
1130
1138
|
return path.join(this.projectPath, config.basePath, contentPath, filename);
|
|
1131
1139
|
}
|
|
1132
1140
|
// Helper methods
|
|
@@ -3381,9 +3389,9 @@ var IdeDetectionService = class {
|
|
|
3381
3389
|
/**
|
|
3382
3390
|
* Check if a path exists
|
|
3383
3391
|
*/
|
|
3384
|
-
async pathExists(
|
|
3392
|
+
async pathExists(path9) {
|
|
3385
3393
|
try {
|
|
3386
|
-
await fs7.access(
|
|
3394
|
+
await fs7.access(path9);
|
|
3387
3395
|
return true;
|
|
3388
3396
|
} catch {
|
|
3389
3397
|
return false;
|
|
@@ -4578,15 +4586,19 @@ var LlmProxyService = class {
|
|
|
4578
4586
|
}
|
|
4579
4587
|
const reader = response.body.getReader();
|
|
4580
4588
|
const decoder = new TextDecoder();
|
|
4589
|
+
let buffer = "";
|
|
4581
4590
|
try {
|
|
4582
4591
|
while (true) {
|
|
4583
4592
|
const { done, value } = await reader.read();
|
|
4584
4593
|
if (done) break;
|
|
4585
|
-
|
|
4586
|
-
const lines =
|
|
4594
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4595
|
+
const lines = buffer.split("\n");
|
|
4596
|
+
buffer = lines.pop() || "";
|
|
4587
4597
|
for (const line of lines) {
|
|
4588
|
-
|
|
4589
|
-
|
|
4598
|
+
const trimmedLine = line.trim();
|
|
4599
|
+
if (!trimmedLine) continue;
|
|
4600
|
+
if (trimmedLine.startsWith("data: ")) {
|
|
4601
|
+
const data = trimmedLine.slice(6);
|
|
4590
4602
|
if (data === "[DONE]") {
|
|
4591
4603
|
return;
|
|
4592
4604
|
}
|
|
@@ -4601,6 +4613,22 @@ var LlmProxyService = class {
|
|
|
4601
4613
|
}
|
|
4602
4614
|
}
|
|
4603
4615
|
}
|
|
4616
|
+
if (buffer.trim()) {
|
|
4617
|
+
const trimmedLine = buffer.trim();
|
|
4618
|
+
if (trimmedLine.startsWith("data: ")) {
|
|
4619
|
+
const data = trimmedLine.slice(6);
|
|
4620
|
+
if (data !== "[DONE]") {
|
|
4621
|
+
try {
|
|
4622
|
+
const parsed = JSON.parse(data);
|
|
4623
|
+
const content = parsed.choices[0]?.delta?.content;
|
|
4624
|
+
if (content) {
|
|
4625
|
+
yield content;
|
|
4626
|
+
}
|
|
4627
|
+
} catch (e) {
|
|
4628
|
+
}
|
|
4629
|
+
}
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4604
4632
|
} finally {
|
|
4605
4633
|
reader.releaseLock();
|
|
4606
4634
|
}
|
|
@@ -5083,8 +5111,469 @@ var ChatApp = ({ service, initialModel }) => {
|
|
|
5083
5111
|
), /* @__PURE__ */ React25.createElement(Box16, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React25.createElement(Text17, { dimColor: true }, footer)));
|
|
5084
5112
|
};
|
|
5085
5113
|
|
|
5114
|
+
// src/server/web-chat-server.ts
|
|
5115
|
+
import http from "http";
|
|
5116
|
+
import fs8 from "fs";
|
|
5117
|
+
import path4 from "path";
|
|
5118
|
+
import { fileURLToPath } from "url";
|
|
5119
|
+
|
|
5120
|
+
// src/server/session.ts
|
|
5121
|
+
import crypto from "crypto";
|
|
5122
|
+
var SESSION_PREFIX = "wc_";
|
|
5123
|
+
var DEFAULT_TTL_MINUTES = 30;
|
|
5124
|
+
var SessionManager = class {
|
|
5125
|
+
sessions = /* @__PURE__ */ new Map();
|
|
5126
|
+
cleanupInterval = null;
|
|
5127
|
+
constructor() {
|
|
5128
|
+
this.startCleanup();
|
|
5129
|
+
}
|
|
5130
|
+
/**
|
|
5131
|
+
* Generate a new session token
|
|
5132
|
+
*/
|
|
5133
|
+
generateToken() {
|
|
5134
|
+
const randomBytes = crypto.randomBytes(16).toString("hex");
|
|
5135
|
+
return `${SESSION_PREFIX}${randomBytes}`;
|
|
5136
|
+
}
|
|
5137
|
+
/**
|
|
5138
|
+
* Create a new session
|
|
5139
|
+
*/
|
|
5140
|
+
createSession(config) {
|
|
5141
|
+
const token = this.generateToken();
|
|
5142
|
+
const now = /* @__PURE__ */ new Date();
|
|
5143
|
+
const ttlMinutes = config.ttlMinutes ?? DEFAULT_TTL_MINUTES;
|
|
5144
|
+
const session = {
|
|
5145
|
+
token,
|
|
5146
|
+
accessKey: config.accessKey,
|
|
5147
|
+
apiUrl: config.apiUrl,
|
|
5148
|
+
createdAt: now,
|
|
5149
|
+
expiresAt: new Date(now.getTime() + ttlMinutes * 60 * 1e3),
|
|
5150
|
+
lastActivity: now
|
|
5151
|
+
};
|
|
5152
|
+
this.sessions.set(token, session);
|
|
5153
|
+
return session;
|
|
5154
|
+
}
|
|
5155
|
+
/**
|
|
5156
|
+
* Validate a session token and return session if valid
|
|
5157
|
+
* Also extends the session TTL on successful validation
|
|
5158
|
+
*/
|
|
5159
|
+
validateSession(token) {
|
|
5160
|
+
if (!token || !token.startsWith(SESSION_PREFIX)) {
|
|
5161
|
+
return null;
|
|
5162
|
+
}
|
|
5163
|
+
const session = this.sessions.get(token);
|
|
5164
|
+
if (!session) {
|
|
5165
|
+
return null;
|
|
5166
|
+
}
|
|
5167
|
+
if (/* @__PURE__ */ new Date() > session.expiresAt) {
|
|
5168
|
+
this.sessions.delete(token);
|
|
5169
|
+
return null;
|
|
5170
|
+
}
|
|
5171
|
+
session.lastActivity = /* @__PURE__ */ new Date();
|
|
5172
|
+
session.expiresAt = new Date(
|
|
5173
|
+
session.lastActivity.getTime() + DEFAULT_TTL_MINUTES * 60 * 1e3
|
|
5174
|
+
);
|
|
5175
|
+
return session;
|
|
5176
|
+
}
|
|
5177
|
+
/**
|
|
5178
|
+
* Get session without extending TTL
|
|
5179
|
+
*/
|
|
5180
|
+
getSession(token) {
|
|
5181
|
+
if (!token || !token.startsWith(SESSION_PREFIX)) {
|
|
5182
|
+
return null;
|
|
5183
|
+
}
|
|
5184
|
+
const session = this.sessions.get(token);
|
|
5185
|
+
if (!session) {
|
|
5186
|
+
return null;
|
|
5187
|
+
}
|
|
5188
|
+
if (/* @__PURE__ */ new Date() > session.expiresAt) {
|
|
5189
|
+
this.sessions.delete(token);
|
|
5190
|
+
return null;
|
|
5191
|
+
}
|
|
5192
|
+
return session;
|
|
5193
|
+
}
|
|
5194
|
+
/**
|
|
5195
|
+
* Invalidate/delete a session
|
|
5196
|
+
*/
|
|
5197
|
+
invalidateSession(token) {
|
|
5198
|
+
this.sessions.delete(token);
|
|
5199
|
+
}
|
|
5200
|
+
/**
|
|
5201
|
+
* Get remaining TTL in seconds
|
|
5202
|
+
*/
|
|
5203
|
+
getTimeToLive(token) {
|
|
5204
|
+
const session = this.getSession(token);
|
|
5205
|
+
if (!session) {
|
|
5206
|
+
return 0;
|
|
5207
|
+
}
|
|
5208
|
+
const remaining = session.expiresAt.getTime() - Date.now();
|
|
5209
|
+
return Math.max(0, Math.floor(remaining / 1e3));
|
|
5210
|
+
}
|
|
5211
|
+
/**
|
|
5212
|
+
* Start periodic cleanup of expired sessions
|
|
5213
|
+
*/
|
|
5214
|
+
startCleanup() {
|
|
5215
|
+
this.cleanupInterval = setInterval(() => {
|
|
5216
|
+
const now = /* @__PURE__ */ new Date();
|
|
5217
|
+
for (const [token, session] of this.sessions.entries()) {
|
|
5218
|
+
if (now > session.expiresAt) {
|
|
5219
|
+
this.sessions.delete(token);
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
}, 5 * 60 * 1e3);
|
|
5223
|
+
this.cleanupInterval.unref();
|
|
5224
|
+
}
|
|
5225
|
+
/**
|
|
5226
|
+
* Stop cleanup interval (call when shutting down)
|
|
5227
|
+
*/
|
|
5228
|
+
stopCleanup() {
|
|
5229
|
+
if (this.cleanupInterval) {
|
|
5230
|
+
clearInterval(this.cleanupInterval);
|
|
5231
|
+
this.cleanupInterval = null;
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
/**
|
|
5235
|
+
* Clear all sessions (for shutdown)
|
|
5236
|
+
*/
|
|
5237
|
+
clearAll() {
|
|
5238
|
+
this.sessions.clear();
|
|
5239
|
+
this.stopCleanup();
|
|
5240
|
+
}
|
|
5241
|
+
/**
|
|
5242
|
+
* Get active session count (for debugging)
|
|
5243
|
+
*/
|
|
5244
|
+
getActiveSessionCount() {
|
|
5245
|
+
return this.sessions.size;
|
|
5246
|
+
}
|
|
5247
|
+
};
|
|
5248
|
+
|
|
5249
|
+
// src/server/web-chat-server.ts
|
|
5250
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
5251
|
+
var __dirname = path4.dirname(__filename);
|
|
5252
|
+
var MIME_TYPES = {
|
|
5253
|
+
".html": "text/html",
|
|
5254
|
+
".css": "text/css",
|
|
5255
|
+
".js": "application/javascript",
|
|
5256
|
+
".json": "application/json",
|
|
5257
|
+
".png": "image/png",
|
|
5258
|
+
".svg": "image/svg+xml",
|
|
5259
|
+
".ico": "image/x-icon"
|
|
5260
|
+
};
|
|
5261
|
+
async function findAvailablePort(startPort, endPort) {
|
|
5262
|
+
for (let port = startPort; port <= endPort; port++) {
|
|
5263
|
+
try {
|
|
5264
|
+
await new Promise((resolve4, reject) => {
|
|
5265
|
+
const server = http.createServer();
|
|
5266
|
+
server.listen(port, "127.0.0.1");
|
|
5267
|
+
server.once("listening", () => {
|
|
5268
|
+
server.close(() => resolve4());
|
|
5269
|
+
});
|
|
5270
|
+
server.once("error", reject);
|
|
5271
|
+
});
|
|
5272
|
+
return port;
|
|
5273
|
+
} catch {
|
|
5274
|
+
continue;
|
|
5275
|
+
}
|
|
5276
|
+
}
|
|
5277
|
+
throw new Error(`No available port found in range ${startPort}-${endPort}`);
|
|
5278
|
+
}
|
|
5279
|
+
async function parseJsonBody(req) {
|
|
5280
|
+
return new Promise((resolve4, reject) => {
|
|
5281
|
+
let body = "";
|
|
5282
|
+
req.on("data", (chunk) => {
|
|
5283
|
+
body += chunk.toString();
|
|
5284
|
+
});
|
|
5285
|
+
req.on("end", () => {
|
|
5286
|
+
try {
|
|
5287
|
+
resolve4(body ? JSON.parse(body) : {});
|
|
5288
|
+
} catch (e) {
|
|
5289
|
+
reject(new Error("Invalid JSON body"));
|
|
5290
|
+
}
|
|
5291
|
+
});
|
|
5292
|
+
req.on("error", reject);
|
|
5293
|
+
});
|
|
5294
|
+
}
|
|
5295
|
+
function sendJson(res, statusCode, data) {
|
|
5296
|
+
res.writeHead(statusCode, {
|
|
5297
|
+
"Content-Type": "application/json",
|
|
5298
|
+
"Access-Control-Allow-Origin": "*",
|
|
5299
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
5300
|
+
"Access-Control-Allow-Headers": "Content-Type, X-Session-Token"
|
|
5301
|
+
});
|
|
5302
|
+
res.end(JSON.stringify(data));
|
|
5303
|
+
}
|
|
5304
|
+
function sendError(res, statusCode, code, message) {
|
|
5305
|
+
sendJson(res, statusCode, {
|
|
5306
|
+
success: false,
|
|
5307
|
+
error: { code, message }
|
|
5308
|
+
});
|
|
5309
|
+
}
|
|
5310
|
+
function getSessionToken(req) {
|
|
5311
|
+
const headerToken = req.headers["x-session-token"];
|
|
5312
|
+
if (headerToken && typeof headerToken === "string") {
|
|
5313
|
+
return headerToken;
|
|
5314
|
+
}
|
|
5315
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
5316
|
+
return url.searchParams.get("session");
|
|
5317
|
+
}
|
|
5318
|
+
function createWebChatServer(options) {
|
|
5319
|
+
const { config } = options;
|
|
5320
|
+
const host = options.host ?? "127.0.0.1";
|
|
5321
|
+
let port = options.port ?? 0;
|
|
5322
|
+
const sessionManager = new SessionManager();
|
|
5323
|
+
let server = null;
|
|
5324
|
+
let session = null;
|
|
5325
|
+
const staticDir = path4.join(__dirname, "web-chat");
|
|
5326
|
+
async function serveStaticFile(res, filePath) {
|
|
5327
|
+
const fullPath = path4.join(staticDir, filePath);
|
|
5328
|
+
if (!fullPath.startsWith(staticDir)) {
|
|
5329
|
+
sendError(res, 403, "ERR-WC-006", "Forbidden");
|
|
5330
|
+
return;
|
|
5331
|
+
}
|
|
5332
|
+
try {
|
|
5333
|
+
const stat = await fs8.promises.stat(fullPath);
|
|
5334
|
+
if (!stat.isFile()) {
|
|
5335
|
+
sendError(res, 404, "ERR-WC-007", "Not found");
|
|
5336
|
+
return;
|
|
5337
|
+
}
|
|
5338
|
+
const ext = path4.extname(fullPath);
|
|
5339
|
+
const contentType = MIME_TYPES[ext] || "application/octet-stream";
|
|
5340
|
+
const content = await fs8.promises.readFile(fullPath);
|
|
5341
|
+
res.writeHead(200, { "Content-Type": contentType });
|
|
5342
|
+
res.end(content);
|
|
5343
|
+
} catch (error) {
|
|
5344
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
5345
|
+
sendError(res, 404, "ERR-WC-007", "Not found");
|
|
5346
|
+
} else {
|
|
5347
|
+
sendError(res, 500, "ERR-WC-008", "Internal server error");
|
|
5348
|
+
}
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
async function handleApi(req, res, pathname) {
|
|
5352
|
+
if (req.method === "OPTIONS") {
|
|
5353
|
+
res.writeHead(204, {
|
|
5354
|
+
"Access-Control-Allow-Origin": "*",
|
|
5355
|
+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
5356
|
+
"Access-Control-Allow-Headers": "Content-Type, X-Session-Token"
|
|
5357
|
+
});
|
|
5358
|
+
res.end();
|
|
5359
|
+
return;
|
|
5360
|
+
}
|
|
5361
|
+
if (pathname === "/health") {
|
|
5362
|
+
sendJson(res, 200, { status: "ok" });
|
|
5363
|
+
return;
|
|
5364
|
+
}
|
|
5365
|
+
const token = getSessionToken(req);
|
|
5366
|
+
const validSession = token ? sessionManager.validateSession(token) : null;
|
|
5367
|
+
if (!validSession) {
|
|
5368
|
+
sendError(
|
|
5369
|
+
res,
|
|
5370
|
+
401,
|
|
5371
|
+
"ERR-WC-001",
|
|
5372
|
+
"Session expired. Please run 'jai1 chat --web' again."
|
|
5373
|
+
);
|
|
5374
|
+
return;
|
|
5375
|
+
}
|
|
5376
|
+
const llmService = new LlmProxyService({
|
|
5377
|
+
accessKey: validSession.accessKey,
|
|
5378
|
+
apiUrl: validSession.apiUrl
|
|
5379
|
+
});
|
|
5380
|
+
if (pathname === "/api/session" && req.method === "GET") {
|
|
5381
|
+
try {
|
|
5382
|
+
const models = await llmService.getModelsWithUsage();
|
|
5383
|
+
const allowedModels = models.filter((m) => m.allowed).map((m) => m.id);
|
|
5384
|
+
sendJson(res, 200, {
|
|
5385
|
+
success: true,
|
|
5386
|
+
data: {
|
|
5387
|
+
expiresAt: validSession.expiresAt.toISOString(),
|
|
5388
|
+
models: allowedModels
|
|
5389
|
+
}
|
|
5390
|
+
});
|
|
5391
|
+
} catch (error) {
|
|
5392
|
+
sendError(res, 502, "ERR-WC-004", "Failed to connect to LLM service.");
|
|
5393
|
+
}
|
|
5394
|
+
return;
|
|
5395
|
+
}
|
|
5396
|
+
if (pathname === "/api/models" && req.method === "GET") {
|
|
5397
|
+
try {
|
|
5398
|
+
const models = await llmService.getModelsWithUsage();
|
|
5399
|
+
sendJson(res, 200, {
|
|
5400
|
+
success: true,
|
|
5401
|
+
data: models.filter((m) => m.allowed)
|
|
5402
|
+
});
|
|
5403
|
+
} catch (error) {
|
|
5404
|
+
sendError(res, 502, "ERR-WC-004", "Failed to connect to LLM service.");
|
|
5405
|
+
}
|
|
5406
|
+
return;
|
|
5407
|
+
}
|
|
5408
|
+
if (pathname === "/api/stats" && req.method === "GET") {
|
|
5409
|
+
try {
|
|
5410
|
+
const [limits, usage] = await Promise.all([
|
|
5411
|
+
llmService.getLimits(),
|
|
5412
|
+
llmService.getUsage(1)
|
|
5413
|
+
// Today only
|
|
5414
|
+
]);
|
|
5415
|
+
const today = (/* @__PURE__ */ new Date()).toLocaleDateString("en-CA", { timeZone: "Asia/Ho_Chi_Minh" });
|
|
5416
|
+
const modelStats = {};
|
|
5417
|
+
const allowedModels = limits.effectiveAllowedModels || [];
|
|
5418
|
+
const rateLimits = limits.effectiveRateLimits || {};
|
|
5419
|
+
allowedModels.forEach((modelId) => {
|
|
5420
|
+
const limit = rateLimits[modelId] || rateLimits[modelId.toLowerCase()] || 0;
|
|
5421
|
+
const usageRecord = (usage.data || []).find(
|
|
5422
|
+
(u) => (u.model === modelId || u.model.toLowerCase() === modelId.toLowerCase()) && u.date === today
|
|
5423
|
+
);
|
|
5424
|
+
const used = usageRecord?.count || 0;
|
|
5425
|
+
modelStats[modelId] = {
|
|
5426
|
+
limit,
|
|
5427
|
+
used,
|
|
5428
|
+
remaining: Math.max(0, limit - used)
|
|
5429
|
+
};
|
|
5430
|
+
});
|
|
5431
|
+
sendJson(res, 200, {
|
|
5432
|
+
success: true,
|
|
5433
|
+
data: {
|
|
5434
|
+
date: today,
|
|
5435
|
+
models: modelStats
|
|
5436
|
+
}
|
|
5437
|
+
});
|
|
5438
|
+
} catch (error) {
|
|
5439
|
+
sendError(res, 502, "ERR-WC-004", "Failed to connect to LLM service.");
|
|
5440
|
+
}
|
|
5441
|
+
return;
|
|
5442
|
+
}
|
|
5443
|
+
if (pathname === "/api/chat" && req.method === "POST") {
|
|
5444
|
+
try {
|
|
5445
|
+
const body = await parseJsonBody(req);
|
|
5446
|
+
if (!body.message || !body.model) {
|
|
5447
|
+
sendError(res, 400, "ERR-WC-009", "Missing message or model");
|
|
5448
|
+
return;
|
|
5449
|
+
}
|
|
5450
|
+
const messages = [];
|
|
5451
|
+
if (body.systemPrompt) {
|
|
5452
|
+
messages.push({ role: "system", content: body.systemPrompt });
|
|
5453
|
+
}
|
|
5454
|
+
messages.push(
|
|
5455
|
+
...(body.history || []).map((m) => ({
|
|
5456
|
+
role: m.role,
|
|
5457
|
+
content: m.content
|
|
5458
|
+
})),
|
|
5459
|
+
{ role: "user", content: body.message }
|
|
5460
|
+
);
|
|
5461
|
+
res.writeHead(200, {
|
|
5462
|
+
"Content-Type": "text/event-stream",
|
|
5463
|
+
"Cache-Control": "no-cache",
|
|
5464
|
+
Connection: "keep-alive",
|
|
5465
|
+
"Access-Control-Allow-Origin": "*"
|
|
5466
|
+
});
|
|
5467
|
+
try {
|
|
5468
|
+
for await (const chunk of llmService.chatStream(
|
|
5469
|
+
messages,
|
|
5470
|
+
body.model
|
|
5471
|
+
)) {
|
|
5472
|
+
res.write(
|
|
5473
|
+
`data: ${JSON.stringify({ type: "chunk", content: chunk })}
|
|
5474
|
+
|
|
5475
|
+
`
|
|
5476
|
+
);
|
|
5477
|
+
}
|
|
5478
|
+
res.write(`data: ${JSON.stringify({ type: "done" })}
|
|
5479
|
+
|
|
5480
|
+
`);
|
|
5481
|
+
} catch (streamError) {
|
|
5482
|
+
const errorMessage = streamError instanceof Error ? streamError.message : "Unknown error";
|
|
5483
|
+
if (errorMessage.includes("not allowed")) {
|
|
5484
|
+
res.write(
|
|
5485
|
+
`data: ${JSON.stringify({ type: "error", code: "ERR-WC-002", message: "You don't have access to this model." })}
|
|
5486
|
+
|
|
5487
|
+
`
|
|
5488
|
+
);
|
|
5489
|
+
} else if (errorMessage.includes("rate limit")) {
|
|
5490
|
+
res.write(
|
|
5491
|
+
`data: ${JSON.stringify({ type: "error", code: "ERR-WC-003", message: "Rate limit exceeded. Please wait." })}
|
|
5492
|
+
|
|
5493
|
+
`
|
|
5494
|
+
);
|
|
5495
|
+
} else {
|
|
5496
|
+
res.write(
|
|
5497
|
+
`data: ${JSON.stringify({ type: "error", code: "ERR-WC-004", message: errorMessage })}
|
|
5498
|
+
|
|
5499
|
+
`
|
|
5500
|
+
);
|
|
5501
|
+
}
|
|
5502
|
+
}
|
|
5503
|
+
res.end();
|
|
5504
|
+
} catch (error) {
|
|
5505
|
+
sendError(res, 500, "ERR-WC-008", "Internal server error");
|
|
5506
|
+
}
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5509
|
+
sendError(res, 404, "ERR-WC-010", "API endpoint not found");
|
|
5510
|
+
}
|
|
5511
|
+
async function handleRequest(req, res) {
|
|
5512
|
+
const url = new URL(req.url || "/", `http://${req.headers.host}`);
|
|
5513
|
+
const pathname = url.pathname;
|
|
5514
|
+
try {
|
|
5515
|
+
if (pathname.startsWith("/api/") || pathname === "/health") {
|
|
5516
|
+
await handleApi(req, res, pathname);
|
|
5517
|
+
return;
|
|
5518
|
+
}
|
|
5519
|
+
let filePath = pathname === "/" ? "/index.html" : pathname;
|
|
5520
|
+
filePath = filePath.replace(/^\//, "");
|
|
5521
|
+
await serveStaticFile(res, filePath);
|
|
5522
|
+
} catch (error) {
|
|
5523
|
+
console.error("[WebChatServer] Error:", error);
|
|
5524
|
+
sendError(res, 500, "ERR-WC-008", "Internal server error");
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5527
|
+
return {
|
|
5528
|
+
async start() {
|
|
5529
|
+
if (!port || port === 0) {
|
|
5530
|
+
port = await findAvailablePort(54321, 54399);
|
|
5531
|
+
}
|
|
5532
|
+
session = sessionManager.createSession({
|
|
5533
|
+
accessKey: config.accessKey,
|
|
5534
|
+
apiUrl: config.apiUrl
|
|
5535
|
+
});
|
|
5536
|
+
server = http.createServer(handleRequest);
|
|
5537
|
+
return new Promise((resolve4, reject) => {
|
|
5538
|
+
server.listen(port, host, () => {
|
|
5539
|
+
console.log(`
|
|
5540
|
+
\u{1F310} Web Chat server started at http://${host}:${port}`);
|
|
5541
|
+
console.log(`\u{1F4CB} Session token: ${session.token.slice(0, 12)}...`);
|
|
5542
|
+
console.log(`\u23F0 Session expires in 30 minutes
|
|
5543
|
+
`);
|
|
5544
|
+
resolve4({
|
|
5545
|
+
port,
|
|
5546
|
+
sessionToken: session.token
|
|
5547
|
+
});
|
|
5548
|
+
});
|
|
5549
|
+
server.once("error", (err) => {
|
|
5550
|
+
if (err.code === "EADDRINUSE") {
|
|
5551
|
+
reject(new Error(`Port ${port} is already in use.`));
|
|
5552
|
+
} else {
|
|
5553
|
+
reject(err);
|
|
5554
|
+
}
|
|
5555
|
+
});
|
|
5556
|
+
});
|
|
5557
|
+
},
|
|
5558
|
+
async stop() {
|
|
5559
|
+
if (server) {
|
|
5560
|
+
return new Promise((resolve4) => {
|
|
5561
|
+
server.close(() => {
|
|
5562
|
+
sessionManager.clearAll();
|
|
5563
|
+
console.log("\n\u{1F44B} Web Chat server stopped");
|
|
5564
|
+
resolve4();
|
|
5565
|
+
});
|
|
5566
|
+
});
|
|
5567
|
+
}
|
|
5568
|
+
},
|
|
5569
|
+
getUrl() {
|
|
5570
|
+
return `http://${host}:${port}/?session=${session?.token || ""}`;
|
|
5571
|
+
}
|
|
5572
|
+
};
|
|
5573
|
+
}
|
|
5574
|
+
|
|
5086
5575
|
// src/commands/chat.ts
|
|
5087
|
-
async function
|
|
5576
|
+
async function handleTerminalChat(options) {
|
|
5088
5577
|
const configService = new ConfigService();
|
|
5089
5578
|
const config = await configService.load();
|
|
5090
5579
|
if (!config) {
|
|
@@ -5105,8 +5594,58 @@ async function handleChatCommand(options) {
|
|
|
5105
5594
|
await waitUntilExit();
|
|
5106
5595
|
clear();
|
|
5107
5596
|
}
|
|
5597
|
+
async function handleWebChat(options) {
|
|
5598
|
+
const configService = new ConfigService();
|
|
5599
|
+
const config = await configService.load();
|
|
5600
|
+
if (!config) {
|
|
5601
|
+
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
5602
|
+
}
|
|
5603
|
+
const port = options.port ? parseInt(options.port, 10) : void 0;
|
|
5604
|
+
const server = createWebChatServer({
|
|
5605
|
+
config,
|
|
5606
|
+
port
|
|
5607
|
+
});
|
|
5608
|
+
try {
|
|
5609
|
+
const { port: actualPort, sessionToken } = await server.start();
|
|
5610
|
+
const url = server.getUrl();
|
|
5611
|
+
if (!options.noOpen) {
|
|
5612
|
+
const { default: open } = await import("open");
|
|
5613
|
+
await open(url);
|
|
5614
|
+
console.log("\u{1F310} Browser opened with chat interface");
|
|
5615
|
+
} else {
|
|
5616
|
+
console.log(`
|
|
5617
|
+
\u{1F4CB} Open this URL in your browser:
|
|
5618
|
+
${url}
|
|
5619
|
+
`);
|
|
5620
|
+
}
|
|
5621
|
+
console.log("Press Ctrl+C to stop the server\n");
|
|
5622
|
+
await new Promise((resolve4) => {
|
|
5623
|
+
process.on("SIGINT", async () => {
|
|
5624
|
+
console.log("\n\n\u{1F6D1} Shutting down...");
|
|
5625
|
+
await server.stop();
|
|
5626
|
+
resolve4();
|
|
5627
|
+
});
|
|
5628
|
+
process.on("SIGTERM", async () => {
|
|
5629
|
+
await server.stop();
|
|
5630
|
+
resolve4();
|
|
5631
|
+
});
|
|
5632
|
+
});
|
|
5633
|
+
} catch (error) {
|
|
5634
|
+
if (error instanceof Error && error.message.includes("Port")) {
|
|
5635
|
+
throw new ValidationError(error.message + " Try specifying a different port with --port.");
|
|
5636
|
+
}
|
|
5637
|
+
throw error;
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
async function handleChatCommand(options) {
|
|
5641
|
+
if (options.web) {
|
|
5642
|
+
await handleWebChat(options);
|
|
5643
|
+
} else {
|
|
5644
|
+
await handleTerminalChat(options);
|
|
5645
|
+
}
|
|
5646
|
+
}
|
|
5108
5647
|
function createChatCommand() {
|
|
5109
|
-
const cmd = new Command12("chat").description("Interactive AI chat with Jai1 LLM Proxy").option("--model <model>", "Initial model to use (e.g., gpt-4o, claude-3-opus)").action(async (options) => {
|
|
5648
|
+
const cmd = new Command12("chat").description("Interactive AI chat with Jai1 LLM Proxy").option("--model <model>", "Initial model to use (e.g., gpt-4o, claude-3-opus)").option("--web", "Open chat in web browser instead of terminal").option("--port <port>", "Port for web server (default: auto-find available port)").option("--no-open", "Don't auto-open browser (web mode only)").action(async (options) => {
|
|
5110
5649
|
await handleChatCommand(options);
|
|
5111
5650
|
});
|
|
5112
5651
|
return cmd;
|
|
@@ -5254,8 +5793,8 @@ function createStatsCommand() {
|
|
|
5254
5793
|
import { Command as Command15 } from "commander";
|
|
5255
5794
|
|
|
5256
5795
|
// src/services/translation.service.ts
|
|
5257
|
-
import { promises as
|
|
5258
|
-
import
|
|
5796
|
+
import { promises as fs9 } from "fs";
|
|
5797
|
+
import path5 from "path";
|
|
5259
5798
|
import pLimit from "p-limit";
|
|
5260
5799
|
import pRetry from "p-retry";
|
|
5261
5800
|
var TRANSLATABLE_EXTS = [".md", ".txt", ".json", ".yaml", ".yml"];
|
|
@@ -5273,7 +5812,7 @@ var TranslationService = class {
|
|
|
5273
5812
|
*/
|
|
5274
5813
|
async detectInputType(input4) {
|
|
5275
5814
|
try {
|
|
5276
|
-
const stat = await
|
|
5815
|
+
const stat = await fs9.stat(input4);
|
|
5277
5816
|
if (stat.isDirectory()) return "folder";
|
|
5278
5817
|
if (stat.isFile()) return "file";
|
|
5279
5818
|
} catch {
|
|
@@ -5310,13 +5849,13 @@ var TranslationService = class {
|
|
|
5310
5849
|
*/
|
|
5311
5850
|
async translateFile(filePath) {
|
|
5312
5851
|
try {
|
|
5313
|
-
const content = await
|
|
5314
|
-
const ext =
|
|
5852
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
5853
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
5315
5854
|
const fileType = this.getFileType(ext);
|
|
5316
5855
|
const translatedContent = await this.translateWithRetry(content, fileType);
|
|
5317
5856
|
const outputPath = this.generateOutputPath(filePath);
|
|
5318
5857
|
if (!this.options.dryRun) {
|
|
5319
|
-
await
|
|
5858
|
+
await fs9.writeFile(outputPath, translatedContent, "utf-8");
|
|
5320
5859
|
}
|
|
5321
5860
|
return {
|
|
5322
5861
|
inputPath: filePath,
|
|
@@ -5371,27 +5910,27 @@ var TranslationService = class {
|
|
|
5371
5910
|
if (this.options.output) {
|
|
5372
5911
|
return this.options.output;
|
|
5373
5912
|
}
|
|
5374
|
-
const ext =
|
|
5375
|
-
const base =
|
|
5376
|
-
const dir =
|
|
5913
|
+
const ext = path5.extname(inputPath);
|
|
5914
|
+
const base = path5.basename(inputPath, ext);
|
|
5915
|
+
const dir = path5.dirname(inputPath);
|
|
5377
5916
|
const lang = this.options.to;
|
|
5378
|
-
return
|
|
5917
|
+
return path5.join(dir, `${base}.${lang}${ext}`);
|
|
5379
5918
|
}
|
|
5380
5919
|
/**
|
|
5381
5920
|
* Discover translatable files in folder recursively
|
|
5382
5921
|
*/
|
|
5383
5922
|
async discoverFiles(folderPath) {
|
|
5384
|
-
const entries = await
|
|
5923
|
+
const entries = await fs9.readdir(folderPath, { withFileTypes: true });
|
|
5385
5924
|
const files = [];
|
|
5386
5925
|
for (const entry of entries) {
|
|
5387
|
-
const fullPath =
|
|
5926
|
+
const fullPath = path5.join(folderPath, entry.name);
|
|
5388
5927
|
if (entry.isDirectory()) {
|
|
5389
5928
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
5390
5929
|
const subFiles = await this.discoverFiles(fullPath);
|
|
5391
5930
|
files.push(...subFiles);
|
|
5392
5931
|
}
|
|
5393
5932
|
} else if (entry.isFile()) {
|
|
5394
|
-
const ext =
|
|
5933
|
+
const ext = path5.extname(entry.name).toLowerCase();
|
|
5395
5934
|
if (TRANSLATABLE_EXTS.includes(ext)) {
|
|
5396
5935
|
if (!this.isTranslatedFile(entry.name)) {
|
|
5397
5936
|
files.push(fullPath);
|
|
@@ -5896,7 +6435,7 @@ import { Command as Command34 } from "commander";
|
|
|
5896
6435
|
import { Command as Command21 } from "commander";
|
|
5897
6436
|
|
|
5898
6437
|
// src/services/utils.service.ts
|
|
5899
|
-
import
|
|
6438
|
+
import crypto2 from "crypto";
|
|
5900
6439
|
import bcrypt from "bcryptjs";
|
|
5901
6440
|
import { readFile } from "fs/promises";
|
|
5902
6441
|
var UtilsService = class {
|
|
@@ -5920,7 +6459,7 @@ var UtilsService = class {
|
|
|
5920
6459
|
if (charset.length === 0) {
|
|
5921
6460
|
throw new Error("At least one character type must be enabled");
|
|
5922
6461
|
}
|
|
5923
|
-
const randomBytes =
|
|
6462
|
+
const randomBytes = crypto2.randomBytes(length);
|
|
5924
6463
|
let password = "";
|
|
5925
6464
|
for (let i = 0; i < length; i++) {
|
|
5926
6465
|
const randomIndex = randomBytes[i] % charset.length;
|
|
@@ -5932,7 +6471,7 @@ var UtilsService = class {
|
|
|
5932
6471
|
* Generate UUID v4
|
|
5933
6472
|
*/
|
|
5934
6473
|
generateUuid(options) {
|
|
5935
|
-
let uuid =
|
|
6474
|
+
let uuid = crypto2.randomUUID();
|
|
5936
6475
|
if (options?.uppercase) {
|
|
5937
6476
|
uuid = uuid.toUpperCase();
|
|
5938
6477
|
}
|
|
@@ -5948,7 +6487,7 @@ var UtilsService = class {
|
|
|
5948
6487
|
if (algorithm === "bcrypt") {
|
|
5949
6488
|
throw new Error("Use hashBcrypt for bcrypt algorithm");
|
|
5950
6489
|
}
|
|
5951
|
-
const hash =
|
|
6490
|
+
const hash = crypto2.createHash(algorithm);
|
|
5952
6491
|
hash.update(input4);
|
|
5953
6492
|
return hash.digest("hex");
|
|
5954
6493
|
}
|
|
@@ -6036,7 +6575,7 @@ var UtilsService = class {
|
|
|
6036
6575
|
const headerB64 = this.base64Encode(JSON.stringify(finalHeader), true);
|
|
6037
6576
|
const payloadB64 = this.base64Encode(JSON.stringify(payload), true);
|
|
6038
6577
|
const signatureInput = `${headerB64}.${payloadB64}`;
|
|
6039
|
-
const signature =
|
|
6578
|
+
const signature = crypto2.createHmac("sha256", secret).update(signatureInput).digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
6040
6579
|
return `${headerB64}.${payloadB64}.${signature}`;
|
|
6041
6580
|
}
|
|
6042
6581
|
/**
|
|
@@ -7943,8 +8482,8 @@ var HttpView = () => {
|
|
|
7943
8482
|
import React37, { useState as useState24 } from "react";
|
|
7944
8483
|
import { Box as Box27, Text as Text28, useInput as useInput22 } from "ink";
|
|
7945
8484
|
import TextInput14 from "ink-text-input";
|
|
7946
|
-
import * as
|
|
7947
|
-
import * as
|
|
8485
|
+
import * as fs10 from "fs";
|
|
8486
|
+
import * as path6 from "path";
|
|
7948
8487
|
var MarkdownView = () => {
|
|
7949
8488
|
const [filePath, setFilePath] = useState24("");
|
|
7950
8489
|
const [content, setContent] = useState24("");
|
|
@@ -7964,12 +8503,12 @@ var MarkdownView = () => {
|
|
|
7964
8503
|
}
|
|
7965
8504
|
try {
|
|
7966
8505
|
setError("");
|
|
7967
|
-
const resolvedPath =
|
|
7968
|
-
if (!
|
|
8506
|
+
const resolvedPath = path6.resolve(filePath);
|
|
8507
|
+
if (!fs10.existsSync(resolvedPath)) {
|
|
7969
8508
|
setError(`File not found: ${resolvedPath}`);
|
|
7970
8509
|
return;
|
|
7971
8510
|
}
|
|
7972
|
-
let fileContent =
|
|
8511
|
+
let fileContent = fs10.readFileSync(resolvedPath, "utf-8");
|
|
7973
8512
|
fileContent = fileContent.replace(/```mermaid\n([\s\S]*?)```/g, (match, code) => {
|
|
7974
8513
|
return `
|
|
7975
8514
|
\u{1F3A8} **Mermaid Diagram**
|
|
@@ -8250,8 +8789,8 @@ import { Command as Command35 } from "commander";
|
|
|
8250
8789
|
import { checkbox as checkbox3, confirm as confirm5 } from "@inquirer/prompts";
|
|
8251
8790
|
|
|
8252
8791
|
// src/services/deps.service.ts
|
|
8253
|
-
import { promises as
|
|
8254
|
-
import
|
|
8792
|
+
import { promises as fs11 } from "fs";
|
|
8793
|
+
import path7 from "path";
|
|
8255
8794
|
import { execSync } from "child_process";
|
|
8256
8795
|
import pLimit2 from "p-limit";
|
|
8257
8796
|
var DepsService = class {
|
|
@@ -8260,9 +8799,9 @@ var DepsService = class {
|
|
|
8260
8799
|
* Đọc package.json từ thư mục
|
|
8261
8800
|
*/
|
|
8262
8801
|
async readPackageJson(cwd) {
|
|
8263
|
-
const pkgPath =
|
|
8802
|
+
const pkgPath = path7.join(cwd, "package.json");
|
|
8264
8803
|
try {
|
|
8265
|
-
const content = await
|
|
8804
|
+
const content = await fs11.readFile(pkgPath, "utf-8");
|
|
8266
8805
|
return JSON.parse(content);
|
|
8267
8806
|
} catch (error) {
|
|
8268
8807
|
if (error.code === "ENOENT") {
|
|
@@ -8368,7 +8907,7 @@ var DepsService = class {
|
|
|
8368
8907
|
];
|
|
8369
8908
|
for (const { file, pm } of lockFiles) {
|
|
8370
8909
|
try {
|
|
8371
|
-
await
|
|
8910
|
+
await fs11.access(path7.join(cwd, file));
|
|
8372
8911
|
return pm;
|
|
8373
8912
|
} catch {
|
|
8374
8913
|
}
|
|
@@ -8652,7 +9191,7 @@ import { Command as Command40 } from "commander";
|
|
|
8652
9191
|
import { Command as Command37 } from "commander";
|
|
8653
9192
|
|
|
8654
9193
|
// src/services/starter-kit.service.ts
|
|
8655
|
-
import { promises as
|
|
9194
|
+
import { promises as fs12 } from "fs";
|
|
8656
9195
|
import { join as join5 } from "path";
|
|
8657
9196
|
import AdmZip from "adm-zip";
|
|
8658
9197
|
var StarterKitService = class {
|
|
@@ -8701,16 +9240,16 @@ var StarterKitService = class {
|
|
|
8701
9240
|
}
|
|
8702
9241
|
if (onProgress) onProgress(30);
|
|
8703
9242
|
const tmpDir = join5(process.env.TMPDIR || "/tmp", "jai1-kits");
|
|
8704
|
-
await
|
|
9243
|
+
await fs12.mkdir(tmpDir, { recursive: true });
|
|
8705
9244
|
const tmpFile = join5(tmpDir, `${slug}.zip`);
|
|
8706
9245
|
const buffer = await response.arrayBuffer();
|
|
8707
|
-
await
|
|
9246
|
+
await fs12.writeFile(tmpFile, Buffer.from(buffer));
|
|
8708
9247
|
if (onProgress) onProgress(60);
|
|
8709
9248
|
const zip = new AdmZip(tmpFile);
|
|
8710
|
-
await
|
|
9249
|
+
await fs12.mkdir(targetDir, { recursive: true });
|
|
8711
9250
|
zip.extractAllTo(targetDir, true);
|
|
8712
9251
|
if (onProgress) onProgress(100);
|
|
8713
|
-
await
|
|
9252
|
+
await fs12.unlink(tmpFile);
|
|
8714
9253
|
}
|
|
8715
9254
|
};
|
|
8716
9255
|
|
|
@@ -8805,7 +9344,7 @@ Post-Init Commands:`);
|
|
|
8805
9344
|
|
|
8806
9345
|
// src/commands/kit/create.ts
|
|
8807
9346
|
import { Command as Command39 } from "commander";
|
|
8808
|
-
import { promises as
|
|
9347
|
+
import { promises as fs13 } from "fs";
|
|
8809
9348
|
import { join as join6 } from "path";
|
|
8810
9349
|
import { select as select2, input, checkbox as checkbox4 } from "@inquirer/prompts";
|
|
8811
9350
|
import { execa as execa2 } from "execa";
|
|
@@ -8871,7 +9410,7 @@ function createKitCreateCommand() {
|
|
|
8871
9410
|
}
|
|
8872
9411
|
const targetDir = directory || join6(process.cwd(), options.name || slug);
|
|
8873
9412
|
try {
|
|
8874
|
-
await
|
|
9413
|
+
await fs13.access(targetDir);
|
|
8875
9414
|
throw new Error(`Directory already exists: ${targetDir}`);
|
|
8876
9415
|
} catch (error) {
|
|
8877
9416
|
if (error.code !== "ENOENT") {
|
|
@@ -8989,7 +9528,7 @@ function createKitCreateCommand() {
|
|
|
8989
9528
|
async function applyVariableSubstitution(dir, variables) {
|
|
8990
9529
|
const files = await getAllFiles(dir);
|
|
8991
9530
|
for (const file of files) {
|
|
8992
|
-
let content = await
|
|
9531
|
+
let content = await fs13.readFile(file, "utf-8");
|
|
8993
9532
|
let modified = false;
|
|
8994
9533
|
for (const [key, value] of Object.entries(variables)) {
|
|
8995
9534
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
@@ -8999,13 +9538,13 @@ async function applyVariableSubstitution(dir, variables) {
|
|
|
8999
9538
|
}
|
|
9000
9539
|
}
|
|
9001
9540
|
if (modified) {
|
|
9002
|
-
await
|
|
9541
|
+
await fs13.writeFile(file, content, "utf-8");
|
|
9003
9542
|
}
|
|
9004
9543
|
}
|
|
9005
9544
|
}
|
|
9006
9545
|
async function getAllFiles(dir) {
|
|
9007
9546
|
const files = [];
|
|
9008
|
-
const entries = await
|
|
9547
|
+
const entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
9009
9548
|
for (const entry of entries) {
|
|
9010
9549
|
const fullPath = join6(dir, entry.name);
|
|
9011
9550
|
if (entry.isDirectory()) {
|
|
@@ -9092,7 +9631,7 @@ function createRulesListCommand() {
|
|
|
9092
9631
|
|
|
9093
9632
|
// src/commands/rules/init.ts
|
|
9094
9633
|
import { Command as Command42 } from "commander";
|
|
9095
|
-
import { promises as
|
|
9634
|
+
import { promises as fs14 } from "fs";
|
|
9096
9635
|
import { join as join7 } from "path";
|
|
9097
9636
|
import { select as select3, confirm as confirm6 } from "@inquirer/prompts";
|
|
9098
9637
|
function createRulesInitCommand() {
|
|
@@ -9178,7 +9717,7 @@ function createRulesInitCommand() {
|
|
|
9178
9717
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9179
9718
|
customContext: "09-custom.mdc"
|
|
9180
9719
|
};
|
|
9181
|
-
await
|
|
9720
|
+
await fs14.writeFile("jai1-rules.json", JSON.stringify(projectConfig, null, 2));
|
|
9182
9721
|
console.log("\u2713 Created jai1-rules.json");
|
|
9183
9722
|
console.log("\n\u2705 Preset applied successfully!\n");
|
|
9184
9723
|
console.log("Next steps:");
|
|
@@ -9189,10 +9728,10 @@ function createRulesInitCommand() {
|
|
|
9189
9728
|
}
|
|
9190
9729
|
async function applyCursorFormat(bundle) {
|
|
9191
9730
|
const rulesDir = join7(process.cwd(), ".cursor", "rules");
|
|
9192
|
-
await
|
|
9731
|
+
await fs14.mkdir(rulesDir, { recursive: true });
|
|
9193
9732
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
9194
9733
|
const filePath = join7(rulesDir, filename);
|
|
9195
|
-
await
|
|
9734
|
+
await fs14.writeFile(filePath, content, "utf-8");
|
|
9196
9735
|
console.log(`\u2713 Created .cursor/rules/${filename}`);
|
|
9197
9736
|
}
|
|
9198
9737
|
}
|
|
@@ -9219,13 +9758,13 @@ async function applyAgentsMdFormat(bundle) {
|
|
|
9219
9758
|
}
|
|
9220
9759
|
}
|
|
9221
9760
|
const agentsMd = sections.join("\n");
|
|
9222
|
-
await
|
|
9761
|
+
await fs14.writeFile("AGENTS.md", agentsMd, "utf-8");
|
|
9223
9762
|
console.log("\u2713 Created AGENTS.md");
|
|
9224
9763
|
}
|
|
9225
9764
|
|
|
9226
9765
|
// src/commands/rules/apply.ts
|
|
9227
9766
|
import { Command as Command43 } from "commander";
|
|
9228
|
-
import { promises as
|
|
9767
|
+
import { promises as fs16 } from "fs";
|
|
9229
9768
|
import { join as join9 } from "path";
|
|
9230
9769
|
import { select as select4, confirm as confirm7, checkbox as checkbox5 } from "@inquirer/prompts";
|
|
9231
9770
|
|
|
@@ -9586,7 +10125,7 @@ Follow all instructions and patterns defined in AGENTS.md above.
|
|
|
9586
10125
|
};
|
|
9587
10126
|
|
|
9588
10127
|
// src/services/backup.service.ts
|
|
9589
|
-
import { promises as
|
|
10128
|
+
import { promises as fs15 } from "fs";
|
|
9590
10129
|
import { join as join8, dirname } from "path";
|
|
9591
10130
|
var BackupService = class {
|
|
9592
10131
|
backupDir = ".jai1/backups";
|
|
@@ -9617,16 +10156,16 @@ var BackupService = class {
|
|
|
9617
10156
|
await this.backupSingleFile("GEMINI.md", backupPath, ideId, backedUpFiles);
|
|
9618
10157
|
hasContent = true;
|
|
9619
10158
|
} else {
|
|
9620
|
-
const stats = await
|
|
10159
|
+
const stats = await fs15.stat(rulesPath);
|
|
9621
10160
|
if (stats.isDirectory()) {
|
|
9622
|
-
const files = await
|
|
10161
|
+
const files = await fs15.readdir(rulesPath);
|
|
9623
10162
|
for (const file of files) {
|
|
9624
10163
|
if (file.endsWith(format.fileExtension)) {
|
|
9625
10164
|
const originalPath = join8(rulesPath, file);
|
|
9626
10165
|
const relativePath = join8(format.rulesPath, file);
|
|
9627
10166
|
const destPath = join8(backupPath, ideId, file);
|
|
9628
|
-
await
|
|
9629
|
-
await
|
|
10167
|
+
await fs15.mkdir(dirname(destPath), { recursive: true });
|
|
10168
|
+
await fs15.copyFile(originalPath, destPath);
|
|
9630
10169
|
backedUpFiles.push({
|
|
9631
10170
|
originalPath: relativePath,
|
|
9632
10171
|
backupPath: join8(ideId, file),
|
|
@@ -9650,8 +10189,8 @@ var BackupService = class {
|
|
|
9650
10189
|
ides,
|
|
9651
10190
|
files: backedUpFiles
|
|
9652
10191
|
};
|
|
9653
|
-
await
|
|
9654
|
-
await
|
|
10192
|
+
await fs15.mkdir(backupPath, { recursive: true });
|
|
10193
|
+
await fs15.writeFile(
|
|
9655
10194
|
join8(backupPath, "metadata.json"),
|
|
9656
10195
|
JSON.stringify(metadata, null, 2),
|
|
9657
10196
|
"utf-8"
|
|
@@ -9669,8 +10208,8 @@ var BackupService = class {
|
|
|
9669
10208
|
return;
|
|
9670
10209
|
}
|
|
9671
10210
|
const destPath = join8(backupPath, ideId, filename);
|
|
9672
|
-
await
|
|
9673
|
-
await
|
|
10211
|
+
await fs15.mkdir(dirname(destPath), { recursive: true });
|
|
10212
|
+
await fs15.copyFile(originalPath, destPath);
|
|
9674
10213
|
backedUpFiles.push({
|
|
9675
10214
|
originalPath: filename,
|
|
9676
10215
|
backupPath: join8(ideId, filename),
|
|
@@ -9684,15 +10223,15 @@ var BackupService = class {
|
|
|
9684
10223
|
*/
|
|
9685
10224
|
async restoreBackup(backupPath) {
|
|
9686
10225
|
const metadataPath = join8(backupPath, "metadata.json");
|
|
9687
|
-
const metadataContent = await
|
|
10226
|
+
const metadataContent = await fs15.readFile(metadataPath, "utf-8");
|
|
9688
10227
|
const metadata = JSON.parse(metadataContent);
|
|
9689
10228
|
console.log(`
|
|
9690
10229
|
Restoring backup from ${metadata.timestamp}...`);
|
|
9691
10230
|
for (const file of metadata.files) {
|
|
9692
10231
|
const sourcePath = join8(backupPath, file.backupPath);
|
|
9693
10232
|
const destPath = join8(process.cwd(), file.originalPath);
|
|
9694
|
-
await
|
|
9695
|
-
await
|
|
10233
|
+
await fs15.mkdir(dirname(destPath), { recursive: true });
|
|
10234
|
+
await fs15.copyFile(sourcePath, destPath);
|
|
9696
10235
|
console.log(`\u2713 Restored ${file.originalPath}`);
|
|
9697
10236
|
}
|
|
9698
10237
|
console.log("\n\u2705 Backup restored successfully!");
|
|
@@ -9707,13 +10246,13 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9707
10246
|
if (!exists) {
|
|
9708
10247
|
return [];
|
|
9709
10248
|
}
|
|
9710
|
-
const entries = await
|
|
10249
|
+
const entries = await fs15.readdir(backupDirPath, { withFileTypes: true });
|
|
9711
10250
|
const backups = [];
|
|
9712
10251
|
for (const entry of entries) {
|
|
9713
10252
|
if (entry.isDirectory()) {
|
|
9714
10253
|
const metadataPath = join8(backupDirPath, entry.name, "metadata.json");
|
|
9715
10254
|
try {
|
|
9716
|
-
const metadataContent = await
|
|
10255
|
+
const metadataContent = await fs15.readFile(metadataPath, "utf-8");
|
|
9717
10256
|
const metadata = JSON.parse(metadataContent);
|
|
9718
10257
|
backups.push(metadata);
|
|
9719
10258
|
} catch {
|
|
@@ -9760,9 +10299,9 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9760
10299
|
/**
|
|
9761
10300
|
* Check if a path exists
|
|
9762
10301
|
*/
|
|
9763
|
-
async pathExists(
|
|
10302
|
+
async pathExists(path9) {
|
|
9764
10303
|
try {
|
|
9765
|
-
await
|
|
10304
|
+
await fs15.access(path9);
|
|
9766
10305
|
return true;
|
|
9767
10306
|
} catch {
|
|
9768
10307
|
return false;
|
|
@@ -9771,22 +10310,22 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9771
10310
|
/**
|
|
9772
10311
|
* Recursively delete a directory
|
|
9773
10312
|
*/
|
|
9774
|
-
async deleteDirectory(
|
|
10313
|
+
async deleteDirectory(path9) {
|
|
9775
10314
|
try {
|
|
9776
|
-
const exists = await this.pathExists(
|
|
10315
|
+
const exists = await this.pathExists(path9);
|
|
9777
10316
|
if (!exists) {
|
|
9778
10317
|
return;
|
|
9779
10318
|
}
|
|
9780
|
-
const entries = await
|
|
10319
|
+
const entries = await fs15.readdir(path9, { withFileTypes: true });
|
|
9781
10320
|
for (const entry of entries) {
|
|
9782
|
-
const fullPath = join8(
|
|
10321
|
+
const fullPath = join8(path9, entry.name);
|
|
9783
10322
|
if (entry.isDirectory()) {
|
|
9784
10323
|
await this.deleteDirectory(fullPath);
|
|
9785
10324
|
} else {
|
|
9786
|
-
await
|
|
10325
|
+
await fs15.unlink(fullPath);
|
|
9787
10326
|
}
|
|
9788
10327
|
}
|
|
9789
|
-
await
|
|
10328
|
+
await fs15.rmdir(path9);
|
|
9790
10329
|
} catch (error) {
|
|
9791
10330
|
}
|
|
9792
10331
|
}
|
|
@@ -9801,7 +10340,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9801
10340
|
*/
|
|
9802
10341
|
async ensureBackupDir() {
|
|
9803
10342
|
const backupDirPath = join8(process.cwd(), this.backupDir);
|
|
9804
|
-
await
|
|
10343
|
+
await fs15.mkdir(backupDirPath, { recursive: true });
|
|
9805
10344
|
}
|
|
9806
10345
|
};
|
|
9807
10346
|
|
|
@@ -9959,19 +10498,19 @@ function createRulesApplyCommand() {
|
|
|
9959
10498
|
console.log("\n\u{1F4DD} Applying preset...\n");
|
|
9960
10499
|
const rulePresetDir = join9(process.cwd(), ".jai1", "rule-preset");
|
|
9961
10500
|
try {
|
|
9962
|
-
await
|
|
10501
|
+
await fs16.rm(rulePresetDir, { recursive: true, force: true });
|
|
9963
10502
|
} catch {
|
|
9964
10503
|
}
|
|
9965
|
-
await
|
|
9966
|
-
await
|
|
10504
|
+
await fs16.mkdir(rulePresetDir, { recursive: true });
|
|
10505
|
+
await fs16.writeFile(
|
|
9967
10506
|
join9(rulePresetDir, "preset.json"),
|
|
9968
10507
|
JSON.stringify(bundle.preset, null, 2),
|
|
9969
10508
|
"utf-8"
|
|
9970
10509
|
);
|
|
9971
10510
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
9972
10511
|
const filePath = join9(rulePresetDir, filename);
|
|
9973
|
-
await
|
|
9974
|
-
await
|
|
10512
|
+
await fs16.mkdir(join9(filePath, ".."), { recursive: true });
|
|
10513
|
+
await fs16.writeFile(filePath, content, "utf-8");
|
|
9975
10514
|
}
|
|
9976
10515
|
console.log(`\u2713 Saved preset to .jai1/rule-preset/`);
|
|
9977
10516
|
const allGeneratedFiles = [];
|
|
@@ -9980,8 +10519,8 @@ function createRulesApplyCommand() {
|
|
|
9980
10519
|
const files = generatorService.generateForIde(bundle, ideId);
|
|
9981
10520
|
for (const file of files) {
|
|
9982
10521
|
const fullPath = join9(process.cwd(), file.path);
|
|
9983
|
-
await
|
|
9984
|
-
await
|
|
10522
|
+
await fs16.mkdir(join9(fullPath, ".."), { recursive: true });
|
|
10523
|
+
await fs16.writeFile(fullPath, file.content, "utf-8");
|
|
9985
10524
|
console.log(`\u2713 [${ideId}] ${file.path}`);
|
|
9986
10525
|
allGeneratedFiles.push({
|
|
9987
10526
|
ide: ideId,
|
|
@@ -10009,7 +10548,7 @@ function createRulesApplyCommand() {
|
|
|
10009
10548
|
};
|
|
10010
10549
|
try {
|
|
10011
10550
|
const existingConfigPath = join9(process.cwd(), "jai1-rules.json");
|
|
10012
|
-
const existingConfigContent = await
|
|
10551
|
+
const existingConfigContent = await fs16.readFile(existingConfigPath, "utf-8");
|
|
10013
10552
|
const existingConfig = JSON.parse(existingConfigContent);
|
|
10014
10553
|
if (existingConfig.backups && existingConfig.backups.length > 0) {
|
|
10015
10554
|
projectConfig.backups = [
|
|
@@ -10020,7 +10559,7 @@ function createRulesApplyCommand() {
|
|
|
10020
10559
|
}
|
|
10021
10560
|
} catch {
|
|
10022
10561
|
}
|
|
10023
|
-
await
|
|
10562
|
+
await fs16.writeFile(
|
|
10024
10563
|
join9(process.cwd(), "jai1-rules.json"),
|
|
10025
10564
|
JSON.stringify(projectConfig, null, 2),
|
|
10026
10565
|
"utf-8"
|
|
@@ -10125,7 +10664,7 @@ function formatTimestamp(timestamp) {
|
|
|
10125
10664
|
|
|
10126
10665
|
// src/commands/rules/sync.ts
|
|
10127
10666
|
import { Command as Command45 } from "commander";
|
|
10128
|
-
import { promises as
|
|
10667
|
+
import { promises as fs17 } from "fs";
|
|
10129
10668
|
import { join as join11 } from "path";
|
|
10130
10669
|
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
10131
10670
|
function createRulesSyncCommand() {
|
|
@@ -10133,7 +10672,7 @@ function createRulesSyncCommand() {
|
|
|
10133
10672
|
const configPath = join11(process.cwd(), "jai1-rules.json");
|
|
10134
10673
|
let projectConfig;
|
|
10135
10674
|
try {
|
|
10136
|
-
const configContent = await
|
|
10675
|
+
const configContent = await fs17.readFile(configPath, "utf-8");
|
|
10137
10676
|
projectConfig = JSON.parse(configContent);
|
|
10138
10677
|
} catch {
|
|
10139
10678
|
throw new ValidationError(
|
|
@@ -10216,11 +10755,11 @@ Detected ${detected.length} active IDE(s):
|
|
|
10216
10755
|
const rulePresetDir = join11(process.cwd(), ".jai1", "rule-preset");
|
|
10217
10756
|
const presetExists = await checkPathExists(rulePresetDir);
|
|
10218
10757
|
if (presetExists) {
|
|
10219
|
-
const files = await
|
|
10758
|
+
const files = await fs17.readdir(rulePresetDir);
|
|
10220
10759
|
for (const file of files) {
|
|
10221
10760
|
if (file.endsWith(".mdc")) {
|
|
10222
10761
|
const filePath = join11(rulePresetDir, file);
|
|
10223
|
-
const content = await
|
|
10762
|
+
const content = await fs17.readFile(filePath, "utf-8");
|
|
10224
10763
|
bundle.files[file] = content;
|
|
10225
10764
|
}
|
|
10226
10765
|
}
|
|
@@ -10240,8 +10779,8 @@ Detected ${detected.length} active IDE(s):
|
|
|
10240
10779
|
const files = generatorService.generateForIde(bundle, ideId);
|
|
10241
10780
|
for (const file of files) {
|
|
10242
10781
|
const fullPath = join11(process.cwd(), file.path);
|
|
10243
|
-
await
|
|
10244
|
-
await
|
|
10782
|
+
await fs17.mkdir(join11(fullPath, ".."), { recursive: true });
|
|
10783
|
+
await fs17.writeFile(fullPath, file.content, "utf-8");
|
|
10245
10784
|
}
|
|
10246
10785
|
console.log(`\u2713 ${format.name} - ${files.length} files regenerated`);
|
|
10247
10786
|
} catch (error) {
|
|
@@ -10250,7 +10789,7 @@ Detected ${detected.length} active IDE(s):
|
|
|
10250
10789
|
}
|
|
10251
10790
|
if (JSON.stringify(projectConfig.ides) !== JSON.stringify(idesToSync)) {
|
|
10252
10791
|
projectConfig.ides = idesToSync;
|
|
10253
|
-
await
|
|
10792
|
+
await fs17.writeFile(
|
|
10254
10793
|
join11(process.cwd(), "jai1-rules.json"),
|
|
10255
10794
|
JSON.stringify(projectConfig, null, 2),
|
|
10256
10795
|
"utf-8"
|
|
@@ -10266,7 +10805,7 @@ Detected ${detected.length} active IDE(s):
|
|
|
10266
10805
|
}
|
|
10267
10806
|
async function checkPathExists(absolutePath) {
|
|
10268
10807
|
try {
|
|
10269
|
-
await
|
|
10808
|
+
await fs17.access(absolutePath);
|
|
10270
10809
|
return true;
|
|
10271
10810
|
} catch {
|
|
10272
10811
|
return false;
|
|
@@ -10275,14 +10814,14 @@ async function checkPathExists(absolutePath) {
|
|
|
10275
10814
|
|
|
10276
10815
|
// src/commands/rules/info.ts
|
|
10277
10816
|
import { Command as Command46 } from "commander";
|
|
10278
|
-
import { promises as
|
|
10817
|
+
import { promises as fs18 } from "fs";
|
|
10279
10818
|
import { join as join12 } from "path";
|
|
10280
10819
|
function createRulesInfoCommand() {
|
|
10281
10820
|
return new Command46("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
|
|
10282
10821
|
const configPath = join12(process.cwd(), "jai1-rules.json");
|
|
10283
10822
|
let projectConfig;
|
|
10284
10823
|
try {
|
|
10285
|
-
const configContent = await
|
|
10824
|
+
const configContent = await fs18.readFile(configPath, "utf-8");
|
|
10286
10825
|
projectConfig = JSON.parse(configContent);
|
|
10287
10826
|
} catch {
|
|
10288
10827
|
throw new ValidationError(
|
|
@@ -10299,9 +10838,9 @@ function createRulesInfoCommand() {
|
|
|
10299
10838
|
let presetMetadata = null;
|
|
10300
10839
|
let presetFiles = [];
|
|
10301
10840
|
try {
|
|
10302
|
-
const presetContent = await
|
|
10841
|
+
const presetContent = await fs18.readFile(presetJsonPath, "utf-8");
|
|
10303
10842
|
presetMetadata = JSON.parse(presetContent);
|
|
10304
|
-
const files = await
|
|
10843
|
+
const files = await fs18.readdir(rulePresetDir);
|
|
10305
10844
|
presetFiles = files.filter((f) => f.endsWith(".mdc"));
|
|
10306
10845
|
} catch {
|
|
10307
10846
|
}
|
|
@@ -10360,9 +10899,9 @@ Available Backups (${projectConfig.backups.length}):`);
|
|
|
10360
10899
|
console.log(' \u2022 "jai1 rules apply" - Apply a different preset (replaces current)');
|
|
10361
10900
|
});
|
|
10362
10901
|
}
|
|
10363
|
-
async function checkPathExists2(
|
|
10902
|
+
async function checkPathExists2(path9) {
|
|
10364
10903
|
try {
|
|
10365
|
-
await
|
|
10904
|
+
await fs18.access(join12(process.cwd(), path9));
|
|
10366
10905
|
return true;
|
|
10367
10906
|
} catch {
|
|
10368
10907
|
return false;
|
|
@@ -10793,8 +11332,8 @@ var RedmineApiClient = class {
|
|
|
10793
11332
|
this.retryConfig = config.defaults.retry;
|
|
10794
11333
|
this.concurrencyLimit = pLimit3(config.defaults.concurrency);
|
|
10795
11334
|
}
|
|
10796
|
-
async request(
|
|
10797
|
-
const url = `${this.baseUrl}${
|
|
11335
|
+
async request(path9, options = {}) {
|
|
11336
|
+
const url = `${this.baseUrl}${path9}`;
|
|
10798
11337
|
const headers = {
|
|
10799
11338
|
"X-Redmine-API-Key": this.apiAccessToken,
|
|
10800
11339
|
"Content-Type": "application/json",
|
|
@@ -10855,8 +11394,8 @@ var RedmineApiClient = class {
|
|
|
10855
11394
|
if (include && include.length > 0) {
|
|
10856
11395
|
params.append("include", include.join(","));
|
|
10857
11396
|
}
|
|
10858
|
-
const
|
|
10859
|
-
return this.request(
|
|
11397
|
+
const path9 = `/issues/${issueId}.json${params.toString() ? `?${params.toString()}` : ""}`;
|
|
11398
|
+
return this.request(path9);
|
|
10860
11399
|
}
|
|
10861
11400
|
async getIssues(projectId, options = {}) {
|
|
10862
11401
|
const params = new URLSearchParams();
|
|
@@ -10876,8 +11415,8 @@ var RedmineApiClient = class {
|
|
|
10876
11415
|
if (options.updatedSince) {
|
|
10877
11416
|
params.append("updated_on", `>=${options.updatedSince}`);
|
|
10878
11417
|
}
|
|
10879
|
-
const
|
|
10880
|
-
return this.request(
|
|
11418
|
+
const path9 = `/issues.json?${params.toString()}`;
|
|
11419
|
+
return this.request(path9);
|
|
10881
11420
|
}
|
|
10882
11421
|
async getAllIssues(projectId, options = {}) {
|
|
10883
11422
|
const pageSize = options.pageSize || 100;
|
|
@@ -11561,7 +12100,7 @@ async function handleSyncProject(options) {
|
|
|
11561
12100
|
|
|
11562
12101
|
// src/commands/framework/info.ts
|
|
11563
12102
|
import { Command as Command53 } from "commander";
|
|
11564
|
-
import { promises as
|
|
12103
|
+
import { promises as fs19 } from "fs";
|
|
11565
12104
|
import { join as join14 } from "path";
|
|
11566
12105
|
import { homedir as homedir5 } from "os";
|
|
11567
12106
|
function createInfoCommand() {
|
|
@@ -11613,7 +12152,7 @@ function maskKey3(key) {
|
|
|
11613
12152
|
async function getProjectStatus2() {
|
|
11614
12153
|
const projectJai1 = join14(process.cwd(), ".jai1");
|
|
11615
12154
|
try {
|
|
11616
|
-
await
|
|
12155
|
+
await fs19.access(projectJai1);
|
|
11617
12156
|
return { exists: true, version: "Synced" };
|
|
11618
12157
|
} catch {
|
|
11619
12158
|
return { exists: false };
|
|
@@ -11803,8 +12342,8 @@ function createClearBackupsCommand() {
|
|
|
11803
12342
|
// src/commands/vscode/index.ts
|
|
11804
12343
|
import { Command as Command56 } from "commander";
|
|
11805
12344
|
import { checkbox as checkbox7, confirm as confirm14, select as select7 } from "@inquirer/prompts";
|
|
11806
|
-
import
|
|
11807
|
-
import
|
|
12345
|
+
import fs20 from "fs/promises";
|
|
12346
|
+
import path8 from "path";
|
|
11808
12347
|
import { existsSync as existsSync3 } from "fs";
|
|
11809
12348
|
var PERFORMANCE_GROUPS2 = {
|
|
11810
12349
|
telemetry: {
|
|
@@ -12024,8 +12563,8 @@ async function selectGroupsToApply2(action) {
|
|
|
12024
12563
|
}
|
|
12025
12564
|
}
|
|
12026
12565
|
async function applyGroups2(groupKeys, action) {
|
|
12027
|
-
const vscodeDir =
|
|
12028
|
-
const settingsPath =
|
|
12566
|
+
const vscodeDir = path8.join(process.cwd(), ".vscode");
|
|
12567
|
+
const settingsPath = path8.join(vscodeDir, "settings.json");
|
|
12029
12568
|
const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS2[key]);
|
|
12030
12569
|
if (invalidGroups.length > 0) {
|
|
12031
12570
|
console.log(`
|
|
@@ -12034,13 +12573,13 @@ async function applyGroups2(groupKeys, action) {
|
|
|
12034
12573
|
return;
|
|
12035
12574
|
}
|
|
12036
12575
|
if (!existsSync3(vscodeDir)) {
|
|
12037
|
-
await
|
|
12576
|
+
await fs20.mkdir(vscodeDir, { recursive: true });
|
|
12038
12577
|
console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
|
|
12039
12578
|
}
|
|
12040
12579
|
let currentSettings = {};
|
|
12041
12580
|
if (existsSync3(settingsPath)) {
|
|
12042
12581
|
try {
|
|
12043
|
-
const content = await
|
|
12582
|
+
const content = await fs20.readFile(settingsPath, "utf-8");
|
|
12044
12583
|
currentSettings = JSON.parse(content);
|
|
12045
12584
|
console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
|
|
12046
12585
|
} catch {
|
|
@@ -12080,14 +12619,14 @@ async function applyGroups2(groupKeys, action) {
|
|
|
12080
12619
|
}
|
|
12081
12620
|
}
|
|
12082
12621
|
}
|
|
12083
|
-
await
|
|
12622
|
+
await fs20.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
|
|
12084
12623
|
console.log(`
|
|
12085
12624
|
\u2705 \u0110\xE3 c\u1EADp nh\u1EADt c\xE0i \u0111\u1EB7t VSCode t\u1EA1i: ${settingsPath}`);
|
|
12086
12625
|
console.log("\u{1F4A1} M\u1EB9o: Kh\u1EDFi \u0111\u1ED9ng l\u1EA1i VSCode \u0111\u1EC3 \xE1p d\u1EE5ng c\xE1c thay \u0111\u1ED5i.");
|
|
12087
12626
|
}
|
|
12088
12627
|
async function resetSettings2(groupKeys) {
|
|
12089
|
-
const vscodeDir =
|
|
12090
|
-
const settingsPath =
|
|
12628
|
+
const vscodeDir = path8.join(process.cwd(), ".vscode");
|
|
12629
|
+
const settingsPath = path8.join(vscodeDir, "settings.json");
|
|
12091
12630
|
if (!existsSync3(settingsPath)) {
|
|
12092
12631
|
console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
|
|
12093
12632
|
return;
|
|
@@ -12101,7 +12640,7 @@ async function resetSettings2(groupKeys) {
|
|
|
12101
12640
|
return;
|
|
12102
12641
|
}
|
|
12103
12642
|
if (groupKeys.length === 0) {
|
|
12104
|
-
await
|
|
12643
|
+
await fs20.unlink(settingsPath);
|
|
12105
12644
|
console.log("\n\u2705 \u0110\xE3 x\xF3a file settings.json");
|
|
12106
12645
|
} else {
|
|
12107
12646
|
await applyGroups2(groupKeys, "disable");
|