create-napukettoqq 0.0.1 → 0.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.
package/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # create-napukettoqq
2
+
3
+ 一键生成 NapukettoQQ 机器人项目(pnpm / yarn / npm 均可)。
4
+
5
+ ```bash
6
+ pnpm create napukettoqq # 交互输入部署目录名(回车取默认 NapukettoQQ)
7
+ pnpm create napukettoqq my-bot # 位置参数指定目录,跳过交互
8
+ ```
9
+
10
+ 生成 `package.json` / `napuketto.toml` / `readme.md` / `.gitignore` 骨架,用调用方包管理器自动安装依赖,并询问是否立即启动(默认 Y)。
11
+
12
+ 生成的用户项目依赖发布版的 `@napuketto/cli`,`pnpm start` 即启动。
package/dist/index.mjs CHANGED
@@ -1,19 +1,52 @@
1
1
  #!/usr/bin/env node
2
- import process$1 from "node:process";
3
- import { createInterface } from "node:readline/promises";
4
- import { mkdir, readdir, writeFile } from "node:fs/promises";
2
+ import { spawn } from "node:child_process";
3
+ import { readFileSync } from "node:fs";
4
+ import path, { relative } from "node:path";
5
+ import process from "node:process";
6
+ import { cancel, confirm, intro, isCancel, log, outro, spinner, text } from "@clack/prompts";
7
+ import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
5
8
  import { homedir } from "node:os";
6
- import path from "node:path";
7
9
  //#region src/scaffold.ts
8
10
  /**
9
- * create-napukettoqq 脚手架核心:模板 + 目标目录创建 + 文件写入。
11
+ * create-napukettoqq 脚手架核心:模板渲染 + 目标目录创建 + 文件写入。
10
12
  *
11
- * 零依赖(仅 node:fs/promises / node:os / node:path / node:process),
12
- * 一次性工具保持自包含。生成的用户项目依赖发布版的 @napuketto/cli,
13
- * 用户 install 后经 cli loader 自建宿主启动。
13
+ * 模板为独立文件(templates/ 目录,运行时读取),不硬编码在源码里;
14
+ * 需要插值的占位符用 {{key}} 标记(见 renderTemplate)。
15
+ * 依赖极轻(node:fs/promises / node:os / node:path / node:process)。
16
+ * 生成的用户项目依赖发布版的 @napuketto/cli,用户 install 后经 cli → loader
17
+ * 自建宿主启动。
14
18
  */
15
- /** 生成的用户项目所依赖的 @napuketto/cli 版本范围(发布新版时统一改)。 */
16
- const CLI_VERSION = "^0.0.1";
19
+ /** 模板目录(相对本文件:src dist 都在包根下,../templates 均指向包根/templates)。 */
20
+ const TEMPLATES = new URL("../templates/", import.meta.url);
21
+ /**
22
+ * 生成的用户项目所依赖的 @napuketto/cli 版本范围。
23
+ * 运行时从脚手架自身 package.json 读取(dependencies["@napuketto/cli"])——
24
+ * 发版时 changesets 升版本 + pnpm publish 把 workspace:* 替换为实际版本,
25
+ * 模板版本自动跟随,无需手动同步(比硬编码常量可靠)。
26
+ */
27
+ function cliVersionRange() {
28
+ try {
29
+ const range = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).dependencies?.["@napuketto/cli"];
30
+ if (range !== void 0 && range !== "workspace:*") return range;
31
+ } catch {}
32
+ return "^0.0.1";
33
+ }
34
+ /** 默认部署文件夹名(用户交互回车缺省值)。 */
35
+ const DEFAULT_PROJECT_NAME = "NapukettoQQ";
36
+ /**
37
+ * 由 npm_config_user_agent 检测调用方包管理器(yarn create / npm create /
38
+ * pnpm create 都会注入该变量)。兜底 pnpm(项目生态,或直接 node 运行)。
39
+ */
40
+ function detectPackageManager() {
41
+ const ua = process.env["npm_config_user_agent"] ?? "";
42
+ if (ua.startsWith("yarn/")) return "yarn";
43
+ if (ua.startsWith("npm/")) return "npm";
44
+ return "pnpm";
45
+ }
46
+ /** Windows 下包管理器 bin 需带 .cmd 后缀(CreateProcess 不认无扩展名 shim)。 */
47
+ function pmBin(pm) {
48
+ return process.platform === "win32" ? `${pm}.cmd` : pm;
49
+ }
17
50
  /** Windows 路径非法字符(项目文件夹名校验用)。 */
