pan-stack-cli 0.1.5 → 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 (62) hide show
  1. package/README.md +572 -24
  2. package/bin/pan.js +204 -21
  3. package/lib/ai-skills.js +829 -0
  4. package/lib/create.js +22 -4
  5. package/lib/discover.js +59 -1
  6. package/lib/git/git-checkout.js +395 -0
  7. package/lib/git/git-clone.js +162 -27
  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 +42 -28
  15. package/lib/git/helpers/bulk-report.js +202 -0
  16. package/lib/git/helpers/cli.js +40 -5
  17. package/lib/git/helpers/manifest.js +93 -0
  18. package/lib/git/helpers/repo-select.js +328 -9
  19. package/lib/git/helpers/repo-summary.js +161 -0
  20. package/lib/i18n.js +61 -0
  21. package/lib/inquirer-theme.js +20 -0
  22. package/lib/language.js +121 -0
  23. package/lib/languages.js +14 -0
  24. package/lib/manifest.js +115 -14
  25. package/lib/menu-prompt.js +201 -0
  26. package/lib/menu.js +134 -0
  27. package/lib/messages.js +647 -0
  28. package/lib/run-command.js +173 -52
  29. package/lib/select.js +0 -0
  30. package/lib/serve.js +75 -0
  31. package/lib/skill-registry-client.js +173 -0
  32. package/lib/stats/aggregate.js +51 -0
  33. package/lib/stats/identity.js +20 -0
  34. package/lib/stats/index.js +189 -0
  35. package/lib/stats/parse.js +46 -0
  36. package/lib/stats/range.js +38 -0
  37. package/lib/stats/report.js +264 -0
  38. package/lib/stats/review.js +157 -0
  39. package/lib/stats/scan.js +92 -0
  40. package/lib/stats/serve.js +25 -0
  41. package/lib/ui/api.js +202 -0
  42. package/lib/ui/clone.js +115 -0
  43. package/lib/ui/config.js +182 -0
  44. package/lib/ui/diff.js +128 -0
  45. package/lib/ui/export-gate.js +79 -0
  46. package/lib/ui/gen-react-mobile-code.js +6 -0
  47. package/lib/ui/gen-react-web-code.js +75 -0
  48. package/lib/ui/index.js +89 -0
  49. package/lib/ui/init.js +252 -0
  50. package/lib/ui/login.js +128 -0
  51. package/lib/ui/logout.js +22 -0
  52. package/lib/ui/palettes.js +103 -0
  53. package/lib/ui/published-html.js +24 -0
  54. package/lib/ui/sync.js +137 -0
  55. package/lib/ui/workspace.js +213 -0
  56. package/package.json +10 -3
  57. package/templates/workspace/.claude/skills/writing-guidelines/SKILL.md +0 -48
  58. package/templates/workspace/.codex/skills/writing-guidelines/SKILL.md +0 -48
  59. package/templates/workspace/.cursor/skills/writing-guidelines/SKILL.md +0 -48
  60. package/templates/workspace/.pan.yaml +0 -25
  61. package/templates/workspace/.project.yaml +0 -15
  62. package/templates/workspace/README.md +0 -35
package/bin/pan.js CHANGED
@@ -4,28 +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",
22
24
  "sync-main": "git-sync-main.js",
23
25
  sm: "git-sync-main.js",
26
+ mr: "git-mr.js",
27
+ checkout: "git-checkout.js",
28
+ co: "git-checkout.js",
24
29
  };
