pan-stack-cli 0.1.4 → 0.2.1

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.
Files changed (64) hide show
  1. package/README.md +572 -23
  2. package/bin/pan.js +206 -20
  3. package/lib/ai-skills.js +829 -0
  4. package/lib/create.js +22 -4
  5. package/lib/discover.js +69 -2
  6. package/lib/git/git-checkout.js +395 -0
  7. package/lib/git/git-clone.js +163 -28
  8. package/lib/git/git-fetch-all.js +100 -33
  9. package/lib/git/git-grv.js +68 -37
  10. package/lib/git/git-gst.js +97 -26
  11. package/lib/git/git-mr.js +546 -0
  12. package/lib/git/git-pull.js +104 -43
  13. package/lib/git/git-push.js +150 -41
  14. package/lib/git/git-sync-main.js +334 -0
  15. package/lib/git/helpers/bulk-report.js +202 -0
  16. package/lib/git/helpers/cli.js +41 -5
  17. package/lib/git/helpers/colors.js +19 -0
  18. package/lib/git/helpers/git.js +74 -0
  19. package/lib/git/helpers/manifest.js +93 -0
  20. package/lib/git/helpers/repo-select.js +416 -0
  21. package/lib/git/helpers/repo-summary.js +161 -0
  22. package/lib/i18n.js +61 -0
  23. package/lib/inquirer-theme.js +20 -0
  24. package/lib/language.js +121 -0
  25. package/lib/languages.js +14 -0
  26. package/lib/manifest.js +115 -14
  27. package/lib/menu-prompt.js +201 -0
  28. package/lib/menu.js +134 -0
  29. package/lib/messages.js +647 -0
  30. package/lib/run-command.js +173 -52
  31. package/lib/select.js +0 -0
  32. package/lib/serve.js +75 -0
  33. package/lib/skill-registry-client.js +173 -0
  34. package/lib/stats/aggregate.js +51 -0
  35. package/lib/stats/identity.js +20 -0
  36. package/lib/stats/index.js +189 -0
  37. package/lib/stats/parse.js +46 -0
  38. package/lib/stats/range.js +38 -0
  39. package/lib/stats/report.js +264 -0
  40. package/lib/stats/review.js +157 -0
  41. package/lib/stats/scan.js +92 -0
  42. package/lib/stats/serve.js +25 -0
  43. package/lib/ui/api.js +202 -0
  44. package/lib/ui/clone.js +115 -0
  45. package/lib/ui/config.js +182 -0
  46. package/lib/ui/diff.js +128 -0
  47. package/lib/ui/export-gate.js +79 -0
  48. package/lib/ui/gen-react-mobile-code.js +6 -0
  49. package/lib/ui/gen-react-web-code.js +75 -0
  50. package/lib/ui/index.js +89 -0
  51. package/lib/ui/init.js +252 -0
  52. package/lib/ui/login.js +128 -0
  53. package/lib/ui/logout.js +22 -0
  54. package/lib/ui/palettes.js +103 -0
  55. package/lib/ui/published-html.js +24 -0
  56. package/lib/ui/sync.js +137 -0
  57. package/lib/ui/workspace.js +213 -0
  58. package/package.json +10 -3
  59. package/templates/workspace/.claude/skills/writing-guidelines/SKILL.md +0 -48
  60. package/templates/workspace/.codex/skills/writing-guidelines/SKILL.md +0 -48
  61. package/templates/workspace/.cursor/skills/writing-guidelines/SKILL.md +0 -48
  62. package/templates/workspace/.pan.yaml +0 -25
  63. package/templates/workspace/.project.yaml +0 -15
  64. package/templates/workspace/README.md +0 -35
package/bin/pan.js CHANGED
@@ -4,26 +4,49 @@ const {
4
4
  findNearestWorkspace,
5
5
  loadAllCommandSources,
6
6
  findSourcesWithCommand,
7
- formatProjectList,
8
7
  isBuiltinGitCommand,
9
8
  isBuiltinWorkspaceCommand,
10
9
  } = require("../lib/discover");
