@exulu/backend 2.2.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  LITELLM_UI_PATH,
15
15
  LiteLLMAdminError,
16
16
  OAUTH_CALLBACK_PATH,
17
+ PreviewRenderError,
17
18
  ResolveModelError,
18
19
  STATISTICS_TYPE_ENUM,
19
20
  authentication,
@@ -37,6 +38,7 @@ import {
37
38
  exchangeCodeForTokens,
38
39
  exuluApp,
39
40
  getBudgetSettings,
41
+ getPdfPreviewBytes,
40
42
  getPresignedUrl,
41
43
  getS3ObjectBytes,
42
44
  getS3ObjectContent,
@@ -47,6 +49,7 @@ import {
47
49
  getToken,
48
50
  getUserBudgetView,
49
51
  guardExtractedFileText,
52
+ imageAttachmentGuard,
50
53
  invalidateBudgetCaches,
51
54
  isLiteLLMEnabled,
52
55
  listS3ObjectsByPrefix,
@@ -74,7 +77,7 @@ import {
74
77
  upsertBudget,
75
78
  waitForLiteLLMReady,
76
79
  withRetry
77
- } from "./chunk-Y7JPNBFM.js";
80
+ } from "./chunk-2242AI5L.js";
78
81
  import {
79
82
  findLiteLLMModel
80
83
  } from "./chunk-7CCMW3IW.js";
@@ -11279,76 +11282,6 @@ async function parseSkillFrontmatter(zipBytes) {
11279
11282
  return { name: fm.name, description: fm.description };
11280
11283
  }
11281
11284
 
11282
- // src/sessions/pdf-preview-cache.ts
11283
- import { exec } from "child_process";
11284
- import { existsSync as existsSync3 } from "fs";
11285
- import { mkdir, readFile, rename, rm, writeFile } from "fs/promises";
11286
- import { extname, join } from "path";
11287
- import { promisify } from "util";
11288
- var execAsync = promisify(exec);
11289
- var CACHE_ROOT = "/tmp/exulu-pdf-cache";
11290
- var CACHE_IN = join(CACHE_ROOT, "_in");
11291
- var CACHE_OUT = join(CACHE_ROOT, "_out");
11292
- var inFlight = /* @__PURE__ */ new Map();
11293
- var PreviewRenderError = class extends Error {
11294
- constructor(message) {
11295
- super(message);
11296
- this.name = "PreviewRenderError";
11297
- }
11298
- };
11299
- function sanitizeEtag(raw) {
11300
- return raw.replace(/^"|"$/g, "").replace(/[^a-zA-Z0-9_-]/g, "_");
11301
- }
11302
- async function getPdfPreviewBytes(opts) {
11303
- const { sourceKey, etag, config } = opts;
11304
- const safeEtag = sanitizeEtag(etag);
11305
- if (!safeEtag) {
11306
- throw new PreviewRenderError(`Invalid ETag for ${sourceKey}`);
11307
- }
11308
- const cachedPath = join(CACHE_ROOT, `${safeEtag}.pdf`);
11309
- if (existsSync3(cachedPath)) {
11310
- return readFile(cachedPath);
11311
- }
11312
- const existing = inFlight.get(safeEtag);
11313
- if (existing) return existing;
11314
- const promise = (async () => {
11315
- try {
11316
- await mkdir(CACHE_IN, { recursive: true });
11317
- await mkdir(CACHE_OUT, { recursive: true });
11318
- const ext = (extname(sourceKey) || ".docx").toLowerCase();
11319
- const inputPath = join(CACHE_IN, `${safeEtag}${ext}`);
11320
- const outputPath = join(CACHE_OUT, `${safeEtag}.pdf`);
11321
- try {
11322
- const bytes = await getS3ObjectBytes(sourceKey, config);
11323
- await writeFile(inputPath, bytes);
11324
- try {
11325
- await execAsync(
11326
- `soffice --headless --convert-to pdf "${inputPath}" --outdir "${CACHE_OUT}"`,
11327
- { timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }
11328
- );
11329
- } catch (err) {
11330
- throw new PreviewRenderError(
11331
- `LibreOffice conversion failed for ${sourceKey} (etag ${etag}): ${err?.stderr ?? err?.message ?? "unknown error"}`
11332
- );
11333
- }
11334
- if (!existsSync3(outputPath)) {
11335
- throw new PreviewRenderError(
11336
- `LibreOffice produced no output for ${sourceKey} (etag ${etag})`
11337
- );
11338
- }
11339
- await rename(outputPath, cachedPath);
11340
- return await readFile(cachedPath);
11341
- } finally {
11342
- await rm(inputPath, { force: true });
11343
- }
11344
- } finally {
11345
- inFlight.delete(safeEtag);
11346
- }
11347
- })();
11348
- inFlight.set(safeEtag, promise);
11349
- return promise;
11350
- }
11351
-
11352
11285
  // src/exulu/routes.ts
11353
11286
  import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
11354
11287
  import bodyParser from "body-parser";
@@ -11964,7 +11897,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
11964
11897
  // Stop after the image_generation tool fires — the widget IS the
11965
11898
  // assistant's response, no follow-up text turn is wanted (same
11966
11899
  // reasoning as question_ask: the UI artifact is the message).
11967
- prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
11900
+ prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
11968
11901
  stopWhen: [stepCountIs(turnBudget), hasToolCall("image_generation")]
11969
11902
  });
11970
11903
  console.log("[EXULU] Output: " + JSON.stringify(output, null, 2));
@@ -12027,7 +11960,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
12027
11960
  }),
12028
11961
  maxRetries: 2,
12029
11962
  tools,
12030
- prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
11963
+ prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
12031
11964
  stopWhen: [stepCountIs(turnBudget), hasToolCall("image_generation")]
12032
11965
  });
12033
11966
  if (statistics) {
@@ -12442,7 +12375,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
12442
12375
  );
12443
12376
  },
12444
12377
  // todo allow configuring the step budget per skill
12445
- prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
12378
+ prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
12446
12379
  stopWhen: [stepCountIs(turnBudget), hasToolCall("image_generation")]
12447
12380
  });
