neko-harness 0.2.2 → 0.2.4

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";
@@ -1088,10 +1098,19 @@ var init_prompt = __esm(() => {
1088
1098
  });
1089
1099
 
1090
1100
  // packages/core/src/prompt/resources.ts
1101
+ import { existsSync as existsSync2 } from "fs";
1091
1102
  import { readFile as readFile3 } from "fs/promises";
1092
1103
  import { homedir as homedir3 } from "os";
1093
1104
  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) => {
1105
+ import { fileURLToPath } from "url";
1106
+ 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"), resolveBuiltinSkillsDir = () => {
1107
+ for (const url of [new URL("./skills/", import.meta.url), new URL("../../skills/", import.meta.url)]) {
1108
+ const path = fileURLToPath(url);
1109
+ if (existsSync2(join4(path, "skill-creator", "SKILL.md")))
1110
+ return path;
1111
+ }
1112
+ return null;
1113
+ }, BUILTIN_SKILLS_DIR, readResources = async (workspace, home, builtinDir, report, invocationName) => {
1095
1114
  const contextFiles = [];
1096
1115
  for (const path of [userAgentsFile(home), workspaceAgentsFile(workspace)]) {
1097
1116
  try {
@@ -1112,11 +1131,13 @@ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDi
1112
1131
  if (skill.name === invocationName)
1113
1132
  candidates.set(skill.filePath, content);
1114
1133
  } : undefined;
1115
- const [user, project] = await Promise.all([
1116
- scanSkillsDir(userSkillsDir(home), report, capture),
1117
- scanSkillsDir(workspaceSkillsDir(workspace), report, capture)
1134
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report, capture)).map((skill) => ({ ...skill, level }));
1135
+ const [builtin, user, project] = await Promise.all([
1136
+ builtinDir === null ? Promise.resolve([]) : scan(builtinDir, "builtin"),
1137
+ scan(userSkillsDir(home), "user"),
1138
+ scan(workspaceSkillsDir(workspace), "project")
1118
1139
  ]);
1119
- const skills = mergeSkills([user, project]);
1140
+ const skills = mergeSkills([builtin, user, project]);
1120
1141
  const winner = invocationName === undefined ? undefined : skills.find((skill) => skill.name === invocationName);
1121
1142
  if (winner && candidates) {
1122
1143
  const content = candidates.get(winner.filePath);
@@ -1124,11 +1145,12 @@ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDi
1124
1145
  return { contextFiles, skills, invocation: { filePath: winner.filePath, content } };
1125
1146
  }
1126
1147
  return { contextFiles, skills };
1127
- }, createResourceLoader = (home = homedir3(), report = warnPromptDiagnostic) => {
1148
+ }, createResourceLoader = (home = homedir3(), report = warnPromptDiagnostic, options) => {
1149
+ const builtinDir = options?.builtinDir === undefined ? BUILTIN_SKILLS_DIR : options.builtinDir;
1128
1150
  const previous = new Map;
1129
1151
  return async (workspace, invocationName) => {
1130
1152
  const diagnostics = [];
1131
- const resources = await readResources(workspace, home, (d) => diagnostics.push(d), invocationName);
1153
+ const resources = await readResources(workspace, home, builtinDir, (d) => diagnostics.push(d), invocationName);
1132
1154
  const known = previous.get(workspace);
1133
1155
  const current = new Set;
1134
1156
  for (const diagnostic of diagnostics) {
@@ -1143,9 +1165,18 @@ var userAgentsFile = (home) => join4(home, ".agents", "AGENTS.md"), userSkillsDi
1143
1165
  previous.delete(workspace);
1144
1166
  return resources;
1145
1167
  };
1168
+ }, scanSkillsByLevel = async (workspace, home = homedir3(), builtinDir = BUILTIN_SKILLS_DIR, report = warnPromptDiagnostic) => {
1169
+ const scan = async (dir, level) => (await scanSkillsDir(dir, report)).map((skill) => ({ ...skill, level }));
1170
+ const [builtin, user, project] = await Promise.all([
1171
+ builtinDir === null ? [] : scan(builtinDir, "builtin"),
1172
+ scan(userSkillsDir(home), "user"),
1173
+ scan(workspaceSkillsDir(workspace), "project")
1174
+ ]);
1175
+ return [...project, ...user, ...builtin];
1146
1176
  };
1147
1177
  var init_resources = __esm(() => {
1148
1178
  init_skills();
1179
+ BUILTIN_SKILLS_DIR = resolveBuiltinSkillsDir();
1149
1180
  });
1150
1181
 
1151
1182
  // packages/core/src/prompt/snapshots.ts
@@ -2313,7 +2344,21 @@ async function createNekoRuntime(options = {}) {
2313
2344
  const session = await sessionFor(id);
2314
2345
  const agent = await session.conversation.agent(context);
2315
2346
  const { skills } = await loadResources(savedWorkspace(agent.cwd));
2316
- return skills.map((skill) => ({ name: skill.name, description: skill.description, location: skill.filePath }));
2347
+ return skills.map((skill) => ({
2348
+ name: skill.name,
2349
+ description: skill.description,
2350
+ location: skill.filePath,
2351
+ level: skill.level
2352
+ }));
2353
+ },
2354
+ async listSkillsByLevel(cwd) {
2355
+ const skills = await scanSkillsByLevel(normalizeWorkspace(cwd ?? defaultCwd), options.promptHome ?? homedir4());
2356
+ return skills.map((skill) => ({
2357
+ name: skill.name,
2358
+ description: skill.description,
2359
+ location: skill.filePath,
2360
+ level: skill.level
2361
+ }));
2317
2362
  },
2318
2363
  installExtension(extension) {
2319
2364
  registry.install(extension);
@@ -2367,6 +2412,27 @@ var init_runtime = __esm(() => {
2367
2412
  });
2368
2413
  });
2369
2414
 
2415
+ // packages/core/src/skill-invocation.ts
2416
+ var unescapeXml = (value) => value.replace(/&apos;/g, "'").replace(/&quot;/g, '"').replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&"), parseSkillInvocation = (text) => {
2417
+ const open = /^<skill name="([^"]*)" location="([^"]*)">/.exec(text);
2418
+ if (!open)
2419
+ return null;
2420
+ const close = text.lastIndexOf("</skill>");
2421
+ if (close < open[0].length)
2422
+ return null;
2423
+ const rest = text.slice(close + "</skill>".length);
2424
+ if (rest !== "" && !rest.startsWith(`
2425
+
2426
+ `))
2427
+ return null;
2428
+ return {
2429
+ name: unescapeXml(open[1]),
2430
+ location: unescapeXml(open[2]),
2431
+ body: text.slice(open[0].length, close),
2432
+ args: rest.slice(2)
2433
+ };
2434
+ };
2435
+
2370
2436
  // packages/core/src/index.ts
2371
2437
  var init_src = __esm(() => {
2372
2438
  init_compaction();
@@ -2375,6 +2441,7 @@ var init_src = __esm(() => {
2375
2441
  init_image_metadata();
2376
2442
  init_images();
2377
2443
  init_projection();
2444
+ init_frontmatter();
2378
2445
  init_runtime();
2379
2446
  init_session_metadata();
2380
2447
  init_workspace();
@@ -2770,7 +2837,8 @@ var init_labels = __esm(() => {
2770
2837
  { name: "model", description: "Choose a model" },
2771
2838
  { name: "think", description: "Set thinking level (/think high)" },
2772
2839
  { name: "fork", description: "Fork this session" },
2773
- { name: "archive", description: "Archive this session" }
2840
+ { name: "archive", description: "Archive this session" },
2841
+ { name: "skills", description: "Browse and install skills from the market" }
2774
2842
  ];
2775
2843
  labels = {
2776
2844
  ready: "Ready",
@@ -3174,17 +3242,62 @@ class PickerPanel {
3174
3242
  }
3175
3243
  close() {
3176
3244
  ++this.#generation;
3245
+ this.#details = [];
3177
3246
  this.#state = "closed";
3178
3247
  this.closed();
3179
3248
  this.changed();
3180
3249
  }
3250
+ showDetail(detail) {
3251
+ if (!this.open)
3252
+ return;
3253
+ this.#details.push(detail);
3254
+ this.changed();
3255
+ }
3256
+ popDetail() {
3257
+ if (this.#details.length === 0)
3258
+ return;
3259
+ this.#details.pop();
3260
+ this.changed();
3261
+ }
3262
+ get detailOpen() {
3263
+ return this.#details.length > 0;
3264
+ }
3265
+ get #detail() {
3266
+ return this.#details[this.#details.length - 1];
3267
+ }
3268
+ #details = [];
3181
3269
  #filtered() {
3182
3270
  const query = this.search.getValue().toLowerCase();
3183
- return this.#items.filter((item) => `${item.label} ${item.description ?? ""} ${item.searchText ?? ""}`.toLowerCase().includes(query));
3271
+ if (query !== "")
3272
+ return this.#items.filter((item) => !item.header && this.#matches(item, query));
3273
+ return this.#items;
3274
+ }
3275
+ #matches(item, query) {
3276
+ return `${item.label} ${item.description ?? ""} ${item.searchText ?? ""}`.toLowerCase().includes(query);
3277
+ }
3278
+ #firstSelectable(items) {
3279
+ return items.findIndex((item) => !item.header);
3280
+ }
3281
+ #move(delta) {
3282
+ const items = this.#filtered();
3283
+ let next = this.#index + delta;
3284
+ while (next >= 0 && next < items.length && items[next]?.header)
3285
+ next += delta;
3286
+ this.#index = Math.max(0, Math.min(Math.max(0, items.length - 1), next));
3287
+ if (items[this.#index]?.header)
3288
+ this.#index = this.#firstSelectable(items);
3184
3289
  }
3185
3290
  handleInput(data) {
3186
3291
  if (isKeyRelease3(data))
3187
3292
  return;
3293
+ if (this.#detail !== undefined) {
3294
+ if (matchesKey3(data, "escape") || matchesKey3(data, "ctrl+c")) {
3295
+ this.popDetail();
3296
+ } else if (this.#detail.handleKey(data)) {
3297
+ this.changed();
3298
+ }
3299
+ return;
3300
+ }
3188
3301
  if (matchesKey3(data, "escape") || matchesKey3(data, "ctrl+c")) {
3189
3302
  this.close();
3190
3303
  return;
@@ -3198,18 +3311,25 @@ class PickerPanel {
3198
3311
  return;
3199
3312
  const items = this.#filtered();
3200
3313
  if (matchesKey3(data, "up"))
3201
- this.#index = Math.max(0, this.#index - 1);
3314
+ this.#move(-1);
3202
3315
  else if (matchesKey3(data, "down"))
3203
- this.#index = Math.min(Math.max(0, items.length - 1), this.#index + 1);
3316
+ this.#move(1);
3204
3317
  else if (matchesKey3(data, "enter")) {
3205
3318
  const item = items[this.#index];
3206
- if (!item)
3319
+ if (!item || item.header)
3207
3320
  return;
3208
3321
  const generation = this.#generation;
3209
3322
  this.#state = "applying";
3210
- this.#pick(item.value).then(() => {
3211
- if (generation === this.#generation)
3212
- this.close();
3323
+ this.#pick(item.value).then((result) => {
3324
+ if (generation !== this.#generation)
3325
+ return;
3326
+ if (result !== undefined && typeof result === "object" && "lines" in result) {
3327
+ this.#state = "ready";
3328
+ this.showDetail(result);
3329
+ this.changed();
3330
+ return;
3331
+ }
3332
+ this.close();
3213
3333
  }, (error) => {
3214
3334
  if (generation !== this.#generation)
3215
3335
  return;
@@ -3219,7 +3339,7 @@ class PickerPanel {
3219
3339
  });
3220
3340
  } else {
3221
3341
  this.search.handleInput(data);
3222
- this.#index = 0;
3342
+ this.#index = this.#firstSelectable(this.#filtered());
3223
3343
  }
3224
3344
  this.changed();
3225
3345
  }
@@ -3227,6 +3347,15 @@ class PickerPanel {
3227
3347
  const height = Math.max(1, this.height);
3228
3348
  const w = Math.max(1, width - 2);
3229
3349
  const rows = [style.bold(this.#title)];
3350
+ if (this.#detail !== undefined) {
3351
+ const body = this.#detail.lines(w, Math.max(1, height - 2));
3352
+ rows.push(...body);
3353
+ while (rows.length < height - 1)
3354
+ rows.push("");
3355
+ if (height > 1)
3356
+ rows.push(this.#detail.help);
3357
+ return rows.slice(0, height).map((line) => theme.panel(` ${truncateToWidth3(line, w, "...", true)} `));
3358
+ }
3230
3359
  if (height >= 4)
3231
3360
  rows.push(...this.search.render(w).slice(0, 1));
3232
3361
  const help = this.#state === "error" ? labels.retry : this.#state === "ready" ? labels.select : labels.close;
@@ -3236,11 +3365,20 @@ class PickerPanel {
3236
3365
  else {
3237
3366
  const items = this.#filtered();
3238
3367
  this.#index = Math.min(this.#index, Math.max(0, items.length - 1));
3368
+ if (items[this.#index]?.header) {
3369
+ const first = this.#firstSelectable(items);
3370
+ if (first >= 0)
3371
+ this.#index = first;
3372
+ }
3239
3373
  const selected = items[this.#index];
3240
3374
  const details = height >= 9 && selected?.description ? wrapTextWithAnsi(selected.description, w).slice(0, 2) : [];
3241
3375
  const count = Math.max(1, available - details.length);
3242
3376
  const start = Math.max(0, Math.min(this.#index - Math.floor(count / 2), items.length - count));
3243
3377
  for (const [i, item] of items.slice(start, start + count).entries()) {
3378
+ if (item.header) {
3379
+ rows.push(theme.dim(` ${item.label}`));
3380
+ continue;
3381
+ }
3244
3382
  const right = item.rightLabel ?? "";
3245
3383
  const leftWidth = Math.max(1, w - 3 - visibleWidth2(right));
3246
3384
  const left = truncateToWidth3(item.label, leftWidth, "...");
@@ -3341,7 +3479,7 @@ ${theme.error(state.note)}`;
3341
3479
  });
3342
3480
 
3343
3481
  // packages/tui/src/components/user-message.ts
3344
- import { Container as Container4, Markdown as Markdown2 } from "@earendil-works/pi-tui";
3482
+ import { Container as Container4, Markdown as Markdown2, Text as Text4 } from "@earendil-works/pi-tui";
3345
3483
  var UserMessageComponent;
3346
3484
  var init_user_message = __esm(() => {
3347
3485
  init_theme();
@@ -3358,6 +3496,16 @@ var init_user_message = __esm(() => {
3358
3496
  }
3359
3497
  #rebuild() {
3360
3498
  this.clear();
3499
+ const invocation = parseSkillInvocation(this.#text);
3500
+ if (invocation !== null) {
3501
+ const args = invocation.args.replace(/\s+/g, " ").trim();
3502
+ const preview = args.length > 100 ? `${args.slice(0, 99)}\u2026` : args;
3503
+ const head = `${theme.toolTitle(`\u2726 skill:${invocation.name}`)}${preview ? ` ${theme.userText(preview)}` : ""}`;
3504
+ const source = theme.dim(`skill body from ${invocation.location} (collapsed)`);
3505
+ this.addChild(new Text4(`${head}
3506
+ ${source}`, 1, 0, theme.userBg));
3507
+ return;
3508
+ }
3361
3509
  this.addChild(new Markdown2(this.#text, 1, 0, getMarkdownTheme(), {
3362
3510
  color: theme.userText,
3363
3511
  bgColor: theme.userBg
@@ -3621,7 +3769,7 @@ var init_file_completion = __esm(() => {
3621
3769
 
3622
3770
  // packages/gateway/src/contract.ts
3623
3771
  import { defineService } from "@earendil-works/chord";
3624
- var NekoSessionDirectory, NekoSessionManagement, NekoModels, NekoChat, NekoWorkspaces, NekoCredentials, NekoWorkspaceFiles;
3772
+ var NekoSessionDirectory, NekoSessionManagement, NekoModels, NekoChat, NekoWorkspaces, NekoCredentials, NekoWorkspaceFiles, NekoSkillMarket;
3625
3773
  var init_contract = __esm(() => {
3626
3774
  NekoSessionDirectory = defineService("neko.session-directory");
3627
3775
  NekoSessionManagement = defineService("neko.session-management");
@@ -3630,6 +3778,7 @@ var init_contract = __esm(() => {
3630
3778
  NekoWorkspaces = defineService("neko.workspaces");
3631
3779
  NekoCredentials = defineService("neko.credentials");
3632
3780
  NekoWorkspaceFiles = defineService("neko.workspace-files");
3781
+ NekoSkillMarket = defineService("neko.skill-market");
3633
3782
  });
3634
3783
 
3635
3784
  // packages/gateway/src/ws-listener.ts
@@ -3782,7 +3931,8 @@ async function connectNekoGateway(address) {
3782
3931
  NekoModels,
3783
3932
  NekoWorkspaces,
3784
3933
  NekoWorkspaceFiles,
3785
- NekoCredentials
3934
+ NekoCredentials,
3935
+ NekoSkillMarket
3786
3936
  ],
3787
3937
  transport: serverTransport
3788
3938
  });
@@ -3798,6 +3948,7 @@ async function connectNekoGateway(address) {
3798
3948
  workspaces: serverBinding.use(NekoWorkspaces),
3799
3949
  workspaceFiles: serverBinding.use(NekoWorkspaceFiles),
3800
3950
  credentials: serverBinding.use(NekoCredentials),
3951
+ skillMarket: serverBinding.use(NekoSkillMarket),
3801
3952
  listModels: () => models.list(BACKGROUND_CONTEXT2),
3802
3953
  async attach(sessionId) {
3803
3954
  await sessionBinding?.dispose(BACKGROUND_CONTEXT2).catch(() => {});
@@ -3913,10 +4064,617 @@ function fileSearchInstallHelp(platform = process.platform) {
3913
4064
  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
4065
  }
3915
4066
 
4067
+ // node_modules/fflate/esm/index.mjs
4068
+ import { createRequire } from "module";
4069
+ function inflateSync(data, opts) {
4070
+ return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
4071
+ }
4072
+ function strFromU8(dat, latin1) {
4073
+ if (latin1) {
4074
+ var r = "";
4075
+ for (var i = 0;i < dat.length; i += 16384)
4076
+ r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));
4077
+ return r;
4078
+ } else if (td) {
4079
+ return td.decode(dat);
4080
+ } else {
4081
+ var _a = dutf8(dat), { s, r } = _a;
4082
+ if (r.length)
4083
+ err(8);
4084
+ return s;
4085
+ }
4086
+ }
4087
+ function unzipSync(data, opts) {
4088
+ var files = {};
4089
+ var e = data.length - 22;
4090
+ for (;b4(data, e) != 101010256; --e) {
4091
+ if (!e || data.length - e > 65558)
4092
+ err(13);
4093
+ }
4094
+ var c = b2(data, e + 8);
4095
+ if (!c)
4096
+ return {};
4097
+ var o = b4(data, e + 16);
4098
+ var z = b4(data, e - 20) == 117853008;
4099
+ if (z) {
4100
+ var ze = b4(data, e - 12);
4101
+ z = b4(data, ze) == 101075792;
4102
+ if (z) {
4103
+ c = b4(data, ze + 32);
4104
+ o = b4(data, ze + 48);
4105
+ }
4106
+ }
4107
+ var fltr = opts && opts.filter;
4108
+ for (var i = 0;i < c; ++i) {
4109
+ 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);
4110
+ o = no;
4111
+ if (!fltr || fltr({
4112
+ name: fn,
4113
+ size: sc,
4114
+ originalSize: su,
4115
+ compression: c_2
4116
+ })) {
4117
+ if (!c_2)
4118
+ files[fn] = slc(data, b, b + sc);
4119
+ else if (c_2 == 8)
4120
+ files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
4121
+ else
4122
+ err(14, "unknown compression type " + c_2);
4123
+ }
4124
+ }
4125
+ return files;
4126
+ }
4127
+ var require2, _a, Worker, isMarkedAsUntransferable, u8, u16, i32, fleb, fdeb, clim, freb = function(eb, start) {
4128
+ var b = new u16(31);
4129
+ for (var i = 0;i < 31; ++i) {
4130
+ b[i] = start += 1 << eb[i - 1];
4131
+ }
4132
+ var r = new i32(b[30]);
4133
+ for (var i = 1;i < 30; ++i) {
4134
+ for (var j = b[i];j < b[i + 1]; ++j) {
4135
+ r[j] = j - b[i] << 5 | i;
4136
+ }
4137
+ }
4138
+ return { b, r };
4139
+ }, _a, fl, revfl, _b, fd, revfd, rev, x, i, hMap = function(cd, mb, r) {
4140
+ var s = cd.length;
4141
+ var i = 0;
4142
+ var l = new u16(mb);
4143
+ for (;i < s; ++i) {
4144
+ if (cd[i])
4145
+ ++l[cd[i] - 1];
4146
+ }
4147
+ var le = new u16(mb);
4148
+ for (i = 1;i < mb; ++i) {
4149
+ le[i] = le[i - 1] + l[i - 1] << 1;
4150
+ }
4151
+ var co;
4152
+ if (r) {
4153
+ co = new u16(1 << mb);
4154
+ var rvb = 15 - mb;
4155
+ for (i = 0;i < s; ++i) {
4156
+ if (cd[i]) {
4157
+ var sv = i << 4 | cd[i];
4158
+ var r_1 = mb - cd[i];
4159
+ var v = le[cd[i] - 1]++ << r_1;
4160
+ for (var m = v | (1 << r_1) - 1;v <= m; ++v) {
4161
+ co[rev[v] >> rvb] = sv;
4162
+ }
4163
+ }
4164
+ }
4165
+ } else {
4166
+ co = new u16(s);
4167
+ for (i = 0;i < s; ++i) {
4168
+ if (cd[i]) {
4169
+ co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i];
4170
+ }
4171
+ }
4172
+ }
4173
+ return co;
4174
+ }, flt, i, i, i, i, fdt, i, flrm, fdrm, max = function(a) {
4175
+ var m = a[0];
4176
+ for (var i = 1;i < a.length; ++i) {
4177
+ if (a[i] > m)
4178
+ m = a[i];
4179
+ }
4180
+ return m;
4181
+ }, bits = function(d, p, m) {
4182
+ var o = p / 8 | 0;
4183
+ return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
4184
+ }, bits16 = function(d, p) {
4185
+ var o = p / 8 | 0;
4186
+ return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
4187
+ }, shft = function(p) {
4188
+ return (p + 7) / 8 | 0;
4189
+ }, slc = function(v, s, e) {
4190
+ if (s == null || s < 0)
4191
+ s = 0;
4192
+ if (e == null || e > v.length)
4193
+ e = v.length;
4194
+ return new u8(v.subarray(s, e));
4195
+ }, ec, err = function(ind, msg, nt) {
4196
+ var e = new Error(msg || ec[ind]);
4197
+ e.code = ind;
4198
+ if (Error.captureStackTrace)
4199
+ Error.captureStackTrace(e, err);
4200
+ if (!nt)
4201
+ throw e;
4202
+ return e;
4203
+ }, inflt = function(dat, st, buf, dict) {
4204
+ var sl = dat.length, dl = dict ? dict.length : 0;
4205
+ if (!sl || st.f && !st.l)
4206
+ return buf || new u8(0);
4207
+ var noBuf = !buf;
4208
+ var resize = noBuf || st.i != 2;
4209
+ var noSt = st.i;
4210
+ if (noBuf)
4211
+ buf = new u8(sl * 3);
4212
+ var cbuf = function(l) {
4213
+ var bl = buf.length;
4214
+ if (l > bl) {
4215
+ var nbuf = new u8(Math.max(bl * 2, l));
4216
+ nbuf.set(buf);
4217
+ buf = nbuf;
4218
+ }
4219
+ };
4220
+ var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, { l: lm, d: dm, m: lbt, n: dbt } = st;
4221
+ var tbts = sl * 8;
4222
+ do {
4223
+ if (!lm) {
4224
+ final = bits(dat, pos, 1);
4225
+ var type = bits(dat, pos + 1, 3);
4226
+ pos += 3;
4227
+ if (!type) {
4228
+ var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
4229
+ if (t > sl) {
4230
+ if (noSt)
4231
+ err(0);
4232
+ break;
4233
+ }
4234
+ if (resize)
4235
+ cbuf(bt + l);
4236
+ buf.set(dat.subarray(s, t), bt);
4237
+ st.b = bt += l, st.p = pos = t * 8, st.f = final;
4238
+ continue;
4239
+ } else if (type == 1)
4240
+ lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
4241
+ else if (type == 2) {
4242
+ var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
4243
+ var tl = hLit + bits(dat, pos + 5, 31) + 1;
4244
+ pos += 14;
4245
+ var ldt = new u8(tl);
4246
+ var clt = new u8(19);
4247
+ for (var i = 0;i < hcLen; ++i) {
4248
+ clt[clim[i]] = bits(dat, pos + i * 3, 7);
4249
+ }
4250
+ pos += hcLen * 3;
4251
+ var clb = max(clt), clbmsk = (1 << clb) - 1;
4252
+ var clm = hMap(clt, clb, 1);
4253
+ for (var i = 0;i < tl; ) {
4254
+ var r = clm[bits(dat, pos, clbmsk)];
4255
+ pos += r & 15;
4256
+ var s = r >> 4;
4257
+ if (s < 16) {
4258
+ ldt[i++] = s;
4259
+ } else {
4260
+ var c = 0, n = 0;
4261
+ if (s == 16)
4262
+ n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1];
4263
+ else if (s == 17)
4264
+ n = 3 + bits(dat, pos, 7), pos += 3;
4265
+ else if (s == 18)
4266
+ n = 11 + bits(dat, pos, 127), pos += 7;
4267
+ while (n--)
4268
+ ldt[i++] = c;
4269
+ }
4270
+ }
4271
+ var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
4272
+ lbt = max(lt);
4273
+ dbt = max(dt);
4274
+ lm = hMap(lt, lbt, 1);
4275
+ dm = hMap(dt, dbt, 1);
4276
+ } else
4277
+ err(1);
4278
+ if (pos > tbts) {
4279
+ if (noSt)
4280
+ err(0);
4281
+ break;
4282
+ }
4283
+ }
4284
+ if (resize)
4285
+ cbuf(bt + 131072);
4286
+ var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
4287
+ var lpos = pos;
4288
+ for (;; lpos = pos) {
4289
+ var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
4290
+ pos += c & 15;
4291
+ if (pos > tbts) {
4292
+ if (noSt)
4293
+ err(0);
4294
+ break;
4295
+ }
4296
+ if (!c)
4297
+ err(2);
4298
+ if (sym < 256)
4299
+ buf[bt++] = sym;
4300
+ else if (sym == 256) {
4301
+ lpos = pos, lm = null;
4302
+ break;
4303
+ } else {
4304
+ var add = sym - 254;
4305
+ if (sym > 264) {
4306
+ var i = sym - 257, b = fleb[i];
4307
+ add = bits(dat, pos, (1 << b) - 1) + fl[i];
4308
+ pos += b;
4309
+ }
4310
+ var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
4311
+ if (!d)
4312
+ err(3);
4313
+ pos += d & 15;
4314
+ var dt = fd[dsym];
4315
+ if (dsym > 3) {
4316
+ var b = fdeb[dsym];
4317
+ dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
4318
+ }
4319
+ if (pos > tbts) {
4320
+ if (noSt)
4321
+ err(0);
4322
+ break;
4323
+ }
4324
+ if (resize)
4325
+ cbuf(bt + 131072);
4326
+ var end = bt + add;
4327
+ if (bt < dt) {
4328
+ var shift = dl - dt, dend = Math.min(dt, end);
4329
+ if (shift + bt < 0)
4330
+ err(3);
4331
+ for (;bt < dend; ++bt)
4332
+ buf[bt] = dict[shift + bt];
4333
+ }
4334
+ for (;bt < end; ++bt)
4335
+ buf[bt] = buf[bt - dt];
4336
+ }
4337
+ }
4338
+ st.l = lm, st.p = lpos, st.b = bt, st.f = final;
4339
+ if (lm)
4340
+ final = 1, st.m = lbt, st.d = dm, st.n = dbt;
4341
+ } while (!final);
4342
+ return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
4343
+ }, et, b2 = function(d, b) {
4344
+ return d[b] | d[b + 1] << 8;
4345
+ }, b4 = function(d, b) {
4346
+ return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
4347
+ }, b8 = function(d, b) {
4348
+ return b4(d, b) + b4(d, b + 4) * 4294967296;
4349
+ }, td, tds = 0, dutf8 = function(d) {
4350
+ for (var r = "", i = 0;; ) {
4351
+ var c = d[i++];
4352
+ var eb = (c > 127) + (c > 223) + (c > 239);
4353
+ if (i + eb > d.length)
4354
+ return { s: r, r: slc(d, i - 1) };
4355
+ if (!eb)
4356
+ r += String.fromCharCode(c);
4357
+ else if (eb == 3) {
4358
+ 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);
4359
+ } else if (eb & 1)
4360
+ r += String.fromCharCode((c & 31) << 6 | d[i++] & 63);
4361
+ else
4362
+ r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63);
4363
+ }
4364
+ }, slzh = function(d, b) {
4365
+ return b + 30 + b2(d, b + 26) + b2(d, b + 28);
4366
+ }, zh = function(d, b, z) {
4367
+ 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;
4368
+ 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];
4369
+ return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off];
4370
+ }, z64hs = function(d, b, l, z, sc, su, off) {
4371
+ var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l;
4372
+ var nf = nsc + nsu + noff;
4373
+ if (z && nf) {
4374
+ for (;b + 4 < e; b += 4 + b2(d, b + 2)) {
4375
+ if (b2(d, b) == 1) {
4376
+ return [
4377
+ nsc ? b8(d, b + 4 + 8 * nsu) : sc,
4378
+ nsu ? b8(d, b + 4) : su,
4379
+ noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off,
4380
+ 1
4381
+ ];
4382
+ }
4383
+ }
4384
+ if (z < 2)
4385
+ err(13);
4386
+ }
4387
+ return [sc, su, off, 0];
4388
+ };
4389
+ var init_esm = __esm(() => {
4390
+ require2 = createRequire("/");
4391
+ try {
4392
+ _a = require2("worker_threads"), Worker = _a.Worker, isMarkedAsUntransferable = _a.isMarkedAsUntransferable;
4393
+ } catch (e) {}
4394
+ u8 = Uint8Array;
4395
+ u16 = Uint16Array;
4396
+ i32 = Int32Array;
4397
+ 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]);
4398
+ 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]);
4399
+ clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
4400
+ _a = freb(fleb, 2);
4401
+ fl = _a.b;
4402
+ revfl = _a.r;
4403
+ fl[28] = 258, revfl[258] = 28;
4404
+ _b = freb(fdeb, 0);
4405
+ fd = _b.b;
4406
+ revfd = _b.r;
4407
+ rev = new u16(32768);
4408
+ for (i = 0;i < 32768; ++i) {
4409
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
4410
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
4411
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
4412
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
4413
+ }
4414
+ flt = new u8(288);
4415
+ for (i = 0;i < 144; ++i)
4416
+ flt[i] = 8;
4417
+ for (i = 144;i < 256; ++i)
4418
+ flt[i] = 9;
4419
+ for (i = 256;i < 280; ++i)
4420
+ flt[i] = 7;
4421
+ for (i = 280;i < 288; ++i)
4422
+ flt[i] = 8;
4423
+ fdt = new u8(32);
4424
+ for (i = 0;i < 32; ++i)
4425
+ fdt[i] = 5;
4426
+ flrm = /* @__PURE__ */ hMap(flt, 9, 1);
4427
+ fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
4428
+ ec = [
4429
+ "unexpected EOF",
4430
+ "invalid block type",
4431
+ "invalid length/literal",
4432
+ "invalid distance",
4433
+ "stream finished",
4434
+ "no stream handler",
4435
+ ,
4436
+ "no callback",
4437
+ "invalid UTF-8 data",
4438
+ "extra field too long",
4439
+ "date not in range 1980-2099",
4440
+ "filename too long",
4441
+ "stream finishing",
4442
+ "invalid zip data"
4443
+ ];
4444
+ et = /* @__PURE__ */ new u8(0);
4445
+ td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder;
4446
+ try {
4447
+ td.decode(et, { stream: true });
4448
+ tds = 1;
4449
+ } catch (e) {}
4450
+ });
4451
+
4452
+ // packages/gateway/src/skill-market.ts
4453
+ import { createHash as createHash2, randomUUID } from "crypto";
4454
+ import { mkdir as mkdir2, readdir as readdir2, rename as rename2, rm as rm2, stat as stat2, writeFile as writeFile2 } from "fs/promises";
4455
+ import { homedir as homedir5 } from "os";
4456
+ import { dirname as dirname5, join as join8 } from "path";
4457
+ function createSkillMarketClient(options) {
4458
+ const home = options.home ?? homedir5();
4459
+ const fetchImpl = options.fetchImpl ?? fetch;
4460
+ const now = options.now ?? Date.now;
4461
+ let base;
4462
+ try {
4463
+ base = new URL(options.marketUrl);
4464
+ if (base.protocol !== "https:" && base.protocol !== "http:")
4465
+ throw new Error("unsupported protocol");
4466
+ } catch {
4467
+ throw new SkillMarketError("market_invalid_url", `Invalid skill market URL: ${options.marketUrl}`);
4468
+ }
4469
+ base = new URL(base.toString().replace(/\/+$/, ""));
4470
+ let catalog;
4471
+ const zipCache = new Map;
4472
+ const request = async (path) => {
4473
+ let response;
4474
+ try {
4475
+ response = await fetchImpl(new URL(path, `${base}/`).toString());
4476
+ } catch (error) {
4477
+ throw new SkillMarketError("market_unavailable", `Skill market unreachable: ${error instanceof Error ? error.message : String(error)}`);
4478
+ }
4479
+ return response;
4480
+ };
4481
+ const refreshCatalog = async () => {
4482
+ const response = await request("api/skills");
4483
+ if (!response.ok)
4484
+ throw new SkillMarketError("market_unavailable", `Skill list failed with HTTP ${response.status}`);
4485
+ const data = await response.json();
4486
+ const skills = Array.isArray(data.skills) ? data.skills.map(normalizeEntry) : [];
4487
+ catalog = { at: now(), skills };
4488
+ return skills;
4489
+ };
4490
+ const listSkills = async () => catalog !== undefined && now() - catalog.at < CATALOG_TTL_MS ? catalog.skills : refreshCatalog();
4491
+ const detail = async (name) => {
4492
+ let hit = (await listSkills()).find((skill) => skill.name === name);
4493
+ if (hit === undefined)
4494
+ hit = (await refreshCatalog()).find((skill) => skill.name === name);
4495
+ if (hit === undefined)
4496
+ throw new SkillMarketError("skill_not_found", `Skill not found on market: ${name}`);
4497
+ return hit;
4498
+ };
4499
+ const download = async (skill) => {
4500
+ const cached = zipCache.get(skill.sha256);
4501
+ if (cached !== undefined) {
4502
+ zipCache.delete(skill.sha256);
4503
+ zipCache.set(skill.sha256, cached);
4504
+ return cached;
4505
+ }
4506
+ if (skill.sizeBytes > MAX_PACKAGE_BYTES)
4507
+ throw new SkillMarketError("skill_too_large", `Package ${skill.name} is ${skill.sizeBytes} bytes (limit ${MAX_PACKAGE_BYTES})`);
4508
+ const response = await request(`api/skills/${encodeURIComponent(skill.name)}/download`);
4509
+ if (!response.ok)
4510
+ throw new SkillMarketError(response.status === 404 ? "skill_not_found" : "market_unavailable", `Download of ${skill.name} failed with HTTP ${response.status}`);
4511
+ const bytes = new Uint8Array(await response.arrayBuffer());
4512
+ if (bytes.byteLength > MAX_PACKAGE_BYTES)
4513
+ throw new SkillMarketError("skill_too_large", `Package ${skill.name} exceeds ${MAX_PACKAGE_BYTES} bytes`);
4514
+ const digest = createHash2("sha256").update(bytes).digest("hex");
4515
+ if (digest !== skill.sha256)
4516
+ throw new SkillMarketError("skill_hash_mismatch", `SHA-256 mismatch for ${skill.name}: catalog says ${skill.sha256}, got ${digest}`);
4517
+ zipCache.set(skill.sha256, bytes);
4518
+ while (zipCache.size > ZIP_CACHE_MAX) {
4519
+ const oldest = zipCache.keys().next().value;
4520
+ if (oldest === undefined)
4521
+ break;
4522
+ zipCache.delete(oldest);
4523
+ }
4524
+ return bytes;
4525
+ };
4526
+ const unpack = (skill, zip) => {
4527
+ let entries;
4528
+ try {
4529
+ entries = unzipSync(zip);
4530
+ } catch {
4531
+ throw new SkillMarketError("skill_invalid_package", `Package ${skill.name} is not a valid zip archive`);
4532
+ }
4533
+ const files = new Map;
4534
+ const skillMds = [];
4535
+ for (const name of Object.keys(entries)) {
4536
+ if (name.endsWith("/"))
4537
+ continue;
4538
+ if (name.includes("\\") || name.startsWith("/"))
4539
+ throw new SkillMarketError("skill_invalid_package", `Unsafe path in package: ${name}`);
4540
+ const segments = name.split("/");
4541
+ if (segments.some((s) => s === ".." || s === ""))
4542
+ throw new SkillMarketError("skill_invalid_package", `Unsafe path in package: ${name}`);
4543
+ if (segments[segments.length - 1] === "SKILL.md")
4544
+ skillMds.push(name);
4545
+ files.set(name, entries[name]);
4546
+ }
4547
+ if (skillMds.length !== 1)
4548
+ throw new SkillMarketError("skill_invalid_package", skillMds.length === 0 ? `Package ${skill.name} contains no SKILL.md` : `Package ${skill.name} contains multiple SKILL.md files`);
4549
+ const root = skillMds[0].slice(0, -"SKILL.md".length);
4550
+ const inner = new Map;
4551
+ for (const [name, bytes] of files) {
4552
+ if (!name.startsWith(root))
4553
+ continue;
4554
+ inner.set(name.slice(root.length), bytes);
4555
+ }
4556
+ return inner;
4557
+ };
4558
+ const skillsDirFor = (scope, workspaceRoot) => {
4559
+ if (scope === "user")
4560
+ return userSkillsDir2(home);
4561
+ if (workspaceRoot === undefined || workspaceRoot === "")
4562
+ throw new SkillMarketError("market_unavailable", "Project-scope install needs a workspaceRoot");
4563
+ return projectSkillsDir(workspaceRoot);
4564
+ };
4565
+ const sweepTempDirs = async (skillsDir) => {
4566
+ const parent = dirname5(skillsDir);
4567
+ let names = [];
4568
+ try {
4569
+ names = await readdir2(parent);
4570
+ } catch {
4571
+ return;
4572
+ }
4573
+ await Promise.all(names.filter((n) => n.startsWith(".tmp-")).map((n) => rm2(join8(parent, n), { recursive: true, force: true }).catch(() => {})));
4574
+ };
4575
+ const install = async ({ name, scope, workspaceRoot, overwrite }) => {
4576
+ const skillsDir = skillsDirFor(scope, workspaceRoot);
4577
+ const target = join8(skillsDir, name);
4578
+ const exists = await stat2(target).catch(() => {
4579
+ return;
4580
+ }) !== undefined;
4581
+ if (exists && overwrite !== true)
4582
+ throw new SkillMarketError("skill_exists", `Skill ${name} already exists at ${scope} level (${target})`);
4583
+ const skill = await detail(name);
4584
+ const files = unpack(skill, await download(skill));
4585
+ await sweepTempDirs(skillsDir);
4586
+ const temp = join8(dirname5(skillsDir), `.tmp-${randomUUID()}`);
4587
+ try {
4588
+ for (const [relative, bytes] of files) {
4589
+ const dest = join8(temp, ...relative.split("/"));
4590
+ await mkdir2(dirname5(dest), { recursive: true });
4591
+ await writeFile2(dest, bytes);
4592
+ }
4593
+ if (exists)
4594
+ await rm2(target, { recursive: true, force: true });
4595
+ await mkdir2(skillsDir, { recursive: true });
4596
+ await rename2(temp, target);
4597
+ } finally {
4598
+ await rm2(temp, { recursive: true, force: true }).catch(() => {});
4599
+ }
4600
+ let shadowedBy;
4601
+ if (scope === "user" && workspaceRoot !== undefined && workspaceRoot !== "") {
4602
+ const projectDir = join8(projectSkillsDir(workspaceRoot), name);
4603
+ if (await stat2(projectDir).catch(() => {
4604
+ return;
4605
+ }) !== undefined)
4606
+ shadowedBy = "project";
4607
+ }
4608
+ return { name, scope, path: target, ...shadowedBy !== undefined ? { shadowedBy } : {} };
4609
+ };
4610
+ const uninstall = async ({ name, scope, workspaceRoot }) => {
4611
+ const skillsDir = skillsDirFor(scope, workspaceRoot);
4612
+ const target = join8(skillsDir, name);
4613
+ if (await stat2(target).catch(() => {
4614
+ return;
4615
+ }) === undefined)
4616
+ throw new SkillMarketError("skill_not_installed", `Skill ${name} is not installed at ${scope} level`);
4617
+ await rm2(target, { recursive: true, force: true });
4618
+ };
4619
+ return {
4620
+ async list(query) {
4621
+ if (query !== undefined && query.trim() !== "") {
4622
+ const response = await request(`api/skills?q=${encodeURIComponent(query.trim())}`);
4623
+ if (!response.ok)
4624
+ throw new SkillMarketError("market_unavailable", `Skill search failed with HTTP ${response.status}`);
4625
+ const data = await response.json();
4626
+ return Array.isArray(data.skills) ? data.skills.map(normalizeEntry) : [];
4627
+ }
4628
+ return listSkills();
4629
+ },
4630
+ detail,
4631
+ async preview(name) {
4632
+ const skill = await detail(name);
4633
+ const inner = unpack(skill, await download(skill));
4634
+ const bodyBytes = inner.get("SKILL.md");
4635
+ if (bodyBytes === undefined)
4636
+ throw new SkillMarketError("skill_invalid_package", `Package ${name} lost its SKILL.md`);
4637
+ const body = stripFrontmatter(new TextDecoder().decode(bodyBytes));
4638
+ return { body, files: [...inner.keys()].sort() };
4639
+ },
4640
+ install,
4641
+ uninstall
4642
+ };
4643
+ }
4644
+ 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) => {
4645
+ const row = raw ?? {};
4646
+ const metadata = row.metadata;
4647
+ const source = metadata !== null && typeof metadata === "object" ? metadata.source : undefined;
4648
+ return {
4649
+ name: String(row.name ?? ""),
4650
+ description: String(row.description ?? ""),
4651
+ category: typeof row.category === "string" && row.category !== "" ? row.category : "uncategorized",
4652
+ source: typeof source === "string" && source !== "" ? source : "unknown",
4653
+ license: String(row.license ?? ""),
4654
+ version: String(row.version ?? ""),
4655
+ sha256: String(row.sha256 ?? ""),
4656
+ sizeBytes: Number(row.size_bytes ?? 0),
4657
+ uploadedAt: String(row.uploaded_at ?? "")
4658
+ };
4659
+ };
4660
+ var init_skill_market = __esm(() => {
4661
+ init_src();
4662
+ init_esm();
4663
+ SkillMarketError = class SkillMarketError extends Error {
4664
+ code;
4665
+ constructor(code, message) {
4666
+ super(message);
4667
+ this.name = "SkillMarketError";
4668
+ this.code = code;
4669
+ }
4670
+ };
4671
+ MAX_PACKAGE_BYTES = 32 * 1024 * 1024;
4672
+ });
4673
+
3916
4674
  // packages/gateway/src/workspaces.ts
3917
4675
  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";
4676
+ import { access, readdir as readdir3, stat as stat3 } from "fs/promises";
4677
+ import { dirname as dirname6, isAbsolute as isAbsolute3, join as join9 } from "path";
3920
4678
  async function browseWorkspaces(options) {
3921
4679
  const requested = options.path.replace(/^\/+([A-Za-z]:[\\/])/, "$1");
3922
4680
  if (process.platform === "win32" && (requested === "/" || requested === "\\")) {
@@ -3938,16 +4696,16 @@ async function browseWorkspaces(options) {
3938
4696
  throw new Error("\u76EE\u5F55\u5206\u9875\u5DF2\u5931\u6548\uFF0C\u8BF7\u5237\u65B0\u5217\u8868");
3939
4697
  }
3940
4698
  }
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);
4699
+ 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
4700
  const directories = [];
3943
4701
  let next = offset;
3944
4702
  for (;next < entries.length && directories.length < 100; next++) {
3945
4703
  const entry = entries[next];
3946
- const target = join8(path, entry.name);
4704
+ const target = join9(path, entry.name);
3947
4705
  if (!entry.isDirectory() && !entry.isSymbolicLink())
3948
4706
  continue;
3949
4707
  try {
3950
- if (!(await stat2(target)).isDirectory())
4708
+ if (!(await stat3(target)).isDirectory())
3951
4709
  continue;
3952
4710
  await access(target, constants3.R_OK | constants3.X_OK);
3953
4711
  directories.push({ name: entry.name, path: target });
@@ -3957,7 +4715,7 @@ async function browseWorkspaces(options) {
3957
4715
  }
3958
4716
  return {
3959
4717
  path,
3960
- parent: dirname5(path) === path ? null : dirname5(path),
4718
+ parent: dirname6(path) === path ? null : dirname6(path),
3961
4719
  entries: directories,
3962
4720
  next: next < entries.length ? JSON.stringify({ key, offset: next }) : null
3963
4721
  };
@@ -3986,6 +4744,26 @@ import {
3986
4744
  } from "@earendil-works/chord";
3987
4745
  async function createNekoServerServices(options) {
3988
4746
  const { runtime, context } = options;
4747
+ const market = createSkillMarketClient({
4748
+ marketUrl: options.marketUrl,
4749
+ ...options.promptHome !== undefined ? { home: options.promptHome } : {}
4750
+ });
4751
+ const toRemote = (error) => {
4752
+ if (error instanceof SkillMarketError) {
4753
+ const code = error.code === "skill_not_found" || error.code === "skill_not_installed" ? "service_not_found" : "service_invalid_value";
4754
+ throw new RemoteServiceError(code, `[${error.code}] ${error.message}`);
4755
+ }
4756
+ throw error;
4757
+ };
4758
+ const withInstallState = async (skill, workspaceRoot) => {
4759
+ const locals = await runtime.listSkillsByLevel(workspaceRoot);
4760
+ const installed = {
4761
+ project: locals.some((s) => s.name === skill.name && s.level === "project"),
4762
+ user: locals.some((s) => s.name === skill.name && s.level === "user"),
4763
+ builtin: locals.some((s) => s.name === skill.name && s.level === "builtin")
4764
+ };
4765
+ return { ...skill, installed, activeLevel: activeLevelOf(installed) };
4766
+ };
3989
4767
  const list = async () => (await runtime.listConversations()).map((info) => ({
3990
4768
  id: String(info.id),
3991
4769
  workspaceRoot: info.workspaceRoot,
@@ -4041,7 +4819,8 @@ async function createNekoServerServices(options) {
4041
4819
  { service: NekoModels, mode: "singleton" },
4042
4820
  { service: NekoCredentials, mode: "singleton" },
4043
4821
  { service: NekoWorkspaces, mode: "singleton" },
4044
- { service: NekoWorkspaceFiles, mode: "singleton" }
4822
+ { service: NekoWorkspaceFiles, mode: "singleton" },
4823
+ { service: NekoSkillMarket, mode: "singleton" }
4045
4824
  ]);
4046
4825
  provider.provide(NekoSessionDirectory, { state: directory });
4047
4826
  const updateCredentials = (operation) => serialize(async () => {
@@ -4091,6 +4870,13 @@ async function createNekoServerServices(options) {
4091
4870
  }
4092
4871
  });
4093
4872
  provider.provide(NekoModels, { list: async () => runtime.listModels() });
4873
+ provider.provide(NekoSkillMarket, {
4874
+ list: (input) => market.list(input.query).then((skills) => Promise.all(skills.map((skill) => withInstallState(skill, input.workspaceRoot)))).catch(toRemote),
4875
+ detail: (input) => market.detail(input.name).then((skill) => withInstallState(skill, input.workspaceRoot)).catch(toRemote),
4876
+ preview: (input) => market.preview(input.name).catch(toRemote),
4877
+ install: (input) => serialize(() => market.install(input).then((result) => ({ ...result })).catch(toRemote)),
4878
+ uninstall: (input) => serialize(() => market.uninstall(input).catch(toRemote))
4879
+ });
4094
4880
  provider.provide(NekoSessionManagement, {
4095
4881
  create: (createOptions) => serialize(async () => {
4096
4882
  const created = await runtime.createConversation(createOptions.model !== undefined || createOptions.thinkingLevel !== undefined || createOptions.cwd !== undefined ? {
@@ -4171,6 +4957,7 @@ var init_server_services = __esm(() => {
4171
4957
  init_src();
4172
4958
  init_workspace_files();
4173
4959
  init_contract();
4960
+ init_skill_market();
4174
4961
  init_workspaces();
4175
4962
  });
4176
4963
 
@@ -4517,9 +5304,9 @@ var init_session_host = __esm(() => {
4517
5304
  });
4518
5305
 
4519
5306
  // 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";
5307
+ import { randomUUID as randomUUID2 } from "crypto";
5308
+ import { mkdir as mkdir3, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
5309
+ import { basename as basename5, dirname as dirname7, join as join10 } from "path";
4523
5310
  import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT3 } from "@earendil-works/chord/context";
4524
5311
  import { isServerId } from "@earendil-works/pi-protocol";
4525
5312
  import { Server, SessionNotFoundError } from "@earendil-works/pi-server";
@@ -4531,9 +5318,10 @@ async function startGateway(options = {}) {
4531
5318
  const storagePath = options.storagePath ?? fileConfig.storagePath ?? DEFAULT_STORAGE_PATH;
4532
5319
  const host = options.host ?? fileConfig.gateway?.host ?? DEFAULT_HOST;
4533
5320
  const port = options.port !== undefined ? options.port : fileConfig.gateway?.port ?? (process.platform === "win32" ? 0 : DEFAULT_PORT);
4534
- const nekoDir = dirname6(storagePath);
5321
+ const marketUrl = options.marketUrl ?? fileConfig.skills?.marketUrl ?? DEFAULT_SKILL_MARKET_URL;
5322
+ const nekoDir = dirname7(storagePath);
4535
5323
  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`);
5324
+ const socketPath = options.socketPath !== undefined ? options.socketPath : fileConfig.gateway?.socketPath !== undefined ? fileConfig.gateway.socketPath : process.platform === "win32" ? null : join10(nekoDir, `${serverId}.sock`);
4537
5325
  const runtime = await createNekoRuntime({
4538
5326
  context,
4539
5327
  storage: options.storage ?? "sqlite",
@@ -4556,6 +5344,8 @@ async function startGateway(options = {}) {
4556
5344
  serverId,
4557
5345
  nekoDir,
4558
5346
  model,
5347
+ marketUrl,
5348
+ promptHome: options.promptHome,
4559
5349
  onError: options.onError
4560
5350
  });
4561
5351
  } catch (error) {
@@ -4566,7 +5356,12 @@ async function startGateway(options = {}) {
4566
5356
  async function bindGateway(runtime, options) {
4567
5357
  const { context, host, port, socketPath, serverId, nekoDir, model } = options;
4568
5358
  const modelLabel = `${model.provider}/${model.modelId}`;
4569
- const serverServices = await createNekoServerServices({ runtime, context });
5359
+ const serverServices = await createNekoServerServices({
5360
+ runtime,
5361
+ context,
5362
+ marketUrl: options.marketUrl,
5363
+ ...options.promptHome !== undefined ? { promptHome: options.promptHome } : {}
5364
+ });
4570
5365
  let wsListener;
4571
5366
  const listeners = [];
4572
5367
  if (socketPath !== null) {
@@ -4644,15 +5439,15 @@ function serverIdFromSocketPath(socketPath) {
4644
5439
  return isServerId(candidate) ? candidate : undefined;
4645
5440
  }
4646
5441
  async function loadOrCreateServerId(nekoDir) {
4647
- const file = join9(nekoDir, SERVER_ID_FILENAME);
5442
+ const file = join10(nekoDir, SERVER_ID_FILENAME);
4648
5443
  try {
4649
5444
  const existing = (await readFile5(file, "utf8")).trim();
4650
5445
  if (existing !== "")
4651
5446
  return existing;
4652
5447
  } catch {}
4653
- const created = randomUUID();
4654
- await mkdir2(nekoDir, { recursive: true, mode: 448 });
4655
- await writeFile2(file, `${created}
5448
+ const created = randomUUID2();
5449
+ await mkdir3(nekoDir, { recursive: true, mode: 448 });
5450
+ await writeFile3(file, `${created}
4656
5451
  `, { mode: 384 });
4657
5452
  return created;
4658
5453
  }
@@ -4666,14 +5461,14 @@ var init_gateway = __esm(() => {
4666
5461
  });
4667
5462
 
4668
5463
  // packages/gateway/src/process.ts
4669
- import { fileURLToPath } from "url";
5464
+ import { fileURLToPath as fileURLToPath2 } from "url";
4670
5465
  function gatewayCommand(args) {
4671
5466
  const source = import.meta.url.endsWith(".ts");
4672
5467
  return {
4673
5468
  binary: process.execPath,
4674
5469
  args: [
4675
5470
  ...source ? ["--conditions=neko-source"] : [],
4676
- fileURLToPath(new URL("./gateway.js", import.meta.url)),
5471
+ fileURLToPath2(new URL("./gateway.js", import.meta.url)),
4677
5472
  ...args
4678
5473
  ]
4679
5474
  };
@@ -4704,14 +5499,14 @@ var init_src2 = __esm(() => {
4704
5499
 
4705
5500
  // packages/tui/src/gateway.ts
4706
5501
  import { spawn as spawn2 } from "child_process";
4707
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
4708
- import { basename as basename6, dirname as dirname7, join as join10 } from "path";
5502
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
5503
+ import { basename as basename6, dirname as dirname8, join as join11 } from "path";
4709
5504
  import { isServerId as isServerId2 } from "@earendil-works/pi-protocol";
4710
5505
  async function connectToGateway(options = {}) {
4711
5506
  const config = loadNekoConfig(options.configDir);
4712
5507
  const storagePath = options.storagePath ?? config.storagePath ?? DEFAULT_STORAGE_PATH;
4713
- const nekoDir = dirname7(storagePath);
4714
- const fileServerId = readServerId(join10(nekoDir, SERVER_ID_FILENAME2));
5508
+ const nekoDir = dirname8(storagePath);
5509
+ const fileServerId = readServerId(join11(nekoDir, SERVER_ID_FILENAME2));
4715
5510
  if (process.platform === "win32") {
4716
5511
  if (fileServerId !== undefined) {
4717
5512
  const wsUrl = await readGatewayPortFile(nekoDir, fileServerId);
@@ -4724,9 +5519,9 @@ async function connectToGateway(options = {}) {
4724
5519
  }
4725
5520
  return spawnAndConnect({ ...options, storagePath, socketPath: undefined, nekoDir, fileServerId });
4726
5521
  }
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));
4729
- if (socketPath !== undefined && existsSync2(socketPath)) {
5522
+ const socketPath = options.socketPath ?? config.gateway?.socketPath ?? (fileServerId !== undefined ? join11(nekoDir, `${fileServerId}.sock`) : undefined);
5523
+ const serverIdFor = (path) => fileServerId ?? serverIdFromSocketPath2(path) ?? readServerId(join11(nekoDir, SERVER_ID_FILENAME2));
5524
+ if (socketPath !== undefined && existsSync3(socketPath)) {
4730
5525
  const serverId = serverIdFor(socketPath);
4731
5526
  if (serverId !== undefined) {
4732
5527
  try {
@@ -4755,7 +5550,7 @@ async function spawnAndConnect(plan) {
4755
5550
  async function connectSpawned(plan, child, outputTail, spawnError) {
4756
5551
  const timeoutMs = plan.spawnTimeoutMs ?? 20000;
4757
5552
  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);
5553
+ const serverId = plan.fileServerId ?? await waitForValue(() => readServerId(join11(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
4759
5554
  const wsUrl = await waitForValue(() => readGatewayPortFile(plan.nekoDir, serverId), "gateway ready (port file)", timeoutMs, child, outputTail, spawnError);
4760
5555
  let lastError;
4761
5556
  for (let attempt = 0;attempt < CONNECT_RETRIES; attempt++) {
@@ -4784,11 +5579,11 @@ ${outputTail()}`);
4784
5579
  }
4785
5580
  let socketPath = plan.socketPath;
4786
5581
  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`);
5582
+ const generated = await waitForValue(() => readServerId(join11(plan.nekoDir, SERVER_ID_FILENAME2)), "gateway serverId file", timeoutMs, child, outputTail, spawnError);
5583
+ socketPath = join11(plan.nekoDir, `${generated}.sock`);
4789
5584
  }
4790
- 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));
5585
+ await waitForValue(() => existsSync3(socketPath) ? true : undefined, `gateway ready (socket: ${socketPath})`, timeoutMs, child, outputTail, spawnError);
5586
+ const serverId = plan.fileServerId ?? serverIdFromSocketPath2(socketPath) ?? readServerId(join11(plan.nekoDir, SERVER_ID_FILENAME2));
4792
5587
  if (serverId === undefined) {
4793
5588
  throw new Error(`Cannot determine gateway serverId (socket: ${socketPath})`);
4794
5589
  }
@@ -4900,8 +5695,8 @@ var init_gateway2 = __esm(() => {
4900
5695
 
4901
5696
  // packages/tui/src/image-input.ts
4902
5697
  import { execFile } from "child_process";
4903
- import { readFile as readFile6, stat as stat3 } from "fs/promises";
4904
- import { homedir as homedir5 } from "os";
5698
+ import { readFile as readFile6, stat as stat4 } from "fs/promises";
5699
+ import { homedir as homedir6 } from "os";
4905
5700
  import { basename as basename7, resolve as resolve5 } from "path";
4906
5701
  import { promisify } from "util";
4907
5702
  import { isKeyRelease as isKeyRelease4, isKeyRepeat as isKeyRepeat2, matchesKey as matchesKey4 } from "@earendil-works/pi-tui";
@@ -4936,12 +5731,12 @@ function imagePaths(text, platform = process.platform) {
4936
5731
  return paths.length && paths.every((p) => /\.(png|jpe?g|webp)$/i.test(p)) ? paths : null;
4937
5732
  }
4938
5733
  async function readImageFile(path, cwd) {
4939
- const full = resolve5(cwd, path.startsWith("~/") ? homedir5() + path.slice(1) : path);
4940
- const info = await stat3(full);
5734
+ const full = resolve5(cwd, path.startsWith("~/") ? homedir6() + path.slice(1) : path);
5735
+ const info = await stat4(full);
4941
5736
  if (!info.isFile() || info.size > 20 * 1024 * 1024)
4942
5737
  throw new Error("Image must be a file no larger than 20 MiB");
4943
5738
  const bytes = await readFile6(full);
4944
- const after = await stat3(full);
5739
+ const after = await stat4(full);
4945
5740
  if (bytes.length !== info.size || after.size !== info.size || after.mtimeMs !== info.mtimeMs)
4946
5741
  throw new Error("Image changed while reading");
4947
5742
  const mimeType = /\.png$/i.test(full) ? "image/png" : /\.webp$/i.test(full) ? "image/webp" : "image/jpeg";
@@ -5006,7 +5801,7 @@ var init_image_input = __esm(() => {
5006
5801
  });
5007
5802
 
5008
5803
  // packages/tui/src/layout.ts
5009
- import { Text as Text4, VStack } from "@earendil-works/pi-tui";
5804
+ import { Text as Text5, VStack } from "@earendil-works/pi-tui";
5010
5805
  function createAppLayout(parts) {
5011
5806
  const normal = (v) => {
5012
5807
  parts.footer.panelOpen = parts.panelOpen();
@@ -5016,7 +5811,7 @@ function createAppLayout(parts) {
5016
5811
  };
5017
5812
  return new VStack([
5018
5813
  {
5019
- component: new Text4("Terminal too small. Resize to 40x10. Ctrl+C Exit", 0, 0),
5814
+ component: new Text5("Terminal too small. Resize to 40x10. Ctrl+C Exit", 0, 0),
5020
5815
  visible: (v) => !normal(v),
5021
5816
  grow: 1
5022
5817
  },
@@ -5030,6 +5825,235 @@ function createAppLayout(parts) {
5030
5825
  var usableSize = (width, height) => width >= 40 && height >= 10;
5031
5826
  var init_layout = () => {};
5032
5827
 
5828
+ // packages/tui/src/skill-market-ui.ts
5829
+ import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
5830
+ import { Markdown as Markdown3, matchesKey as matchesKey5, wrapTextWithAnsi as wrapTextWithAnsi2 } from "@earendil-works/pi-tui";
5831
+
5832
+ class SkillDetailView {
5833
+ #skill = null;
5834
+ #error = "";
5835
+ #notice = "";
5836
+ #busy = false;
5837
+ #confirm = null;
5838
+ #name;
5839
+ #client;
5840
+ #workspaceRoot;
5841
+ #panel;
5842
+ #openPreview;
5843
+ constructor(name, client, workspaceRoot, panel, openPreview) {
5844
+ this.#name = name;
5845
+ this.#client = client;
5846
+ this.#workspaceRoot = workspaceRoot;
5847
+ this.#panel = panel;
5848
+ this.#openPreview = openPreview;
5849
+ this.refresh();
5850
+ }
5851
+ get help() {
5852
+ if (this.#confirm)
5853
+ return hint(["y", "Confirm"], ["n", "Cancel"]);
5854
+ const skill = this.#skill;
5855
+ const builtinOnly = skill?.installed.builtin === true && !skill.installed.user && !skill.installed.project;
5856
+ 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"]);
5857
+ }
5858
+ async refresh() {
5859
+ try {
5860
+ this.#skill = await this.#client.skillMarket.detail({ name: this.#name, workspaceRoot: this.#workspaceRoot }, CTX);
5861
+ this.#error = "";
5862
+ } catch (error) {
5863
+ this.#error = error instanceof Error ? error.message : String(error);
5864
+ }
5865
+ this.#panel.changed();
5866
+ }
5867
+ handleKey(data) {
5868
+ if (this.#confirm) {
5869
+ if (matchesKey5(data, "y")) {
5870
+ const pending = this.#confirm;
5871
+ this.#confirm = null;
5872
+ this.run(pending.run);
5873
+ } else if (matchesKey5(data, "n")) {
5874
+ this.#confirm = null;
5875
+ }
5876
+ return true;
5877
+ }
5878
+ if (this.#busy || this.#skill === null)
5879
+ return true;
5880
+ const skill = this.#skill;
5881
+ const builtinOnly = skill.installed.builtin && !skill.installed.user && !skill.installed.project;
5882
+ if (matchesKey5(data, "v")) {
5883
+ this.#openPreview(this.#name);
5884
+ return true;
5885
+ }
5886
+ if (builtinOnly)
5887
+ return true;
5888
+ if (matchesKey5(data, "u"))
5889
+ this.askInstall("user", skill);
5890
+ else if (matchesKey5(data, "p"))
5891
+ this.askInstall("project", skill);
5892
+ else if (matchesKey5(data, "d"))
5893
+ this.askUninstall(skill);
5894
+ else
5895
+ return false;
5896
+ return true;
5897
+ }
5898
+ askInstall(scope, skill) {
5899
+ const exists = scope === "user" ? skill.installed.user : skill.installed.project;
5900
+ const shadowed = scope === "user" && skill.installed.project;
5901
+ if (exists) {
5902
+ this.#confirm = {
5903
+ prompt: `${skill.name} exists at ${scope} level. Overwrite with market version ${skill.version}? Local edits are lost.`,
5904
+ run: () => this.install(scope, true)
5905
+ };
5906
+ } else if (shadowed) {
5907
+ this.#confirm = {
5908
+ prompt: `Project level already has ${skill.name} and wins. Install at user level anyway?`,
5909
+ run: () => this.install(scope, false)
5910
+ };
5911
+ } else {
5912
+ this.run(() => this.install(scope, false));
5913
+ }
5914
+ }
5915
+ askUninstall(skill) {
5916
+ const scope = skill.installed.project ? "project" : skill.installed.user ? "user" : null;
5917
+ if (scope === null) {
5918
+ this.#notice = "Not installed locally.";
5919
+ return;
5920
+ }
5921
+ this.#confirm = {
5922
+ prompt: `Uninstall ${skill.name} from ${scope} level? This cannot be undone.`,
5923
+ run: async () => {
5924
+ await this.#client.skillMarket.uninstall({ name: this.#name, scope, workspaceRoot: this.#workspaceRoot }, CTX);
5925
+ this.#notice = `Uninstalled from ${scope}. Takes effect on the next message.`;
5926
+ }
5927
+ };
5928
+ }
5929
+ async install(scope, overwrite) {
5930
+ try {
5931
+ const result = await this.#client.skillMarket.install({ name: this.#name, scope, workspaceRoot: this.#workspaceRoot, overwrite }, CTX);
5932
+ 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.`;
5933
+ } catch (error) {
5934
+ if (marketErrorCode(error) === "skill_exists") {
5935
+ this.#confirm = {
5936
+ prompt: `${this.#name} already exists at ${scope} level. Overwrite with the market version?`,
5937
+ run: () => this.install(scope, true)
5938
+ };
5939
+ return;
5940
+ }
5941
+ throw error;
5942
+ }
5943
+ }
5944
+ async run(action) {
5945
+ this.#busy = true;
5946
+ this.#notice = "";
5947
+ this.#panel.changed();
5948
+ try {
5949
+ await action();
5950
+ } catch (error) {
5951
+ this.#notice = `Failed: ${error instanceof Error ? error.message : String(error)}`;
5952
+ } finally {
5953
+ this.#busy = false;
5954
+ await this.refresh();
5955
+ }
5956
+ }
5957
+ lines(width, _height) {
5958
+ if (this.#confirm) {
5959
+ return [theme.accent("Confirm"), "", ...wrapTextWithAnsi2(this.#confirm.prompt, width), ""];
5960
+ }
5961
+ const skill = this.#skill;
5962
+ if (skill === null)
5963
+ return [this.#error !== "" ? theme.error(`Error: ${this.#error}`) : theme.dim("Loading...")];
5964
+ const mark = (on, label) => on ? theme.ok(`${label} \u2713`) : theme.dim(`${label} \u2014`);
5965
+ const state = [
5966
+ mark(skill.installed.project, "project"),
5967
+ mark(skill.installed.user, "user"),
5968
+ mark(skill.installed.builtin, "builtin")
5969
+ ].join(" ");
5970
+ const rows = [
5971
+ `${style.bold(skill.name)} ${theme.dim(skill.version)}`,
5972
+ theme.dim(`${skill.source} \xB7 ${skill.category} \xB7 ${formatSize(skill.sizeBytes)} \xB7 sha256 ${skill.sha256.slice(0, 10)}\u2026`),
5973
+ `${theme.dim("installed:")} ${state}${skill.activeLevel ? theme.dim(` (active: ${skill.activeLevel})`) : ""}`,
5974
+ "",
5975
+ ...wrapTextWithAnsi2(skill.description, width)
5976
+ ];
5977
+ if (this.#busy)
5978
+ rows.push("", theme.dim("Working..."));
5979
+ if (this.#notice !== "")
5980
+ rows.push("", theme.accent(this.#notice));
5981
+ return rows;
5982
+ }
5983
+ }
5984
+
5985
+ class SkillPreviewView {
5986
+ #header = null;
5987
+ #markdown = null;
5988
+ #error = "";
5989
+ #offset = 0;
5990
+ #panel;
5991
+ get help() {
5992
+ return hint(["Up/Down", "Scroll"], ["Esc", "Back"]);
5993
+ }
5994
+ constructor(name, client, panel) {
5995
+ this.#panel = panel;
5996
+ client.skillMarket.preview({ name }, CTX).then((preview) => {
5997
+ this.#header = [`${theme.dim("files:")} ${preview.files.join(" ")}`, ""];
5998
+ this.#markdown = new Markdown3(preview.body, 0, 0, getMarkdownTheme());
5999
+ this.#panel.changed();
6000
+ }).catch((error) => {
6001
+ this.#error = error instanceof Error ? error.message : String(error);
6002
+ this.#panel.changed();
6003
+ });
6004
+ }
6005
+ handleKey(data) {
6006
+ if (matchesKey5(data, "up"))
6007
+ this.#offset = Math.max(0, this.#offset - 1);
6008
+ else if (matchesKey5(data, "down"))
6009
+ this.#offset += 1;
6010
+ else
6011
+ return false;
6012
+ return true;
6013
+ }
6014
+ lines(width, height) {
6015
+ if (this.#markdown === null || this.#header === null)
6016
+ return [this.#error !== "" ? theme.error(`Error: ${this.#error}`) : theme.dim("Loading preview...")];
6017
+ const flat = [...this.#header, ...this.#markdown.render(width)];
6018
+ const max = Math.max(0, flat.length - height);
6019
+ this.#offset = Math.min(this.#offset, max);
6020
+ return flat.slice(this.#offset, this.#offset + height);
6021
+ }
6022
+ }
6023
+ async function openSkillMarket(panel, client, workspaceRoot) {
6024
+ const openPreview = (name) => panel.showDetail(new SkillPreviewView(name, client, panel));
6025
+ await panel.show("Skills Market", async () => toItems(await client.skillMarket.list({ workspaceRoot }, CTX)), async (name) => new SkillDetailView(name, client, workspaceRoot, panel, openPreview));
6026
+ }
6027
+ 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) => {
6028
+ const message = error instanceof Error ? error.message : String(error);
6029
+ return /^\[([a-z_]+)\]/.exec(message)?.[1] ?? null;
6030
+ }, stateMarks = (skill) => `${skill.installed.user ? "[u]" : ""}${skill.installed.project ? "[p]" : ""}${skill.installed.builtin ? "[b]" : ""}`, toItems = (skills) => {
6031
+ const groups = new Map;
6032
+ for (const skill of skills) {
6033
+ const list = groups.get(skill.category) ?? [];
6034
+ list.push(skill);
6035
+ groups.set(skill.category, list);
6036
+ }
6037
+ const items = [];
6038
+ for (const [category, members] of [...groups.entries()].sort((a, b) => b[1].length - a[1].length)) {
6039
+ items.push({ value: `header:${category}`, label: `${category} (${members.length})`, header: true });
6040
+ for (const skill of members) {
6041
+ items.push({
6042
+ value: skill.name,
6043
+ label: skill.name,
6044
+ description: skill.description,
6045
+ searchText: `${skill.source} ${skill.category}`,
6046
+ rightLabel: `${skill.source} ${skill.version} ${stateMarks(skill)}`.trim()
6047
+ });
6048
+ }
6049
+ }
6050
+ return items;
6051
+ };
6052
+ var init_skill_market_ui = __esm(() => {
6053
+ init_theme();
6054
+ CTX = BACKGROUND_CONTEXT4;
6055
+ });
6056
+
5033
6057
  // packages/core/src/session-display.ts
5034
6058
  function relativeTime(at, now, language = "en") {
5035
6059
  const minutes = Math.floor(Math.max(0, now - at) / 60000);
@@ -5064,15 +6088,15 @@ ${new Date(messageTime(s)).toLocaleString("en-GB")}`
5064
6088
  var init_workspace2 = () => {};
5065
6089
 
5066
6090
  // packages/tui/src/tui.ts
5067
- import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT4 } from "@earendil-works/chord/context";
6091
+ import { BACKGROUND_CONTEXT as BACKGROUND_CONTEXT5 } from "@earendil-works/chord/context";
5068
6092
  import {
5069
6093
  Container as Container5,
5070
6094
  isKeyRelease as isKeyRelease5,
5071
- matchesKey as matchesKey5,
6095
+ matchesKey as matchesKey6,
5072
6096
  ProcessTerminal,
5073
6097
  ScrollView,
5074
6098
  Spacer as Spacer2,
5075
- Text as Text5,
6099
+ Text as Text6,
5076
6100
  TruncatedText,
5077
6101
  TuiAltScreen
5078
6102
  } from "@earendil-works/pi-tui";
@@ -5138,8 +6162,8 @@ class NekoTuiApp {
5138
6162
  this.#login = new LoginPanel({
5139
6163
  changed: () => this.#ui.requestRender(),
5140
6164
  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)
6165
+ save: (provider, key) => this.#gateway.client.credentials.save({ provider, key }, BACKGROUND_CONTEXT5),
6166
+ remove: (provider) => this.#gateway.client.credentials.remove({ provider }, BACKGROUND_CONTEXT5)
5143
6167
  });
5144
6168
  const complete = fileCompletion(this.#commands, options.cwd ?? process.cwd(), (text) => this.addNotice(text));
5145
6169
  const original = complete.applyCompletion.bind(complete);
@@ -5175,10 +6199,10 @@ class NekoTuiApp {
5175
6199
  this.#ui.addInputListener((data) => {
5176
6200
  if (isKeyRelease5(data))
5177
6201
  return { consume: true };
5178
- if (!matchesKey5(data, "escape") || this.#panel.open || this.#login.open || this.#busyText !== undefined || this.#lastView?.compaction?.active.length)
6202
+ if (!matchesKey6(data, "escape") || this.#panel.open || this.#login.open || this.#busyText !== undefined || this.#lastView?.compaction?.active.length)
5179
6203
  this.#editor.resetEscapeSequence();
5180
6204
  if (!usableSize(this.#terminal.columns, this.#terminal.rows)) {
5181
- if (matchesKey5(data, "ctrl+c"))
6205
+ if (matchesKey6(data, "ctrl+c"))
5182
6206
  this.exit();
5183
6207
  return { consume: true };
5184
6208
  }
@@ -5187,7 +6211,7 @@ class NekoTuiApp {
5187
6211
  }
5188
6212
  if (this.#sessionOperation)
5189
6213
  return { consume: true };
5190
- if (matchesKey5(data, process.platform === "win32" ? "alt+v" : "ctrl+v")) {
6214
+ if (matchesKey6(data, process.platform === "win32" ? "alt+v" : "ctrl+v")) {
5191
6215
  if (isClipboardShortcut(data))
5192
6216
  this.pasteClipboard();
5193
6217
  return { consume: true };
@@ -5204,26 +6228,26 @@ class NekoTuiApp {
5204
6228
  return { consume: true };
5205
6229
  }
5206
6230
  }
5207
- if (matchesKey5(data, "escape") && this.#editor.isShowingAutocomplete()) {
6231
+ if (matchesKey6(data, "escape") && this.#editor.isShowingAutocomplete()) {
5208
6232
  this.#editor.resetEscapeSequence();
5209
6233
  return;
5210
6234
  }
5211
- if (matchesKey5(data, "ctrl+c")) {
6235
+ if (matchesKey6(data, "ctrl+c")) {
5212
6236
  if (this.#busyText !== undefined)
5213
6237
  this.abort();
5214
6238
  else
5215
6239
  this.exit();
5216
6240
  return { consume: true };
5217
6241
  }
5218
- if (matchesKey5(data, "escape") && (this.#busyText !== undefined || this.#lastView?.compaction?.active.some((c) => c.reason === "manual" || c.blocking))) {
6242
+ if (matchesKey6(data, "escape") && (this.#busyText !== undefined || this.#lastView?.compaction?.active.some((c) => c.reason === "manual" || c.blocking))) {
5219
6243
  this.abort();
5220
6244
  return { consume: true };
5221
6245
  }
5222
- if (matchesKey5(data, "ctrl+s")) {
6246
+ if (matchesKey6(data, "ctrl+s")) {
5223
6247
  this.openSessionsOverlay();
5224
6248
  return { consume: true };
5225
6249
  }
5226
- if (this.#busyText !== undefined && matchesKey5(data, "alt+enter")) {
6250
+ if (this.#busyText !== undefined && matchesKey6(data, "alt+enter")) {
5227
6251
  this.submitFollowUp();
5228
6252
  return { consume: true };
5229
6253
  }
@@ -5285,7 +6309,7 @@ class NekoTuiApp {
5285
6309
  const chat = this.#chatFacade;
5286
6310
  const session = this.#sessionId;
5287
6311
  if (chat)
5288
- chat.compact({ instructions }, BACKGROUND_CONTEXT4).catch((e) => {
6312
+ chat.compact({ instructions }, BACKGROUND_CONTEXT5).catch((e) => {
5289
6313
  if (session === this.#sessionId) {
5290
6314
  this.#editor.setText(text);
5291
6315
  this.addNotice(messageOf(e), true);
@@ -5317,6 +6341,10 @@ class NekoTuiApp {
5317
6341
  this.openModelOverlay();
5318
6342
  return;
5319
6343
  }
6344
+ if (trimmed === "/skills") {
6345
+ this.openSkillsOverlay();
6346
+ return;
6347
+ }
5320
6348
  if (trimmed.startsWith("/think")) {
5321
6349
  this.configureThinking(trimmed.slice("/think".length).trim());
5322
6350
  return;
@@ -5402,14 +6430,14 @@ class NekoTuiApp {
5402
6430
  for (const [ref, promise] of refs) {
5403
6431
  const file = await promise;
5404
6432
  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)
6433
+ begin: (i) => chat.beginImage(i, BACKGROUND_CONTEXT5),
6434
+ append: (i) => chat.appendImage(i, BACKGROUND_CONTEXT5),
6435
+ finish: (id) => chat.finishImage(id, BACKGROUND_CONTEXT5),
6436
+ release: (id) => chat.releaseImage(id, BACKGROUND_CONTEXT5)
5409
6437
  }));
5410
6438
  content = content.replaceAll(ref, "");
5411
6439
  }
5412
- await chat.submit({ content: content.trim(), images: ids, requestId, ...whenBusy ? { whenBusy } : {} }, BACKGROUND_CONTEXT4);
6440
+ await chat.submit({ content: content.trim(), images: ids, requestId, ...whenBusy ? { whenBusy } : {} }, BACKGROUND_CONTEXT5);
5413
6441
  if (session === this.#sessionId && this.#editor.getText() === before)
5414
6442
  this.#editor.setText("");
5415
6443
  if (this.#texts.get(session) === before)
@@ -5424,7 +6452,7 @@ class NekoTuiApp {
5424
6452
  this.addNotice(messageOf(e), true);
5425
6453
  } finally {
5426
6454
  for (const id of ids)
5427
- await chat.releaseImage(id, BACKGROUND_CONTEXT4).catch(() => {});
6455
+ await chat.releaseImage(id, BACKGROUND_CONTEXT5).catch(() => {});
5428
6456
  this.#sending.delete(session);
5429
6457
  this.#ui.requestRender();
5430
6458
  }
@@ -5433,7 +6461,7 @@ class NekoTuiApp {
5433
6461
  const chat = this.#chatFacade;
5434
6462
  if (chat === undefined)
5435
6463
  return;
5436
- chat.abort(BACKGROUND_CONTEXT4).catch((error) => this.addNotice(messageOf(error), true));
6464
+ chat.abort(BACKGROUND_CONTEXT5).catch((error) => this.addNotice(messageOf(error), true));
5437
6465
  }
5438
6466
  exit() {
5439
6467
  if (this.#exitStarted)
@@ -5464,7 +6492,7 @@ class NekoTuiApp {
5464
6492
  async#refreshSkills(facade) {
5465
6493
  this.#commands.splice(0, this.#commands.length, ...commands);
5466
6494
  try {
5467
- const skills = await facade.skills(BACKGROUND_CONTEXT4);
6495
+ const skills = await facade.skills(BACKGROUND_CONTEXT5);
5468
6496
  if (facade !== this.#chatFacade)
5469
6497
  return;
5470
6498
  for (const skill of skills)
@@ -5484,16 +6512,16 @@ class NekoTuiApp {
5484
6512
  }
5485
6513
  }
5486
6514
  async createSession() {
5487
- const created = await this.#gateway.client.management.create({ ...this.#options.cwd !== undefined ? { cwd: this.#options.cwd } : {} }, BACKGROUND_CONTEXT4);
6515
+ const created = await this.#gateway.client.management.create({ ...this.#options.cwd !== undefined ? { cwd: this.#options.cwd } : {} }, BACKGROUND_CONTEXT5);
5488
6516
  await this.switchSession(created.id);
5489
6517
  }
5490
6518
  async forkSession() {
5491
- const forked = await this.#gateway.client.management.fork(this.#sessionId, BACKGROUND_CONTEXT4);
6519
+ const forked = await this.#gateway.client.management.fork(this.#sessionId, BACKGROUND_CONTEXT5);
5492
6520
  await this.switchSession(forked.id);
5493
6521
  }
5494
6522
  async archiveSession() {
5495
6523
  const current = this.#sessionId;
5496
- await this.#gateway.client.management.archive(current, BACKGROUND_CONTEXT4);
6524
+ await this.#gateway.client.management.archive(current, BACKGROUND_CONTEXT5);
5497
6525
  const next = recentlyVisited(this.#gateway.client.directory.state.value?.sessions ?? [], this.#options.cwd).find((s) => s.id !== current);
5498
6526
  if (next)
5499
6527
  await this.switchSession(next.id);
@@ -5518,6 +6546,14 @@ class NekoTuiApp {
5518
6546
  this.#sessionPanelTimer = setInterval(() => this.#panel.update(this.sessionItems()), 60000);
5519
6547
  this.#ui.setFocus(this.#panel);
5520
6548
  }
6549
+ async openSkillsOverlay() {
6550
+ if (this.#exitStarted || this.#panel.open || this.#login.open)
6551
+ return;
6552
+ const workspace = this.#options.cwd ?? process.cwd();
6553
+ const ready = openSkillMarket(this.#panel, this.#gateway.client, workspace);
6554
+ this.#ui.setFocus(this.#panel);
6555
+ await ready;
6556
+ }
5521
6557
  async openModelOverlay() {
5522
6558
  if (this.#exitStarted || this.#panel.open || this.#login.open)
5523
6559
  return;
@@ -5535,7 +6571,7 @@ class NekoTuiApp {
5535
6571
  }, async (value) => {
5536
6572
  const selected = models.find((m) => keyOf(m) === value);
5537
6573
  if (selected && value !== currentKey)
5538
- await chat?.configure({ model: { provider: selected.provider, modelId: selected.modelId } }, BACKGROUND_CONTEXT4);
6574
+ await chat?.configure({ model: { provider: selected.provider, modelId: selected.modelId } }, BACKGROUND_CONTEXT5);
5539
6575
  });
5540
6576
  this.#ui.setFocus(this.#panel);
5541
6577
  await ready;
@@ -5557,14 +6593,14 @@ class NekoTuiApp {
5557
6593
  this.addNotice(`Available levels: ${model.supportedThinkingLevels.join(" / ")}`, true);
5558
6594
  return;
5559
6595
  }
5560
- await chat.configure({ thinkingLevel: level }, BACKGROUND_CONTEXT4);
6596
+ await chat.configure({ thinkingLevel: level }, BACKGROUND_CONTEXT5);
5561
6597
  } else {
5562
6598
  this.#thinkingModel = keyOf(model);
5563
6599
  this.#panel.show("Thinking level", async () => model.supportedThinkingLevels.map((level) => ({
5564
6600
  value: level,
5565
6601
  label: `${level}${level === this.#lastView?.agent.thinkingLevel ? " (Current)" : ""}`
5566
6602
  })), async (value) => {
5567
- await chat.configure({ thinkingLevel: value }, BACKGROUND_CONTEXT4);
6603
+ await chat.configure({ thinkingLevel: value }, BACKGROUND_CONTEXT5);
5568
6604
  });
5569
6605
  this.#ui.setFocus(this.#panel);
5570
6606
  }
@@ -5676,7 +6712,7 @@ class NekoTuiApp {
5676
6712
  if (!chat)
5677
6713
  return;
5678
6714
  for (const image of images) {
5679
- const component = new HistoryImage(image, () => downloadImage((offset) => chat.getImage({ entryId: image.entryId, contentIndex: image.contentIndex, offset }, BACKGROUND_CONTEXT4)), () => {
6715
+ const component = new HistoryImage(image, () => downloadImage((offset) => chat.getImage({ entryId: image.entryId, contentIndex: image.contentIndex, offset }, BACKGROUND_CONTEXT5)), () => {
5680
6716
  if (session === this.#sessionId)
5681
6717
  this.#ui.requestRender();
5682
6718
  });
@@ -5712,7 +6748,7 @@ class NekoTuiApp {
5712
6748
  this.#items.set(item.key, { kind: "tool", component });
5713
6749
  return;
5714
6750
  }
5715
- const component = new Text5(theme.dim(item.text), 1, 0);
6751
+ const component = new Text6(theme.dim(item.text), 1, 0);
5716
6752
  this.#chat.addChild(component);
5717
6753
  this.#items.set(item.key, { kind: "system", component });
5718
6754
  }
@@ -5876,7 +6912,7 @@ async function initialSessionId(gateway, cwd) {
5876
6912
  const first = recentlyVisited(sessions, cwd)[0];
5877
6913
  if (first !== undefined)
5878
6914
  return first.id;
5879
- return (await gateway.client.management.create({ ...cwd !== undefined ? { cwd } : {} }, BACKGROUND_CONTEXT4)).id;
6915
+ return (await gateway.client.management.create({ ...cwd !== undefined ? { cwd } : {} }, BACKGROUND_CONTEXT5)).id;
5880
6916
  }
5881
6917
  var EXIT_COMMANDS, SESSIONS_COMMANDS, NEW_SESSION_VALUE = "__new__";
5882
6918
  var init_tui = __esm(() => {
@@ -5894,6 +6930,7 @@ var init_tui = __esm(() => {
5894
6930
  init_image_input();
5895
6931
  init_labels();
5896
6932
  init_layout();
6933
+ init_skill_market_ui();
5897
6934
  init_theme();
5898
6935
  init_workspace2();
5899
6936
  EXIT_COMMANDS = new Set(["/exit", "/quit"]);
@@ -6011,11 +7048,11 @@ var init_open_browser = () => {};
6011
7048
 
6012
7049
  // packages/web/src/gateway-spawn.ts
6013
7050
  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";
7051
+ import { randomUUID as randomUUID3 } from "crypto";
7052
+ import { mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "fs/promises";
7053
+ import { dirname as dirname9, join as join12 } from "path";
6017
7054
  async function ensureNekoGateway(options) {
6018
- const nekoDir = dirname8(options.storagePath);
7055
+ const nekoDir = dirname9(options.storagePath);
6019
7056
  const serverId = options.serverId ?? await loadOrCreateServerId2(nekoDir);
6020
7057
  if (process.platform === "win32") {
6021
7058
  const discovered = await readGatewayPortFile(nekoDir, serverId);
@@ -6054,7 +7091,7 @@ async function ensureNekoGateway(options) {
6054
7091
  throw error;
6055
7092
  }
6056
7093
  }
6057
- const socketPath = options.socketPath ?? join11(nekoDir, `${serverId}.sock`);
7094
+ const socketPath = options.socketPath ?? join12(nekoDir, `${serverId}.sock`);
6058
7095
  const host = options.host ?? "127.0.0.1";
6059
7096
  const port = options.port ?? 8321;
6060
7097
  if (await probeGateway(socketPath, serverId)) {
@@ -6157,15 +7194,15 @@ function makeHandle(serverId, socketPath, wsUrl, spawned, nekoDir) {
6157
7194
  };
6158
7195
  }
6159
7196
  async function loadOrCreateServerId2(nekoDir) {
6160
- const file = join11(nekoDir, SERVER_ID_FILENAME3);
7197
+ const file = join12(nekoDir, SERVER_ID_FILENAME3);
6161
7198
  try {
6162
7199
  const existing = (await readFile7(file, "utf8")).trim();
6163
7200
  if (existing !== "")
6164
7201
  return existing;
6165
7202
  } catch {}
6166
- const created = randomUUID2();
6167
- await mkdir3(nekoDir, { recursive: true, mode: 448 });
6168
- await writeFile3(file, `${created}
7203
+ const created = randomUUID3();
7204
+ await mkdir4(nekoDir, { recursive: true, mode: 448 });
7205
+ await writeFile4(file, `${created}
6169
7206
  `, { mode: 384 });
6170
7207
  return created;
6171
7208
  }
@@ -6179,10 +7216,10 @@ var init_gateway_spawn = __esm(() => {
6179
7216
 
6180
7217
  // packages/web/src/server.ts
6181
7218
  import { createReadStream } from "fs";
6182
- import { stat as stat4 } from "fs/promises";
7219
+ import { stat as stat5 } from "fs/promises";
6183
7220
  import { createServer } from "http";
6184
7221
  import { extname as extname2, resolve as resolve6, sep as sep2 } from "path";
6185
- import { fileURLToPath as fileURLToPath2 } from "url";
7222
+ import { fileURLToPath as fileURLToPath3 } from "url";
6186
7223
  async function startWebServer(options = {}) {
6187
7224
  const staticDir = resolve6(options.staticDir ?? DEFAULT_STATIC_DIR);
6188
7225
  const workspaceRoot = normalizeWorkspace(options.gateway?.cwd ?? process.cwd());
@@ -6245,18 +7282,18 @@ async function serveStatic(pathname, method, response, staticDir) {
6245
7282
  return;
6246
7283
  }
6247
7284
  let file = candidate;
6248
- let stats = await stat4(file).catch(() => {
7285
+ let stats = await stat5(file).catch(() => {
6249
7286
  return;
6250
7287
  });
6251
7288
  if (stats?.isDirectory()) {
6252
7289
  file = resolve6(file, "index.html");
6253
- stats = await stat4(file).catch(() => {
7290
+ stats = await stat5(file).catch(() => {
6254
7291
  return;
6255
7292
  });
6256
7293
  }
6257
7294
  if (stats === undefined && extname2(candidate) === "") {
6258
7295
  file = resolve6(staticDir, "index.html");
6259
- stats = await stat4(file).catch(() => {
7296
+ stats = await stat5(file).catch(() => {
6260
7297
  return;
6261
7298
  });
6262
7299
  }
@@ -6281,7 +7318,7 @@ var DEFAULT_PORT2 = 8322, DEFAULT_HOST2 = "127.0.0.1", DEFAULT_STATIC_DIR, MIME;
6281
7318
  var init_server = __esm(() => {
6282
7319
  init_src();
6283
7320
  init_gateway_spawn();
6284
- DEFAULT_STATIC_DIR = fileURLToPath2(new URL("./client", import.meta.url));
7321
+ DEFAULT_STATIC_DIR = fileURLToPath3(new URL("./client", import.meta.url));
6285
7322
  MIME = {
6286
7323
  ".html": "text/html; charset=utf-8",
6287
7324
  ".js": "text/javascript; charset=utf-8",
@@ -6615,7 +7652,7 @@ switch (command2) {
6615
7652
  }
6616
7653
  case "--version":
6617
7654
  case "-v":
6618
- console.log("0.2.2");
7655
+ console.log("0.2.4");
6619
7656
  break;
6620
7657
  case undefined:
6621
7658
  case "-h":