@atlisp/cli 1.2.0

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 (45) hide show
  1. package/.github/workflows/publish.yml +18 -0
  2. package/.github/workflows/test.yml +14 -0
  3. package/README.md +88 -0
  4. package/__tests__/__snapshots__/cli.test.js.snap +12 -0
  5. package/__tests__/build.test.js +48 -0
  6. package/__tests__/cli.test.js +246 -0
  7. package/__tests__/config.test.js +71 -0
  8. package/__tests__/deps.test.js +64 -0
  9. package/__tests__/index.test.js +63 -0
  10. package/__tests__/lint.test.js +58 -0
  11. package/atlisp.js +219 -0
  12. package/commands/batch.js +14 -0
  13. package/commands/build.js +267 -0
  14. package/commands/checksum.js +23 -0
  15. package/commands/config.js +264 -0
  16. package/commands/dcl-migrate.js +62 -0
  17. package/commands/doc.js +45 -0
  18. package/commands/doctor.js +292 -0
  19. package/commands/find.js +14 -0
  20. package/commands/index.js +33 -0
  21. package/commands/info.js +46 -0
  22. package/commands/init.js +202 -0
  23. package/commands/install.js +23 -0
  24. package/commands/lint.js +79 -0
  25. package/commands/list.js +99 -0
  26. package/commands/publish.js +237 -0
  27. package/commands/pull.js +118 -0
  28. package/commands/qrcode.js +33 -0
  29. package/commands/test.js +223 -0
  30. package/commands/watch.js +47 -0
  31. package/completion/atlisp-completion.bash +107 -0
  32. package/completion/atlisp-completion.ps1 +64 -0
  33. package/index.js +13 -0
  34. package/kernel-order.json +28 -0
  35. package/lib/atlisp.js +15 -0
  36. package/lib/batch.js +173 -0
  37. package/lib/cad.js +199 -0
  38. package/lib/common.js +189 -0
  39. package/lib/deps.js +121 -0
  40. package/lib/docgen.js +93 -0
  41. package/lib/find.js +154 -0
  42. package/lib/mcp-client.js +203 -0
  43. package/package.json +49 -0
  44. package/scripts/add-deps.js +81 -0
  45. package/scripts/fix-deps.js +98 -0
