luckytri 1.0.0 → 1.0.2

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 (139) hide show
  1. package/.github/SECURITY.md +4 -4
  2. package/README.en.md +252 -250
  3. package/README.md +252 -250
  4. package/bin/luckytri.js +5 -1
  5. package/docs/en/CHANGELOG.md +8 -0
  6. package/docs/en/connect.md +18 -18
  7. package/docs/en/guide.md +202 -216
  8. package/docs/en/npm-release.md +25 -25
  9. package/docs/zh/CHANGELOG.md +8 -0
  10. package/docs/zh/connect.md +17 -17
  11. package/docs/zh/guide.md +202 -216
  12. package/docs/zh/npm-release.md +33 -33
  13. package/package.json +110 -108
  14. package/plugins/sdk/index.d.ts +155 -155
  15. package/plugins/sdk/testing.js +58 -58
  16. package/plugins/weather/index.js +82 -82
  17. package/plugins/weather/luckytri-plugin.json +29 -29
  18. package/public/app/assets/ChatsPage-BhlJHseG.js +3 -0
  19. package/public/app/assets/Empty-DP6WMGHi.js +2 -0
  20. package/public/app/assets/Field-CtbdqaiB.js +1 -0
  21. package/public/app/assets/HeartPage-32BeTPn2.js +1 -0
  22. package/public/app/assets/LifePage-CxSm0XKI.js +1 -0
  23. package/public/app/assets/MemoryPage-BeKBcPCY.js +1 -0
  24. package/public/app/assets/NaturePage-NEPYl2HS.js +3 -0
  25. package/public/app/assets/PeoplePage-DxtpekwM.js +1 -0
  26. package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
  27. package/public/app/assets/PluginsPage-DksEZbUU.js +2 -0
  28. package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-BkRu0Rvx.css} +1 -1
  29. package/public/app/assets/SystemPage-BvzJlHuY.js +1 -0
  30. package/public/app/assets/TimePage-7-awH5XL.js +3 -0
  31. package/public/app/assets/index-4rItIkD-.js +4 -0
  32. package/public/app/assets/{index-YDz1ree1.css → index-DY4e15Kh.css} +1 -1
  33. package/public/app/assets/{knowledge-DG0e0uKw.js → knowledge-C6_HvoyB.js} +1 -1
  34. package/public/app/assets/media-SMfTT-yc.js +1 -0
  35. package/public/app/index.html +3 -3
  36. package/public/guide.en.html +72 -99
  37. package/public/guide.html +44 -86
  38. package/public/tutorial.en.md +25 -39
  39. package/public/tutorial.md +25 -39
  40. package/scripts/backup.js +311 -311
  41. package/scripts/browser.js +41 -41
  42. package/scripts/import-data.js +128 -0
  43. package/scripts/launch.js +101 -94
  44. package/scripts/plugin.js +150 -150
  45. package/scripts/reset-password.js +22 -0
  46. package/scripts/runtime.js +25 -25
  47. package/scripts/service.js +56 -47
  48. package/scripts/setup.js +50 -50
  49. package/scripts/start-server.js +41 -41
  50. package/scripts/stop.js +36 -36
  51. package/server/app.js +112 -96
  52. package/server/auth.js +217 -0
  53. package/server/channels/contract.js +57 -57
  54. package/server/channels/hub.js +165 -165
  55. package/server/channels/index.js +28 -25
  56. package/server/channels/onebot/index.js +335 -336
  57. package/server/channels/onebot/token.js +22 -3
  58. package/server/core/capabilities.js +208 -208
  59. package/server/core/events.js +64 -60
  60. package/server/core/image-preprocessor.js +208 -208
  61. package/server/core/model-manager.js +1213 -1213
  62. package/server/core/net-guard.js +49 -49
  63. package/server/core/orchestrator.js +569 -569
  64. package/server/core/perceivers.js +91 -91
  65. package/server/core/persona-manager.js +243 -243
  66. package/server/core/repository.js +293 -293
  67. package/server/core/response-validator.js +508 -508
  68. package/server/core/turn-processor.js +630 -630
  69. package/server/core/turn.js +236 -236
  70. package/server/core/vision-manager.js +633 -633
  71. package/server/i18n/errors.js +548 -497
  72. package/server/i18n/labels.js +649 -653
  73. package/server/index.js +151 -102
  74. package/server/knowledge/manager.js +386 -386
  75. package/server/knowledge/schema.js +12 -8
  76. package/server/local-access.js +32 -0
  77. package/server/mind/anticipations.js +507 -507
  78. package/server/mind/api.js +703 -703
  79. package/server/mind/budget.js +152 -152
  80. package/server/mind/context-selection.js +61 -61
  81. package/server/mind/day-planner.js +324 -324
  82. package/server/mind/evidence.js +148 -148
  83. package/server/mind/index.js +445 -445
  84. package/server/mind/life/diary.js +786 -786
  85. package/server/mind/life/index.js +1049 -1049
  86. package/server/mind/observations.js +84 -84
  87. package/server/mind/own-day.js +324 -324
  88. package/server/mind/own-voice.js +318 -318
  89. package/server/mind/senses.js +53 -53
  90. package/server/mind/time/activity-clock.js +196 -196
  91. package/server/mind/time/availability.js +69 -69
  92. package/server/mind/time/commitment-review.js +198 -198
  93. package/server/mind/time/executor.js +512 -512
  94. package/server/mind/time/experiences.js +128 -128
  95. package/server/mind/time/games.js +562 -562
  96. package/server/mind/time/index.js +739 -739
  97. package/server/mind/time/intent.js +166 -166
  98. package/server/mind/time/kinds.js +171 -171
  99. package/server/mind/time/plugin-activity.js +207 -207
  100. package/server/mind/time/tasks.js +587 -587
  101. package/server/mind/util.js +110 -110
  102. package/server/mind/view.js +273 -273
  103. package/server/paths.js +102 -102
  104. package/server/plugins/api.js +204 -204
  105. package/server/plugins/host.js +967 -967
  106. package/server/plugins/install.js +295 -295
  107. package/server/plugins/manifest.js +201 -201
  108. package/server/plugins/permissions.js +42 -42
  109. package/server/plugins/runner/guard.mjs +36 -36
  110. package/server/plugins/runner/worker.mjs +250 -250
  111. package/server/plugins/schema.js +91 -91
  112. package/server/plugins/zip.js +112 -112
  113. package/server/startup-lock.js +118 -118
  114. package/server/storage/archive.js +242 -241
  115. package/server/storage/backup-cleanup-worker.js +8 -8
  116. package/server/storage/backup-cleanup.js +415 -414
  117. package/server/storage/backup-lock.js +76 -76
  118. package/server/storage/backup-scheduler.js +131 -131
  119. package/server/storage/data-package.js +298 -0
  120. package/server/storage/data-transfer-api.js +50 -0
  121. package/server/storage/data-transfer-worker.js +18 -0
  122. package/server/storage/data-transfer.js +415 -0
  123. package/server/storage/store.js +202 -199
  124. package/server/studio/readiness.js +181 -190
  125. package/server/studio/settings.js +236 -236
  126. package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
  127. package/public/app/assets/Empty-CpP5kQHH.js +0 -2
  128. package/public/app/assets/Field-D4gY-INU.js +0 -1
  129. package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
  130. package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
  131. package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
  132. package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
  133. package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
  134. package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
  135. package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
  136. package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
  137. package/public/app/assets/TimePage-Cw44inM4.js +0 -3
  138. package/public/app/assets/index-BheRW-1t.js +0 -4
  139. package/public/app/assets/media-BzPDBm7e.js +0 -1
