cortico 0.1.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 (311) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/package.json +43 -0
  4. package/src/boot.ts +46 -0
  5. package/src/bot.ts +1342 -0
  6. package/src/config-file.ts +41 -0
  7. package/src/core/README.md +92 -0
  8. package/src/core/billing.ts +26 -0
  9. package/src/core/blobs.ts +106 -0
  10. package/src/core/bus.ts +311 -0
  11. package/src/core/config-schema.ts +201 -0
  12. package/src/core/config.ts +262 -0
  13. package/src/core/core.ts +699 -0
  14. package/src/core/cost.ts +244 -0
  15. package/src/core/event-store.ts +304 -0
  16. package/src/core/fork.ts +113 -0
  17. package/src/core/generation.ts +118 -0
  18. package/src/core/instance-lock.ts +215 -0
  19. package/src/core/ipc-logger.ts +105 -0
  20. package/src/core/language.ts +62 -0
  21. package/src/core/log-context.ts +38 -0
  22. package/src/core/loop.ts +1858 -0
  23. package/src/core/markers.ts +48 -0
  24. package/src/core/prefix.ts +121 -0
  25. package/src/core/run.ts +95 -0
  26. package/src/core/secrets.ts +18 -0
  27. package/src/core/session.ts +56 -0
  28. package/src/core/sessions.ts +261 -0
  29. package/src/core/state.ts +104 -0
  30. package/src/core/template.ts +72 -0
  31. package/src/core/timers.ts +129 -0
  32. package/src/core/tool-log.ts +135 -0
  33. package/src/core/transcript.ts +92 -0
  34. package/src/core/truncate.ts +121 -0
  35. package/src/core/types.ts +1140 -0
  36. package/src/core/usage-log.ts +79 -0
  37. package/src/core/util.ts +386 -0
  38. package/src/deploy-listing.ts +160 -0
  39. package/src/deploy.ts +162 -0
  40. package/src/extensions/README.md +72 -0
  41. package/src/extensions/dry-mount.ts +390 -0
  42. package/src/extensions/manifest.ts +153 -0
  43. package/src/extensions/runtime.ts +42 -0
  44. package/src/extensions.ts +519 -0
  45. package/src/launcher.ts +285 -0
  46. package/src/paths.ts +155 -0
  47. package/src/protocol/open-responses/LICENSE +201 -0
  48. package/src/protocol/open-responses/README.md +14 -0
  49. package/src/protocol/open-responses/context-helpers.ts +27 -0
  50. package/src/protocol/open-responses/context-log.ts +59 -0
  51. package/src/protocol/open-responses/context.ts +89 -0
  52. package/src/protocol/open-responses/generated.ts +115 -0
  53. package/src/protocol/open-responses/index.ts +33 -0
  54. package/src/protocol/open-responses/openapi.json +4230 -0
  55. package/src/protocol/open-responses/stream.ts +146 -0
  56. package/src/protocol/open-responses/tokens.ts +31 -0
  57. package/src/providers/README.md +86 -0
  58. package/src/providers/base.ts +111 -0
  59. package/src/providers/configuration.ts +97 -0
  60. package/src/providers/console/config.ts +21 -0
  61. package/src/providers/console/settings.ts +443 -0
  62. package/src/providers/console/strings.ts +74 -0
  63. package/src/providers/console/types.ts +11 -0
  64. package/src/providers/llamacpp/archive.ts +148 -0
  65. package/src/providers/llamacpp/catalog.ts +247 -0
  66. package/src/providers/llamacpp/config.ts +36 -0
  67. package/src/providers/llamacpp/console/client.ts +6 -0
  68. package/src/providers/llamacpp/console/models-panel.ts +120 -0
  69. package/src/providers/llamacpp/console/runtime-panel.ts +173 -0
  70. package/src/providers/llamacpp/console/server.ts +153 -0
  71. package/src/providers/llamacpp/index.ts +95 -0
  72. package/src/providers/llamacpp/native.ts +51 -0
  73. package/src/providers/llamacpp/options.ts +117 -0
  74. package/src/providers/llamacpp/runtime-store.ts +139 -0
  75. package/src/providers/llamacpp/runtime.ts +214 -0
  76. package/src/providers/llamacpp/server.ts +286 -0
  77. package/src/providers/llamacpp/strings.ts +230 -0
  78. package/src/providers/openai-responses-compat/config.ts +20 -0
  79. package/src/providers/openai-responses-compat/index.ts +89 -0
  80. package/src/providers/openai-responses-compat/native.ts +127 -0
  81. package/src/providers/openai-responses-compat/strings.ts +19 -0
  82. package/src/providers/pricebook.ts +75 -0
  83. package/src/providers/registry.ts +137 -0
  84. package/src/providers/strings.ts +71 -0
  85. package/src/providers/transport/chat.ts +156 -0
  86. package/src/providers/transport/errors.ts +48 -0
  87. package/src/providers/transport/history.ts +68 -0
  88. package/src/providers/transport/native-input.ts +83 -0
  89. package/src/providers/transport/native-types.ts +13 -0
  90. package/src/providers/transport/response-assembly.ts +191 -0
  91. package/src/providers/transport/response-http.ts +196 -0
  92. package/src/providers/transport/response-meters.ts +27 -0
  93. package/src/providers/transport/responses-input.ts +56 -0
  94. package/src/web/README.md +84 -0
  95. package/src/web/client/console-pages/builtins/llm-settings/panel.ts +625 -0
  96. package/src/web/client/console-pages/builtins/llm-settings/pricing-panel.ts +168 -0
  97. package/src/web/client/console-pages/builtins/llm-settings/strings.ts +213 -0
  98. package/src/web/client/console-pages/builtins.ts +10 -0
  99. package/src/web/client/console-pages/context.ts +135 -0
  100. package/src/web/client/console-pages/host.ts +633 -0
  101. package/src/web/client/console-pages/loader.ts +132 -0
  102. package/src/web/client/console-pages/strings.ts +90 -0
  103. package/src/web/client/console-pages/tools/strings.ts +49 -0
  104. package/src/web/client/console-pages/tools/view.ts +215 -0
  105. package/src/web/client/core/api.ts +200 -0
  106. package/src/web/client/core/language.ts +56 -0
  107. package/src/web/client/core/lifecycle.ts +132 -0
  108. package/src/web/client/core/router.ts +199 -0
  109. package/src/web/client/core/stream.ts +157 -0
  110. package/src/web/client/core/websocket.ts +82 -0
  111. package/src/web/client/features/appearance/index.ts +333 -0
  112. package/src/web/client/features/appearance/strings.ts +87 -0
  113. package/src/web/client/features/config/index.ts +11 -0
  114. package/src/web/client/features/config/strings.ts +41 -0
  115. package/src/web/client/features/config/view.ts +428 -0
  116. package/src/web/client/features/core/events.ts +197 -0
  117. package/src/web/client/features/core/index.ts +254 -0
  118. package/src/web/client/features/core/run.ts +56 -0
  119. package/src/web/client/features/core/runlog.ts +214 -0
  120. package/src/web/client/features/core/sessions.ts +53 -0
  121. package/src/web/client/features/core/strings.ts +157 -0
  122. package/src/web/client/features/extensions/index.ts +422 -0
  123. package/src/web/client/features/extensions/strings.ts +164 -0
  124. package/src/web/client/features/feature.ts +93 -0
  125. package/src/web/client/features/live/context.ts +280 -0
  126. package/src/web/client/features/live/fork.ts +156 -0
  127. package/src/web/client/features/live/index.ts +392 -0
  128. package/src/web/client/features/live/onboarding.ts +113 -0
  129. package/src/web/client/features/live/protocol.ts +122 -0
  130. package/src/web/client/features/live/sessions.ts +71 -0
  131. package/src/web/client/features/live/status.ts +68 -0
  132. package/src/web/client/features/live/strings.ts +252 -0
  133. package/src/web/client/features/live/timeline.ts +590 -0
  134. package/src/web/client/features/prompts/editor.ts +292 -0
  135. package/src/web/client/features/prompts/index.ts +346 -0
  136. package/src/web/client/features/prompts/strings.ts +136 -0
  137. package/src/web/client/features/prompts/view.ts +256 -0
  138. package/src/web/client/features/providers/index.ts +100 -0
  139. package/src/web/client/features/providers/strings.ts +23 -0
  140. package/src/web/client/features/settings/general.ts +28 -0
  141. package/src/web/client/features/settings/index.ts +82 -0
  142. package/src/web/client/features/settings/strings.ts +33 -0
  143. package/src/web/client/features/storage/index.ts +1 -0
  144. package/src/web/client/features/storage/strings.ts +45 -0
  145. package/src/web/client/features/storage/view.ts +154 -0
  146. package/src/web/client/features/usage/chart.ts +486 -0
  147. package/src/web/client/features/usage/index.ts +407 -0
  148. package/src/web/client/features/usage/labels.ts +46 -0
  149. package/src/web/client/features/usage/range.ts +36 -0
  150. package/src/web/client/features/usage/state.ts +37 -0
  151. package/src/web/client/features/usage/strings.ts +191 -0
  152. package/src/web/client/features/usage/tooltip.ts +86 -0
  153. package/src/web/client/features/usage/types.ts +96 -0
  154. package/src/web/client/features/worlds/index.ts +424 -0
  155. package/src/web/client/features/worlds/strings.ts +121 -0
  156. package/src/web/client/main.ts +273 -0
  157. package/src/web/client/shell/avatar.ts +233 -0
  158. package/src/web/client/shell/index.ts +549 -0
  159. package/src/web/client/shell/strings.ts +89 -0
  160. package/src/web/client/theme/handoff.ts +43 -0
  161. package/src/web/client/theme/palette.ts +109 -0
  162. package/src/web/client/theme/registry.ts +130 -0
  163. package/src/web/client/theme/storage.ts +72 -0
  164. package/src/web/client/theme/strings.ts +123 -0
  165. package/src/web/client/theme/studio.ts +366 -0
  166. package/src/web/client/ui/actions.ts +109 -0
  167. package/src/web/client/ui/data.ts +185 -0
  168. package/src/web/client/ui/dom.ts +23 -0
  169. package/src/web/client/ui/fields.ts +191 -0
  170. package/src/web/client/ui/format.ts +75 -0
  171. package/src/web/client/ui/icons.ts +183 -0
  172. package/src/web/client/ui/images.ts +91 -0
  173. package/src/web/client/ui/index.ts +89 -0
  174. package/src/web/client/ui/lamp.ts +125 -0
  175. package/src/web/client/ui/log.ts +98 -0
  176. package/src/web/client/ui/overlay.ts +184 -0
  177. package/src/web/client/ui/page.ts +8 -0
  178. package/src/web/client/ui/prompt-input.tsx +311 -0
  179. package/src/web/client/ui/sheet.ts +131 -0
  180. package/src/web/client/ui/strings.ts +71 -0
  181. package/src/web/console-pages.ts +367 -0
  182. package/src/web/files.ts +107 -0
  183. package/src/web/path-picker.ts +296 -0
  184. package/src/web/public/index.html +17 -0
  185. package/src/web/public/styles.css +1715 -0
  186. package/src/web/server.ts +1883 -0
  187. package/src/web/shared/client-panel.ts +603 -0
  188. package/src/web/shared/console-protocol.ts +560 -0
  189. package/src/web/shared/css.d.ts +1 -0
  190. package/src/web/shared/path-picker.ts +23 -0
  191. package/src/web/shared/theme.ts +256 -0
  192. package/src/web/theme-store.ts +37 -0
  193. package/src/world.ts +449 -0
  194. package/src/worlds/bilibili/ENV_PROMPT.md +5 -0
  195. package/src/worlds/bilibili/README.md +176 -0
  196. package/src/worlds/bilibili/audience-admission.ts +995 -0
  197. package/src/worlds/bilibili/client.ts +528 -0
  198. package/src/worlds/bilibili/coalescing-buffer.ts +125 -0
  199. package/src/worlds/bilibili/config.ts +185 -0
  200. package/src/worlds/bilibili/console/client.ts +202 -0
  201. package/src/worlds/bilibili/definition.ts +35 -0
  202. package/src/worlds/bilibili/gift-frame.ts +131 -0
  203. package/src/worlds/bilibili/normalize.ts +452 -0
  204. package/src/worlds/bilibili/overlay/announcement.ts +76 -0
  205. package/src/worlds/bilibili/overlay/assets.ts +82 -0
  206. package/src/worlds/bilibili/overlay/model.ts +494 -0
  207. package/src/worlds/bilibili/overlay/project.ts +202 -0
  208. package/src/worlds/bilibili/overlay/server.ts +286 -0
  209. package/src/worlds/bilibili/overlay/types.ts +217 -0
  210. package/src/worlds/bilibili/overlay/web/app.js +690 -0
  211. package/src/worlds/bilibili/overlay/web/editor.css +297 -0
  212. package/src/worlds/bilibili/overlay/web/editor.html +102 -0
  213. package/src/worlds/bilibili/overlay/web/editor.js +2116 -0
  214. package/src/worlds/bilibili/overlay/web/overlay.html +13 -0
  215. package/src/worlds/bilibili/overlay/web/styles.css +258 -0
  216. package/src/worlds/bilibili/protobuf.ts +123 -0
  217. package/src/worlds/bilibili/wire.ts +94 -0
  218. package/src/worlds/bilibili/world.ts +1389 -0
  219. package/src/worlds/console-fixture/ENV_PROMPT.md +1 -0
  220. package/src/worlds/console-fixture/console/client.ts +72 -0
  221. package/src/worlds/console-fixture/world.ts +73 -0
  222. package/src/worlds/index.ts +15 -0
  223. package/src/worlds/minecraft/ENV_PROMPT.md +32 -0
  224. package/src/worlds/minecraft/ENV_PROMPT_CAMERA.md +2 -0
  225. package/src/worlds/minecraft/LICENSE-mineflayer-pathfinder.txt +21 -0
  226. package/src/worlds/minecraft/README.md +917 -0
  227. package/src/worlds/minecraft/blueprint-plan.ts +915 -0
  228. package/src/worlds/minecraft/blueprint-registry.ts +566 -0
  229. package/src/worlds/minecraft/blueprint-repair.ts +236 -0
  230. package/src/worlds/minecraft/blueprint-resource.ts +272 -0
  231. package/src/worlds/minecraft/blueprint.ts +492 -0
  232. package/src/worlds/minecraft/body-lease.ts +340 -0
  233. package/src/worlds/minecraft/bridge.ts +749 -0
  234. package/src/worlds/minecraft/check.ts +651 -0
  235. package/src/worlds/minecraft/chests.ts +292 -0
  236. package/src/worlds/minecraft/client-launch.ts +272 -0
  237. package/src/worlds/minecraft/client-options.ts +85 -0
  238. package/src/worlds/minecraft/client-skins.ts +164 -0
  239. package/src/worlds/minecraft/client-window.ps1 +91 -0
  240. package/src/worlds/minecraft/client.ts +423 -0
  241. package/src/worlds/minecraft/combat-context.ts +109 -0
  242. package/src/worlds/minecraft/combat.ts +1552 -0
  243. package/src/worlds/minecraft/config.ts +499 -0
  244. package/src/worlds/minecraft/console/access.ts +218 -0
  245. package/src/worlds/minecraft/console/client.ts +244 -0
  246. package/src/worlds/minecraft/console/log.ts +110 -0
  247. package/src/worlds/minecraft/console/mount.ts +420 -0
  248. package/src/worlds/minecraft/console/skin.ts +257 -0
  249. package/src/worlds/minecraft/console/style.css +154 -0
  250. package/src/worlds/minecraft/console/world.ts +449 -0
  251. package/src/worlds/minecraft/deaths.ts +135 -0
  252. package/src/worlds/minecraft/definition.ts +18 -0
  253. package/src/worlds/minecraft/engine-child.ts +274 -0
  254. package/src/worlds/minecraft/engine-ipc.ts +98 -0
  255. package/src/worlds/minecraft/entity-facts.ts +132 -0
  256. package/src/worlds/minecraft/escape.ts +407 -0
  257. package/src/worlds/minecraft/executor.ts +14494 -0
  258. package/src/worlds/minecraft/explored.ts +156 -0
  259. package/src/worlds/minecraft/geometry.ts +205 -0
  260. package/src/worlds/minecraft/goal-plan.ts +647 -0
  261. package/src/worlds/minecraft/item-break.ts +126 -0
  262. package/src/worlds/minecraft/item-facts.ts +146 -0
  263. package/src/worlds/minecraft/item-pick.ts +67 -0
  264. package/src/worlds/minecraft/level-dat.ts +176 -0
  265. package/src/worlds/minecraft/log.ts +132 -0
  266. package/src/worlds/minecraft/mineflayer-fixes.ts +1180 -0
  267. package/src/worlds/minecraft/names.ts +377 -0
  268. package/src/worlds/minecraft/pathfinder-lib.d.ts +13 -0
  269. package/src/worlds/minecraft/pathfinder-perf.ts +796 -0
  270. package/src/worlds/minecraft/piglin.ts +49 -0
  271. package/src/worlds/minecraft/policy.ts +547 -0
  272. package/src/worlds/minecraft/precheck.ts +655 -0
  273. package/src/worlds/minecraft/proxy.ts +445 -0
  274. package/src/worlds/minecraft/ranged.ts +635 -0
  275. package/src/worlds/minecraft/readouts.ts +102 -0
  276. package/src/worlds/minecraft/round.ts +26 -0
  277. package/src/worlds/minecraft/search-observation.ts +92 -0
  278. package/src/worlds/minecraft/server-config.ts +467 -0
  279. package/src/worlds/minecraft/server.ts +658 -0
  280. package/src/worlds/minecraft/show.ts +84 -0
  281. package/src/worlds/minecraft/skills.ts +1763 -0
  282. package/src/worlds/minecraft/terrain.ts +1735 -0
  283. package/src/worlds/minecraft/window.ts +96 -0
  284. package/src/worlds/minecraft/works.ts +179 -0
  285. package/src/worlds/minecraft/world.ts +6438 -0
  286. package/src/worlds/qq/ENV_PROMPT.md +5 -0
  287. package/src/worlds/qq/config.ts +108 -0
  288. package/src/worlds/qq/console/client.ts +126 -0
  289. package/src/worlds/qq/console/events.ts +155 -0
  290. package/src/worlds/qq/console/gate.ts +232 -0
  291. package/src/worlds/qq/console/roster.ts +203 -0
  292. package/src/worlds/qq/console/style.css +65 -0
  293. package/src/worlds/qq/conversation.ts +28 -0
  294. package/src/worlds/qq/definition.ts +62 -0
  295. package/src/worlds/qq/driver.ts +375 -0
  296. package/src/worlds/qq/history-tools.ts +209 -0
  297. package/src/worlds/qq/normalize.ts +323 -0
  298. package/src/worlds/qq/qface-map.ts +282 -0
  299. package/src/worlds/qq/vision-prompt.ts +27 -0
  300. package/src/worlds/qq/vision.ts +642 -0
  301. package/src/worlds/qq/vlm.ts +142 -0
  302. package/src/worlds/qq/world.ts +1503 -0
  303. package/src/worlds/terminal/ENV_PROMPT.md +11 -0
  304. package/src/worlds/terminal/config.ts +9 -0
  305. package/src/worlds/terminal/definition.ts +14 -0
  306. package/src/worlds/terminal/world.ts +700 -0
  307. package/src/worlds/websearch/ENV_PROMPT.md +1 -0
  308. package/src/worlds/websearch/brave-client.ts +261 -0
  309. package/src/worlds/websearch/config.ts +75 -0
  310. package/src/worlds/websearch/definition.ts +20 -0
  311. package/src/worlds/websearch/world.ts +171 -0