25
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
+
26
46
  function parseGlobalArgs(argv) {
27
47
  const result = {
28
48
  help: false,
49
+ version: false,
29
50
  project: "",
30
51
  command: "",
31
52
  args: [],
@@ -33,8 +54,12 @@ function parseGlobalArgs(argv) {
33
54
 
34
55
  for (let index = 0; index < argv.length; index += 1) {
35
56
  const arg = argv[index];
36
- 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) {
37
60
  result.help = true;
61
+ } else if (isVersionFlag && !result.command) {
62
+ result.version = true;
38
63
  } else if (arg === "--project") {
39
64
  result.project = argv[++index] || "";
40
65
  } else if (arg.startsWith("--project=")) {
@@ -52,15 +77,24 @@ function parseGlobalArgs(argv) {
52
77
  function printUsage() {
53
78
  console.log(`usage: pan <command> [options] [-- args...]
54
79
 
55
- workspace commands (built-in):
56
- 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]
57
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)
58
91
  pull Pull latest changes for all repositories
59
- sync-main (sm) Fetch origin/main and merge into current branch (pick repo or all)
60
- fetch Fetch all remotes for all repositories
61
92
  push Push all repositories and set upstream
62
- grv List git remotes -v for workspace + child repos
63
- 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
64
98
 
65
99
  project commands:
66
100
  Loaded from .pan.yaml (workspace root + each repo in .project.yaml)
@@ -70,6 +104,12 @@ options:
70
104
  --base-dir DIR (git commands) base directory for child repo paths
71
105
  --dry-run (git commands) print commands only
72
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.
73
113
 
74
114
  env:
75
115
  PAN_PROJECT_ROOTS Semicolon-separated workspace roots (; on Windows, : or ; on Unix)
@@ -80,6 +120,13 @@ examples:
80
120
  pan create bookshop-stack
81
121
  pan clone
82
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
83
130
  pan test fe
84
131
  pan test --project bookshop-web
85
132
  `);
@@ -87,12 +134,14 @@ examples:
87
134
 
88
135
  function printAllCommands(sources) {
89
136
  if (sources.length === 0) {
90
- console.log("No .pan.yaml found (workspace or child repos).");
137
+ console.log(t("NO_PAN_YAML_FOUND"));
91
138
  return;
92
139
  }
93
140
 
94
- console.log("Loaded commands:");
95
- 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) {
96
145
  const commands = listCommands(source);
97
146
  if (commands.length === 0) {
98
147
  continue;
@@ -104,14 +153,115 @@ function printAllCommands(sources) {
104
153
  : "";
105
154
  const aliasHint =
106
155
  command.aliases.length > 0 ? ` (${command.aliases.join(", ")})` : "";
156
+ const seqHint = command.hasSequence ? " [sequence]" : "";
107
157
  const desc = command.description ? ` — ${command.description}` : "";
108
- 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}` }));
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;
109
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
+ });
110
238
  }
239
+ if (picked === "__exit__") {
240
+ console.log(t("EXITED"));
241
+ return;
242
+ }
243
+ runGitSubcommand(picked, []);
111
244
  }
112
245
 
113
246
  async function main() {
114
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
+ }
115
265
 
116
266
  if (parsed.help || !parsed.command) {
117
267
  printUsage();
@@ -131,24 +281,57 @@ async function main() {
131
281
  return;
132
282
  }
133
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
+
134
324
  if (isBuiltinGitCommand(command)) {
135
- const gitArgs = [...args];
136
- if (command === "push" && !gitArgs.includes("--set-upstream")) {
137
- gitArgs.unshift("--set-upstream");
138
- }
139
- runGitScript(GIT_SCRIPT_MAP[command], gitArgs);
325
+ runGitSubcommand(command, args);
140
326
  return;
141
327
  }
142
328
 
143
329
  if (isBuiltinWorkspaceCommand(command)) {
144
- throw new Error(`Unknown built-in command: ${command}`);
330
+ throw new Error(t("UNKNOWN_BUILTIN_COMMAND__COMMAND", { command }));
145
331
  }
146
332
 
147
333
  const allSources = loadAllCommandSources();
148
334
  const matches = findSourcesWithCommand(command);
149
- if (matches.length > 1 && !project && process.stdin.isTTY) {
150
- console.log(formatProjectList(matches, command));
151
- }
152
335
 
153
336
  await runWithProjectResolution(matches, command, args, project, allSources);
154
337
  }