pan-stack-cli 0.2.1 → 0.2.3

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 (59) hide show
  1. package/README.md +37 -731
  2. package/bin/pan.js +4978 -269
  3. package/package.json +17 -5
  4. package/lib/ai-skills.js +0 -829
  5. package/lib/create.js +0 -105
  6. package/lib/discover.js +0 -294
  7. package/lib/git/git-checkout.js +0 -395
  8. package/lib/git/git-clone.js +0 -206
  9. package/lib/git/git-fetch-all.js +0 -129
  10. package/lib/git/git-grv.js +0 -125
  11. package/lib/git/git-gst.js +0 -165
  12. package/lib/git/git-mr.js +0 -546
  13. package/lib/git/git-pull.js +0 -137
  14. package/lib/git/git-push.js +0 -185
  15. package/lib/git/git-sync-main.js +0 -334
  16. package/lib/git/helpers/bulk-report.js +0 -202
  17. package/lib/git/helpers/cli.js +0 -188
  18. package/lib/git/helpers/colors.js +0 -19
  19. package/lib/git/helpers/git.js +0 -136
  20. package/lib/git/helpers/manifest.js +0 -223
  21. package/lib/git/helpers/repo-select.js +0 -416
  22. package/lib/git/helpers/repo-summary.js +0 -161
  23. package/lib/i18n.js +0 -61
  24. package/lib/inquirer-theme.js +0 -20
  25. package/lib/language.js +0 -121
  26. package/lib/languages.js +0 -14
  27. package/lib/manifest.js +0 -467
  28. package/lib/menu-prompt.js +0 -201
  29. package/lib/menu.js +0 -134
  30. package/lib/messages.js +0 -647
  31. package/lib/platform.js +0 -140
  32. package/lib/run-command.js +0 -334
  33. package/lib/select.js +0 -0
  34. package/lib/serve.js +0 -75
  35. package/lib/skill-registry-client.js +0 -173
  36. package/lib/stats/aggregate.js +0 -51
  37. package/lib/stats/identity.js +0 -20
  38. package/lib/stats/index.js +0 -189
  39. package/lib/stats/parse.js +0 -46
  40. package/lib/stats/range.js +0 -38
  41. package/lib/stats/report.js +0 -264
  42. package/lib/stats/review.js +0 -157
  43. package/lib/stats/scan.js +0 -92
  44. package/lib/stats/serve.js +0 -25
  45. package/lib/ui/api.js +0 -202
  46. package/lib/ui/clone.js +0 -115
  47. package/lib/ui/config.js +0 -182
  48. package/lib/ui/diff.js +0 -128
  49. package/lib/ui/export-gate.js +0 -79
  50. package/lib/ui/gen-react-mobile-code.js +0 -6
  51. package/lib/ui/gen-react-web-code.js +0 -75
  52. package/lib/ui/index.js +0 -89
  53. package/lib/ui/init.js +0 -252
  54. package/lib/ui/login.js +0 -128
  55. package/lib/ui/logout.js +0 -22
  56. package/lib/ui/palettes.js +0 -103
  57. package/lib/ui/published-html.js +0 -24
  58. package/lib/ui/sync.js +0 -137
  59. package/lib/ui/workspace.js +0 -213
