neko-harness 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neko.js CHANGED
@@ -229,8 +229,18 @@ function validate(config, source) {
229
229
  if (theme !== undefined && theme !== "light" && theme !== "dark" && theme !== "auto") {
230
230
  throw new Error(`neko config: ${source}: "theme" must be "light", "dark", or "auto".`);
231
231
  }
232
+ const skills = config.skills;
233
+ if (skills !== undefined) {
234
+ if (typeof skills !== "object" || skills === null) {
235
+ throw new Error(`neko config: ${source}: "skills" must be an object.`);
236
+ }
237
+ const { marketUrl } = skills;
238
+ if (marketUrl !== undefined && typeof marketUrl !== "string") {
239
+ throw new Error(`neko config: ${source}: "skills.marketUrl" must be a string.`);
240
+ }
241
+ }
232
242
  }
233
- var NEKO_CONFIG_BASENAME = "neko.config.json", ENV_REFERENCE;
243
+ var DEFAULT_SKILL_MARKET_URL = "https://skill.kirineko.tech", NEKO_CONFIG_BASENAME = "neko.config.json", ENV_REFERENCE;
234
244
  var init_config = __esm(() => {
235
245
  ENV_REFERENCE = /\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g;
236
246
  });
@@ -866,11 +876,6 @@ var init_projection = __esm(() => {
866
876
  init_workspace();
867
877
  });
868
878
 
869
- // packages/core/src/prompt/diagnostics.ts
870
- var ignorePromptDiagnostic = () => {}, warnPromptDiagnostic = ({ path, message }) => {
871
- console.warn(`[neko-prompt] ${JSON.stringify(path)}: ${message}`);
872
- }, isMissing = (error) => typeof error === "object" && error !== null && ("code" in error) && (error.code === "ENOENT" || error.code === "ENOTDIR");
873
-
874
879
  // packages/core/src/prompt/frontmatter.ts
875
880
  import { parse } from "yaml";
876
881
  var normalizeNewlines = (value) => value.replace(/\r\n/g, `
@@ -896,6 +901,11 @@ var normalizeNewlines = (value) => value.replace(/\r\n/g, `
896
901
  }, stripFrontmatter = (content) => extractFrontmatter(content).body;
897
902
  var init_frontmatter = () => {};
898
903
 
904
+ // packages/core/src/prompt/diagnostics.ts
905
+ var ignorePromptDiagnostic = () => {}, warnPromptDiagnostic = ({ path, message }) => {
906
+ console.warn(`[neko-prompt] ${JSON.stringify(path)}: ${message}`);
907
+ }, isMissing = (error) => typeof error === "object" && error !== null && ("code" in error) && (error.code === "ENOENT" || error.code === "ENOTDIR");
908
+
899
909
  // packages/core/src/prompt/skills.ts
900
910
  import { readdir, readFile as readFile2 } from "fs/promises";
901
911
  import { basename, dirname as dirname3, join as join3 } from "path";