@@ -0,0 +1,296 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { stat } from 'node:fs/promises';
3
+ import { dirname, isAbsolute, resolve, sep } from 'node:path';
4
+ import { platform } from 'node:process';
5
+ import type { PathPicker, PathPickerOptions } from './shared/path-picker.ts';
6
+
7
+ interface ExecFailure extends Error {
8
+ code?: string | number;
9
+ stdout?: string;
10
+ stderr?: string;
11
+ }
12
+
13
+ export class PathPickerRequestError extends Error {
14
+ constructor(message: string) {
15
+ super(message);
16
+ this.name = 'PathPickerRequestError';
17
+ }
18
+ }
19
+
20
+ export class PathPickerUnavailableError extends Error {
21
+ constructor(message: string) {
22
+ super(message);
23
+ this.name = 'PathPickerUnavailableError';
24
+ }
25
+ }
26
+
27
+ function stringOption(value: unknown, name: string): string | undefined {
28
+ if (value == null || value === '') return undefined;
29
+ if (typeof value !== 'string') throw new PathPickerRequestError(`${name} 必须是字符串`);
30
+ if (value.includes('\0')) throw new PathPickerRequestError(`${name} 不能含 NUL 字符`);
31
+ return value;
32
+ }
33
+
34
+ function normalizeExtension(value: string): string {
35
+ const extension = value.trim().toLowerCase();
36
+ if (!/^\.?[a-z0-9][a-z0-9._+-]*$/i.test(extension)) {
37
+ throw new PathPickerRequestError(`不合法的文件后缀: ${value}`);
38
+ }
39
+ return extension.startsWith('.') ? extension : `.${extension}`;
40
+ }
41
+
42
+ /** 平台命令只接收经过校验的 JSON 边界值。 */
43
+ export function parsePathPickerOptions(raw: unknown): PathPickerOptions {
44
+ if (!raw || typeof raw !== 'object') throw new PathPickerRequestError('请求体必须是对象');
45
+ const body = raw as Record<string, unknown>;
46
+ if (body.kind !== 'file' && body.kind !== 'directory') {
47
+ throw new PathPickerRequestError('kind 必须是 file 或 directory');
48
+ }
49
+ if (body.extensions != null && !Array.isArray(body.extensions)) {
50
+ throw new PathPickerRequestError('extensions 必须是字符串数组');
51
+ }
52
+ const extensions = body.extensions === undefined
53
+ ? undefined
54
+ : [...new Set((body.extensions as unknown[]).map((item) => {
55
+ if (typeof item !== 'string') throw new PathPickerRequestError('extensions 必须是字符串数组');
56
+ return normalizeExtension(item);
57
+ }))];
58
+ const title = stringOption(body.title, 'title');
59
+ const currentPath = stringOption(body.currentPath, 'currentPath');
60
+ const recommendedDir = stringOption(body.recommendedDir, 'recommendedDir');
61
+ return {
62
+ kind: body.kind,
63
+ ...(title ? { title } : {}),
64
+ ...(currentPath ? { currentPath } : {}),
65
+ ...(recommendedDir ? { recommendedDir } : {}),
66
+ ...(extensions ? { extensions } : {}),
67
+ };
68
+ }
69
+
70
+ /** 浏览器只会收到存在、类型匹配的绝对路径。 */
71
+ export async function validatePickedPath(
72
+ selected: string | null,
73
+ options: PathPickerOptions,
74
+ ): Promise<string | null> {
75
+ if (selected == null || selected.trim() === '') return null;
76
+ const path = isAbsolute(selected) ? resolve(selected) : resolve(process.cwd(), selected);
77
+ let info;
78
+ try {
79
+ info = await stat(path);
80
+ } catch {
81
+ throw new PathPickerRequestError(`选择的路径不存在: ${path}`);
82
+ }
83
+ if (options.kind === 'file' && !info.isFile()) {
84
+ throw new PathPickerRequestError(`选择的路径不是文件: ${path}`);
85
+ }
86
+ if (options.kind === 'directory' && !info.isDirectory()) {
87
+ throw new PathPickerRequestError(`选择的路径不是目录: ${path}`);
88
+ }
89
+ if (options.kind === 'file' && options.extensions?.length) {
90
+ const lower = path.toLowerCase();
91
+ if (!options.extensions.some((extension) => lower.endsWith(extension.toLowerCase()))) {
92
+ throw new PathPickerRequestError(`文件类型应为 ${options.extensions.join(' / ')}`);
93
+ }
94
+ }
95
+ return path;
96
+ }
97
+
98
+ function run(command: string, args: string[], env?: NodeJS.ProcessEnv): Promise<string> {
99
+ return new Promise((resolveOutput, reject) => {
100
+ execFile(command, args, {
101
+ encoding: 'utf8',
102
+ env: env ? { ...process.env, ...env } : process.env,
103
+ maxBuffer: 64 * 1024,
104
+ windowsHide: true,
105
+ }, (error, stdout, stderr) => {
106
+ if (!error) {
107
+ resolveOutput(stdout);
108
+ return;
109
+ }
110
+ const failure = error as ExecFailure;
111
+ failure.stdout = stdout;
112
+ failure.stderr = stderr;
113
+ reject(failure);
114
+ });
115
+ });
116
+ }
117
+
118
+ function commandMissing(error: unknown): boolean {
119
+ return (error as ExecFailure | null)?.code === 'ENOENT';
120
+ }
121
+
122
+ function cancelled(error: unknown): boolean {
123
+ const failure = error as ExecFailure | null;
124
+ if (failure?.code === 1 && !failure.stderr?.trim()) return true;
125
+ return failure?.code === 1 && /user canceled|-128/i.test(failure.stderr ?? '');
126
+ }
127
+
128
+ async function initialDirectory(options: PathPickerOptions): Promise<string> {
129
+ const requested = options.currentPath?.trim() || options.recommendedDir?.trim();
130
+ if (!requested) return '';
131
+ let candidate = resolve(requested);
132
+ for (;;) {
133
+ try {
134
+ const info = await stat(candidate);
135
+ return info.isDirectory() ? candidate : dirname(candidate);
136
+ } catch {
137
+ const parent = dirname(candidate);
138
+ if (parent === candidate) return '';
139
+ candidate = parent;
140
+ }
141
+ }
142
+ }
143
+
144
+ function patterns(options: PathPickerOptions): string[] {
145
+ return (options.extensions ?? []).map((extension) => `*${extension}`);
146
+ }
147
+
148
+ const WINDOWS_SCRIPT = String.raw`
149
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
150
+ Add-Type -AssemblyName System.Windows.Forms
151
+ $title = $env:CORTICO_PICKER_TITLE
152
+ $initial = $env:CORTICO_PICKER_INITIAL
153
+ if ($env:CORTICO_PICKER_KIND -eq 'directory') {
154
+ $dialog = New-Object System.Windows.Forms.FolderBrowserDialog
155
+ $dialog.Description = $title
156
+ $dialog.ShowNewFolderButton = $true
157
+ if ($initial -and [System.IO.Directory]::Exists($initial)) { $dialog.SelectedPath = $initial }
158
+ } else {
159
+ $dialog = New-Object System.Windows.Forms.OpenFileDialog
160
+ $dialog.Title = $title
161
+ $dialog.CheckFileExists = $true
162
+ $dialog.Multiselect = $false
163
+ if ($initial -and [System.IO.Directory]::Exists($initial)) { $dialog.InitialDirectory = $initial }
164
+ $patterns = $env:CORTICO_PICKER_PATTERNS
165
+ if ($patterns) { $dialog.Filter = "支持的文件|$patterns|所有文件|*.*" }
166
+ }
167
+ if ($dialog.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
168
+ if ($env:CORTICO_PICKER_KIND -eq 'directory') {
169
+ [Console]::Out.Write($dialog.SelectedPath)
170
+ } else {
171
+ [Console]::Out.Write($dialog.FileName)
172
+ }
173
+ }
174
+ $dialog.Dispose()
175
+ `;
176
+
177
+ async function pickWindows(options: PathPickerOptions, initial: string): Promise<string | null> {
178
+ try {
179
+ const stdout = await run('powershell.exe', [
180
+ '-NoProfile', '-STA', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', WINDOWS_SCRIPT,
181
+ ], {
182
+ CORTICO_PICKER_KIND: options.kind,
183
+ CORTICO_PICKER_TITLE: options.title ?? (options.kind === 'file' ? '选择文件' : '选择目录'),
184
+ CORTICO_PICKER_INITIAL: initial,
185
+ CORTICO_PICKER_PATTERNS: patterns(options).join(';'),
186
+ });
187
+ return stdout.trim() || null;
188
+ } catch (error) {
189
+ throw new PathPickerUnavailableError(commandMissing(error)
190
+ ? '本机找不到 PowerShell,无法打开路径选择器'
191
+ : `无法打开本机路径选择器: ${String((error as Error).message ?? error)}`);
192
+ }
193
+ }
194
+
195
+ const MACOS_SCRIPT = String.raw`
196
+ on run argv
197
+ set pickerKind to item 1 of argv
198
+ set dialogTitle to item 2 of argv
199
+ set initialPath to item 3 of argv
200
+ set extensionText to item 4 of argv
201
+ set AppleScript's text item delimiters to ","
202
+ set fileTypes to text items of extensionText
203
+ set AppleScript's text item delimiters to ""
204
+ if pickerKind is "directory" then
205
+ if initialPath is "" then
206
+ set picked to choose folder with prompt dialogTitle
207
+ else
208
+ set picked to choose folder with prompt dialogTitle default location (POSIX file initialPath)
209
+ end if
210
+ else if extensionText is "" then
211
+ if initialPath is "" then
212
+ set picked to choose file with prompt dialogTitle
213
+ else
214
+ set picked to choose file with prompt dialogTitle default location (POSIX file initialPath)
215
+ end if
216
+ else
217
+ if initialPath is "" then
218
+ set picked to choose file with prompt dialogTitle of type fileTypes
219
+ else
220
+ set picked to choose file with prompt dialogTitle of type fileTypes default location (POSIX file initialPath)
221
+ end if
222
+ end if
223
+ return POSIX path of picked
224
+ end run
225
+ `;
226
+
227
+ async function pickMacos(options: PathPickerOptions, initial: string): Promise<string | null> {
228
+ try {
229
+ const extensions = (options.extensions ?? []).map((extension) => extension.slice(1)).join(',');
230
+ const stdout = await run('osascript', [
231
+ '-e', MACOS_SCRIPT, '--', options.kind,
232
+ options.title ?? (options.kind === 'file' ? '选择文件' : '选择目录'),
233
+ initial,
234
+ extensions,
235
+ ]);
236
+ return stdout.trim() || null;
237
+ } catch (error) {
238
+ if (cancelled(error)) return null;
239
+ throw new PathPickerUnavailableError(commandMissing(error)
240
+ ? '本机找不到 osascript,无法打开路径选择器'
241
+ : `无法打开本机路径选择器: ${String((error as Error).message ?? error)}`);
242
+ }
243
+ }
244
+
245
+ async function pickZenity(options: PathPickerOptions, initial: string): Promise<string | null> {
246
+ const args = ['--file-selection', `--title=${options.title ?? (options.kind === 'file' ? '选择文件' : '选择目录')}`];
247
+ if (options.kind === 'directory') args.push('--directory');
248
+ if (initial) args.push(`--filename=${initial}${sep}`);
249
+ const filter = patterns(options);
250
+ if (filter.length) args.push(`--file-filter=支持的文件 | ${filter.join(' ')}`);
251
+ const stdout = await run('zenity', args);
252
+ return stdout.trim() || null;
253
+ }
254
+
255
+ async function pickKdialog(options: PathPickerOptions, initial: string): Promise<string | null> {
256
+ const title = options.title ?? (options.kind === 'file' ? '选择文件' : '选择目录');
257
+ const filter = patterns(options);
258
+ const args = options.kind === 'directory'
259
+ ? ['--getexistingdirectory', initial || resolve('.'), '--title', title]
260
+ : [
261
+ '--getopenfilename', initial || resolve('.'),
262
+ filter.length ? `${filter.join(' ')}|支持的文件` : '*|所有文件',
263
+ '--title', title,
264
+ ];
265
+ const stdout = await run('kdialog', args);
266
+ return stdout.trim() || null;
267
+ }
268
+
269
+ async function pickLinux(options: PathPickerOptions, initial: string): Promise<string | null> {
270
+ let zenityError: unknown;
271
+ try {
272
+ return await pickZenity(options, initial);
273
+ } catch (error) {
274
+ if (cancelled(error)) return null;
275
+ zenityError = error;
276
+ }
277
+ try {
278
+ return await pickKdialog(options, initial);
279
+ } catch (error) {
280
+ if (cancelled(error)) return null;
281
+ const commandsMissing = commandMissing(zenityError) && commandMissing(error);
282
+ throw new PathPickerUnavailableError(commandsMissing
283
+ ? '本机没有可用的路径选择器;请安装 zenity 或 kdialog'
284
+ : `无法打开本机路径选择器: ${String((error as Error).message ?? error)}`);
285
+ }
286
+ }
287
+
288
+ export async function pickNativePath(options: PathPickerOptions): Promise<string | null> {
289
+ const initial = await initialDirectory(options);
290
+ if (platform === 'win32') return pickWindows(options, initial);
291
+ if (platform === 'darwin') return pickMacos(options, initial);
292
+ if (platform === 'linux') return pickLinux(options, initial);
293
+ throw new PathPickerUnavailableError(`当前平台不支持本机路径选择器: ${platform}`);
294
+ }
295
+
296
+ export const nativePathPicker: PathPicker = { pick: pickNativePath };
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>控制台 · Cortico</title>
7
+ <meta name="theme-color" content="#f4f5f4">
8
+ <link rel="stylesheet" href="/assets/styles.css">
9
+ </head>
10
+ <body>
11
+ <!-- 服务端按构建清单在 body 末尾注入带内容 hash 的内核脚本。 -->
12
+ <noscript>控制台需要 JavaScript。 The console needs JavaScript.</noscript>
13
+ <div id="boot-note">
14
+ 控制台正在载入…… Loading the console…
15
+ </div>
16
+ </body>
17
+ </html>