package/lib/create.js DELETED
@@ -1,105 +0,0 @@
1
- const fs = require("fs");
2
- const os = require("os");
3
- const path = require("path");
4
- const readline = require("readline");
5
- const { validateFolderName } = require("./platform");
6
- const { getRegistryBaseUrl, fetchRegistry, downloadAndExtract } = require("./skill-registry-client");
7
-
8
- const TEMPLATE_NAME = "workspace";
9
-
10
- function promptFolderName() {
11
- return new Promise((resolve, reject) => {
12
- if (!process.stdin.isTTY) {
13
- reject(new Error("Folder name required: pan create <folder-name>"));
14
- return;
15
- }
16
-
17
- const rl = readline.createInterface({
18
- input: process.stdin,
19
- output: process.stdout,
20
- });
21
-
22
- rl.question("Workspace folder name: ", (answer) => {
23
- rl.close();
24
- const name = answer.trim();
25
- if (!name) {
26
- reject(new Error("Folder name cannot be empty."));
27
- return;
28
- }
29
- resolve(name);
30
- });
31
- });
32
- }
33
-
34
- function copyDir(src, dest) {
35
- fs.mkdirSync(dest, { recursive: true });
36
- for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
37
- if (entry.name === "template.json") continue; // metadata riêng cho registry, không phải file scaffold
38
- const srcPath = path.join(src, entry.name);
39
- const destPath = path.join(dest, entry.name);
40
- if (entry.isDirectory()) {
41
- copyDir(srcPath, destPath);
42
- } else {
43
- fs.copyFileSync(srcPath, destPath);
44
- }
45
- }
46
- }
47
-
48
- function applyPlaceholders(targetDir, folderName) {
49
- const files = [".project.yaml", "README.md"];
50
- for (const relativePath of files) {
51
- const filePath = path.join(targetDir, relativePath);
52
- if (!fs.existsSync(filePath)) {
53
- continue;
54
- }
55
- const content = fs.readFileSync(filePath, "utf8");
56
- fs.writeFileSync(filePath, content.replaceAll("__PROJECT_NAME__", folderName));
57
- }
58
- }
59
-
60
- async function runCreate(args = []) {
61
- let folderName = args[0]?.trim();
62
- if (!folderName) {
63
- folderName = await promptFolderName();
64
- }
65
-
66
- validateFolderName(folderName);
67
-
68
- const targetDir = path.resolve(process.cwd(), folderName);
69
- if (fs.existsSync(targetDir)) {
70
- throw new Error(`Folder already exists: ${targetDir}`);
71
- }
72
-
73
- // Template scaffold không còn bundle sẵn trong pan-stack-cli — tải từ registry server (giống
74
- // cơ chế `pan ai install`). Cần PAN_SKILL_REGISTRY_URL (hoặc default localhost:4600) chạy được.
75
- const baseUrl = getRegistryBaseUrl();
76
- const items = await fetchRegistry(baseUrl);
77
- const template = items.find((i) => i.type === "template" && i.name === TEMPLATE_NAME);
78
- if (!template) {
79
- throw new Error(
80
- `Không tìm thấy template '${TEMPLATE_NAME}' trên registry server (${baseUrl}). Kiểm tra apps/pan-agent-skill-registry đã build/deploy đủ registry/templates/${TEMPLATE_NAME} chưa.`
81
- );
82
- }
83
-
84
- const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "pan-create-"));
85
- let extractedDir;
86
- try {
87
- extractedDir = await downloadAndExtract(baseUrl, template, stagingDir);
88
- copyDir(extractedDir, targetDir);
89
- } finally {
90
- fs.rmSync(stagingDir, { recursive: true, force: true });
91
- }
92
-
93
- applyPlaceholders(targetDir, folderName);
94
-
95
- console.log(`Created PAN Stack workspace: ${targetDir}`);
96
- console.log("");
97
- console.log("Next steps:");
98
- console.log(` cd ${folderName}`);
99
- console.log(" # Edit .project.yaml with your repository URLs");
100
- console.log(" pan clone");
101
- }
102
-
103
- module.exports = {
104
- runCreate,
105
- };
package/lib/discover.js DELETED
@@ -1,294 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const os = require("os");
4
- const { splitEnvList } = require("./platform");
5
- const {
6
- PAN_CONFIG_FILE,
7
- loadManifest,
8
- loadPanConfig,
9
- getCommandDefinition,
10
- commandAliases,
11
- validateRepo,
12
- cleanValue,
13
- } = require("./manifest");
14
-
15
- const BUILTIN_GIT_COMMANDS = new Set([
16
- "clone",
17
- "pull",
18
- "sync-main",
19
- "sm",
20
- "fetch",
21
- "gfa",
22
- "push",
23
- "grv",
24
- "gst",
25
- "mr",
26
- "checkout",
27
- "co",
28
- ]);
29
- const BUILTIN_WORKSPACE_COMMANDS = new Set([
30
- "create",
31
- "serve",
32
- "language",
33
- "lang",
34
- "ai",
35
- "skills",
36
- "ui",
37
- "stats",
38
- "graph",
39
- "git",
40
- ...BUILTIN_GIT_COMMANDS,
41
- ]);
42
-
43
- function readProjectRoots() {
44
- const roots = [];
45
- const fromEnv = process.env.PAN_PROJECT_ROOTS || "";
46
- for (const part of splitEnvList(fromEnv)) {
47
- roots.push(path.resolve(part));
48
- }
49
-
50
- const registry = path.join(os.homedir(), ".pan", "projects");
51
- if (fs.existsSync(registry)) {
52
- const content = fs.readFileSync(registry, "utf8");
53
- for (const line of content.split(/\r?\n/)) {
54
- const trimmed = line.trim();
55
- if (!trimmed || trimmed.startsWith("#")) {
56
- continue;
57
- }
58
- roots.push(path.resolve(trimmed));
59
- }
60
- }
61
-
62
- return [...new Set(roots)];
63
- }
64
-
65
- function manifestPathInDir(dir) {
66
- const candidate = path.join(dir, ".project.yaml");
67
- return fs.existsSync(candidate) ? candidate : null;
68
- }
69
-
70
- function walkUpManifests(startDir) {
71
- const found = [];
72
- let current = path.resolve(startDir);
73
- const seen = new Set();
74
-
75
- while (true) {
76
- const manifestPath = manifestPathInDir(current);
77
- if (manifestPath && !seen.has(manifestPath)) {
78
- seen.add(manifestPath);
79
- found.push(manifestPath);
80
- }
81
- const parent = path.dirname(current);
82
- if (parent === current) {
83
- break;
84
- }
85
- current = parent;
86
- }
87
-
88
- return found;
89
- }
90
-
91
- function findNearestProjectManifest(startDir = process.cwd()) {
92
- const walked = walkUpManifests(startDir);
93
- return walked[0] || null;
94
- }
95
-
96
- const DISCOVER_SKIP_DIRS = new Set([
97
- ".git", "node_modules", ".pnpm", "dist", "build", ".next", "target", "vendor",
98
- ]);
99
-
100
- /**
101
- * Fallback khi không có .project.yaml: quét git repo từ CHÍNH thư mục đang gõ
102
- * lệnh (startDir) xuống tối đa `maxDepth` cấp — bao gồm chính startDir, thư mục
103
- * con trực tiếp, và các cấp con tiếp theo (đủ bắt submodule). Dùng chung cho
104
- * `pan stats` và các lệnh git built-in (`pan pull|push|fetch|checkout|...`) khi
105
- * chạy ngoài workspace pan-stack (không có `.project.yaml`).
106
- * @returns {Array<{name,dir}>}
107
- */
108
- function discoverGitRepos(startDir = process.cwd(), maxDepth = 2) {
109
- const base = path.resolve(startDir);
110
- const found = [];
111
- const seen = new Set();
112
-
113
- function walk(dir, depth) {
114
- if (depth > maxDepth) return;
115
- let entries;
116
- try {
117
- entries = fs.readdirSync(dir, { withFileTypes: true });
118
- } catch {
119
- return;
120
- }
121
- if (entries.some((e) => e.name === ".git")) {
122
- const abs = path.resolve(dir);
123
- if (!seen.has(abs)) {
124
- seen.add(abs);
125
- found.push({ name: path.basename(abs), dir: abs });
126
- }
127
- }
128
- for (const e of entries) {
129
- if (e.isDirectory() && !DISCOVER_SKIP_DIRS.has(e.name)) walk(path.join(dir, e.name), depth + 1);
130
- }
131
- }
132
-
133
- walk(base, 0);
134
- return found.sort((a, b) => a.name.localeCompare(b.name));
135
- }
136
-
137
- function findNearestWorkspace(startDir = process.cwd()) {
138
- const manifestPath = findNearestProjectManifest(startDir);
139
- if (!manifestPath) {
140
- return null;
141
- }
142
- return loadManifest(manifestPath);
143
- }
144
-
145
- function loadCommandSourcesForWorkspace(workspace) {
146
- const sources = [];
147
- const workspaceRoot = workspace.root;
148
- const workspacePan = path.join(workspaceRoot, PAN_CONFIG_FILE);
149
-
150
- if (fs.existsSync(workspacePan)) {
151
- sources.push(
152
- loadPanConfig(workspacePan, {
153
- workspaceRoot,
154
- project: workspace.project,
155
- repoName: workspace.project,
156
- scope: "workspace",
157
- })
158
- );
159
- }
160
-
161
- for (const repo of workspace.repositories) {
162
- try {
163
- const { name, repoPath } = validateRepo(repo);
164
- const repoDir = path.resolve(workspaceRoot, repoPath);
165
- const repoPan = path.join(repoDir, PAN_CONFIG_FILE);
166
- if (!fs.existsSync(repoPan)) {
167
- continue;
168
- }
169
- sources.push(
170
- loadPanConfig(repoPan, {
171
- workspaceRoot,
172
- project: name,
173
- repoName: name,
174
- scope: "repo",
175
- })
176
- );
177
- } catch {
178
- // skip invalid repo entries
179
- }
180
- }
181
-
182
- return sources;
183
- }
184
-
185
- function collectWorkspaces(startDir = process.cwd()) {
186
- const workspaces = new Map();
187
-
188
- for (const manifestPath of walkUpManifests(startDir)) {
189
- try {
190
- const workspace = loadManifest(manifestPath);
191
- workspaces.set(workspace.root, workspace);
192
- } catch {
193
- // ignore
194
- }
195
- }
196
-
197
- for (const root of readProjectRoots()) {
198
- const manifestPath = manifestPathInDir(root);
199
- if (!manifestPath) {
200
- continue;
201
- }
202
- try {
203
- const workspace = loadManifest(manifestPath);
204
- workspaces.set(workspace.root, workspace);
205
- } catch {
206
- // ignore
207
- }
208
- }
209
-
210
- return [...workspaces.values()];
211
- }
212
-
213
- function loadAllCommandSources(startDir = process.cwd()) {
214
- const sources = [];
215
- const seen = new Set();
216
-
217
- for (const workspace of collectWorkspaces(startDir)) {
218
- for (const source of loadCommandSourcesForWorkspace(workspace)) {
219
- const key = source.path;
220
- if (seen.has(key)) {
221
- continue;
222
- }
223
- seen.add(key);
224
- sources.push(source);
225
- }
226
- }
227
-
228
- return sources;
229
- }
230
-
231
- function sourceMatchesProject(source, projectFlag) {
232
- const flag = projectFlag.trim();
233
- return (
234
- source.project === flag ||
235
- source.repoName === flag ||
236
- path.basename(source.root) === flag
237
- );
238
- }
239
-
240
- function sourceHasCommand(source, commandName) {
241
- if (source.commands[commandName]) {
242
- return true;
243
- }
244
- return Object.values(source.commands || {}).some((def) =>
245
- commandAliases(def).includes(commandName)
246
- );
247
- }
248
-
249
- function findSourcesWithCommand(commandName, startDir = process.cwd()) {
250
- return loadAllCommandSources(startDir).filter((source) =>
251
- sourceHasCommand(source, commandName)
252
- );
253
- }
254
-
255
- function formatProjectList(matches, commandName) {
256
- const lines = [`Command '${commandName}' is defined in multiple places:`];
257
- matches.forEach((source, index) => {
258
- const def = source.commands[commandName];
259
- const desc = def?.description ? ` — ${def.description}` : "";
260
- const label =
261
- source.scope === "workspace"
262
- ? `${source.project} (workspace)`
263
- : source.repoName || source.project;
264
- lines.push(` ${index + 1}. ${label} (${source.root})${desc}`);
265
- });
266
- lines.push("");
267
- lines.push("Pick a number, or rerun with --project <repo-name>.");
268
- return lines.join("\n");
269
- }
270
-
271
- function isBuiltinGitCommand(name) {
272
- return BUILTIN_GIT_COMMANDS.has(name);
273
- }
274
-
275
- function isBuiltinWorkspaceCommand(name) {
276
- return BUILTIN_WORKSPACE_COMMANDS.has(name);
277
- }
278
-
279
- module.exports = {
280
- BUILTIN_GIT_COMMANDS,
281
- BUILTIN_WORKSPACE_COMMANDS,
282
- PAN_CONFIG_FILE,
283
- findNearestProjectManifest,
284
- findNearestWorkspace,
285
- discoverGitRepos,
286
- loadAllCommandSources,
287
- loadCommandSourcesForWorkspace,
288
- findSourcesWithCommand,
289
- formatProjectList,
290
- isBuiltinGitCommand,
291
- isBuiltinWorkspaceCommand,
292
- getCommandDefinition,
293
- sourceMatchesProject,
294
- };