11
10
  const { listCommands } = require("../lib/manifest");
12
11
  const { runWithProjectResolution, runGitScript, sourceLabel } = require("../lib/run-command");
13
12
  const { runCreate } = require("../lib/create");
13
+ const { runServe } = require("../lib/serve");
14
+ const { t } = require("../lib/i18n");
14
15
 
15
16
  const GIT_SCRIPT_MAP = {
16
17
  clone: "git-clone.js",
17
18
  pull: "git-pull.js",
18
19
  fetch: "git-fetch-all.js",
20
+ gfa: "git-fetch-all.js",
19
21
  push: "git-push.js",
20
22
  grv: "git-grv.js",
21
23
  gst: "git-gst.js",
24
+ "sync-main": "git-sync-main.js",
25
+ sm: "git-sync-main.js",
26
+ mr: "git-mr.js",
27
+ checkout: "git-checkout.js",
28
+ co: "git-checkout.js",
22
29
  };
23
30
 
31
+ // `pan git` — sổ danh sách lệnh git (kể cả `stats`, vì stats cũng là thống kê Git từ log) ra 1
32
+ // menu, đỡ phải nhớ tên/xem help dài. Sort A-Z (xem "lệnh sort A-Z" trước đó trong CLI).
33
+ const GIT_MENU_COMMANDS = [
34
+ { name: "checkout", aliases: ["co"], description: "Chuyển/tạo nhánh: pan co [-b|-B] <branch> [repo|all|list]" },
35
+ { name: "clone", aliases: [], description: "Clone all repositories from .project.yaml" },
36
+ { name: "fetch", aliases: ["gfa"], description: "Fetch all remotes for all repositories" },
37
+ { name: "grv", aliases: [], description: "List git remotes -v for workspace + child repos" },
38
+ { name: "gst", aliases: [], description: "Summary of repos with uncommitted changes" },
39
+ { name: "mr", aliases: [], description: "Tạo GitLab Merge Request (multi-select repo · target=main mặc định)" },
40
+ { name: "pull", aliases: [], description: "Pull latest changes for all repositories" },
41
+ { name: "push", aliases: [], description: "Push all repositories and set upstream" },
42
+ { name: "stats", aliases: ["graph"], description: "Thống kê đóng góp Git (report HTML / people / review md)" },
43
+ { name: "sync-main", aliases: ["sm"], description: "Fetch origin/main and merge into current branch (pick repo or all)" },
44
+ ].sort((a, b) => a.name.localeCompare(b.name));
45
+
24
46
  function parseGlobalArgs(argv) {
25
47
  const result = {
26
48
  help: false,
49
+ version: false,
27
50
  project: "",
28
51
  command: "",
29
52
  args: [],
@@ -31,8 +54,12 @@ function parseGlobalArgs(argv) {
31
54
 
32
55
  for (let index = 0; index < argv.length; index += 1) {
33
56
  const arg = argv[index];
34
- if (arg === "-h" || arg === "--help" || arg === "help") {
57
+ const isHelpFlag = arg === "-h" || arg === "--help" || arg === "help";
58
+ const isVersionFlag = arg === "-v" || arg === "--version";
59
+ if (isHelpFlag && !result.command) {
35
60
  result.help = true;
61
+ } else if (isVersionFlag && !result.command) {
62
+ result.version = true;
36
63
  } else if (arg === "--project") {
37
64
  result.project = argv[++index] || "";
38
65
  } else if (arg.startsWith("--project=")) {
@@ -50,14 +77,24 @@ function parseGlobalArgs(argv) {
50
77
  function printUsage() {
51
78
  console.log(`usage: pan <command> [options] [-- args...]
52
79
 
53
- workspace commands (built-in):
54
- create Scaffold a new PAN Stack workspace (prompts for folder name)
80
+ workspace commands (built-in, A-Z):
81
+ ai (skills) Enable/disable agent skills (SKILL.md ↔ .disabled) at workspace root
82
+ checkout (co) Chuyển/tạo nhánh: pan co [-b|-B] <branch> [repo|all|list]
55
83
  clone Clone all repositories from .project.yaml
84
+ create Scaffold a new PAN Stack workspace (prompts for folder name)
85
+ fetch (gfa) Fetch all remotes for all repositories
86
+ git Menu chọn lệnh git (kể cả stats) — đỡ nhớ tên: pan git [<command>]
87
+ grv List git remotes -v for workspace + child repos
88
+ gst Summary of repos with uncommitted changes (pan gst --verbose for full git status)
89
+ language (lang) Đổi ngôn ngữ CLI — ghi 'language: <code>' vào .project.yaml gần nhất: pan language vi
90
+ mr Tạo GitLab Merge Request (multi-select repo · target=main mặc định)
56
91
  pull Pull latest changes for all repositories
57
- fetch Fetch all remotes for all repositories
58
92
  push Push all repositories and set upstream
59
- grv List git remotes -v for workspace + child repos
60
- gst Git status for workspace + child repos (e.g. pan gst -sb)
93
+ select Interactive picker (in ra stdout): sel=$(pan select --multi --all --from "ls *.yaml")
94
+ serve Static server cho thư mục build (vd output panui:gen): pan serve <dir> [-p port]
95
+ stats (graph) Thống kê đóng góp Git từ .project.yaml → báo cáo HTML / people / review md
96
+ sync-main (sm) Fetch origin/main and merge into current branch (pick repo or all)
97
+ ui Pan Studio sync CLI: login/logout/clone/sync/published-html/gen-react-web-code
61
98
 
62
99
  project commands:
63
100
  Loaded from .pan.yaml (workspace root + each repo in .project.yaml)
@@ -67,6 +104,12 @@ options:
67
104
  --base-dir DIR (git commands) base directory for child repo paths
68
105
  --dry-run (git commands) print commands only
69
106
  -h, --help Show help
107
+ -v, --version Show pan-stack-cli version
108
+
109
+ tip:
110
+ Run 'pan' (no args) in a terminal → interactive MENU: pick project → pick command → run.
111
+ Navigate with ↑↓, type to filter, hotkeys: h help · v version · b back · x exit.
112
+ Language: default English; set 'language: vi' in .project.yaml (or PAN_LANG=vi) for Vietnamese.
70
113
 
71
114
  env:
72
115
  PAN_PROJECT_ROOTS Semicolon-separated workspace roots (; on Windows, : or ; on Unix)
@@ -77,6 +120,13 @@ examples:
77
120
  pan create bookshop-stack
78
121
  pan clone
79
122
  pan grv
123
+ pan mr # interactive multi-select, target=main
124
+ pan mr develop # interactive multi-select, target=develop
125
+ pan mr main all # non-interactive, all repos
126
+ pan mr main csdl-cc-backend,csdl-cc-b-frontend
127
+ pan co -b feat/#1234 # tạo nhánh mới, tương tác chọn repo
128
+ pan co -b feat/#1234 all # tạo nhánh mới cho tất cả repo
129
+ pan co main # switch tất cả sang main
80
130
  pan test fe
81
131
  pan test --project bookshop-web
82
132
  `);
@@ -84,12 +134,14 @@ examples:
84
134
 
85
135
  function printAllCommands(sources) {
86
136
  if (sources.length === 0) {
87
- console.log("No .pan.yaml found (workspace or child repos).");
137
+ console.log(t("NO_PAN_YAML_FOUND"));
88
138
  return;
89
139
  }
90
140
 
91
- console.log("Loaded commands:");
92
- for (const source of sources) {
141
+ console.log(t("LOADED_COMMANDS"));
142
+ // Sort A-Z theo tên hiển thị (project/repo) — dễ dò khi .project.yaml có nhiều repo.
143
+ const sorted = [...sources].sort((a, b) => sourceLabel(a).localeCompare(sourceLabel(b)));
144
+ for (const source of sorted) {
93
145
  const commands = listCommands(source);
94
146
  if (commands.length === 0) {
95
147
  continue;
@@ -101,14 +153,115 @@ function printAllCommands(sources) {
101
153
  : "";
102
154
  const aliasHint =
103
155
  command.aliases.length > 0 ? ` (${command.aliases.join(", ")})` : "";
156
+ const seqHint = command.hasSequence ? " [sequence]" : "";
104
157
  const desc = command.description ? ` — ${command.description}` : "";
105
- console.log(` ${command.name}${aliasHint}${targetHint}${desc}`);
158
+ console.log(` ${command.name}${aliasHint}${targetHint}${seqHint}${desc}`);
159
+ }
160
+ }
161
+ }
162
+
163
+ function printGitMenuHelp() {
164
+ console.log(`usage: pan git [<command>] [options] [-- args...]
165
+
166
+ Không truyền <command> → TTY: menu tương tác chọn 1 lệnh git rồi chạy ngay.
167
+ Không TTY: in danh sách dưới đây rồi thoát (không lỗi).
168
+ Có <command> → chạy thẳng, y hệt gõ 'pan <command>' (chỉ là lối gõ khác, dễ nhớ hơn).
169
+
170
+ lệnh (A-Z):
171
+ ${GIT_MENU_COMMANDS.map((c) => ` ${c.name}${c.aliases.length ? ` (${c.aliases.join(", ")})` : ""} ${c.description}`).join("\n")}
172
+ `);
173
+ }
174
+
175
+ // Chạy 1 lệnh git/stats theo tên (đã resolve alias) — dùng chung bởi 'pan <cmd>' (main) và
176
+ // 'pan git <cmd>' (đỡ nhớ tên khi không chắc, xem printGitMenuHelp).
177
+ function runGitSubcommand(name, args) {
178
+ if (name === "stats" || name === "graph") {
179
+ const { runStats } = require("../lib/stats");
180
+ return runStats(args);
181
+ }
182
+ const gitArgs = [...args];
183
+ if (name === "push" && !gitArgs.includes("--set-upstream")) {
184
+ gitArgs.unshift("--set-upstream");
185
+ }
186
+ runGitScript(GIT_SCRIPT_MAP[name], gitArgs);
187
+ }
188
+
189
+ async function runGitMenu(args) {
190
+ if (args[0] === "-h" || args[0] === "--help") {
191
+ printGitMenuHelp();
192
+ return;
193
+ }
194
+
195
+ // Có sẵn tên lệnh (vd `pan git pull`) → chạy thẳng, khỏi qua menu.
196
+ if (args[0]) {
197
+ const sub = args[0];
198
+ const known = GIT_MENU_COMMANDS.find((c) => c.name === sub || c.aliases.includes(sub));
199
+ if (!known) {
200
+ throw new Error(t("UNKNOWN_BUILTIN_COMMAND__COMMAND", { command: `git ${sub}` }));
106
201
  }
202
+ runGitSubcommand(known.name, args.slice(1));
203
+ return;
204
+ }
205
+
206
+ if (!process.stdin.isTTY) {
207
+ printGitMenuHelp();
208
+ return;
209
+ }
210
+
211
+ const { menuSelect } = require("../lib/menu-prompt");
212
+ const choices = GIT_MENU_COMMANDS.map((c) => ({
213
+ name: c.name + (c.aliases.length ? ` (${c.aliases.join(", ")})` : ""),
214
+ value: c.name,
215
+ description: c.description,
216
+ }));
217
+ let picked;
218
+ try {
219
+ picked = await menuSelect({
220
+ message: "pan git · chọn lệnh:",
221
+ choices,
222
+ hotkeys: [{ key: "x", label: t("EXIT_LABEL"), value: "__exit__" }],
223
+ });
224
+ } catch (err) {
225
+ if (String(err && err.message) !== "no-core") throw err;
226
+ const { loadInquirer } = require("../lib/select");
227
+ const inq = await loadInquirer();
228
+ if (!inq || typeof inq.select !== "function") {
229
+ printGitMenuHelp();
230
+ return;
231
+ }
232
+ picked = await inq.select({
233
+ message: "pan git · chọn lệnh:",
234
+ choices: [...choices, { name: `x ${t("EXIT_LABEL")}`, value: "__exit__" }],
235
+ pageSize: 20,
236
+ loop: false,
237
+ });
238
+ }
239
+ if (picked === "__exit__") {
240
+ console.log(t("EXITED"));
241
+ return;
107
242
  }
243
+ runGitSubcommand(picked, []);
108
244
  }
109
245
 
110
246
  async function main() {
111
247
  const parsed = parseGlobalArgs(process.argv.slice(2));
248
+ const pkg = require("../package.json");
249
+
250
+ if (parsed.version || parsed.command === "version") {
251
+ console.log(`pan-stack-cli ${pkg.version}`);
252
+ return;
253
+ }
254
+
255
+ // Gõ `pan` trống trên terminal → MENU tương tác (chọn dự án → lệnh → chạy).
256
+ if (!parsed.command && !parsed.help && process.stdin.isTTY) {
257
+ try {
258
+ const { runInteractiveMenu } = require("../lib/menu");
259
+ const handled = await runInteractiveMenu({ printUsage, version: pkg.version });
260
+ if (handled) return;
261
+ } catch (err) {
262
+ console.error(err.message || err);
263
+ }
264
+ }
112
265
 
113
266
  if (parsed.help || !parsed.command) {
114
267
  printUsage();
@@ -128,24 +281,57 @@ async function main() {
128
281
  return;
129
282
  }
130
283
 
284
+ if (command === "serve") {
285
+ runServe(args);
286
+ return;
287
+ }
288
+
289
+ if (command === "select") {
290
+ const { runSelectCli } = require("../lib/select");
291
+ await runSelectCli(args);
292
+ return;
293
+ }
294
+
295
+ if (command === "language" || command === "lang") {
296
+ const { runLanguage } = require("../lib/language");
297
+ await runLanguage(args);
298
+ return;
299
+ }
300
+
301
+ if (command === "ai" || command === "skills") {
302
+ const { runAiSkills } = require("../lib/ai-skills");
303
+ await runAiSkills(args);
304
+ return;
305
+ }
306
+
307
+ if (command === "ui") {
308
+ const { runUi } = require("../lib/ui");
309
+ await runUi(args);
310
+ return;
311
+ }
312
+
313
+ if (command === "stats" || command === "graph") {
314
+ const { runStats } = require("../lib/stats");
315
+ await runStats(args);
316
+ return;
317
+ }
318
+
319
+ if (command === "git") {
320
+ await runGitMenu(args);
321
+ return;
322
+ }
323
+
131
324
  if (isBuiltinGitCommand(command)) {
132
- const gitArgs = [...args];
133
- if (command === "push" && !gitArgs.includes("--set-upstream")) {
134
- gitArgs.unshift("--set-upstream");
135
- }
136
- runGitScript(GIT_SCRIPT_MAP[command], gitArgs);
325
+ runGitSubcommand(command, args);
137
326
  return;
138
327
  }
139
328
 
140
329
  if (isBuiltinWorkspaceCommand(command)) {
141
- throw new Error(`Unknown built-in command: ${command}`);
330
+ throw new Error(t("UNKNOWN_BUILTIN_COMMAND__COMMAND", { command }));
142
331
  }
143
332
 
144
333
  const allSources = loadAllCommandSources();
145
334
  const matches = findSourcesWithCommand(command);
146
- if (matches.length > 1 && !project && process.stdin.isTTY) {
147
- console.log(formatProjectList(matches, command));
148
- }
149
335
 
150
336
  await runWithProjectResolution(matches, command, args, project, allSources);
151
337
  }