12448
12381
  return {
@@ -12926,7 +12859,7 @@ async function editImage(args) {
12926
12859
  }
12927
12860
 
12928
12861
  // src/exulu/litellm/parse-image-models.ts
12929
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
12862
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
12930
12863
  var stripComment3 = (line) => {
12931
12864
  const idx = line.indexOf("#");
12932
12865
  return idx >= 0 ? line.slice(0, idx) : line;
@@ -12949,7 +12882,7 @@ var parseInt103 = (raw) => {
12949
12882
  return Number.isInteger(n) ? n : void 0;
12950
12883
  };
12951
12884
  var parseImageGenerationModels = (configPath) => {
12952
- if (!existsSync4(configPath)) return [];
12885
+ if (!existsSync3(configPath)) return [];
12953
12886
  const text = readFileSync3(configPath, "utf8");
12954
12887
  const lines = text.split("\n");
12955
12888
  const entries = [];
@@ -13573,7 +13506,7 @@ ${project.description}` : ""}` : "",
13573
13506
  messages: coreMessages,
13574
13507
  tools: hasTools ? activeTools : void 0,
13575
13508
  maxRetries: 2,
13576
- prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
13509
+ prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
13577
13510
  stopWhen: clientTools.length > 0 ? void 0 : [stepCountIs2(turnBudget)],
13578
13511
  onError: (error) => {
13579
13512
  console.error("[OPENAI GATEWAY] stream error:", error);
@@ -13613,7 +13546,7 @@ ${project.description}` : ""}` : "",
13613
13546
  messages: coreMessages,
13614
13547
  tools: hasTools ? activeTools : void 0,
13615
13548
  maxRetries: 2,
13616
- prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
13549
+ prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
13617
13550
  stopWhen: clientTools.length > 0 ? void 0 : [stepCountIs2(turnBudget)]
13618
13551
  });
13619
13552
  res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
@@ -13885,34 +13818,34 @@ var CLIENT_MANIFEST = [
13885
13818
  // exception: nested under agent/
13886
13819
  ];
13887
13820
 
13888
- // src/skills/bootstrap/exulu-sh.generated.ts
13889
- var EXULU_SH_B64 = "IyEvYmluL3NoCiMgZXh1bHUg4oCUIGhlbHBlciBmb3IgdGhlIEV4dWx1IGNlbnRyYWwgc2tpbGwgbGlicmFyeS4gVGhlIGFnZW50IGludm9rZXMgdGhpcwojIChuZXZlciByYXcgY3VybCk6IHRoZSB0b2tlbiBpcyByZWFkIGZyb20gdGhlIGNvbmZpZyBmaWxlIGhlcmUgYW5kIHNlbnQgdmlhCiMgYHgtYXBpLWtleTogQmVhcmVyYCwgc28gaXQgbmV2ZXIgZW50ZXJzIHRoZSBtb2RlbCBjb250ZXh0LiBDbGllbnQgZmFuLW91dAojIChjb3B5L3N5bWxpbmsgYWNyb3NzIGFnZW50IGNsaWVudHMpIGlzIGRldGVybWluaXN0aWMuCnNldCAtZXUKCkNPTkZJR19ESVI9IiRIT01FLy5jb25maWcvZXh1bHUiCkNPTkZJR19GSUxFPSIkQ09ORklHX0RJUi9za2lsbHMuanNvbiIKCmRpZSgpIHsgcHJpbnRmICdleHVsdTogJXNcbicgIiQqIiA+JjI7IGV4aXQgMTsgfQppbmZvKCkgeyBwcmludGYgJyVzXG4nICIkKiIgPiYyOyB9Cgpqc29uX3N0cigpIHsgIyBqc29uX3N0ciA8a2V5PiA8ZmlsZT4g4oCUIGZsYXQgImtleSI6InZhbHVlIgogIHNlZCAtbiAicy8uKlwiJDFcIltbOnNwYWNlOl1dKjpbWzpzcGFjZTpdXSpcIlxcKFteXCJdKlxcKVwiLiovXFwxL3AiICIkMiIgfCBoZWFkIC1uMQp9CgpbIC1mICIkQ09ORklHX0ZJTEUiIF0gfHwgZGllICJub3QgY29uZmlndXJlZCDigJQgcnVuOiBjdXJsIC1mc1NMIDxiYXNlX3VybD4vYXBpL3NraWxscy9pbnN0YWxsLnNoIHwgc2giCgpCQVNFX1VSTD0iJChqc29uX3N0ciBiYXNlX3VybCAiJENPTkZJR19GSUxFIikiCkJBQ0tFTkQ9IiQoanNvbl9zdHIgYmFja2VuZCAiJENPTkZJR19GSUxFIikiCkFQSV9LRVk9IiQoanNvbl9zdHIgYXBpX2tleSAiJENPTkZJR19GSUxFIikiCkxJTktfTU9ERT0iJChqc29uX3N0ciBsaW5rX21vZGUgIiRDT05GSUdfRklMRSIpIgpTQ09QRT0iJChqc29uX3N0ciBzY29wZSAiJENPTkZJR19GSUxFIikiCkNMSUVOVFM9IiQoc2VkIC1uICdzLy4qImNsaWVudHMiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlxbXChbXl1dKlwpXF0uKi9cMS9wJyAiJENPTkZJR19GSUxFIiB8IHRyICcsJyAnICcgfCB0ciAtZCAnIicgfCB0ciAtcyAnICcpIgoKWyAtbiAiJENMSUVOVFMiIF0gfHwgQ0xJRU5UUz0iYWdlbnRzIgpbIC1uICIkTElOS19NT0RFIiBdIHx8IExJTktfTU9ERT0iY29weSIKWyAtbiAiJFNDT1BFIiBdIHx8IFNDT1BFPSJwcm9qZWN0IgoKaWYgWyAteiAiJEJBQ0tFTkQiIF07IHRoZW4KICBbIC1uICIkQkFTRV9VUkwiIF0gfHwgZGllICJjb25maWcgaGFzIG5vIGJhY2tlbmQgYW5kIG5vIGJhc2VfdXJsIgogIEJBQ0tFTkQ9IiQoY3VybCAtZnNTTCAiJEJBU0VfVVJML2FwaS9jb25maWciIHwgc2VkIC1uICdzLy4qImJhY2tlbmQiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKiJcKFteIl0qXCkiLiovXDEvcCcgfCBoZWFkIC1uMSkiCiAgWyAtbiAiJEJBQ0tFTkQiIF0gfHwgZGllICJjb3VsZCBub3QgcmVzb2x2ZSBiYWNrZW5kIGZyb20gJEJBU0VfVVJML2FwaS9jb25maWciCmZpCkJBQ0tFTkQ9IiQocHJpbnRmICclcycgIiRCQUNLRU5EIiB8IHNlZCAnczovKiQ6OicpIgoKUk9PVD0iJFBXRCIKWyAiJFNDT1BFIiA9ICJob21lIiBdICYmIFJPT1Q9IiRIT01FIgoKIyBkaXJfZm9yIENMSUVOVF9JRCAtPiByZWxhdGl2ZSBza2lsbCBkaXIgKGdlbmVyYXRlZCBmcm9tIHRoZSBtYW5pZmVzdCBpbiB0aGUKIyByZWFsIGJ1aWxkOyBhIHJlcHJlc2VudGF0aXZlIHN1YnNldCBoZXJlIGZvciBsb2NhbCB0ZXN0aW5nKS4KZGlyX2ZvcigpIHsKICBjYXNlICIkMSIgaW4KX19ESVJfRk9SX0NBU0VTX18KICAgICopIHJldHVybiAxIDs7CiAgZXNhYwp9CgphcGkoKSB7ICMgYXBpIDxNRVRIT0Q+IDxwYXRoPiBbZXh0cmEgY3VybCBhcmdzLi4uXSAtPiBib2R5IG9uIHN0ZG91dAogIG09IiQxIjsgcD0iJDIiOyBzaGlmdCAyCiAgY3VybCAtZnNTIC1YICIkbSIgIiRCQUNLRU5EJHAiIC1IICJ4LWFwaS1rZXk6IEJlYXJlciAkQVBJX0tFWSIgIiRAIgp9CgptZXRhX3ZlcnNpb24oKSB7ICMgbWV0YV92ZXJzaW9uIDxuYW1lPiAtPiBjdXJyZW50X3ZlcnNpb24gZnJvbSByZWdpc3RyeQogIGFwaSBHRVQgIi9za2lsbHMvcmVnaXN0cnkvJDEiIDI+L2Rldi9udWxsIFwKICAgIHwgc2VkIC1uICdzLy4qImN1cnJlbnRfdmVyc2lvbiJbWzpzcGFjZTpdXSo6W1s6c3BhY2U6XV0qXChbMC05XVswLTldKlwpLiovXDEvcCcgfCBoZWFkIC1uMQp9CgpfbWFuYWdlZCgpIHsgIyBfbWFuYWdlZCA8ZGlyPiAtPiAwIGlmIHNhZmUgdG8gb3ZlcndyaXRlIChvdXJzIG9yIHN5bWxpbmsgb3IgYWJzZW50KQogIGQ9IiQxIgogIGlmIFsgLWUgIiRkIiBdICYmIFsgISAtTCAiJGQiIF0gJiYgWyAhIC1mICIkZC8uZXh1bHUtc2tpbGwuanNvbiIgXTsgdGhlbgogICAgaW5mbyAic2tpcCAkZCAoZXhpc3RzLCBub3QgbWFuYWdlZCBieSBleHVsdSkiOyByZXR1cm4gMQogIGZpCiAgcmV0dXJuIDAKfQoKX3B1dF9yZWFsKCkgeyAjIF9wdXRfcmVhbCA8ZGVzdD4gPHNyY2Rpcj4gPG1hcmtlci1qc29uPgogIF9tYW5hZ2VkICIkMSIgfHwgcmV0dXJuIDAKICBybSAtcmYgIiQxIjsgbWtkaXIgLXAgIiQxIjsgY3AgLVIgIiQyLy4iICIkMS8iCiAgcHJpbnRmICclc1xuJyAiJDMiID4gIiQxLy5leHVsdS1za2lsbC5qc29uIgp9CgpwbGFjZV9za2lsbCgpIHsgIyBwbGFjZV9za2lsbCA8bmFtZT4gPHNyY2Rpcj4gPHZlcnNpb24+CiAgcG5hbWU9IiQxIjsgcHNyYz0iJDIiOyBwdmVyPSIkezM6LTF9IgogIG1hcmtlcj0neyAibmFtZSI6ICInIiRwbmFtZSInIiwgInZlcnNpb24iOiAnIiRwdmVyIicsICJzb3VyY2UiOiAiJyIkQkFDS0VORCInIiB9JwogIGNhbm9uPSIkUk9PVC8uYWdlbnRzL3NraWxscy8kcG5hbWUiCiAgWyAiJExJTktfTU9ERSIgPSAic3ltbGluayIgXSAmJiBfcHV0X3JlYWwgIiRjYW5vbiIgIiRwc3JjIiAiJG1hcmtlciIKICBmb3IgaWQgaW4gJENMSUVOVFM7IGRvCiAgICBkPSIkKGRpcl9mb3IgIiRpZCIpIiB8fCB7IGluZm8gInVua25vd24gY2xpZW50OiAkaWQiOyBjb250aW51ZTsgfQogICAgcGFyZW50PSIkUk9PVC8kZCI7IGRlc3Q9IiRwYXJlbnQvJHBuYW1lIgogICAgaWYgWyAiJExJTktfTU9ERSIgPSAic3ltbGluayIgXSAmJiBbICIkaWQiICE9ICJhZ2VudHMiIF07IHRoZW4KICAgICAgX21hbmFnZWQgIiRkZXN0IiB8fCBjb250aW51ZQogICAgICBta2RpciAtcCAiJHBhcmVudCI7IHJtIC1yZiAiJGRlc3QiCiAgICAgIGlmIGxuIC1zICIkY2Fub24iICIkZGVzdCIgMj4vZGV2L251bGw7IHRoZW4KICAgICAgICBpbmZvICJsaW5rZWQgJGRlc3QgLT4gJGNhbm9uIgogICAgICBlbHNlCiAgICAgICAgaW5mbyAic3ltbGluayB1bnN1cHBvcnRlZCBhdCAkZGVzdDsgY29weWluZyIKICAgICAgICBfcHV0X3JlYWwgIiRkZXN0IiAiJHBzcmMiICIkbWFya2VyIgogICAgICBmaQogICAgZWxpZiBbICIkTElOS19NT0RFIiA9ICJjb3B5IiBdOyB0aGVuCiAgICAgIF9wdXRfcmVhbCAiJGRlc3QiICIkcHNyYyIgIiRtYXJrZXIiCiAgICBmaQogICAgIyBzeW1saW5rICsgYWdlbnRzOiBhbHJlYWR5IHBsYWNlZCBhcyB0aGUgY2Fub25pY2FsIHN0b3JlIGFib3ZlLgogIGRvbmUKfQoKaW5zdGFsbGVkX25hbWVzKCkgeyAjIHVuaXF1ZSBza2lsbCBuYW1lcyB0aGF0IGNhcnJ5IG91ciBtYXJrZXIgdW5kZXIgUk9PVAogIHsgZmluZCAiJFJPT1QvLmFnZW50cy9za2lsbHMiIC1tYXhkZXB0aCAyIC1uYW1lIC5leHVsdS1za2lsbC5qc29uIDI+L2Rldi9udWxsCiAgICBmb3IgaWQgaW4gJENMSUVOVFM7IGRvCiAgICAgIGQ9IiQoZGlyX2ZvciAiJGlkIikiIHx8IGNvbnRpbnVlCiAgICAgIGZpbmQgIiRST09ULyRkIiAtbWF4ZGVwdGggMiAtbmFtZSAuZXh1bHUtc2tpbGwuanNvbiAyPi9kZXYvbnVsbAogICAgZG9uZQogIH0gfCB3aGlsZSByZWFkIC1yIG07IGRvIGpzb25fc3RyIG5hbWUgIiRtIjsgZG9uZSB8IHNvcnQgLXUKfQoKbWFya2VyX3ZlcnNpb24oKSB7ICMgbWFya2VyX3ZlcnNpb24gPG5hbWU+CiAgZm9yIGJhc2UgaW4gIiRST09ULy5hZ2VudHMvc2tpbGxzIjsgZG8KICAgIFsgLWYgIiRiYXNlLyQxLy5leHVsdS1za2lsbC5qc29uIiBdICYmIHsganNvbl9zdHIgdmVyc2lvbiAiJGJhc2UvJDEvLmV4dWx1LXNraWxsLmpzb24iOyByZXR1cm47IH0KICBkb25lCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgY29udGludWUKICAgIGY9IiRST09ULyRkLyQxLy5leHVsdS1za2lsbC5qc29uIgogICAgWyAtZiAiJGYiIF0gJiYgeyBqc29uX3N0ciB2ZXJzaW9uICIkZiI7IHJldHVybjsgfQogIGRvbmUKfQoKZG9faW5zdGFsbCgpIHsgIyBkb19pbnN0YWxsIDxuYW1lPgogIG5hbWU9IiQxIgogIFRNUD0iJChta3RlbXAgLWQpIgogIGFwaSBHRVQgIi9za2lsbHMvcmVnaXN0cnkvJG5hbWUvZG93bmxvYWQiIC0tb3V0cHV0ICIkVE1QL3NraWxsLnppcCIgXAogICAgfHwgeyBybSAtcmYgIiRUTVAiOyBkaWUgImRvd25sb2FkIGZhaWxlZCBmb3IgJyRuYW1lJyAoNDAzID0gbm8gYWNjZXNzLCA0MDQgPSB1bmtub3duKSI7IH0KICBta2RpciAtcCAiJFRNUC94IgogIHVuemlwIC1xICIkVE1QL3NraWxsLnppcCIgLWQgIiRUTVAveCIgfHwgeyBybSAtcmYgIiRUTVAiOyBkaWUgImJhZCBhcmNoaXZlIGZvciAnJG5hbWUnIjsgfQogIHNyYz0iJChmaW5kICIkVE1QL3giIC1taW5kZXB0aCAxIC1tYXhkZXB0aCAxIC10eXBlIGQgfCBoZWFkIC1uMSkiCiAgWyAtbiAiJHNyYyIgXSB8fCB7IHJtIC1yZiAiJFRNUCI7IGRpZSAidW5leHBlY3RlZCBhcmNoaXZlIGxheW91dCBmb3IgJyRuYW1lJyI7IH0KICB2ZXI9IiQobWV0YV92ZXJzaW9uICIkbmFtZSIpIgogIHBsYWNlX3NraWxsICIkbmFtZSIgIiRzcmMiICIke3ZlcjotMX0iCiAgcm0gLXJmICIkVE1QIgogIGluZm8gImluc3RhbGxlZCAkbmFtZSAodiR7dmVyOi0xfSkgWyRMSU5LX01PREVdIGludG86ICRDTElFTlRTIgp9Cgp1c2FnZSgpIHsKICBjYXQgPiYyIDw8RU9GCmV4dWx1IOKAlCBFeHVsdSBza2lsbCBsaWJyYXJ5IGhlbHBlcgogIGV4dWx1IGxpc3QgICAgICAgICAgICAgICAgIGxpc3Qgc2tpbGxzIHlvdSBjYW4gYWNjZXNzIChKU09OKQogIGV4dWx1IGdldCA8bmFtZT4gICAgICAgICAgIHNob3cgb25lIHNraWxsJ3MgbWV0YWRhdGEgKEpTT04pCiAgZXh1bHUgaW5zdGFsbCA8bmFtZT4gICAgICAgaW5zdGFsbC9yZWZyZXNoIGEgc2tpbGwgaW50byB5b3VyIGFnZW50IGNsaWVudHMKICBleHVsdSB1cGRhdGUgWzxuYW1lPl0gICAgICB1cGRhdGUgaW5zdGFsbGVkIHNraWxscyAoYWxsLCBvciBvbmUpIHRvIGxhdGVzdAogIGV4dWx1IHB1Ymxpc2ggPG5hbWU+IDxkaXI+IHB1Ymxpc2ggYSBsb2NhbCBza2lsbCBmb2xkZXIgYXMgPG5hbWU+CiAgZXh1bHUgY29uZmlnICAgICAgICAgICAgICAgc2hvdyByZXNvbHZlZCBiYWNrZW5kIC8gc2NvcGUgLyBjbGllbnRzIChubyBzZWNyZXRzKQpFT0YKfQoKY21kPSIkezE6LWhlbHB9IgpbICQjIC1ndCAwIF0gJiYgc2hpZnQgfHwgdHJ1ZQoKY2FzZSAiJGNtZCIgaW4KICBsaXN0KSBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5IiA7OwogIGdldCkgWyAkIyAtZ2UgMSBdIHx8IGRpZSAidXNhZ2U6IGV4dWx1IGdldCA8bmFtZT4iOyBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyQxIiA7OwogIGluc3RhbGwpIFsgJCMgLWdlIDEgXSB8fCBkaWUgInVzYWdlOiBleHVsdSBpbnN0YWxsIDxuYW1lPiI7IGRvX2luc3RhbGwgIiQxIiA7OwogIHVwZGF0ZSkKICAgIGlmIFsgJCMgLWdlIDEgXTsgdGhlbiBuYW1lcz0iJDEiOyBlbHNlIG5hbWVzPSIkKGluc3RhbGxlZF9uYW1lcykiOyBmaQogICAgWyAtbiAiJG5hbWVzIiBdIHx8IHsgaW5mbyAibm8gZXh1bHUtbWFuYWdlZCBza2lsbHMgZm91bmQgdW5kZXIgJFJPT1QiOyBleGl0IDA7IH0KICAgIGZvciBuIGluICRuYW1lczsgZG8KICAgICAgY3VyPSIkKG1hcmtlcl92ZXJzaW9uICIkbiIpIgogICAgICBsYXRlc3Q9IiQobWV0YV92ZXJzaW9uICIkbiIpIgogICAgICBbIC1uICIkbGF0ZXN0IiBdIHx8IHsgaW5mbyAic2tpcCAkbiAobm90IGluIHJlZ2lzdHJ5KSI7IGNvbnRpbnVlOyB9CiAgICAgIGlmIFsgLXogIiRjdXIiIF0gfHwgWyAiJGxhdGVzdCIgLWd0ICIkY3VyIiBdIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAidXBkYXRpbmcgJG46IHYke2N1cjotP30gLT4gdiRsYXRlc3QiOyBkb19pbnN0YWxsICIkbiIKICAgICAgZWxzZQogICAgICAgIGluZm8gIiRuIHVwIHRvIGRhdGUgKHYkY3VyKSIKICAgICAgZmkKICAgIGRvbmUKICAgIDs7CiAgcHVibGlzaCkKICAgIFsgJCMgLWdlIDIgXSB8fCBkaWUgInVzYWdlOiBleHVsdSBwdWJsaXNoIDxuYW1lPiA8ZGlyPiIKICAgIG5hbWU9IiQxIjsgZm9sZGVyPSIkMiIKICAgIFsgLWQgIiRmb2xkZXIiIF0gfHwgZGllICJubyBzdWNoIGZvbGRlcjogJGZvbGRlciIKICAgIFsgLWYgIiRmb2xkZXIvU0tJTEwubWQiIF0gfHwgZGllICIkZm9sZGVyIGhhcyBubyBTS0lMTC5tZCBhdCBpdHMgcm9vdCIKICAgIGNvbW1hbmQgLXYgemlwID4vZGV2L251bGwgMj4mMSB8fCBkaWUgInRoZSAnemlwJyBjb21tYW5kIGlzIHJlcXVpcmVkIHRvIHB1Ymxpc2giCiAgICBUTVA9IiQobWt0ZW1wIC1kKSIKICAgICggY2QgIiQoZGlybmFtZSAiJGZvbGRlciIpIiBcCiAgICAgICYmIHppcCAtcSAtciAtWCAiJFRNUC9za2lsbC56aXAiICIkKGJhc2VuYW1lICIkZm9sZGVyIikiIFwKICAgICAgICAgICAteCAnKi8uZXh1bHUtc2tpbGwuanNvbicgJyovLmdpdC8qJyAnKi5EU19TdG9yZScgJyovX19NQUNPU1gvKicgKSBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJjb3VsZCBub3QgemlwICRmb2xkZXIiOyB9CiAgICBhcGkgUE9TVCAiL3NraWxscy9yZWdpc3RyeS8kbmFtZSIgLUggIkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vemlwIiBcCiAgICAgIC0tZGF0YS1iaW5hcnkgQCIkVE1QL3NraWxsLnppcCIgXAogICAgICB8fCB7IHJtIC1yZiAiJFRNUCI7IGRpZSAicHVibGlzaCBmYWlsZWQgKDQwMyA9IG5vIHdyaXRlIGFjY2VzcywgNDA5ID0gbmFtZSB0YWtlbikiOyB9CiAgICBybSAtcmYgIiRUTVAiCiAgICBpbmZvICJwdWJsaXNoZWQgJG5hbWUiCiAgICA7OwogIGNvbmZpZykKICAgIHByaW50ZiAnYmFja2VuZD0lc1xuc2NvcGU9JXNcbmxpbmtfbW9kZT0lc1xuY2xpZW50cz0lc1xuYXBpX2tleT0lc1xuJyBcCiAgICAgICIkQkFDS0VORCIgIiRTQ09QRSIgIiRMSU5LX01PREUiICIkQ0xJRU5UUyIgXAogICAgICAiJChbIC1uICIkQVBJX0tFWSIgXSAmJiBlY2hvIHNldCB8fCBlY2hvIE1JU1NJTkcpIgogICAgOzsKICBoZWxwfC0taGVscHwtaCkgdXNhZ2UgOzsKICAqKSBpbmZvICJ1bmtub3duIGNvbW1hbmQ6ICRjbWQiOyB1c2FnZTsgZXhpdCAyIDs7CmVzYWMK";
13821
+ // src/skills/bootstrap/imp-sh.generated.ts
13822
+ var IMP_SH_B64 = "IyEvYmluL3NoCiMgaW1wIOKAlCBoZWxwZXIgZm9yIHRoZSBjZW50cmFsIHNraWxsIGxpYnJhcnkuIFRoZSBhZ2VudCBpbnZva2VzIHRoaXMKIyAobmV2ZXIgcmF3IGN1cmwpOiB0aGUgdG9rZW4gaXMgcmVhZCBmcm9tIHRoZSBjb25maWcgZmlsZSBoZXJlIGFuZCBzZW50IHZpYQojIGB4LWFwaS1rZXk6IEJlYXJlcmAsIHNvIGl0IG5ldmVyIGVudGVycyB0aGUgbW9kZWwgY29udGV4dC4gQ2xpZW50IGZhbi1vdXQKIyAoY29weS9zeW1saW5rIGFjcm9zcyBhZ2VudCBjbGllbnRzKSBpcyBkZXRlcm1pbmlzdGljLgpzZXQgLWV1CgpDT05GSUdfRElSPSIkSE9NRS8uY29uZmlnL2ltcCIKQ09ORklHX0ZJTEU9IiRDT05GSUdfRElSL3NraWxscy5qc29uIgoKZGllKCkgeyBwcmludGYgJ2ltcDogJXNcbicgIiQqIiA+JjI7IGV4aXQgMTsgfQppbmZvKCkgeyBwcmludGYgJyVzXG4nICIkKiIgPiYyOyB9Cgpqc29uX3N0cigpIHsgIyBqc29uX3N0ciA8a2V5PiA8ZmlsZT4g4oCUIGZsYXQgImtleSI6InZhbHVlIgogIHNlZCAtbiAicy8uKlwiJDFcIltbOnNwYWNlOl1dKjpbWzpzcGFjZTpdXSpcIlxcKFteXCJdKlxcKVwiLiovXFwxL3AiICIkMiIgfCBoZWFkIC1uMQp9CgpbIC1mICIkQ09ORklHX0ZJTEUiIF0gfHwgZGllICJub3QgY29uZmlndXJlZCDigJQgcnVuOiBjdXJsIC1mc1NMIDxiYXNlX3VybD4vYXBpL3NraWxscy9pbnN0YWxsLnNoIHwgc2giCgpCQVNFX1VSTD0iJChqc29uX3N0ciBiYXNlX3VybCAiJENPTkZJR19GSUxFIikiCkJBQ0tFTkQ9IiQoanNvbl9zdHIgYmFja2VuZCAiJENPTkZJR19GSUxFIikiCkFQSV9LRVk9IiQoanNvbl9zdHIgYXBpX2tleSAiJENPTkZJR19GSUxFIikiCkxJTktfTU9ERT0iJChqc29uX3N0ciBsaW5rX21vZGUgIiRDT05GSUdfRklMRSIpIgpTQ09QRT0iJChqc29uX3N0ciBzY29wZSAiJENPTkZJR19GSUxFIikiCkNMSUVOVFM9IiQoc2VkIC1uICdzLy4qImNsaWVudHMiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlxbXChbXl1dKlwpXF0uKi9cMS9wJyAiJENPTkZJR19GSUxFIiB8IHRyICcsJyAnICcgfCB0ciAtZCAnIicgfCB0ciAtcyAnICcpIgoKWyAtbiAiJENMSUVOVFMiIF0gfHwgQ0xJRU5UUz0iYWdlbnRzIgpbIC1uICIkTElOS19NT0RFIiBdIHx8IExJTktfTU9ERT0iY29weSIKWyAtbiAiJFNDT1BFIiBdIHx8IFNDT1BFPSJwcm9qZWN0IgoKaWYgWyAteiAiJEJBQ0tFTkQiIF07IHRoZW4KICBbIC1uICIkQkFTRV9VUkwiIF0gfHwgZGllICJjb25maWcgaGFzIG5vIGJhY2tlbmQgYW5kIG5vIGJhc2VfdXJsIgogIEJBQ0tFTkQ9IiQoY3VybCAtZnNTTCAiJEJBU0VfVVJML2FwaS9jb25maWciIHwgc2VkIC1uICdzLy4qImJhY2tlbmQiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKiJcKFteIl0qXCkiLiovXDEvcCcgfCBoZWFkIC1uMSkiCiAgWyAtbiAiJEJBQ0tFTkQiIF0gfHwgZGllICJjb3VsZCBub3QgcmVzb2x2ZSBiYWNrZW5kIGZyb20gJEJBU0VfVVJML2FwaS9jb25maWciCmZpCkJBQ0tFTkQ9IiQocHJpbnRmICclcycgIiRCQUNLRU5EIiB8IHNlZCAnczovKiQ6OicpIgoKUk9PVD0iJFBXRCIKWyAiJFNDT1BFIiA9ICJob21lIiBdICYmIFJPT1Q9IiRIT01FIgoKIyBkaXJfZm9yIENMSUVOVF9JRCAtPiByZWxhdGl2ZSBza2lsbCBkaXIgKGdlbmVyYXRlZCBmcm9tIHRoZSBtYW5pZmVzdCBpbiB0aGUKIyByZWFsIGJ1aWxkOyBhIHJlcHJlc2VudGF0aXZlIHN1YnNldCBoZXJlIGZvciBsb2NhbCB0ZXN0aW5nKS4KZGlyX2ZvcigpIHsKICBjYXNlICIkMSIgaW4KX19ESVJfRk9SX0NBU0VTX18KICAgICopIHJldHVybiAxIDs7CiAgZXNhYwp9CgojIE9uZSAiLi4vIiBwZXIgcGF0aCBjb21wb25lbnQgb2YgJDEgKGEgY2xpZW50IGRpciByZWxhdGl2ZSB0byBST09UKSwgc28KIyBzeW1saW5rcyBhcmUgcmVsYXRpdmUgYW5kIHN1cnZpdmUgYmVpbmcgY29tbWl0dGVkIGFuZCBjaGVja2VkIG91dCBlbHNld2hlcmUuCnJlbF90b19yb290KCkgewogIF91cD0iIjsgX29sZGlmcz0kSUZTOyBJRlM9LwogIGZvciBfc2VnIGluICQxOyBkbyBbIC1uICIkX3NlZyIgXSAmJiBfdXA9Ii4uLyRfdXAiOyBkb25lCiAgSUZTPSRfb2xkaWZzOyBwcmludGYgJyVzJyAiJF91cCIKfQoKYXBpKCkgeyAjIGFwaSA8TUVUSE9EPiA8cGF0aD4gW2V4dHJhIGN1cmwgYXJncy4uLl0gLT4gYm9keSBvbiBzdGRvdXQKICBtPSIkMSI7IHA9IiQyIjsgc2hpZnQgMgogIGN1cmwgLWZzUyAtWCAiJG0iICIkQkFDS0VORCRwIiAtSCAieC1hcGkta2V5OiBCZWFyZXIgJEFQSV9LRVkiICIkQCIKfQoKbWV0YV92ZXJzaW9uKCkgeyAjIG1ldGFfdmVyc2lvbiA8bmFtZT4gLT4gY3VycmVudF92ZXJzaW9uIGZyb20gcmVnaXN0cnkKICBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyQxIiAyPi9kZXYvbnVsbCBcCiAgICB8IHNlZCAtbiAncy8uKiJjdXJyZW50X3ZlcnNpb24iW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlwoWzAtOV1bMC05XSpcKS4qL1wxL3AnIHwgaGVhZCAtbjEKfQoKX21hbmFnZWQoKSB7ICMgX21hbmFnZWQgPGRpcj4gLT4gMCBpZiBzYWZlIHRvIG92ZXJ3cml0ZSAob3VycyBvciBzeW1saW5rIG9yIGFic2VudCkKICBkPSIkMSIKICBpZiBbIC1lICIkZCIgXSAmJiBbICEgLUwgIiRkIiBdICYmIFsgISAtZiAiJGQvLmltcC1za2lsbC5qc29uIiBdOyB0aGVuCiAgICBpbmZvICJza2lwICRkIChleGlzdHMsIG5vdCBtYW5hZ2VkIGJ5IGltcCkiOyByZXR1cm4gMQogIGZpCiAgcmV0dXJuIDAKfQoKX3B1dF9yZWFsKCkgeyAjIF9wdXRfcmVhbCA8ZGVzdD4gPHNyY2Rpcj4gPG1hcmtlci1qc29uPgogIF9tYW5hZ2VkICIkMSIgfHwgcmV0dXJuIDAKICBybSAtcmYgIiQxIjsgbWtkaXIgLXAgIiQxIjsgY3AgLVIgIiQyLy4iICIkMS8iCiAgcHJpbnRmICclc1xuJyAiJDMiID4gIiQxLy5pbXAtc2tpbGwuanNvbiIKfQoKcGxhY2Vfc2tpbGwoKSB7ICMgcGxhY2Vfc2tpbGwgPG5hbWU+IDxzcmNkaXI+IDx2ZXJzaW9uPgogIHBuYW1lPSIkMSI7IHBzcmM9IiQyIjsgcHZlcj0iJHszOi0xfSIKICBtYXJrZXI9J3sgIm5hbWUiOiAiJyIkcG5hbWUiJyIsICJ2ZXJzaW9uIjogJyIkcHZlciInLCAic291cmNlIjogIiciJEJBQ0tFTkQiJyIgfScKICBjYW5vbj0iJFJPT1QvLmFnZW50cy9za2lsbHMvJHBuYW1lIgogIFsgIiRMSU5LX01PREUiID0gInN5bWxpbmsiIF0gJiYgX3B1dF9yZWFsICIkY2Fub24iICIkcHNyYyIgIiRtYXJrZXIiCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgeyBpbmZvICJ1bmtub3duIGNsaWVudDogJGlkIjsgY29udGludWU7IH0KICAgIHBhcmVudD0iJFJPT1QvJGQiOyBkZXN0PSIkcGFyZW50LyRwbmFtZSIKICAgIGlmIFsgIiRMSU5LX01PREUiID0gInN5bWxpbmsiIF0gJiYgWyAiJGlkIiAhPSAiYWdlbnRzIiBdOyB0aGVuCiAgICAgIF9tYW5hZ2VkICIkZGVzdCIgfHwgY29udGludWUKICAgICAgbWtkaXIgLXAgIiRwYXJlbnQiOyBybSAtcmYgIiRkZXN0IgogICAgICByZWw9IiQocmVsX3RvX3Jvb3QgIiRkIikuYWdlbnRzL3NraWxscy8kcG5hbWUiCiAgICAgIGlmIGxuIC1zICIkcmVsIiAiJGRlc3QiIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAibGlua2VkICRkZXN0IC0+ICRyZWwiCiAgICAgIGVsc2UKICAgICAgICBpbmZvICJzeW1saW5rIHVuc3VwcG9ydGVkIGF0ICRkZXN0OyBjb3B5aW5nIgogICAgICAgIF9wdXRfcmVhbCAiJGRlc3QiICIkcHNyYyIgIiRtYXJrZXIiCiAgICAgIGZpCiAgICBlbGlmIFsgIiRMSU5LX01PREUiID0gImNvcHkiIF07IHRoZW4KICAgICAgX3B1dF9yZWFsICIkZGVzdCIgIiRwc3JjIiAiJG1hcmtlciIKICAgIGZpCiAgICAjIHN5bWxpbmsgKyBhZ2VudHM6IGFscmVhZHkgcGxhY2VkIGFzIHRoZSBjYW5vbmljYWwgc3RvcmUgYWJvdmUuCiAgZG9uZQp9CgppbnN0YWxsZWRfbmFtZXMoKSB7ICMgdW5pcXVlIHNraWxsIG5hbWVzIHRoYXQgY2Fycnkgb3VyIG1hcmtlciB1bmRlciBST09UCiAgeyBmaW5kICIkUk9PVC8uYWdlbnRzL3NraWxscyIgLW1heGRlcHRoIDIgLW5hbWUgLmltcC1za2lsbC5qc29uIDI+L2Rldi9udWxsCiAgICBmb3IgaWQgaW4gJENMSUVOVFM7IGRvCiAgICAgIGQ9IiQoZGlyX2ZvciAiJGlkIikiIHx8IGNvbnRpbnVlCiAgICAgIGZpbmQgIiRST09ULyRkIiAtbWF4ZGVwdGggMiAtbmFtZSAuaW1wLXNraWxsLmpzb24gMj4vZGV2L251bGwKICAgIGRvbmUKICB9IHwgd2hpbGUgcmVhZCAtciBtOyBkbyBqc29uX3N0ciBuYW1lICIkbSI7IGRvbmUgfCBzb3J0IC11Cn0KCm1hcmtlcl92ZXJzaW9uKCkgeyAjIG1hcmtlcl92ZXJzaW9uIDxuYW1lPgogIGZvciBiYXNlIGluICIkUk9PVC8uYWdlbnRzL3NraWxscyI7IGRvCiAgICBbIC1mICIkYmFzZS8kMS8uaW1wLXNraWxsLmpzb24iIF0gJiYgeyBqc29uX3N0ciB2ZXJzaW9uICIkYmFzZS8kMS8uaW1wLXNraWxsLmpzb24iOyByZXR1cm47IH0KICBkb25lCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgY29udGludWUKICAgIGY9IiRST09ULyRkLyQxLy5pbXAtc2tpbGwuanNvbiIKICAgIFsgLWYgIiRmIiBdICYmIHsganNvbl9zdHIgdmVyc2lvbiAiJGYiOyByZXR1cm47IH0KICBkb25lCn0KCmRvX2luc3RhbGwoKSB7ICMgZG9faW5zdGFsbCA8bmFtZT4KICBuYW1lPSIkMSIKICBUTVA9IiQobWt0ZW1wIC1kKSIKICBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyRuYW1lL2Rvd25sb2FkIiAtLW91dHB1dCAiJFRNUC9za2lsbC56aXAiIFwKICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJkb3dubG9hZCBmYWlsZWQgZm9yICckbmFtZScgKDQwMyA9IG5vIGFjY2VzcywgNDA0ID0gdW5rbm93bikiOyB9CiAgbWtkaXIgLXAgIiRUTVAveCIKICB1bnppcCAtcSAiJFRNUC9za2lsbC56aXAiIC1kICIkVE1QL3giIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJiYWQgYXJjaGl2ZSBmb3IgJyRuYW1lJyI7IH0KICBzcmM9IiQoZmluZCAiJFRNUC94IiAtbWluZGVwdGggMSAtbWF4ZGVwdGggMSAtdHlwZSBkIHwgaGVhZCAtbjEpIgogIFsgLW4gIiRzcmMiIF0gfHwgeyBybSAtcmYgIiRUTVAiOyBkaWUgInVuZXhwZWN0ZWQgYXJjaGl2ZSBsYXlvdXQgZm9yICckbmFtZSciOyB9CiAgdmVyPSIkKG1ldGFfdmVyc2lvbiAiJG5hbWUiKSIKICBwbGFjZV9za2lsbCAiJG5hbWUiICIkc3JjIiAiJHt2ZXI6LTF9IgogIHJtIC1yZiAiJFRNUCIKICBpbmZvICJpbnN0YWxsZWQgJG5hbWUgKHYke3ZlcjotMX0pIFskTElOS19NT0RFXSBpbnRvOiAkQ0xJRU5UUyIKfQoKdXNhZ2UoKSB7CiAgY2F0ID4mMiA8PEVPRgppbXAg4oCUIHNraWxsIGxpYnJhcnkgaGVscGVyCiAgaW1wIGxpc3QgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0IHNraWxscyB5b3UgY2FuIGFjY2VzcyAoSlNPTikKICBpbXAgZ2V0IDxuYW1lPiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3cgb25lIHNraWxsJ3MgbWV0YWRhdGEgKEpTT04pCiAgaW1wIGluc3RhbGwgPG5hbWU+ICAgICAgICAgICAgICAgICAgICAgICBpbnN0YWxsL3JlZnJlc2ggYSBza2lsbCBpbnRvIHlvdXIgYWdlbnQgY2xpZW50cwogIGltcCB1cGRhdGUgWzxuYW1lPl0gICAgICAgICAgICAgICAgICAgICAgdXBkYXRlIGluc3RhbGxlZCBza2lsbHMgKGFsbCwgb3Igb25lKSB0byBsYXRlc3QKICBpbXAgcHVibGlzaCA8bmFtZT4gPGRpcj4gPHB1YmxpY3xwcml2YXRlPiBwdWJsaXNoIGEgbG9jYWwgc2tpbGwgZm9sZGVyIGFzIDxuYW1lPgogIGltcCBjb25maWcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvdyByZXNvbHZlZCBiYWNrZW5kIC8gc2NvcGUgLyBjbGllbnRzIChubyBzZWNyZXRzKQpFT0YKfQoKY21kPSIkezE6LWhlbHB9IgpbICQjIC1ndCAwIF0gJiYgc2hpZnQgfHwgdHJ1ZQoKY2FzZSAiJGNtZCIgaW4KICBsaXN0KSBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5IiA7OwogIGdldCkgWyAkIyAtZ2UgMSBdIHx8IGRpZSAidXNhZ2U6IGltcCBnZXQgPG5hbWU+IjsgYXBpIEdFVCAiL3NraWxscy9yZWdpc3RyeS8kMSIgOzsKICBpbnN0YWxsKSBbICQjIC1nZSAxIF0gfHwgZGllICJ1c2FnZTogaW1wIGluc3RhbGwgPG5hbWU+IjsgZG9faW5zdGFsbCAiJDEiIDs7CiAgdXBkYXRlKQogICAgaWYgWyAkIyAtZ2UgMSBdOyB0aGVuIG5hbWVzPSIkMSI7IGVsc2UgbmFtZXM9IiQoaW5zdGFsbGVkX25hbWVzKSI7IGZpCiAgICBbIC1uICIkbmFtZXMiIF0gfHwgeyBpbmZvICJubyBpbXAtbWFuYWdlZCBza2lsbHMgZm91bmQgdW5kZXIgJFJPT1QiOyBleGl0IDA7IH0KICAgIGZvciBuIGluICRuYW1lczsgZG8KICAgICAgY3VyPSIkKG1hcmtlcl92ZXJzaW9uICIkbiIpIgogICAgICBsYXRlc3Q9IiQobWV0YV92ZXJzaW9uICIkbiIpIgogICAgICBbIC1uICIkbGF0ZXN0IiBdIHx8IHsgaW5mbyAic2tpcCAkbiAobm90IGluIHJlZ2lzdHJ5KSI7IGNvbnRpbnVlOyB9CiAgICAgIGlmIFsgLXogIiRjdXIiIF0gfHwgWyAiJGxhdGVzdCIgLWd0ICIkY3VyIiBdIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAidXBkYXRpbmcgJG46IHYke2N1cjotP30gLT4gdiRsYXRlc3QiOyBkb19pbnN0YWxsICIkbiIKICAgICAgZWxzZQogICAgICAgIGluZm8gIiRuIHVwIHRvIGRhdGUgKHYkY3VyKSIKICAgICAgZmkKICAgIGRvbmUKICAgIDs7CiAgcHVibGlzaCkKICAgIFsgJCMgLWdlIDMgXSB8fCBkaWUgInVzYWdlOiBpbXAgcHVibGlzaCA8bmFtZT4gPGRpcj4gPHB1YmxpY3xwcml2YXRlPiIKICAgIG5hbWU9IiQxIjsgZm9sZGVyPSIkMiI7IHZpc2liaWxpdHk9IiQzIgogICAgY2FzZSAiJHZpc2liaWxpdHkiIGluCiAgICAgIHB1YmxpY3xwcml2YXRlKSA7OwogICAgICAqKSBkaWUgInZpc2liaWxpdHkgbXVzdCBiZSAncHVibGljJyBvciAncHJpdmF0ZScgKGFzayB0aGUgdXNlciB3aGljaCB0aGV5IHdhbnQpIiA7OwogICAgZXNhYwogICAgWyAtZCAiJGZvbGRlciIgXSB8fCBkaWUgIm5vIHN1Y2ggZm9sZGVyOiAkZm9sZGVyIgogICAgWyAtZiAiJGZvbGRlci9TS0lMTC5tZCIgXSB8fCBkaWUgIiRmb2xkZXIgaGFzIG5vIFNLSUxMLm1kIGF0IGl0cyByb290IgogICAgY29tbWFuZCAtdiB6aXAgPi9kZXYvbnVsbCAyPiYxIHx8IGRpZSAidGhlICd6aXAnIGNvbW1hbmQgaXMgcmVxdWlyZWQgdG8gcHVibGlzaCIKICAgIFRNUD0iJChta3RlbXAgLWQpIgogICAgKCBjZCAiJChkaXJuYW1lICIkZm9sZGVyIikiIFwKICAgICAgJiYgemlwIC1xIC1yIC1YICIkVE1QL3NraWxsLnppcCIgIiQoYmFzZW5hbWUgIiRmb2xkZXIiKSIgXAogICAgICAgICAgIC14ICcqLy5pbXAtc2tpbGwuanNvbicgJyovLmdpdC8qJyAnKi5EU19TdG9yZScgJyovX19NQUNPU1gvKicgKSBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJjb3VsZCBub3QgemlwICRmb2xkZXIiOyB9CiAgICBhcGkgUE9TVCAiL3NraWxscy9yZWdpc3RyeS8kbmFtZT92aXNpYmlsaXR5PSR2aXNpYmlsaXR5IiAtSCAiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi96aXAiIFwKICAgICAgLS1kYXRhLWJpbmFyeSBAIiRUTVAvc2tpbGwuemlwIiBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJwdWJsaXNoIGZhaWxlZCAoNDAzID0gbm8gd3JpdGUgYWNjZXNzLCA0MDkgPSBuYW1lIHRha2VuKSI7IH0KICAgIHJtIC1yZiAiJFRNUCIKICAgIGluZm8gInB1Ymxpc2hlZCAkbmFtZSAoJHZpc2liaWxpdHkpIgogICAgOzsKICBjb25maWcpCiAgICBwcmludGYgJ2JhY2tlbmQ9JXNcbnNjb3BlPSVzXG5saW5rX21vZGU9JXNcbmNsaWVudHM9JXNcbmFwaV9rZXk9JXNcbicgXAogICAgICAiJEJBQ0tFTkQiICIkU0NPUEUiICIkTElOS19NT0RFIiAiJENMSUVOVFMiIFwKICAgICAgIiQoWyAtbiAiJEFQSV9LRVkiIF0gJiYgZWNobyBzZXQgfHwgZWNobyBNSVNTSU5HKSIKICAgIDs7CiAgaGVscHwtLWhlbHB8LWgpIHVzYWdlIDs7CiAgKikgaW5mbyAidW5rbm93biBjb21tYW5kOiAkY21kIjsgdXNhZ2U7IGV4aXQgMiA7Owplc2FjCg==";
13890
13823
 
13891
- // src/skills/bootstrap/exulu-skills.ts
13824
+ // src/skills/bootstrap/imp-skills.ts
13892
13825
  var BOOTSTRAP_CLIENTS_JSON = JSON.stringify(CLIENT_MANIFEST, null, 2);
13893
13826
  var DIR_FOR_CASES = CLIENT_MANIFEST.map(
13894
13827
  (c) => ` ${c.id}) printf '%s' '${c.dir}' ;;`
13895
13828
  ).join("\n");
13896
- var BOOTSTRAP_EXULU_SH = Buffer.from(EXULU_SH_B64, "base64").toString("utf8").replace("__DIR_FOR_CASES__", DIR_FOR_CASES);
13829
+ var BOOTSTRAP_IMP_SH = Buffer.from(IMP_SH_B64, "base64").toString("utf8").replace("__DIR_FOR_CASES__", DIR_FOR_CASES);
13897
13830
  var BOOTSTRAP_SKILL_MD = `---
13898
- name: exulu-skills
13899
- description: Install, update, and publish skills from this Exulu instance's central skill library. Use when the user asks to install a skill, get the latest version of a skill, list available skills, or publish a skill to Exulu.
13831
+ name: imp-skills
13832
+ description: Install, update, and publish skills from this instance's central skill library. Use when the user asks to install a skill, get the latest version of a skill, list IMP skills, or publish a skill to IMP.
13900
13833
  ---
13901
13834
 
13902
- # Exulu Skills
13835
+ # IMP Skills
13903
13836
 
13904
- Bridge to the Exulu central skill library. **All operations go through the
13837
+ Bridge to the IMP central skill library. **All operations go through the
13905
13838
  bundled helper script \u2014 do not hand-write curl or copy files yourself.** The
13906
13839
  script reads the API token from config (keeping it out of this conversation) and
13907
13840
  handles the multi-client copy/symlink fan-out deterministically.
13908
13841
 
13909
13842
  ## The helper
13910
13843
 
13911
- Run the script next to this file, \`scripts/exulu\`, with \`sh\` and the absolute
13844
+ Run the script next to this file, \`scripts/imp\`, with \`sh\` and the absolute
13912
13845
  path of this skill's directory:
13913
13846
 
13914
13847
  \`\`\`
13915
- sh "<this-skill-dir>/scripts/exulu" <command>
13848
+ sh "<this-skill-dir>/scripts/imp" <command>
13916
13849
  \`\`\`
13917
13850
 
13918
13851
  Commands:
@@ -13920,25 +13853,27 @@ Commands:
13920
13853
  - \`get <name>\` \u2014 one skill's metadata (JSON)
13921
13854
  - \`install <name>\` \u2014 install/refresh a skill into the user's agent clients
13922
13855
  - \`update [<name>]\` \u2014 update every installed skill, or just \`<name>\`, to latest
13923
- - \`publish <name> <folder>\` \u2014 publish a local skill folder as \`<name>\`
13856
+ - \`publish <name> <folder> <public|private>\` \u2014 publish a local skill folder as \`<name>\`
13924
13857
  - \`config\` \u2014 show resolved backend / scope / clients (prints no secrets)
13925
13858
 
13926
13859
  The token, backend URL, target clients, copy-vs-symlink mode, and scope all come
13927
- from \`~/.config/exulu/skills.json\` (written by the installer). Never print the
13860
+ from \`~/.config/imp/skills.json\` (written by the installer). Never print the
13928
13861
  \`api_key\` or read it into your reply \u2014 the script uses it internally.
13929
13862
 
13930
13863
  ## Requests \u2192 commands
13931
13864
 
13932
- - "list / search skills" \u2192 \`exulu list\`, then filter the JSON for the user.
13933
- - "install skill X" / "add the X skill" \u2192 \`exulu install X\`.
13934
- - "update / get the latest version [of X]" \u2192 \`exulu update [X]\`.
13865
+ - "list / search skills" \u2192 \`imp list\`, then filter the JSON for the user.
13866
+ - "install skill X" / "add the X skill" \u2192 \`imp install X\`.
13867
+ - "update / get the latest version [of X]" \u2192 \`imp update [X]\`.
13935
13868
  - "publish / upload this skill as X" \u2192 confirm the target name with the user; for
13936
- an existing skill run \`exulu get X\` first and confirm a new version is intended;
13937
- then \`exulu publish X <folder>\`.
13869
+ an existing skill run \`imp get X\` first and confirm a new version is intended.
13870
+ For a NEW skill you MUST ask the user whether it should be \`public\` (visible
13871
+ to everyone on the instance) or \`private\` (only them) \u2014 never pick a
13872
+ visibility yourself; then \`imp publish X <folder> <public|private>\`.
13938
13873
 
13939
13874
  ## Not configured yet?
13940
13875
 
13941
- If \`exulu config\` reports it's not configured (or \`~/.config/exulu/skills.json\`
13876
+ If \`imp config\` reports it's not configured (or \`~/.config/imp/skills.json\`
13942
13877
  is missing), tell the user to run the installer \u2014 it sets everything up
13943
13878
  interactively (base URL, API key, target clients, copy/symlink):
13944
13879
 
@@ -13946,14 +13881,15 @@ interactively (base URL, API key, target clients, copy/symlink):
13946
13881
  curl -fsSL <base_url>/api/skills/install.sh | sh
13947
13882
  \`\`\`
13948
13883
 
13949
- \`<base_url>\` is their Exulu frontend URL (e.g. https://ai.open.de). They can
13950
- create an API key at \`<base_url>/token\`.
13884
+ \`<base_url>\` is their instance's frontend URL (e.g. https://ai.open.de). They
13885
+ can create an API key at \`<base_url>/token\`.
13951
13886
 
13952
13887
  ## Errors
13953
13888
 
13954
13889
  - install: \`403\` = no access to that skill; \`404\` = unknown name.
13955
- - publish: \`403\` = you can see it but lack write access; \`409\` = the name is
13956
- taken by a skill you can't access.
13890
+ - publish: \`400\` = missing/invalid visibility (new skills need public|private);
13891
+ \`403\` = you can see it but lack write access; \`409\` = the name is taken by a
13892
+ skill you can't access.
13957
13893
  `;
13958
13894
 
13959
13895
  // src/exulu/routes.ts
@@ -16262,9 +16198,9 @@ ${style.markdown}` : params.prompt;
16262
16198
  app.get("/skills/agent/bootstrap", async (_req, res) => {
16263
16199
  try {
16264
16200
  const zip = new JSZip3();
16265
- zip.file("exulu-skills/SKILL.md", BOOTSTRAP_SKILL_MD);
16266
- zip.file("exulu-skills/references/clients.json", BOOTSTRAP_CLIENTS_JSON);
16267
- zip.file("exulu-skills/scripts/exulu", BOOTSTRAP_EXULU_SH, {
16201
+ zip.file("imp-skills/SKILL.md", BOOTSTRAP_SKILL_MD);
16202
+ zip.file("imp-skills/references/clients.json", BOOTSTRAP_CLIENTS_JSON);
16203
+ zip.file("imp-skills/scripts/imp", BOOTSTRAP_IMP_SH, {
16268
16204
  unixPermissions: 493
16269
16205
  });
16270
16206
  const buffer = await zip.generateAsync({
@@ -16272,7 +16208,7 @@ ${style.markdown}` : params.prompt;
16272
16208
  platform: "UNIX"
16273
16209
  });