@@ -0,0 +1,33 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const commands = new Map();
5
+
6
+ function register(name, handler, aliases = []) {
7
+ commands.set(name, handler);
8
+ for (const alias of aliases) {
9
+ commands.set(alias, handler);
10
+ }
11
+ }
12
+
13
+ function get(name) {
14
+ return commands.get(name);
15
+ }
16
+
17
+ function getAll() {
18
+ return [...commands.keys()];
19
+ }
20
+
21
+ function load(dir) {
22
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
23
+ for (const entry of entries) {
24
+ if (entry.isFile() && entry.name.endsWith(".js") && entry.name !== "index.js") {
25
+ const mod = require(path.join(dir, entry.name));
26
+ if (mod.register) {
27
+ mod.register(register);
28
+ }
29
+ }
30
+ }
31
+ }
32
+
33
+ module.exports = { register, get, getAll, load };
@@ -0,0 +1,46 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const os = require("os");
4
+ const chalk = require("chalk");
5
+ const common = require("../lib/common");
6
+
7
+ async function cmdInfo(pkgName) {
8
+ if (!pkgName) {
9
+ console.error(chalk.red("请指定包名: atlisp info <包名>"));
10
+ process.exit(1);
11
+ }
12
+ const configPath = path.join(common.ATLISP_DIR, "atlisp.json");
13
+ let config = {};
14
+ if (fs.existsSync(configPath)) {
15
+ try { config = JSON.parse(fs.readFileSync(configPath, "utf-8")); } catch (e) {}
16
+ }
17
+ const installDir = config.installDir || path.join(os.homedir(), "@lisp");
18
+ const pkgDir = path.join(installDir, "packages", pkgName);
19
+ if (fs.existsSync(path.join(pkgDir, "pkg.lsp"))) {
20
+ const content = fs.readFileSync(path.join(pkgDir, "pkg.lsp"), "utf-8");
21
+ console.log(chalk.cyan("\n" + pkgName));
22
+ console.log(chalk.gray("─".repeat(40)));
23
+ const extract = (key) => {
24
+ const re = new RegExp(`:${key}\\s+([^)]+)`, "i");
25
+ const m = content.match(re);
26
+ return m ? m[1].trim().replace(/"/g, "") : "—";
27
+ };
28
+ console.log(" 描述: " + extract("desc"));
29
+ console.log(" 版本: " + extract("version"));
30
+ console.log(" 作者: " + extract("author"));
31
+ console.log(" 分类: " + extract("category"));
32
+ console.log(" 包路径: " + pkgDir);
33
+ const files = fs.readdirSync(pkgDir);
34
+ const lspFiles = files.filter(f => f.endsWith(".lsp"));
35
+ if (lspFiles.length > 0) console.log(" 文件: " + lspFiles.join(", "));
36
+ } else {
37
+ console.log(chalk.yellow(`\n包 "${pkgName}" 未安装`));
38
+ console.log(chalk.gray(` 可用: atlisp pull ${pkgName}`));
39
+ }
40
+ }
41
+
42
+ function register(reg) {
43
+ reg("info", (args) => cmdInfo(args[0]));
44
+ }
45
+
46
+ module.exports = { register };
@@ -0,0 +1,202 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const chalk = require("chalk");
4
+
5
+ async function cmdInit(pkgName) {
6
+ if (!pkgName) {
7
+ console.error(chalk.red("用法: atlisp init <包名> [--template basic|dialog|ai|enterprise|module] [--author <作者>]"));
8
+ process.exit(1);
9
+ }
10
+ const opts = {};
11
+ for (let i = 0; i < process.argv.length; i++) {
12
+ if (process.argv[i] === pkgName) {
13
+ const remainingArgs = process.argv.slice(i + 1);
14
+ for (let j = 0; j < remainingArgs.length; j++) {
15
+ if (remainingArgs[j].startsWith("--")) {
16
+ const key = remainingArgs[j].slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
17
+ const val = remainingArgs[j + 1] && !remainingArgs[j + 1].startsWith("--") ? remainingArgs[j + 1] : true;
18
+ if (val !== true) j++;
19
+ opts[key] = val;
20
+ }
21
+ }
22
+ break;
23
+ }
24
+ }
25
+ const template = opts.template || "basic";
26
+ const author = opts.author || process.env.USERNAME || "anonymous";
27
+ const safe = pkgName.toLowerCase().replace(/[^a-z0-9-]/g, "");
28
+ if (safe !== pkgName) {
29
+ console.log(chalk.yellow(` 包名已规范化为: ${safe}`));
30
+ }
31
+ const dir = path.join(process.cwd(), safe);
32
+ if (fs.existsSync(dir)) {
33
+ console.error(chalk.red(`错误: 目录已存在: ${dir}`));
34
+ process.exit(1);
35
+ }
36
+ fs.mkdirSync(dir, { recursive: true });
37
+
38
+ const templates = {
39
+ basic: {
40
+ lsp: `;; ${safe}.lsp — @lisp package
41
+
42
+ (in-package :@)
43
+ (use-package :autolisp)
44
+
45
+ (defun ${safe}:hello ()
46
+ "Say hello from ${safe}"
47
+ "string"
48
+ "(${safe}:hello)"
49
+ (strcat (_"Hello, this is ") (_"${safe}") "!"))
50
+ `,
51
+ extra: []
52
+ },
53
+ dialog: {
54
+ lsp: `;; ${safe}.lsp — @lisp package with DCL dialog
55
+
56
+ (in-package :@)
57
+ (use-package :autolisp)
58
+
59
+ (defun ${safe}:dialog (/ dcl-id)
60
+ "Open main dialog for ${safe}"
61
+ "string"
62
+ "(${safe}:dialog)"
63
+ (require 'dcl:*)
64
+ (dcl:dialog "${safe}"
65
+ :button{key="ok";label=(_"OK");is_default=true;}
66
+ :text{key="msg";label=(_"Welcome to ${safe}!");}))
67
+ `,
68
+ extra: []
69
+ },
70
+ ai: {
71
+ lsp: `;; ${safe}.lsp — @lisp package with AI agent
72
+
73
+ (in-package :@)
74
+ (use-package :autolisp)
75
+
76
+ (defun ${safe}:ask (prompt)
77
+ "Ask AI about ${safe} related topics"
78
+ "string"
79
+ "(${safe}:ask \"How to use?\")"
80
+ (if (@::load-module 'aibot)
81
+ (@::chat-prompt prompt (_"${safe}") nil)
82
+ (_"AI module not available.")))
83
+ `,
84
+ extra: []
85
+ },
86
+ module: {
87
+ lsp: `;; ${safe}.lsp — @lisp kernel module (on-demand)
88
+ ;; Provides: ${safe}
89
+
90
+ (in-package :@)
91
+ (use-package :autolisp)
92
+
93
+ (defun ${safe}:hello ()
94
+ "Example function for ${safe} module"
95
+ "string"
96
+ "(${safe}:hello)"
97
+ (strcat (_"Hello from ${safe} module!")))
98
+ `,
99
+ extra: [],
100
+ test: `;; test-${safe}.lsp — tests for ${safe} module
101
+
102
+ (in-package :@)
103
+ (use-package :autolisp)
104
+
105
+ (@::deftest '${safe}-loaded
106
+ '(progn
107
+ (@::assert-true (boundp '${safe}:hello)
108
+ "${safe}:hello is bound")))
109
+
110
+ (princ (strcat "\\n;; test-${safe}.lsp loaded.\\n"))
111
+ `,
112
+ subfile: `;; ${safe}/core.lsp — sub-module for ${safe}
113
+
114
+ (in-package :@)
115
+ (use-package :autolisp)
116
+
117
+ (defun ${safe}:core-init ()
118
+ "Initialize core sub-module"
119
+ "t"
120
+ "(${safe}:core-init)"
121
+ t)
122
+ `
123
+ },
124
+ enterprise: {
125
+ lsp: `;; ${safe}.lsp — @lisp enterprise package
126
+
127
+ (in-package :@)
128
+ (use-package :autolisp)
129
+
130
+ (defun ${safe}:init ()
131
+ "Initialize enterprise module"
132
+ "string"
133
+ "(${safe}:init)"
134
+ (@::@log "INFO" (strcat (_"Loading ") "${safe}" (_" enterprise module...)"))
135
+ (@::define-config (quote ${safe}:enabled) 1 (_"Enable ${safe}"))
136
+ (@::define-config (quote ${safe}:endpoint) "" (_"Enterprise API endpoint"))
137
+ (@::@log "INFO" (strcat "${safe}" (_" loaded."))))
138
+
139
+ (defun ${safe}:check-license ()
140
+ "Check enterprise license"
141
+ "bool"
142
+ "(${safe}:check-license)"
143
+ (if (= 1 (@::get-config (quote ${safe}:enabled)))
144
+ (progn
145
+ (@::require (quote permission:*))
146
+ (permission:check (quote admin) "${safe}"))
147
+ nil))
148
+ `,
149
+ extra: []
150
+ }
151
+ };
152
+
153
+ const tpl = templates[template] || templates.basic;
154
+ const pkgLsp = tpl.lsp;
155
+ const pkgDef = `;; pkg.lsp — ${safe} package definition
156
+
157
+ (:name "${safe}"
158
+ :full-name "${safe}"
159
+ :description "A @lisp package"
160
+ :version "0.1.0"
161
+ :author "${author}"
162
+ :email ""
163
+ :category "tools"
164
+ :files ("${safe}")
165
+ :required nil)
166
+ `;
167
+
168
+ fs.writeFileSync(path.join(dir, "pkg.lsp"), pkgDef, "utf-8");
169
+ fs.writeFileSync(path.join(dir, safe + ".lsp"), pkgLsp, "utf-8");
170
+ if (template === "dialog") {
171
+ fs.writeFileSync(path.join(dir, safe + ".dcl"),
172
+ `${safe} : dialog {\n label = "${safe}";\n}\n`, "utf-8");
173
+ }
174
+ if (template === "module") {
175
+ const testDir = path.join(process.cwd(), "test");
176
+ if (!fs.existsSync(testDir)) fs.mkdirSync(testDir, { recursive: true });
177
+ fs.writeFileSync(path.join(testDir, `test-${safe}.lsp`), tpl.test, "utf-8");
178
+ const subDir = path.join(dir, safe);
179
+ fs.mkdirSync(subDir, { recursive: true });
180
+ fs.writeFileSync(path.join(subDir, "core.lsp"), tpl.subfile, "utf-8");
181
+ }
182
+ console.log(chalk.green(`\n✓ 包 "${safe}" 已创建 (模板: ${template}): ${dir}`));
183
+ console.log(chalk.gray(` ${dir}/pkg.lsp`));
184
+ console.log(chalk.gray(` ${dir}/${safe}.lsp`));
185
+ if (fs.existsSync(path.join(dir, safe + ".dcl")))
186
+ console.log(chalk.gray(` ${dir}/${safe}.dcl`));
187
+ if (template === "module") {
188
+ console.log(chalk.gray(` ${dir}/${safe}/core.lsp`));
189
+ console.log(chalk.gray(` test/test-${safe}.lsp`));
190
+ }
191
+ console.log(chalk.cyan("\n后续步骤:"));
192
+ console.log(chalk.gray(" 1. 编辑 pkg.lsp 修改描述、版本等信息"));
193
+ console.log(chalk.gray(" 2. 编辑 " + safe + ".lsp 添加功能"));
194
+ console.log(chalk.gray(" 3. 本地测试: atlisp pull " + safe));
195
+ console.log(chalk.gray(" 4. 发布: 上传到 atlisp.cn 或私有仓库"));
196
+ }
197
+
198
+ function register(reg) {
199
+ reg("init", (args) => cmdInit(args[0]));
200
+ }
201
+
202
+ module.exports = { register };
@@ -0,0 +1,23 @@
1
+ const chalk = require("chalk");
2
+ const common = require("../lib/common");
3
+ const cad = require("../lib/cad");
4
+
5
+ async function handler(args) {
6
+ const opts = common.parseArgs(args);
7
+ if (!opts.auto) {
8
+ console.log("\n安装 @lisp Core 到 CAD...");
9
+ console.log("复制以下代码到CAD命令行,即可开始安装:\n");
10
+ console.log(common.installCode);
11
+ console.log("\n或使用 --auto 参数自动安装(仅 Windows):");
12
+ console.log(" atlisp install --auto");
13
+ return;
14
+ }
15
+ console.log("\n自动安装 @lisp Core 到 CAD...");
16
+ await cad.installAtLisp(opts.app, opts.profile);
17
+ }
18
+
19
+ function register(reg) {
20
+ reg("install", handler, ["i"]);
21
+ }
22
+
23
+ module.exports = { register };
@@ -0,0 +1,79 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const chalk = require("chalk");
4
+
5
+ const { getKernelSrc, getKernelTest, getKernelRoot } = require("../lib/common");
6
+ const KERNEL_ROOT = getKernelRoot();
7
+ const KERNEL_SRC = getKernelSrc();
8
+ const TEST_DIR = getKernelTest();
9
+ const MODULES_DIR = path.join(KERNEL_ROOT, "modules");
10
+
11
+ async function cmdLint(target, fix) {
12
+ let lintFiles, applyFixes, loadConfig, formatDefault;
13
+ try {
14
+ const runner = require("@atlisp/lint/dist/runner");
15
+ const configMod = require("@atlisp/lint/dist/config");
16
+ const formatters = require("@atlisp/lint/dist/formatters");
17
+ lintFiles = runner.lintFiles;
18
+ applyFixes = runner.applyFixes;
19
+ loadConfig = configMod.loadConfig;
20
+ formatDefault = formatters.formatDefault;
21
+ } catch (e) {
22
+ console.error(chalk.red(" ✗ @atlisp/lint 未安装。请运行: npm install @atlisp/lint --save"));
23
+ process.exit(1);
24
+ }
25
+
26
+ const dirs = target ? [path.resolve(KERNEL_ROOT, target)] : [KERNEL_SRC, TEST_DIR, MODULES_DIR].filter(d => fs.existsSync(d));
27
+ const config = loadConfig();
28
+ let hasErrors = false;
29
+
30
+ for (const dir of dirs) {
31
+ const rel = path.relative(KERNEL_ROOT, dir);
32
+ const files = collectLspFiles(dir);
33
+ if (files.length === 0) {
34
+ console.log(chalk.yellow(` ⚠ ${rel} 中没有 .lsp 文件`));
35
+ continue;
36
+ }
37
+ console.log(chalk.cyan(`\n检查 ${rel} (${files.length} 个文件)...`));
38
+ const issues = lintFiles(files, config, KERNEL_ROOT);
39
+ if (issues.length === 0) {
40
+ console.log(chalk.green(" ✓ 无问题"));
41
+ } else {
42
+ const output = formatDefault(issues, null);
43
+ process.stdout.write(output);
44
+ if (issues.some(i => i.severity === "error")) hasErrors = true;
45
+ if (fix) {
46
+ applyFixes(KERNEL_ROOT, issues, config);
47
+ console.log(chalk.green(` ✓ 已修复 ${issues.length} 个问题`));
48
+ }
49
+ }
50
+ }
51
+ if (hasErrors && !fix) process.exit(1);
52
+ }
53
+
54
+ function collectLspFiles(dir) {
55
+ const result = [];
56
+ function walk(d) {
57
+ const entries = fs.readdirSync(d, { withFileTypes: true });
58
+ for (const e of entries) {
59
+ const full = path.join(d, e.name);
60
+ if (e.isDirectory()) walk(full);
61
+ else if (e.name.endsWith(".lsp")) result.push(full);
62
+ }
63
+ }
64
+ walk(dir);
65
+ return result;
66
+ }
67
+
68
+ async function handler(args) {
69
+ const clean = args.filter(a => a !== "--" && a !== "--fix");
70
+ const fix = args.includes("--fix");
71
+ const target = clean[0];
72
+ await cmdLint(target, fix);
73
+ }
74
+
75
+ function register(reg) {
76
+ reg("lint", (args) => handler(args), ["check"]);
77
+ }
78
+
79
+ module.exports = { register, cmdLint, collectLspFiles };
@@ -0,0 +1,99 @@
1
+ const chalk = require("chalk");
2
+ const common = require("../lib/common");
3
+
4
+ async function cmdList() {
5
+ console.log("\n正在获取应用包列表...\n");
6
+ try {
7
+ const text = await common.httpGet("/packages-list?edition=stable");
8
+ if (text && text.length > 0) {
9
+ const pkgs = common.parsePackages(text);
10
+ if (pkgs.length > 0) {
11
+ const byCategory = {};
12
+ for (const p of pkgs) {
13
+ const cat = p.category || "未分类";
14
+ if (!byCategory[cat]) byCategory[cat] = [];
15
+ byCategory[cat].push(p);
16
+ }
17
+ for (const [cat, list] of Object.entries(byCategory)) {
18
+ console.log(chalk.bold("\n【" + cat + "】"));
19
+ for (const p of list) {
20
+ console.log(" " + chalk.cyan(p.name) + " - " + p.fullName);
21
+ if (p.description) console.log(" " + p.description);
22
+ }
23
+ }
24
+ console.log(chalk.gray("\n共 " + pkgs.length + " 个应用包"));
25
+ return;
26
+ }
27
+ }
28
+ } catch (err) {}
29
+ const local = common.readLocalPackages();
30
+ if (local.length > 0) {
31
+ console.log(chalk.bold("【已安装的本地应用包】"));
32
+ for (const p of local) {
33
+ const ver = p.version ? chalk.gray(" v" + p.version) : "";
34
+ console.log(" " + chalk.cyan(p.name) + ver + " - " + p.fullName);
35
+ if (p.description) console.log(" " + p.description);
36
+ }
37
+ console.log(chalk.gray("\n共 " + local.length + " 个已安装的应用包"));
38
+ return;
39
+ }
40
+ console.log("当前无可用的应用包列表。");
41
+ console.log("请检查网络连接,或访问 https://atlisp.cn 查看可用应用包。");
42
+ }
43
+
44
+ async function cmdSearch(query) {
45
+ if (!query) {
46
+ console.error(chalk.red("错误: 请指定搜索关键词。用法: atlisp search <关键词>"));
47
+ process.exit(1);
48
+ }
49
+ console.log("\n正在搜索 \"" + query + "\"...\n");
50
+ const q = query.toLowerCase();
51
+ let matched = [];
52
+ try {
53
+ const text = await common.httpGet("/packages-list?edition=stable");
54
+ if (text && text.length > 0) {
55
+ const pkgs = common.parsePackages(text);
56
+ matched = pkgs.filter(p =>
57
+ p.name.toLowerCase().includes(q) ||
58
+ p.fullName.toLowerCase().includes(q) ||
59
+ p.description.toLowerCase().includes(q) ||
60
+ p.category.toLowerCase().includes(q)
61
+ );
62
+ }
63
+ } catch (err) {}
64
+ if (matched.length === 0) {
65
+ const local = common.readLocalPackages();
66
+ matched = local.filter(p =>
67
+ p.name.toLowerCase().includes(q) ||
68
+ p.fullName.toLowerCase().includes(q) ||
69
+ p.description.toLowerCase().includes(q)
70
+ );
71
+ }
72
+ if (matched.length === 0) {
73
+ console.log("未找到与 \"" + query + "\" 相关的应用包。");
74
+ console.log("请访问 https://atlisp.cn 搜索可用应用包。");
75
+ return;
76
+ }
77
+ for (const p of matched) {
78
+ console.log(" " + chalk.cyan(p.name) + " - " + p.fullName);
79
+ if (p.description) console.log(" " + p.description);
80
+ console.log(chalk.gray(" 分类: " + (p.category || "未分类")));
81
+ }
82
+ console.log(chalk.gray("\n找到 " + matched.length + " 个匹配的应用包"));
83
+ }
84
+
85
+ async function handler(args) {
86
+ const cmd = args[0];
87
+ if (cmd === "search" || cmd === "s") {
88
+ await cmdSearch(args[1]);
89
+ } else {
90
+ await cmdList();
91
+ }
92
+ }
93
+
94
+ function register(reg) {
95
+ reg("list", (args) => cmdList(), ["l"]);
96
+ reg("search", (args) => cmdSearch(args[0]), ["s"]);
97
+ }
98
+
99
+ module.exports = { register };