@jvittechs/j 1.0.38 → 1.0.40
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/{chunk-KOEMGOAP.js → chunk-ZG3KLHJ4.js} +69 -1
- package/dist/chunk-ZG3KLHJ4.js.map +1 -0
- package/dist/cli.js +730 -491
- package/dist/cli.js.map +1 -1
- package/dist/{summary-O75DZKM2.js → summary-AEJ34P4Q.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-KOEMGOAP.js.map +0 -1
- /package/dist/{summary-O75DZKM2.js.map → summary-AEJ34P4Q.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
STATUS_ICONS,
|
|
14
14
|
TaskService,
|
|
15
15
|
createTaskSummaryCommand
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-ZG3KLHJ4.js";
|
|
17
17
|
|
|
18
18
|
// src/utils/node-version-check.ts
|
|
19
19
|
import chalk from "chalk";
|
|
@@ -49,7 +49,7 @@ function checkNodeVersion() {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// src/cli.ts
|
|
52
|
-
import { Command as
|
|
52
|
+
import { Command as Command87 } from "commander";
|
|
53
53
|
|
|
54
54
|
// src/services/error-log.service.ts
|
|
55
55
|
import { promises as fs } from "fs";
|
|
@@ -149,7 +149,7 @@ import { basename as basename5 } from "path";
|
|
|
149
149
|
// package.json
|
|
150
150
|
var package_default = {
|
|
151
151
|
name: "@jvittechs/j",
|
|
152
|
-
version: "1.0.
|
|
152
|
+
version: "1.0.40",
|
|
153
153
|
description: "A unified CLI tool for JV-IT TECHS developers to manage Jai1 Framework. Supports both `j` and `jai1` commands. Please contact TeamAI for usage instructions.",
|
|
154
154
|
type: "module",
|
|
155
155
|
bin: {
|
|
@@ -10614,18 +10614,204 @@ function createDepsCommand() {
|
|
|
10614
10614
|
return depsCommand;
|
|
10615
10615
|
}
|
|
10616
10616
|
|
|
10617
|
+
// src/commands/hooks/index.ts
|
|
10618
|
+
import { Command as Command47 } from "commander";
|
|
10619
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, unlinkSync, chmodSync, mkdirSync } from "fs";
|
|
10620
|
+
import { join as join9 } from "path";
|
|
10621
|
+
import { execSync as execSync4 } from "child_process";
|
|
10622
|
+
import chalk20 from "chalk";
|
|
10623
|
+
var MARKER_START = "# >>> jai1-hooks";
|
|
10624
|
+
var MARKER_END = "# <<< jai1-hooks";
|
|
10625
|
+
var SHEBANG = "#!/bin/sh";
|
|
10626
|
+
var HOOK_DEFINITIONS = [
|
|
10627
|
+
{
|
|
10628
|
+
hookName: "post-merge",
|
|
10629
|
+
description: "Auto-pull tasks after git pull",
|
|
10630
|
+
script: [
|
|
10631
|
+
"# task sync: pull after merge",
|
|
10632
|
+
"if command -v j &>/dev/null; then j t sync --pull 2>/dev/null",
|
|
10633
|
+
"elif command -v jai1 &>/dev/null; then jai1 t sync --pull 2>/dev/null",
|
|
10634
|
+
"fi || true"
|
|
10635
|
+
].join("\n")
|
|
10636
|
+
},
|
|
10637
|
+
{
|
|
10638
|
+
hookName: "pre-push",
|
|
10639
|
+
description: "Auto-push tasks before git push",
|
|
10640
|
+
script: [
|
|
10641
|
+
"# task sync: push before push",
|
|
10642
|
+
"if command -v j &>/dev/null; then j t sync --push 2>/dev/null",
|
|
10643
|
+
"elif command -v jai1 &>/dev/null; then jai1 t sync --push 2>/dev/null",
|
|
10644
|
+
"fi || true"
|
|
10645
|
+
].join("\n")
|
|
10646
|
+
}
|
|
10647
|
+
];
|
|
10648
|
+
function isGitInstalled() {
|
|
10649
|
+
try {
|
|
10650
|
+
execSync4("git --version", { stdio: ["pipe", "pipe", "pipe"] });
|
|
10651
|
+
return true;
|
|
10652
|
+
} catch {
|
|
10653
|
+
return false;
|
|
10654
|
+
}
|
|
10655
|
+
}
|
|
10656
|
+
function getGitHooksDir() {
|
|
10657
|
+
if (!isGitInstalled()) {
|
|
10658
|
+
throw new Error("Git ch\u01B0a \u0111\u01B0\u1EE3c c\xE0i \u0111\u1EB7t. H\xE3y c\xE0i git tr\u01B0\u1EDBc khi s\u1EED d\u1EE5ng hooks.");
|
|
10659
|
+
}
|
|
10660
|
+
try {
|
|
10661
|
+
const gitDir = execSync4("git rev-parse --git-common-dir", {
|
|
10662
|
+
encoding: "utf-8",
|
|
10663
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
10664
|
+
}).trim();
|
|
10665
|
+
return join9(gitDir, "hooks");
|
|
10666
|
+
} catch {
|
|
10667
|
+
throw new Error("Th\u01B0 m\u1EE5c hi\u1EC7n t\u1EA1i kh\xF4ng ph\u1EA3i git repository. H\xE3y ch\u1EA1y l\u1EC7nh n\xE0y trong m\u1ED9t git repo.");
|
|
10668
|
+
}
|
|
10669
|
+
}
|
|
10670
|
+
function buildSection(script) {
|
|
10671
|
+
return `${MARKER_START}
|
|
10672
|
+
${script}
|
|
10673
|
+
${MARKER_END}`;
|
|
10674
|
+
}
|
|
10675
|
+
function hasJai1Section(content) {
|
|
10676
|
+
return content.includes(MARKER_START);
|
|
10677
|
+
}
|
|
10678
|
+
function removeJai1Section(content) {
|
|
10679
|
+
const lines = content.split("\n");
|
|
10680
|
+
const result = [];
|
|
10681
|
+
let inSection = false;
|
|
10682
|
+
for (const line of lines) {
|
|
10683
|
+
if (line.trim() === MARKER_START) {
|
|
10684
|
+
inSection = true;
|
|
10685
|
+
continue;
|
|
10686
|
+
}
|
|
10687
|
+
if (line.trim() === MARKER_END) {
|
|
10688
|
+
inSection = false;
|
|
10689
|
+
continue;
|
|
10690
|
+
}
|
|
10691
|
+
if (!inSection) {
|
|
10692
|
+
result.push(line);
|
|
10693
|
+
}
|
|
10694
|
+
}
|
|
10695
|
+
return result.join("\n");
|
|
10696
|
+
}
|
|
10697
|
+
function isEffectivelyEmpty(content) {
|
|
10698
|
+
const stripped = content.split("\n").filter((line) => line.trim() !== "" && line.trim() !== SHEBANG).join("");
|
|
10699
|
+
return stripped.length === 0;
|
|
10700
|
+
}
|
|
10701
|
+
function setupHooks() {
|
|
10702
|
+
const hooksDir = getGitHooksDir();
|
|
10703
|
+
if (!existsSync3(hooksDir)) {
|
|
10704
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
10705
|
+
}
|
|
10706
|
+
let installed = 0;
|
|
10707
|
+
let skipped = 0;
|
|
10708
|
+
for (const def of HOOK_DEFINITIONS) {
|
|
10709
|
+
const hookPath = join9(hooksDir, def.hookName);
|
|
10710
|
+
const section = buildSection(def.script);
|
|
10711
|
+
try {
|
|
10712
|
+
if (existsSync3(hookPath)) {
|
|
10713
|
+
const existing = readFileSync2(hookPath, "utf-8");
|
|
10714
|
+
if (hasJai1Section(existing)) {
|
|
10715
|
+
console.log(chalk20.dim(` \u23ED ${def.hookName} \u2014 \u0111\xE3 c\xF3, skip`));
|
|
10716
|
+
skipped++;
|
|
10717
|
+
continue;
|
|
10718
|
+
}
|
|
10719
|
+
const updated = existing.trimEnd() + "\n\n" + section + "\n";
|
|
10720
|
+
writeFileSync(hookPath, updated);
|
|
10721
|
+
} else {
|
|
10722
|
+
writeFileSync(hookPath, `${SHEBANG}
|
|
10723
|
+
|
|
10724
|
+
${section}
|
|
10725
|
+
`);
|
|
10726
|
+
}
|
|
10727
|
+
chmodSync(hookPath, 493);
|
|
10728
|
+
console.log(chalk20.green(` \u2705 ${def.hookName} \u2014 ${def.description}`));
|
|
10729
|
+
installed++;
|
|
10730
|
+
} catch (err) {
|
|
10731
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10732
|
+
if (msg.includes("EACCES") || msg.includes("permission")) {
|
|
10733
|
+
console.log(chalk20.red(` \u274C ${def.hookName} \u2014 kh\xF4ng c\xF3 quy\u1EC1n ghi. Th\u1EED ch\u1EA1y v\u1EDBi sudo.`));
|
|
10734
|
+
} else {
|
|
10735
|
+
console.log(chalk20.red(` \u274C ${def.hookName} \u2014 l\u1ED7i: ${msg}`));
|
|
10736
|
+
}
|
|
10737
|
+
}
|
|
10738
|
+
}
|
|
10739
|
+
console.log("");
|
|
10740
|
+
if (installed > 0) {
|
|
10741
|
+
console.log(chalk20.green(`\u{1F389} \u0110\xE3 c\xE0i ${installed} hook(s).`));
|
|
10742
|
+
console.log(chalk20.dim(" Tasks s\u1EBD t\u1EF1 \u0111\u1ED9ng sync khi b\u1EA1n push/pull."));
|
|
10743
|
+
} else {
|
|
10744
|
+
console.log(chalk20.dim("\u2139\uFE0F T\u1EA5t c\u1EA3 hooks \u0111\xE3 \u0111\u01B0\u1EE3c c\xE0i s\u1EB5n."));
|
|
10745
|
+
}
|
|
10746
|
+
}
|
|
10747
|
+
function removeHooks() {
|
|
10748
|
+
const hooksDir = getGitHooksDir();
|
|
10749
|
+
let removed = 0;
|
|
10750
|
+
for (const def of HOOK_DEFINITIONS) {
|
|
10751
|
+
const hookPath = join9(hooksDir, def.hookName);
|
|
10752
|
+
if (!existsSync3(hookPath)) {
|
|
10753
|
+
continue;
|
|
10754
|
+
}
|
|
10755
|
+
try {
|
|
10756
|
+
const content = readFileSync2(hookPath, "utf-8");
|
|
10757
|
+
if (!hasJai1Section(content)) {
|
|
10758
|
+
continue;
|
|
10759
|
+
}
|
|
10760
|
+
const cleaned = removeJai1Section(content);
|
|
10761
|
+
if (isEffectivelyEmpty(cleaned)) {
|
|
10762
|
+
unlinkSync(hookPath);
|
|
10763
|
+
console.log(chalk20.yellow(` \u{1F5D1} ${def.hookName} \u2014 xo\xE1 file (ch\u1EC9 c\xF3 jai1 hooks)`));
|
|
10764
|
+
} else {
|
|
10765
|
+
writeFileSync(hookPath, cleaned);
|
|
10766
|
+
console.log(chalk20.yellow(` \u2702\uFE0F ${def.hookName} \u2014 g\u1EE1 ph\u1EA7n jai1`));
|
|
10767
|
+
}
|
|
10768
|
+
removed++;
|
|
10769
|
+
} catch (err) {
|
|
10770
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10771
|
+
if (msg.includes("EACCES") || msg.includes("permission")) {
|
|
10772
|
+
console.log(chalk20.red(` \u274C ${def.hookName} \u2014 kh\xF4ng c\xF3 quy\u1EC1n ghi/xo\xE1. Th\u1EED ch\u1EA1y v\u1EDBi sudo.`));
|
|
10773
|
+
} else {
|
|
10774
|
+
console.log(chalk20.red(` \u274C ${def.hookName} \u2014 l\u1ED7i: ${msg}`));
|
|
10775
|
+
}
|
|
10776
|
+
}
|
|
10777
|
+
}
|
|
10778
|
+
console.log("");
|
|
10779
|
+
if (removed > 0) {
|
|
10780
|
+
console.log(chalk20.green(`\u2705 \u0110\xE3 g\u1EE1 ${removed} hook(s).`));
|
|
10781
|
+
} else {
|
|
10782
|
+
console.log(chalk20.dim("\u2139\uFE0F Kh\xF4ng c\xF3 jai1 hooks n\xE0o \u0111\u1EC3 g\u1EE1."));
|
|
10783
|
+
}
|
|
10784
|
+
}
|
|
10785
|
+
function createHooksCommand() {
|
|
10786
|
+
const cmd = new Command47("hooks").description("Qu\u1EA3n l\xFD Git hooks t\xEDch h\u1EE3p cho jai1");
|
|
10787
|
+
cmd.command("setup").description("C\xE0i \u0111\u1EB7t Git hooks (auto task sync on push/pull)").action(() => {
|
|
10788
|
+
console.log(chalk20.bold("\n\u{1F517} C\xE0i \u0111\u1EB7t jai1 Git hooks...\n"));
|
|
10789
|
+
setupHooks();
|
|
10790
|
+
console.log("");
|
|
10791
|
+
});
|
|
10792
|
+
cmd.command("remove").description("G\u1EE1 b\u1ECF jai1 Git hooks").action(() => {
|
|
10793
|
+
console.log(chalk20.bold("\n\u{1F517} G\u1EE1 b\u1ECF jai1 Git hooks...\n"));
|
|
10794
|
+
removeHooks();
|
|
10795
|
+
console.log("");
|
|
10796
|
+
});
|
|
10797
|
+
cmd.action(() => {
|
|
10798
|
+
cmd.help();
|
|
10799
|
+
});
|
|
10800
|
+
return cmd;
|
|
10801
|
+
}
|
|
10802
|
+
|
|
10617
10803
|
// src/commands/tasks/index.ts
|
|
10618
|
-
import { Command as
|
|
10804
|
+
import { Command as Command59 } from "commander";
|
|
10619
10805
|
|
|
10620
10806
|
// src/commands/tasks/add.ts
|
|
10621
|
-
import { Command as
|
|
10622
|
-
import
|
|
10807
|
+
import { Command as Command48 } from "commander";
|
|
10808
|
+
import chalk21 from "chalk";
|
|
10623
10809
|
function createTaskAddCommand() {
|
|
10624
|
-
return new
|
|
10810
|
+
return new Command48("add").description("Add a new task").argument("<title>", "Task title").option("-p, --priority <n>", "Priority: 0=critical, 1=high, 2=medium, 3=low", "2").option("-P, --parent <parent>", "Parent: feature/xxx, plan/xxx, bug/xxx").option("-t, --tags <tags>", "Comma-separated tags").option("-j, --json", "Output JSON").action(async (title, options) => {
|
|
10625
10811
|
const service = new TaskService();
|
|
10626
10812
|
const priority = Number(options.priority ?? 2);
|
|
10627
10813
|
if (priority < 0 || priority > 3) {
|
|
10628
|
-
console.error(
|
|
10814
|
+
console.error(chalk21.red("\u274C Priority must be 0-3"));
|
|
10629
10815
|
process.exit(1);
|
|
10630
10816
|
}
|
|
10631
10817
|
const tags = options.tags ? options.tags.split(",").map((t) => t.trim()) : [];
|
|
@@ -10641,23 +10827,23 @@ function createTaskAddCommand() {
|
|
|
10641
10827
|
}
|
|
10642
10828
|
const icon = PRIORITY_ICONS[task.priority] || "\u{1F7E1}";
|
|
10643
10829
|
const label = PRIORITY_LABELS[task.priority] || "Medium";
|
|
10644
|
-
console.log(
|
|
10645
|
-
console.log(` ${
|
|
10646
|
-
console.log(` ${
|
|
10830
|
+
console.log(chalk21.green(`\u2705 Task added: ${chalk21.bold(task.id)}`));
|
|
10831
|
+
console.log(` ${chalk21.dim("Title:")} ${task.title}`);
|
|
10832
|
+
console.log(` ${chalk21.dim("Priority:")} ${icon} ${label}`);
|
|
10647
10833
|
if (task.parent) {
|
|
10648
|
-
console.log(` ${
|
|
10834
|
+
console.log(` ${chalk21.dim("Parent:")} ${task.parent}`);
|
|
10649
10835
|
}
|
|
10650
10836
|
if (task.tags.length > 0) {
|
|
10651
|
-
console.log(` ${
|
|
10837
|
+
console.log(` ${chalk21.dim("Tags:")} ${task.tags.join(", ")}`);
|
|
10652
10838
|
}
|
|
10653
10839
|
});
|
|
10654
10840
|
}
|
|
10655
10841
|
|
|
10656
10842
|
// src/commands/tasks/list.ts
|
|
10657
|
-
import { Command as
|
|
10658
|
-
import
|
|
10843
|
+
import { Command as Command49 } from "commander";
|
|
10844
|
+
import chalk22 from "chalk";
|
|
10659
10845
|
function createTaskListCommand() {
|
|
10660
|
-
return new
|
|
10846
|
+
return new Command49("list").alias("ls").description("List tasks").option("-s, --status <status>", "Filter by status: todo, in_progress, done, cancelled").option("-P, --parent <parent>", "Filter by parent: feature/xxx, plan/xxx").option("-j, --json", "Output JSON").action(async (options) => {
|
|
10661
10847
|
await handleTaskList(options);
|
|
10662
10848
|
});
|
|
10663
10849
|
}
|
|
@@ -10676,12 +10862,12 @@ async function handleTaskList(options) {
|
|
|
10676
10862
|
return;
|
|
10677
10863
|
}
|
|
10678
10864
|
if (tasks.length === 0) {
|
|
10679
|
-
console.log(
|
|
10865
|
+
console.log(chalk22.dim("No tasks found."));
|
|
10680
10866
|
return;
|
|
10681
10867
|
}
|
|
10682
10868
|
const resolvedIds = new Set(allTasks.filter((t) => t.status === "done" || t.status === "cancelled").map((t) => t.id));
|
|
10683
10869
|
const header = options.parent ? `\u{1F4CB} ${options.parent} (${tasks.length} tasks)` : `\u{1F4CB} All tasks (${tasks.length})`;
|
|
10684
|
-
console.log(
|
|
10870
|
+
console.log(chalk22.bold(header));
|
|
10685
10871
|
console.log();
|
|
10686
10872
|
for (const task of tasks) {
|
|
10687
10873
|
printTaskLine(task, resolvedIds);
|
|
@@ -10691,25 +10877,25 @@ function printTaskLine(task, resolvedIds) {
|
|
|
10691
10877
|
const isBlocked = task.status === "todo" && task.depends_on.length > 0 && !task.depends_on.every((id) => resolvedIds.has(id));
|
|
10692
10878
|
const statusIcon = isBlocked ? BLOCKED_ICON : STATUS_ICONS[task.status] || "\u{1F4CB}";
|
|
10693
10879
|
const priorityIcon = PRIORITY_ICONS[task.priority] || "\u{1F7E1}";
|
|
10694
|
-
let line = ` ${statusIcon} ${
|
|
10880
|
+
let line = ` ${statusIcon} ${chalk22.dim(task.id)} P${task.priority}${priorityIcon} ${task.title}`;
|
|
10695
10881
|
if (task.status === "in_progress" && task.assigned_to) {
|
|
10696
|
-
line +=
|
|
10882
|
+
line += chalk22.cyan(` @${task.assigned_to}`);
|
|
10697
10883
|
}
|
|
10698
10884
|
if (isBlocked) {
|
|
10699
10885
|
const blockedBy = task.depends_on.filter((id) => !resolvedIds.has(id));
|
|
10700
|
-
line +=
|
|
10886
|
+
line += chalk22.red(` (blocked: ${blockedBy.join(", ")})`);
|
|
10701
10887
|
}
|
|
10702
10888
|
if (task.parent) {
|
|
10703
|
-
line +=
|
|
10889
|
+
line += chalk22.dim(` [${task.parent}]`);
|
|
10704
10890
|
}
|
|
10705
10891
|
console.log(line);
|
|
10706
10892
|
}
|
|
10707
10893
|
|
|
10708
10894
|
// src/commands/tasks/ready.ts
|
|
10709
|
-
import { Command as
|
|
10710
|
-
import
|
|
10895
|
+
import { Command as Command50 } from "commander";
|
|
10896
|
+
import chalk23 from "chalk";
|
|
10711
10897
|
function createTaskReadyCommand() {
|
|
10712
|
-
return new
|
|
10898
|
+
return new Command50("ready").description("Show tasks ready to pick (not blocked, not assigned)").option("-P, --parent <parent>", "Filter by parent").option("-j, --json", "Output JSON").action(async (options) => {
|
|
10713
10899
|
const service = new TaskService();
|
|
10714
10900
|
const tasks = await service.getReady(options.parent);
|
|
10715
10901
|
if (options.json) {
|
|
@@ -10717,37 +10903,37 @@ function createTaskReadyCommand() {
|
|
|
10717
10903
|
return;
|
|
10718
10904
|
}
|
|
10719
10905
|
if (tasks.length === 0) {
|
|
10720
|
-
console.log(
|
|
10721
|
-
console.log(
|
|
10906
|
+
console.log(chalk23.dim("No tasks ready to pick."));
|
|
10907
|
+
console.log(chalk23.dim("\u{1F4A1} Check blocked tasks: jai1 t list -s todo"));
|
|
10722
10908
|
return;
|
|
10723
10909
|
}
|
|
10724
|
-
console.log(
|
|
10910
|
+
console.log(chalk23.bold(`\u{1F4CB} Ready to pick (${tasks.length} tasks):`));
|
|
10725
10911
|
console.log();
|
|
10726
10912
|
for (const task of tasks) {
|
|
10727
10913
|
const icon = PRIORITY_ICONS[task.priority] || "\u{1F7E1}";
|
|
10728
|
-
let line = ` P${task.priority}${icon} ${
|
|
10914
|
+
let line = ` P${task.priority}${icon} ${chalk23.dim(task.id)} ${task.title}`;
|
|
10729
10915
|
if (task.parent) {
|
|
10730
|
-
line +=
|
|
10916
|
+
line += chalk23.dim(` [${task.parent}]`);
|
|
10731
10917
|
}
|
|
10732
10918
|
console.log(line);
|
|
10733
10919
|
}
|
|
10734
10920
|
console.log();
|
|
10735
|
-
console.log(
|
|
10921
|
+
console.log(chalk23.dim("\u{1F4A1} Run: jai1 t pick"));
|
|
10736
10922
|
});
|
|
10737
10923
|
}
|
|
10738
10924
|
|
|
10739
10925
|
// src/commands/tasks/update.ts
|
|
10740
|
-
import { Command as
|
|
10741
|
-
import
|
|
10926
|
+
import { Command as Command51 } from "commander";
|
|
10927
|
+
import chalk24 from "chalk";
|
|
10742
10928
|
var VALID_STATUSES = ["todo", "in_progress", "done", "cancelled"];
|
|
10743
10929
|
function createTaskUpdateCommand() {
|
|
10744
|
-
return new
|
|
10930
|
+
return new Command51("update").description("Update task status and/or notes").argument("<id>", "Task ID (e.g. T-001)").option("-s, --status <status>", "New status: todo, in_progress, done, cancelled").option("-n, --notes <notes>", 'Task notes (e.g. "files: a.ts, b.ts")').option("-j, --json", "Output JSON").action(async (id, options) => {
|
|
10745
10931
|
if (!options.status && !options.notes) {
|
|
10746
|
-
console.error(
|
|
10932
|
+
console.error(chalk24.red("\u274C At least one of --status or --notes is required"));
|
|
10747
10933
|
process.exit(1);
|
|
10748
10934
|
}
|
|
10749
10935
|
if (options.status && !VALID_STATUSES.includes(options.status)) {
|
|
10750
|
-
console.error(
|
|
10936
|
+
console.error(chalk24.red(`\u274C Invalid status. Must be: ${VALID_STATUSES.join(", ")}`));
|
|
10751
10937
|
process.exit(1);
|
|
10752
10938
|
}
|
|
10753
10939
|
const service = new TaskService();
|
|
@@ -10757,8 +10943,8 @@ function createTaskUpdateCommand() {
|
|
|
10757
10943
|
if (existingTask) {
|
|
10758
10944
|
const { blocked, blockedBy } = await service.isBlocked(existingTask);
|
|
10759
10945
|
if (blocked) {
|
|
10760
|
-
console.log(
|
|
10761
|
-
console.log(
|
|
10946
|
+
console.log(chalk24.yellow(`\u26A0\uFE0F Task ${id} is blocked by: ${blockedBy.join(", ")}`));
|
|
10947
|
+
console.log(chalk24.yellow(` Dependencies ch\u01B0a done. Ti\u1EBFp t\u1EE5c update...`));
|
|
10762
10948
|
}
|
|
10763
10949
|
}
|
|
10764
10950
|
}
|
|
@@ -10778,24 +10964,24 @@ function createTaskUpdateCommand() {
|
|
|
10778
10964
|
if (options.notes) {
|
|
10779
10965
|
parts.push(`\u{1F4DD} notes updated`);
|
|
10780
10966
|
}
|
|
10781
|
-
console.log(
|
|
10967
|
+
console.log(chalk24.green(`\u2705 ${task.id} \u2192 ${parts.join(" | ")}`));
|
|
10782
10968
|
} catch (error) {
|
|
10783
|
-
console.error(
|
|
10969
|
+
console.error(chalk24.red(`\u274C ${error instanceof Error ? error.message : String(error)}`));
|
|
10784
10970
|
process.exit(1);
|
|
10785
10971
|
}
|
|
10786
10972
|
});
|
|
10787
10973
|
}
|
|
10788
10974
|
|
|
10789
10975
|
// src/commands/tasks/show.ts
|
|
10790
|
-
import { Command as
|
|
10791
|
-
import
|
|
10976
|
+
import { Command as Command52 } from "commander";
|
|
10977
|
+
import chalk25 from "chalk";
|
|
10792
10978
|
function createTaskShowCommand() {
|
|
10793
|
-
return new
|
|
10979
|
+
return new Command52("show").description("Show task detail or all tasks under a parent").argument("<query>", "Task ID (T-001) or parent (feature/xxx)").option("-j, --json", "Output JSON").action(async (query, options) => {
|
|
10794
10980
|
const service = new TaskService();
|
|
10795
10981
|
if (query.startsWith("T-")) {
|
|
10796
10982
|
const task = await service.findById(query);
|
|
10797
10983
|
if (!task) {
|
|
10798
|
-
console.error(
|
|
10984
|
+
console.error(chalk25.red(`\u274C Task ${query} not found`));
|
|
10799
10985
|
process.exit(1);
|
|
10800
10986
|
}
|
|
10801
10987
|
if (options.json) {
|
|
@@ -10806,34 +10992,34 @@ function createTaskShowCommand() {
|
|
|
10806
10992
|
const statusIcon = blocked ? BLOCKED_ICON : STATUS_ICONS[task.status] || "\u{1F4CB}";
|
|
10807
10993
|
const priIcon = PRIORITY_ICONS[task.priority] || "\u{1F7E1}";
|
|
10808
10994
|
const priLabel = PRIORITY_LABELS[task.priority] || "Medium";
|
|
10809
|
-
console.log(
|
|
10995
|
+
console.log(chalk25.bold(`
|
|
10810
10996
|
\u{1F4CC} ${task.id}: ${task.title}
|
|
10811
10997
|
`));
|
|
10812
|
-
console.log(` ${
|
|
10813
|
-
console.log(` ${
|
|
10998
|
+
console.log(` ${chalk25.dim("Status:")} ${statusIcon} ${task.status}${blocked ? chalk25.red(" (BLOCKED)") : ""}`);
|
|
10999
|
+
console.log(` ${chalk25.dim("Priority:")} ${priIcon} P${task.priority} ${priLabel}`);
|
|
10814
11000
|
if (task.parent) {
|
|
10815
|
-
console.log(` ${
|
|
11001
|
+
console.log(` ${chalk25.dim("Parent:")} ${task.parent}`);
|
|
10816
11002
|
}
|
|
10817
11003
|
if (task.assigned_to) {
|
|
10818
|
-
console.log(` ${
|
|
11004
|
+
console.log(` ${chalk25.dim("Assigned:")} @${task.assigned_to} (${task.claimed_at})`);
|
|
10819
11005
|
}
|
|
10820
11006
|
if (task.depends_on.length > 0) {
|
|
10821
|
-
console.log(` ${
|
|
11007
|
+
console.log(` ${chalk25.dim("Depends on:")} ${task.depends_on.join(", ")}`);
|
|
10822
11008
|
if (blocked) {
|
|
10823
|
-
console.log(` ${
|
|
11009
|
+
console.log(` ${chalk25.dim("Blocked by:")} ${chalk25.red(blockedBy.join(", "))}`);
|
|
10824
11010
|
}
|
|
10825
11011
|
}
|
|
10826
11012
|
if (task.tags.length > 0) {
|
|
10827
|
-
console.log(` ${
|
|
11013
|
+
console.log(` ${chalk25.dim("Tags:")} ${task.tags.join(", ")}`);
|
|
10828
11014
|
}
|
|
10829
11015
|
if (task.branch) {
|
|
10830
|
-
console.log(` ${
|
|
11016
|
+
console.log(` ${chalk25.dim("Branch:")} ${task.branch}`);
|
|
10831
11017
|
}
|
|
10832
11018
|
if (task.notes) {
|
|
10833
|
-
console.log(` ${
|
|
11019
|
+
console.log(` ${chalk25.dim("Notes:")} ${task.notes}`);
|
|
10834
11020
|
}
|
|
10835
|
-
console.log(` ${
|
|
10836
|
-
console.log(` ${
|
|
11021
|
+
console.log(` ${chalk25.dim("Created:")} ${task.created}`);
|
|
11022
|
+
console.log(` ${chalk25.dim("Updated:")} ${task.updated}`);
|
|
10837
11023
|
console.log();
|
|
10838
11024
|
} else {
|
|
10839
11025
|
const tasks = await service.filter({ parent: query });
|
|
@@ -10842,10 +11028,10 @@ function createTaskShowCommand() {
|
|
|
10842
11028
|
return;
|
|
10843
11029
|
}
|
|
10844
11030
|
if (tasks.length === 0) {
|
|
10845
|
-
console.log(
|
|
11031
|
+
console.log(chalk25.dim(`No tasks for parent: ${query}`));
|
|
10846
11032
|
return;
|
|
10847
11033
|
}
|
|
10848
|
-
console.log(
|
|
11034
|
+
console.log(chalk25.bold(`
|
|
10849
11035
|
\u{1F4CB} ${query} (${tasks.length} tasks)
|
|
10850
11036
|
`));
|
|
10851
11037
|
const allTasks = await service.readAll();
|
|
@@ -10853,11 +11039,11 @@ function createTaskShowCommand() {
|
|
|
10853
11039
|
for (const task of tasks) {
|
|
10854
11040
|
const isBlocked = task.status === "todo" && task.depends_on.length > 0 && !task.depends_on.every((id) => doneIds.has(id));
|
|
10855
11041
|
const icon = isBlocked ? BLOCKED_ICON : STATUS_ICONS[task.status] || "\u{1F4CB}";
|
|
10856
|
-
let line = ` ${icon} ${
|
|
10857
|
-
if (task.assigned_to) line +=
|
|
11042
|
+
let line = ` ${icon} ${chalk25.dim(task.id)} P${task.priority} ${task.title}`;
|
|
11043
|
+
if (task.assigned_to) line += chalk25.cyan(` @${task.assigned_to}`);
|
|
10858
11044
|
if (isBlocked) {
|
|
10859
11045
|
const bb = task.depends_on.filter((id) => !doneIds.has(id));
|
|
10860
|
-
line +=
|
|
11046
|
+
line += chalk25.red(` (blocked: ${bb.join(", ")})`);
|
|
10861
11047
|
}
|
|
10862
11048
|
console.log(line);
|
|
10863
11049
|
}
|
|
@@ -10867,11 +11053,11 @@ function createTaskShowCommand() {
|
|
|
10867
11053
|
}
|
|
10868
11054
|
|
|
10869
11055
|
// src/commands/tasks/pick.ts
|
|
10870
|
-
import { Command as
|
|
10871
|
-
import
|
|
11056
|
+
import { Command as Command53 } from "commander";
|
|
11057
|
+
import chalk26 from "chalk";
|
|
10872
11058
|
import { confirm as confirm9 } from "@inquirer/prompts";
|
|
10873
11059
|
function createTaskPickCommand() {
|
|
10874
|
-
return new
|
|
11060
|
+
return new Command53("pick").description("Claim the next available task").option("-j, --json", "Output JSON").action(async (options) => {
|
|
10875
11061
|
const service = new TaskService();
|
|
10876
11062
|
const ready = await service.getReady();
|
|
10877
11063
|
if (ready.length === 0) {
|
|
@@ -10879,8 +11065,8 @@ function createTaskPickCommand() {
|
|
|
10879
11065
|
console.log(JSON.stringify({ picked: null, message: "No tasks ready" }));
|
|
10880
11066
|
return;
|
|
10881
11067
|
}
|
|
10882
|
-
console.log(
|
|
10883
|
-
console.log(
|
|
11068
|
+
console.log(chalk26.dim("No tasks ready to pick."));
|
|
11069
|
+
console.log(chalk26.dim('\u{1F4A1} Add tasks first: jai1 t add "..."'));
|
|
10884
11070
|
return;
|
|
10885
11071
|
}
|
|
10886
11072
|
const top = ready[0];
|
|
@@ -10890,13 +11076,13 @@ function createTaskPickCommand() {
|
|
|
10890
11076
|
console.log(JSON.stringify(picked2, null, 2));
|
|
10891
11077
|
return;
|
|
10892
11078
|
}
|
|
10893
|
-
console.log(
|
|
10894
|
-
console.log(` ${
|
|
11079
|
+
console.log(chalk26.bold("\n\u{1F4CC} Next available task:"));
|
|
11080
|
+
console.log(` ${chalk26.bold(top.id)} P${top.priority}${icon} ${top.title}`);
|
|
10895
11081
|
if (top.parent) {
|
|
10896
|
-
console.log(` ${
|
|
11082
|
+
console.log(` ${chalk26.dim("Parent:")} ${top.parent}`);
|
|
10897
11083
|
}
|
|
10898
11084
|
if (ready.length > 1) {
|
|
10899
|
-
console.log(
|
|
11085
|
+
console.log(chalk26.dim(`
|
|
10900
11086
|
+${ready.length - 1} more tasks ready`));
|
|
10901
11087
|
}
|
|
10902
11088
|
const proceed = await confirm9({
|
|
@@ -10904,20 +11090,20 @@ function createTaskPickCommand() {
|
|
|
10904
11090
|
default: true
|
|
10905
11091
|
});
|
|
10906
11092
|
if (!proceed) {
|
|
10907
|
-
console.log(
|
|
11093
|
+
console.log(chalk26.dim("\nCancelled."));
|
|
10908
11094
|
return;
|
|
10909
11095
|
}
|
|
10910
11096
|
const picked = await service.pick(top.id);
|
|
10911
|
-
console.log(
|
|
11097
|
+
console.log(chalk26.green(`
|
|
10912
11098
|
\u2705 ${picked.id} assigned to @${picked.assigned_to}, status \u2192 in_progress`));
|
|
10913
11099
|
});
|
|
10914
11100
|
}
|
|
10915
11101
|
|
|
10916
11102
|
// src/commands/tasks/done.ts
|
|
10917
|
-
import { Command as
|
|
10918
|
-
import
|
|
11103
|
+
import { Command as Command54 } from "commander";
|
|
11104
|
+
import chalk27 from "chalk";
|
|
10919
11105
|
function createTaskDoneCommand() {
|
|
10920
|
-
return new
|
|
11106
|
+
return new Command54("done").description("Mark task as done").argument("<id>", "Task ID (e.g. T-001)").option("-j, --json", "Output JSON").action(async (id, options) => {
|
|
10921
11107
|
const service = new TaskService();
|
|
10922
11108
|
try {
|
|
10923
11109
|
const task = await service.markDone(id);
|
|
@@ -10925,19 +11111,19 @@ function createTaskDoneCommand() {
|
|
|
10925
11111
|
console.log(JSON.stringify(task, null, 2));
|
|
10926
11112
|
return;
|
|
10927
11113
|
}
|
|
10928
|
-
console.log(
|
|
11114
|
+
console.log(chalk27.green(`\u2705 ${task.id}: ${task.title} \u2192 done`));
|
|
10929
11115
|
} catch (error) {
|
|
10930
|
-
console.error(
|
|
11116
|
+
console.error(chalk27.red(`\u274C ${error instanceof Error ? error.message : String(error)}`));
|
|
10931
11117
|
process.exit(1);
|
|
10932
11118
|
}
|
|
10933
11119
|
});
|
|
10934
11120
|
}
|
|
10935
11121
|
|
|
10936
11122
|
// src/commands/tasks/dep.ts
|
|
10937
|
-
import { Command as
|
|
10938
|
-
import
|
|
11123
|
+
import { Command as Command55 } from "commander";
|
|
11124
|
+
import chalk28 from "chalk";
|
|
10939
11125
|
function createTaskDepCommand() {
|
|
10940
|
-
return new
|
|
11126
|
+
return new Command55("dep").description("Add dependency: child depends on parent").argument("<childId>", "Child task ID (the one that waits)").argument("<parentId>", "Parent task ID (must be done first)").option("-j, --json", "Output JSON").action(async (childId, parentId, options) => {
|
|
10941
11127
|
const service = new TaskService();
|
|
10942
11128
|
try {
|
|
10943
11129
|
const task = await service.addDependency(childId, parentId);
|
|
@@ -10945,165 +11131,216 @@ function createTaskDepCommand() {
|
|
|
10945
11131
|
console.log(JSON.stringify(task, null, 2));
|
|
10946
11132
|
return;
|
|
10947
11133
|
}
|
|
10948
|
-
console.log(
|
|
10949
|
-
console.log(
|
|
11134
|
+
console.log(chalk28.green(`\u2705 ${childId} now depends on ${parentId}`));
|
|
11135
|
+
console.log(chalk28.dim(` ${task.title} \u2192 waits for ${parentId}`));
|
|
10950
11136
|
} catch (error) {
|
|
10951
|
-
console.error(
|
|
11137
|
+
console.error(chalk28.red(`\u274C ${error instanceof Error ? error.message : String(error)}`));
|
|
10952
11138
|
process.exit(1);
|
|
10953
11139
|
}
|
|
10954
11140
|
});
|
|
10955
11141
|
}
|
|
10956
11142
|
|
|
10957
11143
|
// src/commands/tasks/sync.ts
|
|
10958
|
-
import { Command as
|
|
10959
|
-
import
|
|
11144
|
+
import { Command as Command56 } from "commander";
|
|
11145
|
+
import chalk29 from "chalk";
|
|
10960
11146
|
function createTaskSyncCommand() {
|
|
10961
|
-
return new
|
|
11147
|
+
return new Command56("sync").description("Sync tasks via git (default: pull \u2192 push)").option("--pull", "Pull only: merge tasks from origin/jai1").option("--push", "Push only: commit and push tasks to origin/jai1").action(async (options) => {
|
|
10962
11148
|
const service = new TaskService();
|
|
10963
11149
|
const branch = service.getSyncBranch();
|
|
10964
11150
|
const doPull = options.pull || !options.pull && !options.push;
|
|
10965
11151
|
const doPush = options.push || !options.pull && !options.push;
|
|
10966
11152
|
if (doPull) {
|
|
10967
|
-
console.log(
|
|
11153
|
+
console.log(chalk29.dim(`\u23F3 Pulling tasks from origin/${branch}...`));
|
|
10968
11154
|
try {
|
|
10969
11155
|
const result = await service.syncPull();
|
|
10970
11156
|
if (result.merged > 0) {
|
|
10971
|
-
console.log(
|
|
11157
|
+
console.log(chalk29.green(` \u2193 ${result.merged} tasks merged`));
|
|
10972
11158
|
} else {
|
|
10973
|
-
console.log(
|
|
11159
|
+
console.log(chalk29.dim(` \u2193 Already up to date`));
|
|
10974
11160
|
}
|
|
10975
11161
|
} catch (error) {
|
|
10976
|
-
console.error(
|
|
11162
|
+
console.error(chalk29.red(`\u274C Pull failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
10977
11163
|
process.exit(1);
|
|
10978
11164
|
}
|
|
10979
11165
|
}
|
|
10980
11166
|
if (doPush) {
|
|
10981
|
-
console.log(
|
|
11167
|
+
console.log(chalk29.dim(`\u23F3 Pushing tasks to origin/${branch}...`));
|
|
10982
11168
|
try {
|
|
10983
11169
|
await service.syncPush();
|
|
10984
|
-
console.log(
|
|
11170
|
+
console.log(chalk29.green(` \u2191 Tasks pushed to origin/${branch}`));
|
|
10985
11171
|
} catch (error) {
|
|
10986
|
-
console.error(
|
|
11172
|
+
console.error(chalk29.red(`\u274C Push failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
10987
11173
|
process.exit(1);
|
|
10988
11174
|
}
|
|
10989
11175
|
}
|
|
10990
|
-
console.log(
|
|
11176
|
+
console.log(chalk29.green("\u2705 Sync complete"));
|
|
10991
11177
|
});
|
|
10992
11178
|
}
|
|
10993
11179
|
|
|
10994
11180
|
// src/commands/tasks/guide.ts
|
|
10995
|
-
import { Command as
|
|
10996
|
-
import
|
|
11181
|
+
import { Command as Command57 } from "commander";
|
|
11182
|
+
import chalk30 from "chalk";
|
|
10997
11183
|
var GUIDE_TEXT = `
|
|
10998
|
-
${
|
|
10999
|
-
|
|
11000
|
-
${
|
|
11001
|
-
${
|
|
11002
|
-
${
|
|
11003
|
-
${
|
|
11004
|
-
${
|
|
11005
|
-
${
|
|
11006
|
-
|
|
11007
|
-
${
|
|
11008
|
-
${
|
|
11009
|
-
${
|
|
11010
|
-
${
|
|
11011
|
-
${
|
|
11012
|
-
|
|
11013
|
-
${
|
|
11014
|
-
${
|
|
11015
|
-
${
|
|
11016
|
-
${
|
|
11017
|
-
${
|
|
11018
|
-
|
|
11019
|
-
${
|
|
11020
|
-
${
|
|
11021
|
-
${
|
|
11022
|
-
${
|
|
11023
|
-
${
|
|
11024
|
-
${
|
|
11025
|
-
${
|
|
11026
|
-
|
|
11027
|
-
${
|
|
11028
|
-
${
|
|
11029
|
-
${
|
|
11030
|
-
|
|
11031
|
-
${
|
|
11032
|
-
${
|
|
11033
|
-
${
|
|
11034
|
-
${
|
|
11035
|
-
|
|
11036
|
-
${
|
|
11037
|
-
${
|
|
11038
|
-
|
|
11039
|
-
${
|
|
11040
|
-
${
|
|
11041
|
-
|
|
11042
|
-
${
|
|
11043
|
-
${
|
|
11044
|
-
|
|
11045
|
-
${
|
|
11046
|
-
${
|
|
11047
|
-
${
|
|
11048
|
-
${
|
|
11049
|
-
|
|
11050
|
-
${
|
|
11051
|
-
${
|
|
11052
|
-
${
|
|
11053
|
-
${
|
|
11054
|
-
|
|
11055
|
-
${
|
|
11056
|
-
${
|
|
11057
|
-
|
|
11058
|
-
${
|
|
11059
|
-
${
|
|
11060
|
-
${
|
|
11061
|
-
|
|
11062
|
-
${
|
|
11063
|
-
${
|
|
11064
|
-
|
|
11065
|
-
${
|
|
11066
|
-
${
|
|
11067
|
-
1. ${
|
|
11068
|
-
2. ${
|
|
11069
|
-
3. ${
|
|
11070
|
-
4. ${
|
|
11071
|
-
|
|
11072
|
-
${
|
|
11073
|
-
1. ${
|
|
11184
|
+
${chalk30.cyan.bold("\u{1F4D6} Jai1 Task Management Guide")}
|
|
11185
|
+
|
|
11186
|
+
${chalk30.bold("\u2501\u2501\u2501 STATUSES \u2501\u2501\u2501")}
|
|
11187
|
+
${chalk30.dim("todo")} \u{1F4CB} Ch\u01B0a b\u1EAFt \u0111\u1EA7u
|
|
11188
|
+
${chalk30.dim("in_progress")} \u{1F535} \u0110ang l\xE0m (bao g\u1ED3m review)
|
|
11189
|
+
${chalk30.dim("done")} \u2705 Ho\xE0n th\xE0nh
|
|
11190
|
+
${chalk30.dim("cancelled")} \u26AB Hu\u1EF7
|
|
11191
|
+
${chalk30.dim("(blocked)")} \u{1F534} Computed: depends_on ch\u01B0a done
|
|
11192
|
+
|
|
11193
|
+
${chalk30.bold("\u2501\u2501\u2501 PRIORITY \u2501\u2501\u2501")}
|
|
11194
|
+
${chalk30.dim("0")} = \u{1F525} Critical \u2014 Prod down, security, block c\u1EA3 team
|
|
11195
|
+
${chalk30.dim("1")} = \u{1F534} High \u2014 Feature ch\xEDnh, deadline g\u1EA7n
|
|
11196
|
+
${chalk30.dim("2")} = \u{1F7E1} Medium \u2014 B\xECnh th\u01B0\u1EDDng (default)
|
|
11197
|
+
${chalk30.dim("3")} = \u{1F7E2} Low \u2014 Nice-to-have, docs, refactor
|
|
11198
|
+
|
|
11199
|
+
${chalk30.bold("\u2501\u2501\u2501 QUICK START \u2501\u2501\u2501")}
|
|
11200
|
+
${chalk30.cyan("jai1 t add")} "Fix login bug" -p 1 -P bug/login
|
|
11201
|
+
${chalk30.cyan("jai1 t ready")} Show tasks s\u1EB5n s\xE0ng
|
|
11202
|
+
${chalk30.cyan("jai1 t pick")} Claim & start working
|
|
11203
|
+
${chalk30.cyan("jai1 t done")} T-003 Mark complete
|
|
11204
|
+
|
|
11205
|
+
${chalk30.bold("\u2501\u2501\u2501 DAILY WORKFLOW \u2501\u2501\u2501")}
|
|
11206
|
+
${chalk30.cyan("jai1 t sync --pull")} Pull latest tasks
|
|
11207
|
+
${chalk30.cyan("jai1 t summary")} Dashboard t\u1ED5ng quan
|
|
11208
|
+
${chalk30.cyan("jai1 t ready")} Xem tasks s\u1EB5n s\xE0ng
|
|
11209
|
+
${chalk30.cyan("jai1 t pick")} Claim task m\u1EDBi
|
|
11210
|
+
${chalk30.cyan("jai1 t done")} T-xxx Ho\xE0n th\xE0nh task
|
|
11211
|
+
${chalk30.cyan("jai1 t sync --push")} Push l\xEAn git
|
|
11212
|
+
|
|
11213
|
+
${chalk30.bold("\u2501\u2501\u2501 ADDING TASKS \u2501\u2501\u2501")}
|
|
11214
|
+
${chalk30.yellow("\u26A0 Lu\xF4n ki\u1EC3m tra duplicate tr\u01B0\u1EDBc khi add:")}
|
|
11215
|
+
${chalk30.cyan("jai1 t list -P")} feature/xxx
|
|
11216
|
+
|
|
11217
|
+
${chalk30.dim("Add cho feature:")}
|
|
11218
|
+
${chalk30.cyan("jai1 t add")} "Setup DB schema" -p 1 -P feature/xxx
|
|
11219
|
+
${chalk30.cyan("jai1 t add")} "Create API" -p 1 -P feature/xxx
|
|
11220
|
+
${chalk30.cyan("jai1 t add")} "Build UI" -p 2 -P feature/xxx
|
|
11221
|
+
|
|
11222
|
+
${chalk30.dim("Add cho plan:")}
|
|
11223
|
+
${chalk30.cyan("jai1 t add")} "Refactor middleware" -p 2 -P plan/xxx
|
|
11224
|
+
|
|
11225
|
+
${chalk30.dim("Add standalone:")}
|
|
11226
|
+
${chalk30.cyan("jai1 t add")} "Fix README typo" -p 3
|
|
11227
|
+
|
|
11228
|
+
${chalk30.dim("Add bug fix:")}
|
|
11229
|
+
${chalk30.cyan("jai1 t add")} "Fix login redirect" -p 1 -P bug/xxx
|
|
11230
|
+
|
|
11231
|
+
${chalk30.bold("\u2501\u2501\u2501 DEPENDENCY \u2501\u2501\u2501")}
|
|
11232
|
+
${chalk30.dim("Task dependency:")}
|
|
11233
|
+
${chalk30.cyan("jai1 t dep")} T-002 T-001 T-002 ch\u1EDD T-001 done
|
|
11234
|
+
${chalk30.cyan("jai1 t dep")} T-003 T-002 T-003 ch\u1EDD T-002 done
|
|
11235
|
+
|
|
11236
|
+
${chalk30.dim("Feature-level dependency:")}
|
|
11237
|
+
${chalk30.dim("# N\u1EBFu feature/auth ph\u1EE5 thu\u1ED9c feature/user-model:")}
|
|
11238
|
+
${chalk30.cyan("jai1 t add")} "[DEP] Wait for feature/user-model" -p 1 -P feature/auth
|
|
11239
|
+
${chalk30.dim("# R\u1ED3i dep n\xF3 v\u1EDBi tasks cu\u1ED1i c\u1EE7a user-model")}
|
|
11240
|
+
|
|
11241
|
+
${chalk30.dim("View deps:")}
|
|
11242
|
+
${chalk30.cyan("jai1 t show")} T-002 Hi\u1EC7n depends_on
|
|
11243
|
+
|
|
11244
|
+
${chalk30.bold("\u2501\u2501\u2501 TEAM COLLABORATION \u2501\u2501\u2501")}
|
|
11245
|
+
${chalk30.yellow("\u26A0 Assignment ch\u1EC9 qua pick \u2014 kh\xF4ng set th\u1EE7 c\xF4ng.")}
|
|
11246
|
+
${chalk30.dim("Khi b\u1EA1n pick \u2192 team th\u1EA5y task \u0111\xE3 c\xF3 ng\u01B0\u1EDDi nh\u1EADn.")}
|
|
11247
|
+
|
|
11248
|
+
${chalk30.dim("Sync morning:")} ${chalk30.cyan("jai1 t sync --pull")}
|
|
11249
|
+
${chalk30.dim("Sync evening:")} ${chalk30.cyan("jai1 t sync --push")}
|
|
11250
|
+
|
|
11251
|
+
${chalk30.bold("\u2501\u2501\u2501 FOR AI AGENTS (Workflow Integration) \u2501\u2501\u2501")}
|
|
11252
|
+
${chalk30.dim("Khi t\u1EA1o tasks t\u1EEB feature/plan:")}
|
|
11253
|
+
1. ${chalk30.cyan("jai1 t list -P")} <parent> Check existing (tr\xE1nh duplicate)
|
|
11254
|
+
2. ${chalk30.cyan("jai1 t add")} "..." -p <0-3> -P <parent> Add t\u1EEBng task
|
|
11255
|
+
3. ${chalk30.cyan("jai1 t dep")} <child> <parent> Set dependencies
|
|
11256
|
+
4. ${chalk30.cyan("jai1 t done")} <id> Mark complete
|
|
11257
|
+
|
|
11258
|
+
${chalk30.dim("Khi implement task ti\u1EBFp theo:")}
|
|
11259
|
+
1. ${chalk30.cyan("jai1 t pick")} (ho\u1EB7c ${chalk30.cyan("jai1 t ready -P")} <parent>)
|
|
11074
11260
|
2. Implement task
|
|
11075
|
-
3. ${
|
|
11261
|
+
3. ${chalk30.cyan("jai1 t done")} <id>
|
|
11076
11262
|
|
|
11077
|
-
${
|
|
11263
|
+
${chalk30.dim("Status transitions:")}
|
|
11078
11264
|
add \u2192 todo (default)
|
|
11079
11265
|
pick \u2192 in_progress (auto assign)
|
|
11080
11266
|
done \u2192 done
|
|
11081
11267
|
update -s \u2192 any valid status
|
|
11082
11268
|
|
|
11083
|
-
${
|
|
11084
|
-
${
|
|
11085
|
-
${
|
|
11086
|
-
${
|
|
11087
|
-
${
|
|
11088
|
-
${
|
|
11089
|
-
${
|
|
11090
|
-
${
|
|
11091
|
-
${
|
|
11092
|
-
${
|
|
11093
|
-
${
|
|
11094
|
-
${
|
|
11095
|
-
|
|
11096
|
-
|
|
11269
|
+
${chalk30.bold("\u2501\u2501\u2501 ALL COMMANDS \u2501\u2501\u2501")}
|
|
11270
|
+
${chalk30.cyan("jai1 t list")} [-s status] [-P parent] [-j]
|
|
11271
|
+
${chalk30.cyan("jai1 t ready")} [-P parent] [-j]
|
|
11272
|
+
${chalk30.cyan("jai1 t add")} <title> [-p 0-3] [-P parent] [-t tags] [-j]
|
|
11273
|
+
${chalk30.cyan("jai1 t update")} <id> [-s <status>] [-n <notes>] [-j]
|
|
11274
|
+
${chalk30.cyan("jai1 t show")} <id|parent> [-j]
|
|
11275
|
+
${chalk30.cyan("jai1 t pick")} [-j]
|
|
11276
|
+
${chalk30.cyan("jai1 t done")} <id> [-j]
|
|
11277
|
+
${chalk30.cyan("jai1 t dep")} <childId> <parentId> [-j]
|
|
11278
|
+
${chalk30.cyan("jai1 t sync")} [--pull] [--push]
|
|
11279
|
+
${chalk30.cyan("jai1 t summary")} [-j]
|
|
11280
|
+
${chalk30.cyan("jai1 t groups")} [-s status] [-j] ${chalk30.dim("(alias: parents)")}
|
|
11281
|
+
${chalk30.cyan("jai1 t guide")}
|
|
11282
|
+
|
|
11283
|
+
${chalk30.dim("-j / --json available on all commands (except guide, sync)")}
|
|
11097
11284
|
`;
|
|
11098
11285
|
function createTaskGuideCommand() {
|
|
11099
|
-
return new
|
|
11286
|
+
return new Command57("guide").description("Show full task management guide").action(() => {
|
|
11100
11287
|
console.log(GUIDE_TEXT);
|
|
11101
11288
|
});
|
|
11102
11289
|
}
|
|
11103
11290
|
|
|
11291
|
+
// src/commands/tasks/parents.ts
|
|
11292
|
+
import { Command as Command58 } from "commander";
|
|
11293
|
+
import chalk31 from "chalk";
|
|
11294
|
+
var PARENT_STATUS_ICONS = {
|
|
11295
|
+
done: "\u2705",
|
|
11296
|
+
in_progress: "\u{1F535}",
|
|
11297
|
+
ready: "\u{1F4CB}",
|
|
11298
|
+
todo: "\u{1F534}"
|
|
11299
|
+
};
|
|
11300
|
+
function formatProgress(p) {
|
|
11301
|
+
const completed = p.done + p.cancelled;
|
|
11302
|
+
if (p.status === "done") {
|
|
11303
|
+
return chalk31.green(`${completed}/${p.total} done`);
|
|
11304
|
+
}
|
|
11305
|
+
const parts = [];
|
|
11306
|
+
if (p.in_progress > 0) parts.push(`${p.in_progress} in_progress`);
|
|
11307
|
+
if (p.ready > 0) parts.push(`${p.ready} ready`);
|
|
11308
|
+
if (p.blocked > 0) parts.push(chalk31.red(`${p.blocked} blocked`));
|
|
11309
|
+
if (p.done > 0) parts.push(`${p.done} done`);
|
|
11310
|
+
return `${completed}/${p.total} tasks` + (parts.length > 0 ? ` (${parts.join(", ")})` : "");
|
|
11311
|
+
}
|
|
11312
|
+
function createTaskParentsCommand() {
|
|
11313
|
+
return new Command58("groups").aliases(["parents", "pg"]).description("List task groups with computed status").option("-s, --status <status>", "Filter by computed status: done, in_progress, ready, todo").option("-j, --json", "Output JSON").action(async (options) => {
|
|
11314
|
+
const service = new TaskService();
|
|
11315
|
+
const parents = await service.getParents(options.status);
|
|
11316
|
+
if (options.json) {
|
|
11317
|
+
console.log(JSON.stringify(parents, null, 2));
|
|
11318
|
+
return;
|
|
11319
|
+
}
|
|
11320
|
+
if (parents.length === 0) {
|
|
11321
|
+
if (options.status) {
|
|
11322
|
+
console.log(chalk31.dim(`No groups with status: ${options.status}`));
|
|
11323
|
+
} else {
|
|
11324
|
+
console.log(chalk31.dim("No task groups found."));
|
|
11325
|
+
console.log(chalk31.dim('\u{1F4A1} Add tasks with parent: jai1 t add "..." -P feature/xxx'));
|
|
11326
|
+
}
|
|
11327
|
+
return;
|
|
11328
|
+
}
|
|
11329
|
+
const header = options.status ? `\u{1F4E6} Task Groups \u2014 ${options.status} (${parents.length})` : `\u{1F4E6} Task Groups (${parents.length})`;
|
|
11330
|
+
console.log(chalk31.bold(header));
|
|
11331
|
+
console.log();
|
|
11332
|
+
for (const p of parents) {
|
|
11333
|
+
const icon = PARENT_STATUS_ICONS[p.status] || "\u{1F4CB}";
|
|
11334
|
+
const progress = formatProgress(p);
|
|
11335
|
+
console.log(` ${icon} ${chalk31.bold(p.name)} ${chalk31.dim("\u2014")} ${progress}`);
|
|
11336
|
+
}
|
|
11337
|
+
console.log();
|
|
11338
|
+
});
|
|
11339
|
+
}
|
|
11340
|
+
|
|
11104
11341
|
// src/commands/tasks/index.ts
|
|
11105
11342
|
function createTasksCommand() {
|
|
11106
|
-
const cmd = new
|
|
11343
|
+
const cmd = new Command59("tasks").alias("t").description("Task management \u2014 track, assign, and manage development tasks").hook("preAction", (thisCommand, actionCommand) => {
|
|
11107
11344
|
if (actionCommand.name() !== "guide") {
|
|
11108
11345
|
TaskService.ensureJai1Dir();
|
|
11109
11346
|
}
|
|
@@ -11118,26 +11355,27 @@ function createTasksCommand() {
|
|
|
11118
11355
|
cmd.addCommand(createTaskDepCommand());
|
|
11119
11356
|
cmd.addCommand(createTaskSyncCommand());
|
|
11120
11357
|
cmd.addCommand(createTaskSummaryCommand());
|
|
11358
|
+
cmd.addCommand(createTaskParentsCommand());
|
|
11121
11359
|
cmd.addCommand(createTaskGuideCommand());
|
|
11122
11360
|
cmd.action(async () => {
|
|
11123
|
-
const { handleTaskSummary } = await import("./summary-
|
|
11361
|
+
const { handleTaskSummary } = await import("./summary-AEJ34P4Q.js");
|
|
11124
11362
|
await handleTaskSummary({ json: false });
|
|
11125
11363
|
});
|
|
11126
11364
|
return cmd;
|
|
11127
11365
|
}
|
|
11128
11366
|
|
|
11129
11367
|
// src/commands/kit/index.ts
|
|
11130
|
-
import { Command as
|
|
11131
|
-
import
|
|
11368
|
+
import { Command as Command63 } from "commander";
|
|
11369
|
+
import chalk33 from "chalk";
|
|
11132
11370
|
|
|
11133
11371
|
// src/commands/kit/list.ts
|
|
11134
|
-
import { Command as
|
|
11135
|
-
import
|
|
11372
|
+
import { Command as Command60 } from "commander";
|
|
11373
|
+
import chalk32 from "chalk";
|
|
11136
11374
|
import Table6 from "cli-table3";
|
|
11137
11375
|
|
|
11138
11376
|
// src/services/starter-kit.service.ts
|
|
11139
11377
|
import { promises as fs19 } from "fs";
|
|
11140
|
-
import { join as
|
|
11378
|
+
import { join as join10 } from "path";
|
|
11141
11379
|
import AdmZip from "adm-zip";
|
|
11142
11380
|
var StarterKitService = class {
|
|
11143
11381
|
/**
|
|
@@ -11184,9 +11422,9 @@ var StarterKitService = class {
|
|
|
11184
11422
|
throw new NetworkError(`Failed to download kit: HTTP ${response.status}`);
|
|
11185
11423
|
}
|
|
11186
11424
|
if (onProgress) onProgress(30);
|
|
11187
|
-
const tmpDir =
|
|
11425
|
+
const tmpDir = join10(process.env.TMPDIR || "/tmp", "jai1-kits");
|
|
11188
11426
|
await fs19.mkdir(tmpDir, { recursive: true });
|
|
11189
|
-
const tmpFile =
|
|
11427
|
+
const tmpFile = join10(tmpDir, `${slug}.zip`);
|
|
11190
11428
|
const buffer = await response.arrayBuffer();
|
|
11191
11429
|
await fs19.writeFile(tmpFile, Buffer.from(buffer));
|
|
11192
11430
|
if (onProgress) onProgress(60);
|
|
@@ -11200,13 +11438,13 @@ var StarterKitService = class {
|
|
|
11200
11438
|
|
|
11201
11439
|
// src/commands/kit/list.ts
|
|
11202
11440
|
function createKitListCommand() {
|
|
11203
|
-
return new
|
|
11441
|
+
return new Command60("list").description("List available starter kits").option("-c, --category <category>", "Filter by category (backend, frontend, fullstack)").option("-s, --search <term>", "Search kits by name or description").action(async (options) => {
|
|
11204
11442
|
const configService = new ConfigService();
|
|
11205
11443
|
const config = await configService.load();
|
|
11206
11444
|
if (!config) {
|
|
11207
11445
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
11208
11446
|
}
|
|
11209
|
-
console.log(
|
|
11447
|
+
console.log(chalk32.cyan("\u{1F4E6} \u0110ang t\u1EA3i danh s\xE1ch starter kits..."));
|
|
11210
11448
|
console.log();
|
|
11211
11449
|
const kitService = new StarterKitService();
|
|
11212
11450
|
const kits = await kitService.list(config, {
|
|
@@ -11214,9 +11452,9 @@ function createKitListCommand() {
|
|
|
11214
11452
|
search: options.search
|
|
11215
11453
|
});
|
|
11216
11454
|
if (kits.length === 0) {
|
|
11217
|
-
console.log(
|
|
11455
|
+
console.log(chalk32.yellow("Kh\xF4ng t\xECm th\u1EA5y starter kits n\xE0o."));
|
|
11218
11456
|
if (options.category || options.search) {
|
|
11219
|
-
console.log(
|
|
11457
|
+
console.log(chalk32.dim("Th\u1EED b\u1ECF filter \u0111\u1EC3 xem t\u1EA5t c\u1EA3."));
|
|
11220
11458
|
}
|
|
11221
11459
|
return;
|
|
11222
11460
|
}
|
|
@@ -11240,35 +11478,35 @@ function createKitListCommand() {
|
|
|
11240
11478
|
const categoryKits = byCategory[category];
|
|
11241
11479
|
const categoryIcon = category === "frontend" ? "\u{1F3A8}" : category === "backend" ? "\u2699\uFE0F" : category === "fullstack" ? "\u{1F680}" : "\u{1F4E6}";
|
|
11242
11480
|
console.log(
|
|
11243
|
-
|
|
11481
|
+
chalk32.bold(`${categoryIcon} ${category.charAt(0).toUpperCase() + category.slice(1)}`)
|
|
11244
11482
|
);
|
|
11245
11483
|
const table = new Table6({
|
|
11246
11484
|
head: [
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11485
|
+
chalk32.cyan("Slug"),
|
|
11486
|
+
chalk32.cyan("M\xF4 t\u1EA3"),
|
|
11487
|
+
chalk32.cyan("Version")
|
|
11250
11488
|
],
|
|
11251
11489
|
style: { head: [], border: ["gray"] }
|
|
11252
11490
|
});
|
|
11253
11491
|
for (const kit of categoryKits) {
|
|
11254
11492
|
table.push([
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
|
|
11493
|
+
chalk32.white(kit.slug),
|
|
11494
|
+
chalk32.dim(kit.description.slice(0, 50)),
|
|
11495
|
+
chalk32.green(`v${kit.version}`)
|
|
11258
11496
|
]);
|
|
11259
11497
|
}
|
|
11260
11498
|
console.log(table.toString());
|
|
11261
11499
|
console.log();
|
|
11262
11500
|
}
|
|
11263
|
-
console.log(
|
|
11264
|
-
console.log(
|
|
11501
|
+
console.log(chalk32.dim(`T\u1ED5ng c\u1ED9ng: ${kits.length} starter kit(s)`));
|
|
11502
|
+
console.log(chalk32.dim('\n\u{1F4A1} Ch\u1EA1y "jai1 kit create <slug>" \u0111\u1EC3 t\u1EA1o project m\u1EDBi'));
|
|
11265
11503
|
});
|
|
11266
11504
|
}
|
|
11267
11505
|
|
|
11268
11506
|
// src/commands/kit/info.ts
|
|
11269
|
-
import { Command as
|
|
11507
|
+
import { Command as Command61 } from "commander";
|
|
11270
11508
|
function createKitInfoCommand() {
|
|
11271
|
-
return new
|
|
11509
|
+
return new Command61("info").description("Show detailed information about a starter kit").argument("<slug>", "Starter kit slug").action(async (slug) => {
|
|
11272
11510
|
const configService = new ConfigService();
|
|
11273
11511
|
const config = await configService.load();
|
|
11274
11512
|
if (!config) {
|
|
@@ -11317,9 +11555,9 @@ Post-Init Commands:`);
|
|
|
11317
11555
|
}
|
|
11318
11556
|
|
|
11319
11557
|
// src/commands/kit/create.ts
|
|
11320
|
-
import { Command as
|
|
11558
|
+
import { Command as Command62 } from "commander";
|
|
11321
11559
|
import { promises as fs20 } from "fs";
|
|
11322
|
-
import { join as
|
|
11560
|
+
import { join as join11 } from "path";
|
|
11323
11561
|
import { select as select3, input as input2, checkbox as checkbox4 } from "@inquirer/prompts";
|
|
11324
11562
|
import { execa as execa2 } from "execa";
|
|
11325
11563
|
|
|
@@ -11362,7 +11600,7 @@ var HookExecutor = class {
|
|
|
11362
11600
|
|
|
11363
11601
|
// src/commands/kit/create.ts
|
|
11364
11602
|
function createKitCreateCommand() {
|
|
11365
|
-
return new
|
|
11603
|
+
return new Command62("create").description("Create a new project from a starter kit").argument("<slug>", "Starter kit slug").argument("[directory]", "Project directory (default: ./<slug>)").option("-y, --yes", "Auto mode - use defaults, no prompts").option("--name <name>", "Project name").option("--skip-install", "Skip dependency installation").option("--skip-git", "Skip git initialization").option("--skip-framework", "Skip framework apply").option("--skip-ide", "Skip IDE sync").action(async (slug, directory, options) => {
|
|
11366
11604
|
const configService = new ConfigService();
|
|
11367
11605
|
const config = await configService.load();
|
|
11368
11606
|
if (!config) {
|
|
@@ -11382,7 +11620,7 @@ function createKitCreateCommand() {
|
|
|
11382
11620
|
}
|
|
11383
11621
|
}
|
|
11384
11622
|
}
|
|
11385
|
-
const targetDir = directory ||
|
|
11623
|
+
const targetDir = directory || join11(process.cwd(), options.name || slug);
|
|
11386
11624
|
try {
|
|
11387
11625
|
await fs20.access(targetDir);
|
|
11388
11626
|
throw new Error(`Directory already exists: ${targetDir}`);
|
|
@@ -11447,7 +11685,7 @@ function createKitCreateCommand() {
|
|
|
11447
11685
|
);
|
|
11448
11686
|
for (const filepath of expandedPaths) {
|
|
11449
11687
|
console.log(` \u{1F4E5} Installing ${filepath}...`);
|
|
11450
|
-
await componentsService.install(config, filepath,
|
|
11688
|
+
await componentsService.install(config, filepath, join11(targetDir, ".jai1"));
|
|
11451
11689
|
}
|
|
11452
11690
|
console.log(" \u2713 Framework components applied");
|
|
11453
11691
|
}
|
|
@@ -11527,7 +11765,7 @@ async function getAllFiles(dir) {
|
|
|
11527
11765
|
const files = [];
|
|
11528
11766
|
const entries = await fs20.readdir(dir, { withFileTypes: true });
|
|
11529
11767
|
for (const entry of entries) {
|
|
11530
|
-
const fullPath =
|
|
11768
|
+
const fullPath = join11(dir, entry.name);
|
|
11531
11769
|
if (entry.isDirectory()) {
|
|
11532
11770
|
if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
11533
11771
|
files.push(...await getAllFiles(fullPath));
|
|
@@ -11541,23 +11779,23 @@ async function getAllFiles(dir) {
|
|
|
11541
11779
|
|
|
11542
11780
|
// src/commands/kit/index.ts
|
|
11543
11781
|
function showKitHelp() {
|
|
11544
|
-
console.log(
|
|
11782
|
+
console.log(chalk33.bold.cyan("\u{1F4E6} jai1 kit") + chalk33.dim(" - Qu\u1EA3n l\xFD starter kits"));
|
|
11545
11783
|
console.log();
|
|
11546
|
-
console.log(
|
|
11547
|
-
console.log(` ${
|
|
11548
|
-
console.log(` ${
|
|
11549
|
-
console.log(` ${
|
|
11784
|
+
console.log(chalk33.bold("C\xE1c l\u1EC7nh:"));
|
|
11785
|
+
console.log(` ${chalk33.cyan("list")} Li\u1EC7t k\xEA c\xE1c starter kits c\xF3 s\u1EB5n`);
|
|
11786
|
+
console.log(` ${chalk33.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t starter kit`);
|
|
11787
|
+
console.log(` ${chalk33.cyan("create")} T\u1EA1o project m\u1EDBi t\u1EEB starter kit`);
|
|
11550
11788
|
console.log();
|
|
11551
|
-
console.log(
|
|
11552
|
-
console.log(
|
|
11553
|
-
console.log(
|
|
11554
|
-
console.log(
|
|
11555
|
-
console.log(
|
|
11789
|
+
console.log(chalk33.bold("V\xED d\u1EE5:"));
|
|
11790
|
+
console.log(chalk33.dim(" $ jai1 kit list"));
|
|
11791
|
+
console.log(chalk33.dim(" $ jai1 kit list --category frontend"));
|
|
11792
|
+
console.log(chalk33.dim(" $ jai1 kit info next-tw4-shadcn"));
|
|
11793
|
+
console.log(chalk33.dim(" $ jai1 kit create next-tw4-shadcn my-project"));
|
|
11556
11794
|
console.log();
|
|
11557
|
-
console.log(
|
|
11795
|
+
console.log(chalk33.dim('Ch\u1EA1y "jai1 kit <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
|
|
11558
11796
|
}
|
|
11559
11797
|
function createKitCommand() {
|
|
11560
|
-
const cmd = new
|
|
11798
|
+
const cmd = new Command63("kit").description("Manage starter kits for new projects").action(() => {
|
|
11561
11799
|
showKitHelp();
|
|
11562
11800
|
});
|
|
11563
11801
|
cmd.addCommand(createKitListCommand());
|
|
@@ -11567,21 +11805,21 @@ function createKitCommand() {
|
|
|
11567
11805
|
}
|
|
11568
11806
|
|
|
11569
11807
|
// src/commands/rules/index.ts
|
|
11570
|
-
import { Command as
|
|
11571
|
-
import
|
|
11808
|
+
import { Command as Command70 } from "commander";
|
|
11809
|
+
import chalk35 from "chalk";
|
|
11572
11810
|
|
|
11573
11811
|
// src/commands/rules/list.ts
|
|
11574
|
-
import { Command as
|
|
11575
|
-
import
|
|
11812
|
+
import { Command as Command64 } from "commander";
|
|
11813
|
+
import chalk34 from "chalk";
|
|
11576
11814
|
import Table7 from "cli-table3";
|
|
11577
11815
|
function createRulesListCommand() {
|
|
11578
|
-
return new
|
|
11816
|
+
return new Command64("list").description("List available rule presets").option("--json", "Output as JSON").action(async (options) => {
|
|
11579
11817
|
const configService = new ConfigService();
|
|
11580
11818
|
const config = await configService.load();
|
|
11581
11819
|
if (!config) {
|
|
11582
11820
|
throw new ValidationError('Not initialized. Run "jai1 auth" first.');
|
|
11583
11821
|
}
|
|
11584
|
-
console.log(
|
|
11822
|
+
console.log(chalk34.cyan("\u{1F4CB} \u0110ang t\u1EA3i danh s\xE1ch rule presets..."));
|
|
11585
11823
|
console.log();
|
|
11586
11824
|
try {
|
|
11587
11825
|
const response = await fetch(`${config.apiUrl}/api/rules/presets`, {
|
|
@@ -11598,23 +11836,23 @@ function createRulesListCommand() {
|
|
|
11598
11836
|
return;
|
|
11599
11837
|
}
|
|
11600
11838
|
if (data.total === 0) {
|
|
11601
|
-
console.log(
|
|
11839
|
+
console.log(chalk34.yellow("Kh\xF4ng c\xF3 presets n\xE0o."));
|
|
11602
11840
|
return;
|
|
11603
11841
|
}
|
|
11604
11842
|
console.log(
|
|
11605
|
-
|
|
11843
|
+
chalk34.green(`\u2713 T\xECm th\u1EA5y ${chalk34.bold(data.total)} preset${data.total > 1 ? "s" : ""}`)
|
|
11606
11844
|
);
|
|
11607
11845
|
console.log();
|
|
11608
11846
|
for (const preset of data.presets) {
|
|
11609
|
-
console.log(
|
|
11847
|
+
console.log(chalk34.bold.cyan(`\u{1F4E6} ${preset.slug}`));
|
|
11610
11848
|
const table = new Table7({
|
|
11611
11849
|
style: { head: [], border: ["gray"], compact: true },
|
|
11612
11850
|
colWidths: [15, 55]
|
|
11613
11851
|
});
|
|
11614
11852
|
table.push(
|
|
11615
|
-
[
|
|
11616
|
-
[
|
|
11617
|
-
[
|
|
11853
|
+
[chalk34.dim("T\xEAn"), chalk34.white(preset.name)],
|
|
11854
|
+
[chalk34.dim("M\xF4 t\u1EA3"), chalk34.white(preset.description)],
|
|
11855
|
+
[chalk34.dim("Version"), chalk34.green(`v${preset.version}`)]
|
|
11618
11856
|
);
|
|
11619
11857
|
const stackParts = [];
|
|
11620
11858
|
if (preset.stack.frontend) stackParts.push(preset.stack.frontend);
|
|
@@ -11622,16 +11860,16 @@ function createRulesListCommand() {
|
|
|
11622
11860
|
if (preset.stack.css) stackParts.push(preset.stack.css);
|
|
11623
11861
|
if (preset.stack.database) stackParts.push(preset.stack.database);
|
|
11624
11862
|
if (stackParts.length > 0) {
|
|
11625
|
-
table.push([
|
|
11863
|
+
table.push([chalk34.dim("Stack"), chalk34.yellow(stackParts.join(" + "))]);
|
|
11626
11864
|
}
|
|
11627
11865
|
table.push(
|
|
11628
|
-
[
|
|
11629
|
-
[
|
|
11866
|
+
[chalk34.dim("Tags"), chalk34.dim(preset.tags.join(", ") || "-")],
|
|
11867
|
+
[chalk34.dim("Downloads"), chalk34.white(preset.downloads.toString())]
|
|
11630
11868
|
);
|
|
11631
11869
|
console.log(table.toString());
|
|
11632
11870
|
console.log();
|
|
11633
11871
|
}
|
|
11634
|
-
console.log(
|
|
11872
|
+
console.log(chalk34.dim('\u{1F4A1} Ch\u1EA1y "jai1 rules apply <name>" \u0111\u1EC3 \xE1p d\u1EE5ng preset'));
|
|
11635
11873
|
} catch (error) {
|
|
11636
11874
|
throw new Error(
|
|
11637
11875
|
`L\u1ED7i khi t\u1EA3i presets: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -11641,22 +11879,22 @@ function createRulesListCommand() {
|
|
|
11641
11879
|
}
|
|
11642
11880
|
|
|
11643
11881
|
// src/commands/rules/init.ts
|
|
11644
|
-
import { Command as
|
|
11882
|
+
import { Command as Command65 } from "commander";
|
|
11645
11883
|
import { promises as fs22 } from "fs";
|
|
11646
|
-
import { join as
|
|
11884
|
+
import { join as join13 } from "path";
|
|
11647
11885
|
import { select as select4, confirm as confirm10 } from "@inquirer/prompts";
|
|
11648
11886
|
|
|
11649
11887
|
// src/services/project-config.service.ts
|
|
11650
11888
|
import { promises as fs21 } from "fs";
|
|
11651
|
-
import { join as
|
|
11889
|
+
import { join as join12 } from "path";
|
|
11652
11890
|
var ProjectConfigService = class {
|
|
11653
11891
|
projectRoot;
|
|
11654
11892
|
configDir;
|
|
11655
11893
|
configPath;
|
|
11656
11894
|
constructor(projectRoot = process.cwd()) {
|
|
11657
11895
|
this.projectRoot = projectRoot;
|
|
11658
|
-
this.configDir =
|
|
11659
|
-
this.configPath =
|
|
11896
|
+
this.configDir = join12(this.projectRoot, ".jai1");
|
|
11897
|
+
this.configPath = join12(this.configDir, "project.json");
|
|
11660
11898
|
}
|
|
11661
11899
|
/**
|
|
11662
11900
|
* Check if config file exists
|
|
@@ -11766,7 +12004,7 @@ var ProjectConfigService = class {
|
|
|
11766
12004
|
|
|
11767
12005
|
// src/commands/rules/init.ts
|
|
11768
12006
|
function createRulesInitCommand() {
|
|
11769
|
-
return new
|
|
12007
|
+
return new Command65("init").description("Apply rule preset to project").option("--preset <slug>", "Preset slug to apply").option("--output <format>", "Output format: cursor, agents-md, both (default: cursor)", "cursor").option("-y, --yes", "Skip confirmations").action(async (options) => {
|
|
11770
12008
|
const configService = new ConfigService();
|
|
11771
12009
|
const config = await configService.load();
|
|
11772
12010
|
if (!config) {
|
|
@@ -11862,10 +12100,10 @@ function createRulesInitCommand() {
|
|
|
11862
12100
|
});
|
|
11863
12101
|
}
|
|
11864
12102
|
async function applyCursorFormat(bundle) {
|
|
11865
|
-
const rulesDir =
|
|
12103
|
+
const rulesDir = join13(process.cwd(), ".cursor", "rules");
|
|
11866
12104
|
await fs22.mkdir(rulesDir, { recursive: true });
|
|
11867
12105
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
11868
|
-
const filePath =
|
|
12106
|
+
const filePath = join13(rulesDir, filename);
|
|
11869
12107
|
await fs22.writeFile(filePath, content, "utf-8");
|
|
11870
12108
|
console.log(`\u2713 Created .cursor/rules/${filename}`);
|
|
11871
12109
|
}
|
|
@@ -11898,9 +12136,9 @@ async function applyAgentsMdFormat(bundle) {
|
|
|
11898
12136
|
}
|
|
11899
12137
|
|
|
11900
12138
|
// src/commands/rules/apply.ts
|
|
11901
|
-
import { Command as
|
|
12139
|
+
import { Command as Command66 } from "commander";
|
|
11902
12140
|
import { promises as fs24 } from "fs";
|
|
11903
|
-
import { join as
|
|
12141
|
+
import { join as join15 } from "path";
|
|
11904
12142
|
import { search, confirm as confirm11, checkbox as checkbox5 } from "@inquirer/prompts";
|
|
11905
12143
|
|
|
11906
12144
|
// src/services/rules-generator.service.ts
|
|
@@ -12192,7 +12430,7 @@ Follow all instructions and patterns defined in AGENTS.md above.
|
|
|
12192
12430
|
|
|
12193
12431
|
// src/services/backup.service.ts
|
|
12194
12432
|
import { promises as fs23 } from "fs";
|
|
12195
|
-
import { join as
|
|
12433
|
+
import { join as join14, dirname } from "path";
|
|
12196
12434
|
var BackupService = class {
|
|
12197
12435
|
backupDir = ".jai1/backups";
|
|
12198
12436
|
/**
|
|
@@ -12200,7 +12438,7 @@ var BackupService = class {
|
|
|
12200
12438
|
*/
|
|
12201
12439
|
async createBackup(ides, presetSlug) {
|
|
12202
12440
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12203
|
-
const backupPath =
|
|
12441
|
+
const backupPath = join14(this.backupDir, timestamp);
|
|
12204
12442
|
const backedUpFiles = [];
|
|
12205
12443
|
let hasContent = false;
|
|
12206
12444
|
for (const ideId of ides) {
|
|
@@ -12209,7 +12447,7 @@ var BackupService = class {
|
|
|
12209
12447
|
console.warn(`Unknown IDE format: ${ideId}, skipping backup`);
|
|
12210
12448
|
continue;
|
|
12211
12449
|
}
|
|
12212
|
-
const rulesPath = format.rulesPath === "." ? process.cwd() :
|
|
12450
|
+
const rulesPath = format.rulesPath === "." ? process.cwd() : join14(process.cwd(), format.rulesPath);
|
|
12213
12451
|
try {
|
|
12214
12452
|
const exists = await this.pathExists(rulesPath);
|
|
12215
12453
|
if (!exists) {
|
|
@@ -12227,14 +12465,14 @@ var BackupService = class {
|
|
|
12227
12465
|
const files = await fs23.readdir(rulesPath);
|
|
12228
12466
|
for (const file of files) {
|
|
12229
12467
|
if (file.endsWith(format.fileExtension)) {
|
|
12230
|
-
const originalPath =
|
|
12231
|
-
const relativePath =
|
|
12232
|
-
const destPath =
|
|
12468
|
+
const originalPath = join14(rulesPath, file);
|
|
12469
|
+
const relativePath = join14(format.rulesPath, file);
|
|
12470
|
+
const destPath = join14(backupPath, ideId, file);
|
|
12233
12471
|
await fs23.mkdir(dirname(destPath), { recursive: true });
|
|
12234
12472
|
await fs23.copyFile(originalPath, destPath);
|
|
12235
12473
|
backedUpFiles.push({
|
|
12236
12474
|
originalPath: relativePath,
|
|
12237
|
-
backupPath:
|
|
12475
|
+
backupPath: join14(ideId, file),
|
|
12238
12476
|
ide: ideId
|
|
12239
12477
|
});
|
|
12240
12478
|
hasContent = true;
|
|
@@ -12257,7 +12495,7 @@ var BackupService = class {
|
|
|
12257
12495
|
};
|
|
12258
12496
|
await fs23.mkdir(backupPath, { recursive: true });
|
|
12259
12497
|
await fs23.writeFile(
|
|
12260
|
-
|
|
12498
|
+
join14(backupPath, "metadata.json"),
|
|
12261
12499
|
JSON.stringify(metadata, null, 2),
|
|
12262
12500
|
"utf-8"
|
|
12263
12501
|
);
|
|
@@ -12267,18 +12505,18 @@ var BackupService = class {
|
|
|
12267
12505
|
* Backup a single file (for AGENTS.md, GEMINI.md)
|
|
12268
12506
|
*/
|
|
12269
12507
|
async backupSingleFile(filename, backupPath, ideId, backedUpFiles) {
|
|
12270
|
-
const originalPath =
|
|
12508
|
+
const originalPath = join14(process.cwd(), filename);
|
|
12271
12509
|
try {
|
|
12272
12510
|
const exists = await this.pathExists(originalPath);
|
|
12273
12511
|
if (!exists) {
|
|
12274
12512
|
return;
|
|
12275
12513
|
}
|
|
12276
|
-
const destPath =
|
|
12514
|
+
const destPath = join14(backupPath, ideId, filename);
|
|
12277
12515
|
await fs23.mkdir(dirname(destPath), { recursive: true });
|
|
12278
12516
|
await fs23.copyFile(originalPath, destPath);
|
|
12279
12517
|
backedUpFiles.push({
|
|
12280
12518
|
originalPath: filename,
|
|
12281
|
-
backupPath:
|
|
12519
|
+
backupPath: join14(ideId, filename),
|
|
12282
12520
|
ide: ideId
|
|
12283
12521
|
});
|
|
12284
12522
|
} catch (error) {
|
|
@@ -12288,14 +12526,14 @@ var BackupService = class {
|
|
|
12288
12526
|
* Restore files from a backup
|
|
12289
12527
|
*/
|
|
12290
12528
|
async restoreBackup(backupPath) {
|
|
12291
|
-
const metadataPath =
|
|
12529
|
+
const metadataPath = join14(backupPath, "metadata.json");
|
|
12292
12530
|
const metadataContent = await fs23.readFile(metadataPath, "utf-8");
|
|
12293
12531
|
const metadata = JSON.parse(metadataContent);
|
|
12294
12532
|
console.log(`
|
|
12295
12533
|
Restoring backup from ${metadata.timestamp}...`);
|
|
12296
12534
|
for (const file of metadata.files) {
|
|
12297
|
-
const sourcePath =
|
|
12298
|
-
const destPath =
|
|
12535
|
+
const sourcePath = join14(backupPath, file.backupPath);
|
|
12536
|
+
const destPath = join14(process.cwd(), file.originalPath);
|
|
12299
12537
|
await fs23.mkdir(dirname(destPath), { recursive: true });
|
|
12300
12538
|
await fs23.copyFile(sourcePath, destPath);
|
|
12301
12539
|
console.log(`\u2713 Restored ${file.originalPath}`);
|
|
@@ -12307,7 +12545,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12307
12545
|
*/
|
|
12308
12546
|
async listBackups() {
|
|
12309
12547
|
try {
|
|
12310
|
-
const backupDirPath =
|
|
12548
|
+
const backupDirPath = join14(process.cwd(), this.backupDir);
|
|
12311
12549
|
const exists = await this.pathExists(backupDirPath);
|
|
12312
12550
|
if (!exists) {
|
|
12313
12551
|
return [];
|
|
@@ -12316,7 +12554,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12316
12554
|
const backups = [];
|
|
12317
12555
|
for (const entry of entries) {
|
|
12318
12556
|
if (entry.isDirectory()) {
|
|
12319
|
-
const metadataPath =
|
|
12557
|
+
const metadataPath = join14(backupDirPath, entry.name, "metadata.json");
|
|
12320
12558
|
try {
|
|
12321
12559
|
const metadataContent = await fs23.readFile(metadataPath, "utf-8");
|
|
12322
12560
|
const metadata = JSON.parse(metadataContent);
|
|
@@ -12335,7 +12573,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12335
12573
|
* Delete a specific backup
|
|
12336
12574
|
*/
|
|
12337
12575
|
async deleteBackup(timestamp) {
|
|
12338
|
-
const backupPath =
|
|
12576
|
+
const backupPath = join14(process.cwd(), this.backupDir, timestamp);
|
|
12339
12577
|
await this.deleteDirectory(backupPath);
|
|
12340
12578
|
}
|
|
12341
12579
|
/**
|
|
@@ -12384,7 +12622,7 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12384
12622
|
}
|
|
12385
12623
|
const entries = await fs23.readdir(path13, { withFileTypes: true });
|
|
12386
12624
|
for (const entry of entries) {
|
|
12387
|
-
const fullPath =
|
|
12625
|
+
const fullPath = join14(path13, entry.name);
|
|
12388
12626
|
if (entry.isDirectory()) {
|
|
12389
12627
|
await this.deleteDirectory(fullPath);
|
|
12390
12628
|
} else {
|
|
@@ -12399,20 +12637,20 @@ Restoring backup from ${metadata.timestamp}...`);
|
|
|
12399
12637
|
* Get backup directory path
|
|
12400
12638
|
*/
|
|
12401
12639
|
getBackupDir() {
|
|
12402
|
-
return
|
|
12640
|
+
return join14(process.cwd(), this.backupDir);
|
|
12403
12641
|
}
|
|
12404
12642
|
/**
|
|
12405
12643
|
* Ensure backup directory exists
|
|
12406
12644
|
*/
|
|
12407
12645
|
async ensureBackupDir() {
|
|
12408
|
-
const backupDirPath =
|
|
12646
|
+
const backupDirPath = join14(process.cwd(), this.backupDir);
|
|
12409
12647
|
await fs23.mkdir(backupDirPath, { recursive: true });
|
|
12410
12648
|
}
|
|
12411
12649
|
};
|
|
12412
12650
|
|
|
12413
12651
|
// src/commands/rules/apply.ts
|
|
12414
12652
|
function createRulesApplyCommand() {
|
|
12415
|
-
return new
|
|
12653
|
+
return new Command66("apply").description("Apply rule preset to project with multi-IDE support").argument("[preset]", "Preset slug to apply (optional)").option("--ides <ides>", 'Comma-separated list of IDE formats (cursor,windsurf,antigravity,claude,agentsmd,gemini) or "all"').option("--skip-backup", "Skip backup creation").option("-y, --yes", "Skip all confirmations (auto mode)").action(async (presetSlug, options) => {
|
|
12416
12654
|
const configService = new ConfigService();
|
|
12417
12655
|
const config = await configService.load();
|
|
12418
12656
|
if (!config) {
|
|
@@ -12578,20 +12816,20 @@ function createRulesApplyCommand() {
|
|
|
12578
12816
|
}
|
|
12579
12817
|
}
|
|
12580
12818
|
console.log("\n\u{1F4DD} Applying preset...\n");
|
|
12581
|
-
const rulePresetDir =
|
|
12819
|
+
const rulePresetDir = join15(process.cwd(), ".jai1", "rule-preset");
|
|
12582
12820
|
try {
|
|
12583
12821
|
await fs24.rm(rulePresetDir, { recursive: true, force: true });
|
|
12584
12822
|
} catch {
|
|
12585
12823
|
}
|
|
12586
12824
|
await fs24.mkdir(rulePresetDir, { recursive: true });
|
|
12587
12825
|
await fs24.writeFile(
|
|
12588
|
-
|
|
12826
|
+
join15(rulePresetDir, "preset.json"),
|
|
12589
12827
|
JSON.stringify(bundle.preset, null, 2),
|
|
12590
12828
|
"utf-8"
|
|
12591
12829
|
);
|
|
12592
12830
|
for (const [filename, content] of Object.entries(bundle.files)) {
|
|
12593
|
-
const filePath =
|
|
12594
|
-
await fs24.mkdir(
|
|
12831
|
+
const filePath = join15(rulePresetDir, filename);
|
|
12832
|
+
await fs24.mkdir(join15(filePath, ".."), { recursive: true });
|
|
12595
12833
|
await fs24.writeFile(filePath, content, "utf-8");
|
|
12596
12834
|
}
|
|
12597
12835
|
console.log(`\u2713 Saved preset to .jai1/rule-preset/`);
|
|
@@ -12600,8 +12838,8 @@ function createRulesApplyCommand() {
|
|
|
12600
12838
|
try {
|
|
12601
12839
|
const files = generatorService.generateForIde(bundle, ideId);
|
|
12602
12840
|
for (const file of files) {
|
|
12603
|
-
const fullPath =
|
|
12604
|
-
await fs24.mkdir(
|
|
12841
|
+
const fullPath = join15(process.cwd(), file.path);
|
|
12842
|
+
await fs24.mkdir(join15(fullPath, ".."), { recursive: true });
|
|
12605
12843
|
await fs24.writeFile(fullPath, file.content, "utf-8");
|
|
12606
12844
|
console.log(`\u2713 [${ideId}] ${file.path}`);
|
|
12607
12845
|
allGeneratedFiles.push({
|
|
@@ -12707,11 +12945,11 @@ function createRulesApplyCommand() {
|
|
|
12707
12945
|
}
|
|
12708
12946
|
|
|
12709
12947
|
// src/commands/rules/restore.ts
|
|
12710
|
-
import { Command as
|
|
12711
|
-
import { join as
|
|
12948
|
+
import { Command as Command67 } from "commander";
|
|
12949
|
+
import { join as join16 } from "path";
|
|
12712
12950
|
import { select as select5, confirm as confirm12 } from "@inquirer/prompts";
|
|
12713
12951
|
function createRulesRestoreCommand() {
|
|
12714
|
-
return new
|
|
12952
|
+
return new Command67("restore").description("Restore rules from a backup").option("--latest", "Restore the most recent backup").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
12715
12953
|
const backupService = new BackupService();
|
|
12716
12954
|
const backups = await backupService.listBackups();
|
|
12717
12955
|
if (backups.length === 0) {
|
|
@@ -12755,7 +12993,7 @@ function createRulesRestoreCommand() {
|
|
|
12755
12993
|
}
|
|
12756
12994
|
console.log("\n\u{1F504} Restoring backup...\n");
|
|
12757
12995
|
try {
|
|
12758
|
-
const backupPath =
|
|
12996
|
+
const backupPath = join16(backupService.getBackupDir(), selectedBackup.timestamp);
|
|
12759
12997
|
await backupService.restoreBackup(backupPath);
|
|
12760
12998
|
console.log("\n\u2705 Backup restored successfully!\n");
|
|
12761
12999
|
console.log("\u{1F4A1} Tip: Your IDE may need to be restarted to pick up the changes.");
|
|
@@ -12780,14 +13018,14 @@ function formatTimestamp(timestamp) {
|
|
|
12780
13018
|
}
|
|
12781
13019
|
|
|
12782
13020
|
// src/commands/rules/sync.ts
|
|
12783
|
-
import { Command as
|
|
13021
|
+
import { Command as Command68 } from "commander";
|
|
12784
13022
|
import { promises as fs25 } from "fs";
|
|
12785
|
-
import { join as
|
|
13023
|
+
import { join as join17 } from "path";
|
|
12786
13024
|
import { checkbox as checkbox6, confirm as confirm13, Separator } from "@inquirer/prompts";
|
|
12787
13025
|
function createRulesSyncCommand() {
|
|
12788
|
-
return new
|
|
12789
|
-
const rulePresetDir =
|
|
12790
|
-
const presetJsonPath =
|
|
13026
|
+
return new Command68("sync").description("Regenerate rule outputs for all configured IDEs").option("--ides <ides>", "Comma-separated list of IDEs to sync (default: all configured)").option("--detect", "Auto-detect active IDEs instead of using config").option("-y, --yes", "Skip confirmations").action(async (options) => {
|
|
13027
|
+
const rulePresetDir = join17(process.cwd(), ".jai1", "rule-preset");
|
|
13028
|
+
const presetJsonPath = join17(rulePresetDir, "preset.json");
|
|
12791
13029
|
let presetExists = false;
|
|
12792
13030
|
let presetData = null;
|
|
12793
13031
|
try {
|
|
@@ -12924,7 +13162,7 @@ Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
|
12924
13162
|
const files = await fs25.readdir(rulePresetDir);
|
|
12925
13163
|
for (const file of files) {
|
|
12926
13164
|
if (file.endsWith(".mdc") || file.endsWith(".md")) {
|
|
12927
|
-
const filePath =
|
|
13165
|
+
const filePath = join17(rulePresetDir, file);
|
|
12928
13166
|
const content = await fs25.readFile(filePath, "utf-8");
|
|
12929
13167
|
bundle.files[file] = content;
|
|
12930
13168
|
}
|
|
@@ -12939,8 +13177,8 @@ Current IDE(s): ${currentIdes.join(", ") || "none"}`);
|
|
|
12939
13177
|
}
|
|
12940
13178
|
const files2 = generatorService.generateForIde(bundle, ideId);
|
|
12941
13179
|
for (const file of files2) {
|
|
12942
|
-
const fullPath =
|
|
12943
|
-
await fs25.mkdir(
|
|
13180
|
+
const fullPath = join17(process.cwd(), file.path);
|
|
13181
|
+
await fs25.mkdir(join17(fullPath, ".."), { recursive: true });
|
|
12944
13182
|
await fs25.writeFile(fullPath, file.content, "utf-8");
|
|
12945
13183
|
}
|
|
12946
13184
|
console.log(`\u2713 ${format.name} - ${files2.length} files regenerated`);
|
|
@@ -13003,11 +13241,11 @@ function buildIdeChoices(currentIdes, detected, suggestions) {
|
|
|
13003
13241
|
}
|
|
13004
13242
|
|
|
13005
13243
|
// src/commands/rules/info.ts
|
|
13006
|
-
import { Command as
|
|
13244
|
+
import { Command as Command69 } from "commander";
|
|
13007
13245
|
import { promises as fs26 } from "fs";
|
|
13008
|
-
import { join as
|
|
13246
|
+
import { join as join18 } from "path";
|
|
13009
13247
|
function createRulesInfoCommand() {
|
|
13010
|
-
return new
|
|
13248
|
+
return new Command69("info").description("Show current preset information").option("--json", "Output as JSON").action(async (options) => {
|
|
13011
13249
|
const projectConfigService = new ProjectConfigService();
|
|
13012
13250
|
const rulesConfig = await projectConfigService.loadRules();
|
|
13013
13251
|
if (!rulesConfig) {
|
|
@@ -13020,8 +13258,8 @@ function createRulesInfoCommand() {
|
|
|
13020
13258
|
return;
|
|
13021
13259
|
}
|
|
13022
13260
|
console.log("\u{1F4CB} Current Preset Information\n");
|
|
13023
|
-
const rulePresetDir =
|
|
13024
|
-
const presetJsonPath =
|
|
13261
|
+
const rulePresetDir = join18(process.cwd(), ".jai1", "rule-preset");
|
|
13262
|
+
const presetJsonPath = join18(rulePresetDir, "preset.json");
|
|
13025
13263
|
let presetMetadata = null;
|
|
13026
13264
|
let presetFiles = [];
|
|
13027
13265
|
try {
|
|
@@ -13088,7 +13326,7 @@ Available Backups (${rulesConfig.backups.length}):`);
|
|
|
13088
13326
|
}
|
|
13089
13327
|
async function checkPathExists(path13) {
|
|
13090
13328
|
try {
|
|
13091
|
-
await fs26.access(
|
|
13329
|
+
await fs26.access(join18(process.cwd(), path13));
|
|
13092
13330
|
return true;
|
|
13093
13331
|
} catch {
|
|
13094
13332
|
return false;
|
|
@@ -13110,26 +13348,26 @@ async function checkIdeFilesExist(ideId, format) {
|
|
|
13110
13348
|
|
|
13111
13349
|
// src/commands/rules/index.ts
|
|
13112
13350
|
function showRulesHelp() {
|
|
13113
|
-
console.log(
|
|
13351
|
+
console.log(chalk35.bold.cyan("\u{1F4CB} jai1 rules") + chalk35.dim(" - Qu\u1EA3n l\xFD rule presets cho AI agents"));
|
|
13114
13352
|
console.log();
|
|
13115
|
-
console.log(
|
|
13116
|
-
console.log(` ${
|
|
13117
|
-
console.log(` ${
|
|
13118
|
-
console.log(` ${
|
|
13119
|
-
console.log(` ${
|
|
13120
|
-
console.log(` ${
|
|
13121
|
-
console.log(` ${
|
|
13353
|
+
console.log(chalk35.bold("C\xE1c l\u1EC7nh:"));
|
|
13354
|
+
console.log(` ${chalk35.cyan("list")} Li\u1EC7t k\xEA c\xE1c presets c\xF3 s\u1EB5n`);
|
|
13355
|
+
console.log(` ${chalk35.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t preset`);
|
|
13356
|
+
console.log(` ${chalk35.cyan("init")} Kh\u1EDFi t\u1EA1o rules t\u1EEB preset`);
|
|
13357
|
+
console.log(` ${chalk35.cyan("apply")} \xC1p d\u1EE5ng preset v\xE0o project`);
|
|
13358
|
+
console.log(` ${chalk35.cyan("sync")} \u0110\u1ED3ng b\u1ED9 rules sang c\xE1c \u0111\u1ECBnh d\u1EA1ng IDE`);
|
|
13359
|
+
console.log(` ${chalk35.cyan("restore")} Kh\xF4i ph\u1EE5c rules t\u1EEB backup`);
|
|
13122
13360
|
console.log();
|
|
13123
|
-
console.log(
|
|
13124
|
-
console.log(
|
|
13125
|
-
console.log(
|
|
13126
|
-
console.log(
|
|
13127
|
-
console.log(
|
|
13361
|
+
console.log(chalk35.bold("V\xED d\u1EE5:"));
|
|
13362
|
+
console.log(chalk35.dim(" $ jai1 rules list"));
|
|
13363
|
+
console.log(chalk35.dim(" $ jai1 rules info react-typescript"));
|
|
13364
|
+
console.log(chalk35.dim(" $ jai1 rules init --preset=react-typescript"));
|
|
13365
|
+
console.log(chalk35.dim(" $ jai1 rules apply react-typescript"));
|
|
13128
13366
|
console.log();
|
|
13129
|
-
console.log(
|
|
13367
|
+
console.log(chalk35.dim('Ch\u1EA1y "jai1 rules <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt'));
|
|
13130
13368
|
}
|
|
13131
13369
|
function createRulesCommand() {
|
|
13132
|
-
const rulesCommand = new
|
|
13370
|
+
const rulesCommand = new Command70("rules").description("Manage rule presets for AI agents").action(() => {
|
|
13133
13371
|
showRulesHelp();
|
|
13134
13372
|
});
|
|
13135
13373
|
rulesCommand.addCommand(createRulesListCommand());
|
|
@@ -13142,17 +13380,17 @@ function createRulesCommand() {
|
|
|
13142
13380
|
}
|
|
13143
13381
|
|
|
13144
13382
|
// src/commands/skills/index.ts
|
|
13145
|
-
import { Command as
|
|
13146
|
-
import
|
|
13383
|
+
import { Command as Command76 } from "commander";
|
|
13384
|
+
import chalk41 from "chalk";
|
|
13147
13385
|
|
|
13148
13386
|
// src/commands/skills/find.ts
|
|
13149
|
-
import { Command as
|
|
13150
|
-
import
|
|
13387
|
+
import { Command as Command71 } from "commander";
|
|
13388
|
+
import chalk36 from "chalk";
|
|
13151
13389
|
import Table8 from "cli-table3";
|
|
13152
13390
|
|
|
13153
13391
|
// src/services/skills.service.ts
|
|
13154
13392
|
import { promises as fs27 } from "fs";
|
|
13155
|
-
import { join as
|
|
13393
|
+
import { join as join19 } from "path";
|
|
13156
13394
|
import { execFile } from "child_process";
|
|
13157
13395
|
import { promisify } from "util";
|
|
13158
13396
|
var execFileAsync = promisify(execFile);
|
|
@@ -13188,14 +13426,14 @@ var SkillsService = class {
|
|
|
13188
13426
|
* List locally installed skills from .jai1/skills/
|
|
13189
13427
|
*/
|
|
13190
13428
|
async listLocal(projectRoot) {
|
|
13191
|
-
const skillsDir =
|
|
13429
|
+
const skillsDir = join19(projectRoot, ".jai1", "skills");
|
|
13192
13430
|
const skills = [];
|
|
13193
13431
|
try {
|
|
13194
13432
|
const entries = await fs27.readdir(skillsDir, { withFileTypes: true });
|
|
13195
13433
|
for (const entry of entries) {
|
|
13196
13434
|
if (!entry.isDirectory()) continue;
|
|
13197
|
-
const skillPath =
|
|
13198
|
-
const skillMd =
|
|
13435
|
+
const skillPath = join19(skillsDir, entry.name);
|
|
13436
|
+
const skillMd = join19(skillPath, "SKILL.md");
|
|
13199
13437
|
try {
|
|
13200
13438
|
await fs27.access(skillMd);
|
|
13201
13439
|
} catch {
|
|
@@ -13220,8 +13458,8 @@ var SkillsService = class {
|
|
|
13220
13458
|
* Get detailed info for a single skill
|
|
13221
13459
|
*/
|
|
13222
13460
|
async getSkillInfo(projectRoot, skillName) {
|
|
13223
|
-
const skillPath =
|
|
13224
|
-
const skillMd =
|
|
13461
|
+
const skillPath = join19(projectRoot, ".jai1", "skills", skillName);
|
|
13462
|
+
const skillMd = join19(skillPath, "SKILL.md");
|
|
13225
13463
|
try {
|
|
13226
13464
|
await fs27.access(skillMd);
|
|
13227
13465
|
} catch {
|
|
@@ -13296,22 +13534,22 @@ var SkillsService = class {
|
|
|
13296
13534
|
* After npx skills add, copy newly installed skills from .agents/skills/ into .jai1/skills/
|
|
13297
13535
|
*/
|
|
13298
13536
|
async copySkillshResultsToJai1(projectRoot, specificSkill) {
|
|
13299
|
-
const jai1SkillsDir =
|
|
13537
|
+
const jai1SkillsDir = join19(projectRoot, ".jai1", "skills");
|
|
13300
13538
|
await fs27.mkdir(jai1SkillsDir, { recursive: true });
|
|
13301
|
-
const universalDir =
|
|
13539
|
+
const universalDir = join19(projectRoot, ".agents", "skills");
|
|
13302
13540
|
try {
|
|
13303
13541
|
const entries = await fs27.readdir(universalDir, { withFileTypes: true });
|
|
13304
13542
|
for (const entry of entries) {
|
|
13305
13543
|
if (!entry.isDirectory()) continue;
|
|
13306
13544
|
if (specificSkill && entry.name !== specificSkill) continue;
|
|
13307
|
-
const srcSkill =
|
|
13308
|
-
const skillMd =
|
|
13545
|
+
const srcSkill = join19(universalDir, entry.name);
|
|
13546
|
+
const skillMd = join19(srcSkill, "SKILL.md");
|
|
13309
13547
|
try {
|
|
13310
13548
|
await fs27.access(skillMd);
|
|
13311
13549
|
} catch {
|
|
13312
13550
|
continue;
|
|
13313
13551
|
}
|
|
13314
|
-
const targetSkill =
|
|
13552
|
+
const targetSkill = join19(jai1SkillsDir, entry.name);
|
|
13315
13553
|
try {
|
|
13316
13554
|
await fs27.access(targetSkill);
|
|
13317
13555
|
} catch {
|
|
@@ -13350,7 +13588,7 @@ var SkillsService = class {
|
|
|
13350
13588
|
const target = this.getIDETarget(ide);
|
|
13351
13589
|
if (!target) continue;
|
|
13352
13590
|
for (const skill of skillsToSync) {
|
|
13353
|
-
const targetPath =
|
|
13591
|
+
const targetPath = join19(projectRoot, target.skillsPath, skill.slug);
|
|
13354
13592
|
try {
|
|
13355
13593
|
let status = "created";
|
|
13356
13594
|
try {
|
|
@@ -13414,7 +13652,7 @@ var SkillsService = class {
|
|
|
13414
13652
|
const entries = await fs27.readdir(dirPath, { withFileTypes: true });
|
|
13415
13653
|
for (const entry of entries) {
|
|
13416
13654
|
if (entry.isDirectory()) {
|
|
13417
|
-
count += await this.countFiles(
|
|
13655
|
+
count += await this.countFiles(join19(dirPath, entry.name));
|
|
13418
13656
|
} else {
|
|
13419
13657
|
count++;
|
|
13420
13658
|
}
|
|
@@ -13427,8 +13665,8 @@ var SkillsService = class {
|
|
|
13427
13665
|
async copyDir(source, target) {
|
|
13428
13666
|
const entries = await fs27.readdir(source, { withFileTypes: true });
|
|
13429
13667
|
for (const entry of entries) {
|
|
13430
|
-
const srcPath =
|
|
13431
|
-
const tgtPath =
|
|
13668
|
+
const srcPath = join19(source, entry.name);
|
|
13669
|
+
const tgtPath = join19(target, entry.name);
|
|
13432
13670
|
if (entry.isDirectory()) {
|
|
13433
13671
|
await fs27.mkdir(tgtPath, { recursive: true });
|
|
13434
13672
|
await this.copyDir(srcPath, tgtPath);
|
|
@@ -13441,7 +13679,7 @@ var SkillsService = class {
|
|
|
13441
13679
|
|
|
13442
13680
|
// src/commands/skills/find.ts
|
|
13443
13681
|
function createSkillsFindCommand() {
|
|
13444
|
-
return new
|
|
13682
|
+
return new Command71("find").description("Search for skills on server or npm").argument("<query>", "Search query").option("--skillsh", "Search on npm skills registry instead of Jai1 server").option("--all", "Search on both Jai1 server and npm").action(async (query, options) => {
|
|
13445
13683
|
const searchNpm = options.skillsh || options.all;
|
|
13446
13684
|
const searchServer = !options.skillsh || options.all;
|
|
13447
13685
|
if (searchServer) {
|
|
@@ -13450,19 +13688,19 @@ function createSkillsFindCommand() {
|
|
|
13450
13688
|
if (!config) {
|
|
13451
13689
|
throw new ValidationError('Ch\u01B0a x\xE1c th\u1EF1c. Ch\u1EA1y "jai1 auth" tr\u01B0\u1EDBc.');
|
|
13452
13690
|
}
|
|
13453
|
-
console.log(
|
|
13691
|
+
console.log(chalk36.cyan("\u{1F50D} \u0110ang t\xECm ki\u1EBFm tr\xEAn Jai1 server..."));
|
|
13454
13692
|
console.log();
|
|
13455
13693
|
const skillsService = new SkillsService();
|
|
13456
13694
|
const results = await skillsService.searchFromServer(config, query);
|
|
13457
13695
|
if (results.length === 0) {
|
|
13458
|
-
console.log(
|
|
13696
|
+
console.log(chalk36.yellow("Kh\xF4ng t\xECm th\u1EA5y skills n\xE0o tr\xEAn server."));
|
|
13459
13697
|
} else {
|
|
13460
13698
|
const table = new Table8({
|
|
13461
13699
|
head: [
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13700
|
+
chalk36.cyan("T\xEAn"),
|
|
13701
|
+
chalk36.cyan("M\xF4 t\u1EA3"),
|
|
13702
|
+
chalk36.cyan("Version"),
|
|
13703
|
+
chalk36.cyan("Downloads")
|
|
13466
13704
|
],
|
|
13467
13705
|
style: { head: [], border: ["gray"] },
|
|
13468
13706
|
colWidths: [25, 40, 10, 12]
|
|
@@ -13470,70 +13708,70 @@ function createSkillsFindCommand() {
|
|
|
13470
13708
|
for (const skill of results) {
|
|
13471
13709
|
const name = skill.filepath.replace("skills/", "");
|
|
13472
13710
|
table.push([
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13711
|
+
chalk36.white(name),
|
|
13712
|
+
chalk36.dim((skill.description || "").slice(0, 38)),
|
|
13713
|
+
chalk36.green(skill.version || "-"),
|
|
13714
|
+
chalk36.dim(String(skill.downloads || 0))
|
|
13477
13715
|
]);
|
|
13478
13716
|
}
|
|
13479
|
-
console.log(
|
|
13717
|
+
console.log(chalk36.bold(`\u{1F4E6} Jai1 Server (${results.length} k\u1EBFt qu\u1EA3)`));
|
|
13480
13718
|
console.log(table.toString());
|
|
13481
13719
|
console.log();
|
|
13482
13720
|
}
|
|
13483
13721
|
}
|
|
13484
13722
|
if (searchNpm) {
|
|
13485
|
-
console.log(
|
|
13723
|
+
console.log(chalk36.cyan("\u{1F50D} \u0110ang t\xECm ki\u1EBFm tr\xEAn npm skills..."));
|
|
13486
13724
|
console.log();
|
|
13487
13725
|
const skillsService = new SkillsService();
|
|
13488
13726
|
try {
|
|
13489
13727
|
const output = await skillsService.npmSkillsFind(query);
|
|
13490
|
-
console.log(
|
|
13728
|
+
console.log(chalk36.bold("\u{1F310} npm Skills Registry"));
|
|
13491
13729
|
console.log(output);
|
|
13492
13730
|
} catch (error) {
|
|
13493
|
-
console.log(
|
|
13731
|
+
console.log(chalk36.yellow(
|
|
13494
13732
|
`Kh\xF4ng th\u1EC3 t\xECm ki\u1EBFm tr\xEAn npm: ${error instanceof Error ? error.message : String(error)}`
|
|
13495
13733
|
));
|
|
13496
13734
|
}
|
|
13497
13735
|
}
|
|
13498
13736
|
if (searchServer && !searchNpm) {
|
|
13499
|
-
console.log(
|
|
13737
|
+
console.log(chalk36.dim('\u{1F4A1} D\xF9ng "j skills add <t\xEAn>" \u0111\u1EC3 c\xE0i \u0111\u1EB7t t\u1EEB server'));
|
|
13500
13738
|
} else if (searchNpm && !searchServer) {
|
|
13501
|
-
console.log(
|
|
13739
|
+
console.log(chalk36.dim('\u{1F4A1} D\xF9ng "j skills add <owner/repo@skill> --skillsh" \u0111\u1EC3 c\xE0i \u0111\u1EB7t'));
|
|
13502
13740
|
} else {
|
|
13503
|
-
console.log(
|
|
13504
|
-
console.log(
|
|
13505
|
-
console.log(
|
|
13741
|
+
console.log(chalk36.dim("\u{1F4A1} C\xE0i \u0111\u1EB7t:"));
|
|
13742
|
+
console.log(chalk36.dim(" Server: j skills add <t\xEAn>"));
|
|
13743
|
+
console.log(chalk36.dim(" Skills.sh: j skills add <owner/repo@skill> --skillsh"));
|
|
13506
13744
|
}
|
|
13507
13745
|
});
|
|
13508
13746
|
}
|
|
13509
13747
|
|
|
13510
13748
|
// src/commands/skills/add.ts
|
|
13511
|
-
import { Command as
|
|
13512
|
-
import { join as
|
|
13513
|
-
import
|
|
13749
|
+
import { Command as Command72 } from "commander";
|
|
13750
|
+
import { join as join20 } from "path";
|
|
13751
|
+
import chalk37 from "chalk";
|
|
13514
13752
|
import { checkbox as checkbox7 } from "@inquirer/prompts";
|
|
13515
13753
|
function createSkillsAddCommand() {
|
|
13516
|
-
return new
|
|
13754
|
+
return new Command72("add").description("Install a skill to .jai1/skills/").argument("<name>", "Skill name or source (npm: GitHub shorthand, URL)").option("--skillsh", "Install from npm skills registry instead of Jai1 server").option("--sync", "Auto-sync to IDE(s) after install").option("--ides <ides...>", "Target IDEs for sync (cursor, windsurf, antigravity, claudecode, opencode)").option("--all", "Sync to all available IDEs").option("-y, --yes", "Headless mode (skip all prompts)").action(async (name, options) => {
|
|
13517
13755
|
const skillsService = new SkillsService();
|
|
13518
13756
|
const projectRoot = process.cwd();
|
|
13519
13757
|
const headless = options.yes === true;
|
|
13520
13758
|
if (options.skillsh) {
|
|
13521
|
-
console.log(
|
|
13759
|
+
console.log(chalk37.cyan(`\u{1F310} \u0110ang c\xE0i \u0111\u1EB7t skill t\u1EEB npm: ${name}...`));
|
|
13522
13760
|
console.log();
|
|
13523
13761
|
const output = await skillsService.npmSkillsAdd(name, projectRoot);
|
|
13524
13762
|
console.log(output);
|
|
13525
|
-
console.log(
|
|
13763
|
+
console.log(chalk37.green("\u2705 C\xE0i \u0111\u1EB7t t\u1EEB npm th\xE0nh c\xF4ng!"));
|
|
13526
13764
|
} else {
|
|
13527
13765
|
const configService = new ConfigService();
|
|
13528
13766
|
const config = await configService.load();
|
|
13529
13767
|
if (!config) {
|
|
13530
13768
|
throw new ValidationError('Ch\u01B0a x\xE1c th\u1EF1c. Ch\u1EA1y "jai1 auth" tr\u01B0\u1EDBc.');
|
|
13531
13769
|
}
|
|
13532
|
-
console.log(
|
|
13770
|
+
console.log(chalk37.cyan(`\u{1F4E6} \u0110ang c\xE0i \u0111\u1EB7t skill: ${name}...`));
|
|
13533
13771
|
console.log();
|
|
13534
|
-
const targetDir =
|
|
13772
|
+
const targetDir = join20(projectRoot, ".jai1");
|
|
13535
13773
|
await skillsService.installFromServer(config, name, targetDir);
|
|
13536
|
-
console.log(
|
|
13774
|
+
console.log(chalk37.green(`\u2705 \u0110\xE3 c\xE0i \u0111\u1EB7t skill "${name}" v\xE0o .jai1/skills/${name}/`));
|
|
13537
13775
|
}
|
|
13538
13776
|
console.log();
|
|
13539
13777
|
if (options.sync) {
|
|
@@ -13558,7 +13796,7 @@ function createSkillsAddCommand() {
|
|
|
13558
13796
|
});
|
|
13559
13797
|
}
|
|
13560
13798
|
if (selectedIdes.length > 0) {
|
|
13561
|
-
console.log(
|
|
13799
|
+
console.log(chalk37.cyan("\u{1F504} \u0110ang sync sang IDE(s)..."));
|
|
13562
13800
|
console.log();
|
|
13563
13801
|
const slug = name.includes("/") ? name.split("/").pop() : name;
|
|
13564
13802
|
const result = await skillsService.syncToIdes(
|
|
@@ -13571,24 +13809,24 @@ function createSkillsAddCommand() {
|
|
|
13571
13809
|
}
|
|
13572
13810
|
);
|
|
13573
13811
|
console.log();
|
|
13574
|
-
console.log(
|
|
13812
|
+
console.log(chalk37.green(`\u2705 Sync ho\xE0n t\u1EA5t! Created: ${result.created}, Updated: ${result.updated}`));
|
|
13575
13813
|
if (result.errors > 0) {
|
|
13576
|
-
console.log(
|
|
13814
|
+
console.log(chalk37.yellow(`\u26A0\uFE0F Errors: ${result.errors}`));
|
|
13577
13815
|
}
|
|
13578
13816
|
}
|
|
13579
13817
|
} else {
|
|
13580
|
-
console.log(
|
|
13581
|
-
console.log(
|
|
13818
|
+
console.log(chalk37.dim('\u{1F4A1} Ch\u1EA1y "j skills sync" \u0111\u1EC3 \u0111\u1ED3ng b\u1ED9 sang IDE(s)'));
|
|
13819
|
+
console.log(chalk37.dim(' ho\u1EB7c "j ide sync" \u0111\u1EC3 sync to\xE0n b\u1ED9 .jai1/'));
|
|
13582
13820
|
}
|
|
13583
13821
|
});
|
|
13584
13822
|
}
|
|
13585
13823
|
|
|
13586
13824
|
// src/commands/skills/list.ts
|
|
13587
|
-
import { Command as
|
|
13588
|
-
import
|
|
13825
|
+
import { Command as Command73 } from "commander";
|
|
13826
|
+
import chalk38 from "chalk";
|
|
13589
13827
|
import Table9 from "cli-table3";
|
|
13590
13828
|
function createSkillsListCommand() {
|
|
13591
|
-
return new
|
|
13829
|
+
return new Command73("list").description("List installed skills or available skills on server").option("--available", "List all skills available on Jai1 server").option("-s, --search <term>", "Search skills by name or description").action(async (options) => {
|
|
13592
13830
|
const skillsService = new SkillsService();
|
|
13593
13831
|
if (options.available) {
|
|
13594
13832
|
const configService = new ConfigService();
|
|
@@ -13596,19 +13834,19 @@ function createSkillsListCommand() {
|
|
|
13596
13834
|
if (!config) {
|
|
13597
13835
|
throw new ValidationError('Ch\u01B0a x\xE1c th\u1EF1c. Ch\u1EA1y "jai1 auth" tr\u01B0\u1EDBc.');
|
|
13598
13836
|
}
|
|
13599
|
-
console.log(
|
|
13837
|
+
console.log(chalk38.cyan("\u{1F4E6} \u0110ang t\u1EA3i danh s\xE1ch skills t\u1EEB server..."));
|
|
13600
13838
|
console.log();
|
|
13601
13839
|
const results = await skillsService.searchFromServer(config, options.search);
|
|
13602
13840
|
if (results.length === 0) {
|
|
13603
|
-
console.log(
|
|
13841
|
+
console.log(chalk38.yellow("Kh\xF4ng t\xECm th\u1EA5y skills n\xE0o."));
|
|
13604
13842
|
return;
|
|
13605
13843
|
}
|
|
13606
13844
|
const table = new Table9({
|
|
13607
13845
|
head: [
|
|
13608
|
-
|
|
13609
|
-
|
|
13610
|
-
|
|
13611
|
-
|
|
13846
|
+
chalk38.cyan("T\xEAn"),
|
|
13847
|
+
chalk38.cyan("M\xF4 t\u1EA3"),
|
|
13848
|
+
chalk38.cyan("Version"),
|
|
13849
|
+
chalk38.cyan("Downloads")
|
|
13612
13850
|
],
|
|
13613
13851
|
style: { head: [], border: ["gray"] },
|
|
13614
13852
|
colWidths: [28, 40, 10, 12]
|
|
@@ -13616,63 +13854,63 @@ function createSkillsListCommand() {
|
|
|
13616
13854
|
for (const skill of results) {
|
|
13617
13855
|
const name = skill.filepath.replace("skills/", "");
|
|
13618
13856
|
table.push([
|
|
13619
|
-
|
|
13620
|
-
|
|
13621
|
-
|
|
13622
|
-
|
|
13857
|
+
chalk38.white(name),
|
|
13858
|
+
chalk38.dim((skill.description || "").slice(0, 38)),
|
|
13859
|
+
chalk38.green(skill.version || "-"),
|
|
13860
|
+
chalk38.dim(String(skill.downloads || 0))
|
|
13623
13861
|
]);
|
|
13624
13862
|
}
|
|
13625
13863
|
console.log(table.toString());
|
|
13626
13864
|
console.log();
|
|
13627
|
-
console.log(
|
|
13628
|
-
console.log(
|
|
13865
|
+
console.log(chalk38.dim(`T\u1ED5ng c\u1ED9ng: ${results.length} skill(s)`));
|
|
13866
|
+
console.log(chalk38.dim('\n\u{1F4A1} D\xF9ng "j skills add <t\xEAn>" \u0111\u1EC3 c\xE0i \u0111\u1EB7t'));
|
|
13629
13867
|
} else {
|
|
13630
13868
|
const projectRoot = process.cwd();
|
|
13631
13869
|
const skills = await skillsService.listLocal(projectRoot);
|
|
13632
13870
|
if (skills.length === 0) {
|
|
13633
|
-
console.log(
|
|
13871
|
+
console.log(chalk38.yellow("Ch\u01B0a c\xF3 skills n\xE0o \u0111\u01B0\u1EE3c c\xE0i \u0111\u1EB7t."));
|
|
13634
13872
|
console.log();
|
|
13635
|
-
console.log(
|
|
13636
|
-
console.log(
|
|
13873
|
+
console.log(chalk38.dim('\u{1F4A1} D\xF9ng "j skills add <t\xEAn>" \u0111\u1EC3 c\xE0i \u0111\u1EB7t'));
|
|
13874
|
+
console.log(chalk38.dim(' ho\u1EB7c "j skills list --available" \u0111\u1EC3 xem skills c\xF3 s\u1EB5n'));
|
|
13637
13875
|
return;
|
|
13638
13876
|
}
|
|
13639
|
-
console.log(
|
|
13877
|
+
console.log(chalk38.bold.cyan("\u{1F6E0} Skills \u0111\xE3 c\xE0i \u0111\u1EB7t"));
|
|
13640
13878
|
console.log();
|
|
13641
13879
|
const table = new Table9({
|
|
13642
13880
|
head: [
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13881
|
+
chalk38.cyan("T\xEAn"),
|
|
13882
|
+
chalk38.cyan("M\xF4 t\u1EA3"),
|
|
13883
|
+
chalk38.cyan("Files")
|
|
13646
13884
|
],
|
|
13647
13885
|
style: { head: [], border: ["gray"] },
|
|
13648
13886
|
colWidths: [28, 45, 8]
|
|
13649
13887
|
});
|
|
13650
13888
|
for (const skill of skills) {
|
|
13651
13889
|
table.push([
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13890
|
+
chalk38.white(skill.slug),
|
|
13891
|
+
chalk38.dim(skill.description.slice(0, 43)),
|
|
13892
|
+
chalk38.dim(String(skill.fileCount))
|
|
13655
13893
|
]);
|
|
13656
13894
|
}
|
|
13657
13895
|
console.log(table.toString());
|
|
13658
13896
|
console.log();
|
|
13659
|
-
console.log(
|
|
13660
|
-
console.log(
|
|
13897
|
+
console.log(chalk38.dim(`T\u1ED5ng c\u1ED9ng: ${skills.length} skill(s)`));
|
|
13898
|
+
console.log(chalk38.dim('\n\u{1F4A1} D\xF9ng "j skills sync" \u0111\u1EC3 \u0111\u1ED3ng b\u1ED9 sang IDE(s)'));
|
|
13661
13899
|
}
|
|
13662
13900
|
});
|
|
13663
13901
|
}
|
|
13664
13902
|
|
|
13665
13903
|
// src/commands/skills/info.ts
|
|
13666
|
-
import { Command as
|
|
13667
|
-
import
|
|
13904
|
+
import { Command as Command74 } from "commander";
|
|
13905
|
+
import chalk39 from "chalk";
|
|
13668
13906
|
function createSkillsInfoCommand() {
|
|
13669
|
-
return new
|
|
13907
|
+
return new Command74("info").description("Show detailed information about a skill").argument("<name>", "Skill name").option("--server", "Show info from Jai1 server instead of local").action(async (name, options) => {
|
|
13670
13908
|
const skillsService = new SkillsService();
|
|
13671
13909
|
if (options.server) {
|
|
13672
13910
|
const configService = new ConfigService();
|
|
13673
13911
|
const config = await configService.load();
|
|
13674
13912
|
if (!config) {
|
|
13675
|
-
console.log(
|
|
13913
|
+
console.log(chalk39.red('\u274C Ch\u01B0a x\xE1c th\u1EF1c. Ch\u1EA1y "jai1 auth" tr\u01B0\u1EDBc.'));
|
|
13676
13914
|
process.exit(1);
|
|
13677
13915
|
}
|
|
13678
13916
|
const filepath = name.startsWith("skills/") ? name : `skills/${name}`;
|
|
@@ -13681,7 +13919,7 @@ function createSkillsInfoCommand() {
|
|
|
13681
13919
|
try {
|
|
13682
13920
|
const component = await componentsService.get(config, filepath);
|
|
13683
13921
|
console.log(`
|
|
13684
|
-
\u{1F6E0} ${
|
|
13922
|
+
\u{1F6E0} ${chalk39.bold(component.name || name)}
|
|
13685
13923
|
`);
|
|
13686
13924
|
console.log(`Filepath: ${component.filepath}`);
|
|
13687
13925
|
console.log(`Version: ${component.version}`);
|
|
@@ -13694,47 +13932,47 @@ function createSkillsInfoCommand() {
|
|
|
13694
13932
|
}
|
|
13695
13933
|
console.log(`Type: ${component.contentType}`);
|
|
13696
13934
|
console.log();
|
|
13697
|
-
console.log(
|
|
13935
|
+
console.log(chalk39.dim('\u{1F4A1} D\xF9ng "j skills add ' + name + '" \u0111\u1EC3 c\xE0i \u0111\u1EB7t'));
|
|
13698
13936
|
} catch (error) {
|
|
13699
|
-
console.log(
|
|
13937
|
+
console.log(chalk39.red(`\u274C Kh\xF4ng t\xECm th\u1EA5y skill "${name}" tr\xEAn server.`));
|
|
13700
13938
|
process.exit(1);
|
|
13701
13939
|
}
|
|
13702
13940
|
} else {
|
|
13703
13941
|
const projectRoot = process.cwd();
|
|
13704
13942
|
const skill = await skillsService.getSkillInfo(projectRoot, name);
|
|
13705
13943
|
if (!skill) {
|
|
13706
|
-
console.log(
|
|
13707
|
-
console.log(
|
|
13708
|
-
console.log(
|
|
13944
|
+
console.log(chalk39.red(`\u274C Skill "${name}" ch\u01B0a \u0111\u01B0\u1EE3c c\xE0i \u0111\u1EB7t.`));
|
|
13945
|
+
console.log(chalk39.dim('\u{1F4A1} D\xF9ng "j skills info ' + name + ' --server" \u0111\u1EC3 xem tr\xEAn server'));
|
|
13946
|
+
console.log(chalk39.dim(' ho\u1EB7c "j skills add ' + name + '" \u0111\u1EC3 c\xE0i \u0111\u1EB7t'));
|
|
13709
13947
|
process.exit(1);
|
|
13710
13948
|
}
|
|
13711
13949
|
console.log(`
|
|
13712
|
-
\u{1F6E0} ${
|
|
13950
|
+
\u{1F6E0} ${chalk39.bold(skill.name)}
|
|
13713
13951
|
`);
|
|
13714
13952
|
console.log(`Slug: ${skill.slug}`);
|
|
13715
|
-
console.log(`Description: ${skill.description ||
|
|
13953
|
+
console.log(`Description: ${skill.description || chalk39.dim("(none)")}`);
|
|
13716
13954
|
console.log(`Path: ${skill.path}`);
|
|
13717
13955
|
console.log(`Files: ${skill.fileCount}`);
|
|
13718
13956
|
console.log();
|
|
13719
|
-
console.log(
|
|
13957
|
+
console.log(chalk39.dim('\u{1F4A1} D\xF9ng "j skills sync" \u0111\u1EC3 \u0111\u1ED3ng b\u1ED9 sang IDE(s)'));
|
|
13720
13958
|
}
|
|
13721
13959
|
});
|
|
13722
13960
|
}
|
|
13723
13961
|
|
|
13724
13962
|
// src/commands/skills/sync.ts
|
|
13725
|
-
import { Command as
|
|
13726
|
-
import
|
|
13963
|
+
import { Command as Command75 } from "commander";
|
|
13964
|
+
import chalk40 from "chalk";
|
|
13727
13965
|
import { confirm as confirm15, checkbox as checkbox8 } from "@inquirer/prompts";
|
|
13728
13966
|
function createSkillsSyncCommand() {
|
|
13729
|
-
return new
|
|
13967
|
+
return new Command75("sync").description("Sync skills from .jai1/skills/ to IDE directories").option("--ides <ides...>", "Target IDEs (cursor, windsurf, antigravity, claudecode, opencode)").option("--skills <skills...>", "Specific skill slugs to sync (default: all)").option("--all", "Select all available IDEs").option("--dry-run", "Preview changes without writing files").option("-y, --yes", "Headless mode (skip all prompts)").action(async (options) => {
|
|
13730
13968
|
const skillsService = new SkillsService();
|
|
13731
13969
|
const projectRoot = process.cwd();
|
|
13732
13970
|
const headless = options.yes === true;
|
|
13733
|
-
console.log(
|
|
13971
|
+
console.log(chalk40.bold.cyan("\n\u{1F504} Sync skills sang IDE(s)\n"));
|
|
13734
13972
|
const localSkills = await skillsService.listLocal(projectRoot);
|
|
13735
13973
|
if (localSkills.length === 0) {
|
|
13736
|
-
console.log(
|
|
13737
|
-
console.log(
|
|
13974
|
+
console.log(chalk40.yellow("\u26A0\uFE0F Kh\xF4ng c\xF3 skills n\xE0o trong .jai1/skills/"));
|
|
13975
|
+
console.log(chalk40.dim('\u{1F4A1} Ch\u1EA1y "j skills add <t\xEAn>" \u0111\u1EC3 c\xE0i \u0111\u1EB7t skills tr\u01B0\u1EDBc'));
|
|
13738
13976
|
process.exit(1);
|
|
13739
13977
|
}
|
|
13740
13978
|
console.log(`\u{1F4C1} T\xECm th\u1EA5y ${localSkills.length} skill(s) trong .jai1/skills/`);
|
|
@@ -13766,7 +14004,7 @@ function createSkillsSyncCommand() {
|
|
|
13766
14004
|
theme: checkboxTheme
|
|
13767
14005
|
});
|
|
13768
14006
|
if (selectedIdes.length === 0) {
|
|
13769
|
-
console.log(
|
|
14007
|
+
console.log(chalk40.yellow("\n\u26A0\uFE0F Ch\u01B0a ch\u1ECDn IDE n\xE0o!"));
|
|
13770
14008
|
process.exit(0);
|
|
13771
14009
|
}
|
|
13772
14010
|
}
|
|
@@ -13781,7 +14019,7 @@ function createSkillsSyncCommand() {
|
|
|
13781
14019
|
console.log(` Total: ${totalFiles} skill folder(s) s\u1EBD \u0111\u01B0\u1EE3c sync
|
|
13782
14020
|
`);
|
|
13783
14021
|
if (options.dryRun) {
|
|
13784
|
-
console.log(
|
|
14022
|
+
console.log(chalk40.dim("\u{1F50D} DRY RUN - Kh\xF4ng c\xF3 file n\xE0o \u0111\u01B0\u1EE3c ghi\n"));
|
|
13785
14023
|
return;
|
|
13786
14024
|
}
|
|
13787
14025
|
if (!headless) {
|
|
@@ -13790,25 +14028,25 @@ function createSkillsSyncCommand() {
|
|
|
13790
14028
|
default: true
|
|
13791
14029
|
});
|
|
13792
14030
|
if (!confirmed) {
|
|
13793
|
-
console.log(
|
|
14031
|
+
console.log(chalk40.yellow("\n\u274C \u0110\xE3 h\u1EE7y sync.\n"));
|
|
13794
14032
|
process.exit(0);
|
|
13795
14033
|
}
|
|
13796
14034
|
}
|
|
13797
|
-
console.log(
|
|
14035
|
+
console.log(chalk40.cyan("\n\u{1F504} \u0110ang sync...\n"));
|
|
13798
14036
|
const result = await skillsService.syncToIdes(
|
|
13799
14037
|
projectRoot,
|
|
13800
14038
|
selectedIdes,
|
|
13801
14039
|
selectedSlugs,
|
|
13802
14040
|
(res) => {
|
|
13803
14041
|
const icon = res.status === "created" ? "\u2713" : res.status === "updated" ? "\u21BB" : "\u2717";
|
|
13804
|
-
const statusColor = res.status === "error" ?
|
|
13805
|
-
console.log(` ${statusColor(icon)} ${res.ide}: ${res.skill} \u2192 ${
|
|
14042
|
+
const statusColor = res.status === "error" ? chalk40.red : chalk40.green;
|
|
14043
|
+
console.log(` ${statusColor(icon)} ${res.ide}: ${res.skill} \u2192 ${chalk40.dim(res.path)}`);
|
|
13806
14044
|
if (res.status === "error" && res.error) {
|
|
13807
|
-
console.log(` ${
|
|
14045
|
+
console.log(` ${chalk40.red("Error:")} ${res.error}`);
|
|
13808
14046
|
}
|
|
13809
14047
|
}
|
|
13810
14048
|
);
|
|
13811
|
-
console.log(
|
|
14049
|
+
console.log(chalk40.green("\n\u2705 Sync ho\xE0n t\u1EA5t!\n"));
|
|
13812
14050
|
console.log(` Created: ${result.created}`);
|
|
13813
14051
|
console.log(` Updated: ${result.updated}`);
|
|
13814
14052
|
if (result.errors > 0) {
|
|
@@ -13821,27 +14059,27 @@ function createSkillsSyncCommand() {
|
|
|
13821
14059
|
// src/commands/skills/index.ts
|
|
13822
14060
|
function showSkillsHelp() {
|
|
13823
14061
|
const cli = getCliName();
|
|
13824
|
-
console.log(
|
|
14062
|
+
console.log(chalk41.bold.cyan("\u{1F6E0} " + cli + " skills") + chalk41.dim(" - Qu\u1EA3n l\xFD agent skills"));
|
|
13825
14063
|
console.log();
|
|
13826
|
-
console.log(
|
|
13827
|
-
console.log(` ${
|
|
13828
|
-
console.log(` ${
|
|
13829
|
-
console.log(` ${
|
|
13830
|
-
console.log(` ${
|
|
13831
|
-
console.log(` ${
|
|
14064
|
+
console.log(chalk41.bold("C\xE1c l\u1EC7nh:"));
|
|
14065
|
+
console.log(` ${chalk41.cyan("find")} T\xECm ki\u1EBFm skills tr\xEAn server ho\u1EB7c npm`);
|
|
14066
|
+
console.log(` ${chalk41.cyan("add")} C\xE0i \u0111\u1EB7t skill v\xE0o .jai1/skills/`);
|
|
14067
|
+
console.log(` ${chalk41.cyan("list")} Li\u1EC7t k\xEA skills \u0111\xE3 c\xE0i ho\u1EB7c c\xF3 s\u1EB5n`);
|
|
14068
|
+
console.log(` ${chalk41.cyan("info")} Xem chi ti\u1EBFt m\u1ED9t skill`);
|
|
14069
|
+
console.log(` ${chalk41.cyan("sync")} \u0110\u1ED3ng b\u1ED9 skills sang c\xE1c IDE`);
|
|
13832
14070
|
console.log();
|
|
13833
|
-
console.log(
|
|
13834
|
-
console.log(
|
|
13835
|
-
console.log(
|
|
13836
|
-
console.log(
|
|
13837
|
-
console.log(
|
|
13838
|
-
console.log(
|
|
13839
|
-
console.log(
|
|
14071
|
+
console.log(chalk41.bold("V\xED d\u1EE5:"));
|
|
14072
|
+
console.log(chalk41.dim(` $ ${cli} skills find audit`));
|
|
14073
|
+
console.log(chalk41.dim(` $ ${cli} skills find "react" --skillsh`));
|
|
14074
|
+
console.log(chalk41.dim(` $ ${cli} skills add brainstorming`));
|
|
14075
|
+
console.log(chalk41.dim(` $ ${cli} skills add vercel/next-skills --skillsh`));
|
|
14076
|
+
console.log(chalk41.dim(` $ ${cli} skills list`));
|
|
14077
|
+
console.log(chalk41.dim(` $ ${cli} skills sync --all -y`));
|
|
13840
14078
|
console.log();
|
|
13841
|
-
console.log(
|
|
14079
|
+
console.log(chalk41.dim(`Ch\u1EA1y "${cli} skills <l\u1EC7nh> --help" \u0111\u1EC3 xem chi ti\u1EBFt`));
|
|
13842
14080
|
}
|
|
13843
14081
|
function createSkillsCommand() {
|
|
13844
|
-
const cmd = new
|
|
14082
|
+
const cmd = new Command76("skills").alias("s").description("Manage agent skills (search, install, sync to IDEs)").action(() => {
|
|
13845
14083
|
showSkillsHelp();
|
|
13846
14084
|
});
|
|
13847
14085
|
cmd.addCommand(createSkillsFindCommand());
|
|
@@ -13853,9 +14091,9 @@ function createSkillsCommand() {
|
|
|
13853
14091
|
}
|
|
13854
14092
|
|
|
13855
14093
|
// src/commands/upgrade.ts
|
|
13856
|
-
import { Command as
|
|
14094
|
+
import { Command as Command77 } from "commander";
|
|
13857
14095
|
import { confirm as confirm16 } from "@inquirer/prompts";
|
|
13858
|
-
import { execSync as
|
|
14096
|
+
import { execSync as execSync5 } from "child_process";
|
|
13859
14097
|
var colors2 = {
|
|
13860
14098
|
yellow: "\x1B[33m",
|
|
13861
14099
|
green: "\x1B[32m",
|
|
@@ -13865,7 +14103,7 @@ var colors2 = {
|
|
|
13865
14103
|
bold: "\x1B[1m"
|
|
13866
14104
|
};
|
|
13867
14105
|
function createUpgradeCommand() {
|
|
13868
|
-
return new
|
|
14106
|
+
return new Command77("upgrade").description("Upgrade CLI client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force upgrade without confirmation").action(async (options) => {
|
|
13869
14107
|
await handleUpgrade(options);
|
|
13870
14108
|
});
|
|
13871
14109
|
}
|
|
@@ -13926,7 +14164,7 @@ ${colors2.cyan}\u{1F4E5} Installing latest version...${colors2.reset}
|
|
|
13926
14164
|
const packageManager2 = detectPackageManager();
|
|
13927
14165
|
const installCommand = getInstallCommand(packageManager2);
|
|
13928
14166
|
console.log(`${colors2.cyan}Using ${packageManager2}...${colors2.reset}`);
|
|
13929
|
-
|
|
14167
|
+
execSync5(installCommand, {
|
|
13930
14168
|
stdio: "inherit",
|
|
13931
14169
|
env: { ...process.env, FORCE_COLOR: "1" }
|
|
13932
14170
|
});
|
|
@@ -13975,7 +14213,7 @@ function isNewerVersion2(remote, local) {
|
|
|
13975
14213
|
}
|
|
13976
14214
|
function detectPackageManager() {
|
|
13977
14215
|
try {
|
|
13978
|
-
const jai1Path =
|
|
14216
|
+
const jai1Path = execSync5("which j || which jai1 || where j || where jai1", {
|
|
13979
14217
|
encoding: "utf-8",
|
|
13980
14218
|
stdio: ["pipe", "pipe", "ignore"]
|
|
13981
14219
|
}).trim();
|
|
@@ -14013,11 +14251,11 @@ function getInstallCommand(packageManager2) {
|
|
|
14013
14251
|
}
|
|
14014
14252
|
|
|
14015
14253
|
// src/commands/clean.ts
|
|
14016
|
-
import { Command as
|
|
14254
|
+
import { Command as Command78 } from "commander";
|
|
14017
14255
|
import { confirm as confirm17, select as select6 } from "@inquirer/prompts";
|
|
14018
|
-
import { join as
|
|
14256
|
+
import { join as join21 } from "path";
|
|
14019
14257
|
function createCleanCommand() {
|
|
14020
|
-
return new
|
|
14258
|
+
return new Command78("clean").description("Clean up backups, cache, and temporary files").option("-y, --yes", "Skip confirmation").option("--backups", "Clean only backup files").option("--all", "Clean all (backups + cache)").action(async (options) => {
|
|
14021
14259
|
await handleClean(options);
|
|
14022
14260
|
});
|
|
14023
14261
|
}
|
|
@@ -14028,7 +14266,7 @@ async function handleClean(options) {
|
|
|
14028
14266
|
{
|
|
14029
14267
|
name: "Backups",
|
|
14030
14268
|
description: "Component backup files (.jai1_backup/)",
|
|
14031
|
-
path:
|
|
14269
|
+
path: join21(cwd, ".jai1_backup"),
|
|
14032
14270
|
check: async () => {
|
|
14033
14271
|
const backups = await service.listBackups(cwd);
|
|
14034
14272
|
return { exists: backups.length > 0, count: backups.length };
|
|
@@ -14131,7 +14369,7 @@ async function cleanTarget(target, skipConfirm) {
|
|
|
14131
14369
|
}
|
|
14132
14370
|
|
|
14133
14371
|
// src/commands/redmine/check.ts
|
|
14134
|
-
import { Command as
|
|
14372
|
+
import { Command as Command79 } from "commander";
|
|
14135
14373
|
|
|
14136
14374
|
// src/services/redmine-config.service.ts
|
|
14137
14375
|
import { readFile as readFile7 } from "fs/promises";
|
|
@@ -14438,7 +14676,7 @@ async function checkConnectivity(config) {
|
|
|
14438
14676
|
|
|
14439
14677
|
// src/commands/redmine/check.ts
|
|
14440
14678
|
function createRedmineCheckCommand() {
|
|
14441
|
-
const cmd = new
|
|
14679
|
+
const cmd = new Command79("check").description("Check Redmine connectivity").option("-c, --config <path>", "Config file path", "redmine.config.yaml").option("--json", "Output as JSON").action(async (options) => {
|
|
14442
14680
|
await handleRedmineCheck(options);
|
|
14443
14681
|
});
|
|
14444
14682
|
return cmd;
|
|
@@ -14466,7 +14704,7 @@ async function handleRedmineCheck(options) {
|
|
|
14466
14704
|
}
|
|
14467
14705
|
|
|
14468
14706
|
// src/commands/redmine/sync-issue.ts
|
|
14469
|
-
import { Command as
|
|
14707
|
+
import { Command as Command80 } from "commander";
|
|
14470
14708
|
|
|
14471
14709
|
// src/sync-issue.ts
|
|
14472
14710
|
import { resolve as resolve3, relative } from "path";
|
|
@@ -14850,7 +15088,7 @@ function extractIssueIdFromUrl(url) {
|
|
|
14850
15088
|
|
|
14851
15089
|
// src/commands/redmine/sync-issue.ts
|
|
14852
15090
|
function createSyncIssueCommand() {
|
|
14853
|
-
const cmd = new
|
|
15091
|
+
const cmd = new Command80("issue").description("Sync a single issue").option("-i, --id <number>", "Issue ID").option("-u, --url <url>", "Issue URL").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
|
|
14854
15092
|
await handleSyncIssue(options);
|
|
14855
15093
|
});
|
|
14856
15094
|
return cmd;
|
|
@@ -14894,7 +15132,7 @@ async function handleSyncIssue(options) {
|
|
|
14894
15132
|
}
|
|
14895
15133
|
|
|
14896
15134
|
// src/commands/redmine/sync-project.ts
|
|
14897
|
-
import { Command as
|
|
15135
|
+
import { Command as Command81 } from "commander";
|
|
14898
15136
|
|
|
14899
15137
|
// src/sync-project.ts
|
|
14900
15138
|
async function syncProject(config, options = {}) {
|
|
@@ -14964,7 +15202,7 @@ async function syncProject(config, options = {}) {
|
|
|
14964
15202
|
|
|
14965
15203
|
// src/commands/redmine/sync-project.ts
|
|
14966
15204
|
function createSyncProjectCommand() {
|
|
14967
|
-
const cmd = new
|
|
15205
|
+
const cmd = new Command81("project").description("Sync all issues in a project").option("-s, --status <status>", "Filter by status (default: *)", "*").option("--updated-since <date>", "Only sync issues updated since YYYY-MM-DD").option("--concurrency <number>", "Number of concurrent requests").option("--page-size <number>", "Page size for API requests").option("--dry-run", "Preview without making changes").option("-c, --config <path>", "Config file path").option("-o, --output-dir <path>", "Output directory").option("--json", "Output as JSON").action(async (options) => {
|
|
14968
15206
|
await handleSyncProject(options);
|
|
14969
15207
|
});
|
|
14970
15208
|
return cmd;
|
|
@@ -15019,12 +15257,12 @@ async function handleSyncProject(options) {
|
|
|
15019
15257
|
}
|
|
15020
15258
|
|
|
15021
15259
|
// src/commands/framework/info.ts
|
|
15022
|
-
import { Command as
|
|
15260
|
+
import { Command as Command82 } from "commander";
|
|
15023
15261
|
import { promises as fs28 } from "fs";
|
|
15024
|
-
import { join as
|
|
15262
|
+
import { join as join22 } from "path";
|
|
15025
15263
|
import { homedir as homedir5 } from "os";
|
|
15026
15264
|
function createInfoCommand() {
|
|
15027
|
-
const cmd = new
|
|
15265
|
+
const cmd = new Command82("info").description("Show client configuration and status").option("--json", "Output as JSON").option("--verbose", "Show detailed information").action(async (options) => {
|
|
15028
15266
|
await handleInfo(options);
|
|
15029
15267
|
});
|
|
15030
15268
|
return cmd;
|
|
@@ -15035,7 +15273,7 @@ async function handleInfo(options) {
|
|
|
15035
15273
|
if (!config) {
|
|
15036
15274
|
throw new ValidationError(`Not initialized. Run "${getCliName()} auth" first.`);
|
|
15037
15275
|
}
|
|
15038
|
-
const frameworkPath =
|
|
15276
|
+
const frameworkPath = join22(homedir5(), ".jai1", "framework");
|
|
15039
15277
|
const projectStatus = await getProjectStatus2();
|
|
15040
15278
|
const info = {
|
|
15041
15279
|
configPath: configService.getConfigPath(),
|
|
@@ -15070,7 +15308,7 @@ function maskKey4(key) {
|
|
|
15070
15308
|
return "****" + key.slice(-4);
|
|
15071
15309
|
}
|
|
15072
15310
|
async function getProjectStatus2() {
|
|
15073
|
-
const projectJai1 =
|
|
15311
|
+
const projectJai1 = join22(process.cwd(), ".jai1");
|
|
15074
15312
|
try {
|
|
15075
15313
|
await fs28.access(projectJai1);
|
|
15076
15314
|
return { exists: true, version: "Synced" };
|
|
@@ -15080,9 +15318,9 @@ async function getProjectStatus2() {
|
|
|
15080
15318
|
}
|
|
15081
15319
|
|
|
15082
15320
|
// src/commands/self-update.ts
|
|
15083
|
-
import { Command as
|
|
15321
|
+
import { Command as Command83 } from "commander";
|
|
15084
15322
|
import { confirm as confirm18 } from "@inquirer/prompts";
|
|
15085
|
-
import { execSync as
|
|
15323
|
+
import { execSync as execSync6 } from "child_process";
|
|
15086
15324
|
var colors3 = {
|
|
15087
15325
|
yellow: "\x1B[33m",
|
|
15088
15326
|
green: "\x1B[32m",
|
|
@@ -15092,7 +15330,7 @@ var colors3 = {
|
|
|
15092
15330
|
bold: "\x1B[1m"
|
|
15093
15331
|
};
|
|
15094
15332
|
function createSelfUpdateCommand() {
|
|
15095
|
-
return new
|
|
15333
|
+
return new Command83("self-update").description("Update CLI client to the latest version").option("--check", "Only check for updates without installing").option("--force", "Force update without confirmation").action(async (options) => {
|
|
15096
15334
|
await handleSelfUpdate(options);
|
|
15097
15335
|
});
|
|
15098
15336
|
}
|
|
@@ -15153,7 +15391,7 @@ ${colors3.cyan}\u{1F4E5} Installing latest version...${colors3.reset}
|
|
|
15153
15391
|
const packageManager2 = detectPackageManager2();
|
|
15154
15392
|
const installCommand = getInstallCommand2(packageManager2);
|
|
15155
15393
|
console.log(`${colors3.cyan}Using ${packageManager2}...${colors3.reset}`);
|
|
15156
|
-
|
|
15394
|
+
execSync6(installCommand, {
|
|
15157
15395
|
stdio: "inherit",
|
|
15158
15396
|
env: { ...process.env, FORCE_COLOR: "1" }
|
|
15159
15397
|
});
|
|
@@ -15200,17 +15438,17 @@ function detectPackageManager2() {
|
|
|
15200
15438
|
if (userAgent.includes("yarn")) return "yarn";
|
|
15201
15439
|
if (userAgent.includes("bun")) return "bun";
|
|
15202
15440
|
try {
|
|
15203
|
-
|
|
15441
|
+
execSync6("pnpm --version", { stdio: "ignore" });
|
|
15204
15442
|
return "pnpm";
|
|
15205
15443
|
} catch {
|
|
15206
15444
|
}
|
|
15207
15445
|
try {
|
|
15208
|
-
|
|
15446
|
+
execSync6("yarn --version", { stdio: "ignore" });
|
|
15209
15447
|
return "yarn";
|
|
15210
15448
|
} catch {
|
|
15211
15449
|
}
|
|
15212
15450
|
try {
|
|
15213
|
-
|
|
15451
|
+
execSync6("bun --version", { stdio: "ignore" });
|
|
15214
15452
|
return "bun";
|
|
15215
15453
|
} catch {
|
|
15216
15454
|
}
|
|
@@ -15232,10 +15470,10 @@ function getInstallCommand2(packageManager2) {
|
|
|
15232
15470
|
}
|
|
15233
15471
|
|
|
15234
15472
|
// src/commands/clear-backups.ts
|
|
15235
|
-
import { Command as
|
|
15473
|
+
import { Command as Command84 } from "commander";
|
|
15236
15474
|
import { confirm as confirm19 } from "@inquirer/prompts";
|
|
15237
15475
|
function createClearBackupsCommand() {
|
|
15238
|
-
return new
|
|
15476
|
+
return new Command84("clear-backups").description("Clear backup files").option("-y, --yes", "Skip confirmation").action(async (options) => {
|
|
15239
15477
|
const service = new ComponentsService();
|
|
15240
15478
|
const backups = await service.listBackups(process.cwd());
|
|
15241
15479
|
if (backups.length === 0) {
|
|
@@ -15260,11 +15498,11 @@ function createClearBackupsCommand() {
|
|
|
15260
15498
|
}
|
|
15261
15499
|
|
|
15262
15500
|
// src/commands/vscode/index.ts
|
|
15263
|
-
import { Command as
|
|
15501
|
+
import { Command as Command85 } from "commander";
|
|
15264
15502
|
import { checkbox as checkbox9, confirm as confirm20, select as select7 } from "@inquirer/prompts";
|
|
15265
15503
|
import fs29 from "fs/promises";
|
|
15266
15504
|
import path12 from "path";
|
|
15267
|
-
import { existsSync as
|
|
15505
|
+
import { existsSync as existsSync4 } from "fs";
|
|
15268
15506
|
var PERFORMANCE_GROUPS2 = {
|
|
15269
15507
|
telemetry: {
|
|
15270
15508
|
name: "Telemetry",
|
|
@@ -15400,7 +15638,7 @@ var PERFORMANCE_GROUPS2 = {
|
|
|
15400
15638
|
}
|
|
15401
15639
|
};
|
|
15402
15640
|
function createVSCodeCommand() {
|
|
15403
|
-
const vscodeCommand = new
|
|
15641
|
+
const vscodeCommand = new Command85("vscode").description("Qu\u1EA3n l\xFD c\xE0i \u0111\u1EB7t VSCode cho d\u1EF1 \xE1n hi\u1EC7n t\u1EA1i");
|
|
15404
15642
|
vscodeCommand.action(async () => {
|
|
15405
15643
|
await interactiveMode2();
|
|
15406
15644
|
});
|
|
@@ -15494,12 +15732,12 @@ async function applyGroups2(groupKeys, action) {
|
|
|
15494
15732
|
console.log(' \u{1F4A1} Ch\u1EA1y "jai1 vscode list" \u0111\u1EC3 xem danh s\xE1ch nh\xF3m c\xF3 s\u1EB5n.');
|
|
15495
15733
|
return;
|
|
15496
15734
|
}
|
|
15497
|
-
if (!
|
|
15735
|
+
if (!existsSync4(vscodeDir)) {
|
|
15498
15736
|
await fs29.mkdir(vscodeDir, { recursive: true });
|
|
15499
15737
|
console.log("\u{1F4C1} \u0110\xE3 t\u1EA1o th\u01B0 m\u1EE5c .vscode/");
|
|
15500
15738
|
}
|
|
15501
15739
|
let currentSettings = {};
|
|
15502
|
-
if (
|
|
15740
|
+
if (existsSync4(settingsPath)) {
|
|
15503
15741
|
try {
|
|
15504
15742
|
const content = await fs29.readFile(settingsPath, "utf-8");
|
|
15505
15743
|
currentSettings = JSON.parse(content);
|
|
@@ -15549,7 +15787,7 @@ async function applyGroups2(groupKeys, action) {
|
|
|
15549
15787
|
async function resetSettings2(groupKeys) {
|
|
15550
15788
|
const vscodeDir = path12.join(process.cwd(), ".vscode");
|
|
15551
15789
|
const settingsPath = path12.join(vscodeDir, "settings.json");
|
|
15552
|
-
if (!
|
|
15790
|
+
if (!existsSync4(settingsPath)) {
|
|
15553
15791
|
console.log("\n\u26A0\uFE0F Kh\xF4ng t\xECm th\u1EA5y file settings.json");
|
|
15554
15792
|
return;
|
|
15555
15793
|
}
|
|
@@ -15571,10 +15809,10 @@ async function resetSettings2(groupKeys) {
|
|
|
15571
15809
|
}
|
|
15572
15810
|
|
|
15573
15811
|
// src/commands/migrate-ide.ts
|
|
15574
|
-
import { Command as
|
|
15812
|
+
import { Command as Command86 } from "commander";
|
|
15575
15813
|
import { checkbox as checkbox10, confirm as confirm21 } from "@inquirer/prompts";
|
|
15576
15814
|
function createMigrateIdeCommand() {
|
|
15577
|
-
const cmd = new
|
|
15815
|
+
const cmd = new Command86("migrate-ide").description("Migrate .jai1 rules v\xE0 workflows sang IDEs (Cursor, Windsurf, Claude Code, etc.)").option("--ide <ides...>", "Target IDEs (cursor, windsurf, antigravity, claudecode, opencode)").option("--type <types...>", "Content types (rules, workflows, commands)").option("--dry-run", "Preview changes without writing files").action(async (options) => {
|
|
15578
15816
|
await runMigrateIde(options);
|
|
15579
15817
|
});
|
|
15580
15818
|
return cmd;
|
|
@@ -15683,20 +15921,20 @@ async function runMigrateIde(options) {
|
|
|
15683
15921
|
|
|
15684
15922
|
// src/utils/help-formatter.ts
|
|
15685
15923
|
import boxen4 from "boxen";
|
|
15686
|
-
import
|
|
15924
|
+
import chalk42 from "chalk";
|
|
15687
15925
|
import gradient from "gradient-string";
|
|
15688
15926
|
import figlet from "figlet";
|
|
15689
15927
|
function showCustomHelp(version) {
|
|
15690
15928
|
const title = figlet.textSync("JAI1", { font: "Small" });
|
|
15691
15929
|
console.log(gradient.pastel(title));
|
|
15692
15930
|
console.log(
|
|
15693
|
-
boxen4(
|
|
15931
|
+
boxen4(chalk42.cyan(`Agentic Coding CLI v${version}`), {
|
|
15694
15932
|
padding: { left: 1, right: 1, top: 0, bottom: 0 },
|
|
15695
15933
|
borderStyle: "round",
|
|
15696
15934
|
borderColor: "cyan"
|
|
15697
15935
|
})
|
|
15698
15936
|
);
|
|
15699
|
-
console.log(
|
|
15937
|
+
console.log(chalk42.bold("\n\u{1F527} Thi\u1EBFt l\u1EADp & Th\xF4ng tin"));
|
|
15700
15938
|
console.log(" auth X\xE1c th\u1EF1c v\xE0 c\u1EA5u h\xECnh client");
|
|
15701
15939
|
console.log(" status Hi\u1EC3n th\u1ECB tr\u1EA1ng th\xE1i c\u1EA5u h\xECnh");
|
|
15702
15940
|
console.log(" client-info T\u1EA1o th\xF4ng tin client \u0111\u1EC3 g\u1EEDi \u0111\u1ED9i ph\xE1t tri\u1EC3n");
|
|
@@ -15704,43 +15942,43 @@ function showCustomHelp(version) {
|
|
|
15704
15942
|
console.log(" guide H\u01B0\u1EDBng d\u1EABn s\u1EED d\u1EE5ng nhanh");
|
|
15705
15943
|
console.log(" quickstart B\u1EAFt \u0111\u1EA7u t\u1EEB \u0111\xE2u? (theo t\xECnh hu\u1ED1ng)");
|
|
15706
15944
|
console.log(" doctor Chu\u1EA9n \u0111o\xE1n project hi\u1EC7n t\u1EA1i");
|
|
15707
|
-
console.log(
|
|
15945
|
+
console.log(chalk42.bold("\n\u{1F4E6} Qu\u1EA3n l\xFD Components"));
|
|
15708
15946
|
console.log(" apply C\xE0i \u0111\u1EB7t components (interactive)");
|
|
15709
15947
|
console.log(" update C\u1EADp nh\u1EADt components \u0111\xE3 c\xE0i");
|
|
15710
15948
|
console.log(" check Ki\u1EC3m tra c\u1EADp nh\u1EADt t\u1EEB server");
|
|
15711
|
-
console.log(
|
|
15949
|
+
console.log(chalk42.bold("\n\u{1F5A5}\uFE0F IDE & T\xEDch h\u1EE3p"));
|
|
15712
15950
|
console.log(" ide L\u1EC7nh c\u1EA5u h\xECnh IDE");
|
|
15713
15951
|
console.log(" chat Chat AI v\u1EDBi Jai1 LLM Proxy");
|
|
15714
15952
|
console.log(" openai-keys Th\xF4ng tin API credentials");
|
|
15715
|
-
console.log(
|
|
15953
|
+
console.log(chalk42.bold("\n\u{1F916} AI Tools"));
|
|
15716
15954
|
console.log(" translate D\u1ECBch v\u0103n b\u1EA3n/file b\u1EB1ng AI");
|
|
15717
15955
|
console.log(" image T\u1EA1o \u1EA3nh (Coming Soon)");
|
|
15718
15956
|
console.log(" stats Th\u1ED1ng k\xEA s\u1EED d\u1EE5ng LLM");
|
|
15719
15957
|
console.log(" feedback G\u1EEDi b\xE1o c\xE1o/\u0111\u1EC1 xu\u1EA5t");
|
|
15720
|
-
console.log(
|
|
15958
|
+
console.log(chalk42.bold("\n\u{1F4C1} Project"));
|
|
15721
15959
|
console.log(" kit Qu\u1EA3n l\xFD starter kits");
|
|
15722
15960
|
console.log(" tasks (t) Qu\u1EA3n l\xFD tasks ph\xE1t tri\u1EC3n");
|
|
15723
15961
|
console.log(" rules Qu\u1EA3n l\xFD rule presets");
|
|
15724
15962
|
console.log(" deps Qu\u1EA3n l\xFD dependencies");
|
|
15725
15963
|
console.log(" redmine Redmine context sync");
|
|
15726
|
-
console.log(
|
|
15964
|
+
console.log(chalk42.bold("\n\u2699\uFE0F B\u1EA3o tr\xEC"));
|
|
15727
15965
|
console.log(" upgrade C\u1EADp nh\u1EADt CLI client");
|
|
15728
15966
|
console.log(" clean D\u1ECDn d\u1EB9p cache/backup");
|
|
15729
15967
|
console.log(" utils Developer utilities");
|
|
15730
15968
|
const name = getCliName();
|
|
15731
|
-
console.log(
|
|
15969
|
+
console.log(chalk42.dim(`
|
|
15732
15970
|
S\u1EED d\u1EE5ng: ${name} [l\u1EC7nh] --help \u0111\u1EC3 xem chi ti\u1EBFt`));
|
|
15733
15971
|
}
|
|
15734
15972
|
function showUnknownCommand(commandName) {
|
|
15735
|
-
console.error(
|
|
15973
|
+
console.error(chalk42.red(`\u274C L\u1EC7nh kh\xF4ng t\u1ED3n t\u1EA1i: ${commandName}`));
|
|
15736
15974
|
const name = getCliName();
|
|
15737
|
-
console.error(
|
|
15975
|
+
console.error(chalk42.dim(`
|
|
15738
15976
|
G\u1EE3i \xFD: Ch\u1EA1y ${name} --help \u0111\u1EC3 xem danh s\xE1ch l\u1EC7nh`));
|
|
15739
15977
|
}
|
|
15740
15978
|
|
|
15741
15979
|
// src/cli.ts
|
|
15742
15980
|
checkNodeVersion();
|
|
15743
|
-
var program = new
|
|
15981
|
+
var program = new Command87();
|
|
15744
15982
|
if (process.argv.includes("-v") || process.argv.includes("--version")) {
|
|
15745
15983
|
console.log(package_default.version);
|
|
15746
15984
|
if (!process.argv.includes("--skip-update-check")) {
|
|
@@ -15779,11 +16017,12 @@ program.addCommand(createTasksCommand());
|
|
|
15779
16017
|
program.addCommand(createKitCommand());
|
|
15780
16018
|
program.addCommand(createRulesCommand());
|
|
15781
16019
|
program.addCommand(createSkillsCommand());
|
|
16020
|
+
program.addCommand(createHooksCommand());
|
|
15782
16021
|
program.addCommand(createUpgradeCommand());
|
|
15783
16022
|
program.addCommand(createCleanCommand());
|
|
15784
|
-
var redmineCommand = new
|
|
16023
|
+
var redmineCommand = new Command87("redmine").description("Redmine context sync commands");
|
|
15785
16024
|
redmineCommand.addCommand(createRedmineCheckCommand());
|
|
15786
|
-
var syncCommand = new
|
|
16025
|
+
var syncCommand = new Command87("sync").description("Sync Redmine issues to markdown files");
|
|
15787
16026
|
syncCommand.addCommand(createSyncIssueCommand());
|
|
15788
16027
|
syncCommand.addCommand(createSyncProjectCommand());
|
|
15789
16028
|
redmineCommand.addCommand(syncCommand);
|