18
51
  const ILLEGAL_NAME = /[\\/:*?"<>|]/;
19
52
  /** 校验文件夹名:非空、非 . / ..、不含 Windows 非法字符。 */
@@ -27,170 +60,65 @@ function validateProjectName(raw) {
27
60
  function derivePackageName(dirName) {
28
61
  return dirName.toLowerCase().replace(/\s+/g, "-");
29
62
  }
30
- /** 生成 package.json(用户项目,JSON.stringify 保证合法)。 */
31
- function packageJsonTemplate(packageName) {
32
- return `${JSON.stringify({
33
- name: packageName,
34
- version: "0.0.1",
35
- private: true,
36
- type: "module",
37
- description: "NapukettoQQ 机器人项目(OneBot 11)",
38
- scripts: { start: "napuketto" },
39
- dependencies: { "@napuketto/cli": CLI_VERSION }
40
- }, null, 4)}\n`;
63
+ /** 解析目标目录绝对路径(校验名称后,基于 cwd)。 */
64
+ function resolveTargetDir(dirName) {
65
+ return path.resolve(process.cwd(), validateProjectName(dirName));
66
+ }
67
+ /** 检测目标目录状态(供交互层决定是否询问清空)。 */
68
+ async function checkDirStatus(dir) {
69
+ let entries = [];
70
+ try {
71
+ entries = await readdir(dir);
72
+ } catch {
73
+ return "missing";
74
+ }
75
+ return entries.length === 0 ? "empty" : "nonempty";
76
+ }
77
+ /** 清空目录内容(保留目录本身)。 */
78
+ async function emptyDir(dir) {
79
+ for (const entry of await readdir(dir)) await rm(path.join(dir, entry), {
80
+ recursive: true,
81
+ force: true
82
+ });
83
+ }
84
+ /** 读取模板文件(templates/ 目录,UTF-8)。 */
85
+ function readTemplate(file) {
86
+ return readFile(new URL(file, TEMPLATES), "utf8");
41
87
  }
42
88
  /**
43
- * 生成 napuketto.toml:与 cli configTemplate(apps/cli/src/config-cmds.ts)同款内容,
44
- * dataDir 按当前用户主目录插值(JSON.stringify 转义反斜杠 TOML 基本字符串合法写法)。
89
+ * 占位符渲染:把 {{key}} 替换为 vars[key]。
90
+ * 模板中出现未提供的占位符抛错(防漏插值写出半成品)。
45
91
  */
46
- function tomlTemplate() {
47
- return `# ============================================================
48
- # NapukettoQQ 全局配置文件(由 create-napukettoqq 生成)
49
- # ============================================================
50
- # 本文件与项目 napuketto.toml.example 内容一致(参考模板)。
51
- # 数据(账号目录/日志/缓存/QQ 数据)不在此文件,按数据根组织:
52
- # 优先级:--data-dir <dir> > NAPKETTO_DATA 环境变量 > ~/.napuketto
53
- # 配置文件路径解析:NAPKETTO_CONFIG > 项目根探测 > cwd > 数据根兜底。
54
- #
55
- # 单账号启动每次读取本文件;修改后重启生效。
56
-
57
- # ------------------------------------------------------------
58
- # 基础配置
59
- # ------------------------------------------------------------
60
-
61
- # 数据根目录:账号目录/日志/缓存/QQ 数据存放位置(绝对路径)
62
- dataDir = ${JSON.stringify(path.join(homedir(), ".napuketto"))}
63
-
64
- # 多账号(supervisor)模式下,账号进程崩溃是否自动重启
65
- autoRestart = true
66
-
67
- # 崩溃后自动重启的延迟(毫秒)
68
- restartDelayMs = 2000
69
-
70
- # 多账号列表(留空 = 单账号模式:启动后自动快速登录/扫码)
71
- # [[accounts]]
72
- # qq = "123456" # QQ 号(必填)
73
- # enabled = true # 是否启用(可选,缺省 true)
74
-
75
- # ------------------------------------------------------------
76
- # OneBot 11 协议段
77
- # ------------------------------------------------------------
78
- [onebot11]
79
-
80
- # 鉴权 token(全局默认):HTTP/WS 连接必须携带。
81
- # 校验方式:Authorization: Bearer <token> / 裸 token / URL 参数 ?access_token=<token>
82
- # 每个网络实例可单独覆盖(见下文各实例的 token 字段)。
83
- # 留空 = 不鉴权(仅本机调试推荐)。
84
- # token = ""
85
-
86
- # 心跳 meta 事件间隔(毫秒);0 = 关闭心跳事件
87
- heartbeatInterval = 3000
88
-
89
- # 是否上报机器人自己发的消息(OB11 规范默认不上报)
90
- reportSelfMessage = false
91
-
92
- # 消息内容格式:array = 消息段数组(标准 OneBot 11),string = CQ 码字符串
93
- messagePostFormat = "array"
94
-
95
- # ---- HTTP 反向服务器(接收第三方 POST 指令),可配置多个 ----
96
- [[onebot11.httpServers]]
97
- enabled = true
98
- host = "127.0.0.1" # 监听地址;0.0.0.0 = 监听所有网卡(局域网可连)
99
- port = 3000
100
- # token = "abc" # 可选:覆盖全局 token
101
-
102
- # ---- HTTP 正向上报(Webhook:把事件 POST 到第三方地址),可配置多个 ----
103
- # [[onebot11.httpPostUrls]]
104
- # enabled = true
105
- # url = "http://127.0.0.1:8080/onebot" # 第三方接收地址
106
- # token = "abc" # 可选:覆盖全局 token
107
- # timeoutMs = 5000 # 可选:上报超时(毫秒),缺省不超时
108
-
109
- # ---- WS 反向服务器(外部 WS 客户端主动连入),可配置多个 ----
110
- [[onebot11.wsServers]]
111
- enabled = true
112
- host = "127.0.0.1"
113
- port = 3001
114
- # token = "abc" # 可选:覆盖全局 token
115
- # heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)
116
-
117
- # ---- WS 正向客户端(主动连接外部 WS,双向),可配置多个 ----
118
- # [[onebot11.wsReverseUrls]]
119
- # enabled = true
120
- # url = "ws://127.0.0.1:8081/ws" # 第三方 WS 地址(ws:// 或 wss://)
121
- # token = "abc" # 可选:覆盖全局 token
122
- # reconnectDelayMs = 5000 # 可选:断线重连延迟(毫秒)
123
- # maxReconnectAttempts = 10 # 可选:最大重连次数(缺省无限)
124
- # rejectUnauthorized = true # 可选:wss:// 是否校验证书(自签证书设 false)
125
- # heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)
126
- `;
127
- }
128
- /** 生成 readme.md(用户项目使用说明)。 */
129
- function readmeTemplate(dirName) {
130
- return `# ${dirName}
131
-
132
- 基于 **NapukettoQQ** 的 QQ 机器人项目(OneBot 11 协议),由 \`create-napukettoqq\` 生成。
133
-
134
- ## 快速开始
135
-
136
- \`\`\`bash
137
- pnpm install
138
- pnpm start -q 123456 # 换成你的 QQ 号
139
- \`\`\`
140
-
141
- 启动后按终端提示快速登录或扫码;登录成功后 OneBot 11 接口生效
142
- (默认 HTTP \`127.0.0.1:3000\` + WS \`127.0.0.1:3001\`,见 \`napuketto.toml\`)。
143
-
144
- ## 前置要求
145
-
146
- - 已安装 **QQ NT**(Windows,机器人所需的 \`wrapper.node\` 来自 QQ 安装目录,
147
- 未自动探测到时用 \`pnpm start --qq-path <QQ安装目录>\` 指定)
148
- - Node.js 20+ 与 pnpm
149
-
150
- ## 常用命令
151
-
152
- | 命令 | 说明 |
153
- |---|---|
154
- | \`pnpm start -q <QQ号>\` | 单账号启动(走自建宿主) |
155
- | \`pnpm napuketto config init\` | 重新生成默认配置 |
156
- | \`pnpm napuketto supervisor\` | 多账号模式(按 \`napuketto.toml\` 的 \`accounts\`) |
157
-
158
- ## 目录
159
-
160
- - \`napuketto.toml\` — 全局配置(QQ 号、OneBot 端口、token 等,修改后重启生效)
161
- - QQ 数据(账号目录/日志/缓存)默认在 \`~/.napuketto/\`
162
-
163
- ## 升级
164
-
165
- \`\`\`bash
166
- pnpm update @napuketto/cli
167
- \`\`\`
168
- `;
92
+ function renderTemplate(content, vars) {
93
+ return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
94
+ const value = vars[key];
95
+ if (value === void 0) throw new Error(`模板占位符未提供:{{${key}}}(模板 ${match} 无法渲染)`);
96
+ return value;
97
+ });
169
98
  }
170
- /** 生成 .gitignore(用户项目)。 */
171
- const GITIGNORE = `node_modules/
172
- dist/
173
- *.log
174
- napuketto-data/
175
- `;
176
99
  /**
177
100
  * 生成 NapukettoQQ 项目骨架到当前目录下的 <dirName>/。
178
- * 目标目录已存在且非空抛错(提示换名字或先清空)。
101
+ * 目标目录已存在且非空:overwrite=false抛错;overwrite=true → 清空后生成。
102
+ * 模板与写入目标一一对应(同文件名)。
179
103
  */
180
- async function scaffoldProject(dirName) {
104
+ async function scaffoldProject(dirName, overwrite = false) {
181
105
  const name = validateProjectName(dirName);
182
106
  const packageName = derivePackageName(name);
183
- const dir = path.resolve(process.cwd(), name);
184
- let existing = [];
185
- try {
186
- existing = await readdir(dir);
187
- } catch {}
188
- if (existing.length > 0) throw new Error(`目录已存在且非空:${dir}\n请换一个文件夹名,或先清空该目录。`);
107
+ const dir = resolveTargetDir(name);
108
+ const status = await checkDirStatus(dir);
109
+ if (status === "nonempty" && !overwrite) throw new Error(`目录已存在且非空:${dir}\n请换一个文件夹名,或先清空该目录。`);
110
+ if (status === "nonempty") await emptyDir(dir);
111
+ const vars = {
112
+ packageName,
113
+ cliVersion: cliVersionRange(),
114
+ dataDir: JSON.stringify(path.join(homedir(), ".napuketto"))
115
+ };
116
+ const files = ["package.json", "napuketto.toml"];
189
117
  await mkdir(dir, { recursive: true });
190
- await writeFile(path.join(dir, "package.json"), packageJsonTemplate(packageName), "utf8");
191
- await writeFile(path.join(dir, "napuketto.toml"), tomlTemplate(), "utf8");
192
- await writeFile(path.join(dir, "readme.md"), readmeTemplate(name), "utf8");
193
- await writeFile(path.join(dir, ".gitignore"), GITIGNORE, "utf8");
118
+ for (const file of files) {
119
+ const rendered = renderTemplate(await readTemplate(`${file}.tmpl`), vars);
120
+ await writeFile(path.join(dir, file), rendered, "utf8");
121
+ }
194
122
  return {
195
123
  dir,
196
124
  packageName
@@ -199,48 +127,166 @@ async function scaffoldProject(dirName) {
199
127
  //#endregion
200
128
  //#region src/index.ts
201
129
  /**
202
- * create-napukettoqq 入口(2026-08-07)。
130
+ * create-napukettoqq 入口(2026-08-07 美化 v3,交互层用 @clack/prompts——
131
+ * 现代脚手架交互标准,create-vue / create-turbo 同款)。
203
132
  *
204
133
  * 用法:
205
- * create-napukettoqq # 交互问部署文件夹名(默认 NapukettoQQ)
206
- * create-napukettoqq my-bot # 位置参数指定文件夹名,跳过交互
134
+ * create-napukettoqq # intro + 交互问部署文件夹名(默认 NapukettoQQ)
135
+ * create-napukettoqq my-bot # 位置参数指定文件夹名,跳过命名交互
136
+ * create-napukettoqq my-bot -f # 目标目录非空时强制清空覆盖
137
+ * create-napukettoqq -y # 全默认(命名用默认值、启动询问用默认 Y)
138
+ * create-napukettoqq -h # 打印帮助
207
139
  *
208
- * 在当前目录下生成 NapukettoQQ 机器人项目骨架(package.json / napuketto.toml /
209
- * readme.md / .gitignore),随后打印下一步指引(pnpm install && pnpm start)。
140
+ * 流程:intro 问文件夹名 目标目录准备(非空需确认清空)→ spinner 生成骨架
141
+ * (package.json / napuketto.toml / readme.md / .gitignore)→ 用调用方包管理器
142
+ * 自动 install → 询问是否现在启动(默认 Y)→ 前台运行 → outro 收尾。
210
143
  */
211
- /** 交互询问部署文件夹名(回车取默认值)。 */
212
- async function askProjectName() {
213
- const rl = createInterface({
214
- input: process$1.stdin,
215
- output: process$1.stdout
216
- });
144
+ /** 帮助文本(-h/--help)。 */
145
+ const HELP = ` 用法: create-napukettoqq [name] [选项]
146
+
147
+ 选项:
148
+ -f, --forced 目标目录非空时强制清空并覆盖
149
+ -y, --yes 跳过所有交互提示(使用默认值)
150
+ -h, --help 显示本帮助
151
+ `;
152
+ /** 解析命令行参数(flag 仅三个,手写保持自包含,不引 yargs-parser)。 */
153
+ function parseArgs(argv) {
154
+ const opts = {
155
+ forced: false,
156
+ yes: false,
157
+ help: false
158
+ };
159
+ for (const arg of argv) if (arg === "-f" || arg === "--forced") opts.forced = true;
160
+ else if (arg === "-y" || arg === "--yes") opts.yes = true;
161
+ else if (arg === "-h" || arg === "--help") opts.help = true;
162
+ else if (arg.startsWith("-")) throw new Error(`未知参数:${arg}`);
163
+ else if (opts.name === void 0) opts.name = arg;
164
+ else throw new Error(`多余的位置参数:${arg}`);
165
+ return opts;
166
+ }
167
+ /** 读取脚手架自身版本号(intro 显示;dist 场景 import.meta.url 指向包内)。 */
168
+ function readVersion() {
217
169
  try {
218
- return (await rl.question(`? 部署文件夹名(默认 NapukettoQQ): `)).trim() || "NapukettoQQ";
219
- } finally {
220
- rl.close();
170
+ return JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version ?? "0.0.0";
171
+ } catch {
172
+ return "0.0.0";
221
173
  }
222
174
  }
223
- /** 打印生成结果与下一步指引(功能输出,不经日志框架)。 */
224
- function printNextSteps(dir) {
225
- console.log("\n========================================");
226
- console.log(" NapukettoQQ 项目已生成");
227
- console.log(` 位置:${dir}`);
228
- console.log("========================================");
229
- console.log("下一步:");
230
- console.log(` cd ${dir}`);
231
- console.log(" pnpm install");
232
- console.log(" pnpm start -q <你的QQ号>");
233
- console.log("");
234
- console.log("前置要求:本机已安装 QQ NT(wrapper.node 来自 QQ 安装目录)。");
235
- console.log("详细说明见项目内 readme.md napuketto.toml。");
175
+ /** 交互询问部署文件夹名(回车取默认值;-y 直接返回默认值)。 */
176
+ async function askProjectName(yes) {
177
+ if (yes) return DEFAULT_PROJECT_NAME;
178
+ const name = await text({
179
+ message: "部署文件夹名",
180
+ placeholder: DEFAULT_PROJECT_NAME,
181
+ initialValue: DEFAULT_PROJECT_NAME,
182
+ validate: (input) => {
183
+ try {
184
+ validateProjectName(input ?? "");
185
+ return;
186
+ } catch (err) {
187
+ return err instanceof Error ? err.message : String(err);
188
+ }
189
+ }
190
+ });
191
+ if (isCancel(name)) {
192
+ cancel("操作已取消");
193
+ process.exit(0);
194
+ }
195
+ return validateProjectName(name);
196
+ }
197
+ /** 询问是否移除现有文件并继续(目标目录非空时)。 */
198
+ async function confirmRemove(dirName) {
199
+ const ok = await confirm({
200
+ message: `目录 "${dirName}" 已存在且非空,移除现有文件并继续?`,
201
+ initialValue: false
202
+ });
203
+ if (isCancel(ok)) {
204
+ cancel("操作已取消");
205
+ process.exit(0);
206
+ }
207
+ return ok;
208
+ }
209
+ /** 询问是否现在启动(默认 Y;-y 直接返回 true)。 */
210
+ async function askStartNow(yes) {
211
+ if (yes) return true;
212
+ const ok = await confirm({
213
+ message: "是否现在启动?",
214
+ initialValue: true
215
+ });
216
+ if (isCancel(ok)) {
217
+ cancel("操作已取消");
218
+ process.exit(0);
219
+ }
220
+ return ok;
221
+ }
222
+ /** 前台执行命令(stdio 继承,用户可见进度/交互),返回退出码。 */
223
+ function runInteractive(bin, args, cwd) {
224
+ return new Promise((resolve) => {
225
+ spawn(bin, args, {
226
+ cwd,
227
+ stdio: "inherit",
228
+ windowsHide: false,
229
+ shell: process.platform === "win32"
230
+ }).on("exit", (code) => {
231
+ resolve(code ?? 0);
232
+ });
233
+ });
236
234
  }
237
235
  async function main() {
238
- printNextSteps((await scaffoldProject(process$1.argv[2] ?? await askProjectName())).dir);
236
+ const opts = parseArgs(process.argv.slice(2));
237
+ if (opts.help) {
238
+ console.log(HELP);
239
+ return;
240
+ }
241
+ intro(`create-napukettoqq v${readVersion()}`);
242
+ const dirName = opts.name ?? await askProjectName(opts.yes);
243
+ const targetDir = resolveTargetDir(dirName);
244
+ let overwrite = false;
245
+ if (await checkDirStatus(targetDir) === "nonempty") if (!opts.forced) {
246
+ log.warn(`目标目录 "${dirName}" 已存在且非空。`);
247
+ overwrite = await confirmRemove(dirName);
248
+ if (!overwrite) {
249
+ outro("已取消,未做任何修改。");
250
+ return;
251
+ }
252
+ } else overwrite = true;
253
+ const s = spinner();
254
+ s.start(`正在生成项目骨架 ${dirName} ...`);
255
+ const result = await scaffoldProject(dirName, overwrite);
256
+ s.stop(`项目骨架已生成:${dirName}`);
257
+ log.success(`位置:${result.dir}`);
258
+ const pm = detectPackageManager();
259
+ log.info(`包管理器:${pm}(自动检测)`);
260
+ log.info("前置要求:本机已安装 QQ NT(wrapper.node 来自 QQ 安装目录)。");
261
+ log.info(`正在安装依赖(${pm} install)...`);
262
+ const installCode = await runInteractive(pmBin(pm), ["install"], result.dir);
263
+ if (installCode !== 0) {
264
+ log.error(`依赖安装失败(退出码 ${installCode})。请手动执行:`);
265
+ log.step(`cd ${result.dir}`);
266
+ log.step(`${pm} install`);
267
+ outro("创建失败,请手动安装后重试。");
268
+ process.exitCode = 1;
269
+ return;
270
+ }
271
+ if (await askStartNow(opts.yes)) {
272
+ log.info("正在启动 NapukettoQQ(Ctrl+C 停止)...\n");
273
+ const startCode = await runInteractive(pmBin(pm), ["start"], result.dir);
274
+ if (startCode !== 0) process.exitCode = startCode;
275
+ } else {
276
+ log.message("你可以稍后这样启动:");
277
+ if (targetDir !== process.cwd()) {
278
+ const related = relative(process.cwd(), targetDir);
279
+ log.step(`cd ${related}`);
280
+ }
281
+ log.step(`${pm} start -q <你的QQ号>`);
282
+ log.message("配置说明见项目内 napuketto.toml。");
283
+ }
284
+ outro("完成。");
239
285
  }
240
286
  main().catch((err) => {
241
287
  const message = err instanceof Error ? err.message : String(err);
242
- console.error(`创建失败:${message}`);
243
- process$1.exitCode = 1;
288
+ log.error(`创建失败:${message}`);
289
+ process.exitCode = 1;
244
290
  });
245
291
  //#endregion
246
292
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["process"],"sources":["../src/scaffold.ts","../src/index.ts"],"sourcesContent":["/**\n * create-napukettoqq 脚手架核心:模板 + 目标目录创建 + 文件写入。\n *\n * 零依赖(仅 node:fs/promises / node:os / node:path / node:process),\n * 一次性工具保持自包含。生成的用户项目依赖发布版的 @napuketto/cli,\n * 用户 install 后经 cli → loader 自建宿主启动。\n */\n\nimport { mkdir, readdir, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\n/** 生成的用户项目所依赖的 @napuketto/cli 版本范围(发布新版时统一改)。 */\nexport const CLI_VERSION = \"^0.0.1\";\n\n/** 默认部署文件夹名(用户交互回车缺省值)。 */\nexport const DEFAULT_PROJECT_NAME = \"NapukettoQQ\";\n\n/** Windows 路径非法字符(项目文件夹名校验用)。 */\nconst ILLEGAL_NAME = /[\\\\/:*?\"<>|]/;\n\n/** 脚手架生成结果。 */\nexport interface ScaffoldResult {\n /** 目标目录绝对路径。 */\n dir: string;\n /** 派生出的 npm 包名(小写、空格 → -)。 */\n packageName: string;\n}\n\n/** 校验文件夹名:非空、非 . / ..、不含 Windows 非法字符。 */\nexport function validateProjectName(raw: string): string {\n const name = raw.trim();\n if (name === \"\" || name === \".\" || name === \"..\") {\n throw new Error(\"文件夹名不能为空或为 . / ..\");\n }\n if (ILLEGAL_NAME.test(name)) {\n throw new Error(`文件夹名含非法字符:${name}(不允许 \\\\ / : * ? \" < > |)`);\n }\n return name;\n}\n\n/** 由文件夹名派生 npm 包名(小写、空格 → -)。 */\nexport function derivePackageName(dirName: string): string {\n return dirName.toLowerCase().replace(/\\s+/g, \"-\");\n}\n\n/** 生成 package.json(用户项目,JSON.stringify 保证合法)。 */\nfunction packageJsonTemplate(packageName: string): string {\n const pkg = {\n name: packageName,\n version: \"0.0.1\",\n private: true,\n type: \"module\",\n description: \"NapukettoQQ 机器人项目(OneBot 11)\",\n scripts: {\n start: \"napuketto\",\n },\n dependencies: {\n \"@napuketto/cli\": CLI_VERSION,\n },\n };\n return `${JSON.stringify(pkg, null, 4)}\\n`;\n}\n\n/**\n * 生成 napuketto.toml:与 cli configTemplate(apps/cli/src/config-cmds.ts)同款内容,\n * dataDir 按当前用户主目录插值(JSON.stringify 转义反斜杠 → TOML 基本字符串合法写法)。\n */\nfunction tomlTemplate(): string {\n const dataDirLiteral = JSON.stringify(path.join(homedir(), \".napuketto\"));\n return `# ============================================================\n# NapukettoQQ 全局配置文件(由 create-napukettoqq 生成)\n# ============================================================\n# 本文件与项目 napuketto.toml.example 内容一致(参考模板)。\n# 数据(账号目录/日志/缓存/QQ 数据)不在此文件,按数据根组织:\n# 优先级:--data-dir <dir> > NAPKETTO_DATA 环境变量 > ~/.napuketto\n# 配置文件路径解析:NAPKETTO_CONFIG > 项目根探测 > cwd > 数据根兜底。\n#\n# 单账号启动每次读取本文件;修改后重启生效。\n\n# ------------------------------------------------------------\n# 基础配置\n# ------------------------------------------------------------\n\n# 数据根目录:账号目录/日志/缓存/QQ 数据存放位置(绝对路径)\ndataDir = ${dataDirLiteral}\n\n# 多账号(supervisor)模式下,账号进程崩溃是否自动重启\nautoRestart = true\n\n# 崩溃后自动重启的延迟(毫秒)\nrestartDelayMs = 2000\n\n# 多账号列表(留空 = 单账号模式:启动后自动快速登录/扫码)\n# [[accounts]]\n# qq = \"123456\" # QQ 号(必填)\n# enabled = true # 是否启用(可选,缺省 true)\n\n# ------------------------------------------------------------\n# OneBot 11 协议段\n# ------------------------------------------------------------\n[onebot11]\n\n# 鉴权 token(全局默认):HTTP/WS 连接必须携带。\n# 校验方式:Authorization: Bearer <token> / 裸 token / URL 参数 ?access_token=<token>\n# 每个网络实例可单独覆盖(见下文各实例的 token 字段)。\n# 留空 = 不鉴权(仅本机调试推荐)。\n# token = \"\"\n\n# 心跳 meta 事件间隔(毫秒);0 = 关闭心跳事件\nheartbeatInterval = 3000\n\n# 是否上报机器人自己发的消息(OB11 规范默认不上报)\nreportSelfMessage = false\n\n# 消息内容格式:array = 消息段数组(标准 OneBot 11),string = CQ 码字符串\nmessagePostFormat = \"array\"\n\n# ---- HTTP 反向服务器(接收第三方 POST 指令),可配置多个 ----\n[[onebot11.httpServers]]\nenabled = true\nhost = \"127.0.0.1\" # 监听地址;0.0.0.0 = 监听所有网卡(局域网可连)\nport = 3000\n# token = \"abc\" # 可选:覆盖全局 token\n\n# ---- HTTP 正向上报(Webhook:把事件 POST 到第三方地址),可配置多个 ----\n# [[onebot11.httpPostUrls]]\n# enabled = true\n# url = \"http://127.0.0.1:8080/onebot\" # 第三方接收地址\n# token = \"abc\" # 可选:覆盖全局 token\n# timeoutMs = 5000 # 可选:上报超时(毫秒),缺省不超时\n\n# ---- WS 反向服务器(外部 WS 客户端主动连入),可配置多个 ----\n[[onebot11.wsServers]]\nenabled = true\nhost = \"127.0.0.1\"\nport = 3001\n# token = \"abc\" # 可选:覆盖全局 token\n# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)\n\n# ---- WS 正向客户端(主动连接外部 WS,双向),可配置多个 ----\n# [[onebot11.wsReverseUrls]]\n# enabled = true\n# url = \"ws://127.0.0.1:8081/ws\" # 第三方 WS 地址(ws:// 或 wss://)\n# token = \"abc\" # 可选:覆盖全局 token\n# reconnectDelayMs = 5000 # 可选:断线重连延迟(毫秒)\n# maxReconnectAttempts = 10 # 可选:最大重连次数(缺省无限)\n# rejectUnauthorized = true # 可选:wss:// 是否校验证书(自签证书设 false)\n# heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)\n`;\n}\n\n/** 生成 readme.md(用户项目使用说明)。 */\nfunction readmeTemplate(dirName: string): string {\n return `# ${dirName}\n\n基于 **NapukettoQQ** 的 QQ 机器人项目(OneBot 11 协议),由 \\`create-napukettoqq\\` 生成。\n\n## 快速开始\n\n\\`\\`\\`bash\npnpm install\npnpm start -q 123456 # 换成你的 QQ 号\n\\`\\`\\`\n\n启动后按终端提示快速登录或扫码;登录成功后 OneBot 11 接口生效\n(默认 HTTP \\`127.0.0.1:3000\\` + WS \\`127.0.0.1:3001\\`,见 \\`napuketto.toml\\`)。\n\n## 前置要求\n\n- 已安装 **QQ NT**(Windows,机器人所需的 \\`wrapper.node\\` 来自 QQ 安装目录,\n 未自动探测到时用 \\`pnpm start --qq-path <QQ安装目录>\\` 指定)\n- Node.js 20+ 与 pnpm\n\n## 常用命令\n\n| 命令 | 说明 |\n|---|---|\n| \\`pnpm start -q <QQ号>\\` | 单账号启动(走自建宿主) |\n| \\`pnpm napuketto config init\\` | 重新生成默认配置 |\n| \\`pnpm napuketto supervisor\\` | 多账号模式(按 \\`napuketto.toml\\` 的 \\`accounts\\`) |\n\n## 目录\n\n- \\`napuketto.toml\\` — 全局配置(QQ 号、OneBot 端口、token 等,修改后重启生效)\n- QQ 数据(账号目录/日志/缓存)默认在 \\`~/.napuketto/\\`\n\n## 升级\n\n\\`\\`\\`bash\npnpm update @napuketto/cli\n\\`\\`\\`\n`;\n}\n\n/** 生成 .gitignore(用户项目)。 */\nconst GITIGNORE = `node_modules/\ndist/\n*.log\nnapuketto-data/\n`;\n\n/**\n * 生成 NapukettoQQ 项目骨架到当前目录下的 <dirName>/。\n * 目标目录已存在且非空 → 抛错(提示换名字或先清空)。\n */\nexport async function scaffoldProject(dirName: string): Promise<ScaffoldResult> {\n const name = validateProjectName(dirName);\n const packageName = derivePackageName(name);\n const dir = path.resolve(process.cwd(), name);\n\n // 目录已存在且非空 → 拒绝(避免覆盖用户文件)\n let existing: string[] = [];\n try {\n existing = await readdir(dir);\n } catch {\n // 目录不存在,可直接创建\n }\n if (existing.length > 0) {\n throw new Error(`目录已存在且非空:${dir}\\n请换一个文件夹名,或先清空该目录。`);\n }\n\n await mkdir(dir, { recursive: true });\n await writeFile(path.join(dir, \"package.json\"), packageJsonTemplate(packageName), \"utf8\");\n await writeFile(path.join(dir, \"napuketto.toml\"), tomlTemplate(), \"utf8\");\n await writeFile(path.join(dir, \"readme.md\"), readmeTemplate(name), \"utf8\");\n await writeFile(path.join(dir, \".gitignore\"), GITIGNORE, \"utf8\");\n\n return { dir, packageName };\n}\n","#!/usr/bin/env node\n\n/**\n * create-napukettoqq 入口(2026-08-07)。\n *\n * 用法:\n * create-napukettoqq # 交互问部署文件夹名(默认 NapukettoQQ)\n * create-napukettoqq my-bot # 位置参数指定文件夹名,跳过交互\n *\n * 在当前目录下生成 NapukettoQQ 机器人项目骨架(package.json / napuketto.toml /\n * readme.md / .gitignore),随后打印下一步指引(pnpm install && pnpm start)。\n */\n\nimport process from \"node:process\";\nimport { createInterface } from \"node:readline/promises\";\nimport { DEFAULT_PROJECT_NAME, scaffoldProject } from \"./scaffold.js\";\n\n/** 交互询问部署文件夹名(回车取默认值)。 */\nasync function askProjectName(): Promise<string> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n try {\n const answer = await rl.question(`? 部署文件夹名(默认 ${DEFAULT_PROJECT_NAME}): `);\n return answer.trim() || DEFAULT_PROJECT_NAME;\n } finally {\n rl.close();\n }\n}\n\n/** 打印生成结果与下一步指引(功能输出,不经日志框架)。 */\nfunction printNextSteps(dir: string): void {\n console.log(\"\\n========================================\");\n console.log(\" NapukettoQQ 项目已生成\");\n console.log(` 位置:${dir}`);\n console.log(\"========================================\");\n console.log(\"下一步:\");\n console.log(` cd ${dir}`);\n console.log(\" pnpm install\");\n console.log(\" pnpm start -q <你的QQ号>\");\n console.log(\"\");\n console.log(\"前置要求:本机已安装 QQ NT(wrapper.node 来自 QQ 安装目录)。\");\n console.log(\"详细说明见项目内 readme.md 与 napuketto.toml。\");\n}\n\nasync function main(): Promise<void> {\n const dirArg = process.argv[2];\n const dirName = dirArg ?? (await askProjectName());\n const result = await scaffoldProject(dirName);\n printNextSteps(result.dir);\n}\n\nmain().catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`创建失败:${message}`);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/scaffold.ts","../src/index.ts"],"sourcesContent":["/**\n * create-napukettoqq 脚手架核心:模板渲染 + 目标目录创建 + 文件写入。\n *\n * 模板为独立文件(templates/ 目录,运行时读取),不硬编码在源码里;\n * 需要插值的占位符用 {{key}} 标记(见 renderTemplate)。\n * 依赖极轻(node:fs/promises / node:os / node:path / node:process)。\n * 生成的用户项目依赖发布版的 @napuketto/cli,用户 install 后经 cli → loader\n * 自建宿主启动。\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\nimport process from \"node:process\";\n\n/** 模板目录(相对本文件:src 与 dist 都在包根下,../templates 均指向包根/templates)。 */\nconst TEMPLATES = new URL(\"../templates/\", import.meta.url);\n\n/**\n * 生成的用户项目所依赖的 @napuketto/cli 版本范围。\n * 运行时从脚手架自身 package.json 读取(dependencies[\"@napuketto/cli\"])——\n * 发版时 changesets 升版本 + pnpm publish 把 workspace:* 替换为实际版本,\n * 模板版本自动跟随,无需手动同步(比硬编码常量可靠)。\n */\nexport function cliVersionRange(): string {\n try {\n const ownPkg = JSON.parse(\n readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"),\n ) as { dependencies?: Record<string, string> };\n const range = ownPkg.dependencies?.[\"@napuketto/cli\"];\n // workspace:* 仅在本地 monorepo 直跑脚手架时出现(发布后 pnpm 替换为实际版本),\n // 此时兜底默认范围,避免生成的用户项目 install 失败\n if (range !== undefined && range !== \"workspace:*\") {\n return range;\n }\n } catch {\n // package.json 缺失(异常环境)→ 兜底默认范围\n }\n return \"^0.0.1\";\n}\n\n/** 默认部署文件夹名(用户交互回车缺省值)。 */\nexport const DEFAULT_PROJECT_NAME = \"NapukettoQQ\";\n\n/** 用户可用的包管理器(npm_config_user_agent 检测)。 */\nexport type PackageManager = \"pnpm\" | \"yarn\" | \"npm\";\n\n/**\n * 由 npm_config_user_agent 检测调用方包管理器(yarn create / npm create /\n * pnpm create 都会注入该变量)。兜底 pnpm(项目生态,或直接 node 运行)。\n */\nexport function detectPackageManager(): PackageManager {\n // noPropertyAccessFromIndexSignature:process.env 是索引签名,必须括号访问\n const ua = process.env[\"npm_config_user_agent\"] ?? \"\";\n if (ua.startsWith(\"yarn/\")) {\n return \"yarn\";\n }\n if (ua.startsWith(\"npm/\")) {\n return \"npm\";\n }\n return \"pnpm\";\n}\n\n/** Windows 下包管理器 bin 需带 .cmd 后缀(CreateProcess 不认无扩展名 shim)。 */\nexport function pmBin(pm: PackageManager): string {\n return process.platform === \"win32\" ? `${pm}.cmd` : pm;\n}\n\n/** Windows 路径非法字符(项目文件夹名校验用)。 */\nconst ILLEGAL_NAME = /[\\\\/:*?\"<>|]/;\n\n/** 脚手架生成结果。 */\nexport interface ScaffoldResult {\n /** 目标目录绝对路径。 */\n dir: string;\n /** 派生出的 npm 包名(小写、空格 → -)。 */\n packageName: string;\n}\n\n/** 校验文件夹名:非空、非 . / ..、不含 Windows 非法字符。 */\nexport function validateProjectName(raw: string): string {\n const name = raw.trim();\n if (name === \"\" || name === \".\" || name === \"..\") {\n throw new Error(\"文件夹名不能为空或为 . / ..\");\n }\n if (ILLEGAL_NAME.test(name)) {\n throw new Error(`文件夹名含非法字符:${name}(不允许 \\\\ / : * ? \" < > |)`);\n }\n return name;\n}\n\n/** 由文件夹名派生 npm 包名(小写、空格 → -)。 */\nexport function derivePackageName(dirName: string): string {\n return dirName.toLowerCase().replace(/\\s+/g, \"-\");\n}\n\n/** 解析目标目录绝对路径(校验名称后,基于 cwd)。 */\nexport function resolveTargetDir(dirName: string): string {\n return path.resolve(process.cwd(), validateProjectName(dirName));\n}\n\n/** 目标目录状态:不存在 / 空 / 非空。 */\nexport type DirStatus = \"missing\" | \"empty\" | \"nonempty\";\n\n/** 检测目标目录状态(供交互层决定是否询问清空)。 */\nexport async function checkDirStatus(dir: string): Promise<DirStatus> {\n let entries: string[] = [];\n try {\n entries = await readdir(dir);\n } catch {\n return \"missing\";\n }\n return entries.length === 0 ? \"empty\" : \"nonempty\";\n}\n\n/** 清空目录内容(保留目录本身)。 */\nexport async function emptyDir(dir: string): Promise<void> {\n for (const entry of await readdir(dir)) {\n await rm(path.join(dir, entry), { recursive: true, force: true });\n }\n}\n\n/** 读取模板文件(templates/ 目录,UTF-8)。 */\nfunction readTemplate(file: string): Promise<string> {\n return readFile(new URL(file, TEMPLATES), \"utf8\");\n}\n\n/**\n * 占位符渲染:把 {{key}} 替换为 vars[key]。\n * 模板中出现未提供的占位符 → 抛错(防漏插值写出半成品)。\n */\nfunction renderTemplate(content: string, vars: Record<string, string>): string {\n return content.replace(/\\{\\{(\\w+)\\}\\}/g, (match, key: string) => {\n const value = vars[key];\n if (value === undefined) {\n throw new Error(`模板占位符未提供:{{${key}}}(模板 ${match} 无法渲染)`);\n }\n return value;\n });\n}\n\n/**\n * 生成 NapukettoQQ 项目骨架到当前目录下的 <dirName>/。\n * 目标目录已存在且非空:overwrite=false → 抛错;overwrite=true → 清空后生成。\n * 模板与写入目标一一对应(同文件名)。\n */\nexport async function scaffoldProject(dirName: string, overwrite = false): Promise<ScaffoldResult> {\n const name = validateProjectName(dirName);\n const packageName = derivePackageName(name);\n const dir = resolveTargetDir(name);\n\n const status = await checkDirStatus(dir);\n if (status === \"nonempty\" && !overwrite) {\n throw new Error(`目录已存在且非空:${dir}\\n请换一个文件夹名,或先清空该目录。`);\n }\n if (status === \"nonempty\") {\n await emptyDir(dir);\n }\n\n // 渲染变量:dataDir 用 JSON.stringify 转义反斜杠 → TOML 基本字符串合法写法\n const vars: Record<string, string> = {\n packageName,\n cliVersion: cliVersionRange(),\n dataDir: JSON.stringify(path.join(homedir(), \".napuketto\")),\n };\n // 用户项目是「运行壳」:不开发代码,只需 package.json(依赖 + start)+ napuketto.toml(配置)\n // 模板文件为 templates/<name>.tmpl(.tmpl 后缀避免 TOML/JSON 语言服务把占位符当非法语法)\n const files = [\"package.json\", \"napuketto.toml\"];\n\n await mkdir(dir, { recursive: true });\n for (const file of files) {\n const rendered = renderTemplate(await readTemplate(`${file}.tmpl`), vars);\n await writeFile(path.join(dir, file), rendered, \"utf8\");\n }\n\n return { dir, packageName };\n}\n","#!/usr/bin/env node\n\n/**\n * create-napukettoqq 入口(2026-08-07 美化 v3,交互层用 @clack/prompts——\n * 现代脚手架交互标准,create-vue / create-turbo 同款)。\n *\n * 用法:\n * create-napukettoqq # intro + 交互问部署文件夹名(默认 NapukettoQQ)\n * create-napukettoqq my-bot # 位置参数指定文件夹名,跳过命名交互\n * create-napukettoqq my-bot -f # 目标目录非空时强制清空覆盖\n * create-napukettoqq -y # 全默认(命名用默认值、启动询问用默认 Y)\n * create-napukettoqq -h # 打印帮助\n *\n * 流程:intro → 问文件夹名 → 目标目录准备(非空需确认清空)→ spinner 生成骨架\n * (package.json / napuketto.toml / readme.md / .gitignore)→ 用调用方包管理器\n * 自动 install → 询问是否现在启动(默认 Y)→ 前台运行 → outro 收尾。\n */\n\nimport { spawn } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport process from \"node:process\";\nimport { cancel, confirm, intro, isCancel, log, outro, spinner, text } from \"@clack/prompts\";\nimport {\n checkDirStatus,\n DEFAULT_PROJECT_NAME,\n detectPackageManager,\n pmBin,\n resolveTargetDir,\n scaffoldProject,\n validateProjectName,\n} from \"./scaffold.js\";\n\n/** CLI 参数解析结果。 */\ninterface CliOptions {\n /** 位置参数:部署文件夹名(可选)。 */\n name?: string;\n /** -f/--forced:目标目录非空时强制清空覆盖。 */\n forced: boolean;\n /** -y/--yes:跳过所有交互提示(使用默认值)。 */\n yes: boolean;\n /** -h/--help:显示帮助。 */\n help: boolean;\n}\n\n/** 帮助文本(-h/--help)。 */\nconst HELP = ` 用法: create-napukettoqq [name] [选项]\n\n 选项:\n -f, --forced 目标目录非空时强制清空并覆盖\n -y, --yes 跳过所有交互提示(使用默认值)\n -h, --help 显示本帮助\n`;\n\n/** 解析命令行参数(flag 仅三个,手写保持自包含,不引 yargs-parser)。 */\nfunction parseArgs(argv: string[]): CliOptions {\n const opts: CliOptions = { forced: false, yes: false, help: false };\n for (const arg of argv) {\n if (arg === \"-f\" || arg === \"--forced\") {\n opts.forced = true;\n } else if (arg === \"-y\" || arg === \"--yes\") {\n opts.yes = true;\n } else if (arg === \"-h\" || arg === \"--help\") {\n opts.help = true;\n } else if (arg.startsWith(\"-\")) {\n throw new Error(`未知参数:${arg}`);\n } else if (opts.name === undefined) {\n opts.name = arg;\n } else {\n throw new Error(`多余的位置参数:${arg}`);\n }\n }\n return opts;\n}\n\n/** 读取脚手架自身版本号(intro 显示;dist 场景 import.meta.url 指向包内)。 */\nfunction readVersion(): string {\n try {\n const ownPkg = JSON.parse(\n readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"),\n ) as { version?: string };\n return ownPkg.version ?? \"0.0.0\";\n } catch {\n // package.json 缺失(异常环境)→ 兜底\n return \"0.0.0\";\n }\n}\n\n/** 交互询问部署文件夹名(回车取默认值;-y 直接返回默认值)。 */\nasync function askProjectName(yes: boolean): Promise<string> {\n if (yes) {\n return DEFAULT_PROJECT_NAME;\n }\n const name = await text({\n message: \"部署文件夹名\",\n placeholder: DEFAULT_PROJECT_NAME,\n initialValue: DEFAULT_PROJECT_NAME,\n validate: (input: string | undefined) => {\n try {\n validateProjectName(input ?? \"\");\n return undefined;\n } catch (err) {\n return err instanceof Error ? err.message : String(err);\n }\n },\n });\n if (isCancel(name)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return validateProjectName(name);\n}\n\n/** 询问是否移除现有文件并继续(目标目录非空时)。 */\nasync function confirmRemove(dirName: string): Promise<boolean> {\n const ok = await confirm({\n message: `目录 \"${dirName}\" 已存在且非空,移除现有文件并继续?`,\n initialValue: false,\n });\n if (isCancel(ok)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return ok;\n}\n\n/** 询问是否现在启动(默认 Y;-y 直接返回 true)。 */\nasync function askStartNow(yes: boolean): Promise<boolean> {\n if (yes) {\n return true;\n }\n const ok = await confirm({\n message: \"是否现在启动?\",\n initialValue: true,\n });\n if (isCancel(ok)) {\n cancel(\"操作已取消\");\n process.exit(0);\n }\n return ok;\n}\n\n/** 前台执行命令(stdio 继承,用户可见进度/交互),返回退出码。 */\nfunction runInteractive(bin: string, args: string[], cwd: string): Promise<number> {\n return new Promise((resolve) => {\n // Windows 下 .cmd shim 需 shell:true(CreateProcess 不能直接执行 .cmd)\n const child = spawn(bin, args, {\n cwd,\n stdio: \"inherit\",\n windowsHide: false,\n shell: process.platform === \"win32\",\n });\n child.on(\"exit\", (code) => {\n resolve(code ?? 0);\n });\n });\n}\n\nasync function main(): Promise<void> {\n const opts = parseArgs(process.argv.slice(2));\n if (opts.help) {\n console.log(HELP);\n return;\n }\n\n // intro:顶边框 + 名称 + 版本\n intro(`create-napukettoqq v${readVersion()}`);\n\n const dirName = opts.name ?? (await askProjectName(opts.yes));\n const targetDir = resolveTargetDir(dirName);\n\n // 目标目录准备:非空需确认清空(-f 跳过确认;-y 不隐式清空,保持默认安全语义)\n let overwrite = false;\n if ((await checkDirStatus(targetDir)) === \"nonempty\") {\n if (!opts.forced) {\n log.warn(`目标目录 \"${dirName}\" 已存在且非空。`);\n overwrite = await confirmRemove(dirName);\n if (!overwrite) {\n outro(\"已取消,未做任何修改。\");\n return;\n }\n } else {\n overwrite = true;\n }\n }\n\n // 生成骨架(spinner 进度)\n const s = spinner();\n s.start(`正在生成项目骨架 ${dirName} ...`);\n const result = await scaffoldProject(dirName, overwrite);\n s.stop(`项目骨架已生成:${dirName}`);\n log.success(`位置:${result.dir}`);\n\n // 自动安装依赖(用调用方包管理器)\n const pm = detectPackageManager();\n log.info(`包管理器:${pm}(自动检测)`);\n log.info(\"前置要求:本机已安装 QQ NT(wrapper.node 来自 QQ 安装目录)。\");\n log.info(`正在安装依赖(${pm} install)...`);\n const installCode = await runInteractive(pmBin(pm), [\"install\"], result.dir);\n if (installCode !== 0) {\n log.error(`依赖安装失败(退出码 ${installCode})。请手动执行:`);\n log.step(`cd ${result.dir}`);\n log.step(`${pm} install`);\n outro(\"创建失败,请手动安装后重试。\");\n process.exitCode = 1;\n return;\n }\n\n // 询问是否现在启动(默认 Y → 前台运行)\n if (await askStartNow(opts.yes)) {\n log.info(\"正在启动 NapukettoQQ(Ctrl+C 停止)...\\n\");\n const startCode = await runInteractive(pmBin(pm), [\"start\"], result.dir);\n if (startCode !== 0) {\n process.exitCode = startCode;\n }\n } else {\n log.message(\"你可以稍后这样启动:\");\n if (targetDir !== process.cwd()) {\n const related = relative(process.cwd(), targetDir);\n log.step(`cd ${related}`);\n }\n log.step(`${pm} start -q <你的QQ号>`);\n log.message(\"配置说明见项目内 napuketto.toml。\");\n }\n\n outro(\"完成。\");\n}\n\nmain().catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n log.error(`创建失败:${message}`);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "name": "create-napukettoqq",
3
- "version": "0.0.1",
3
+ "version": "0.2.0",
4
4
  "description": "一键生成 NapukettoQQ 机器人项目(yarn/pnpm/npm create napukettoqq)",
5
5
  "type": "module",
6
6
  "files": [
7
- "dist"
7
+ "dist",
8
+ "templates"
8
9
  ],
9
10
  "main": "./dist/index.mjs",
10
11
  "bin": {
11
12
  "create-napukettoqq": "./dist/index.mjs"
12
13
  },
14
+ "dependencies": {
15
+ "@clack/prompts": "^1.7.0",
16
+ "@napuketto/cli": "0.0.2"
17
+ },
13
18
  "devDependencies": {
14
19
  "tsdown": "^0.22.14"
15
20
  },
@@ -0,0 +1,78 @@
1
+ # ============================================================
2
+ # NapukettoQQ 全局配置文件(由 create-napukettoqq 生成)
3
+ # ============================================================
4
+ # 数据(账号目录/日志/缓存/QQ 数据)不在此文件,按数据根组织:
5
+ # 优先级:--data-dir <dir> > NAPKETTO_DATA 环境变量 > ~/.napuketto
6
+ # 配置文件路径解析:NAPKETTO_CONFIG > 项目根探测 > cwd > 数据根兜底。
7
+ #
8
+ # 单账号启动每次读取本文件;修改后重启生效。
9
+
10
+ # ------------------------------------------------------------
11
+ # 基础配置
12
+ # ------------------------------------------------------------
13
+
14
+ dataDir = {{dataDir}}
15
+
16
+ # 数据根目录:账号目录/日志/缓存/QQ 数据存放位置(绝对路径)
17
+ # 多账号(supervisor)模式下,账号进程崩溃是否自动重启
18
+ autoRestart = true
19
+
20
+ # 崩溃后自动重启的延迟(毫秒)
21
+ restartDelayMs = 2000
22
+
23
+ # 多账号列表(留空 = 单账号模式:启动后自动快速登录/扫码)
24
+ # [[accounts]]
25
+ # qq = "123456" # QQ 号(必填)
26
+ # enabled = true # 是否启用(可选,缺省 true)
27
+
28
+ # ------------------------------------------------------------
29
+ # OneBot 11 协议段
30
+ # ------------------------------------------------------------
31
+ [onebot11]
32
+
33
+ # 鉴权 token(全局默认):HTTP/WS 连接必须携带。
34
+ # 校验方式:Authorization: Bearer <token> / 裸 token / URL 参数 ?access_token=<token>
35
+ # 每个网络实例可单独覆盖(见下文各实例的 token 字段)。
36
+ # 留空 = 不鉴权(仅本机调试推荐)。
37
+ # token = ""
38
+
39
+ # 心跳 meta 事件间隔(毫秒);0 = 关闭心跳事件
40
+ heartbeatInterval = 3000
41
+
42
+ # 是否上报机器人自己发的消息(OB11 规范默认不上报)
43
+ reportSelfMessage = false
44
+
45
+ # 消息内容格式:array = 消息段数组(标准 OneBot 11),string = CQ 码字符串
46
+ messagePostFormat = "array"
47
+
48
+ # ---- HTTP 反向服务器(接收第三方 POST 指令),可配置多个 ----
49
+ [[onebot11.httpServers]]
50
+ enabled = true
51
+ host = "127.0.0.1" # 监听地址;0.0.0.0 = 监听所有网卡(局域网可连)
52
+ port = 3000
53
+ # token = "abc" # 可选:覆盖全局 token
54
+
55
+ # ---- HTTP 正向上报(Webhook:把事件 POST 到第三方地址),可配置多个 ----
56
+ # [[onebot11.httpPostUrls]]
57
+ # enabled = true
58
+ # url = "http://127.0.0.1:8080/onebot" # 第三方接收地址
59
+ # token = "abc" # 可选:覆盖全局 token
60
+ # timeoutMs = 5000 # 可选:上报超时(毫秒),缺省不超时
61
+
62
+ # ---- WS 反向服务器(外部 WS 客户端主动连入),可配置多个 ----
63
+ [[onebot11.wsServers]]
64
+ enabled = true
65
+ host = "127.0.0.1"
66
+ port = 3001
67
+ # token = "abc" # 可选:覆盖全局 token
68
+ # heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)
69
+
70
+ # ---- WS 正向客户端(主动连接外部 WS,双向),可配置多个 ----
71
+ # [[onebot11.wsReverseUrls]]
72
+ # enabled = true
73
+ # url = "ws://127.0.0.1:8081/ws" # 第三方 WS 地址(ws:// 或 wss://)
74
+ # token = "abc" # 可选:覆盖全局 token
75
+ # reconnectDelayMs = 5000 # 可选:断线重连延迟(毫秒)
76
+ # maxReconnectAttempts = 10 # 可选:最大重连次数(缺省无限)
77
+ # rejectUnauthorized = true # 可选:wss:// 是否校验证书(自签证书设 false)
78
+ # heartbeatInterval = 30000 # 可选:WS ping 间隔(毫秒)
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "{{packageName}}",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "NapukettoQQ 机器人项目(OneBot 11)",
7
+ "scripts": {
8
+ "start": "napuketto"
9
+ },
10
+ "dependencies": {
11
+ "@napuketto/cli": "{{cliVersion}}"
12
+ }
13
+ }