@jvittechs/jai1-cli 0.1.95 → 0.1.97
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 +684 -122
- 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/node_modules/.bin/0ecdsa-generate-keypair +15 -0
- package/dist/web-chat/node_modules/.bin/0gentesthtml +89 -0
- package/dist/web-chat/node_modules/.bin/0serve +97 -0
- package/dist/web-chat/node_modules/.bin/acorn +4 -0
- package/dist/web-chat/node_modules/.bin/autoprefixer +22 -0
- package/dist/web-chat/node_modules/.bin/baseline-browser-mapping +2 -0
- package/dist/web-chat/node_modules/.bin/browserslist +156 -0
- package/dist/web-chat/node_modules/.bin/esbuild +0 -0
- package/dist/web-chat/node_modules/.bin/jiti +34 -0
- package/dist/web-chat/node_modules/.bin/js-yaml +126 -0
- package/dist/web-chat/node_modules/.bin/jsesc +148 -0
- package/dist/web-chat/node_modules/.bin/json5 +152 -0
- package/dist/web-chat/node_modules/.bin/katex +112 -0
- package/dist/web-chat/node_modules/.bin/loose-envify +16 -0
- package/dist/web-chat/node_modules/.bin/lz-string +13 -0
- package/dist/web-chat/node_modules/.bin/nanoid +55 -0
- package/dist/web-chat/node_modules/.bin/parser +15 -0
- package/dist/web-chat/node_modules/.bin/prebuild-install +78 -0
- package/dist/web-chat/node_modules/.bin/rc +4 -0
- package/dist/web-chat/node_modules/.bin/rollup +1912 -0
- package/dist/web-chat/node_modules/.bin/semver +174 -0
- package/dist/web-chat/node_modules/.bin/tsc +2 -0
- package/dist/web-chat/node_modules/.bin/tsserver +2 -0
- package/dist/web-chat/node_modules/.bin/update-browserslist-db +42 -0
- package/dist/web-chat/node_modules/.bin/uuid +2 -0
- package/dist/web-chat/node_modules/.bin/uvu +35 -0
- package/dist/web-chat/node_modules/.bin/vite +61 -0
- package/dist/web-chat/node_modules/.package-lock.json +15371 -0
- package/dist/web-chat/node_modules/.vite/deps/_metadata.json +67 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-DC5AMYBS.js +39 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-DC5AMYBS.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-NUMECXU6.js +21628 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-NUMECXU6.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-RLJ2RCJQ.js +1906 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-RLJ2RCJQ.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-S725DACQ.js +928 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-S725DACQ.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-XJVUMEYI.js +8578 -0
- package/dist/web-chat/node_modules/.vite/deps/chunk-XJVUMEYI.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/package.json +3 -0
- package/dist/web-chat/node_modules/.vite/deps/react-dom.js +7 -0
- package/dist/web-chat/node_modules/.vite/deps/react-dom.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/react-dom_client.js +39 -0
- package/dist/web-chat/node_modules/.vite/deps/react-dom_client.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/react-markdown.js +8931 -0
- package/dist/web-chat/node_modules/.vite/deps/react-markdown.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/react.js +6 -0
- package/dist/web-chat/node_modules/.vite/deps/react.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/react_jsx-dev-runtime.js +913 -0
- package/dist/web-chat/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/react_jsx-runtime.js +7 -0
- package/dist/web-chat/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
- package/dist/web-chat/node_modules/.vite/deps/remark-gfm.js +7403 -0
- package/dist/web-chat/node_modules/.vite/deps/remark-gfm.js.map +7 -0
- package/dist/web-chat/node_modules/@codemirror/autocomplete/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/commands/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-cpp/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-css/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-go/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-html/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-java/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-javascript/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-json/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-less/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-markdown/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-python/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-rust/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-sass/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-sql/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-wast/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-xml/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lang-yaml/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/language/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/language-data/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/legacy-modes/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/lint/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/search/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/state/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/theme-one-dark/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@codemirror/view/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/@lezer/html/src/.tern-port +1 -0
- package/dist/web-chat/node_modules/@lezer/php/.tern-port +1 -0
- package/dist/web-chat/node_modules/@lezer/php/test/.tern-port +1 -0
- package/dist/web-chat/node_modules/@lezer/sass/src/.tern-port +1 -0
- package/dist/web-chat/node_modules/@lezer/yaml/.tern-port +1 -0
- package/dist/web-chat/node_modules/@ungap/structured-clone/.github/workflows/node.js.yml +31 -0
- package/dist/web-chat/node_modules/bl/.travis.yml +17 -0
- package/dist/web-chat/node_modules/clean-set/.prettierrc +4 -0
- package/dist/web-chat/node_modules/clean-set/.travis.yml +7 -0
- package/dist/web-chat/node_modules/codemirror/.github/workflows/dispatch.yml +16 -0
- package/dist/web-chat/node_modules/entities/src/generated/.eslintrc.json +10 -0
- package/dist/web-chat/node_modules/es5-ext/promise/.eslintrc.json +1 -0
- package/dist/web-chat/node_modules/es6-iterator/.editorconfig +14 -0
- package/dist/web-chat/node_modules/es6-iterator/test/.eslintrc.json +5 -0
- package/dist/web-chat/node_modules/es6-symbol/.testignore +1 -0
- package/dist/web-chat/node_modules/escape-carriage/.github/workflows/node.js.yml +19 -0
- package/dist/web-chat/node_modules/esniff/.prettierignore +2 -0
- package/dist/web-chat/node_modules/esniff/.testignore +3 -0
- package/dist/web-chat/node_modules/event-emitter/.lint +15 -0
- package/dist/web-chat/node_modules/event-emitter/.testignore +1 -0
- package/dist/web-chat/node_modules/event-emitter/.travis.yml +16 -0
- package/dist/web-chat/node_modules/expand-template/.travis.yml +6 -0
- package/dist/web-chat/node_modules/extend/.editorconfig +20 -0
- package/dist/web-chat/node_modules/extend/.eslintrc +17 -0
- package/dist/web-chat/node_modules/extend/.jscs.json +175 -0
- package/dist/web-chat/node_modules/extend/.travis.yml +230 -0
- package/dist/web-chat/node_modules/gensync/test/.babelrc +5 -0
- package/dist/web-chat/node_modules/github-from-package/.travis.yml +4 -0
- package/dist/web-chat/node_modules/intersection-observer/.eslintrc +45 -0
- package/dist/web-chat/node_modules/lib0/.github/workflows/node.js.yml +24 -0
- package/dist/web-chat/node_modules/lib0/.jsdoc.json +18 -0
- package/dist/web-chat/node_modules/lib0/.vscode/launch.json +17 -0
- package/dist/web-chat/node_modules/minimist/.eslintrc +29 -0
- package/dist/web-chat/node_modules/minimist/.github/FUNDING.yml +12 -0
- package/dist/web-chat/node_modules/minimist/.nycrc +14 -0
- package/dist/web-chat/node_modules/napi-build-utils/.github/workflows/run-npm-tests.yml +31 -0
- package/dist/web-chat/node_modules/next-tick/.editorconfig +16 -0
- package/dist/web-chat/node_modules/next-tick/.github/FUNDING.yml +1 -0
- package/dist/web-chat/node_modules/next-tick/.lint +16 -0
- package/dist/web-chat/node_modules/node-abi/node_modules/.bin/semver +191 -0
- package/dist/web-chat/node_modules/pump/.github/FUNDING.yml +2 -0
- package/dist/web-chat/node_modules/pump/.travis.yml +5 -0
- package/dist/web-chat/node_modules/simple-concat/.travis.yml +3 -0
- package/dist/web-chat/node_modules/simple-get/.github/dependabot.yml +15 -0
- package/dist/web-chat/node_modules/simple-get/.github/workflows/ci.yml +23 -0
- package/dist/web-chat/node_modules/tar-fs/.travis.yml +6 -0
- package/dist/web-chat/node_modules/tar-fs/test/fixtures/e/directory/.ignore +0 -0
- package/dist/web-chat/node_modules/w3c-keyname/.tern-port +1 -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.97",
|
|
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) {
|
|
@@ -1000,7 +1005,14 @@ var MigrateIdeService = class {
|
|
|
1000
1005
|
const stat = await fs4.stat(filepath);
|
|
1001
1006
|
if (!stat.isFile()) continue;
|
|
1002
1007
|
const content = await fs4.readFile(filepath, "utf-8");
|
|
1003
|
-
|
|
1008
|
+
let frontmatter = {};
|
|
1009
|
+
try {
|
|
1010
|
+
const { data } = matter(content);
|
|
1011
|
+
frontmatter = data;
|
|
1012
|
+
} catch (e) {
|
|
1013
|
+
console.warn(`\u26A0\uFE0F Skipping ${file}: Invalid YAML frontmatter`);
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
1004
1016
|
const name = path.basename(file, ".md");
|
|
1005
1017
|
const trigger = this.extractTrigger(frontmatter);
|
|
1006
1018
|
const alwaysApply = this.isAlwaysTrigger(trigger);
|
|
@@ -1105,21 +1117,24 @@ ${reference}
|
|
|
1105
1117
|
*/
|
|
1106
1118
|
getTargetPath(config, item) {
|
|
1107
1119
|
let contentPath = null;
|
|
1120
|
+
let extension = config.fileExtension;
|
|
1108
1121
|
switch (item.type) {
|
|
1109
1122
|
case "rules":
|
|
1110
1123
|
contentPath = config.rulesPath;
|
|
1111
1124
|
break;
|
|
1112
1125
|
case "workflows":
|
|
1113
1126
|
contentPath = config.workflowsPath ?? config.commandsPath;
|
|
1127
|
+
extension = config.workflowsExtension ?? config.fileExtension;
|
|
1114
1128
|
break;
|
|
1115
1129
|
case "commands":
|
|
1116
1130
|
contentPath = config.commandsPath;
|
|
1131
|
+
extension = config.workflowsExtension ?? config.fileExtension;
|
|
1117
1132
|
break;
|
|
1118
1133
|
}
|
|
1119
1134
|
if (!contentPath) {
|
|
1120
1135
|
return null;
|
|
1121
1136
|
}
|
|
1122
|
-
const filename = `${item.name}${
|
|
1137
|
+
const filename = `${item.name}${extension}`;
|
|
1123
1138
|
return path.join(this.projectPath, config.basePath, contentPath, filename);
|
|
1124
1139
|
}
|
|
1125
1140
|
// Helper methods
|
|
@@ -2349,7 +2364,15 @@ var ContextScannerService = class {
|
|
|
2349
2364
|
async parseContextItem(filepath, ide, type) {
|
|
2350
2365
|
const content = await fs5.readFile(filepath, "utf-8");
|
|
2351
2366
|
const stat = await fs5.stat(filepath);
|
|
2352
|
-
|
|
2367
|
+
let frontmatter = {};
|
|
2368
|
+
let bodyContent = content;
|
|
2369
|
+
try {
|
|
2370
|
+
const parsed = matter2(content);
|
|
2371
|
+
frontmatter = parsed.data;
|
|
2372
|
+
bodyContent = parsed.content;
|
|
2373
|
+
} catch (e) {
|
|
2374
|
+
console.warn(`\u26A0\uFE0F Invalid YAML frontmatter in ${filepath}, skipping frontmatter parsing`);
|
|
2375
|
+
}
|
|
2353
2376
|
const config = IDE_CONFIGS[ide];
|
|
2354
2377
|
const description = frontmatter[config.frontmatterSchema.descriptionField];
|
|
2355
2378
|
let globs;
|
|
@@ -3366,9 +3389,9 @@ var IdeDetectionService = class {
|
|
|
3366
3389
|
/**
|
|
3367
3390
|
* Check if a path exists
|
|
3368
3391
|
*/
|
|
3369
|
-
async pathExists(
|
|
3392
|
+
async pathExists(path9) {
|
|
3370
3393
|
try {
|
|
3371
|
-
await fs7.access(
|
|
3394
|
+
await fs7.access(path9);
|
|
3372
3395
|
return true;
|
|
3373
3396
|
} catch {
|
|
3374
3397
|
return false;
|
|
@@ -4563,15 +4586,19 @@ var LlmProxyService = class {
|
|
|
4563
4586
|
}
|
|
4564
4587
|
const reader = response.body.getReader();
|
|
4565
4588
|
const decoder = new TextDecoder();
|
|
4589
|
+
let buffer = "";
|
|
4566
4590
|
try {
|
|
4567
4591
|
while (true) {
|
|
4568
4592
|
const { done, value } = await reader.read();
|
|
4569
4593
|
if (done) break;
|
|
4570
|
-
|
|
4571
|
-
const lines =
|
|
4594
|
+
buffer += decoder.decode(value, { stream: true });
|
|
4595
|
+
const lines = buffer.split("\n");
|
|
4596
|
+
buffer = lines.pop() || "";
|
|
4572
4597
|
for (const line of lines) {
|
|
4573
|
-
|
|
4574
|
-
|
|
4598
|
+
const trimmedLine = line.trim();
|
|
4599
|
+
if (!trimmedLine) continue;
|
|
4600
|
+
if (trimmedLine.startsWith("data: ")) {
|
|
4601
|
+
const data = trimmedLine.slice(6);
|
|
4575
4602
|
if (data === "[DONE]") {
|
|
4576
4603
|
return;
|
|
4577
4604
|
}
|
|
@@ -4586,6 +4613,22 @@ var LlmProxyService = class {
|
|
|
4586
4613
|
}
|
|
4587
4614
|
}
|
|
4588
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
|
+
}
|
|
4589
4632
|
} finally {
|
|
4590
4633
|
reader.releaseLock();
|
|
4591
4634
|
}
|
|
@@ -5068,8 +5111,469 @@ var ChatApp = ({ service, initialModel }) => {
|
|
|
5068
5111
|
), /* @__PURE__ */ React25.createElement(Box16, { borderStyle: "single", borderColor: "gray", paddingX: 1 }, /* @__PURE__ */ React25.createElement(Text17, { dimColor: true }, footer)));
|
|
5069
5112
|
};
|
|
5070
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
|
+
|
|
5071
5575
|
// src/commands/chat.ts
|
|
5072
|
-
async function
|
|
5576
|
+
async function handleTerminalChat(options) {
|
|
5073
5577
|
const configService = new ConfigService();
|
|
5074
5578
|
const config = await configService.load();
|
|
5075
5579
|
if (!config) {
|
|
@@ -5090,8 +5594,58 @@ async function handleChatCommand(options) {
|
|
|
5090
5594
|
await waitUntilExit();
|
|
5091
5595
|
clear();
|
|
5092
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
|
+
}
|
|
5093
5647
|
function createChatCommand() {
|
|
5094
|
-
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) => {
|
|
5095
5649
|
await handleChatCommand(options);
|
|
5096
5650
|
});
|
|
5097
5651
|
return cmd;
|
|
@@ -5239,8 +5793,8 @@ function createStatsCommand() {
|
|
|
5239
5793
|
import { Command as Command15 } from "commander";
|
|
5240
5794
|
|
|
5241
5795
|
// src/services/translation.service.ts
|
|
5242
|
-
import { promises as
|
|
5243
|
-
import
|
|
5796
|
+
import { promises as fs9 } from "fs";
|
|
5797
|
+
import path5 from "path";
|
|
5244
5798
|
import pLimit from "p-limit";
|
|
5245
5799
|
import pRetry from "p-retry";
|
|
5246
5800
|
var TRANSLATABLE_EXTS = [".md", ".txt", ".json", ".yaml", ".yml"];
|
|
@@ -5258,7 +5812,7 @@ var TranslationService = class {
|
|
|
5258
5812
|
*/
|
|
5259
5813
|
async detectInputType(input4) {
|
|
5260
5814
|
try {
|
|
5261
|
-
const stat = await
|
|
5815
|
+
const stat = await fs9.stat(input4);
|
|
5262
5816
|
if (stat.isDirectory()) return "folder";
|
|
5263
5817
|
if (stat.isFile()) return "file";
|
|
5264
5818
|
} catch {
|
|
@@ -5295,13 +5849,13 @@ var TranslationService = class {
|
|
|
5295
5849
|
*/
|
|
5296
5850
|
async translateFile(filePath) {
|
|
5297
5851
|
try {
|
|
5298
|
-
const content = await
|
|
5299
|
-
const ext =
|
|
5852
|
+
const content = await fs9.readFile(filePath, "utf-8");
|
|
5853
|
+
const ext = path5.extname(filePath).toLowerCase();
|
|
5300
5854
|
const fileType = this.getFileType(ext);
|
|
5301
5855
|
const translatedContent = await this.translateWithRetry(content, fileType);
|
|
5302
5856
|
const outputPath = this.generateOutputPath(filePath);
|
|
5303
5857
|
if (!this.options.dryRun) {
|
|
5304
|
-
await
|
|
5858
|
+
await fs9.writeFile(outputPath, translatedContent, "utf-8");
|
|
5305
5859
|
}
|
|
5306
5860
|
return {
|
|
5307
5861
|
inputPath: filePath,
|
|
@@ -5356,27 +5910,27 @@ var TranslationService = class {
|
|
|
5356
5910
|
if (this.options.output) {
|
|
5357
5911
|
return this.options.output;
|
|
5358
5912
|
}
|
|
5359
|
-
const ext =
|
|
5360
|
-
const base =
|
|
5361
|
-
const dir =
|
|
5913
|
+
const ext = path5.extname(inputPath);
|
|
5914
|
+
const base = path5.basename(inputPath, ext);
|
|
5915
|
+
const dir = path5.dirname(inputPath);
|
|
5362
5916
|
const lang = this.options.to;
|
|
5363
|
-
return
|
|
5917
|
+
return path5.join(dir, `${base}.${lang}${ext}`);
|
|
5364
5918
|
}
|
|
5365
5919
|
/**
|
|
5366
5920
|
* Discover translatable files in folder recursively
|
|
5367
5921
|
*/
|
|
5368
5922
|
async discoverFiles(folderPath) {
|
|
5369
|
-
const entries = await
|
|
5923
|
+
const entries = await fs9.readdir(folderPath, { withFileTypes: true });
|
|
5370
5924
|
const files = [];
|
|
5371
5925
|
for (const entry of entries) {
|
|
5372
|
-
const fullPath =
|
|
5926
|
+
const fullPath = path5.join(folderPath, entry.name);
|
|
5373
5927
|
if (entry.isDirectory()) {
|
|
5374
5928
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
5375
5929
|
const subFiles = await this.discoverFiles(fullPath);
|
|
5376
5930
|
files.push(...subFiles);
|
|
5377
5931
|
}
|
|
5378
5932
|
} else if (entry.isFile()) {
|
|
5379
|
-
const ext =
|
|
5933
|
+
const ext = path5.extname(entry.name).toLowerCase();
|
|
5380
5934
|
if (TRANSLATABLE_EXTS.includes(ext)) {
|
|
5381
5935
|
if (!this.isTranslatedFile(entry.name)) {
|
|
5382
5936
|
files.push(fullPath);
|
|
@@ -5881,7 +6435,7 @@ import { Command as Command34 } from "commander";
|
|
|
5881
6435
|
import { Command as Command21 } from "commander";
|
|
5882
6436
|
|
|
5883
6437
|
// src/services/utils.service.ts
|
|
5884
|
-
import
|
|
6438
|
+
import crypto2 from "crypto";
|
|
5885
6439
|
import bcrypt from "bcryptjs";
|
|
5886
6440
|
import { readFile } from "fs/promises";
|
|
5887
6441
|
var UtilsService = class {
|
|
@@ -5905,7 +6459,7 @@ var UtilsService = class {
|
|
|
5905
6459
|
if (charset.length === 0) {
|
|
5906
6460
|
throw new Error("At least one character type must be enabled");
|
|
5907
6461
|
}
|
|
5908
|
-
const randomBytes =
|
|
6462
|
+
const randomBytes = crypto2.randomBytes(length);
|
|
5909
6463
|
let password = "";
|
|
5910
6464
|
for (let i = 0; i < length; i++) {
|
|
5911
6465
|
const randomIndex = randomBytes[i] % charset.length;
|
|
@@ -5917,7 +6471,7 @@ var UtilsService = class {
|
|
|
5917
6471
|
* Generate UUID v4
|
|
5918
6472
|
*/
|
|
5919
6473
|
generateUuid(options) {
|
|
5920
|
-
let uuid =
|
|
6474
|
+
let uuid = crypto2.randomUUID();
|
|
5921
6475
|
if (options?.uppercase) {
|
|
5922
6476
|
uuid = uuid.toUpperCase();
|
|
5923
6477
|
}
|
|
@@ -5933,7 +6487,7 @@ var UtilsService = class {
|
|
|
5933
6487
|
if (algorithm === "bcrypt") {
|
|
5934
6488
|
throw new Error("Use hashBcrypt for bcrypt algorithm");
|
|
5935
6489
|
}
|
|
5936
|
-
const hash =
|
|
6490
|
+
const hash = crypto2.createHash(algorithm);
|
|
5937
6491
|
hash.update(input4);
|
|
5938
6492
|
return hash.digest("hex");
|
|
5939
6493
|
}
|
|
@@ -6021,7 +6575,7 @@ var UtilsService = class {
|
|
|
6021
6575
|
const headerB64 = this.base64Encode(JSON.stringify(finalHeader), true);
|
|
6022
6576
|
const payloadB64 = this.base64Encode(JSON.stringify(payload), true);
|
|
6023
6577
|
const signatureInput = `${headerB64}.${payloadB64}`;
|
|
6024
|
-
const signature =
|
|
6578
|
+
const signature = crypto2.createHmac("sha256", secret).update(signatureInput).digest("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
6025
6579
|
return `${headerB64}.${payloadB64}.${signature}`;
|
|
6026
6580
|
}
|
|
6027
6581
|
/**
|
|
@@ -7928,8 +8482,8 @@ var HttpView = () => {
|
|
|
7928
8482
|
import React37, { useState as useState24 } from "react";
|
|
7929
8483
|
import { Box as Box27, Text as Text28, useInput as useInput22 } from "ink";
|
|
7930
8484
|
import TextInput14 from "ink-text-input";
|
|
7931
|
-
import * as
|
|
7932
|
-
import * as
|
|
8485
|
+
import * as fs10 from "fs";
|
|
8486
|
+
import * as path6 from "path";
|
|
7933
8487
|
var MarkdownView = () => {
|
|
7934
8488
|
const [filePath, setFilePath] = useState24("");
|
|
7935
8489
|
const [content, setContent] = useState24("");
|
|
@@ -7949,12 +8503,12 @@ var MarkdownView = () => {
|
|
|
7949
8503
|
}
|
|
7950
8504
|
try {
|
|
7951
8505
|
setError("");
|
|
7952
|
-
const resolvedPath =
|
|
7953
|
-
if (!
|
|
8506
|
+
const resolvedPath = path6.resolve(filePath);
|
|
8507
|
+
if (!fs10.existsSync(resolvedPath)) {
|
|
7954
8508
|
setError(`File not found: ${resolvedPath}`);
|
|
7955
8509
|
return;
|
|
7956
8510
|
}
|
|
7957
|
-
let fileContent =
|
|
8511
|
+
let fileContent = fs10.readFileSync(resolvedPath, "utf-8");
|
|
7958
8512
|
fileContent = fileContent.replace(/```mermaid\n([\s\S]*?)```/g, (match, code) => {
|
|
7959
8513
|
return `
|
|
7960
8514
|
\u{1F3A8} **Mermaid Diagram**
|
|
@@ -8235,8 +8789,8 @@ import { Command as Command35 } from "commander";
|
|
|
8235
8789
|
import { checkbox as checkbox3, confirm as confirm5 } from "@inquirer/prompts";
|
|
8236
8790
|
|
|
8237
8791
|
// src/services/deps.service.ts
|
|
8238
|
-
import { promises as
|
|
8239
|
-
import
|
|
8792
|
+
import { promises as fs11 } from "fs";
|
|
8793
|
+
import path7 from "path";
|
|
8240
8794
|
import { execSync } from "child_process";
|
|
8241
8795
|
import pLimit2 from "p-limit";
|
|
8242
8796
|
var DepsService = class {
|
|
@@ -8245,9 +8799,9 @@ var DepsService = class {
|
|
|
8245
8799
|
* Đọc package.json từ thư mục
|
|
8246
8800
|
*/
|
|
8247
8801
|
async readPackageJson(cwd) {
|
|
8248
|
-
const pkgPath =
|
|
8802
|
+
const pkgPath = path7.join(cwd, "package.json");
|
|
8249
8803
|
try {
|
|
8250
|
-
const content = await
|
|
8804
|
+
const content = await fs11.readFile(pkgPath, "utf-8");
|
|
8251
8805
|
return JSON.parse(content);
|
|
8252
8806
|
} catch (error) {
|
|
8253
8807
|
if (error.code === "ENOENT") {
|
|
@@ -8353,7 +8907,7 @@ var DepsService = class {
|
|
|
8353
8907
|
];
|
|
8354
8908
|
for (const { file, pm } of lockFiles) {
|
|
8355
8909
|
try {
|
|
8356
|
-
await
|
|
8910
|
+
await fs11.access(path7.join(cwd, file));
|
|
8357
8911
|
return pm;
|
|
8358
8912
|
} catch {
|
|
8359
8913
|
}
|
|
@@ -8637,7 +9191,7 @@ import { Command as Command40 } from "commander";
|
|
|
8637
9191
|
import { Command as Command37 } from "commander";
|
|
8638
9192
|
|
|
8639
9193
|
// src/services/starter-kit.service.ts
|
|
8640
|
-
import { promises as
|
|
9194
|
+
import { promises as fs12 } from "fs";
|
|
8641
9195
|
import { join as join5 } from "path";
|
|
8642
9196
|
import AdmZip from "adm-zip";
|
|
8643
9197
|
var StarterKitService = class {
|
|
@@ -8686,16 +9240,16 @@ var StarterKitService = class {
|
|
|
8686
9240
|
}
|
|
8687
9241
|
if (onProgress) onProgress(30);
|
|
8688
9242
|
const tmpDir = join5(process.env.TMPDIR || "/tmp", "jai1-kits");
|
|
8689
|
-
await
|
|
9243
|
+
await fs12.mkdir(tmpDir, { recursive: true });
|
|
8690
9244
|
const tmpFile = join5(tmpDir, `${slug}.zip`);
|
|
8691
9245
|
const buffer = await response.arrayBuffer();
|
|
8692
|
-
await
|
|
9246
|
+
await fs12.writeFile(tmpFile, Buffer.from(buffer));
|
|
8693
9247
|
if (onProgress) onProgress(60);
|
|
8694
9248
|
const zip = new AdmZip(tmpFile);
|
|
8695
|
-
await
|
|
9249
|
+
await fs12.mkdir(targetDir, { recursive: true });
|
|
8696
9250
|
zip.extractAllTo(targetDir, true);
|
|
8697
9251
|
if (onProgress) onProgress(100);
|
|
8698
|
-
await
|
|
9252
|
+
await fs12.unlink(tmpFile);
|
|
8699
9253
|
}
|
|
8700
9254
|
};
|
|
8701
9255
|
|
|
@@ -8790,7 +9344,7 @@ Post-Init Commands:`);
|
|
|
8790
9344
|
|
|
8791
9345
|
// src/commands/kit/create.ts
|
|
8792
9346
|
import { Command as Command39 } from "commander";
|
|
8793
|
-
import { promises as
|
|
9347
|
+
import { promises as fs13 } from "fs";
|
|
8794
9348
|
import { join as join6 } from "path";
|
|
8795
9349
|
import { select as select2, input, checkbox as checkbox4 } from "@inquirer/prompts";
|
|
8796
9350
|
import { execa as execa2 } from "execa";
|
|
@@ -8856,7 +9410,7 @@ function createKitCreateCommand() {
|
|
|
8856
9410
|
}
|
|
8857
9411
|
const targetDir = directory || join6(process.cwd(), options.name || slug);
|
|
8858
9412
|
try {
|
|
8859
|
-
await
|
|
9413
|
+
await fs13.access(targetDir);
|
|
8860
9414
|
throw new Error(`Directory already exists: ${targetDir}`);
|
|
8861
9415
|
} catch (error) {
|
|
8862
9416
|
if (error.code !== "ENOENT") {
|
|
@@ -8974,7 +9528,7 @@ function createKitCreateCommand() {
|
|
|
8974
9528
|
async function applyVariableSubstitution(dir, variables) {
|
|
8975
9529
|
const files = await getAllFiles(dir);
|
|
8976
9530
|
for (const file of files) {
|
|
8977
|
-
let content = await
|
|
9531
|
+
let content = await fs13.readFile(file, "utf-8");
|
|
8978
9532
|
let modified = false;
|
|
8979
9533
|
for (const [key, value] of Object.entries(variables)) {
|
|
8980
9534
|
const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
|
|
@@ -8984,13 +9538,13 @@ async function applyVariableSubstitution(dir, variables) {
|
|
|
8984
9538
|
}
|
|
8985
9539
|
}
|
|
8986
9540
|
if (modified) {
|
|
8987
|
-
await
|
|
9541
|
+
await fs13.writeFile(file, content, "utf-8");
|
|
8988
9542
|
}
|
|
8989
9543
|
}
|
|
8990
9544
|
}
|
|
8991
9545
|
async function getAllFiles(dir) {
|
|
8992
9546
|
const files = [];
|
|
8993
|
-
const entries = await
|
|
9547
|
+
const entries = await fs13.readdir(dir, { withFileTypes: true });
|
|
8994
9548
|
for (const entry of entries) {
|
|
8995
9549
|
const fullPath = join6(dir, entry.name);
|
|
8996
9550
|
if (entry.isDirectory()) {
|
|
@@ -9077,7 +9631,7 @@ function createRulesListCommand() {
|
|
|
9077
9631
|
|
|
9078
9632
|
// src/commands/rules/init.ts
|
|
9079
9633
|
import { Command as Command42 } from "commander";
|
|
9080
|
-
import { promises as
|
|
9634
|
+
import { promises as fs14 } from "fs";
|
|
9081
9635
|
import { join as join7 } from "path";
|
|
9082
9636
|
import { select as select3, confirm as confirm6 } from "@inquirer/prompts";
|
|
9083
9637
|
function createRulesInitCommand() {
|
|
@@ -9163,7 +9717,7 @@ function createRulesInitCommand() {
|
|
|
9163
9717
|
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9164
9718
|
customContext: "09-custom.mdc"
|
|
9165
9719
|
};
|
|
9166
|
-
await
|
|
9720
|
+
await fs14.writeFile("jai1-rules.json", JSON.stringify(projectConfig, null, 2));
|
|
9167
9721
|
console.log("\u2713 Created jai1-rules.json");
|
|
9168
9722
|
console.log("\n\u2705 Preset applied successfully!\n");
|
|
9169
9723
|
console.log("Next steps:");
|
|
@@ -9174,10 +9728,10 @@ function createRulesInitCommand() {
|
|
|
9174
9728
|
}
|
|
9175
9729
|
async function applyCursorFormat(bundle) {
|
|
9176
9730
|
const rulesDir = join7(process.cwd(), ".cursor", "rules");
|
|
9177
|
-
await
|
|
9731
|
+
await fs14.mkdir(rulesDir, { recursive: true });
|
|
9178
9732
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
9179
9733
|
const filePath = join7(rulesDir, filename);
|
|
9180
|
-
await
|
|
9734
|
+
await fs14.writeFile(filePath, content, "utf-8");
|
|
9181
9735
|
console.log(`\u2713 Created .cursor/rules/${filename}`);
|
|
9182
9736
|
}
|
|
9183
9737
|
}
|
|
@@ -9204,13 +9758,13 @@ async function applyAgentsMdFormat(bundle) {
|
|
|
9204
9758
|
}
|
|
9205
9759
|
}
|
|
9206
9760
|
const agentsMd = sections.join("\n");
|
|
9207
|
-
await
|
|
9761
|
+
await fs14.writeFile("AGENTS.md", agentsMd, "utf-8");
|
|
9208
9762
|
console.log("\u2713 Created AGENTS.md");
|
|
9209
9763
|
}
|
|
9210
9764
|
|
|
9211
9765
|
// src/commands/rules/apply.ts
|
|
9212
9766
|
import { Command as Command43 } from "commander";
|
|
9213
|
-
import { promises as
|
|
9767
|
+
import { promises as fs16 } from "fs";
|
|
9214
9768
|
import { join as join9 } from "path";
|
|
9215
9769
|
import { select as select4, confirm as confirm7, checkbox as checkbox5 } from "@inquirer/prompts";
|
|
9216
9770
|
|
|
@@ -9571,7 +10125,7 @@ Follow all instructions and patterns defined in AGENTS.md above.
|
|
|
9571
10125
|
};
|
|
9572
10126
|
|
|
9573
10127
|
// src/services/backup.service.ts
|
|
9574
|
-
import { promises as
|
|
10128
|
+
import { promises as fs15 } from "fs";
|
|
9575
10129
|
import { join as join8, dirname } from "path";
|
|
9576
10130
|
var BackupService = class {
|
|
9577
10131
|
backupDir = ".jai1/backups";
|
|
@@ -9602,16 +10156,16 @@ var BackupService = class {
|
|
|
9602
10156
|
await this.backupSingleFile("GEMINI.md", backupPath, ideId, backedUpFiles);
|
|
9603
10157
|
hasContent = true;
|
|
9604
10158
|
} else {
|
|
9605
|
-
const stats = await
|
|
10159
|
+
const stats = await fs15.stat(rulesPath);
|
|
9606
10160
|
if (stats.isDirectory()) {
|
|
9607
|
-
const files = await
|
|
10161
|
+
const files = await fs15.readdir(rulesPath);
|
|
9608
10162
|
for (const file of files) {
|
|
9609
10163
|
if (file.endsWith(format.fileExtension)) {
|
|
9610
10164
|
const originalPath = join8(rulesPath, file);
|
|
9611
10165
|
const relativePath = join8(format.rulesPath, file);
|
|
9612
10166
|
const destPath = join8(backupPath, ideId, file);
|
|
9613
|
-
await
|
|
9614
|
-
await
|
|
10167
|
+
await fs15.mkdir(dirname(destPath), { recursive: true });
|
|
10168
|
+
await fs15.copyFile(originalPath, destPath);
|
|
9615
10169
|
backedUpFiles.push({
|
|
9616
10170
|
originalPath: relativePath,
|
|
9617
10171
|
backupPath: join8(ideId, file),
|
|
@@ -9635,8 +10189,8 @@ var BackupService = class {
|
|
|
9635
10189
|
ides,
|
|
9636
10190
|
files: backedUpFiles
|
|
9637
10191
|
};
|
|
9638
|
-
await
|
|
9639
|
-
await
|
|
10192
|
+
await fs15.mkdir(backupPath, { recursive: true });
|
|
10193
|
+
await fs15.writeFile(
|
|
9640
10194
|
join8(backupPath, "metadata.json"),
|
|
9641
10195
|
JSON.stringify(metadata, null, 2),
|
|
9642
10196
|
"utf-8"
|
|
@@ -9654,8 +10208,8 @@ var BackupService = class {
|
|
|
9654
10208
|
return;
|
|
9655
10209
|
}
|
|
9656
10210
|
const destPath = join8(backupPath, ideId, filename);
|
|
9657
|
-
await
|
|
9658
|
-
await
|
|
10211
|
+
await fs15.mkdir(dirname(destPath), { recursive: true });
|
|
10212
|
+
await fs15.copyFile(originalPath, destPath);
|
|
9659
10213
|
backedUpFiles.push({
|
|
9660
10214
|
originalPath: filename,
|
|
9661
10215
|
backupPath: join8(ideId, filename),
|
|
@@ -9669,15 +10223,15 @@ var BackupService = class {
|
|
|
9669
10223
|
*/
|
|
9670
10224
|
async restoreBackup(backupPath) {
|
|
9671
10225
|
const metadataPath = join8(backupPath, "metadata.json");
|
|
9672
|
-
const metadataContent = await
|
|
10226
|
+
const metadataContent = await fs15.readFile(metadataPath, "utf-8");
|
|
9673
10227
|
const metadata = JSON.parse(metadataContent);
|
|
9674
10228
|
console.log(`
|
|
9675
10229
|
Restoring backup from ${metadata.timestamp}...`);
|
|
9676
10230
|
for (const file of metadata.files) {
|
|
9677
10231
|
const sourcePath = join8(backupPath, file.backupPath);
|
|
9678
10232
|
const destPath = join8(process.cwd(), file.originalPath);
|
|
9679
|
-
await
|
|
9680
|
-
await
|
|
10233
|
+
await fs15.mkdir(dirname(destPath), { recursive: true });
|
|
10234
|
+
await fs15.copyFile(sourcePath, destPath);
|
|
9681
10235
|
console.log(`\u2713 Restored ${file.originalPath}`);
|
|
9682
10236
|
}
|
|
9683
10237
|
console.log("\n\u2705 Backup restored successfully!");
|
|
@@ -9692,13 +10246,13 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9692
10246
|
if (!exists) {
|
|
9693
10247
|
return [];
|
|
9694
10248
|
}
|
|
9695
|
-
const entries = await
|
|
10249
|
+
const entries = await fs15.readdir(backupDirPath, { withFileTypes: true });
|
|
9696
10250
|
const backups = [];
|
|
9697
10251
|
for (const entry of entries) {
|
|
9698
10252
|
if (entry.isDirectory()) {
|
|
9699
10253
|
const metadataPath = join8(backupDirPath, entry.name, "metadata.json");
|
|
9700
10254
|
try {
|
|
9701
|
-
const metadataContent = await
|
|
10255
|
+
const metadataContent = await fs15.readFile(metadataPath, "utf-8");
|
|
9702
10256
|
const metadata = JSON.parse(metadataContent);
|
|
9703
10257
|
backups.push(metadata);
|
|
9704
10258
|
} catch {
|
|
@@ -9745,9 +10299,9 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9745
10299
|
/**
|
|
9746
10300
|
* Check if a path exists
|
|
9747
10301
|
*/
|
|
9748
|
-
async pathExists(
|
|
10302
|
+
async pathExists(path9) {
|
|
9749
10303
|
try {
|
|
9750
|
-
await
|
|
10304
|
+
await fs15.access(path9);
|
|
9751
10305
|
return true;
|
|
9752
10306
|
} catch {
|
|
9753
10307
|
return false;
|
|
@@ -9756,22 +10310,22 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9756
10310
|
/**
|
|
9757
10311
|
* Recursively delete a directory
|
|
9758
10312
|
*/
|
|
9759
|
-
async deleteDirectory(
|
|
10313
|
+
async deleteDirectory(path9) {
|
|
9760
10314
|
try {
|
|
9761
|
-
const exists = await this.pathExists(
|
|
10315
|
+
const exists = await this.pathExists(path9);
|
|
9762
10316
|
if (!exists) {
|
|
9763
10317
|
return;
|
|
9764
10318
|
}
|
|
9765
|
-
const entries = await
|
|
10319
|
+
const entries = await fs15.readdir(path9, { withFileTypes: true });
|
|
9766
10320
|
for (const entry of entries) {
|
|
9767
|
-
const fullPath = join8(
|
|
10321
|
+
const fullPath = join8(path9, entry.name);
|
|
9768
10322
|
if (entry.isDirectory()) {
|
|
9769
10323
|
await this.deleteDirectory(fullPath);
|
|
9770
10324
|
} else {
|
|
9771
|
-
await
|
|
10325
|
+
await fs15.unlink(fullPath);
|
|
9772
10326
|
}
|
|
9773
10327
|
}
|
|
9774
|
-
await
|
|
10328
|
+
await fs15.rmdir(path9);
|
|
9775
10329
|
} catch (error) {
|
|
9776
10330
|
}
|
|
9777
10331
|
}
|
|
@@ -9786,7 +10340,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
9786
10340
|
*/
|
|
9787
10341
|
async ensureBackupDir() {
|
|
9788
10342
|
const backupDirPath = join8(process.cwd(), this.backupDir);
|
|
9789
|
-
await
|
|
10343
|
+
await fs15.mkdir(backupDirPath, { recursive: true });
|
|
9790
10344
|
}
|
|
9791
10345
|
};
|
|
9792
10346
|
|
|
@@ -9944,19 +10498,19 @@ function createRulesApplyCommand() {
|
|
|
9944
10498
|
console.log("\n\u{1F4DD} Applying preset...\n");
|
|
9945
10499
|
const rulePresetDir = join9(process.cwd(), ".jai1", "rule-preset");
|
|
9946
10500
|
try {
|
|
9947
|
-
await
|
|
10501
|
+
await fs16.rm(rulePresetDir, { recursive: true, force: true });
|
|
9948
10502
|
} catch {
|
|
9949
10503
|
}
|
|
9950
|
-
await
|
|
9951
|
-
await
|
|
10504
|
+
await fs16.mkdir(rulePresetDir, { recursive: true });
|
|
10505
|
+
await fs16.writeFile(
|
|
9952
10506
|
join9(rulePresetDir, "preset.json"),
|
|
9953
10507
|
JSON.stringify(bundle.preset, null, 2),
|
|
9954
10508
|
"utf-8"
|
|
9955
10509
|
);
|
|
9956
10510
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
9957
10511
|
const filePath = join9(rulePresetDir, filename);
|
|
9958
|
-
await
|
|
9959
|
-
await
|
|
10512
|
+
await fs16.mkdir(join9(filePath, ".."), { recursive: true });
|
|
10513
|
+
await fs16.writeFile(filePath, content, "utf-8");
|
|
9960
10514
|
}
|
|
9961
10515
|
console.log(`\u2713 Saved preset to .jai1/rule-preset/`);
|
|
9962
10516
|
const allGeneratedFiles = [];
|
|
@@ -9965,8 +10519,8 @@ function createRulesApplyCommand() {
|
|
|
9965
10519
|
const files = generatorService.generateForIde(bundle, ideId);
|
|
9966
10520
|
for (const file of files) {
|
|
9967
10521
|
const fullPath = join9(process.cwd(), file.path);
|
|
9968
|
-
await
|
|
9969
|
-
await
|
|
10522
|
+
await fs16.mkdir(join9(fullPath, ".."), { recursive: true });
|
|
10523
|
+
await fs16.writeFile(fullPath, file.content, "utf-8");
|
|
9970
10524
|
console.log(`\u2713 [${ideId}] ${file.path}`);
|
|
9971
10525
|
allGeneratedFiles.push({
|
|
9972
10526
|
ide: ideId,
|
|
@@ -9994,7 +10548,7 @@ function createRulesApplyCommand() {
|
|
|
9994
10548
|
};
|
|
9995
10549
|
try {
|
|
9996
10550
|
const existingConfigPath = join9(process.cwd(), "jai1-rules.json");
|
|
9997
|
-
const existingConfigContent = await
|
|
10551
|
+
const existingConfigContent = await fs16.readFile(existingConfigPath, "utf-8");
|
|
9998
10552
|
const existingConfig = JSON.parse(existingConfigContent);
|
|
9999
10553
|
if (existingConfig.backups && existingConfig.backups.length > 0) {
|
|
10000
10554
|
projectConfig.backups = [
|
|
@@ -10005,7 +10559,7 @@ function createRulesApplyCommand() {
|
|
|
10005
10559
|
}
|
|
10006
10560
|
} catch {
|
|
10007
10561
|
}
|
|
10008
|
-
await
|
|
10562
|
+
await fs16.writeFile(
|
|
10009
10563
|
join9(process.cwd(), "jai1-rules.json"),
|
|
10010
10564
|
JSON.stringify(projectConfig, null, 2),
|
|
10011
10565
|
"utf-8"
|
|
@@ -10110,7 +10664,7 @@ function formatTimestamp(timestamp) {
|
|
|
10110
10664
|
|
|
10111
10665
|
// src/commands/rules/sync.ts
|
|
10112
10666
|
import { Command as Command45 } from "commander";
|
|
10113
|
-
import { promises as
|
|
10667
|
+
import { promises as fs17 } from "fs";
|
|
10114
10668
|
import { join as join11 } from "path";
|
|
10115
10669
|
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
10116
10670
|
function createRulesSyncCommand() {
|
|
@@ -10118,7 +10672,7 @@ function createRulesSyncCommand() {
|
|
|
10118
10672
|
const configPath = join11(process.cwd(), "jai1-rules.json");
|
|
10119
10673
|
let projectConfig;
|
|
10120
10674
|
try {
|
|
10121
|
-
const configContent = await
|
|
10675
|
+
const configContent = await fs17.readFile(configPath, "utf-8");
|
|
10122
10676
|
projectConfig = JSON.parse(configContent);
|
|
10123
10677
|
} catch {
|
|
10124
10678
|
throw new ValidationError(
|
|
@@ -10201,11 +10755,11 @@ Detected ${detected.length} active IDE(s):
|
|
|
10201
10755
|
const rulePresetDir = join11(process.cwd(), ".jai1", "rule-preset");
|
|
10202
10756
|
const presetExists = await checkPathExists(rulePresetDir);
|
|
10203
10757
|
if (presetExists) {
|
|
10204
|
-
const files = await
|
|
10758
|
+
const files = await fs17.readdir(rulePresetDir);
|
|
10205
10759
|
for (const file of files) {
|
|
10206
10760
|
if (file.endsWith(".mdc")) {
|
|
10207
10761
|
const filePath = join11(rulePresetDir, file);
|
|
10208
|
-
const content = await
|
|
10762
|
+
const content = await fs17.readFile(filePath, "utf-8");
|
|
10209
10763
|
bundle.files[file] = content;
|
|
10210
10764
|
}
|
|
10211
10765
|
}
|
|
@@ -10225,8 +10779,8 @@ Detected ${detected.length} active IDE(s):
|
|
|
10225
10779
|
const files = generatorService.generateForIde(bundle, ideId);
|
|
10226
10780
|
for (const file of files) {
|
|
10227
10781
|
const fullPath = join11(process.cwd(), file.path);
|
|
10228
|
-
await
|
|
10229
|
-
await
|
|
10782
|
+
await fs17.mkdir(join11(fullPath, ".."), { recursive: true });
|
|
10783
|
+
await fs17.writeFile(fullPath, file.content, "utf-8");
|
|
10230
10784
|
}
|
|
10231
10785
|
console.log(`\u2713 ${format.name} - ${files.length} files regenerated`);
|
|
10232
10786
|
} catch (error) {
|
|
@@ -10235,7 +10789,7 @@ Detected ${detected.length} active IDE(s):
|
|
|
10235
10789
|
}
|
|
10236
10790
|
if (JSON.stringify(projectConfig.ides) !== JSON.stringify(idesToSync)) {
|
|
10237
10791
|
projectConfig.ides = idesToSync;
|
|
10238
|
-
await
|
|
10792
|
+
await fs17.writeFile(
|
|
10239
10793
|
join11(process.cwd(), "jai1-rules.json"),
|
|
10240
10794
|
JSON.stringify(projectConfig, null, 2),
|
|
10241
10795
|
"utf-8"
|
|
@@ -10251,7 +10805,7 @@ Detected ${detected.length} active IDE(s):
|
|
|
10251
10805
|
}
|
|
10252
10806
|
async function checkPathExists(absolutePath) {
|
|
10253
10807
|
try {
|
|
10254
|
-
await
|
|
10808
|
+
await fs17.access(absolutePath);
|
|
10255
10809
|
return true;
|
|
10256
10810
|
} catch {
|
|
10257
10811
|
return false;
|
|
@@ -10260,14 +10814,14 @@ async function checkPathExists(absolutePath) {
|
|
|
10260
10814
|
|
|
10261
10815
|
// src/commands/rules/info.ts
|
|
10262
10816
|
import { Command as Command46 } from "commander";
|
|
10263
|
-
import { promises as
|
|
10817
|
+
import { promises as fs18 } from "fs";
|
|
10264
10818
|
import { join as join12 } from "path";
|
|
10265
10819
|
function createRulesInfoCommand() {
|
|
10266
10820
|
return new Command46("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
|
|
10267
10821
|
const configPath = join12(process.cwd(), "jai1-rules.json");
|
|
10268
10822
|
let projectConfig;
|
|
10269
10823
|
try {
|
|
10270
|
-
const configContent = await
|
|
10824
|
+
const configContent = await fs18.readFile(configPath, "utf-8");
|
|
10271
10825
|
projectConfig = JSON.parse(configContent);
|
|
10272
10826
|
} catch {
|
|
10273
10827
|
throw new ValidationError(
|
|
@@ -10284,9 +10838,9 @@ function createRulesInfoCommand() {
|
|
|
10284
10838
|
let presetMetadata = null;
|
|
10285
10839
|
let presetFiles = [];
|
|
10286
10840
|
try {
|
|
10287
|
-
const presetContent = await
|
|
10841
|
+
const presetContent = await fs18.readFile(presetJsonPath, "utf-8");
|
|
10288
10842
|
presetMetadata = JSON.parse(presetContent);
|
|
10289
|
-
const files = await
|
|
10843
|
+
const files = await fs18.readdir(rulePresetDir);
|
|
10290
10844
|
presetFiles = files.filter((f) => f.endsWith(".mdc"));
|
|
10291
10845
|
} catch {
|
|
10292
10846
|
}
|
|
@@ -10345,9 +10899,9 @@ Available Backups (${projectConfig.backups.length}):`);
|
|
|
10345
10899
|
console.log(' \u2022 "jai1 rules apply" - Apply a different preset (replaces current)');
|
|
10346
10900
|
});
|
|
10347
10901
|
}
|
|
10348
|
-
async function checkPathExists2(
|
|
10902
|
+
async function checkPathExists2(path9) {
|
|
10349
10903
|
try {
|
|
10350
|
-
await
|
|
10904
|
+
await fs18.access(join12(process.cwd(), path9));
|
|
10351
10905
|
return true;
|
|
10352
10906
|
} catch {
|
|
10353
10907
|
return false;
|
|
@@ -10778,8 +11332,8 @@ var RedmineApiClient = class {
|
|
|
10778
11332
|
this.retryConfig = config.defaults.retry;
|
|
10779
11333
|
this.concurrencyLimit = pLimit3(config.defaults.concurrency);
|
|
10780
11334
|
}
|
|
10781
|
-
async request(
|
|
10782
|
-
const url = `${this.baseUrl}${
|
|
11335
|
+
async request(path9, options = {}) {
|
|
11336
|
+
const url = `${this.baseUrl}${path9}`;
|
|
10783
11337
|
const headers = {
|
|
10784
11338
|
"X-Redmine-API-Key": this.apiAccessToken,
|
|
10785
11339
|
"Content-Type": "application/json",
|
|
@@ -10840,8 +11394,8 @@ var RedmineApiClient = class {
|
|
|
10840
11394
|
if (include && include.length > 0) {
|
|
10841
11395
|
params.append("include", include.join(","));
|
|
10842
11396
|
}
|
|
10843
|
-
const
|
|
10844
|
-
return this.request(
|
|
11397
|
+
const path9 = `/issues/${issueId}.json${params.toString() ? `?${params.toString()}` : ""}`;
|
|
11398
|
+
return this.request(path9);
|
|
10845
11399
|
}
|
|
10846
11400
|
async getIssues(projectId, options = {}) {
|
|
10847
11401
|
const params = new URLSearchParams();
|
|
@@ -10861,8 +11415,8 @@ var RedmineApiClient = class {
|
|
|
10861
11415
|
if (options.updatedSince) {
|
|
10862
11416
|
params.append("updated_on", `>=${options.updatedSince}`);
|
|
10863
11417
|
}
|
|
10864
|
-
const
|
|
10865
|
-
return this.request(
|
|
11418
|
+
const path9 = `/issues.json?${params.toString()}`;
|
|
11419
|
+
return this.request(path9);
|
|
10866
11420
|
}
|
|
10867
11421
|
async getAllIssues(projectId, options = {}) {
|
|
10868
11422
|
const pageSize = options.pageSize || 100;
|
|
@@ -11166,7 +11720,15 @@ function parseMarkdownContent(content) {
|
|
|
11166
11720
|
rawContent: content
|
|
11167
11721
|
};
|
|
11168
11722
|
}
|
|
11169
|
-
|
|
11723
|
+
let frontmatter = {};
|
|
11724
|
+
let body = content;
|
|
11725
|
+
try {
|
|
11726
|
+
const parsed = matter3(content);
|
|
11727
|
+
frontmatter = parsed.data;
|
|
11728
|
+
body = parsed.content;
|
|
11729
|
+
} catch (e) {
|
|
11730
|
+
console.warn(`\u26A0\uFE0F Invalid YAML frontmatter, skipping frontmatter parsing`);
|
|
11731
|
+
}
|
|
11170
11732
|
const comments = extractCommentsSection(body);
|
|
11171
11733
|
const mainContent = removeCommentsSection(body);
|
|
11172
11734
|
return {
|
|
@@ -11538,7 +12100,7 @@ async function handleSyncProject(options) {
|
|
|
11538
12100
|
|
|
11539
12101
|
// src/commands/framework/info.ts
|
|
11540
12102
|
import { Command as Command53 } from "commander";
|
|
11541
|
-
import { promises as
|
|
12103
|
+
import { promises as fs19 } from "fs";
|
|
11542
12104
|
import { join as join14 } from "path";
|
|
11543
12105
|
import { homedir as homedir5 } from "os";
|
|
11544
12106
|
function createInfoCommand() {
|
|
@@ -11590,7 +12152,7 @@ function maskKey3(key) {
|
|
|
11590
12152
|
async function getProjectStatus2() {
|
|
11591
12153
|
const projectJai1 = join14(process.cwd(), ".jai1");
|
|
11592
12154
|
try {
|
|
11593
|
-
await
|
|
12155
|
+
await fs19.access(projectJai1);
|
|
11594
12156
|
return { exists: true, version: "Synced" };
|
|
11595
12157
|
} catch {
|
|
11596
12158
|
return { exists: false };
|
|
@@ -11780,8 +12342,8 @@ function createClearBackupsCommand() {
|
|
|
11780
12342
|
// src/commands/vscode/index.ts
|
|
11781
12343
|
import { Command as Command56 } from "commander";
|
|
11782
12344
|
import { checkbox as checkbox7, confirm as confirm14, select as select7 } from "@inquirer/prompts";
|
|
11783
|
-
import
|
|
11784
|
-
import
|
|
12345
|
+
import fs20 from "fs/promises";
|
|
12346
|
+
import path8 from "path";
|
|
11785
12347
|
import { existsSync as existsSync3 } from "fs";
|
|
11786
12348
|
var PERFORMANCE_GROUPS2 = {
|
|
11787
12349
|
telemetry: {
|
|
@@ -12001,8 +12563,8 @@ async function selectGroupsToApply2(action) {
|
|
|
12001
12563
|
}
|
|
12002
12564
|
}
|
|
12003
12565
|
async function applyGroups2(groupKeys, action) {
|
|
12004
|
-
const vscodeDir =
|
|
12005
|
-
const settingsPath =
|
|
12566
|
+
const vscodeDir = path8.join(process.cwd(), ".vscode");
|
|
12567
|
+
const settingsPath = path8.join(vscodeDir, "settings.json");
|
|
12006
12568
|
const invalidGroups = groupKeys.filter((key) => !PERFORMANCE_GROUPS2[key]);
|
|
12007
12569
|
if (invalidGroups.length > 0) {
|
|
12008
12570
|
console.log(`
|
|
@@ -12011,13 +12573,13 @@ async function applyGroups2(groupKeys, action) {
|
|
|
12011
12573
|
return;
|
|
12012
12574
|
}
|
|
12013
12575
|
if (!existsSync3(vscodeDir)) {
|
|
12014
|
-
await
|
|
12576
|
+
await fs20.mkdir(vscodeDir, { recursive: true });
|
|
12015
12577
|
console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
|
|
12016
12578
|
}
|
|
12017
12579
|
let currentSettings = {};
|
|
12018
12580
|
if (existsSync3(settingsPath)) {
|
|
12019
12581
|
try {
|
|
12020
|
-
const content = await
|
|
12582
|
+
const content = await fs20.readFile(settingsPath, "utf-8");
|
|
12021
12583
|
currentSettings = JSON.parse(content);
|
|
12022
12584
|
console.log("\u{1F4C4} \u0110\xE3 \u0111\u1ECDc c\xE0i \u0111\u1EB7t hi\u1EC7n t\u1EA1i t\u1EEB settings.json");
|
|
12023
12585
|
} catch {
|
|
@@ -12057,14 +12619,14 @@ async function applyGroups2(groupKeys, action) {
|
|
|
12057
12619
|
}
|
|
12058
12620
|
}
|
|
12059
12621
|
}
|
|
12060
|
-
await
|
|
12622
|
+
await fs20.writeFile(settingsPath, JSON.stringify(newSettings, null, 2));
|
|
12061
12623
|
console.log(`
|
|
12062
12624
|
\u2705 \u0110\xE3 c\u1EADp nh\u1EADt c\xE0i \u0111\u1EB7t VSCode t\u1EA1i: ${settingsPath}`);
|
|
12063
12625
|
console.log("\u{1F4A1} M\u1EB9o: Kh\u1EDFi \u0111\u1ED9ng l\u1EA1i VSCode \u0111\u1EC3 \xE1p d\u1EE5ng c\xE1c thay \u0111\u1ED5i.");
|
|
12064
12626
|
}
|
|
12065
12627
|
async function resetSettings2(groupKeys) {
|
|
12066
|
-
const vscodeDir =
|
|
12067
|
-
const settingsPath =
|
|
12628
|
+
const vscodeDir = path8.join(process.cwd(), ".vscode");
|
|
12629
|
+
const settingsPath = path8.join(vscodeDir, "settings.json");
|
|
12068
12630
|
if (!existsSync3(settingsPath)) {
|
|
12069
12631
|
console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
|
|
12070
12632
|
return;
|
|
@@ -12078,7 +12640,7 @@ async function resetSettings2(groupKeys) {
|
|
|
12078
12640
|
return;
|
|
12079
12641
|
}
|
|
12080
12642
|
if (groupKeys.length === 0) {
|
|
12081
|
-
await
|
|
12643
|
+
await fs20.unlink(settingsPath);
|
|
12082
12644
|
console.log("\n\u2705 \u0110\xE3 x\xF3a file settings.json");
|
|
12083
12645
|
} else {
|
|
12084
12646
|
await applyGroups2(groupKeys, "disable");
|