16274
16210
  res.setHeader("Content-Type", "application/zip");
16275
- res.setHeader("Content-Disposition", 'attachment; filename="exulu-skills.zip"');
16211
+ res.setHeader("Content-Disposition", 'attachment; filename="imp-skills.zip"');
16276
16212
  res.send(buffer);
16277
16213
  } catch (err) {
16278
16214
  console.error("[SKILLS] Failed to build bootstrap zip", err);
@@ -16393,6 +16329,13 @@ ${style.markdown}` : params.prompt;
16393
16329
  return;
16394
16330
  }
16395
16331
  }
16332
+ const visibility = req.query.visibility;
16333
+ if (visibility !== "public" && visibility !== "private") {
16334
+ res.status(400).json({
16335
+ detail: "Missing or invalid 'visibility'. New skills require ?visibility=public or ?visibility=private \u2014 ask the user which they want."
16336
+ });
16337
+ return;
16338
+ }
16396
16339
  const meta = await parseSkillFrontmatter(bytes);
16397
16340
  const skillId = randomUUID3();
16398
16341
  try {
@@ -16419,7 +16362,7 @@ ${style.markdown}` : params.prompt;
16419
16362
  history: JSON.stringify([
16420
16363
  { version: 1, created_at: (/* @__PURE__ */ new Date()).toISOString(), label: "Published from agent" }
16421
16364
  ]),
16422
- rights_mode: "private",
16365
+ rights_mode: visibility,
16423
16366
  created_by: authResult.user.id
16424
16367
  });