@@ -1091,7 +1101,8 @@ var init_prompt = __esm(() => {
1091
1101
  import { readFile as readFile3 } from "fs/promises";
1092
1102
  import { homedir as homedir3 } from "os";
1093
1103
  import { join as join4 } from "path";
1094
- var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDir = (home) => join4(home, ".agents", "skills"), workspaceAgentsFile = (workspace) => join4(workspace, "AGENTS.md"), workspaceSkillsDir = (workspace) => join4(workspace, ".agents", "skills"), readResources = async (workspace, home, report, invocationName) => {
1104
+ import { fileURLToPath } from "url";
1105
+ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDir = (home) => join4(home, ".agents", "skills"), workspaceAgentsFile = (workspace) => join4(workspace, "AGENTS.md"), workspaceSkillsDir = (workspace) => join4(workspace, ".agents", "skills"), BUILTIN_SKILLS_DIR, readResources = async (workspace, home, builtinDir, report, invocationName) => {
1095
1106
  const contextFiles = [];
1096
1107
  for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
1097
1108
  try {
@@ -1112,11 +1123,13 @@ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDi
1112
1123
  if (skill.name === invocationName)
1113
1124
  candidates.set(skill.filePath, content);
1114
1125
  } : undefined;
1115
- const [user, project] = await Promise.all([
1116
- scanSkillsDir(userSkillsDir(home), report, capture),
1117
- scanSkillsDir(workspaceSkillsDir(workspace), report, capture)
1126
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report, capture)).map((skill) => ({ ...skill, level }));
1127
+ const [builtin, user, project] = await Promise.all([
1128
+ builtinDir === null ? Promise.resolve([]) : scan(builtinDir, "builtin"),
1129
+ scan(userSkillsDir(home), "user"),
1130
+ scan(workspaceSkillsDir(workspace), "project")
1118
1131
  ]);
1119
- const skills = mergeSkills([user, project]);
1132
+ const skills = mergeSkills([builtin, user, project]);
1120
1133
  const winner = invocationName === undefined ? undefined : skills.find((skill) => skill.name === invocationName);
1121
1134
  if (winner && candidates) {
1122
1135
  const content = candidates.get(winner.filePath);
@@ -1124,11 +1137,12 @@ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDi
1124
1137
  return { contextFiles, skills, invocation: { filePath: winner.filePath, content } };
1125
1138
  }
1126
1139
  return { contextFiles, skills };
1127
- }, createResourceLoader = (home = homedir3(), report = warnPromptDiagnostic) => {
1140
+ }, createResourceLoader = (home = homedir3(), report = warnPromptDiagnostic, options) => {
1141
+ const builtinDir = options?.builtinDir === undefined ? BUILTIN_SKILLS_DIR : options.builtinDir;
1128
1142
  const previous = new Map;
1129
1143
  return async (workspace, invocationName) => {
1130
1144
  const diagnostics = [];
1131
- const resources = await readResources(workspace, home, (d) => diagnostics.push(d), invocationName);
1145
+ const resources = await readResources(workspace, home, builtinDir, (d) => diagnostics.push(d), invocationName);
1132
1146
  const known = previous.get(workspace);
1133
1147
  const current = new Set;
1134
1148
  for (const diagnostic of diagnostics) {
@@ -1143,9 +1157,18 @@ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDi
1143
1157
  previous.delete(workspace);
1144
1158
  return resources;
1145
1159
  };
1160
+ }, scanSkillsByLevel = async (workspace, home = homedir3(), builtinDir = BUILTIN_SKILLS_DIR, report = warnPromptDiagnostic) => {
1161
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report)).map((skill) => ({ ...skill, level }));
1162
+ const [builtin, user, project] = await Promise.all([
1163
+ builtinDir === null ? [] : scan(builtinDir, "builtin"),
1164
+ scan(userSkillsDir(home), "user"),
1165
+ scan(workspaceSkillsDir(workspace), "project")
1166
+ ]);
1167
+ return [...project, ...user, ...builtin];
1146
1168
  };
1147
1169
  var init_resources = __esm(() => {
1148
1170
  init_skills();
1171
+ BUILTIN_SKILLS_DIR = fileURLToPath(new URL("../../skills/", import.meta.url));
1149
1172
  });
1150
1173
 
1151
1174
  // packages/core/src/prompt/snapshots.ts
@@ -2313,7 +2336,21 @@ async function createNekoRuntime(options = {}) {
2313
2336
  const session = await sessionFor(id);
2314
2337
  const agent = await session.conversation.agent(context);
2315
2338
  const { skills } = await loadResources(savedWorkspace(agent.cwd));
2316
- return skills.map((skill) => ({ name: skill.name, description: skill.description, location: skill.filePath }));
2339
+ return skills.map((skill) => ({
2340
+ name: skill.name,
2341
+ description: skill.description,
2342
+ location: skill.filePath,
2343
+ level: skill.level
2344
+ }));
2345
+ },
2346
+ async listSkillsByLevel(cwd) {
2347
+ const skills = await scanSkillsByLevel(normalizeWorkspace(cwd ?? defaultCwd), options.promptHome ?? homedir4());
2348
+ return skills.map((skill) => ({
2349
+ name: skill.name,
2350
+ description: skill.description,
2351
+ location: skill.filePath,
2352
+ level: skill.level
2353
+ }));
2317
2354
  },
2318
2355
  installExtension(extension) {
2319
2356
  registry.install(extension);
@@ -2367,6 +2404,27 @@ var init_runtime = __esm(() => {
2367
2404
  });
2368
2405
  });
2369
2406
 
2407
+ // packages/core/src/skill-invocation.ts
2408
+ var unescapeXml = (value) => value.replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&"), parseSkillInvocation = (text) => {
2409
+ const open = /^<skill name="([^"]*)" location="([^"]*)">/.exec(text);
2410
+ if (!open)
2411
+ return null;
2412
+ const close = text.lastIndexOf("</skill>");
2413
+ if (close < open[0].length)
2414
+ return null;
2415
+ const rest = text.slice(close + "</skill>".length);
2416
+ if (rest !== "" && !rest.startsWith(`
2417
+
2418
+ `))
2419
+ return null;
2420
+ return {
2421
+ name: unescapeXml(open[1]),
2422
+ location: unescapeXml(open[2]),
2423
+ body: text.slice(open[0].length, close),
2424
+ args: rest.slice(2)
2425
+ };
2426
+ };
2427
+
2370
2428
  // packages/core/src/index.ts
2371
2429
  var init_src = __esm(() => {
2372
2430
  init_compaction();
@@ -2375,6 +2433,7 @@ var init_src = __esm(() => {
2375
2433
  init_image_metadata();
2376
2434
  init_images();
2377
2435
  init_projection();
2436
+ init_frontmatter();
2378
2437
  init_runtime();
2379
2438
  init_session_metadata();
2380
2439
  init_workspace();
@@ -2770,7 +2829,8 @@ var init_labels = __esm(() => {
2770
2829
  { name: "model", description: "Choose a model" },
2771
2830
  { name: "think", description: "Set thinking level (/think high)" },
2772
2831
  { name: "fork", description: "Fork this session" },
2773
- { name: "archive", description: "Archive this session" }
2832
+ { name: "archive", description: "Archive this session" },
2833
+ { name: "skills", description: "Browse and install skills from the market" }
2774
2834
  ];
2775
2835
  labels = {
2776
2836
  ready: "Ready",
@@ -3174,17 +3234,62 @@ class PickerPanel {
3174
3234
  }
3175
3235
  close() {
3176
3236
  ++this.#generation;
3237
+ this.#details = [];
3177
3238
  this.#state = "closed";
3178
3239
  this.closed();
3179
3240
  this.changed();
3180
3241
  }
3242
+ showDetail(detail) {
3243
+ if (!this.open)
3244
+ return;
3245
+ this.#details.push(detail);
3246
+ this.changed();
3247
+ }
3248
+ popDetail() {
3249
+ if (this.#details.length === 0)
3250
+ return;
3251
+ this.#details.pop();
3252
+ this.changed();
3253
+ }
3254
+ get detailOpen() {
3255
+ return this.#details.length > 0;
3256
+ }
3257
+ get #detail() {
3258
+ return this.#details[this.#details.length - 1];
3259
+ }
3260
+ #details = [];
3181
3261
  #filtered() {
3182
3262
  const query = this.search.getValue().toLowerCase();
3183
- return this.#items.filter((item) => `${item.label} ${item.description ?? ""} ${item.searchText ?? ""}`.toLowerCase().includes(query));
3263
+ if (query !== "")
3264
+ return this.#items.filter((item) => !item.header && this.#matches(item, query));
3265
+ return this.#items;
3266
+ }
3267
+ #matches(item, query) {
3268
+ return `${item.label} ${item.description ?? ""} ${item.searchText ?? ""}`.toLowerCase().includes(query);
3269
+ }
3270
+ #firstSelectable(items) {
3271
+ return items.findIndex((item) => !item.header);
3272
+ }
3273
+ #move(delta) {
3274
+ const items = this.#filtered();
3275
+ let next = this.#index + delta;
3276
+ while (next >= 0 && next < items.length && items[next]?.header)
3277
+ next += delta;
3278
+ this.#index = Math.max(0, Math.min(Math.max(0, items.length - 1), next));
3279
+ if (items[this.#index]?.header)
3280
+ this.#index = this.#firstSelectable(items);
3184
3281
  }
3185
3282
  handleInput(data) {
3186
3283
  if (isKeyRelease3(data))
3187
3284
  return;
3285
+ if (this.#detail !== undefined) {
3286
+ if (matchesKey3(data, "escape") || matchesKey3(data, "ctrl+c")) {
3287
+ this.popDetail();
3288
+ } else if (this.#detail.handleKey(data)) {
3289
+ this.changed();
3290
+ }
3291
+ return;
3292
+ }
3188
3293
  if (matchesKey3(data, "escape") || matchesKey3(data, "ctrl+c")) {
3189
3294
  this.close();
3190
3295
  return;
@@ -3198,18 +3303,25 @@ class PickerPanel {
3198
3303
  return;
3199
3304
  const items = this.#filtered();
3200
3305
  if (matchesKey3(data, "up"))
3201
- this.#index = Math.max(0, this.#index - 1);
3306
+ this.#move(-1);
3202
3307
  else if (matchesKey3(data, "down"))
3203
- this.#index = Math.min(Math.max(0, items.length - 1), this.#index + 1);
3308
+ this.#move(1);
3204
3309
  else if (matchesKey3(data, "enter")) {
3205
3310
  const item = items[this.#index];
3206
- if (!item)
3311
+ if (!item || item.header)
3207
3312
  return;
3208
3313
  const generation = this.#generation;
3209
3314
  this.#state = "applying";
3210
- this.#pick(item.value).then(() => {
3211
- if (generation === this.#generation)
3212
- this.close();
3315
+ this.#pick(item.value).then((result) => {
3316
+ if (generation !== this.#generation)
3317
+ return;
3318
+ if (result !== undefined && typeof result === "object" && "lines" in result) {
3319
+ this.#state = "ready";
3320
+ this.showDetail(result);
3321
+ this.changed();
3322
+ return;
3323
+ }
3324
+ this.close();
3213
3325
  }, (error) => {
3214
3326
  if (generation !== this.#generation)
3215
3327
  return;
@@ -3219,7 +3331,7 @@ class PickerPanel {
3219
3331
  });
3220
3332
  } else {
3221
3333
  this.search.handleInput(data);
3222
- this.#index = 0;
3334
+ this.#index = this.#firstSelectable(this.#filtered());
3223
3335
  }
3224
3336
  this.changed();
3225
3337
  }
@@ -3227,6 +3339,15 @@ class PickerPanel {
3227
3339
  const height = Math.max(1, this.height);
3228
3340
  const w = Math.max(1, width - 2);
3229
3341
  const rows = [style.bold(this.#title)];
3342
+ if (this.#detail !== undefined) {
3343
+ const body = this.#detail.lines(w, Math.max(1, height - 2));
3344
+ rows.push(...body);
3345
+ while (rows.length < height - 1)
3346
+ rows.push("");
3347
+ if (height > 1)
3348
+ rows.push(this.#detail.help);
3349
+ return rows.slice(0, height).map((line) => theme.panel(` ${truncateToWidth3(line, w, "...", true)} `));
3350
+ }
3230
3351
  if (height >= 4)
3231
3352
  rows.push(...this.search.render(w).slice(0, 1));
3232
3353
  const help = this.#state === "error" ? labels.retry : this.#state === "ready" ? labels.select : labels.close;
@@ -3236,11 +3357,20 @@ class PickerPanel {
3236
3357
  else {
3237
3358
  const items = this.#filtered();
3238
3359
  this.#index = Math.min(this.#index, Math.max(0, items.length - 1));
3360
+ if (items[this.#index]?.header) {
3361
+ const first = this.#firstSelectable(items);
3362
+ if (first >= 0)
3363
+ this.#index = first;
3364
+ }
3239
3365
  const selected = items[this.#index];
3240
3366
  const details = height >= 9 && selected?.description ? wrapTextWithAnsi(selected.description, w).slice(0, 2) : [];
3241
3367
  const count = Math.max(1, available - details.length);
3242
3368
  const start = Math.max(0, Math.min(this.#index - Math.floor(count / 2), items.length - count));
3243
3369
  for (const [i, item] of items.slice(start, start + count).entries()) {
3370
+ if (item.header) {
3371
+ rows.push(theme.dim(` ${item.label}`));
3372
+ continue;
3373
+ }
3244
3374
  const right = item.rightLabel ?? "";
3245
3375
  const leftWidth = Math.max(1, w - 3 - visibleWidth2(right));
3246
3376
  const left = truncateToWidth3(item.label, leftWidth, "...");
@@ -3341,7 +3471,7 @@ ${theme.error(state.note)}`;
3341
3471
  });
3342
3472
 
3343
3473
  // packages/tui/src/components/user-message.ts
3344
- import { Container as Container4, Markdown as Markdown2 } from "@earendil-works/pi-tui";
3474
+ import { Container as Container4, Markdown as Markdown2, Text as Text4 } from "@earendil-works/pi-tui";
3345
3475
  var UserMessageComponent;
3346
3476
  var init_user_message = __esm(() => {
3347
3477
  init_theme();
@@ -3358,6 +3488,16 @@ var init_user_message = __esm(() => {
3358
3488
  }
3359
3489
  #rebuild() {
3360
3490
  this.clear();
3491
+ const invocation = parseSkillInvocation(this.#text);
3492
+ if (invocation !== null) {
3493
+ const args = invocation.args.replace(/\s+/g, " ").trim();
3494
+ const preview = args.length > 100 ? `${args.slice(0, 99)}\u2026` : args;
3495
+ const head = `${theme.toolTitle(`\u2726 skill:${invocation.name}`)}${preview ? ` ${theme.userText(preview)}` : ""}`;
3496
+ const source = theme.dim(`skill body from ${invocation.location} (collapsed)`);
3497
+ this.addChild(new Text4(`${head}
3498
+ ${source}`, 1, 0, theme.userBg));
3499
+ return;
3500
+ }
3361
3501
  this.addChild(new Markdown2(this.#text, 1, 0, getMarkdownTheme(), {
3362
3502
  color: theme.userText,
3363
3503
  bgColor: theme.userBg
@@ -3621,7 +3761,7 @@ var init_file_completion = __esm(() => {
3621
3761
 
3622
3762
  // packages/gateway/src/contract.ts
3623
3763
  import { defineService } from "@earendil-works/chord";
3624
- var NekoSessionDirectory, NekoSessionManagement, NekoModels, NekoChat, NekoWorkspaces, NekoCredentials, NekoWorkspaceFiles;
3764
+ var NekoSessionDirectory, NekoSessionManagement, NekoModels, NekoChat, NekoWorkspaces, NekoCredentials, NekoWorkspaceFiles, NekoSkillMarket;
3625
3765
  var init_contract = __esm(() => {
3626
3766
  NekoSessionDirectory = defineService("neko.session-directory");
3627
3767
  NekoSessionManagement = defineService("neko.session-management");
@@ -3630,6 +3770,7 @@ var init_contract = __esm(() => {
3630
3770
  NekoWorkspaces = defineService("neko.workspaces");
3631
3771
  NekoCredentials = defineService("neko.credentials");
3632
3772
  NekoWorkspaceFiles = defineService("neko.workspace-files");
3773
+ NekoSkillMarket = defineService("neko.skill-market");
3633
3774
  });
3634
3775
 
3635
3776
  // packages/gateway/src/ws-listener.ts
@@ -3782,7 +3923,8 @@ async function connectNekoGateway(address) {
3782
3923
  NekoModels,
3783
3924
  NekoWorkspaces,
3784
3925
  NekoWorkspaceFiles,
3785
- NekoCredentials
3926
+ NekoCredentials,
3927
+ NekoSkillMarket
3786
3928
  ],
3787
3929
  transport: serverTransport
3788
3930
  });
@@ -3798,6 +3940,7 @@ async function connectNekoGateway(address) {
3798
3940
  workspaces: serverBinding.use(NekoWorkspaces),
3799
3941
  workspaceFiles: serverBinding.use(NekoWorkspaceFiles),
3800
3942
  credentials: serverBinding.use(NekoCredentials),
3943
+ skillMarket: serverBinding.use(NekoSkillMarket),
3801
3944
  listModels: () => models.list(BACKGROUND_CONTEXT2),
3802
3945
  async attach(sessionId) {
3803
3946
  await sessionBinding?.dispose(BACKGROUND_CONTEXT2).catch(() => {});
@@ -3913,10 +4056,617 @@ function fileSearchInstallHelp(platform = process.platform) {
3913
4056
  return `\u672A\u627E\u5230 fd \u6216 fdfind\uFF0C\u6682\u65F6\u65E0\u6CD5\u4F7F\u7528 @ \u6587\u4EF6\u8865\u5168\u3002\u8BF7\u5728\u8FD0\u884C neko \u7684\u7535\u8111\u4E0A\u5B89\u88C5\u3002${install}\u5B89\u88C5\u540E\u91CD\u65B0\u6253\u5F00\u7EC8\u7AEF\u5E76\u91CD\u542F neko\u3002`;
3914
4057
  }
3915
4058
 
4059
+ // node_modules/fflate/esm/index.mjs
4060
+ import { createRequire } from "module";
4061
+ function inflateSync(data, opts) {
4062
+ return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
4063
+ }
4064
+ function strFromU8(dat, latin1) {
4065
+ if (latin1) {
4066
+ var r = "";
4067
+ for (var i = 0;i < dat.length; i += 16384)
4068
+ r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));
4069
+ return r;
4070
+ } else if (td) {
4071
+ return td.decode(dat);
4072
+ } else {
4073
+ var _a = dutf8(dat), { s, r } = _a;
4074
+ if (r.length)
4075
+ err(8);
4076
+ return s;
4077
+ }
4078
+ }
4079
+ function unzipSync(data, opts) {
4080
+ var files = {};
4081
+ var e = data.length - 22;
4082
+ for (;b4(data, e) != 101010256; --e) {
4083
+ if (!e || data.length - e > 65558)
4084
+ err(13);
4085
+ }
4086
+ var c = b2(data, e + 8);
4087
+ if (!c)
4088
+ return {};
4089
+ var o = b4(data, e + 16);
4090
+ var z = b4(data, e - 20) == 117853008;
4091
+ if (z) {
4092
+ var ze = b4(data, e - 12);
4093
+ z = b4(data, ze) == 101075792;
4094
+ if (z) {
4095
+ c = b4(data, ze + 32);
4096
+ o = b4(data, ze + 48);
4097
+ }
4098
+ }
4099
+ var fltr = opts && opts.filter;
4100
+ for (var i = 0;i < c; ++i) {
4101
+ var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);
4102
+ o = no;
4103
+ if (!fltr || fltr({
4104
+ name: fn,
4105
+ size: sc,
4106
+ originalSize: su,
4107
+ compression: c_2
4108
+ })) {
4109
+ if (!c_2)
4110
+ files[fn] = slc(data, b, b + sc);
4111
+ else if (c_2 == 8)
4112
+ files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
4113
+ else
4114
+ err(14, "unknown compression type " + c_2);
4115
+ }
4116
+ }
4117
+ return files;
4118
+ }
4119
+ var require2, _a, Worker, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, clim, freb = function(eb, start) {
4120
+ var b = new u16(31);
4121
+ for (var i = 0;i < 31; ++i) {
4122
+ b[i] = start += 1 << eb[i - 1];
4123
+ }
4124
+ var r = new i32(b[30]);
4125
+ for (var i = 1;i < 30; ++i) {
4126
+ for (var j = b[i];j < b[i + 1]; ++j) {
4127
+ r[j] = j - b[i] << 5 | i;
4128
+ }
4129
+ }
4130
+ return { b, r };
4131
+ }, _a, fl, revfl, _b, fd, revfd, rev, x, i, hMap = function(cd, mb, r) {
4132
+ var s = cd.length;
4133
+ var i = 0;
4134
+ var l = new u16(mb);
4135
+ for (;i < s; ++i) {
4136
+ if (cd[i])
4137
+ ++l[cd[i] - 1];
4138
+ }
4139
+ var le = new u16(mb);
4140
+ for (i = 1;i < mb; ++i) {
4141
+ le[i] = le[i - 1] + l[i - 1] << 1;
4142
+ }
4143
+ var co;
4144
+ if (r) {
4145
+ co = new u16(1 << mb);
4146
+ var rvb = 15 - mb;
4147
+ for (i = 0;i < s; ++i) {
4148
+ if (cd[i]) {
4149
+ var sv = i << 4 | cd[i];
4150
+ var r_1 = mb - cd[i];
4151
+ var v = le[cd[i] - 1]++ << r_1;
4152
+ for (var m = v | (1 << r_1) - 1;v <= m; ++v) {
4153
+ co[rev[v] >> rvb] = sv;
4154
+ }
4155
+ }
4156
+ }
4157
+ } else {
4158
+ co = new u16(s);
4159
+ for (i = 0;i < s; ++i) {
4160
+ if (cd[i]) {
4161
+ co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
4162
+ }
4163
+ }
4164
+ }
4165
+ return co;
4166
+ }, flt, i, i, i, i, fdt, i, flrm, fdrm, max = function(a) {
4167
+ var m = a[0];
4168
+ for (var i = 1;i < a.length; ++i) {
4169
+ if (a[i] > m)
4170
+ m = a[i];
4171
+ }
4172
+ return m;
4173
+ }, bits = function(d, p, m) {
4174
+ var o = p / 8 | 0;
4175
+ return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
4176
+ }, bits16 = function(d, p) {
4177
+ var o = p / 8 | 0;
4178
+ return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
4179
+ }, shft = function(p) {
4180
+ return (p + 7) / 8 | 0;
4181
+ }, slc = function(v, s, e) {
4182
+ if (s == null || s < 0)
4183
+ s = 0;
4184
+ if (e == null || e > v.length)
4185
+ e = v.length;
4186
+ return new u8(v.subarray(s, e));
4187
+ }, ec, err = function(ind, msg, nt) {
4188
+ var e = new Error(msg || ec[ind]);
4189
+ e.code = ind;
4190
+ if (Error.captureStackTrace)
4191
+ Error.captureStackTrace(e, err);
4192
+ if (!nt)
4193
+ throw e;
4194
+ return e;
4195
+ }, inflt = function(dat, st, buf, dict) {
4196
+ var sl = dat.length, dl = dict ? dict.length : 0;
4197
+ if (!sl || st.f && !st.l)
4198
+ return buf || new u8(0);
4199
+ var noBuf = !buf;
4200
+ var resize = noBuf || st.i != 2;
4201
+ var noSt = st.i;
4202
+ if (noBuf)
4203
+ buf = new u8(sl * 3);
4204
+ var cbuf = function(l) {
4205
+ var bl = buf.length;
4206
+ if (l > bl) {
4207
+ var nbuf = new u8(Math.max(bl * 2, l));
4208
+ nbuf.set(buf);
4209
+ buf = nbuf;
4210
+ }
4211
+ };
4212
+ var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, { l: lm, d: dm, m: lbt, n: dbt } = st;
4213
+ var tbts = sl * 8;
4214
+ do {
4215
+ if (!lm) {
4216
+ final = bits(dat, pos, 1);
4217
+ var type = bits(dat, pos + 1, 3);
4218
+ pos += 3;
4219
+ if (!type) {
4220
+ var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
4221
+ if (t > sl) {
4222
+ if (noSt)
4223
+ err(0);
4224
+ break;
4225
+ }
4226
+ if (resize)
4227
+ cbuf(bt + l);
4228
+ buf.set(dat.subarray(s, t), bt);
4229
+ st.b = bt += l, st.p = pos = t * 8, st.f = final;
4230
+ continue;
4231
+ } else if (type == 1)
4232
+ lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
4233
+ else if (type == 2) {
4234
+ var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
4235
+ var tl = hLit + bits(dat, pos + 5, 31) + 1;
4236
+ pos += 14;
4237
+ var ldt = new u8(tl);
4238
+ var clt = new u8(19);
4239
+ for (var i = 0;i < hcLen; ++i) {
4240
+ clt[clim[i]] = bits(dat, pos + i * 3, 7);
4241
+ }
4242
+ pos += hcLen * 3;
4243
+ var clb = max(clt), clbmsk = (1 << clb) - 1;
4244
+ var clm = hMap(clt, clb, 1);
4245
+ for (var i = 0;i < tl; ) {
4246
+ var r = clm[bits(dat, pos, clbmsk)];
4247
+ pos += r & 15;
4248
+ var s = r >> 4;
4249
+ if (s < 16) {
4250
+ ldt[i++] = s;
4251
+ } else {
4252
+ var c = 0, n = 0;
4253
+ if (s == 16)
4254
+ n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
4255
+ else if (s == 17)
4256
+ n = 3 + bits(dat, pos, 7), pos += 3;
4257
+ else if (s == 18)
4258
+ n = 11 + bits(dat, pos, 127), pos += 7;
4259
+ while (n--)
4260
+ ldt[i++] = c;
4261
+ }
4262
+ }
4263
+ var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
4264
+ lbt = max(lt);
4265
+ dbt = max(dt);
4266
+ lm = hMap(lt, lbt, 1);
4267
+ dm = hMap(dt, dbt, 1);
4268
+ } else
4269
+ err(1);
4270
+ if (pos > tbts) {
4271
+ if (noSt)
4272
+ err(0);
4273
+ break;
4274
+ }
4275
+ }
4276
+ if (resize)
4277
+ cbuf(bt + 131072);
4278
+ var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
4279
+ var lpos = pos;
4280
+ for (;; lpos = pos) {
4281
+ var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
4282
+ pos += c & 15;
4283
+ if (pos > tbts) {
4284
+ if (noSt)
4285
+ err(0);
4286
+ break;
4287
+ }
4288
+ if (!c)
4289
+ err(2);
4290
+ if (sym < 256)
4291
+ buf[bt++] = sym;
4292
+ else if (sym == 256) {
4293
+ lpos = pos, lm = null;
4294
+ break;
4295
+ } else {
4296
+ var add = sym - 254;
4297
+ if (sym > 264) {
4298
+ var i = sym - 257, b = fleb[i];
4299
+ add = bits(dat, pos, (1 << b) - 1) + fl[i];
4300
+ pos += b;
4301
+ }
4302
+ var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
4303
+ if (!d)
4304
+ err(3);
4305
+ pos += d & 15;
4306
+ var dt = fd[dsym];
4307
+ if (dsym > 3) {
4308
+ var b = fdeb[dsym];
4309
+ dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
4310
+ }
4311
+ if (pos > tbts) {
4312
+ if (noSt)
4313
+ err(0);
4314
+ break;
4315
+ }
4316
+ if (resize)
4317
+ cbuf(bt + 131072);
4318
+ var end = bt + add;
4319
+ if (bt < dt) {
4320
+ var shift = dl - dt, dend = Math.min(dt, end);
4321
+ if (shift + bt < 0)
4322
+ err(3);
4323
+ for (;bt < dend; ++bt)
4324
+ buf[bt] = dict[shift + bt];
4325
+ }
4326
+ for (;bt < end; ++bt)
4327
+ buf[bt] = buf[bt - dt];
4328
+ }
4329
+ }
4330
+ st.l = lm, st.p = lpos, st.b = bt, st.f = final;
4331
+ if (lm)
4332
+ final = 1, st.m = lbt, st.d = dm, st.n = dbt;
4333
+ } while (!final);
4334
+ return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
4335
+ }, et, b2 = function(d, b) {
4336
+ return d[b] | d[b + 1] << 8;
4337
+ }, b4 = function(d, b) {
4338
+ return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
4339
+ }, b8 = function(d, b) {
4340
+ return b4(d, b) + b4(d, b + 4) * 4294967296;
4341
+ }, td, tds = 0, dutf8 = function(d) {
4342
+ for (var r = "", i = 0;; ) {
4343
+ var c = d[i++];
4344
+ var eb = (c > 127) + (c > 223) + (c > 239);
4345
+ if (i + eb > d.length)
4346
+ return { s: r, r: slc(d, i - 1) };
4347
+ if (!eb)
4348
+ r += String.fromCharCode(c);
4349
+ else if (eb == 3) {
4350
+ c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | d[i++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023);
4351
+ } else if (eb & 1)
4352
+ r += String.fromCharCode((c & 31) << 6 | d[i++] & 63);
4353
+ else
4354
+ r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63);
4355
+ }
4356
+ }, slzh = function(d, b) {
4357
+ return b + 30 + b2(d, b + 26) + b2(d, b + 28);
4358
+ }, zh = function(d, b, z) {
4359
+ var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl;
4360
+ var _a = z64hs(d, es, efl, z, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a[0], su = _a[1], off = _a[2];
4361
+ return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
4362
+ }, z64hs = function(d, b, l, z, sc, su, off) {
4363
+ var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
4364
+ var nf = nsc + nsu + noff;
4365
+ if (z && nf) {
4366
+ for (;b + 4 < e; b += 4 + b2(d, b + 2)) {
4367
+ if (b2(d, b) == 1) {
4368
+ return [
4369
+ nsc ? b8(d, b + 4 + 8 * nsu) : sc,
4370
+ nsu ? b8(d, b + 4) : su,
4371
+ noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off,
4372
+ 1
4373
+ ];
4374
+ }
4375
+ }
4376
+ if (z < 2)
4377
+ err(13);
4378
+ }
4379
+ return [sc, su, off, 0];
4380
+ };
4381
+ var init_esm = __esm(() => {
4382
+ require2 = createRequire("/");
4383
+ try {
4384
+ _a = require2("worker_threads"), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable;
4385
+ } catch (e) {}
4386
+ u8 = Uint8Array;
4387
+ u16 = Uint16Array;
4388
+ i32 = Int32Array;
4389
+ fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0]);
4390
+ fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0]);
4391
+ clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
4392
+ _a = freb(fleb, 2);
4393
+ fl = _a.b;
4394
+ revfl = _a.r;
4395
+ fl[28] = 258, revfl[258] = 28;
4396
+ _b = freb(fdeb, 0);
4397
+ fd = _b.b;
4398
+ revfd = _b.r;
4399
+ rev = new u16(32768);
4400
+ for (i = 0;i < 32768; ++i) {
4401
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
4402
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
4403
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
4404
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
4405
+ }
4406
+ flt = new u8(288);
4407
+ for (i = 0;i < 144; ++i)
4408
+ flt[i] = 8;
4409
+ for (i = 144;i < 256; ++i)
4410
+ flt[i] = 9;
4411
+ for (i = 256;i < 280; ++i)
4412
+ flt[i] = 7;
4413
+ for (i = 280;i < 288; ++i)
4414
+ flt[i] = 8;
4415
+ fdt = new u8(32);
4416
+ for (i = 0;i < 32; ++i)
4417
+ fdt[i] = 5;
4418
+ flrm = /* @__PURE__ */ hMap(flt, 9, 1);
4419
+ fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
4420
+ ec = [
4421
+ "unexpected EOF",
4422
+ "invalid block type",
4423
+ "invalid length/literal",
4424
+ "invalid distance",
4425
+ "stream finished",
4426
+ "no stream handler",
4427
+ ,
4428
+ "no callback",
4429
+ "invalid UTF-8 data",
4430
+ "extra field too long",
4431
+ "date not in range 1980-2099",
4432
+ "filename too long",
4433
+ "stream finishing",
4434
+ "invalid zip data"
4435
+ ];
4436
+ et = /* @__PURE__ */ new u8(0);
4437
+ td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder;
4438
+ try {
4439
+ td.decode(et, { stream: true });
4440
+ tds = 1;
4441
+ } catch (e) {}
4442
+ });
4443
+
4444
+ // packages/gateway/src/skill-market.ts
4445
+ import { createHash as createHash2, randomUUID } from "crypto";
4446
+ import { mkdir as mkdir2, readdir as readdir2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
4447
+ import { homedir as homedir5 } from "os";
4448
+ import { dirname as dirname5, join as join8 } from "path";
4449
+ function createSkillMarketClient(options) {
4450
+ const home = options.home ?? homedir5();
4451
+ const fetchImpl = options.fetchImpl ?? fetch;
4452
+ const now = options.now ?? Date.now;
4453
+ let base;
4454
+ try {
4455
+ base = new URL(options.marketUrl);
4456
+ if (base.protocol !== "https:" && base.protocol !== "http:")
4457
+ throw new Error("unsupported protocol");
4458
+ } catch {
4459
+ throw new SkillMarketError("market_invalid_url", `Invalid skill market URL: ${options.marketUrl}`);
4460
+ }
4461
+ base = new URL(base.toString().replace(/\/+$/, ""));
4462
+ let catalog;
4463
+ const zipCache = new Map;
4464
+ const request = async (path) => {
4465
+ let response;
4466
+ try {
4467
+ response = await fetchImpl(new URL(path, `${base}/`).toString());
4468
+ } catch (error) {
4469
+ throw new SkillMarketError("market_unavailable", `Skill market unreachable: ${error instanceof Error ? error.message : String(error)}`);
4470
+ }
4471
+ return response;
4472
+ };
4473
+ const refreshCatalog = async () => {
4474
+ const response = await request("api/skills");
4475
+ if (!response.ok)
4476
+ throw new SkillMarketError("market_unavailable", `Skill list failed with HTTP ${response.status}`);
4477
+ const data = await response.json();
4478
+ const skills = Array.isArray(data.skills) ? data.skills.map(normalizeEntry) : [];
4479
+ catalog = { at: now(), skills };
4480
+ return skills;
4481
+ };
4482
+ const listSkills = async () => catalog !== undefined && now() - catalog.at < CATALOG_TTL_MS ? catalog.skills : refreshCatalog();
4483
+ const detail = async (name) => {
4484
+ let hit = (await listSkills()).find((skill) => skill.name === name);
4485
+ if (hit === undefined)
4486
+ hit = (await refreshCatalog()).find((skill) => skill.name === name);
4487
+ if (hit === undefined)
4488
+ throw new SkillMarketError("skill_not_found", `Skill not found on market: ${name}`);
4489
+ return hit;
4490
+ };
4491
+ const download = async (skill) => {
4492
+ const cached = zipCache.get(skill.sha256);
4493
+ if (cached !== undefined) {
4494
+ zipCache.delete(skill.sha256);
4495
+ zipCache.set(skill.sha256, cached);
4496
+ return cached;
4497
+ }
4498
+ if (skill.sizeBytes > MAX_PACKAGE_BYTES)
4499
+ throw new SkillMarketError("skill_too_large", `Package ${skill.name} is ${skill.sizeBytes} bytes (limit ${MAX_PACKAGE_BYTES})`);
4500
+ const response = await request(`api/skills/${encodeURIComponent(skill.name)}/download`);
4501
+ if (!response.ok)
4502
+ throw new SkillMarketError(response.status === 404 ? "skill_not_found" : "market_unavailable", `Download of ${skill.name} failed with HTTP ${response.status}`);
4503
+ const bytes = new Uint8Array(await response.arrayBuffer());
4504
+ if (bytes.byteLength > MAX_PACKAGE_BYTES)
4505
+ throw new SkillMarketError("skill_too_large", `Package ${skill.name} exceeds ${MAX_PACKAGE_BYTES} bytes`);
4506
+ const digest = createHash2("sha256").update(bytes).digest("hex");
4507
+ if (digest !== skill.sha256)
4508
+ throw new SkillMarketError("skill_hash_mismatch", `SHA-256 mismatch for ${skill.name}: catalog says ${skill.sha256}, got ${digest}`);
4509
+ zipCache.set(skill.sha256, bytes);
4510
+ while (zipCache.size > ZIP_CACHE_MAX) {
4511
+ const oldest = zipCache.keys().next().value;
4512
+ if (oldest === undefined)
4513
+ break;
4514
+ zipCache.delete(oldest);
4515
+ }
4516
+ return bytes;
4517
+ };
4518
+ const unpack = (skill, zip) => {
4519
+ let entries;
4520
+ try {
4521
+ entries = unzipSync(zip);
4522
+ } catch {
4523
+ throw new SkillMarketError("skill_invalid_package", `Package ${skill.name} is not a valid zip archive`);
4524
+ }
4525
+ const files = new Map;
4526
+ const skillMds = [];
4527
+ for (const name of Object.keys(entries)) {
4528
+ if (name.endsWith("/"))
4529
+ continue;
4530
+ if (name.includes("\\") || name.startsWith("/"))
4531
+ throw new SkillMarketError("skill_invalid_package", `Unsafe path in package: ${name}`);
4532
+ const segments = name.split("/");
4533
+ if (segments.some((s) => s === ".." || s === ""))
4534
+ throw new SkillMarketError("skill_invalid_package", `Unsafe path in package: ${name}`);
4535
+ if (segments[segments.length - 1] === "SKILL.md")
4536
+ skillMds.push(name);
4537
+ files.set(name, entries[name]);
4538
+ }
4539
+ if (skillMds.length !== 1)
4540
+ throw new SkillMarketError("skill_invalid_package", skillMds.length === 0 ? `Package ${skill.name} contains no SKILL.md` : `Package ${skill.name} contains multiple SKILL.md files`);
4541
+ const root = skillMds[0].slice(0, -"SKILL.md".length);
4542
+ const inner = new Map;
4543
+ for (const [name, bytes] of files) {
4544
+ if (!name.startsWith(root))
4545
+ continue;
4546
+ inner.set(name.slice(root.length), bytes);
4547
+ }
4548
+ return inner;
4549
+ };
4550
+ const skillsDirFor = (scope, workspaceRoot) => {
4551
+ if (scope === "user")
4552
+ return userSkillsDir2(home);
4553
+ if (workspaceRoot === undefined || workspaceRoot === "")
4554
+ throw new SkillMarketError("market_unavailable", "Project-scope install needs a workspaceRoot");
4555
+ return projectSkillsDir(workspaceRoot);
4556
+ };
4557
+ const sweepTempDirs = async (skillsDir) => {
4558
+ const parent = dirname5(skillsDir);
4559
+ let names = [];
4560
+ try {
4561
+ names = await readdir2(parent);
4562
+ } catch {
4563
+ return;
4564
+ }
4565
+ await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm2(join8(parent, n), { recursive: true, force: true }).catch(() => {})));
4566
+ };
4567
+ const install = async ({ name, scope, workspaceRoot, overwrite }) => {
4568
+ const skillsDir = skillsDirFor(scope, workspaceRoot);
4569
+ const target = join8(skillsDir, name);
4570
+ const exists = await stat2(target).catch(() => {
4571
+ return;
4572
+ }) !== undefined;
4573
+ if (exists && overwrite !== true)
4574
+ throw new SkillMarketError("skill_exists", `Skill ${name} already exists at ${scope} level (${target})`);
4575
+ const skill = await detail(name);
4576
+ const files = unpack(skill, await download(skill));
4577
+ await sweepTempDirs(skillsDir);
4578
+ const temp = join8(dirname5(skillsDir), `.tmp-${randomUUID()}`);
4579
+ try {
4580
+ for (const [relative, bytes] of files) {
4581
+ const dest = join8(temp, ...relative.split("/"));
4582
+ await mkdir2(dirname5(dest), { recursive: true });
4583
+ await writeFile2(dest, bytes);
4584
+ }
4585
+ if (exists)
4586
+ await rm2(target, { recursive: true, force: true });
4587
+ await mkdir2(skillsDir, { recursive: true });
4588
+ await rename2(temp, target);
4589
+ } finally {
4590
+ await rm2(temp, { recursive: true, force: true }).catch(() => {});
4591
+ }
4592
+ let shadowedBy;
4593
+ if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
4594
+ const projectDir = join8(projectSkillsDir(workspaceRoot), name);
4595
+ if (await stat2(projectDir).catch(() => {
4596
+ return;
4597
+ }) !== undefined)
4598
+ shadowedBy = "project";
4599
+ }
4600
+ return { name, scope, path: target, ...shadowedBy !== undefined ? { shadowedBy } : {} };
4601
+ };
4602
+ const uninstall = async ({ name, scope, workspaceRoot }) => {
4603
+ const skillsDir = skillsDirFor(scope, workspaceRoot);
4604
+ const target = join8(skillsDir, name);
4605
+ if (await stat2(target).catch(() => {
4606
+ return;
4607
+ }) === undefined)
4608
+ throw new SkillMarketError("skill_not_installed", `Skill ${name} is not installed at ${scope} level`);
4609
+ await rm2(target, { recursive: true, force: true });
4610
+ };
4611
+ return {
4612
+ async list(query) {
4613
+ if (query !== undefined && query.trim() !== "") {
4614
+ const response = await request(`api/skills?q=${encodeURIComponent(query.trim())}`);
4615
+ if (!response.ok)
4616
+ throw new SkillMarketError("market_unavailable", `Skill search failed with HTTP ${response.status}`);
4617
+ const data = await response.json();
4618
+ return Array.isArray(data.skills) ? data.skills.map(normalizeEntry) : [];
4619
+ }
4620
+ return listSkills();
4621
+ },
4622
+ detail,
4623
+ async preview(name) {
4624
+ const skill = await detail(name);
4625
+ const inner = unpack(skill, await download(skill));
4626
+ const bodyBytes = inner.get("SKILL.md");
4627
+ if (bodyBytes === undefined)
4628
+ throw new SkillMarketError("skill_invalid_package", `Package ${name} lost its SKILL.md`);
4629
+ const body = stripFrontmatter(new TextDecoder().decode(bodyBytes));
4630
+ return { body, files: [...inner.keys()].sort() };
4631
+ },
4632
+ install,
4633
+ uninstall
4634
+ };
4635
+ }
4636
+ var SkillMarketError, CATALOG_TTL_MS = 60000, ZIP_CACHE_MAX = 16, MAX_PACKAGE_BYTES, userSkillsDir2 = (home) => join8(home, ".agents", "skills"), projectSkillsDir = (workspaceRoot) => join8(workspaceRoot, ".agents", "skills"), activeLevelOf = (state) => state.project ? "project" : state.user ? "user" : state.builtin ? "builtin" : null, normalizeEntry = (raw) => {
4637
+ const row = raw ?? {};
4638
+ const metadata = row.metadata;
4639
+ const source = metadata !== null && typeof metadata === "object" ? metadata.source : undefined;
4640
+ return {
4641
+ name: String(row.name ?? ""),
4642
+ description: String(row.description ?? ""),
4643
+ category: typeof row.category === "string" && row.category !== "" ? row.category : "uncategorized",
4644
+ source: typeof source === "string" && source !== "" ? source : "unknown",
4645
+ license: String(row.license ?? ""),
4646
+ version: String(row.version ?? ""),
4647
+ sha256: String(row.sha256 ?? ""),
4648
+ sizeBytes: Number(row.size_bytes ?? 0),
4649
+ uploadedAt: String(row.uploaded_at ?? "")
4650
+ };
4651
+ };
4652
+ var init_skill_market = __esm(() => {
4653
+ init_src();
4654
+ init_esm();
4655
+ SkillMarketError = class SkillMarketError extends Error {
4656
+ code;
4657
+ constructor(code, message) {
4658
+ super(message);
4659
+ this.name = "SkillMarketError";
4660
+ this.code = code;
4661
+ }
4662
+ };
4663
+ MAX_PACKAGE_BYTES = 32 * 1024 * 1024;
4664
+ });
4665
+
3916
4666
  // packages/gateway/src/workspaces.ts
3917
4667
  import { constants as constants3 } from "fs";
3918
- import { access, readdir as readdir2, stat as stat2 } from "fs/promises";
3919
- import { dirname as dirname5, isAbsolute as isAbsolute3, join as join8 } from "path";
4668
+ import { access, readdir as readdir3, stat as stat3 } from "fs/promises";
4669
+ import { dirname as dirname6, isAbsolute as isAbsolute3, join as join9 } from "path";
3920
4670
  async function browseWorkspaces(options) {
3921
4671
  const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
3922
4672
  if (process.platform === "win32" && (requested === "/" || requested === "\\")) {
@@ -3938,16 +4688,16 @@ async function browseWorkspaces(options) {
3938
4688
  throw new Error("\u76EE\u5F55\u5206\u9875\u5DF2\u5931\u6548\uFF0C\u8BF7\u5237\u65B0\u5217\u8868");
3939
4689
  }
3940
4690
  }
3941
- const entries = (await readdir2(path, { withFileTypes: true })).filter((entry) => (options.showHidden || !entry.name.startsWith(".")) && entry.name.toLowerCase().includes(query)).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
4691
+ const entries = (await readdir3(path, { withFileTypes: true })).filter((entry) => (options.showHidden || !entry.name.startsWith(".")) && entry.name.toLowerCase().includes(query)).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
3942
4692
  const directories = [];
3943
4693
  let next = offset;
3944
4694
  for (;next < entries.length && directories.length < 100; next++) {
3945
4695
  const entry = entries[next];
3946
- const target = join8(path, entry.name);
4696
+ const target = join9(path, entry.name);
3947
4697
  if (!entry.isDirectory() && !entry.isSymbolicLink())
3948
4698
  continue;
3949
4699
  try {
3950
- if (!(await stat2(target)).isDirectory())
4700
+ if (!(await stat3(target)).isDirectory())
3951
4701
  continue;
3952
4702
  await access(target, constants3.R_OK | constants3.X_OK);
3953
4703
  directories.push({ name: entry.name, path: target });
@@ -3957,7 +4707,7 @@ async function browseWorkspaces(options) {
3957
4707
  }
3958
4708
  return {
3959
4709
  path,
3960
- parent: dirname5(path) === path ? null : dirname5(path),
4710
+ parent: dirname6(path) === path ? null : dirname6(path),
3961
4711
  entries: directories,
3962
4712
  next: next < entries.length ? JSON.stringify({ key, offset: next }) : null
3963
4713
  };
@@ -3986,6 +4736,26 @@ import {
3986
4736
  } from "@earendil-works/chord";
3987
4737
  async function createNekoServerServices(options) {
3988
4738
  const { runtime, context } = options;
4739
+ const market = createSkillMarketClient({
4740
+ marketUrl: options.marketUrl,
4741
+ ...options.promptHome !== undefined ? { home: options.promptHome } : {}
4742
+ });
4743
+ const toRemote = (error) => {
4744
+ if (error instanceof SkillMarketError) {
4745
+ const code = error.code === "skill_not_found" || error.code === "skill_not_installed" ? "service_not_found" : "service_invalid_value";
4746
+ throw new RemoteServiceError(code, `[${error.code}] ${error.message}`);
4747
+ }
4748
+ throw error;
4749
+ };
4750
+ const withInstallState = async (skill, workspaceRoot) => {
4751
+ const locals = await runtime.listSkillsByLevel(workspaceRoot);
4752
+ const installed = {
4753
+ project: locals.some((s) => s.name === skill.name && s.level === "project"),
4754
+ user: locals.some((s) => s.name === skill.name && s.level === "user"),
4755
+ builtin: locals.some((s) => s.name === skill.name && s.level === "builtin")
4756
+ };
4757
+ return { ...skill, installed, activeLevel: activeLevelOf(installed) };
4758
+ };
3989
4759
  const list = async () => (await runtime.listConversations()).map((info) => ({
3990
4760
  id: String(info.id),
3991
4761
  workspaceRoot: info.workspaceRoot,
@@ -4041,7 +4811,8 @@ async function createNekoServerServices(options) {
4041
4811
  { service: NekoModels, mode: "singleton" },
4042
4812
  { service: NekoCredentials, mode: "singleton" },
4043
4813
  { service: NekoWorkspaces, mode: "singleton" },
4044
- { service: NekoWorkspaceFiles, mode: "singleton" }
4814
+ { service: NekoWorkspaceFiles, mode: "singleton" },
4815
+ { service: NekoSkillMarket, mode: "singleton" }
4045
4816
  ]);
4046
4817
  provider.provide(NekoSessionDirectory, { state: directory });
4047
4818
  const updateCredentials = (operation) => serialize(async () => {
@@ -4091,6 +4862,13 @@ async function createNekoServerServices(options) {
4091
4862
  }
4092
4863
  });
4093
4864
  provider.provide(NekoModels, { list: async () => runtime.listModels() });
4865
+ provider.provide(NekoSkillMarket, {
4866
+ list: (input) => market.list(input.query).then((skills) => Promise.all(skills.map((skill) => withInstallState(skill, input.workspaceRoot)))).catch(toRemote),
4867
+ detail: (input) => market.detail(input.name).then((skill) => withInstallState(skill, input.workspaceRoot)).catch(toRemote),
4868
+ preview: (input) => market.preview(input.name).catch(toRemote),
4869
+ install: (input) => serialize(() => market.install(input).then((result) => ({ ...result })).catch(toRemote)),
4870
+ uninstall: (input) => serialize(() => market.uninstall(input).catch(toRemote))
4871
+ });
4094
4872
  provider.provide(NekoSessionManagement, {
4095
4873
  create: (createOptions) => serialize(async () => {
4096
4874
  const created = await runtime.createConversation(createOptions.model !== undefined || createOptions.thinkingLevel !== undefined || createOptions.cwd !== undefined ? {
@@ -4171,6 +4949,7 @@ var init_server_services = __esm(() => {
4171
4949
  init_src();
4172
4950
  init_workspace_files();
4173
4951
  init_contract();
4952
+ init_skill_market();
4174
4953
  init_workspaces();
4175
4954
  });
4176
4955
 
@@ -4517,9 +5296,9 @@ var init_session_host = __esm(() => {
4517
5296
  });
4518
5297
 
4519
5298
  // packages/gateway/src/gateway.ts
4520
- import { randomUUID } from "crypto";
4521
- import { mkdir as mkdir2, readFile as readFile5, writeFile as writeFile2 } from "fs/promises";
4522
- import { basename as basename5, dirname as dirname6, join as join9 } from "path";
5299
+ import { randomUUID as randomUUID2 } from "crypto";
5300
+ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
5301
+ import { basename as basename5, dirname as dirname7, join as join10 } from "path";
4523
5302
  import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT3 } from "@earendil-works/chord/context";
4524
5303
  import { isServerId } from "@earendil-works/pi-protocol";
4525
5304
  import { Server, SessionNotFoundError } from "@earendil-works/pi-server";
@@ -4531,9 +5310,10 @@ async function startGateway(options = {}) {
4531
5310
  const storagePath = options.storagePath ?? fileConfig.storagePath ?? DEFAULT_STORAGE_PATH;
4532
5311
  const host = options.host ?? fileConfig.gateway?.host ?? DEFAULT_HOST;
4533
5312
  const port = options.port !== undefined ? options.port : fileConfig.gateway?.port ?? (process.platform === "win32" ? 0 : DEFAULT_PORT);
4534
- const nekoDir = dirname6(storagePath);
5313
+ const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
5314
+ const nekoDir = dirname7(storagePath);
4535
5315
  const serverId = options.serverId ?? await loadOrCreateServerId(nekoDir);
4536
- const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null : join9(nekoDir, `${serverId}.sock`);
5316
+ const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null : join10(nekoDir, `${serverId}.sock`);
4537
5317
  const runtime = await createNekoRuntime({
4538
5318
  context,
4539
5319
  storage: options.storage ?? "sqlite",
@@ -4556,6 +5336,8 @@ async function startGateway(options = {}) {
4556
5336
  serverId,
4557
5337
  nekoDir,
4558
5338
  model,
5339
+ marketUrl,
5340
+ promptHome: options.promptHome,
4559
5341
  onError: options.onError
4560
5342
  });
4561
5343
  } catch (error) {
@@ -4566,7 +5348,12 @@ async function startGateway(options = {}) {
4566
5348
  async function bindGateway(runtime, options) {
4567
5349
  const { context, host, port, socketPath, serverId, nekoDir, model } = options;
4568
5350
  const modelLabel = `${model.provider}/${model.modelId}`;
4569
- const serverServices = await createNekoServerServices({ runtime, context });
5351
+ const serverServices = await createNekoServerServices({
5352
+ runtime,
5353
+ context,
5354
+ marketUrl: options.marketUrl,
5355
+ ...options.promptHome !== undefined ? { promptHome: options.promptHome } : {}
5356
+ });
4570
5357
  let wsListener;
4571
5358
  const listeners = [];
4572
5359
  if (socketPath !== null) {
@@ -4644,15 +5431,15 @@ function serverIdFromSocketPath(socketPath) {
4644
5431
  return isServerId(candidate) ? candidate : undefined;
4645
5432
  }
4646
5433
  async function loadOrCreateServerId(nekoDir) {
4647
- const file = join9(nekoDir, SERVER_ID_FILENAME);
5434
+ const file = join10(nekoDir, SERVER_ID_FILENAME);
4648
5435
  try {
4649
5436
  const existing = (await readFile5(file, "utf8")).trim();
4650
5437
  if (existing !== "")
4651
5438
  return existing;
4652
5439
  } catch {}
4653
- const created = randomUUID();
4654
- await mkdir2(nekoDir, { recursive: true, mode: 448 });
4655
- await writeFile2(file, `${created}
5440
+ const created = randomUUID2();
5441
+ await mkdir3(nekoDir, { recursive: true, mode: 448 });
5442
+ await writeFile3(file, `${created}
4656
5443
  `, { mode: 384 });
4657
5444
  return created;
4658
5445
  }
@@ -4666,14 +5453,14 @@ var init_gateway = __esm(() => {
4666
5453
  });
4667
5454
 
4668
5455
  // packages/gateway/src/process.ts
4669
- import { fileURLToPath } from "url";
5456
+ import { fileURLToPath as fileURLToPath2 } from "url";
4670
5457
  function gatewayCommand(args) {
4671
5458
  const source = import.meta.url.endsWith(".ts");
4672
5459
  return {
4673
5460
  binary: process.execPath,
4674
5461
  args: [
4675
5462
  ...source ? ["--conditions=neko-source"] : [],
4676
- fileURLToPath(new URL("./gateway.js", import.meta.url)),
5463
+ fileURLToPath2(new URL("./gateway.js", import.meta.url)),
4677
5464
  ...args
4678
5465
  ]
4679
5466
  };
@@ -4705,13 +5492,13 @@ var init_src2 = __esm(() => {
4705
5492
  // packages/tui/src/gateway.ts
4706
5493
  import { spawn as spawn2 } from "child_process";
4707
5494
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
4708
- import { basename as basename6, dirname as dirname7, join as join10 } from "path";
5495
+ import { basename as basename6, dirname as dirname8, join as join11 } from "path";
4709
5496
  import { isServerId as isServerId2 } from "@earendil-works/pi-protocol";
4710
5497
  async function connectToGateway(options = {}) {
4711
5498
  const config = loadNekoConfig(options.configDir);
4712
5499
  const storagePath = options.storagePath ?? config.storagePath ?? DEFAULT_STORAGE_PATH;
4713
- const nekoDir = dirname7(storagePath);
4714
- const fileServerId = readServerId(join10(nekoDir, SERVER_ID_FILENAME2));
5500
+ const nekoDir = dirname8(storagePath);
5501
+ const fileServerId = readServerId(join11(nekoDir, SERVER_ID_FILENAME2));
4715
5502
  if (process.platform === "win32") {
4716
5503
  if (fileServerId !== undefined) {
4717
5504
  const wsUrl = await readGatewayPortFile(nekoDir, fileServerId);
@@ -4724,8 +5511,8 @@ async function connectToGateway(options = {}) {
4724
5511
  }
4725
5512
  return spawnAndConnect({ ...options, storagePath, socketPath: undefined, nekoDir, fileServerId });
4726
5513
  }
4727
- const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ? join10(nekoDir, `${fileServerId}.sock`) : undefined);
4728
- const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(join10(nekoDir, SERVER_ID_FILENAME2));
5514
+ const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ? join11(nekoDir, `${fileServerId}.sock`) : undefined);
5515
+ const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(join11(nekoDir, SERVER_ID_FILENAME2));
4729
5516
  if (socketPath !== undefined && existsSync2(socketPath)) {
4730
5517
  const serverId = serverIdFor(socketPath);
4731
5518
  if (serverId !== undefined) {
@@ -4755,7 +5542,7 @@ async function spawnAndConnect(plan) {
4755
5542
  async function connectSpawned(plan, child, outputTail, spawnError) {
4756
5543
  const timeoutMs = plan.spawnTimeoutMs ?? 20000;
4757
5544
  if (process.platform === "win32") {
4758
- const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(join10(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
5545
+ const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(join11(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
4759
5546
  const wsUrl = await waitForValue(() => readGatewayPortFile(plan.nekoDir, serverId), "gateway ready (port file)", timeoutMs, child, outputTail, spawnError);
4760
5547
  let lastError;
4761
5548
  for (let attempt = 0;attempt < CONNECT_RETRIES; attempt++) {
@@ -4784,11 +5571,11 @@ ${outputTail()}`);
4784
5571
  }
4785
5572
  let socketPath = plan.socketPath;
4786
5573
  if (socketPath === undefined) {
4787
- const generated = await waitForValue(() => readServerId(join10(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
4788
- socketPath = join10(plan.nekoDir, `${generated}.sock`);
5574
+ const generated = await waitForValue(() => readServerId(join11(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
5575
+ socketPath = join11(plan.nekoDir, `${generated}.sock`);
4789
5576
  }
4790
5577
  await waitForValue(() => existsSync2(socketPath) ? true : undefined, `gateway ready (socket: ${socketPath})`, timeoutMs, child, outputTail, spawnError);
4791
- const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join10(plan.nekoDir, SERVER_ID_FILENAME2));
5578
+ const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join11(plan.nekoDir, SERVER_ID_FILENAME2));
4792
5579
  if (serverId === undefined) {
4793
5580
  throw new Error(`Cannot determine gateway serverId (socket: ${socketPath})`);
4794
5581
  }
@@ -4900,8 +5687,8 @@ var init_gateway2 = __esm(() => {
4900
5687
 
4901
5688
  // packages/tui/src/image-input.ts
4902
5689
  import { execFile } from "child_process";
4903
- import { readFile as readFile6, stat as stat3 } from "fs/promises";
4904
- import { homedir as homedir5 } from "os";
5690
+ import { readFile as readFile6, stat as stat4 } from "fs/promises";
5691
+ import { homedir as homedir6 } from "os";
4905
5692
  import { basename as basename7, resolve as resolve5 } from "path";
4906
5693
  import { promisify } from "util";
4907
5694
  import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey4 } from "@earendil-works/pi-tui";
@@ -4936,12 +5723,12 @@ function imagePaths(text, platform = process.platform) {
4936
5723
  return paths.length && paths.every((p) => /\.(png|jpe?g|webp)$/i.test(p)) ? paths : null;
4937
5724
  }
4938
5725
  async function readImageFile(path, cwd) {
4939
- const full = resolve5(cwd, path.startsWith("~/") ? homedir5() + path.slice(1) : path);
4940
- const info = await stat3(full);
5726
+ const full = resolve5(cwd, path.startsWith("~/") ? homedir6() + path.slice(1) : path);
5727
+ const info = await stat4(full);
4941
5728
  if (!info.isFile() || info.size > 20 * 1024 * 1024)
4942
5729
  throw new Error("Image must be a file no larger than 20 MiB");
4943
5730
  const bytes = await readFile6(full);
4944
- const after = await stat3(full);
5731
+ const after = await stat4(full);
4945
5732
  if (bytes.length !== info.size || after.size !== info.size || after.mtimeMs !== info.mtimeMs)
4946
5733
  throw new Error("Image changed while reading");
4947
5734
  const mimeType = /\.png$/i.test(full) ? "image/png" : /\.webp$/i.test(full) ? "image/webp" : "image/jpeg";
@@ -5006,7 +5793,7 @@ var init_image_input = __esm(() => {
5006
5793
  });
5007
5794
 
5008
5795
  // packages/tui/src/layout.ts
5009
- import { Text as Text4, VStack } from "@earendil-works/pi-tui";
5796
+ import { Text as Text5, VStack } from "@earendil-works/pi-tui";
5010
5797
  function createAppLayout(parts) {
5011
5798
  const normal = (v) => {
5012
5799
  parts.footer.panelOpen = parts.panelOpen();
@@ -5016,7 +5803,7 @@ function createAppLayout(parts) {
5016
5803
  };
5017
5804
  return new VStack([
5018
5805
  {
5019
- component: new Text4("Terminal too small. Resize to 40x10. Ctrl+C Exit", 0, 0),
5806
+ component: new Text5("Terminal too small. Resize to 40x10. Ctrl+C Exit", 0, 0),
5020
5807
  visible: (v) => !normal(v),
5021
5808
  grow: 1
5022
5809
  },
@@ -5030,6 +5817,235 @@ function createAppLayout(parts) {
5030
5817
  var usableSize = (width, height) => width >= 40 && height >= 10;
5031
5818
  var init_layout = () => {};
5032
5819
 
5820
+ // packages/tui/src/skill-market-ui.ts
5821
+ import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
5822
+ import { Markdown as Markdown3, matchesKey as matchesKey5, wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-works/pi-tui";
5823
+
5824
+ class SkillDetailView {
5825
+ #skill = null;
5826
+ #error = "";
5827
+ #notice = "";
5828
+ #busy = false;
5829
+ #confirm = null;
5830
+ #name;
5831
+ #client;
5832
+ #workspaceRoot;
5833
+ #panel;
5834
+ #openPreview;
5835
+ constructor(name, client, workspaceRoot, panel, openPreview) {
5836
+ this.#name = name;
5837
+ this.#client = client;
5838
+ this.#workspaceRoot = workspaceRoot;
5839
+ this.#panel = panel;
5840
+ this.#openPreview = openPreview;
5841
+ this.refresh();
5842
+ }
5843
+ get help() {
5844
+ if (this.#confirm)
5845
+ return hint(["y", "Confirm"], ["n", "Cancel"]);
5846
+ const skill = this.#skill;
5847
+ const builtinOnly = skill?.installed.builtin === true && !skill.installed.user && !skill.installed.project;
5848
+ return builtinOnly ? hint(["v", "Preview"], ["Esc", "Back"]) + theme.dim(" (builtin ships with neko)") : hint(["u", "Install user"], ["p", "Install project"], ["d", "Uninstall"], ["v", "Preview"], ["Esc", "Back"]);
5849
+ }
5850
+ async refresh() {
5851
+ try {
5852
+ this.#skill = await this.#client.skillMarket.detail({ name: this.#name, workspaceRoot: this.#workspaceRoot }, CTX);
5853
+ this.#error = "";
5854
+ } catch (error) {
5855
+ this.#error = error instanceof Error ? error.message : String(error);
5856
+ }
5857
+ this.#panel.changed();
5858
+ }
5859
+ handleKey(data) {
5860
+ if (this.#confirm) {
5861
+ if (matchesKey5(data, "y")) {
5862
+ const pending = this.#confirm;
5863
+ this.#confirm = null;
5864
+ this.run(pending.run);
5865
+ } else if (matchesKey5(data, "n")) {
5866
+ this.#confirm = null;
5867
+ }
5868
+ return true;
5869
+ }
5870
+ if (this.#busy || this.#skill === null)
5871
+ return true;
5872
+ const skill = this.#skill;
5873
+ const builtinOnly = skill.installed.builtin && !skill.installed.user && !skill.installed.project;
5874
+ if (matchesKey5(data, "v")) {
5875
+ this.#openPreview(this.#name);
5876
+ return true;
5877
+ }
5878
+ if (builtinOnly)
5879
+ return true;
5880
+ if (matchesKey5(data, "u"))
5881
+ this.askInstall("user", skill);
5882
+ else if (matchesKey5(data, "p"))
5883
+ this.askInstall("project", skill);
5884
+ else if (matchesKey5(data, "d"))
5885
+ this.askUninstall(skill);
5886
+ else
5887
+ return false;
5888
+ return true;
5889
+ }
5890
+ askInstall(scope, skill) {
5891
+ const exists = scope === "user" ? skill.installed.user : skill.installed.project;
5892
+ const shadowed = scope === "user" && skill.installed.project;
5893
+ if (exists) {
5894
+ this.#confirm = {
5895
+ prompt: `${skill.name} exists at ${scope} level. Overwrite with market version ${skill.version}? Local edits are lost.`,
5896
+ run: () => this.install(scope, true)
5897
+ };
5898
+ } else if (shadowed) {
5899
+ this.#confirm = {
5900
+ prompt: `Project level already has ${skill.name} and wins. Install at user level anyway?`,
5901
+ run: () => this.install(scope, false)
5902
+ };
5903
+ } else {
5904
+ this.run(() => this.install(scope, false));
5905
+ }
5906
+ }
5907
+ askUninstall(skill) {
5908
+ const scope = skill.installed.project ? "project" : skill.installed.user ? "user" : null;
5909
+ if (scope === null) {
5910
+ this.#notice = "Not installed locally.";
5911
+ return;
5912
+ }
5913
+ this.#confirm = {
5914
+ prompt: `Uninstall ${skill.name} from ${scope} level? This cannot be undone.`,
5915
+ run: async () => {
5916
+ await this.#client.skillMarket.uninstall({ name: this.#name, scope, workspaceRoot: this.#workspaceRoot }, CTX);
5917
+ this.#notice = `Uninstalled from ${scope}. Takes effect on the next message.`;
5918
+ }
5919
+ };
5920
+ }
5921
+ async install(scope, overwrite) {
5922
+ try {
5923
+ const result = await this.#client.skillMarket.install({ name: this.#name, scope, workspaceRoot: this.#workspaceRoot, overwrite }, CTX);
5924
+ this.#notice = result.shadowedBy === "project" ? "Installed at user level, but the project-level copy still wins." : `Installed at ${scope} level. Takes effect on the next message.`;
5925
+ } catch (error) {
5926
+ if (marketErrorCode(error) === "skill_exists") {
5927
+ this.#confirm = {
5928
+ prompt: `${this.#name} already exists at ${scope} level. Overwrite with the market version?`,
5929
+ run: () => this.install(scope, true)
5930
+ };
5931
+ return;
5932
+ }
5933
+ throw error;
5934
+ }
5935
+ }
5936
+ async run(action) {
5937
+ this.#busy = true;
5938
+ this.#notice = "";
5939
+ this.#panel.changed();
5940
+ try {
5941
+ await action();
5942
+ } catch (error) {
5943
+ this.#notice = `Failed: ${error instanceof Error ? error.message : String(error)}`;
5944
+ } finally {
5945
+ this.#busy = false;
5946
+ await this.refresh();
5947
+ }
5948
+ }
5949
+ lines(width, _height) {
5950
+ if (this.#confirm) {
5951
+ return [theme.accent("Confirm"), "", ...wrapTextWithAnsi2(this.#confirm.prompt, width), ""];
5952
+ }
5953
+ const skill = this.#skill;
5954
+ if (skill === null)
5955
+ return [this.#error !== "" ? theme.error(`Error: ${this.#error}`) : theme.dim("Loading...")];
5956
+ const mark = (on, label) => on ? theme.ok(`${label} \u2713`) : theme.dim(`${label} \u2014`);
5957
+ const state = [
5958
+ mark(skill.installed.project, "project"),
5959
+ mark(skill.installed.user, "user"),
5960
+ mark(skill.installed.builtin, "builtin")
5961
+ ].join(" ");
5962
+ const rows = [
5963
+ `${style.bold(skill.name)} ${theme.dim(skill.version)}`,
5964
+ theme.dim(`${skill.source} \xB7 ${skill.category} \xB7 ${formatSize(skill.sizeBytes)} \xB7 sha256 ${skill.sha256.slice(0, 10)}\u2026`),
5965
+ `${theme.dim("installed:")} ${state}${skill.activeLevel ? theme.dim(` (active: ${skill.activeLevel})`) : ""}`,
5966
+ "",
5967
+ ...wrapTextWithAnsi2(skill.description, width)
5968
+ ];
5969
+ if (this.#busy)
5970
+ rows.push("", theme.dim("Working..."));
5971
+ if (this.#notice !== "")
5972
+ rows.push("", theme.accent(this.#notice));
5973
+ return rows;
5974
+ }
5975
+ }
5976
+
5977
+ class SkillPreviewView {
5978
+ #header = null;
5979
+ #markdown = null;
5980
+ #error = "";
5981
+ #offset = 0;
5982
+ #panel;
5983
+ get help() {
5984
+ return hint(["Up/Down", "Scroll"], ["Esc", "Back"]);
5985
+ }
5986
+ constructor(name, client, panel) {
5987
+ this.#panel = panel;
5988
+ client.skillMarket.preview({ name }, CTX).then((preview) => {
5989
+ this.#header = [`${theme.dim("files:")} ${preview.files.join(" ")}`, ""];
5990
+ this.#markdown = new Markdown3(preview.body, 0, 0, getMarkdownTheme());
5991
+ this.#panel.changed();
5992
+ }).catch((error) => {
5993
+ this.#error = error instanceof Error ? error.message : String(error);
5994
+ this.#panel.changed();
5995
+ });
5996
+ }
5997
+ handleKey(data) {
5998
+ if (matchesKey5(data, "up"))
5999
+ this.#offset = Math.max(0, this.#offset - 1);
6000
+ else if (matchesKey5(data, "down"))
6001
+ this.#offset += 1;
6002
+ else
6003
+ return false;
6004
+ return true;
6005
+ }
6006
+ lines(width, height) {
6007
+ if (this.#markdown === null || this.#header === null)
6008
+ return [this.#error !== "" ? theme.error(`Error: ${this.#error}`) : theme.dim("Loading preview...")];
6009
+ const flat = [...this.#header, ...this.#markdown.render(width)];
6010
+ const max = Math.max(0, flat.length - height);
6011
+ this.#offset = Math.min(this.#offset, max);
6012
+ return flat.slice(this.#offset, this.#offset + height);
6013
+ }
6014
+ }
6015
+ async function openSkillMarket(panel, client, workspaceRoot) {
6016
+ const openPreview = (name) => panel.showDetail(new SkillPreviewView(name, client, panel));
6017
+ await panel.show("Skills Market", async () => toItems(await client.skillMarket.list({ workspaceRoot }, CTX)), async (name) => new SkillDetailView(name, client, workspaceRoot, panel, openPreview));
6018
+ }
6019
+ var hint = (...parts) => parts.map(([key, label]) => `${theme.accent(key)} ${theme.dim(label)}`).join(theme.dim(" \xB7 ")), CTX, formatSize = (bytes) => bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(1)}MB` : `${Math.max(1, Math.round(bytes / 1024))}KB`, marketErrorCode = (error) => {
6020
+ const message = error instanceof Error ? error.message : String(error);
6021
+ return /^\[([a-z_]+)\]/.exec(message)?.[1] ?? null;
6022
+ }, stateMarks = (skill) => `${skill.installed.user ? "[u]" : ""}${skill.installed.project ? "[p]" : ""}${skill.installed.builtin ? "[b]" : ""}`, toItems = (skills) => {
6023
+ const groups = new Map;
6024
+ for (const skill of skills) {
6025
+ const list = groups.get(skill.category) ?? [];
6026
+ list.push(skill);
6027
+ groups.set(skill.category, list);
6028
+ }
6029
+ const items = [];
6030
+ for (const [category, members] of [...groups.entries()].sort((a, b) => b[1].length - a[1].length)) {
6031
+ items.push({ value: `header:${category}`, label: `${category} (${members.length})`, header: true });
6032
+ for (const skill of members) {
6033
+ items.push({
6034
+ value: skill.name,
6035
+ label: skill.name,
6036
+ description: skill.description,
6037
+ searchText: `${skill.source} ${skill.category}`,
6038
+ rightLabel: `${skill.source} ${skill.version} ${stateMarks(skill)}`.trim()
6039
+ });
6040
+ }
6041
+ }
6042
+ return items;
6043
+ };
6044
+ var init_skill_market_ui = __esm(() => {
6045
+ init_theme();
6046
+ CTX = BACKGROUND_CONTEXT4;
6047
+ });
6048
+
5033
6049
  // packages/core/src/session-display.ts
5034
6050
  function relativeTime(at, now, language = "en") {
5035
6051
  const minutes = Math.floor(Math.max(0, now - at) / 60000);
@@ -5064,15 +6080,15 @@ ${new Date(messageTime(s)).toLocaleString("en-GB")}`
5064
6080
  var init_workspace2 = () => {};
5065
6081
 
5066
6082
  // packages/tui/src/tui.ts
5067
- import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
6083
+ import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT5 } from "@earendil-works/chord/context";
5068
6084
  import {
5069
6085
  Container as Container5,
5070
6086
  isKeyRelease as isKeyRelease5,
5071
- matchesKey as matchesKey5,
6087
+ matchesKey as matchesKey6,
5072
6088
  ProcessTerminal,
5073
6089
  ScrollView,
5074
6090
  Spacer as Spacer2,
5075
- Text as Text5,
6091
+ Text as Text6,
5076
6092
  TruncatedText,
5077
6093
  TuiAltScreen
5078
6094
  } from "@earendil-works/pi-tui";
@@ -5138,8 +6154,8 @@ class NekoTuiApp {
5138
6154
  this.#login = new LoginPanel({
5139
6155
  changed: () => this.#ui.requestRender(),
5140
6156
  closed: () => this.#ui.setFocus(this.#editor),
5141
- save: (provider, key) => this.#gateway.client.credentials.save({ provider, key }, BACKGROUND_CONTEXT4),
5142
- remove: (provider) => this.#gateway.client.credentials.remove({ provider }, BACKGROUND_CONTEXT4)
6157
+ save: (provider, key) => this.#gateway.client.credentials.save({ provider, key }, BACKGROUND_CONTEXT5),
6158
+ remove: (provider) => this.#gateway.client.credentials.remove({ provider }, BACKGROUND_CONTEXT5)
5143
6159
  });
5144
6160
  const complete = fileCompletion(this.#commands, options.cwd ?? process.cwd(), (text) => this.addNotice(text));
5145
6161
  const original = complete.applyCompletion.bind(complete);
@@ -5175,10 +6191,10 @@ class NekoTuiApp {
5175
6191
  this.#ui.addInputListener((data) => {
5176
6192
  if (isKeyRelease5(data))
5177
6193
  return { consume: true };
5178
- if (!matchesKey5(data, "escape") || this.#panel.open || this.#login.open || this.#busyText !== undefined || this.#lastView?.compaction?.active.length)
6194
+ if (!matchesKey6(data, "escape") || this.#panel.open || this.#login.open || this.#busyText !== undefined || this.#lastView?.compaction?.active.length)
5179
6195
  this.#editor.resetEscapeSequence();
5180
6196
  if (!usableSize(this.#terminal.columns, this.#terminal.rows)) {
5181
- if (matchesKey5(data, "ctrl+c"))
6197
+ if (matchesKey6(data, "ctrl+c"))
5182
6198
  this.exit();
5183
6199
  return { consume: true };
5184
6200
  }
@@ -5187,7 +6203,7 @@ class NekoTuiApp {
5187
6203
  }
5188
6204
  if (this.#sessionOperation)
5189
6205
  return { consume: true };
5190
- if (matchesKey5(data, process.platform === "win32" ? "alt+v" : "ctrl+v")) {
6206
+ if (matchesKey6(data, process.platform === "win32" ? "alt+v" : "ctrl+v")) {
5191
6207
  if (isClipboardShortcut(data))
5192
6208
  this.pasteClipboard();
5193
6209
  return { consume: true };
@@ -5204,26 +6220,26 @@ class NekoTuiApp {
5204
6220
  return { consume: true };
5205
6221
  }
5206
6222
  }
5207
- if (matchesKey5(data, "escape") && this.#editor.isShowingAutocomplete()) {
6223
+ if (matchesKey6(data, "escape") && this.#editor.isShowingAutocomplete()) {
5208
6224
  this.#editor.resetEscapeSequence();
5209
6225
  return;
5210
6226
  }
5211
- if (matchesKey5(data, "ctrl+c")) {
6227
+ if (matchesKey6(data, "ctrl+c")) {
5212
6228
  if (this.#busyText !== undefined)
5213
6229
  this.abort();
5214
6230
  else
5215
6231
  this.exit();
5216
6232
  return { consume: true };
5217
6233
  }
5218
- if (matchesKey5(data, "escape") && (this.#busyText !== undefined || this.#lastView?.compaction?.active.some((c) => c.reason === "manual" || c.blocking))) {
6234
+ if (matchesKey6(data, "escape") && (this.#busyText !== undefined || this.#lastView?.compaction?.active.some((c) => c.reason === "manual" || c.blocking))) {
5219
6235
  this.abort();
5220
6236
  return { consume: true };
5221
6237
  }
5222
- if (matchesKey5(data, "ctrl+s")) {
6238
+ if (matchesKey6(data, "ctrl+s")) {
5223
6239
  this.openSessionsOverlay();
5224
6240
  return { consume: true };
5225
6241
  }
5226
- if (this.#busyText !== undefined && matchesKey5(data, "alt+enter")) {
6242
+ if (this.#busyText !== undefined && matchesKey6(data, "alt+enter")) {
5227
6243
  this.submitFollowUp();
5228
6244
  return { consume: true };
5229
6245
  }
@@ -5285,7 +6301,7 @@ class NekoTuiApp {
5285
6301
  const chat = this.#chatFacade;
5286
6302
  const session = this.#sessionId;
5287
6303
  if (chat)
5288
- chat.compact({ instructions }, BACKGROUND_CONTEXT4).catch((e) => {
6304
+ chat.compact({ instructions }, BACKGROUND_CONTEXT5).catch((e) => {
5289
6305
  if (session === this.#sessionId) {
5290
6306
  this.#editor.setText(text);
5291
6307
  this.addNotice(messageOf(e), true);
@@ -5317,6 +6333,10 @@ class NekoTuiApp {
5317
6333
  this.openModelOverlay();
5318
6334
  return;
5319
6335
  }
6336
+ if (trimmed === "/skills") {
6337
+ this.openSkillsOverlay();
6338
+ return;
6339
+ }
5320
6340
  if (trimmed.startsWith("/think")) {
5321
6341
  this.configureThinking(trimmed.slice("/think".length).trim());
5322
6342
  return;
@@ -5402,14 +6422,14 @@ class NekoTuiApp {
5402
6422
  for (const [ref, promise] of refs) {
5403
6423
  const file = await promise;
5404
6424
  ids.push(await uploadImage(file, {
5405
- begin: (i) => chat.beginImage(i, BACKGROUND_CONTEXT4),
5406
- append: (i) => chat.appendImage(i, BACKGROUND_CONTEXT4),
5407
- finish: (id) => chat.finishImage(id, BACKGROUND_CONTEXT4),
5408
- release: (id) => chat.releaseImage(id, BACKGROUND_CONTEXT4)
6425
+ begin: (i) => chat.beginImage(i, BACKGROUND_CONTEXT5),
6426
+ append: (i) => chat.appendImage(i, BACKGROUND_CONTEXT5),
6427
+ finish: (id) => chat.finishImage(id, BACKGROUND_CONTEXT5),
6428
+ release: (id) => chat.releaseImage(id, BACKGROUND_CONTEXT5)
5409
6429
  }));
5410
6430
  content = content.replaceAll(ref, "");
5411
6431
  }
5412
- await chat.submit({ content: content.trim(), images: ids, requestId, ...whenBusy ? { whenBusy } : {} }, BACKGROUND_CONTEXT4);
6432
+ await chat.submit({ content: content.trim(), images: ids, requestId, ...whenBusy ? { whenBusy } : {} }, BACKGROUND_CONTEXT5);
5413
6433
  if (session === this.#sessionId && this.#editor.getText() === before)
5414
6434
  this.#editor.setText("");
5415
6435
  if (this.#texts.get(session) === before)
@@ -5424,7 +6444,7 @@ class NekoTuiApp {
5424
6444
  this.addNotice(messageOf(e), true);
5425
6445
  } finally {
5426
6446
  for (const id of ids)
5427
- await chat.releaseImage(id, BACKGROUND_CONTEXT4).catch(() => {});
6447
+ await chat.releaseImage(id, BACKGROUND_CONTEXT5).catch(() => {});
5428
6448
  this.#sending.delete(session);
5429
6449
  this.#ui.requestRender();
5430
6450
  }
@@ -5433,7 +6453,7 @@ class NekoTuiApp {
5433
6453
  const chat = this.#chatFacade;
5434
6454
  if (chat === undefined)
5435
6455
  return;
5436
- chat.abort(BACKGROUND_CONTEXT4).catch((error) => this.addNotice(messageOf(error), true));
6456
+ chat.abort(BACKGROUND_CONTEXT5).catch((error) => this.addNotice(messageOf(error), true));
5437
6457
  }
5438
6458
  exit() {
5439
6459
  if (this.#exitStarted)
@@ -5464,7 +6484,7 @@ class NekoTuiApp {
5464
6484
  async#refreshSkills(facade) {
5465
6485
  this.#commands.splice(0, this.#commands.length, ...commands);
5466
6486
  try {
5467
- const skills = await facade.skills(BACKGROUND_CONTEXT4);
6487
+ const skills = await facade.skills(BACKGROUND_CONTEXT5);
5468
6488
  if (facade !== this.#chatFacade)
5469
6489
  return;
5470
6490
  for (const skill of skills)
@@ -5484,16 +6504,16 @@ class NekoTuiApp {
5484
6504
  }
5485
6505
  }
5486
6506
  async createSession() {
5487
- const created = await this.#gateway.client.management.create({ ...this.#options.cwd !== undefined ? { cwd: this.#options.cwd } : {} }, BACKGROUND_CONTEXT4);
6507
+ const created = await this.#gateway.client.management.create({ ...this.#options.cwd !== undefined ? { cwd: this.#options.cwd } : {} }, BACKGROUND_CONTEXT5);
5488
6508
  await this.switchSession(created.id);
5489
6509
  }
5490
6510
  async forkSession() {
5491
- const forked = await this.#gateway.client.management.fork(this.#sessionId, BACKGROUND_CONTEXT4);
6511
+ const forked = await this.#gateway.client.management.fork(this.#sessionId, BACKGROUND_CONTEXT5);
5492
6512
  await this.switchSession(forked.id);
5493
6513
  }
5494
6514
  async archiveSession() {
5495
6515
  const current = this.#sessionId;
5496
- await this.#gateway.client.management.archive(current, BACKGROUND_CONTEXT4);
6516
+ await this.#gateway.client.management.archive(current, BACKGROUND_CONTEXT5);
5497
6517
  const next = recentlyVisited(this.#gateway.client.directory.state.value?.sessions ?? [], this.#options.cwd).find((s) => s.id !== current);
5498
6518
  if (next)
5499
6519
  await this.switchSession(next.id);
@@ -5518,6 +6538,14 @@ class NekoTuiApp {
5518
6538
  this.#sessionPanelTimer = setInterval(() => this.#panel.update(this.sessionItems()), 60000);
5519
6539
  this.#ui.setFocus(this.#panel);
5520
6540
  }
6541
+ async openSkillsOverlay() {
6542
+ if (this.#exitStarted || this.#panel.open || this.#login.open)
6543
+ return;
6544
+ const workspace = this.#options.cwd ?? process.cwd();
6545
+ const ready = openSkillMarket(this.#panel, this.#gateway.client, workspace);
6546
+ this.#ui.setFocus(this.#panel);
6547
+ await ready;
6548
+ }
5521
6549
  async openModelOverlay() {
5522
6550
  if (this.#exitStarted || this.#panel.open || this.#login.open)
5523
6551
  return;
@@ -5535,7 +6563,7 @@ class NekoTuiApp {
5535
6563
  }, async (value) => {
5536
6564
  const selected = models.find((m) => keyOf(m) === value);
5537
6565
  if (selected && value !== currentKey)
5538
- await chat?.configure({ model: { provider: selected.provider, modelId: selected.modelId } }, BACKGROUND_CONTEXT4);
6566
+ await chat?.configure({ model: { provider: selected.provider, modelId: selected.modelId } }, BACKGROUND_CONTEXT5);
5539
6567
  });
5540
6568
  this.#ui.setFocus(this.#panel);
5541
6569
  await ready;
@@ -5557,14 +6585,14 @@ class NekoTuiApp {
5557
6585
  this.addNotice(`Available levels: ${model.supportedThinkingLevels.join(" / ")}`, true);
5558
6586
  return;
5559
6587
  }
5560
- await chat.configure({ thinkingLevel: level }, BACKGROUND_CONTEXT4);
6588
+ await chat.configure({ thinkingLevel: level }, BACKGROUND_CONTEXT5);
5561
6589
  } else {
5562
6590
  this.#thinkingModel = keyOf(model);
5563
6591
  this.#panel.show("Thinking level", async () => model.supportedThinkingLevels.map((level) => ({
5564
6592
  value: level,
5565
6593
  label: `${level}${level === this.#lastView?.agent.thinkingLevel ? " (Current)" : ""}`
5566
6594
  })), async (value) => {
5567
- await chat.configure({ thinkingLevel: value }, BACKGROUND_CONTEXT4);
6595
+ await chat.configure({ thinkingLevel: value }, BACKGROUND_CONTEXT5);
5568
6596
  });
5569
6597
  this.#ui.setFocus(this.#panel);
5570
6598
  }
@@ -5676,7 +6704,7 @@ class NekoTuiApp {
5676
6704
  if (!chat)
5677
6705
  return;
5678
6706
  for (const image of images) {
5679
- const component = new HistoryImage(image, () => downloadImage((offset) => chat.getImage({ entryId: image.entryId, contentIndex: image.contentIndex, offset }, BACKGROUND_CONTEXT4)), () => {
6707
+ const component = new HistoryImage(image, () => downloadImage((offset) => chat.getImage({ entryId: image.entryId, contentIndex: image.contentIndex, offset }, BACKGROUND_CONTEXT5)), () => {
5680
6708
  if (session === this.#sessionId)
5681
6709
  this.#ui.requestRender();
5682
6710
  });
@@ -5712,7 +6740,7 @@ class NekoTuiApp {
5712
6740
  this.#items.set(item.key, { kind: "tool", component });
5713
6741
  return;
5714
6742
  }
5715
- const component = new Text5(theme.dim(item.text), 1, 0);
6743
+ const component = new Text6(theme.dim(item.text), 1, 0);
5716
6744
  this.#chat.addChild(component);
5717
6745
  this.#items.set(item.key, { kind: "system", component });
5718
6746
  }
@@ -5876,7 +6904,7 @@ async function initialSessionId(gateway, cwd) {
5876
6904
  const first = recentlyVisited(sessions, cwd)[0];
5877
6905
  if (first !== undefined)
5878
6906
  return first.id;
5879
- return (await gateway.client.management.create({ ...cwd !== undefined ? { cwd } : {} }, BACKGROUND_CONTEXT4)).id;
6907
+ return (await gateway.client.management.create({ ...cwd !== undefined ? { cwd } : {} }, BACKGROUND_CONTEXT5)).id;
5880
6908
  }
5881
6909
  var EXIT_COMMANDS, SESSIONS_COMMANDS, NEW_SESSION_VALUE = "__new__";
5882
6910
  var init_tui = __esm(() => {
@@ -5894,6 +6922,7 @@ var init_tui = __esm(() => {
5894
6922
  init_image_input();
5895
6923
  init_labels();
5896
6924
  init_layout();
6925
+ init_skill_market_ui();
5897
6926
  init_theme();
5898
6927
  init_workspace2();
5899
6928
  EXIT_COMMANDS = new Set(["/exit", "/quit"]);
@@ -6011,11 +7040,11 @@ var init_open_browser = () => {};
6011
7040
 
6012
7041
  // packages/web/src/gateway-spawn.ts
6013
7042
  import { spawn as spawn3 } from "child_process";
6014
- import { randomUUID as randomUUID2 } from "crypto";
6015
- import { mkdir as mkdir3, readFile as readFile7, writeFile as writeFile3 } from "fs/promises";
6016
- import { dirname as dirname8, join as join11 } from "path";
7043
+ import { randomUUID as randomUUID3 } from "crypto";
7044
+ import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
7045
+ import { dirname as dirname9, join as join12 } from "path";
6017
7046
  async function ensureNekoGateway(options) {
6018
- const nekoDir = dirname8(options.storagePath);
7047
+ const nekoDir = dirname9(options.storagePath);
6019
7048
  const serverId = options.serverId ?? await loadOrCreateServerId2(nekoDir);
6020
7049
  if (process.platform === "win32") {
6021
7050
  const discovered = await readGatewayPortFile(nekoDir, serverId);
@@ -6054,7 +7083,7 @@ async function ensureNekoGateway(options) {
6054
7083
  throw error;
6055
7084
  }
6056
7085
  }
6057
- const socketPath = options.socketPath ?? join11(nekoDir, `${serverId}.sock`);
7086
+ const socketPath = options.socketPath ?? join12(nekoDir, `${serverId}.sock`);
6058
7087
  const host = options.host ?? "127.0.0.1";
6059
7088
  const port = options.port ?? 8321;
6060
7089
  if (await probeGateway(socketPath, serverId)) {
@@ -6157,15 +7186,15 @@ function makeHandle(serverId, socketPath, wsUrl, spawned, nekoDir) {
6157
7186
  };
6158
7187
  }
6159
7188
  async function loadOrCreateServerId2(nekoDir) {
6160
- const file = join11(nekoDir, SERVER_ID_FILENAME3);
7189
+ const file = join12(nekoDir, SERVER_ID_FILENAME3);
6161
7190
  try {
6162
7191
  const existing = (await readFile7(file, "utf8")).trim();
6163
7192
  if (existing !== "")
6164
7193
  return existing;
6165
7194
  } catch {}
6166
- const created = randomUUID2();
6167
- await mkdir3(nekoDir, { recursive: true, mode: 448 });
6168
- await writeFile3(file, `${created}
7195
+ const created = randomUUID3();
7196
+ await mkdir4(nekoDir, { recursive: true, mode: 448 });
7197
+ await writeFile4(file, `${created}
6169
7198
  `, { mode: 384 });
6170
7199
  return created;
6171
7200
  }
@@ -6179,10 +7208,10 @@ var init_gateway_spawn = __esm(() => {
6179
7208
 
6180
7209
  // packages/web/src/server.ts
6181
7210
  import { createReadStream } from "fs";
6182
- import { stat as stat4 } from "fs/promises";
7211
+ import { stat as stat5 } from "fs/promises";
6183
7212
  import { createServer } from "http";
6184
7213
  import { extname as extname2, resolve as resolve6, sep as sep2 } from "path";
6185
- import { fileURLToPath as fileURLToPath2 } from "url";
7214
+ import { fileURLToPath as fileURLToPath3 } from "url";
6186
7215
  async function startWebServer(options = {}) {
6187
7216
  const staticDir = resolve6(options.staticDir ?? DEFAULT_STATIC_DIR);
6188
7217
  const workspaceRoot = normalizeWorkspace(options.gateway?.cwd ?? process.cwd());
@@ -6245,18 +7274,18 @@ async function serveStatic(pathname, method, response, staticDir) {
6245
7274
  return;
6246
7275
  }
6247
7276
  let file = candidate;
6248
- let stats = await stat4(file).catch(() => {
7277
+ let stats = await stat5(file).catch(() => {
6249
7278
  return;
6250
7279
  });
6251
7280
  if (stats?.isDirectory()) {
6252
7281
  file = resolve6(file, "index.html");
6253
- stats = await stat4(file).catch(() => {
7282
+ stats = await stat5(file).catch(() => {
6254
7283
  return;
6255
7284
  });
6256
7285
  }
6257
7286
  if (stats === undefined && extname2(candidate) === "") {
6258
7287
  file = resolve6(staticDir, "index.html");
6259
- stats = await stat4(file).catch(() => {
7288
+ stats = await stat5(file).catch(() => {
6260
7289
  return;
6261
7290
  });
6262
7291
  }
@@ -6281,7 +7310,7 @@ var DEFAULT_PORT2 = 8322, DEFAULT_HOST2 = "127.0.0.1", DEFAULT_STATIC_DIR, MIME;
6281
7310
  var init_server = __esm(() => {
6282
7311
  init_src();
6283
7312
  init_gateway_spawn();
6284
- DEFAULT_STATIC_DIR = fileURLToPath2(new URL("./client", import.meta.url));
7313
+ DEFAULT_STATIC_DIR = fileURLToPath3(new URL("./client", import.meta.url));
6285
7314
  MIME = {
6286
7315
  ".html": "text/html; charset=utf-8",
6287
7316
  ".js": "text/javascript; charset=utf-8",
@@ -6615,7 +7644,7 @@ switch (command2) {
6615
7644
  }
6616
7645
  case "--version":
6617
7646
  case "-v":
6618
- console.log("0.2.1");
7647
+ console.log("0.2.3");
6619
7648
  break;
6620
7649
  case undefined:
6621
7650
  case "-h":