package/scripts/plugin.js CHANGED
@@ -1,150 +1,150 @@
1
- import { createHash } from "node:crypto";
2
- import {
3
- mkdirSync,
4
- readdirSync,
5
- readFileSync,
6
- statSync,
7
- writeFileSync,
8
- } from "node:fs";
9
- import { join, relative } from "node:path";
10
- import { validateManifest } from "../server/plugins/manifest.js";
11
-
12
- const command = process.argv[2];
13
- const arg = process.argv[3];
14
-
15
- function walk(dir, out = []) {
16
- for (const name of readdirSync(dir)) {
17
- if (name === "node_modules") continue;
18
- const path = join(dir, name);
19
- if (statSync(path).isDirectory()) walk(path, out);
20
- else out.push(path);
21
- }
22
- return out;
23
- }
24
-
25
- function crc32(buffer) {
26
- let crc = ~0;
27
- for (const byte of buffer) {
28
- crc ^= byte;
29
- for (let bit = 0; bit < 8; bit++)
30
- crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
31
- }
32
- return ~crc >>> 0;
33
- }
34
-
35
- function pack(dir) {
36
- const files = walk(dir).map((path) => ({
37
- name: relative(dir, path).replace(/\\/g, "/"),
38
- bytes: readFileSync(path),
39
- }));
40
- const locals = [];
41
- const central = [];
42
- let offset = 0;
43
- for (const file of files) {
44
- const name = Buffer.from(file.name);
45
- const crc = crc32(file.bytes);
46
- const local = Buffer.alloc(30 + name.length);
47
- local.writeUInt32LE(0x04034b50, 0);
48
- local.writeUInt16LE(20, 4);
49
- local.writeUInt32LE(crc, 14);
50
- local.writeUInt32LE(file.bytes.length, 18);
51
- local.writeUInt32LE(file.bytes.length, 22);
52
- local.writeUInt16LE(name.length, 26);
53
- name.copy(local, 30);
54
- const centralHeader = Buffer.alloc(46 + name.length);
55
- centralHeader.writeUInt32LE(0x02014b50, 0);
56
- centralHeader.writeUInt16LE(20, 4);
57
- centralHeader.writeUInt16LE(20, 6);
58
- centralHeader.writeUInt32LE(crc, 16);
59
- centralHeader.writeUInt32LE(file.bytes.length, 20);
60
- centralHeader.writeUInt32LE(file.bytes.length, 24);
61
- centralHeader.writeUInt16LE(name.length, 28);
62
- centralHeader.writeUInt32LE(offset, 42);
63
- name.copy(centralHeader, 46);
64
- locals.push(local, file.bytes);
65
- central.push(centralHeader);
66
- offset += local.length + file.bytes.length;
67
- }
68
- const directory = Buffer.concat(central);
69
- const end = Buffer.alloc(22);
70
- end.writeUInt32LE(0x06054b50, 0);
71
- end.writeUInt16LE(files.length, 8);
72
- end.writeUInt16LE(files.length, 10);
73
- end.writeUInt32LE(directory.length, 12);
74
- end.writeUInt32LE(offset, 16);
75
- return Buffer.concat([...locals, directory, end]);
76
- }
77
-
78
- if (command === "new") {
79
- const id = arg || "example";
80
- const dir = join("plugins", id);
81
- mkdirSync(dir, { recursive: true });
82
- writeFileSync(
83
- join(dir, "luckytri-plugin.json"),
84
- JSON.stringify(
85
- {
86
- id,
87
- name: id,
88
- version: "0.1.0",
89
- pluginApi: "1.0",
90
- luckytri: ">=1.0.0 <2.0.0",
91
- description: "",
92
- entry: "index.js",
93
- permissions: [],
94
- contributes: {},
95
- },
96
- null,
97
- 2,
98
- ),
99
- );
100
- writeFileSync(
101
- join(dir, "index.js"),
102
- "export default { activate(ctx) { ctx.status.set({ text: '已接上', tone: 'ok' }); } };\n",
103
- );
104
- console.log(dir);
105
- } else if (command === "pack") {
106
- const dir = arg;
107
- const manifest = JSON.parse(
108
- readFileSync(join(dir, "luckytri-plugin.json"), "utf8"),
109
- );
110
- const bytes = pack(dir);
111
- const file = `${manifest.id}-${manifest.version}.zip`;
112
- writeFileSync(file, bytes);
113
- const sha256 = createHash("sha256").update(bytes).digest("hex");
114
- console.log(
115
- JSON.stringify(
116
- {
117
- id: manifest.id,
118
- version: manifest.version,
119
- pluginApi: manifest.pluginApi,
120
- luckytri: manifest.luckytri,
121
- sha256,
122
- permissions: manifest.permissions,
123
- network: manifest.network || [],
124
- file,
125
- },
126
- null,
127
- 2,
128
- ),
129
- );
130
- } else if (command === "registry-check") {
131
- const index = JSON.parse(readFileSync(arg || "index.json", "utf8"));
132
- if (index.format !== 1 || !Array.isArray(index.plugins))
133
- throw new Error("插件索引无效");
134
- for (const plugin of index.plugins)
135
- for (const version of plugin.versions || [])
136
- if (!version.sha256 || !version.url) throw new Error("插件索引无效");
137
- console.log(`ok ${index.plugins.length}`);
138
- } else if (command === "check") {
139
- const dir = resolveDir(arg);
140
- const manifest = validateManifest(
141
- JSON.parse(readFileSync(join(dir, "luckytri-plugin.json"), "utf8")),
142
- );
143
- console.log(`${manifest.id} ${manifest.version} 清单通过`);
144
- } else {
145
- console.log("用法: npm run plugin -- new|check|pack|registry-check");
146
- }
147
-
148
- function resolveDir(value) {
149
- return value || ".";
150
- }
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ mkdirSync,
4
+ readdirSync,
5
+ readFileSync,
6
+ statSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { join, relative } from "node:path";
10
+ import { validateManifest } from "../server/plugins/manifest.js";
11
+
12
+ const command = process.argv[2];
13
+ const arg = process.argv[3];
14
+
15
+ function walk(dir, out = []) {
16
+ for (const name of readdirSync(dir)) {
17
+ if (name === "node_modules") continue;
18
+ const path = join(dir, name);
19
+ if (statSync(path).isDirectory()) walk(path, out);
20
+ else out.push(path);
21
+ }
22
+ return out;
23
+ }
24
+
25
+ function crc32(buffer) {
26
+ let crc = ~0;
27
+ for (const byte of buffer) {
28
+ crc ^= byte;
29
+ for (let bit = 0; bit < 8; bit++)
30
+ crc = crc & 1 ? (crc >>> 1) ^ 0xedb88320 : crc >>> 1;
31
+ }
32
+ return ~crc >>> 0;
33
+ }
34
+
35
+ function pack(dir) {
36
+ const files = walk(dir).map((path) => ({
37
+ name: relative(dir, path).replace(/\\/g, "/"),
38
+ bytes: readFileSync(path),
39
+ }));
40
+ const locals = [];
41
+ const central = [];
42
+ let offset = 0;
43
+ for (const file of files) {
44
+ const name = Buffer.from(file.name);
45
+ const crc = crc32(file.bytes);
46
+ const local = Buffer.alloc(30 + name.length);
47
+ local.writeUInt32LE(0x04034b50, 0);
48
+ local.writeUInt16LE(20, 4);
49
+ local.writeUInt32LE(crc, 14);
50
+ local.writeUInt32LE(file.bytes.length, 18);
51
+ local.writeUInt32LE(file.bytes.length, 22);
52
+ local.writeUInt16LE(name.length, 26);
53
+ name.copy(local, 30);
54
+ const centralHeader = Buffer.alloc(46 + name.length);
55
+ centralHeader.writeUInt32LE(0x02014b50, 0);
56
+ centralHeader.writeUInt16LE(20, 4);
57
+ centralHeader.writeUInt16LE(20, 6);
58
+ centralHeader.writeUInt32LE(crc, 16);
59
+ centralHeader.writeUInt32LE(file.bytes.length, 20);
60
+ centralHeader.writeUInt32LE(file.bytes.length, 24);
61
+ centralHeader.writeUInt16LE(name.length, 28);
62
+ centralHeader.writeUInt32LE(offset, 42);
63
+ name.copy(centralHeader, 46);
64
+ locals.push(local, file.bytes);
65
+ central.push(centralHeader);
66
+ offset += local.length + file.bytes.length;
67
+ }
68
+ const directory = Buffer.concat(central);
69
+ const end = Buffer.alloc(22);
70
+ end.writeUInt32LE(0x06054b50, 0);
71
+ end.writeUInt16LE(files.length, 8);
72
+ end.writeUInt16LE(files.length, 10);
73
+ end.writeUInt32LE(directory.length, 12);
74
+ end.writeUInt32LE(offset, 16);
75
+ return Buffer.concat([...locals, directory, end]);
76
+ }
77
+
78
+ if (command === "new") {
79
+ const id = arg || "example";
80
+ const dir = join("plugins", id);
81
+ mkdirSync(dir, { recursive: true });
82
+ writeFileSync(
83
+ join(dir, "luckytri-plugin.json"),
84
+ JSON.stringify(
85
+ {
86
+ id,
87
+ name: id,
88
+ version: "0.1.0",
89
+ pluginApi: "1.0",
90
+ luckytri: ">=1.0.0 <2.0.0",
91
+ description: "",
92
+ entry: "index.js",
93
+ permissions: [],
94
+ contributes: {},
95
+ },
96
+ null,
97
+ 2,
98
+ ),
99
+ );
100
+ writeFileSync(
101
+ join(dir, "index.js"),
102
+ "export default { activate(ctx) { ctx.status.set({ text: '已接上', tone: 'ok' }); } };\n",
103
+ );
104
+ console.log(dir);
105
+ } else if (command === "pack") {
106
+ const dir = arg;
107
+ const manifest = JSON.parse(
108
+ readFileSync(join(dir, "luckytri-plugin.json"), "utf8"),
109
+ );
110
+ const bytes = pack(dir);
111
+ const file = `${manifest.id}-${manifest.version}.zip`;
112
+ writeFileSync(file, bytes);
113
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
114
+ console.log(
115
+ JSON.stringify(
116
+ {
117
+ id: manifest.id,
118
+ version: manifest.version,
119
+ pluginApi: manifest.pluginApi,
120
+ luckytri: manifest.luckytri,
121
+ sha256,
122
+ permissions: manifest.permissions,
123
+ network: manifest.network || [],
124
+ file,
125
+ },
126
+ null,
127
+ 2,
128
+ ),
129
+ );
130
+ } else if (command === "registry-check") {
131
+ const index = JSON.parse(readFileSync(arg || "index.json", "utf8"));
132
+ if (index.format !== 1 || !Array.isArray(index.plugins))
133
+ throw new Error("插件索引无效");
134
+ for (const plugin of index.plugins)
135
+ for (const version of plugin.versions || [])
136
+ if (!version.sha256 || !version.url) throw new Error("插件索引无效");
137
+ console.log(`ok ${index.plugins.length}`);
138
+ } else if (command === "check") {
139
+ const dir = resolveDir(arg);
140
+ const manifest = validateManifest(
141
+ JSON.parse(readFileSync(join(dir, "luckytri-plugin.json"), "utf8")),
142
+ );
143
+ console.log(`${manifest.id} ${manifest.version} 清单通过`);
144
+ } else {
145
+ console.log("用法: npm run plugin -- new|check|pack|registry-check");
146
+ }
147
+
148
+ function resolveDir(value) {
149
+ return value || ".";
150
+ }
@@ -0,0 +1,22 @@
1
+ import { prepareRuntime } from "./runtime.js";
2
+ import { running, serviceHeaders, serviceUrl } from "./service.js";
3
+ try {
4
+ prepareRuntime({ initialize: false });
5
+ const base = serviceUrl();
6
+ if (!(await running({ base })))
7
+ throw Error("请先运行 luckytri,再重置管理密码。");
8
+ const response = await fetch(base + "/api/auth/reset", {
9
+ method: "POST",
10
+ headers: serviceHeaders(),
11
+ body: "{}",
12
+ signal: AbortSignal.timeout(5000),
13
+ });
14
+ if (!response.ok)
15
+ throw Error("无法重置密码,请确认正在运行 LuckyTri 1.0.1 或更新版本。");
16
+ console.log(
17
+ "管理密码已重置,刷新管理台后重新设置。数据库、记忆和配置均保留。",
18
+ );
19
+ } catch (error) {
20
+ console.error(error.message);
21
+ process.exitCode = 1;
22
+ }
@@ -1,25 +1,25 @@
1
- import { mkdirSync } from "node:fs";
2
- import { runtimePaths } from "../server/paths.js";
3
- import { initializeEnvironment } from "./setup.js";
4
-
5
- export function prepareRuntime({ initialize = true } = {}) {
6
- let paths = runtimePaths();
7
- process.env.LUCKYTRI_HOME = paths.home;
8
- if (initialize) {
9
- mkdirSync(paths.home, { recursive: true, mode: 0o700 });
10
- if (initializeEnvironment(paths.home))
11
- console.log(
12
- `已创建本机配置 / Created local configuration: ${paths.config}\n首次访问请在本机编辑器查看 ADMIN_TOKEN;不要分享配置文件。`,
13
- );
14
- }
15
- try {
16
- process.loadEnvFile(paths.config);
17
- } catch (error) {
18
- if (error.code !== "ENOENT") throw error;
19
- }
20
- paths = runtimePaths();
21
- // Child processes and backup workers inherit the same absolute paths.
22
- process.env.DB_PATH = paths.database;
23
- process.env.BACKUP_DIR = paths.backups;
24
- return paths;
25
- }
1
+ import { mkdirSync } from "node:fs";
2
+ import { runtimePaths } from "../server/paths.js";
3
+ import { initializeEnvironment } from "./setup.js";
4
+
5
+ export function prepareRuntime({ initialize = true } = {}) {
6
+ let paths = runtimePaths();
7
+ process.env.LUCKYTRI_HOME = paths.home;
8
+ if (initialize) {
9
+ mkdirSync(paths.home, { recursive: true, mode: 0o700 });
10
+ if (initializeEnvironment(paths.home))
11
+ console.log(
12
+ `已创建本机配置 / Created local configuration: ${paths.config}\n首次打开管理台时设置一个密码即可。`,
13
+ );
14
+ }
15
+ try {
16
+ process.loadEnvFile(paths.config);
17
+ } catch (error) {
18
+ if (error.code !== "ENOENT") throw error;
19
+ }
20
+ paths = runtimePaths();
21
+ // Child processes and backup workers inherit the same absolute paths.
22
+ process.env.DB_PATH = paths.database;
23
+ process.env.BACKUP_DIR = paths.backups;
24
+ return paths;
25
+ }
@@ -1,47 +1,56 @@
1
- import { runtimePaths } from "../server/paths.js";
2
-
3
- export function serviceUrl(env = process.env) {
4
- const port = Number(env.PORT || 3210);
5
- if (!Number.isInteger(port) || port < 1 || port > 65535)
6
- throw Error("PORT 必须是 1–65535 / PORT must be between 1 and 65535");
7
- const raw = env.HOST || "127.0.0.1";
8
- const host = raw === "0.0.0.0" ? "127.0.0.1" : raw === "::" ? "::1" : raw;
9
- return new URL(`http://${host.includes(":") ? `[${host}]` : host}:${port}`)
10
- .origin;
11
- }
12
-
13
- export const serviceHeaders = () => ({
14
- "Content-Type": "application/json",
15
- Authorization: `Bearer ${process.env.ADMIN_TOKEN || ""}`,
16
- });
17
-
18
- export async function running({ base = serviceUrl(), lenient = false } = {}) {
19
- let response;
20
- try {
21
- response = await fetch(base + "/api/service/status", {
22
- headers: serviceHeaders(),
23
- signal: AbortSignal.timeout(1500),
24
- });
25
- } catch (error) {
26
- if (lenient || error.cause?.code === "ECONNREFUSED") return false;
27
- throw Error("无法确认端口状态,请检查服务或网络;未重复启动。");
28
- }
29
- if (!response.ok)
30
- throw Error("端口已被占用或管理令牌不匹配,请检查本机配置。");
31
- let info;
32
- try {
33
- info = await response.json();
34
- } catch {
35
- throw Error("此端口运行的不是 LuckyTri。");
36
- }
37
- const paths = runtimePaths();
38
- const normalize = (value) =>
39
- process.platform === "win32" ? value.toLowerCase() : value;
40
- if (
41
- !["luckytri", "luckybot", "lucky", "xiaoman"].includes(info.app) ||
42
- typeof (info.home ?? info.workspace) !== "string" ||
43
- normalize(info.home ?? info.workspace) !== normalize(paths.home)
44
- )
45
- throw Error("此端口已被其他实例占用,未操作任何进程。");
46
- return true;
47
- }
1
+ import { runtimePaths } from "../server/paths.js";
2
+ import { localServiceKey } from "../server/local-access.js";
3
+ let legacy = false;
4
+
5
+ export function serviceUrl(env = process.env) {
6
+ const port = Number(env.PORT || 3210);
7
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
8
+ throw Error("PORT 必须是 1–65535 / PORT must be between 1 and 65535");
9
+ const raw = env.HOST || "127.0.0.1";
10
+ const host = raw === "0.0.0.0" ? "127.0.0.1" : raw === "::" ? "::1" : raw;
11
+ return new URL(`http://${host.includes(":") ? `[${host}]` : host}:${port}`)
12
+ .origin;
13
+ }
14
+
15
+ export const serviceHeaders = () => ({
16
+ "Content-Type": "application/json",
17
+ Authorization: `Bearer ${legacy ? process.env.ADMIN_TOKEN : localServiceKey()}`,
18
+ });
19
+
20
+ export async function running({ base = serviceUrl(), lenient = false } = {}) {
21
+ let response;
22
+ try {
23
+ response = await fetch(base + "/api/service/status", {
24
+ headers: serviceHeaders(),
25
+ signal: AbortSignal.timeout(1500),
26
+ });
27
+ } catch (error) {
28
+ if (lenient || error.cause?.code === "ECONNREFUSED") {
29
+ legacy = false;
30
+ return false;
31
+ }
32
+ throw Error("无法确认端口状态,请检查服务或网络;未重复启动。");
33
+ }
34
+ if (response.status === 401 && !legacy && process.env.ADMIN_TOKEN) {
35
+ // Allow the new CLI to gracefully stop a still-running 1.0.0 instance.
36
+ legacy = true;
37
+ return running({ base, lenient });
38
+ }
39
+ if (!response.ok) throw Error("端口已被占用或不是当前实例,请检查本机配置。");
40
+ let info;
41
+ try {
42
+ info = await response.json();
43
+ } catch {
44
+ throw Error("此端口运行的不是 LuckyTri。");
45
+ }
46
+ const paths = runtimePaths();
47
+ const normalize = (value) =>
48
+ process.platform === "win32" ? value.toLowerCase() : value;
49
+ if (
50
+ !["luckytri", "luckybot", "lucky", "xiaoman"].includes(info.app) ||
51
+ typeof (info.home ?? info.workspace) !== "string" ||
52
+ normalize(info.home ?? info.workspace) !== normalize(paths.home)
53
+ )
54
+ throw Error("此端口已被其他实例占用,未操作任何进程。");
55
+ return info;
56
+ }
package/scripts/setup.js CHANGED
@@ -1,50 +1,50 @@
1
- import { randomBytes } from "node:crypto";
2
- import { mkdirSync, writeFileSync } from "node:fs";
3
- import { resolve } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
- import { runtimePaths } from "../server/paths.js";
6
-
7
- export function initializeEnvironment(directory = runtimePaths().home) {
8
- mkdirSync(directory, { recursive: true, mode: 0o700 });
9
- const file = resolve(directory, ".env");
10
- const token = () => randomBytes(32).toString("hex");
11
- const content = [
12
- "# LuckyTri 本机配置。修改后重启服务,不要公开此文件。",
13
- "# LuckyTri local configuration. Restart after editing and never share this file.",
14
- "HOST=127.0.0.1",
15
- "PORT=3210",
16
- `ADMIN_TOKEN=${token()}`,
17
- "",
18
- "# 连接 QQ 的方式二选一,也可以在管理界面「连接 QQ」里选择。",
19
- "# Pick one way to connect QQ; it can also be chosen in the studio under Connect QQ.",
20
- "# LUCKYTRI_CHANNEL=onebot # onebot | qqbot;设置后界面里不能再改 / locks the choice when set",
21
- "",
22
- "# OneBot 11",
23
- `ONEBOT_TOKEN=${token()}`,
24
- "",
25
- "# QQ 官方机器人 / QQ official bot",
26
- "# QQBOT_APP_ID=",
27
- "# QQBOT_APP_SECRET=",
28
- "",
29
- "LLM_API_KEY=",
30
- "",
31
- ].join("\n");
32
- try {
33
- writeFileSync(file, content, { flag: "wx", mode: 0o600 });
34
- return true;
35
- } catch (error) {
36
- if (error.code === "EEXIST") return false;
37
- throw error;
38
- }
39
- }
40
- if (
41
- process.argv[1] &&
42
- import.meta.url === pathToFileURL(resolve(process.argv[1])).href
43
- ) {
44
- const created = initializeEnvironment();
45
- console.log(
46
- created
47
- ? "已生成 .env 与两枚不同的随机令牌。请在本机编辑器中查看,修改后重启服务。\nCreated .env with two different random tokens. Open it in a local editor and restart after changes."
48
- : "已有 .env,已保留原文件,不覆盖任何配置。\nA .env already exists and was left untouched.",
49
- );
50
- }
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { runtimePaths } from "../server/paths.js";
5
+
6
+ export function initializeEnvironment(directory = runtimePaths().home) {
7
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
8
+ const file = resolve(directory, ".env");
9
+ const content = [
10
+ "# LuckyTri 本机配置。修改后重启服务,不要公开此文件。",
11
+ "# LuckyTri local configuration. Restart after editing and never share this file.",
12
+ "HOST=127.0.0.1",
13
+ "PORT=3210",
14
+ "# 首次打开管理台时设置一个管理密码。",
15
+ "# Set your management password on the first visit to the studio.",
16
+ "",
17
+ "# 连接 QQ 的方式二选一,也可以在管理界面「连接 QQ」里选择。",
18
+ "# Pick one way to connect QQ; it can also be chosen in the studio under Connect QQ.",
19
+ "# LUCKYTRI_CHANNEL=onebot # onebot | qqbot;设置后界面里不能再改 / locks the choice when set",
20
+ "",
21
+ "# OneBot 11",
22
+ "# 本机 OneBot 接入无需单独令牌;远程接入可使用管理密码。",
23
+ "# Local OneBot clients need no separate token; remote clients can use the management password.",
24
+ "",
25
+ "# QQ 官方机器人 / QQ official bot",
26
+ "# QQBOT_APP_ID=",
27
+ "# QQBOT_APP_SECRET=",
28
+ "",
29
+ "LLM_API_KEY=",
30
+ "",
31
+ ].join("\n");
32
+ try {
33
+ writeFileSync(file, content, { flag: "wx", mode: 0o600 });
34
+ return true;
35
+ } catch (error) {
36
+ if (error.code === "EEXIST") return false;
37
+ throw error;
38
+ }
39
+ }
40
+ if (
41
+ process.argv[1] &&
42
+ import.meta.url === pathToFileURL(resolve(process.argv[1])).href
43
+ ) {
44
+ const created = initializeEnvironment();
45
+ console.log(
46
+ created
47
+ ? "已生成本机配置,首次打开管理台时设置密码。\nCreated local configuration. Set a password on the first visit to the studio."
48
+ : "已有 .env,已保留原文件,不覆盖任何配置。\nA .env already exists and was left untouched.",
49
+ );
50
+ }