16425
16368
  } catch (err) {
@@ -21297,15 +21240,15 @@ var execute = async ({ contexts }) => {
21297
21240
  };
21298
21241
 
21299
21242
  // src/exulu/litellm/db-init.ts
21300
- import { existsSync as existsSync6, readdirSync } from "fs";
21243
+ import { existsSync as existsSync5, readdirSync } from "fs";
21301
21244
  import { resolve as resolve3 } from "path";
21302
21245
  import { spawnSync } from "child_process";
21303
21246
  import { Client } from "pg";
21304
21247
 
21305
21248
  // src/exulu/litellm/db-setup-check.ts
21306
- import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
21249
+ import { readFileSync as readFileSync4, existsSync as existsSync4 } from "fs";
21307
21250
  var readLiteLLMDatabaseUrl = (configPath) => {
21308
- if (!existsSync5(configPath)) return void 0;
21251
+ if (!existsSync4(configPath)) return void 0;
21309
21252
  const text = readFileSync4(configPath, "utf8");
21310
21253
  const match = text.match(
21311
21254
  /^\s*database_url:\s*["']?([^"'\n#]+?)["']?\s*(#.*)?$/m
@@ -21508,7 +21451,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
21508
21451
  const venvBin = resolve3(packageRoot, "ee/python/.venv/bin");
21509
21452
  const prismaCli = resolve3(venvBin, "prisma");
21510
21453
  const venvLibDir = resolve3(packageRoot, "ee/python/.venv/lib");
21511
- const pythonVersionDir = existsSync6(venvLibDir) ? readdirSync(venvLibDir).find((entry) => /^python3\.\d+$/.test(entry)) : void 0;
21454
+ const pythonVersionDir = existsSync5(venvLibDir) ? readdirSync(venvLibDir).find((entry) => /^python3\.\d+$/.test(entry)) : void 0;
21512
21455
  if (!pythonVersionDir) {
21513
21456
  warn2([
21514
21457
  `Could not find a python3.* directory under ${venvLibDir}.`,
@@ -21523,7 +21466,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
21523
21466
  "site-packages/litellm/proxy"
21524
21467
  );
21525
21468
  const schemaPath = resolve3(litellmProxyDir, "schema.prisma");
21526
- if (!existsSync6(prismaCli)) {
21469
+ if (!existsSync5(prismaCli)) {
21527
21470
  warn2([
21528
21471
  `Prisma CLI not found at ${prismaCli}.`,
21529
21472
  `Run \`npm run python:setup\` to create the venv and install prisma.`,
@@ -21531,7 +21474,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
21531
21474
  ]);
21532
21475
  return;
21533
21476
  }
21534
- if (!existsSync6(schemaPath)) {
21477
+ if (!existsSync5(schemaPath)) {
21535
21478
  warn2([
21536
21479
  `LiteLLM Prisma schema not found at ${schemaPath}.`,
21537
21480
  `Re-run \`npm run python:setup\`. Skipping LiteLLM database setup.`
@@ -22114,20 +22057,20 @@ import WordExtractor from "word-extractor";
22114
22057
  import { parseOfficeAsync as parseOfficeAsync2 } from "officeparser";
22115
22058
 
22116
22059
  // src/utils/python-executor.ts
22117
- import { exec as exec2 } from "child_process";
22118
- import { promisify as promisify2 } from "util";
22119
- import { resolve as resolve4, join as join2, dirname } from "path";
22120
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
22060
+ import { exec } from "child_process";
22061
+ import { promisify } from "util";
22062
+ import { resolve as resolve4, join, dirname } from "path";
22063
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
22121
22064
  import { fileURLToPath } from "url";
22122
- var execAsync2 = promisify2(exec2);
22065
+ var execAsync = promisify(exec);
22123
22066
  function getPackageRoot2() {
22124
22067
  const currentFile = fileURLToPath(import.meta.url);
22125
22068
  let currentDir = dirname(currentFile);
22126
22069
  let attempts = 0;
22127
22070
  const maxAttempts = 10;
22128
22071
  while (attempts < maxAttempts) {
22129
- const packageJsonPath = join2(currentDir, "package.json");
22130
- if (existsSync7(packageJsonPath)) {
22072
+ const packageJsonPath = join(currentDir, "package.json");
22073
+ if (existsSync6(packageJsonPath)) {
22131
22074
  try {
22132
22075
  const packageJson = JSON.parse(readFileSync5(packageJsonPath, "utf-8"));
22133
22076
  if (packageJson.name === "@exulu/backend") {
@@ -22168,7 +22111,7 @@ function getVenvPath(packageRoot) {
22168
22111
  }
22169
22112
  function getPythonExecutable(packageRoot) {
22170
22113
  const venvPath = getVenvPath(packageRoot);
22171
- return join2(venvPath, "bin", "python");
22114
+ return join(venvPath, "bin", "python");
22172
22115
  }
22173
22116
  async function validatePythonEnvironmentForExecution(packageRoot) {
22174
22117
  const validation = await validatePythonEnvironment(packageRoot);
@@ -22191,7 +22134,7 @@ async function executePythonScript(config) {
22191
22134
  await validatePythonEnvironmentForExecution(packageRoot);
22192
22135
  }
22193
22136
  const resolvedScriptPath = resolve4(packageRoot, scriptPath);
22194
- if (!existsSync7(resolvedScriptPath)) {
22137
+ if (!existsSync6(resolvedScriptPath)) {
22195
22138
  throw new PythonExecutionError(
22196
22139
  `Python script not found: ${resolvedScriptPath}`,
22197
22140
  "",
@@ -22205,7 +22148,7 @@ async function executePythonScript(config) {
22205
22148
  });
22206
22149
  const command = `${pythonExecutable} "${resolvedScriptPath}" ${quotedArgs.join(" ")}`;
22207
22150
  try {
22208
- const { stdout, stderr } = await execAsync2(command, {
22151
+ const { stdout, stderr } = await execAsync(command, {
22209
22152
  cwd,
22210
22153
  timeout,
22211
22154
  env: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "2.2.0",
4
+ "version": "2.3.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {