@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.cjs CHANGED
@@ -3332,6 +3332,15 @@ var init_system_dependencies = __esm({
3332
3332
  macos: "brew install poppler"
3333
3333
  }
3334
3334
  },
3335
+ {
3336
+ check: { kind: "binary", binary: "pdftotext" },
3337
+ displayName: "Poppler (pdftotext)",
3338
+ purpose: "parse_document tool: extracting page-marked text from PDFs",
3339
+ installHints: {
3340
+ debian: "apt-get install -y poppler-utils",
3341
+ macos: "brew install poppler"
3342
+ }
3343
+ },
3335
3344
  {
3336
3345
  check: { kind: "npm-global", packageName: "docx" },
3337
3346
  displayName: "docx (npm global)",
@@ -4169,6 +4178,595 @@ var init_session_file_read_tool = __esm({
4169
4178
  }
4170
4179
  });
4171
4180
 
4181
+ // src/templates/tools/document-render-helpers.ts
4182
+ async function pdfToText(pdf) {
4183
+ const dir = await (0, import_promises2.mkdtemp)((0, import_node_path5.join)((0, import_node_os.tmpdir)(), "exulu-parse-"));
4184
+ try {
4185
+ const inputPath = (0, import_node_path5.join)(dir, "input.pdf");
4186
+ await (0, import_promises2.writeFile)(inputPath, pdf);
4187
+ const { stdout } = await execFileAsync("pdftotext", ["-layout", inputPath, "-"], {
4188
+ timeout: 6e4,
4189
+ maxBuffer: MAX_STDOUT_BYTES
4190
+ });
4191
+ return stdout;
4192
+ } finally {
4193
+ await (0, import_promises2.rm)(dir, { recursive: true, force: true });
4194
+ }
4195
+ }
4196
+ async function renderPdfPageToPng(pdf, page, scaleTo) {
4197
+ const dir = await (0, import_promises2.mkdtemp)((0, import_node_path5.join)((0, import_node_os.tmpdir)(), "exulu-render-"));
4198
+ try {
4199
+ const inputPath = (0, import_node_path5.join)(dir, "input.pdf");
4200
+ await (0, import_promises2.writeFile)(inputPath, pdf);
4201
+ try {
4202
+ await execFileAsync(
4203
+ "pdftoppm",
4204
+ ["-png", "-f", String(page), "-l", String(page), "-scale-to", String(scaleTo), inputPath, (0, import_node_path5.join)(dir, "page")],
4205
+ { timeout: 6e4, maxBuffer: MAX_STDOUT_BYTES }
4206
+ );
4207
+ } catch (err) {
4208
+ if (err?.code === 99) return null;
4209
+ throw err;
4210
+ }
4211
+ const produced = (await (0, import_promises2.readdir)(dir)).find((f) => f.startsWith("page") && f.endsWith(".png"));
4212
+ if (!produced) return null;
4213
+ return await (0, import_promises2.readFile)((0, import_node_path5.join)(dir, produced));
4214
+ } finally {
4215
+ await (0, import_promises2.rm)(dir, { recursive: true, force: true });
4216
+ }
4217
+ }
4218
+ var import_node_child_process4, import_node_util3, import_promises2, import_node_os, import_node_path5, execFileAsync, MAX_STDOUT_BYTES;
4219
+ var init_document_render_helpers = __esm({
4220
+ "src/templates/tools/document-render-helpers.ts"() {
4221
+ "use strict";
4222
+ init_cjs_shims();
4223
+ import_node_child_process4 = require("child_process");
4224
+ import_node_util3 = require("util");
4225
+ import_promises2 = require("fs/promises");
4226
+ import_node_os = require("os");
4227
+ import_node_path5 = require("path");
4228
+ execFileAsync = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
4229
+ MAX_STDOUT_BYTES = 64 * 1024 * 1024;
4230
+ }
4231
+ });
4232
+
4233
+ // src/templates/tools/parse-document-tool.ts
4234
+ var import_zod6, import_node_path6, import_officeparser, DEFAULT_LIMIT2, MAX_CONTENT_CHARS2, MIN_CHARS_PER_PAGE, OFFICE_EXTENSIONS, pagesPattern, createParseDocumentTool;
4235
+ var init_parse_document_tool = __esm({
4236
+ "src/templates/tools/parse-document-tool.ts"() {
4237
+ "use strict";
4238
+ init_cjs_shims();
4239
+ import_zod6 = require("zod");
4240
+ import_node_path6 = require("path");
4241
+ import_officeparser = require("officeparser");
4242
+ init_tool();
4243
+ init_uppy();
4244
+ init_document_render_helpers();
4245
+ DEFAULT_LIMIT2 = 250;
4246
+ MAX_CONTENT_CHARS2 = 16e3;
4247
+ MIN_CHARS_PER_PAGE = 20;
4248
+ OFFICE_EXTENSIONS = /* @__PURE__ */ new Set([
4249
+ ".docx",
4250
+ ".doc",
4251
+ ".xlsx",
4252
+ ".xls",
4253
+ ".pptx",
4254
+ ".ppt",
4255
+ ".odt",
4256
+ ".ods",
4257
+ ".odp",
4258
+ ".rtf"
4259
+ ]);
4260
+ pagesPattern = /^(\d+)(?:-(\d+))?$/;
4261
+ createParseDocumentTool = ({
4262
+ sessionID,
4263
+ user,
4264
+ exuluConfig
4265
+ }) => {
4266
+ if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
4267
+ const parseDocumentExecute = async ({
4268
+ filename,
4269
+ pages,
4270
+ offset,
4271
+ limit
4272
+ }) => {
4273
+ const safeName = String(filename ?? "").trim();
4274
+ if (!safeName || safeName.includes("..") || safeName.includes("/") || safeName.includes("\\")) {
4275
+ return {
4276
+ error: "Invalid filename \u2014 pass the bare file name exactly as listed in the session files (no paths)."
4277
+ };
4278
+ }
4279
+ const ext = (0, import_node_path6.extname)(safeName).toLowerCase();
4280
+ if (ext !== ".pdf" && !OFFICE_EXTENSIONS.has(ext)) {
4281
+ return {
4282
+ error: `Unsupported extension "${ext}" \u2014 parse_document handles PDF and Office formats. For plain-text files use read_session_file.`
4283
+ };
4284
+ }
4285
+ if (pages && ext !== ".pdf") {
4286
+ return { error: `The pages option is only supported for PDF files \u2014 "${ext}" documents are extracted whole.` };
4287
+ }
4288
+ const uploads = exuluConfig.fileUploads;
4289
+ const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
4290
+ const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
4291
+ try {
4292
+ const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
4293
+ const res = await fetch(url);
4294
+ if (!res.ok) {
4295
+ return { error: `Could not read session file "${safeName}" (status ${res.status}). Check the exact file name.` };
4296
+ }
4297
+ const bytes = Buffer.from(await res.arrayBuffer());
4298
+ let fullText;
4299
+ let totalPages;
4300
+ if (ext === ".pdf") {
4301
+ const raw = await pdfToText(bytes);
4302
+ const pageTexts = raw.replace(/\f$/, "").split("\f");
4303
+ totalPages = pageTexts.length;
4304
+ const nonWhitespace = raw.replace(/\s/g, "").length;
4305
+ if (nonWhitespace < totalPages * MIN_CHARS_PER_PAGE) {
4306
+ return {
4307
+ error: `"${safeName}" has no extractable text layer (likely a scan or image-based PDF). Use view_document_page to look at pages visually, or suggest the user add the document to a knowledge base with a document processor for full OCR.`
4308
+ };
4309
+ }
4310
+ let range = [1, totalPages];
4311
+ if (pages) {
4312
+ const m = pagesPattern.exec(pages.trim());
4313
+ if (!m) return { error: `Invalid pages "${pages}" \u2014 use "3" or "2-5".` };
4314
+ range = [Number(m[1]), Number(m[2] ?? m[1])];
4315
+ if (range[0] < 1 || range[0] > range[1]) {
4316
+ return { error: `Invalid pages "${pages}" \u2014 start must be at least 1 and not greater than the end.` };
4317
+ }
4318
+ if (range[0] > totalPages) {
4319
+ return { error: `Page range starts at ${range[0]} but "${safeName}" has only ${totalPages} page${totalPages === 1 ? "" : "s"}.` };
4320
+ }
4321
+ }
4322
+ fullText = pageTexts.map((text, i) => ({ page: i + 1, text })).filter(({ page }) => page >= range[0] && page <= range[1]).map(({ page, text }) => `--- page ${page} ---
4323
+ ${text.trim()}`).join("\n");
4324
+ } else {
4325
+ const extracted = await (0, import_officeparser.parseOfficeAsync)(bytes, {
4326
+ outputErrorToConsole: false,
4327
+ newlineDelimiter: "\n"
4328
+ });
4329
+ fullText = String(extracted);
4330
+ }
4331
+ const lines = fullText.split("\n");
4332
+ const start = (offset ?? 1) - 1;
4333
+ const requested = limit ?? DEFAULT_LIMIT2;
4334
+ const sliced = lines.slice(start, start + requested);
4335
+ let content = sliced.join("\n");
4336
+ let linesReturned = sliced.length;
4337
+ if (content.length > MAX_CONTENT_CHARS2) {
4338
+ content = content.slice(0, MAX_CONTENT_CHARS2);
4339
+ linesReturned = Math.max(1, content.split("\n").length - 1);
4340
+ content = content + "\n[slice truncated \u2014 request fewer lines]";
4341
+ }
4342
+ return {
4343
+ content,
4344
+ ...totalPages !== void 0 ? { totalPages } : {},
4345
+ totalLines: lines.length,
4346
+ offset: start + 1,
4347
+ linesReturned
4348
+ };
4349
+ } catch (err) {
4350
+ return { error: `Failed to parse "${safeName}": ${err instanceof Error ? err.message : "unknown error"}` };
4351
+ }
4352
+ };
4353
+ return ExuluTool.internal({
4354
+ id: "parse_document",
4355
+ name: "parse_document",
4356
+ needsApproval: false,
4357
+ description: `Extract the text of an uploaded PDF or Office document from this session's files, with "--- page N ---" markers for PDFs so you can locate content by page. Free and fast (no OCR): works only on documents with a real text layer. To SEE a page or an image inside a document, use view_document_page.`,
4358
+ inputSchema: import_zod6.z.object({
4359
+ filename: import_zod6.z.string().describe('Exact session file name, e.g. "report.pdf"'),
4360
+ pages: import_zod6.z.string().optional().describe('PDF page or range to extract, e.g. "2" or "1-5" (default: all pages) (PDF only)'),
4361
+ offset: import_zod6.z.number().int().min(1).optional().describe("1-based first output line to read (default 1)"),
4362
+ limit: import_zod6.z.number().int().min(1).max(1e3).optional().describe(`Number of lines to read (default ${DEFAULT_LIMIT2})`)
4363
+ }),
4364
+ type: "function",
4365
+ category: "session",
4366
+ config: [],
4367
+ // Same shape mismatch as read_session_file / memory-tool: internal utility
4368
+ // tools return richer objects than ExuluTool's retrieval-flavored execute
4369
+ // type; the AI SDK passes the object through verbatim.
4370
+ execute: parseDocumentExecute
4371
+ });
4372
+ };
4373
+ }
4374
+ });
4375
+
4376
+ // src/sessions/pdf-preview-cache.ts
4377
+ function sanitizeEtag(raw) {
4378
+ return raw.replace(/^"|"$/g, "").replace(/[^a-zA-Z0-9_-]/g, "_");
4379
+ }
4380
+ async function getPdfPreviewBytes(opts) {
4381
+ const { sourceKey, etag, config } = opts;
4382
+ const safeEtag = sanitizeEtag(etag);
4383
+ if (!safeEtag) {
4384
+ throw new PreviewRenderError(`Invalid ETag for ${sourceKey}`);
4385
+ }
4386
+ const cachedPath = (0, import_node_path7.join)(CACHE_ROOT, `${safeEtag}.pdf`);
4387
+ if ((0, import_node_fs6.existsSync)(cachedPath)) {
4388
+ return (0, import_promises3.readFile)(cachedPath);
4389
+ }
4390
+ const existing = inFlight.get(safeEtag);
4391
+ if (existing) return existing;
4392
+ const promise = (async () => {
4393
+ try {
4394
+ await (0, import_promises3.mkdir)(CACHE_IN, { recursive: true });
4395
+ await (0, import_promises3.mkdir)(CACHE_OUT, { recursive: true });
4396
+ const ext = ((0, import_node_path7.extname)(sourceKey) || ".docx").toLowerCase();
4397
+ const inputPath = (0, import_node_path7.join)(CACHE_IN, `${safeEtag}${ext}`);
4398
+ const outputPath = (0, import_node_path7.join)(CACHE_OUT, `${safeEtag}.pdf`);
4399
+ try {
4400
+ const bytes = await getS3ObjectBytes(sourceKey, config);
4401
+ await (0, import_promises3.writeFile)(inputPath, bytes);
4402
+ try {
4403
+ await execAsync3(
4404
+ `soffice --headless --convert-to pdf "${inputPath}" --outdir "${CACHE_OUT}"`,
4405
+ { timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }
4406
+ );
4407
+ } catch (err) {
4408
+ throw new PreviewRenderError(
4409
+ `LibreOffice conversion failed for ${sourceKey} (etag ${etag}): ${err?.stderr ?? err?.message ?? "unknown error"}`
4410
+ );
4411
+ }
4412
+ if (!(0, import_node_fs6.existsSync)(outputPath)) {
4413
+ throw new PreviewRenderError(
4414
+ `LibreOffice produced no output for ${sourceKey} (etag ${etag})`
4415
+ );
4416
+ }
4417
+ await (0, import_promises3.rename)(outputPath, cachedPath);
4418
+ return await (0, import_promises3.readFile)(cachedPath);
4419
+ } finally {
4420
+ await (0, import_promises3.rm)(inputPath, { force: true });
4421
+ }
4422
+ } finally {
4423
+ inFlight.delete(safeEtag);
4424
+ }
4425
+ })();
4426
+ inFlight.set(safeEtag, promise);
4427
+ return promise;
4428
+ }
4429
+ var import_node_child_process5, import_node_fs6, import_promises3, import_node_path7, import_node_util4, execAsync3, CACHE_ROOT, CACHE_IN, CACHE_OUT, inFlight, PreviewRenderError;
4430
+ var init_pdf_preview_cache = __esm({
4431
+ "src/sessions/pdf-preview-cache.ts"() {
4432
+ "use strict";
4433
+ init_cjs_shims();
4434
+ import_node_child_process5 = require("child_process");
4435
+ import_node_fs6 = require("fs");
4436
+ import_promises3 = require("fs/promises");
4437
+ import_node_path7 = require("path");
4438
+ import_node_util4 = require("util");
4439
+ init_uppy();
4440
+ execAsync3 = (0, import_node_util4.promisify)(import_node_child_process5.exec);
4441
+ CACHE_ROOT = "/tmp/exulu-pdf-cache";
4442
+ CACHE_IN = (0, import_node_path7.join)(CACHE_ROOT, "_in");
4443
+ CACHE_OUT = (0, import_node_path7.join)(CACHE_ROOT, "_out");
4444
+ inFlight = /* @__PURE__ */ new Map();
4445
+ PreviewRenderError = class extends Error {
4446
+ constructor(message) {
4447
+ super(message);
4448
+ this.name = "PreviewRenderError";
4449
+ }
4450
+ };
4451
+ }
4452
+ });
4453
+
4454
+ // src/exulu/litellm/catalog.ts
4455
+ var catalog_exports = {};
4456
+ __export(catalog_exports, {
4457
+ __resetLiteLLMCatalogCacheForTesting: () => __resetLiteLLMCatalogCacheForTesting,
4458
+ fetchLiteLLMCatalog: () => fetchLiteLLMCatalog,
4459
+ findLiteLLMModel: () => findLiteLLMModel
4460
+ });
4461
+ var CACHE_TTL_MS, _cache, __resetLiteLLMCatalogCacheForTesting, fetchLiteLLMCatalog, findLiteLLMModel;
4462
+ var init_catalog = __esm({
4463
+ "src/exulu/litellm/catalog.ts"() {
4464
+ "use strict";
4465
+ init_cjs_shims();
4466
+ CACHE_TTL_MS = 3e4;
4467
+ __resetLiteLLMCatalogCacheForTesting = () => {
4468
+ _cache = void 0;
4469
+ };
4470
+ fetchLiteLLMCatalog = async () => {
4471
+ if (process.env.EXULU_USE_LITELLM !== "true") return [];
4472
+ if (_cache && _cache.expiresAt > Date.now()) {
4473
+ return _cache.items;
4474
+ }
4475
+ const host = process.env.LITELLM_HOST ?? "127.0.0.1";
4476
+ const port = process.env.LITELLM_PORT ?? "4000";
4477
+ const masterKey = process.env.LITELLM_MASTER_KEY;
4478
+ if (!masterKey) return [];
4479
+ try {
4480
+ const res = await fetch(`http://${host}:${port}/model/info`, {
4481
+ method: "GET",
4482
+ headers: { Authorization: `Bearer ${masterKey}` }
4483
+ });
4484
+ if (!res.ok) {
4485
+ console.error(
4486
+ `[EXULU] litellmCatalog: LiteLLM /model/info returned ${res.status}`
4487
+ );
4488
+ return [];
4489
+ }
4490
+ const json = await res.json();
4491
+ const items = (Array.isArray(json?.data) ? json.data : []).map((m) => ({
4492
+ // filter out trailing * from model_name
4493
+ model_name: m.model_name.replace(/\*$/, ""),
4494
+ upstream_model: m.litellm_params?.model ?? null,
4495
+ tags: Array.isArray(m.model_info?.tags) ? m.model_info.tags : [],
4496
+ brand: m.model_info?.brand ?? null,
4497
+ type: m.model_info?.type ?? null,
4498
+ region: m.model_info?.region ?? null,
4499
+ max_tokens: m.model_info?.max_tokens ?? null,
4500
+ max_input_tokens: m.model_info?.max_input_tokens ?? null,
4501
+ input_cost_per_million_tokens: m.model_info?.input_cost_per_token * 1e6,
4502
+ output_cost_per_million_tokens: m.model_info?.output_cost_per_token * 1e6,
4503
+ active: m.model_info?.active ?? true,
4504
+ max_output_tokens: m.model_info?.max_output_tokens ?? null,
4505
+ supports_vision: !!m.model_info?.supports_vision,
4506
+ supports_function_calling: !!m.model_info?.supports_function_calling,
4507
+ supports_pdf_input: !!m.model_info?.supports_pdf_input,
4508
+ supports_audio_input: !!m.model_info?.supports_audio_input,
4509
+ sizes: Array.isArray(m.model_info?.sizes) ? m.model_info.sizes : null,
4510
+ qualities: Array.isArray(m.model_info?.qualities) ? m.model_info.qualities : null,
4511
+ supports_edit: !!m.model_info?.supports_edit,
4512
+ max_n: typeof m.model_info?.max_n === "number" ? m.model_info.max_n : null
4513
+ }));
4514
+ const map = /* @__PURE__ */ new Map();
4515
+ for (const item of items) {
4516
+ const key = `${item.model_name}-${item.upstream_model}`;
4517
+ if (map.has(key)) {
4518
+ map.get(key).tags.push(...item.tags);
4519
+ } else {
4520
+ map.set(key, item);
4521
+ }
4522
+ }
4523
+ const uniqueItems = Array.from(map.values());
4524
+ _cache = { expiresAt: Date.now() + CACHE_TTL_MS, items: uniqueItems };
4525
+ return uniqueItems.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
4526
+ } catch (err) {
4527
+ console.error("[EXULU] litellmCatalog: failed to fetch /model/info:", err);
4528
+ return [];
4529
+ }
4530
+ };
4531
+ findLiteLLMModel = async (modelName) => {
4532
+ if (!modelName) return void 0;
4533
+ const items = await fetchLiteLLMCatalog();
4534
+ return items.find((m) => m.model_name === modelName);
4535
+ };
4536
+ }
4537
+ });
4538
+
4539
+ // src/exulu/tool-image-attachments.ts
4540
+ function sweep() {
4541
+ const cutoff = Date.now() - TTL_MS;
4542
+ for (const [id, entry] of stash) {
4543
+ if (entry.stashedAt < cutoff) stash.delete(id);
4544
+ }
4545
+ while (stash.size > MAX_ENTRIES) {
4546
+ const oldest = stash.keys().next().value;
4547
+ if (oldest === void 0) break;
4548
+ stash.delete(oldest);
4549
+ }
4550
+ let totalBytes = 0;
4551
+ for (const entry of stash.values()) totalBytes += entry.data.length;
4552
+ while (totalBytes > MAX_TOTAL_BYTES) {
4553
+ const oldest = stash.keys().next().value;
4554
+ if (oldest === void 0) break;
4555
+ totalBytes -= stash.get(oldest).data.length;
4556
+ stash.delete(oldest);
4557
+ }
4558
+ }
4559
+ function stashToolImage(toolCallId, image) {
4560
+ stash.set(toolCallId, { ...image, stashedAt: Date.now() });
4561
+ sweep();
4562
+ }
4563
+ function stashedIdsInMessage(message) {
4564
+ if (message?.role !== "tool" || !Array.isArray(message.content)) return [];
4565
+ return message.content.filter(
4566
+ (p) => p?.type === "tool-result" && typeof p.toolCallId === "string" && p.toolName === STASH_TOOL_NAME && stash.has(p.toolCallId)
4567
+ ).map((p) => p.toolCallId);
4568
+ }
4569
+ function injectedTextFor(id, label) {
4570
+ return `${INJECTED_IMAGE_PREFIX}${id}: ${label}]`;
4571
+ }
4572
+ function firstTextPart(message) {
4573
+ if (message?.role !== "user" || !Array.isArray(message.content)) return void 0;
4574
+ const first = message.content[0];
4575
+ return first?.type === "text" ? first.text : void 0;
4576
+ }
4577
+ function imageAttachmentGuard() {
4578
+ return ({ messages }) => {
4579
+ sweep();
4580
+ if (!Array.isArray(messages) || messages.length === 0 || stash.size === 0) return void 0;
4581
+ let changed = false;
4582
+ const next = [];
4583
+ for (let i = 0; i < messages.length; i++) {
4584
+ const message = messages[i];
4585
+ next.push(message);
4586
+ const ids = stashedIdsInMessage(message);
4587
+ if (ids.length === 0) continue;
4588
+ const alreadyInjected = [];
4589
+ for (let j = i + 1; j < messages.length; j++) {
4590
+ const text = firstTextPart(messages[j]);
4591
+ if (text === void 0 || !text.startsWith(INJECTED_IMAGE_PREFIX)) break;
4592
+ alreadyInjected.push(text);
4593
+ }
4594
+ for (const id of ids) {
4595
+ if (alreadyInjected.some((text) => text.startsWith(`${INJECTED_IMAGE_PREFIX}${id}:`))) continue;
4596
+ const image = stash.get(id);
4597
+ changed = true;
4598
+ next.push({
4599
+ role: "user",
4600
+ content: [
4601
+ { type: "text", text: injectedTextFor(id, image.label) },
4602
+ { type: "image", image: image.data, mediaType: image.mediaType }
4603
+ ]
4604
+ });
4605
+ }
4606
+ }
4607
+ return changed ? { messages: next } : void 0;
4608
+ };
4609
+ }
4610
+ var INJECTED_IMAGE_PREFIX, stash, MAX_ENTRIES, TTL_MS, MAX_TOTAL_BYTES, STASH_TOOL_NAME;
4611
+ var init_tool_image_attachments = __esm({
4612
+ "src/exulu/tool-image-attachments.ts"() {
4613
+ "use strict";
4614
+ init_cjs_shims();
4615
+ INJECTED_IMAGE_PREFIX = "[Image attached from tool call ";
4616
+ stash = /* @__PURE__ */ new Map();
4617
+ MAX_ENTRIES = 100;
4618
+ TTL_MS = 30 * 60 * 1e3;
4619
+ MAX_TOTAL_BYTES = 1e8;
4620
+ STASH_TOOL_NAME = "view_document_page";
4621
+ }
4622
+ });
4623
+
4624
+ // src/templates/tools/view-document-page-tool.ts
4625
+ var import_zod7, import_node_path8, MAX_IMAGE_BYTES, SCALE_PRIMARY, SCALE_FALLBACK, IMAGE_MEDIA_TYPES, OFFICE_EXTENSIONS2, createViewDocumentPageTool;
4626
+ var init_view_document_page_tool = __esm({
4627
+ "src/templates/tools/view-document-page-tool.ts"() {
4628
+ "use strict";
4629
+ init_cjs_shims();
4630
+ import_zod7 = require("zod");
4631
+ import_node_path8 = require("path");
4632
+ init_tool();
4633
+ init_uppy();
4634
+ init_pdf_preview_cache();
4635
+ init_catalog();
4636
+ init_tool_image_attachments();
4637
+ init_document_render_helpers();
4638
+ MAX_IMAGE_BYTES = 375e4;
4639
+ SCALE_PRIMARY = 1568;
4640
+ SCALE_FALLBACK = 1024;
4641
+ IMAGE_MEDIA_TYPES = {
4642
+ ".png": "image/png",
4643
+ ".jpg": "image/jpeg",
4644
+ ".jpeg": "image/jpeg",
4645
+ ".gif": "image/gif",
4646
+ ".webp": "image/webp"
4647
+ };
4648
+ OFFICE_EXTENSIONS2 = /* @__PURE__ */ new Set([
4649
+ ".docx",
4650
+ ".doc",
4651
+ ".xlsx",
4652
+ ".xls",
4653
+ ".pptx",
4654
+ ".ppt",
4655
+ ".odt",
4656
+ ".ods",
4657
+ ".odp",
4658
+ ".rtf"
4659
+ ]);
4660
+ createViewDocumentPageTool = ({
4661
+ sessionID,
4662
+ user,
4663
+ exuluConfig
4664
+ }) => {
4665
+ if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
4666
+ const viewDocumentPageExecute = async ({ filename, page, model }, options) => {
4667
+ const safeName = String(filename ?? "").trim();
4668
+ if (!safeName || safeName.includes("..") || safeName.includes("/") || safeName.includes("\\")) {
4669
+ return {
4670
+ error: "Invalid filename \u2014 pass the bare file name exactly as listed in the session files (no paths)."
4671
+ };
4672
+ }
4673
+ const ext = (0, import_node_path8.extname)(safeName).toLowerCase();
4674
+ const isImage = ext in IMAGE_MEDIA_TYPES;
4675
+ const isPdf = ext === ".pdf";
4676
+ const isOffice = OFFICE_EXTENSIONS2.has(ext);
4677
+ if (!isImage && !isPdf && !isOffice) {
4678
+ return { error: `Unsupported extension "${ext}" \u2014 view_document_page handles PDF, Office, and image files.` };
4679
+ }
4680
+ const modelId = typeof model === "string" ? model : model?.modelId;
4681
+ if (!modelId) {
4682
+ console.warn("[EXULU] view_document_page: no model id available for vision gating \u2014 proceeding optimistically.");
4683
+ }
4684
+ if (modelId) {
4685
+ try {
4686
+ const entry = await findLiteLLMModel(modelId);
4687
+ if (entry && entry.supports_vision === false) {
4688
+ return {
4689
+ error: `The current model "${modelId}" does not support images, so this page cannot be shown to you. Use parse_document for the text, or tell the user a vision-capable model is required.`
4690
+ };
4691
+ }
4692
+ } catch {
4693
+ }
4694
+ }
4695
+ const uploads = exuluConfig.fileUploads;
4696
+ const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
4697
+ const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
4698
+ const pageNumber = page ?? 1;
4699
+ try {
4700
+ let imageBytes;
4701
+ let mediaType = "image/png";
4702
+ if (isImage) {
4703
+ imageBytes = await getS3ObjectBytes(key, exuluConfig);
4704
+ mediaType = IMAGE_MEDIA_TYPES[ext];
4705
+ if (imageBytes.length > MAX_IMAGE_BYTES) {
4706
+ return {
4707
+ error: `"${safeName}" is too large to attach (${imageBytes.length} bytes, max ${MAX_IMAGE_BYTES}). Ask the user for a smaller version of the image.`
4708
+ };
4709
+ }
4710
+ } else {
4711
+ let pdfBytes;
4712
+ if (isPdf) {
4713
+ pdfBytes = await getS3ObjectBytes(key, exuluConfig);
4714
+ } else {
4715
+ const etag = await getS3ObjectEtag(key, exuluConfig);
4716
+ if (!etag) {
4717
+ return { error: `Could not read session file "${safeName}". Check the exact file name.` };
4718
+ }
4719
+ pdfBytes = await getPdfPreviewBytes({ sourceKey: key, etag, config: exuluConfig });
4720
+ }
4721
+ let rendered = await renderPdfPageToPng(pdfBytes, pageNumber, SCALE_PRIMARY);
4722
+ if (!rendered) {
4723
+ return { error: `Could not render page ${pageNumber} of "${safeName}" \u2014 the document may have fewer pages.` };
4724
+ }
4725
+ if (rendered.length > MAX_IMAGE_BYTES) {
4726
+ rendered = await renderPdfPageToPng(pdfBytes, pageNumber, SCALE_FALLBACK);
4727
+ }
4728
+ if (!rendered || rendered.length > MAX_IMAGE_BYTES) {
4729
+ return { error: `Page ${pageNumber} of "${safeName}" is too complex to attach within the image size limit.` };
4730
+ }
4731
+ imageBytes = rendered;
4732
+ }
4733
+ if (!options?.toolCallId) {
4734
+ return { error: "Internal error: missing toolCallId \u2014 the image cannot be attached." };
4735
+ }
4736
+ stashToolImage(options.toolCallId, {
4737
+ data: imageBytes.toString("base64"),
4738
+ mediaType,
4739
+ label: isImage ? safeName : `${safeName} page ${pageNumber}`
4740
+ });
4741
+ return {
4742
+ attached: true,
4743
+ filename: safeName,
4744
+ page: pageNumber,
4745
+ note: "The rendered image follows this tool result as an attached user message \u2014 analyze it there. If no image message follows, the attachment has expired; call this tool again to re-render it."
4746
+ };
4747
+ } catch (err) {
4748
+ return { error: `Failed to render "${safeName}": ${err instanceof Error ? err.message : "unknown error"}` };
4749
+ }
4750
+ };
4751
+ return ExuluTool.internal({
4752
+ id: "view_document_page",
4753
+ name: "view_document_page",
4754
+ needsApproval: false,
4755
+ description: "LOOK at a page of an uploaded PDF/Office document, or at an uploaded image, from this session's files. The rendered image is attached as a user message directly after this tool result so you can visually analyze photos, charts, scans, and layouts. Use parse_document first to find which page you need. Requires a vision-capable model.",
4756
+ inputSchema: import_zod7.z.object({
4757
+ filename: import_zod7.z.string().describe('Exact session file name, e.g. "report.pdf" or "screenshot.png"'),
4758
+ page: import_zod7.z.number().int().min(1).optional().describe("Page number to render (default 1; ignored for image files)")
4759
+ }),
4760
+ type: "function",
4761
+ category: "session",
4762
+ config: [],
4763
+ // Same execute-shape cast as read_session_file / parse_document.
4764
+ execute: viewDocumentPageExecute
4765
+ });
4766
+ };
4767
+ }
4768
+ });
4769
+
4172
4770
  // src/templates/tools/convert-exulu-tools-to-ai-sdk-tools.ts
4173
4771
  var convert_exulu_tools_to_ai_sdk_tools_exports = {};
4174
4772
  __export(convert_exulu_tools_to_ai_sdk_tools_exports, {
@@ -4197,6 +4795,8 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
4197
4795
  init_truncate_tool_output();
4198
4796
  init_tool_output_offload();
4199
4797
  init_session_file_read_tool();
4798
+ init_parse_document_tool();
4799
+ init_view_document_page_tool();
4200
4800
  init_context_budget();
4201
4801
  OUTPUT_OFFLOAD_EXEMPT_TOOL_IDS = /* @__PURE__ */ new Set(["agentic_context_search"]);
4202
4802
  generateS3Key = (filename) => `${(0, import_node_crypto4.randomUUID)()}-${filename}`;
@@ -4377,6 +4977,14 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
4377
4977
  if (sessionFileReadTool && !disabled.has(sessionFileReadTool.id)) {
4378
4978
  currentTools.push(sessionFileReadTool);
4379
4979
  }
4980
+ const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig });
4981
+ if (parseDocumentTool && !disabled.has(parseDocumentTool.id)) {
4982
+ currentTools.push(parseDocumentTool);
4983
+ }
4984
+ const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig });
4985
+ if (viewDocumentPageTool && !disabled.has(viewDocumentPageTool.id)) {
4986
+ currentTools.push(viewDocumentPageTool);
4987
+ }
4380
4988
  console.log("[EXULU] Creating agentic search tool", contexts?.length, model);
4381
4989
  if (contexts?.length && model && !disabled.has("agentic_context_search")) {
4382
4990
  const index = currentTools.findIndex((tool4) => tool4.id === "agentic_context_search");
@@ -4644,13 +5252,13 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
4644
5252
  });
4645
5253
 
4646
5254
  // src/exulu/tool.ts
4647
- var import_ai3, import_zod6, import_node_crypto5, PUBLIC_TOOL_TYPES, ExuluTool;
5255
+ var import_ai3, import_zod8, import_node_crypto5, PUBLIC_TOOL_TYPES, ExuluTool;
4648
5256
  var init_tool = __esm({
4649
5257
  "src/exulu/tool.ts"() {
4650
5258
  "use strict";
4651
5259
  init_cjs_shims();
4652
5260
  import_ai3 = require("ai");
4653
- import_zod6 = require("zod");
5261
+ import_zod8 = require("zod");
4654
5262
  init_sanitize_name();
4655
5263
  import_node_crypto5 = require("crypto");
4656
5264
  init_singleton();
@@ -4707,7 +5315,7 @@ var init_tool = __esm({
4707
5315
  this.type = type;
4708
5316
  this.tool = (0, import_ai3.tool)({
4709
5317
  description,
4710
- inputSchema: inputSchema || import_zod6.z.object({}),
5318
+ inputSchema: inputSchema || import_zod8.z.object({}),
4711
5319
  execute: oauth ? wrapExecuteWithOauth(id, oauth, execute2) : execute2
4712
5320
  });
4713
5321
  }
@@ -5000,62 +5608,62 @@ function effectiveKbSettings(profile, ctx) {
5000
5608
  keywordPrefilter: preset.keywordPrefilter
5001
5609
  };
5002
5610
  }
5003
- var import_zod7, KB_KINDS, DEFAULT_PREFILTER_CUTOFF, RRF_K, CHUNK_GROUP_MAX, kbProfileSchema, knowledgeBasesSchema, routingRuleSchema, routingSchema, identifierSetSchema, vocabularySchema, memorySchema, tuningSchema, boolVal, strVal, KIND_PRESETS;
5611
+ var import_zod9, KB_KINDS, DEFAULT_PREFILTER_CUTOFF, RRF_K, CHUNK_GROUP_MAX, kbProfileSchema, knowledgeBasesSchema, routingRuleSchema, routingSchema, identifierSetSchema, vocabularySchema, memorySchema, tuningSchema, boolVal, strVal, KIND_PRESETS;
5004
5612
  var init_config = __esm({
5005
5613
  "ee/agentic-retrieval/pipeline/config.ts"() {
5006
5614
  "use strict";
5007
5615
  init_cjs_shims();
5008
- import_zod7 = require("zod");
5616
+ import_zod9 = require("zod");
5009
5617
  KB_KINDS = ["documents", "conversations", "records"];
5010
5618
  DEFAULT_PREFILTER_CUTOFF = 2.5;
5011
5619
  RRF_K = 60;
5012
5620
  CHUNK_GROUP_MAX = 10;
5013
- kbProfileSchema = import_zod7.z.object({
5014
- enabled: import_zod7.z.boolean().default(true),
5015
- kind: import_zod7.z.enum(KB_KINDS).default("documents"),
5016
- instructions: import_zod7.z.string().default(""),
5017
- overrides: import_zod7.z.object({
5018
- limit: import_zod7.z.number().int().positive().optional(),
5019
- expand: import_zod7.z.number().int().min(0).optional(),
5020
- multiQuery: import_zod7.z.boolean().optional(),
5021
- hyde: import_zod7.z.boolean().optional()
5621
+ kbProfileSchema = import_zod9.z.object({
5622
+ enabled: import_zod9.z.boolean().default(true),
5623
+ kind: import_zod9.z.enum(KB_KINDS).default("documents"),
5624
+ instructions: import_zod9.z.string().default(""),
5625
+ overrides: import_zod9.z.object({
5626
+ limit: import_zod9.z.number().int().positive().optional(),
5627
+ expand: import_zod9.z.number().int().min(0).optional(),
5628
+ multiQuery: import_zod9.z.boolean().optional(),
5629
+ hyde: import_zod9.z.boolean().optional()
5022
5630
  }).default({})
5023
5631
  });
5024
- knowledgeBasesSchema = import_zod7.z.record(import_zod7.z.string(), kbProfileSchema);
5025
- routingRuleSchema = import_zod7.z.object({
5026
- id: import_zod7.z.string(),
5027
- label: import_zod7.z.string(),
5028
- description: import_zod7.z.string(),
5029
- main: import_zod7.z.array(import_zod7.z.string()),
5030
- fallback: import_zod7.z.array(import_zod7.z.string()).default([])
5632
+ knowledgeBasesSchema = import_zod9.z.record(import_zod9.z.string(), kbProfileSchema);
5633
+ routingRuleSchema = import_zod9.z.object({
5634
+ id: import_zod9.z.string(),
5635
+ label: import_zod9.z.string(),
5636
+ description: import_zod9.z.string(),
5637
+ main: import_zod9.z.array(import_zod9.z.string()),
5638
+ fallback: import_zod9.z.array(import_zod9.z.string()).default([])
5031
5639
  });
5032
- routingSchema = import_zod7.z.object({ rules: import_zod7.z.array(routingRuleSchema).default([]) });
5033
- identifierSetSchema = import_zod7.z.object({
5034
- name: import_zod7.z.string(),
5035
- description: import_zod7.z.string().default(""),
5036
- examples: import_zod7.z.array(import_zod7.z.string()).default([]),
5037
- strategy: import_zod7.z.enum(["fuzzy", "exact"]),
5038
- contexts: import_zod7.z.array(import_zod7.z.string()).default([])
5640
+ routingSchema = import_zod9.z.object({ rules: import_zod9.z.array(routingRuleSchema).default([]) });
5641
+ identifierSetSchema = import_zod9.z.object({
5642
+ name: import_zod9.z.string(),
5643
+ description: import_zod9.z.string().default(""),
5644
+ examples: import_zod9.z.array(import_zod9.z.string()).default([]),
5645
+ strategy: import_zod9.z.enum(["fuzzy", "exact"]),
5646
+ contexts: import_zod9.z.array(import_zod9.z.string()).default([])
5039
5647
  });
5040
- vocabularySchema = import_zod7.z.object({
5041
- glossary: import_zod7.z.array(import_zod7.z.object({ term: import_zod7.z.string(), meaning: import_zod7.z.string() })).default([]),
5042
- identifiers: import_zod7.z.array(identifierSetSchema).default([]),
5043
- rewrites: import_zod7.z.array(import_zod7.z.object({ find: import_zod7.z.string(), replace: import_zod7.z.string() })).default([]),
5044
- styleHint: import_zod7.z.string().default("")
5648
+ vocabularySchema = import_zod9.z.object({
5649
+ glossary: import_zod9.z.array(import_zod9.z.object({ term: import_zod9.z.string(), meaning: import_zod9.z.string() })).default([]),
5650
+ identifiers: import_zod9.z.array(identifierSetSchema).default([]),
5651
+ rewrites: import_zod9.z.array(import_zod9.z.object({ find: import_zod9.z.string(), replace: import_zod9.z.string() })).default([]),
5652
+ styleHint: import_zod9.z.string().default("")
5045
5653
  });
5046
- memorySchema = import_zod7.z.object({
5047
- enabled: import_zod7.z.boolean().default(true),
5048
- override: import_zod7.z.boolean().default(false),
5049
- filePrioritization: import_zod7.z.boolean().default(false),
5050
- queryAugmentation: import_zod7.z.boolean().default(true)
5654
+ memorySchema = import_zod9.z.object({
5655
+ enabled: import_zod9.z.boolean().default(true),
5656
+ override: import_zod9.z.boolean().default(false),
5657
+ filePrioritization: import_zod9.z.boolean().default(false),
5658
+ queryAugmentation: import_zod9.z.boolean().default(true)
5051
5659
  });
5052
- tuningSchema = import_zod7.z.object({
5053
- topK: import_zod7.z.number().int().positive().default(5),
5054
- fallbackThreshold: import_zod7.z.number().min(0).max(1).default(0.95),
5055
- pinBoost: import_zod7.z.number().min(0).max(1).default(0.15),
5056
- identifierBoost: import_zod7.z.number().min(0).max(1).default(0.15),
5057
- pageWindow: import_zod7.z.number().int().min(0).default(1),
5058
- maxQueriesPerContext: import_zod7.z.number().int().positive().default(5)
5660
+ tuningSchema = import_zod9.z.object({
5661
+ topK: import_zod9.z.number().int().positive().default(5),
5662
+ fallbackThreshold: import_zod9.z.number().min(0).max(1).default(0.95),
5663
+ pinBoost: import_zod9.z.number().min(0).max(1).default(0.15),
5664
+ identifierBoost: import_zod9.z.number().min(0).max(1).default(0.15),
5665
+ pageWindow: import_zod9.z.number().int().min(0).default(1),
5666
+ maxQueriesPerContext: import_zod9.z.number().int().positive().default(5)
5059
5667
  });
5060
5668
  boolVal = (v) => v === true || v === "true" || v === 1;
5061
5669
  strVal = (v, fallback) => typeof v === "string" && v.length > 0 ? v : fallback;
@@ -5302,9 +5910,9 @@ async function resolveIdentifierPins({
5302
5910
  system: set.strategy === "exact" ? EXACT_EXTRACTION_PROMPT(set) : FUZZY_EXTRACTION_PROMPT(set),
5303
5911
  messages: [{ role: "user", content: question }],
5304
5912
  output: import_ai4.Output.object({
5305
- schema: import_zod8.z.object({
5306
- hasMatches: import_zod8.z.boolean(),
5307
- matches: import_zod8.z.array(import_zod8.z.string()).optional()
5913
+ schema: import_zod10.z.object({
5914
+ hasMatches: import_zod10.z.boolean(),
5915
+ matches: import_zod10.z.array(import_zod10.z.string()).optional()
5308
5916
  })
5309
5917
  }),
5310
5918
  maxOutputTokens: 300
@@ -5352,14 +5960,14 @@ async function resolveIdentifierPins({
5352
5960
  );
5353
5961
  return { pinsByContext, exactPinsByContext, steps };
5354
5962
  }
5355
- var import_fuse, import_ai4, import_zod8, itemCaches, ensureItemsCache, FUZZY_EXTRACTION_PROMPT, EXACT_EXTRACTION_PROMPT;
5963
+ var import_fuse, import_ai4, import_zod10, itemCaches, ensureItemsCache, FUZZY_EXTRACTION_PROMPT, EXACT_EXTRACTION_PROMPT;
5356
5964
  var init_prefilter = __esm({
5357
5965
  "ee/agentic-retrieval/pipeline/prefilter.ts"() {
5358
5966
  "use strict";
5359
5967
  init_cjs_shims();
5360
5968
  import_fuse = __toESM(require("fuse.js"), 1);
5361
5969
  import_ai4 = require("ai");
5362
- import_zod8 = require("zod");
5970
+ import_zod10 = require("zod");
5363
5971
  init_with_retry();
5364
5972
  init_text_utils();
5365
5973
  init_config();
@@ -5444,11 +6052,11 @@ If explicit, return the knowledge base ids. If not, return an empty array.`;
5444
6052
  system: buildDocPagePrompt(knownIdentifiers),
5445
6053
  messages: [{ role: "user", content: question }],
5446
6054
  output: import_ai5.Output.object({
5447
- schema: import_zod9.z.object({
5448
- hasFilenameHint: import_zod9.z.boolean(),
5449
- filenameHints: import_zod9.z.array(import_zod9.z.string()).optional(),
5450
- hasPageHint: import_zod9.z.boolean(),
5451
- pageNumber: import_zod9.z.number().int().nullable().optional()
6055
+ schema: import_zod11.z.object({
6056
+ hasFilenameHint: import_zod11.z.boolean(),
6057
+ filenameHints: import_zod11.z.array(import_zod11.z.string()).optional(),
6058
+ hasPageHint: import_zod11.z.boolean(),
6059
+ pageNumber: import_zod11.z.number().int().nullable().optional()
5452
6060
  })
5453
6061
  }),
5454
6062
  maxOutputTokens: 300
@@ -5475,9 +6083,9 @@ If explicit, return the knowledge base ids. If not, return an empty array.`;
5475
6083
  temperature: 0,
5476
6084
  system: kbSystemPrompt,
5477
6085
  output: import_ai5.Output.object({
5478
- schema: import_zod9.z.object({
5479
- explicitlyRequestedKnowledgeBases: import_zod9.z.array(
5480
- import_zod9.z.enum(enabledContexts.map((c) => c.id))
6086
+ schema: import_zod11.z.object({
6087
+ explicitlyRequestedKnowledgeBases: import_zod11.z.array(
6088
+ import_zod11.z.enum(enabledContexts.map((c) => c.id))
5481
6089
  )
5482
6090
  })
5483
6091
  }),
@@ -5575,9 +6183,9 @@ ${extraInstructions}
5575
6183
  system: classifyPrompt,
5576
6184
  messages: [{ role: "user", content: question }],
5577
6185
  output: import_ai5.Output.object({
5578
- schema: import_zod9.z.object({
5579
- ruleId: import_zod9.z.enum(ruleIds),
5580
- reason: import_zod9.z.string()
6186
+ schema: import_zod11.z.object({
6187
+ ruleId: import_zod11.z.enum(ruleIds),
6188
+ reason: import_zod11.z.string()
5581
6189
  })
5582
6190
  }),
5583
6191
  maxOutputTokens: 200
@@ -5636,13 +6244,13 @@ ${extraInstructions}
5636
6244
  };
5637
6245
  }
5638
6246
  }
5639
- var import_ai5, import_zod9, MAX_USER_PIN_MATCHES, buildDocPagePrompt;
6247
+ var import_ai5, import_zod11, MAX_USER_PIN_MATCHES, buildDocPagePrompt;
5640
6248
  var init_routing = __esm({
5641
6249
  "ee/agentic-retrieval/pipeline/routing.ts"() {
5642
6250
  "use strict";
5643
6251
  init_cjs_shims();
5644
6252
  import_ai5 = require("ai");
5645
- import_zod9 = require("zod");
6253
+ import_zod11 = require("zod");
5646
6254
  init_with_retry();
5647
6255
  init_prefilter();
5648
6256
  init_text_utils();
@@ -5914,8 +6522,8 @@ async function runMemoryPhase({
5914
6522
  }
5915
6523
  ],
5916
6524
  output: import_ai6.Output.object({
5917
- schema: import_zod10.z.object({
5918
- relevantChunkIds: import_zod10.z.array(import_zod10.z.string()).describe(
6525
+ schema: import_zod12.z.object({
6526
+ relevantChunkIds: import_zod12.z.array(import_zod12.z.string()).describe(
5919
6527
  "The chunk_ids (UUIDs at the start of each bullet) of chunks containing information relevant to the user's question. Empty array if none are relevant."
5920
6528
  )
5921
6529
  })
@@ -6031,17 +6639,17 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
6031
6639
  }
6032
6640
  ],
6033
6641
  output: import_ai6.Output.object({
6034
- schema: import_zod10.z.object({
6035
- overrides: import_zod10.z.boolean().describe(
6642
+ schema: import_zod12.z.object({
6643
+ overrides: import_zod12.z.boolean().describe(
6036
6644
  "True ONLY if a memory chunk directly and sufficiently answers the user's question and should be authoritative over the documents. Be strict; when unsure, false."
6037
6645
  ),
6038
- confidence: import_zod10.z.enum(["high", "medium", "low"]).describe(
6646
+ confidence: import_zod12.z.enum(["high", "medium", "low"]).describe(
6039
6647
  "Confidence that the selected memory chunk(s) fully and directly answer the question."
6040
6648
  ),
6041
- authoritativeChunkIds: import_zod10.z.array(import_zod10.z.string()).describe(
6649
+ authoritativeChunkIds: import_zod12.z.array(import_zod12.z.string()).describe(
6042
6650
  "The chunk_ids of the memory chunk(s) that directly answer the question. Empty if overrides is false."
6043
6651
  ),
6044
- reason: import_zod10.z.string().describe(
6652
+ reason: import_zod12.z.string().describe(
6045
6653
  "One short sentence: why this memory does or does not directly answer the question."
6046
6654
  )
6047
6655
  })
@@ -6072,9 +6680,9 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
6072
6680
  system: "You are a helpful assistant that will strictly follow the user's instructions.",
6073
6681
  messages: [{ role: "user", content: PROMPT_EXTRACT_PRIORITIZED_FILES }],
6074
6682
  output: import_ai6.Output.object({
6075
- schema: import_zod10.z.object({
6076
- shouldPrioritizeFiles: import_zod10.z.boolean(),
6077
- fileNameHints: import_zod10.z.array(import_zod10.z.string()).optional()
6683
+ schema: import_zod12.z.object({
6684
+ shouldPrioritizeFiles: import_zod12.z.boolean(),
6685
+ fileNameHints: import_zod12.z.array(import_zod12.z.string()).optional()
6078
6686
  })
6079
6687
  }),
6080
6688
  maxOutputTokens: 300
@@ -6093,10 +6701,10 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
6093
6701
  system: "You are a helpful assistant that will strictly follow the user's instructions.",
6094
6702
  messages: [{ role: "user", content: QUERY_AUGMENTATION_PROMPT }],
6095
6703
  output: import_ai6.Output.object({
6096
- schema: import_zod10.z.object({
6097
- updatedUserQuestion: import_zod10.z.string(),
6098
- updatedRelevantKeywords: import_zod10.z.array(import_zod10.z.string()),
6099
- updatedImportantKeyword: import_zod10.z.string()
6704
+ schema: import_zod12.z.object({
6705
+ updatedUserQuestion: import_zod12.z.string(),
6706
+ updatedRelevantKeywords: import_zod12.z.array(import_zod12.z.string()),
6707
+ updatedImportantKeyword: import_zod12.z.string()
6100
6708
  })
6101
6709
  }),
6102
6710
  maxOutputTokens: 600
@@ -6182,13 +6790,13 @@ ${glossary.map((g) => `${g.term} : ${g.meaning}`).join("\n")}` : "";
6182
6790
  return neutralResult(question, keywords, importantKeyword);
6183
6791
  }
6184
6792
  }
6185
- var import_ai6, import_zod10, MEMORY_OVERRIDE_MIN_CONFIDENCE, MEMORY_SYNTHETIC_RERANK_SCORE, ITEM_CACHE_TTL_MS, memoryItemCache;
6793
+ var import_ai6, import_zod12, MEMORY_OVERRIDE_MIN_CONFIDENCE, MEMORY_SYNTHETIC_RERANK_SCORE, ITEM_CACHE_TTL_MS, memoryItemCache;
6186
6794
  var init_memory = __esm({
6187
6795
  "ee/agentic-retrieval/pipeline/memory.ts"() {
6188
6796
  "use strict";
6189
6797
  init_cjs_shims();
6190
6798
  import_ai6 = require("ai");
6191
- import_zod10 = require("zod");
6799
+ import_zod12 = require("zod");
6192
6800
  init_with_retry();
6193
6801
  init_multi_query();
6194
6802
  init_prefilter();
@@ -6684,11 +7292,11 @@ function createAgenticRetrievalTool(opts) {
6684
7292
  default: '{"topK":5,"fallbackThreshold":0.95,"pinBoost":0.15,"identifierBoost":0.15,"pageWindow":1,"maxQueriesPerContext":5}'
6685
7293
  }
6686
7294
  ],
6687
- inputSchema: import_zod11.z.object({
6688
- userQuery: import_zod11.z.string().describe("The original unaltered question from the user"),
6689
- relevantKeywords: import_zod11.z.array(import_zod11.z.string()).describe("Keywords extracted from the user's question relevant to the search"),
6690
- importantKeyword: import_zod11.z.string().describe("The single most important keyword from the user's question"),
6691
- confirmedContextIds: import_zod11.z.array(import_zod11.z.string()).optional().describe(
7295
+ inputSchema: import_zod13.z.object({
7296
+ userQuery: import_zod13.z.string().describe("The original unaltered question from the user"),
7297
+ relevantKeywords: import_zod13.z.array(import_zod13.z.string()).describe("Keywords extracted from the user's question relevant to the search"),
7298
+ importantKeyword: import_zod13.z.string().describe("The single most important keyword from the user's question"),
7299
+ confirmedContextIds: import_zod13.z.array(import_zod13.z.string()).optional().describe(
6692
7300
  "Knowledge base IDs explicitly confirmed by the user to be used in the retrieval. When present, only searches these contexts."
6693
7301
  )
6694
7302
  }),
@@ -7079,12 +7687,12 @@ Verified answer:
7079
7687
  }
7080
7688
  });
7081
7689
  }
7082
- var import_zod11;
7690
+ var import_zod13;
7083
7691
  var init_pipeline = __esm({
7084
7692
  "ee/agentic-retrieval/pipeline/index.ts"() {
7085
7693
  "use strict";
7086
7694
  init_cjs_shims();
7087
- import_zod11 = require("zod");
7695
+ import_zod13 = require("zod");
7088
7696
  init_tool();
7089
7697
  init_entitlements();
7090
7698
  init_resolve_reranker();
@@ -7102,91 +7710,6 @@ var init_pipeline = __esm({
7102
7710
  }
7103
7711
  });
7104
7712
 
7105
- // src/exulu/litellm/catalog.ts
7106
- var catalog_exports = {};
7107
- __export(catalog_exports, {
7108
- __resetLiteLLMCatalogCacheForTesting: () => __resetLiteLLMCatalogCacheForTesting,
7109
- fetchLiteLLMCatalog: () => fetchLiteLLMCatalog,
7110
- findLiteLLMModel: () => findLiteLLMModel
7111
- });
7112
- var CACHE_TTL_MS, _cache, __resetLiteLLMCatalogCacheForTesting, fetchLiteLLMCatalog, findLiteLLMModel;
7113
- var init_catalog = __esm({
7114
- "src/exulu/litellm/catalog.ts"() {
7115
- "use strict";
7116
- init_cjs_shims();
7117
- CACHE_TTL_MS = 3e4;
7118
- __resetLiteLLMCatalogCacheForTesting = () => {
7119
- _cache = void 0;
7120
- };
7121
- fetchLiteLLMCatalog = async () => {
7122
- if (process.env.EXULU_USE_LITELLM !== "true") return [];
7123
- if (_cache && _cache.expiresAt > Date.now()) {
7124
- return _cache.items;
7125
- }
7126
- const host = process.env.LITELLM_HOST ?? "127.0.0.1";
7127
- const port = process.env.LITELLM_PORT ?? "4000";
7128
- const masterKey = process.env.LITELLM_MASTER_KEY;
7129
- if (!masterKey) return [];
7130
- try {
7131
- const res = await fetch(`http://${host}:${port}/model/info`, {
7132
- method: "GET",
7133
- headers: { Authorization: `Bearer ${masterKey}` }
7134
- });
7135
- if (!res.ok) {
7136
- console.error(
7137
- `[EXULU] litellmCatalog: LiteLLM /model/info returned ${res.status}`
7138
- );
7139
- return [];
7140
- }
7141
- const json = await res.json();
7142
- const items = (Array.isArray(json?.data) ? json.data : []).map((m) => ({
7143
- // filter out trailing * from model_name
7144
- model_name: m.model_name.replace(/\*$/, ""),
7145
- upstream_model: m.litellm_params?.model ?? null,
7146
- tags: Array.isArray(m.model_info?.tags) ? m.model_info.tags : [],
7147
- brand: m.model_info?.brand ?? null,
7148
- type: m.model_info?.type ?? null,
7149
- region: m.model_info?.region ?? null,
7150
- max_tokens: m.model_info?.max_tokens ?? null,
7151
- max_input_tokens: m.model_info?.max_input_tokens ?? null,
7152
- input_cost_per_million_tokens: m.model_info?.input_cost_per_token * 1e6,
7153
- output_cost_per_million_tokens: m.model_info?.output_cost_per_token * 1e6,
7154
- active: m.model_info?.active ?? true,
7155
- max_output_tokens: m.model_info?.max_output_tokens ?? null,
7156
- supports_vision: !!m.model_info?.supports_vision,
7157
- supports_function_calling: !!m.model_info?.supports_function_calling,
7158
- supports_pdf_input: !!m.model_info?.supports_pdf_input,
7159
- supports_audio_input: !!m.model_info?.supports_audio_input,
7160
- sizes: Array.isArray(m.model_info?.sizes) ? m.model_info.sizes : null,
7161
- qualities: Array.isArray(m.model_info?.qualities) ? m.model_info.qualities : null,
7162
- supports_edit: !!m.model_info?.supports_edit,
7163
- max_n: typeof m.model_info?.max_n === "number" ? m.model_info.max_n : null
7164
- }));
7165
- const map = /* @__PURE__ */ new Map();
7166
- for (const item of items) {
7167
- const key = `${item.model_name}-${item.upstream_model}`;
7168
- if (map.has(key)) {
7169
- map.get(key).tags.push(...item.tags);
7170
- } else {
7171
- map.set(key, item);
7172
- }
7173
- }
7174
- const uniqueItems = Array.from(map.values());
7175
- _cache = { expiresAt: Date.now() + CACHE_TTL_MS, items: uniqueItems };
7176
- return uniqueItems.filter((m) => m.type !== "speech_to_text" && m.type !== "text_to_speech");
7177
- } catch (err) {
7178
- console.error("[EXULU] litellmCatalog: failed to fetch /model/info:", err);
7179
- return [];
7180
- }
7181
- };
7182
- findLiteLLMModel = async (modelName) => {
7183
- if (!modelName) return void 0;
7184
- const items = await fetchLiteLLMCatalog();
7185
- return items.find((m) => m.model_name === modelName);
7186
- };
7187
- }
7188
- });
7189
-
7190
7713
  // src/index.ts
7191
7714
  var index_exports = {};
7192
7715
  __export(index_exports, {
@@ -18596,79 +19119,8 @@ async function parseSkillFrontmatter(zipBytes) {
18596
19119
  return { name: fm.name, description: fm.description };
18597
19120
  }
18598
19121
 
18599
- // src/sessions/pdf-preview-cache.ts
18600
- init_cjs_shims();
18601
- var import_node_child_process4 = require("child_process");
18602
- var import_node_fs6 = require("fs");
18603
- var import_promises2 = require("fs/promises");
18604
- var import_node_path5 = require("path");
18605
- var import_node_util3 = require("util");
18606
- init_uppy();
18607
- var execAsync3 = (0, import_node_util3.promisify)(import_node_child_process4.exec);
18608
- var CACHE_ROOT = "/tmp/exulu-pdf-cache";
18609
- var CACHE_IN = (0, import_node_path5.join)(CACHE_ROOT, "_in");
18610
- var CACHE_OUT = (0, import_node_path5.join)(CACHE_ROOT, "_out");
18611
- var inFlight = /* @__PURE__ */ new Map();
18612
- var PreviewRenderError = class extends Error {
18613
- constructor(message) {
18614
- super(message);
18615
- this.name = "PreviewRenderError";
18616
- }
18617
- };
18618
- function sanitizeEtag(raw) {
18619
- return raw.replace(/^"|"$/g, "").replace(/[^a-zA-Z0-9_-]/g, "_");
18620
- }
18621
- async function getPdfPreviewBytes(opts) {
18622
- const { sourceKey, etag, config } = opts;
18623
- const safeEtag = sanitizeEtag(etag);
18624
- if (!safeEtag) {
18625
- throw new PreviewRenderError(`Invalid ETag for ${sourceKey}`);
18626
- }
18627
- const cachedPath = (0, import_node_path5.join)(CACHE_ROOT, `${safeEtag}.pdf`);
18628
- if ((0, import_node_fs6.existsSync)(cachedPath)) {
18629
- return (0, import_promises2.readFile)(cachedPath);
18630
- }
18631
- const existing = inFlight.get(safeEtag);
18632
- if (existing) return existing;
18633
- const promise = (async () => {
18634
- try {
18635
- await (0, import_promises2.mkdir)(CACHE_IN, { recursive: true });
18636
- await (0, import_promises2.mkdir)(CACHE_OUT, { recursive: true });
18637
- const ext = ((0, import_node_path5.extname)(sourceKey) || ".docx").toLowerCase();
18638
- const inputPath = (0, import_node_path5.join)(CACHE_IN, `${safeEtag}${ext}`);
18639
- const outputPath = (0, import_node_path5.join)(CACHE_OUT, `${safeEtag}.pdf`);
18640
- try {
18641
- const bytes = await getS3ObjectBytes(sourceKey, config);
18642
- await (0, import_promises2.writeFile)(inputPath, bytes);
18643
- try {
18644
- await execAsync3(
18645
- `soffice --headless --convert-to pdf "${inputPath}" --outdir "${CACHE_OUT}"`,
18646
- { timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }
18647
- );
18648
- } catch (err) {
18649
- throw new PreviewRenderError(
18650
- `LibreOffice conversion failed for ${sourceKey} (etag ${etag}): ${err?.stderr ?? err?.message ?? "unknown error"}`
18651
- );
18652
- }
18653
- if (!(0, import_node_fs6.existsSync)(outputPath)) {
18654
- throw new PreviewRenderError(
18655
- `LibreOffice produced no output for ${sourceKey} (etag ${etag})`
18656
- );
18657
- }
18658
- await (0, import_promises2.rename)(outputPath, cachedPath);
18659
- return await (0, import_promises2.readFile)(cachedPath);
18660
- } finally {
18661
- await (0, import_promises2.rm)(inputPath, { force: true });
18662
- }
18663
- } finally {
18664
- inFlight.delete(safeEtag);
18665
- }
18666
- })();
18667
- inFlight.set(safeEtag, promise);
18668
- return promise;
18669
- }
18670
-
18671
19122
  // src/exulu/routes.ts
19123
+ init_pdf_preview_cache();
18672
19124
  init_create_sandbox();
18673
19125
  var import_utils5 = require("@apollo/utils.keyvaluecache");
18674
19126
  var import_body_parser = __toESM(require("body-parser"), 1);
@@ -18814,6 +19266,7 @@ function composePrepareSteps(...guards) {
18814
19266
  }
18815
19267
 
18816
19268
  // src/exulu/provider.ts
19269
+ init_tool_image_attachments();
18817
19270
  init_sanitize_tool_name();
18818
19271
 
18819
19272
  // src/exulu/auto-decline-stale-approvals.ts
@@ -18845,7 +19298,7 @@ var autoDeclineStaleApprovals = (messages) => {
18845
19298
  };
18846
19299
 
18847
19300
  // src/exulu/provider.ts
18848
- var import_zod12 = require("zod");
19301
+ var import_zod14 = require("zod");
18849
19302
  init_tool();
18850
19303
  init_resolve_model();
18851
19304
  init_statistics2();
@@ -18865,7 +19318,7 @@ init_check_record_access();
18865
19318
  init_client();
18866
19319
  init_statistics();
18867
19320
  init_convert_exulu_tools_to_ai_sdk_tools();
18868
- var import_officeparser = require("officeparser");
19321
+ var import_officeparser2 = require("officeparser");
18869
19322
  init_singleton();
18870
19323
  init_entitlements();
18871
19324
 
@@ -18994,9 +19447,9 @@ var ExuluProvider = class {
18994
19447
  name: `${agent.name}`,
18995
19448
  type: "agent",
18996
19449
  category: "agents",
18997
- inputSchema: import_zod12.z.object({
18998
- prompt: import_zod12.z.string().describe("The prompt (usually a question for the agent) to send to the agent."),
18999
- information: import_zod12.z.string().describe("A summary of relevant context / information from the current session")
19450
+ inputSchema: import_zod14.z.object({
19451
+ prompt: import_zod14.z.string().describe("The prompt (usually a question for the agent) to send to the agent."),
19452
+ information: import_zod14.z.string().describe("A summary of relevant context / information from the current session")
19000
19453
  }),
19001
19454
  description: `This tool calls an agent named: ${agent.name}. The agent does the following: ${agent.description}.`,
19002
19455
  config: [],
@@ -19303,7 +19756,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
19303
19756
  // Stop after the image_generation tool fires — the widget IS the
19304
19757
  // assistant's response, no follow-up text turn is wanted (same
19305
19758
  // reasoning as question_ask: the UI artifact is the message).
19306
- prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
19759
+ prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
19307
19760
  stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
19308
19761
  });
19309
19762
  console.log("[EXULU] Output: " + JSON.stringify(output, null, 2));
@@ -19366,7 +19819,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
19366
19819
  }),
19367
19820
  maxRetries: 2,
19368
19821
  tools,
19369
- prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
19822
+ prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
19370
19823
  stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
19371
19824
  });
19372
19825
  if (statistics) {
@@ -19460,7 +19913,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
19460
19913
  };
19461
19914
  }
19462
19915
  const arrayBuffer = await response.arrayBuffer();
19463
- const extractedText = await (0, import_officeparser.parseOfficeAsync)(arrayBuffer, {
19916
+ const extractedText = await (0, import_officeparser2.parseOfficeAsync)(arrayBuffer, {
19464
19917
  outputErrorToConsole: false,
19465
19918
  newlineDelimiter: "\n"
19466
19919
  });
@@ -19781,7 +20234,7 @@ When a tool execution is not approved by the user, do not retry it unless explic
19781
20234
  );
19782
20235
  },
19783
20236
  // todo allow configuring the step budget per skill
19784
- prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget)),
20237
+ prepareStep: composePrepareSteps(contextGuard(contextWindow), retrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
19785
20238
  stopWhen: [(0, import_ai11.stepCountIs)(turnBudget), (0, import_ai11.hasToolCall)("image_generation")]
19786
20239
  });
19787
20240
  return {
@@ -19836,12 +20289,12 @@ var saveChat = async ({
19836
20289
  // src/exulu/suggestions.ts
19837
20290
  init_cjs_shims();
19838
20291
  var import_ai12 = require("ai");
19839
- var import_zod13 = require("zod");
20292
+ var import_zod15 = require("zod");
19840
20293
  var SUGGESTIONS_SYSTEM_PROMPT = "You generate short follow-up message suggestions for the user. You are NOT continuing the conversation as the assistant \u2014 you are predicting what the user might want to say next. Suggest up to 3 short follow-up questions or messages the user might want to send next. Each suggestion must be written from the user's perspective (first person) and be 12 words or fewer. You MUST submit your answer by calling the `submit_suggestions` tool exactly once. Do not emit any plain text \u2014 only the tool call.";
19841
20294
  var submitSuggestionsTool = (0, import_ai12.tool)({
19842
20295
  description: "Submit the final list of follow-up message suggestions for the user. Must be called exactly once. Each suggestion is written from the user's perspective (first person) and is 12 words or fewer.",
19843
- inputSchema: import_zod13.z.object({
19844
- suggestions: import_zod13.z.array(import_zod13.z.string()).max(3)
20296
+ inputSchema: import_zod15.z.object({
20297
+ suggestions: import_zod15.z.array(import_zod15.z.string()).max(3)
19845
20298
  })
19846
20299
  });
19847
20300
  var MAX_CHARS_PER_MESSAGE = 1e4;
@@ -20403,7 +20856,7 @@ See docs/superpowers/specs/2026-05-31-in-chat-image-generation-design.md for the
20403
20856
  };
20404
20857
 
20405
20858
  // src/exulu/routes.ts
20406
- var import_node_path6 = require("path");
20859
+ var import_node_path9 = require("path");
20407
20860
  init_tags();
20408
20861
  init_admin_client();
20409
20862
  init_env();
@@ -20532,6 +20985,7 @@ init_resolve_model();
20532
20985
  init_sanitize_tool_name();
20533
20986
  init_context_budget();
20534
20987
  init_supervisor();
20988
+ init_tool_image_attachments();
20535
20989
  function convertOpenAIToolsToAiSdkTools(tools) {
20536
20990
  return Object.fromEntries(
20537
20991
  tools.map((t) => {
@@ -20938,7 +21392,7 @@ ${project.description}` : ""}` : "",
20938
21392
  messages: coreMessages,
20939
21393
  tools: hasTools ? activeTools : void 0,
20940
21394
  maxRetries: 2,
20941
- prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
21395
+ prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
20942
21396
  stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai14.stepCountIs)(turnBudget)],
20943
21397
  onError: (error) => {
20944
21398
  console.error("[OPENAI GATEWAY] stream error:", error);
@@ -20978,7 +21432,7 @@ ${project.description}` : ""}` : "",
20978
21432
  messages: coreMessages,
20979
21433
  tools: hasTools ? activeTools : void 0,
20980
21434
  maxRetries: 2,
20981
- prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget)),
21435
+ prepareStep: clientTools.length > 0 ? void 0 : composePrepareSteps(contextGuard(contextWindow), gatewayRetrievalGuard, finalAnswerGuard(turnBudget), imageAttachmentGuard()),
20982
21436
  stopWhen: clientTools.length > 0 ? void 0 : [(0, import_ai14.stepCountIs)(turnBudget)]
20983
21437
  });
20984
21438
  res.json(transformCompletion(text, usage.inputTokens ?? 0, usage.outputTokens ?? 0, ctx));
@@ -21212,7 +21666,7 @@ async function filterReadableSkills(db2, skills, user) {
21212
21666
  return out;
21213
21667
  }
21214
21668
 
21215
- // src/skills/bootstrap/exulu-skills.ts
21669
+ // src/skills/bootstrap/imp-skills.ts
21216
21670
  init_cjs_shims();
21217
21671
 
21218
21672
  // src/skills/bootstrap/clients.ts
@@ -21270,35 +21724,35 @@ var CLIENT_MANIFEST = [
21270
21724
  // exception: nested under agent/
21271
21725
  ];
21272
21726
 
21273
- // src/skills/bootstrap/exulu-sh.generated.ts
21727
+ // src/skills/bootstrap/imp-sh.generated.ts
21274
21728
  init_cjs_shims();
21275
- var EXULU_SH_B64 = "IyEvYmluL3NoCiMgZXh1bHUg4oCUIGhlbHBlciBmb3IgdGhlIEV4dWx1IGNlbnRyYWwgc2tpbGwgbGlicmFyeS4gVGhlIGFnZW50IGludm9rZXMgdGhpcwojIChuZXZlciByYXcgY3VybCk6IHRoZSB0b2tlbiBpcyByZWFkIGZyb20gdGhlIGNvbmZpZyBmaWxlIGhlcmUgYW5kIHNlbnQgdmlhCiMgYHgtYXBpLWtleTogQmVhcmVyYCwgc28gaXQgbmV2ZXIgZW50ZXJzIHRoZSBtb2RlbCBjb250ZXh0LiBDbGllbnQgZmFuLW91dAojIChjb3B5L3N5bWxpbmsgYWNyb3NzIGFnZW50IGNsaWVudHMpIGlzIGRldGVybWluaXN0aWMuCnNldCAtZXUKCkNPTkZJR19ESVI9IiRIT01FLy5jb25maWcvZXh1bHUiCkNPTkZJR19GSUxFPSIkQ09ORklHX0RJUi9za2lsbHMuanNvbiIKCmRpZSgpIHsgcHJpbnRmICdleHVsdTogJXNcbicgIiQqIiA+JjI7IGV4aXQgMTsgfQppbmZvKCkgeyBwcmludGYgJyVzXG4nICIkKiIgPiYyOyB9Cgpqc29uX3N0cigpIHsgIyBqc29uX3N0ciA8a2V5PiA8ZmlsZT4g4oCUIGZsYXQgImtleSI6InZhbHVlIgogIHNlZCAtbiAicy8uKlwiJDFcIltbOnNwYWNlOl1dKjpbWzpzcGFjZTpdXSpcIlxcKFteXCJdKlxcKVwiLiovXFwxL3AiICIkMiIgfCBoZWFkIC1uMQp9CgpbIC1mICIkQ09ORklHX0ZJTEUiIF0gfHwgZGllICJub3QgY29uZmlndXJlZCDigJQgcnVuOiBjdXJsIC1mc1NMIDxiYXNlX3VybD4vYXBpL3NraWxscy9pbnN0YWxsLnNoIHwgc2giCgpCQVNFX1VSTD0iJChqc29uX3N0ciBiYXNlX3VybCAiJENPTkZJR19GSUxFIikiCkJBQ0tFTkQ9IiQoanNvbl9zdHIgYmFja2VuZCAiJENPTkZJR19GSUxFIikiCkFQSV9LRVk9IiQoanNvbl9zdHIgYXBpX2tleSAiJENPTkZJR19GSUxFIikiCkxJTktfTU9ERT0iJChqc29uX3N0ciBsaW5rX21vZGUgIiRDT05GSUdfRklMRSIpIgpTQ09QRT0iJChqc29uX3N0ciBzY29wZSAiJENPTkZJR19GSUxFIikiCkNMSUVOVFM9IiQoc2VkIC1uICdzLy4qImNsaWVudHMiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlxbXChbXl1dKlwpXF0uKi9cMS9wJyAiJENPTkZJR19GSUxFIiB8IHRyICcsJyAnICcgfCB0ciAtZCAnIicgfCB0ciAtcyAnICcpIgoKWyAtbiAiJENMSUVOVFMiIF0gfHwgQ0xJRU5UUz0iYWdlbnRzIgpbIC1uICIkTElOS19NT0RFIiBdIHx8IExJTktfTU9ERT0iY29weSIKWyAtbiAiJFNDT1BFIiBdIHx8IFNDT1BFPSJwcm9qZWN0IgoKaWYgWyAteiAiJEJBQ0tFTkQiIF07IHRoZW4KICBbIC1uICIkQkFTRV9VUkwiIF0gfHwgZGllICJjb25maWcgaGFzIG5vIGJhY2tlbmQgYW5kIG5vIGJhc2VfdXJsIgogIEJBQ0tFTkQ9IiQoY3VybCAtZnNTTCAiJEJBU0VfVVJML2FwaS9jb25maWciIHwgc2VkIC1uICdzLy4qImJhY2tlbmQiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKiJcKFteIl0qXCkiLiovXDEvcCcgfCBoZWFkIC1uMSkiCiAgWyAtbiAiJEJBQ0tFTkQiIF0gfHwgZGllICJjb3VsZCBub3QgcmVzb2x2ZSBiYWNrZW5kIGZyb20gJEJBU0VfVVJML2FwaS9jb25maWciCmZpCkJBQ0tFTkQ9IiQocHJpbnRmICclcycgIiRCQUNLRU5EIiB8IHNlZCAnczovKiQ6OicpIgoKUk9PVD0iJFBXRCIKWyAiJFNDT1BFIiA9ICJob21lIiBdICYmIFJPT1Q9IiRIT01FIgoKIyBkaXJfZm9yIENMSUVOVF9JRCAtPiByZWxhdGl2ZSBza2lsbCBkaXIgKGdlbmVyYXRlZCBmcm9tIHRoZSBtYW5pZmVzdCBpbiB0aGUKIyByZWFsIGJ1aWxkOyBhIHJlcHJlc2VudGF0aXZlIHN1YnNldCBoZXJlIGZvciBsb2NhbCB0ZXN0aW5nKS4KZGlyX2ZvcigpIHsKICBjYXNlICIkMSIgaW4KX19ESVJfRk9SX0NBU0VTX18KICAgICopIHJldHVybiAxIDs7CiAgZXNhYwp9CgphcGkoKSB7ICMgYXBpIDxNRVRIT0Q+IDxwYXRoPiBbZXh0cmEgY3VybCBhcmdzLi4uXSAtPiBib2R5IG9uIHN0ZG91dAogIG09IiQxIjsgcD0iJDIiOyBzaGlmdCAyCiAgY3VybCAtZnNTIC1YICIkbSIgIiRCQUNLRU5EJHAiIC1IICJ4LWFwaS1rZXk6IEJlYXJlciAkQVBJX0tFWSIgIiRAIgp9CgptZXRhX3ZlcnNpb24oKSB7ICMgbWV0YV92ZXJzaW9uIDxuYW1lPiAtPiBjdXJyZW50X3ZlcnNpb24gZnJvbSByZWdpc3RyeQogIGFwaSBHRVQgIi9za2lsbHMvcmVnaXN0cnkvJDEiIDI+L2Rldi9udWxsIFwKICAgIHwgc2VkIC1uICdzLy4qImN1cnJlbnRfdmVyc2lvbiJbWzpzcGFjZTpdXSo6W1s6c3BhY2U6XV0qXChbMC05XVswLTldKlwpLiovXDEvcCcgfCBoZWFkIC1uMQp9CgpfbWFuYWdlZCgpIHsgIyBfbWFuYWdlZCA8ZGlyPiAtPiAwIGlmIHNhZmUgdG8gb3ZlcndyaXRlIChvdXJzIG9yIHN5bWxpbmsgb3IgYWJzZW50KQogIGQ9IiQxIgogIGlmIFsgLWUgIiRkIiBdICYmIFsgISAtTCAiJGQiIF0gJiYgWyAhIC1mICIkZC8uZXh1bHUtc2tpbGwuanNvbiIgXTsgdGhlbgogICAgaW5mbyAic2tpcCAkZCAoZXhpc3RzLCBub3QgbWFuYWdlZCBieSBleHVsdSkiOyByZXR1cm4gMQogIGZpCiAgcmV0dXJuIDAKfQoKX3B1dF9yZWFsKCkgeyAjIF9wdXRfcmVhbCA8ZGVzdD4gPHNyY2Rpcj4gPG1hcmtlci1qc29uPgogIF9tYW5hZ2VkICIkMSIgfHwgcmV0dXJuIDAKICBybSAtcmYgIiQxIjsgbWtkaXIgLXAgIiQxIjsgY3AgLVIgIiQyLy4iICIkMS8iCiAgcHJpbnRmICclc1xuJyAiJDMiID4gIiQxLy5leHVsdS1za2lsbC5qc29uIgp9CgpwbGFjZV9za2lsbCgpIHsgIyBwbGFjZV9za2lsbCA8bmFtZT4gPHNyY2Rpcj4gPHZlcnNpb24+CiAgcG5hbWU9IiQxIjsgcHNyYz0iJDIiOyBwdmVyPSIkezM6LTF9IgogIG1hcmtlcj0neyAibmFtZSI6ICInIiRwbmFtZSInIiwgInZlcnNpb24iOiAnIiRwdmVyIicsICJzb3VyY2UiOiAiJyIkQkFDS0VORCInIiB9JwogIGNhbm9uPSIkUk9PVC8uYWdlbnRzL3NraWxscy8kcG5hbWUiCiAgWyAiJExJTktfTU9ERSIgPSAic3ltbGluayIgXSAmJiBfcHV0X3JlYWwgIiRjYW5vbiIgIiRwc3JjIiAiJG1hcmtlciIKICBmb3IgaWQgaW4gJENMSUVOVFM7IGRvCiAgICBkPSIkKGRpcl9mb3IgIiRpZCIpIiB8fCB7IGluZm8gInVua25vd24gY2xpZW50OiAkaWQiOyBjb250aW51ZTsgfQogICAgcGFyZW50PSIkUk9PVC8kZCI7IGRlc3Q9IiRwYXJlbnQvJHBuYW1lIgogICAgaWYgWyAiJExJTktfTU9ERSIgPSAic3ltbGluayIgXSAmJiBbICIkaWQiICE9ICJhZ2VudHMiIF07IHRoZW4KICAgICAgX21hbmFnZWQgIiRkZXN0IiB8fCBjb250aW51ZQogICAgICBta2RpciAtcCAiJHBhcmVudCI7IHJtIC1yZiAiJGRlc3QiCiAgICAgIGlmIGxuIC1zICIkY2Fub24iICIkZGVzdCIgMj4vZGV2L251bGw7IHRoZW4KICAgICAgICBpbmZvICJsaW5rZWQgJGRlc3QgLT4gJGNhbm9uIgogICAgICBlbHNlCiAgICAgICAgaW5mbyAic3ltbGluayB1bnN1cHBvcnRlZCBhdCAkZGVzdDsgY29weWluZyIKICAgICAgICBfcHV0X3JlYWwgIiRkZXN0IiAiJHBzcmMiICIkbWFya2VyIgogICAgICBmaQogICAgZWxpZiBbICIkTElOS19NT0RFIiA9ICJjb3B5IiBdOyB0aGVuCiAgICAgIF9wdXRfcmVhbCAiJGRlc3QiICIkcHNyYyIgIiRtYXJrZXIiCiAgICBmaQogICAgIyBzeW1saW5rICsgYWdlbnRzOiBhbHJlYWR5IHBsYWNlZCBhcyB0aGUgY2Fub25pY2FsIHN0b3JlIGFib3ZlLgogIGRvbmUKfQoKaW5zdGFsbGVkX25hbWVzKCkgeyAjIHVuaXF1ZSBza2lsbCBuYW1lcyB0aGF0IGNhcnJ5IG91ciBtYXJrZXIgdW5kZXIgUk9PVAogIHsgZmluZCAiJFJPT1QvLmFnZW50cy9za2lsbHMiIC1tYXhkZXB0aCAyIC1uYW1lIC5leHVsdS1za2lsbC5qc29uIDI+L2Rldi9udWxsCiAgICBmb3IgaWQgaW4gJENMSUVOVFM7IGRvCiAgICAgIGQ9IiQoZGlyX2ZvciAiJGlkIikiIHx8IGNvbnRpbnVlCiAgICAgIGZpbmQgIiRST09ULyRkIiAtbWF4ZGVwdGggMiAtbmFtZSAuZXh1bHUtc2tpbGwuanNvbiAyPi9kZXYvbnVsbAogICAgZG9uZQogIH0gfCB3aGlsZSByZWFkIC1yIG07IGRvIGpzb25fc3RyIG5hbWUgIiRtIjsgZG9uZSB8IHNvcnQgLXUKfQoKbWFya2VyX3ZlcnNpb24oKSB7ICMgbWFya2VyX3ZlcnNpb24gPG5hbWU+CiAgZm9yIGJhc2UgaW4gIiRST09ULy5hZ2VudHMvc2tpbGxzIjsgZG8KICAgIFsgLWYgIiRiYXNlLyQxLy5leHVsdS1za2lsbC5qc29uIiBdICYmIHsganNvbl9zdHIgdmVyc2lvbiAiJGJhc2UvJDEvLmV4dWx1LXNraWxsLmpzb24iOyByZXR1cm47IH0KICBkb25lCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgY29udGludWUKICAgIGY9IiRST09ULyRkLyQxLy5leHVsdS1za2lsbC5qc29uIgogICAgWyAtZiAiJGYiIF0gJiYgeyBqc29uX3N0ciB2ZXJzaW9uICIkZiI7IHJldHVybjsgfQogIGRvbmUKfQoKZG9faW5zdGFsbCgpIHsgIyBkb19pbnN0YWxsIDxuYW1lPgogIG5hbWU9IiQxIgogIFRNUD0iJChta3RlbXAgLWQpIgogIGFwaSBHRVQgIi9za2lsbHMvcmVnaXN0cnkvJG5hbWUvZG93bmxvYWQiIC0tb3V0cHV0ICIkVE1QL3NraWxsLnppcCIgXAogICAgfHwgeyBybSAtcmYgIiRUTVAiOyBkaWUgImRvd25sb2FkIGZhaWxlZCBmb3IgJyRuYW1lJyAoNDAzID0gbm8gYWNjZXNzLCA0MDQgPSB1bmtub3duKSI7IH0KICBta2RpciAtcCAiJFRNUC94IgogIHVuemlwIC1xICIkVE1QL3NraWxsLnppcCIgLWQgIiRUTVAveCIgfHwgeyBybSAtcmYgIiRUTVAiOyBkaWUgImJhZCBhcmNoaXZlIGZvciAnJG5hbWUnIjsgfQogIHNyYz0iJChmaW5kICIkVE1QL3giIC1taW5kZXB0aCAxIC1tYXhkZXB0aCAxIC10eXBlIGQgfCBoZWFkIC1uMSkiCiAgWyAtbiAiJHNyYyIgXSB8fCB7IHJtIC1yZiAiJFRNUCI7IGRpZSAidW5leHBlY3RlZCBhcmNoaXZlIGxheW91dCBmb3IgJyRuYW1lJyI7IH0KICB2ZXI9IiQobWV0YV92ZXJzaW9uICIkbmFtZSIpIgogIHBsYWNlX3NraWxsICIkbmFtZSIgIiRzcmMiICIke3ZlcjotMX0iCiAgcm0gLXJmICIkVE1QIgogIGluZm8gImluc3RhbGxlZCAkbmFtZSAodiR7dmVyOi0xfSkgWyRMSU5LX01PREVdIGludG86ICRDTElFTlRTIgp9Cgp1c2FnZSgpIHsKICBjYXQgPiYyIDw8RU9GCmV4dWx1IOKAlCBFeHVsdSBza2lsbCBsaWJyYXJ5IGhlbHBlcgogIGV4dWx1IGxpc3QgICAgICAgICAgICAgICAgIGxpc3Qgc2tpbGxzIHlvdSBjYW4gYWNjZXNzIChKU09OKQogIGV4dWx1IGdldCA8bmFtZT4gICAgICAgICAgIHNob3cgb25lIHNraWxsJ3MgbWV0YWRhdGEgKEpTT04pCiAgZXh1bHUgaW5zdGFsbCA8bmFtZT4gICAgICAgaW5zdGFsbC9yZWZyZXNoIGEgc2tpbGwgaW50byB5b3VyIGFnZW50IGNsaWVudHMKICBleHVsdSB1cGRhdGUgWzxuYW1lPl0gICAgICB1cGRhdGUgaW5zdGFsbGVkIHNraWxscyAoYWxsLCBvciBvbmUpIHRvIGxhdGVzdAogIGV4dWx1IHB1Ymxpc2ggPG5hbWU+IDxkaXI+IHB1Ymxpc2ggYSBsb2NhbCBza2lsbCBmb2xkZXIgYXMgPG5hbWU+CiAgZXh1bHUgY29uZmlnICAgICAgICAgICAgICAgc2hvdyByZXNvbHZlZCBiYWNrZW5kIC8gc2NvcGUgLyBjbGllbnRzIChubyBzZWNyZXRzKQpFT0YKfQoKY21kPSIkezE6LWhlbHB9IgpbICQjIC1ndCAwIF0gJiYgc2hpZnQgfHwgdHJ1ZQoKY2FzZSAiJGNtZCIgaW4KICBsaXN0KSBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5IiA7OwogIGdldCkgWyAkIyAtZ2UgMSBdIHx8IGRpZSAidXNhZ2U6IGV4dWx1IGdldCA8bmFtZT4iOyBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyQxIiA7OwogIGluc3RhbGwpIFsgJCMgLWdlIDEgXSB8fCBkaWUgInVzYWdlOiBleHVsdSBpbnN0YWxsIDxuYW1lPiI7IGRvX2luc3RhbGwgIiQxIiA7OwogIHVwZGF0ZSkKICAgIGlmIFsgJCMgLWdlIDEgXTsgdGhlbiBuYW1lcz0iJDEiOyBlbHNlIG5hbWVzPSIkKGluc3RhbGxlZF9uYW1lcykiOyBmaQogICAgWyAtbiAiJG5hbWVzIiBdIHx8IHsgaW5mbyAibm8gZXh1bHUtbWFuYWdlZCBza2lsbHMgZm91bmQgdW5kZXIgJFJPT1QiOyBleGl0IDA7IH0KICAgIGZvciBuIGluICRuYW1lczsgZG8KICAgICAgY3VyPSIkKG1hcmtlcl92ZXJzaW9uICIkbiIpIgogICAgICBsYXRlc3Q9IiQobWV0YV92ZXJzaW9uICIkbiIpIgogICAgICBbIC1uICIkbGF0ZXN0IiBdIHx8IHsgaW5mbyAic2tpcCAkbiAobm90IGluIHJlZ2lzdHJ5KSI7IGNvbnRpbnVlOyB9CiAgICAgIGlmIFsgLXogIiRjdXIiIF0gfHwgWyAiJGxhdGVzdCIgLWd0ICIkY3VyIiBdIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAidXBkYXRpbmcgJG46IHYke2N1cjotP30gLT4gdiRsYXRlc3QiOyBkb19pbnN0YWxsICIkbiIKICAgICAgZWxzZQogICAgICAgIGluZm8gIiRuIHVwIHRvIGRhdGUgKHYkY3VyKSIKICAgICAgZmkKICAgIGRvbmUKICAgIDs7CiAgcHVibGlzaCkKICAgIFsgJCMgLWdlIDIgXSB8fCBkaWUgInVzYWdlOiBleHVsdSBwdWJsaXNoIDxuYW1lPiA8ZGlyPiIKICAgIG5hbWU9IiQxIjsgZm9sZGVyPSIkMiIKICAgIFsgLWQgIiRmb2xkZXIiIF0gfHwgZGllICJubyBzdWNoIGZvbGRlcjogJGZvbGRlciIKICAgIFsgLWYgIiRmb2xkZXIvU0tJTEwubWQiIF0gfHwgZGllICIkZm9sZGVyIGhhcyBubyBTS0lMTC5tZCBhdCBpdHMgcm9vdCIKICAgIGNvbW1hbmQgLXYgemlwID4vZGV2L251bGwgMj4mMSB8fCBkaWUgInRoZSAnemlwJyBjb21tYW5kIGlzIHJlcXVpcmVkIHRvIHB1Ymxpc2giCiAgICBUTVA9IiQobWt0ZW1wIC1kKSIKICAgICggY2QgIiQoZGlybmFtZSAiJGZvbGRlciIpIiBcCiAgICAgICYmIHppcCAtcSAtciAtWCAiJFRNUC9za2lsbC56aXAiICIkKGJhc2VuYW1lICIkZm9sZGVyIikiIFwKICAgICAgICAgICAteCAnKi8uZXh1bHUtc2tpbGwuanNvbicgJyovLmdpdC8qJyAnKi5EU19TdG9yZScgJyovX19NQUNPU1gvKicgKSBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJjb3VsZCBub3QgemlwICRmb2xkZXIiOyB9CiAgICBhcGkgUE9TVCAiL3NraWxscy9yZWdpc3RyeS8kbmFtZSIgLUggIkNvbnRlbnQtVHlwZTogYXBwbGljYXRpb24vemlwIiBcCiAgICAgIC0tZGF0YS1iaW5hcnkgQCIkVE1QL3NraWxsLnppcCIgXAogICAgICB8fCB7IHJtIC1yZiAiJFRNUCI7IGRpZSAicHVibGlzaCBmYWlsZWQgKDQwMyA9IG5vIHdyaXRlIGFjY2VzcywgNDA5ID0gbmFtZSB0YWtlbikiOyB9CiAgICBybSAtcmYgIiRUTVAiCiAgICBpbmZvICJwdWJsaXNoZWQgJG5hbWUiCiAgICA7OwogIGNvbmZpZykKICAgIHByaW50ZiAnYmFja2VuZD0lc1xuc2NvcGU9JXNcbmxpbmtfbW9kZT0lc1xuY2xpZW50cz0lc1xuYXBpX2tleT0lc1xuJyBcCiAgICAgICIkQkFDS0VORCIgIiRTQ09QRSIgIiRMSU5LX01PREUiICIkQ0xJRU5UUyIgXAogICAgICAiJChbIC1uICIkQVBJX0tFWSIgXSAmJiBlY2hvIHNldCB8fCBlY2hvIE1JU1NJTkcpIgogICAgOzsKICBoZWxwfC0taGVscHwtaCkgdXNhZ2UgOzsKICAqKSBpbmZvICJ1bmtub3duIGNvbW1hbmQ6ICRjbWQiOyB1c2FnZTsgZXhpdCAyIDs7CmVzYWMK";
21729
+ var IMP_SH_B64 = "IyEvYmluL3NoCiMgaW1wIOKAlCBoZWxwZXIgZm9yIHRoZSBjZW50cmFsIHNraWxsIGxpYnJhcnkuIFRoZSBhZ2VudCBpbnZva2VzIHRoaXMKIyAobmV2ZXIgcmF3IGN1cmwpOiB0aGUgdG9rZW4gaXMgcmVhZCBmcm9tIHRoZSBjb25maWcgZmlsZSBoZXJlIGFuZCBzZW50IHZpYQojIGB4LWFwaS1rZXk6IEJlYXJlcmAsIHNvIGl0IG5ldmVyIGVudGVycyB0aGUgbW9kZWwgY29udGV4dC4gQ2xpZW50IGZhbi1vdXQKIyAoY29weS9zeW1saW5rIGFjcm9zcyBhZ2VudCBjbGllbnRzKSBpcyBkZXRlcm1pbmlzdGljLgpzZXQgLWV1CgpDT05GSUdfRElSPSIkSE9NRS8uY29uZmlnL2ltcCIKQ09ORklHX0ZJTEU9IiRDT05GSUdfRElSL3NraWxscy5qc29uIgoKZGllKCkgeyBwcmludGYgJ2ltcDogJXNcbicgIiQqIiA+JjI7IGV4aXQgMTsgfQppbmZvKCkgeyBwcmludGYgJyVzXG4nICIkKiIgPiYyOyB9Cgpqc29uX3N0cigpIHsgIyBqc29uX3N0ciA8a2V5PiA8ZmlsZT4g4oCUIGZsYXQgImtleSI6InZhbHVlIgogIHNlZCAtbiAicy8uKlwiJDFcIltbOnNwYWNlOl1dKjpbWzpzcGFjZTpdXSpcIlxcKFteXCJdKlxcKVwiLiovXFwxL3AiICIkMiIgfCBoZWFkIC1uMQp9CgpbIC1mICIkQ09ORklHX0ZJTEUiIF0gfHwgZGllICJub3QgY29uZmlndXJlZCDigJQgcnVuOiBjdXJsIC1mc1NMIDxiYXNlX3VybD4vYXBpL3NraWxscy9pbnN0YWxsLnNoIHwgc2giCgpCQVNFX1VSTD0iJChqc29uX3N0ciBiYXNlX3VybCAiJENPTkZJR19GSUxFIikiCkJBQ0tFTkQ9IiQoanNvbl9zdHIgYmFja2VuZCAiJENPTkZJR19GSUxFIikiCkFQSV9LRVk9IiQoanNvbl9zdHIgYXBpX2tleSAiJENPTkZJR19GSUxFIikiCkxJTktfTU9ERT0iJChqc29uX3N0ciBsaW5rX21vZGUgIiRDT05GSUdfRklMRSIpIgpTQ09QRT0iJChqc29uX3N0ciBzY29wZSAiJENPTkZJR19GSUxFIikiCkNMSUVOVFM9IiQoc2VkIC1uICdzLy4qImNsaWVudHMiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlxbXChbXl1dKlwpXF0uKi9cMS9wJyAiJENPTkZJR19GSUxFIiB8IHRyICcsJyAnICcgfCB0ciAtZCAnIicgfCB0ciAtcyAnICcpIgoKWyAtbiAiJENMSUVOVFMiIF0gfHwgQ0xJRU5UUz0iYWdlbnRzIgpbIC1uICIkTElOS19NT0RFIiBdIHx8IExJTktfTU9ERT0iY29weSIKWyAtbiAiJFNDT1BFIiBdIHx8IFNDT1BFPSJwcm9qZWN0IgoKaWYgWyAteiAiJEJBQ0tFTkQiIF07IHRoZW4KICBbIC1uICIkQkFTRV9VUkwiIF0gfHwgZGllICJjb25maWcgaGFzIG5vIGJhY2tlbmQgYW5kIG5vIGJhc2VfdXJsIgogIEJBQ0tFTkQ9IiQoY3VybCAtZnNTTCAiJEJBU0VfVVJML2FwaS9jb25maWciIHwgc2VkIC1uICdzLy4qImJhY2tlbmQiW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKiJcKFteIl0qXCkiLiovXDEvcCcgfCBoZWFkIC1uMSkiCiAgWyAtbiAiJEJBQ0tFTkQiIF0gfHwgZGllICJjb3VsZCBub3QgcmVzb2x2ZSBiYWNrZW5kIGZyb20gJEJBU0VfVVJML2FwaS9jb25maWciCmZpCkJBQ0tFTkQ9IiQocHJpbnRmICclcycgIiRCQUNLRU5EIiB8IHNlZCAnczovKiQ6OicpIgoKUk9PVD0iJFBXRCIKWyAiJFNDT1BFIiA9ICJob21lIiBdICYmIFJPT1Q9IiRIT01FIgoKIyBkaXJfZm9yIENMSUVOVF9JRCAtPiByZWxhdGl2ZSBza2lsbCBkaXIgKGdlbmVyYXRlZCBmcm9tIHRoZSBtYW5pZmVzdCBpbiB0aGUKIyByZWFsIGJ1aWxkOyBhIHJlcHJlc2VudGF0aXZlIHN1YnNldCBoZXJlIGZvciBsb2NhbCB0ZXN0aW5nKS4KZGlyX2ZvcigpIHsKICBjYXNlICIkMSIgaW4KX19ESVJfRk9SX0NBU0VTX18KICAgICopIHJldHVybiAxIDs7CiAgZXNhYwp9CgojIE9uZSAiLi4vIiBwZXIgcGF0aCBjb21wb25lbnQgb2YgJDEgKGEgY2xpZW50IGRpciByZWxhdGl2ZSB0byBST09UKSwgc28KIyBzeW1saW5rcyBhcmUgcmVsYXRpdmUgYW5kIHN1cnZpdmUgYmVpbmcgY29tbWl0dGVkIGFuZCBjaGVja2VkIG91dCBlbHNld2hlcmUuCnJlbF90b19yb290KCkgewogIF91cD0iIjsgX29sZGlmcz0kSUZTOyBJRlM9LwogIGZvciBfc2VnIGluICQxOyBkbyBbIC1uICIkX3NlZyIgXSAmJiBfdXA9Ii4uLyRfdXAiOyBkb25lCiAgSUZTPSRfb2xkaWZzOyBwcmludGYgJyVzJyAiJF91cCIKfQoKYXBpKCkgeyAjIGFwaSA8TUVUSE9EPiA8cGF0aD4gW2V4dHJhIGN1cmwgYXJncy4uLl0gLT4gYm9keSBvbiBzdGRvdXQKICBtPSIkMSI7IHA9IiQyIjsgc2hpZnQgMgogIGN1cmwgLWZzUyAtWCAiJG0iICIkQkFDS0VORCRwIiAtSCAieC1hcGkta2V5OiBCZWFyZXIgJEFQSV9LRVkiICIkQCIKfQoKbWV0YV92ZXJzaW9uKCkgeyAjIG1ldGFfdmVyc2lvbiA8bmFtZT4gLT4gY3VycmVudF92ZXJzaW9uIGZyb20gcmVnaXN0cnkKICBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyQxIiAyPi9kZXYvbnVsbCBcCiAgICB8IHNlZCAtbiAncy8uKiJjdXJyZW50X3ZlcnNpb24iW1s6c3BhY2U6XV0qOltbOnNwYWNlOl1dKlwoWzAtOV1bMC05XSpcKS4qL1wxL3AnIHwgaGVhZCAtbjEKfQoKX21hbmFnZWQoKSB7ICMgX21hbmFnZWQgPGRpcj4gLT4gMCBpZiBzYWZlIHRvIG92ZXJ3cml0ZSAob3VycyBvciBzeW1saW5rIG9yIGFic2VudCkKICBkPSIkMSIKICBpZiBbIC1lICIkZCIgXSAmJiBbICEgLUwgIiRkIiBdICYmIFsgISAtZiAiJGQvLmltcC1za2lsbC5qc29uIiBdOyB0aGVuCiAgICBpbmZvICJza2lwICRkIChleGlzdHMsIG5vdCBtYW5hZ2VkIGJ5IGltcCkiOyByZXR1cm4gMQogIGZpCiAgcmV0dXJuIDAKfQoKX3B1dF9yZWFsKCkgeyAjIF9wdXRfcmVhbCA8ZGVzdD4gPHNyY2Rpcj4gPG1hcmtlci1qc29uPgogIF9tYW5hZ2VkICIkMSIgfHwgcmV0dXJuIDAKICBybSAtcmYgIiQxIjsgbWtkaXIgLXAgIiQxIjsgY3AgLVIgIiQyLy4iICIkMS8iCiAgcHJpbnRmICclc1xuJyAiJDMiID4gIiQxLy5pbXAtc2tpbGwuanNvbiIKfQoKcGxhY2Vfc2tpbGwoKSB7ICMgcGxhY2Vfc2tpbGwgPG5hbWU+IDxzcmNkaXI+IDx2ZXJzaW9uPgogIHBuYW1lPSIkMSI7IHBzcmM9IiQyIjsgcHZlcj0iJHszOi0xfSIKICBtYXJrZXI9J3sgIm5hbWUiOiAiJyIkcG5hbWUiJyIsICJ2ZXJzaW9uIjogJyIkcHZlciInLCAic291cmNlIjogIiciJEJBQ0tFTkQiJyIgfScKICBjYW5vbj0iJFJPT1QvLmFnZW50cy9za2lsbHMvJHBuYW1lIgogIFsgIiRMSU5LX01PREUiID0gInN5bWxpbmsiIF0gJiYgX3B1dF9yZWFsICIkY2Fub24iICIkcHNyYyIgIiRtYXJrZXIiCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgeyBpbmZvICJ1bmtub3duIGNsaWVudDogJGlkIjsgY29udGludWU7IH0KICAgIHBhcmVudD0iJFJPT1QvJGQiOyBkZXN0PSIkcGFyZW50LyRwbmFtZSIKICAgIGlmIFsgIiRMSU5LX01PREUiID0gInN5bWxpbmsiIF0gJiYgWyAiJGlkIiAhPSAiYWdlbnRzIiBdOyB0aGVuCiAgICAgIF9tYW5hZ2VkICIkZGVzdCIgfHwgY29udGludWUKICAgICAgbWtkaXIgLXAgIiRwYXJlbnQiOyBybSAtcmYgIiRkZXN0IgogICAgICByZWw9IiQocmVsX3RvX3Jvb3QgIiRkIikuYWdlbnRzL3NraWxscy8kcG5hbWUiCiAgICAgIGlmIGxuIC1zICIkcmVsIiAiJGRlc3QiIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAibGlua2VkICRkZXN0IC0+ICRyZWwiCiAgICAgIGVsc2UKICAgICAgICBpbmZvICJzeW1saW5rIHVuc3VwcG9ydGVkIGF0ICRkZXN0OyBjb3B5aW5nIgogICAgICAgIF9wdXRfcmVhbCAiJGRlc3QiICIkcHNyYyIgIiRtYXJrZXIiCiAgICAgIGZpCiAgICBlbGlmIFsgIiRMSU5LX01PREUiID0gImNvcHkiIF07IHRoZW4KICAgICAgX3B1dF9yZWFsICIkZGVzdCIgIiRwc3JjIiAiJG1hcmtlciIKICAgIGZpCiAgICAjIHN5bWxpbmsgKyBhZ2VudHM6IGFscmVhZHkgcGxhY2VkIGFzIHRoZSBjYW5vbmljYWwgc3RvcmUgYWJvdmUuCiAgZG9uZQp9CgppbnN0YWxsZWRfbmFtZXMoKSB7ICMgdW5pcXVlIHNraWxsIG5hbWVzIHRoYXQgY2Fycnkgb3VyIG1hcmtlciB1bmRlciBST09UCiAgeyBmaW5kICIkUk9PVC8uYWdlbnRzL3NraWxscyIgLW1heGRlcHRoIDIgLW5hbWUgLmltcC1za2lsbC5qc29uIDI+L2Rldi9udWxsCiAgICBmb3IgaWQgaW4gJENMSUVOVFM7IGRvCiAgICAgIGQ9IiQoZGlyX2ZvciAiJGlkIikiIHx8IGNvbnRpbnVlCiAgICAgIGZpbmQgIiRST09ULyRkIiAtbWF4ZGVwdGggMiAtbmFtZSAuaW1wLXNraWxsLmpzb24gMj4vZGV2L251bGwKICAgIGRvbmUKICB9IHwgd2hpbGUgcmVhZCAtciBtOyBkbyBqc29uX3N0ciBuYW1lICIkbSI7IGRvbmUgfCBzb3J0IC11Cn0KCm1hcmtlcl92ZXJzaW9uKCkgeyAjIG1hcmtlcl92ZXJzaW9uIDxuYW1lPgogIGZvciBiYXNlIGluICIkUk9PVC8uYWdlbnRzL3NraWxscyI7IGRvCiAgICBbIC1mICIkYmFzZS8kMS8uaW1wLXNraWxsLmpzb24iIF0gJiYgeyBqc29uX3N0ciB2ZXJzaW9uICIkYmFzZS8kMS8uaW1wLXNraWxsLmpzb24iOyByZXR1cm47IH0KICBkb25lCiAgZm9yIGlkIGluICRDTElFTlRTOyBkbwogICAgZD0iJChkaXJfZm9yICIkaWQiKSIgfHwgY29udGludWUKICAgIGY9IiRST09ULyRkLyQxLy5pbXAtc2tpbGwuanNvbiIKICAgIFsgLWYgIiRmIiBdICYmIHsganNvbl9zdHIgdmVyc2lvbiAiJGYiOyByZXR1cm47IH0KICBkb25lCn0KCmRvX2luc3RhbGwoKSB7ICMgZG9faW5zdGFsbCA8bmFtZT4KICBuYW1lPSIkMSIKICBUTVA9IiQobWt0ZW1wIC1kKSIKICBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5LyRuYW1lL2Rvd25sb2FkIiAtLW91dHB1dCAiJFRNUC9za2lsbC56aXAiIFwKICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJkb3dubG9hZCBmYWlsZWQgZm9yICckbmFtZScgKDQwMyA9IG5vIGFjY2VzcywgNDA0ID0gdW5rbm93bikiOyB9CiAgbWtkaXIgLXAgIiRUTVAveCIKICB1bnppcCAtcSAiJFRNUC9za2lsbC56aXAiIC1kICIkVE1QL3giIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJiYWQgYXJjaGl2ZSBmb3IgJyRuYW1lJyI7IH0KICBzcmM9IiQoZmluZCAiJFRNUC94IiAtbWluZGVwdGggMSAtbWF4ZGVwdGggMSAtdHlwZSBkIHwgaGVhZCAtbjEpIgogIFsgLW4gIiRzcmMiIF0gfHwgeyBybSAtcmYgIiRUTVAiOyBkaWUgInVuZXhwZWN0ZWQgYXJjaGl2ZSBsYXlvdXQgZm9yICckbmFtZSciOyB9CiAgdmVyPSIkKG1ldGFfdmVyc2lvbiAiJG5hbWUiKSIKICBwbGFjZV9za2lsbCAiJG5hbWUiICIkc3JjIiAiJHt2ZXI6LTF9IgogIHJtIC1yZiAiJFRNUCIKICBpbmZvICJpbnN0YWxsZWQgJG5hbWUgKHYke3ZlcjotMX0pIFskTElOS19NT0RFXSBpbnRvOiAkQ0xJRU5UUyIKfQoKdXNhZ2UoKSB7CiAgY2F0ID4mMiA8PEVPRgppbXAg4oCUIHNraWxsIGxpYnJhcnkgaGVscGVyCiAgaW1wIGxpc3QgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0IHNraWxscyB5b3UgY2FuIGFjY2VzcyAoSlNPTikKICBpbXAgZ2V0IDxuYW1lPiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3cgb25lIHNraWxsJ3MgbWV0YWRhdGEgKEpTT04pCiAgaW1wIGluc3RhbGwgPG5hbWU+ICAgICAgICAgICAgICAgICAgICAgICBpbnN0YWxsL3JlZnJlc2ggYSBza2lsbCBpbnRvIHlvdXIgYWdlbnQgY2xpZW50cwogIGltcCB1cGRhdGUgWzxuYW1lPl0gICAgICAgICAgICAgICAgICAgICAgdXBkYXRlIGluc3RhbGxlZCBza2lsbHMgKGFsbCwgb3Igb25lKSB0byBsYXRlc3QKICBpbXAgcHVibGlzaCA8bmFtZT4gPGRpcj4gPHB1YmxpY3xwcml2YXRlPiBwdWJsaXNoIGEgbG9jYWwgc2tpbGwgZm9sZGVyIGFzIDxuYW1lPgogIGltcCBjb25maWcgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvdyByZXNvbHZlZCBiYWNrZW5kIC8gc2NvcGUgLyBjbGllbnRzIChubyBzZWNyZXRzKQpFT0YKfQoKY21kPSIkezE6LWhlbHB9IgpbICQjIC1ndCAwIF0gJiYgc2hpZnQgfHwgdHJ1ZQoKY2FzZSAiJGNtZCIgaW4KICBsaXN0KSBhcGkgR0VUICIvc2tpbGxzL3JlZ2lzdHJ5IiA7OwogIGdldCkgWyAkIyAtZ2UgMSBdIHx8IGRpZSAidXNhZ2U6IGltcCBnZXQgPG5hbWU+IjsgYXBpIEdFVCAiL3NraWxscy9yZWdpc3RyeS8kMSIgOzsKICBpbnN0YWxsKSBbICQjIC1nZSAxIF0gfHwgZGllICJ1c2FnZTogaW1wIGluc3RhbGwgPG5hbWU+IjsgZG9faW5zdGFsbCAiJDEiIDs7CiAgdXBkYXRlKQogICAgaWYgWyAkIyAtZ2UgMSBdOyB0aGVuIG5hbWVzPSIkMSI7IGVsc2UgbmFtZXM9IiQoaW5zdGFsbGVkX25hbWVzKSI7IGZpCiAgICBbIC1uICIkbmFtZXMiIF0gfHwgeyBpbmZvICJubyBpbXAtbWFuYWdlZCBza2lsbHMgZm91bmQgdW5kZXIgJFJPT1QiOyBleGl0IDA7IH0KICAgIGZvciBuIGluICRuYW1lczsgZG8KICAgICAgY3VyPSIkKG1hcmtlcl92ZXJzaW9uICIkbiIpIgogICAgICBsYXRlc3Q9IiQobWV0YV92ZXJzaW9uICIkbiIpIgogICAgICBbIC1uICIkbGF0ZXN0IiBdIHx8IHsgaW5mbyAic2tpcCAkbiAobm90IGluIHJlZ2lzdHJ5KSI7IGNvbnRpbnVlOyB9CiAgICAgIGlmIFsgLXogIiRjdXIiIF0gfHwgWyAiJGxhdGVzdCIgLWd0ICIkY3VyIiBdIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgICAgaW5mbyAidXBkYXRpbmcgJG46IHYke2N1cjotP30gLT4gdiRsYXRlc3QiOyBkb19pbnN0YWxsICIkbiIKICAgICAgZWxzZQogICAgICAgIGluZm8gIiRuIHVwIHRvIGRhdGUgKHYkY3VyKSIKICAgICAgZmkKICAgIGRvbmUKICAgIDs7CiAgcHVibGlzaCkKICAgIFsgJCMgLWdlIDMgXSB8fCBkaWUgInVzYWdlOiBpbXAgcHVibGlzaCA8bmFtZT4gPGRpcj4gPHB1YmxpY3xwcml2YXRlPiIKICAgIG5hbWU9IiQxIjsgZm9sZGVyPSIkMiI7IHZpc2liaWxpdHk9IiQzIgogICAgY2FzZSAiJHZpc2liaWxpdHkiIGluCiAgICAgIHB1YmxpY3xwcml2YXRlKSA7OwogICAgICAqKSBkaWUgInZpc2liaWxpdHkgbXVzdCBiZSAncHVibGljJyBvciAncHJpdmF0ZScgKGFzayB0aGUgdXNlciB3aGljaCB0aGV5IHdhbnQpIiA7OwogICAgZXNhYwogICAgWyAtZCAiJGZvbGRlciIgXSB8fCBkaWUgIm5vIHN1Y2ggZm9sZGVyOiAkZm9sZGVyIgogICAgWyAtZiAiJGZvbGRlci9TS0lMTC5tZCIgXSB8fCBkaWUgIiRmb2xkZXIgaGFzIG5vIFNLSUxMLm1kIGF0IGl0cyByb290IgogICAgY29tbWFuZCAtdiB6aXAgPi9kZXYvbnVsbCAyPiYxIHx8IGRpZSAidGhlICd6aXAnIGNvbW1hbmQgaXMgcmVxdWlyZWQgdG8gcHVibGlzaCIKICAgIFRNUD0iJChta3RlbXAgLWQpIgogICAgKCBjZCAiJChkaXJuYW1lICIkZm9sZGVyIikiIFwKICAgICAgJiYgemlwIC1xIC1yIC1YICIkVE1QL3NraWxsLnppcCIgIiQoYmFzZW5hbWUgIiRmb2xkZXIiKSIgXAogICAgICAgICAgIC14ICcqLy5pbXAtc2tpbGwuanNvbicgJyovLmdpdC8qJyAnKi5EU19TdG9yZScgJyovX19NQUNPU1gvKicgKSBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJjb3VsZCBub3QgemlwICRmb2xkZXIiOyB9CiAgICBhcGkgUE9TVCAiL3NraWxscy9yZWdpc3RyeS8kbmFtZT92aXNpYmlsaXR5PSR2aXNpYmlsaXR5IiAtSCAiQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi96aXAiIFwKICAgICAgLS1kYXRhLWJpbmFyeSBAIiRUTVAvc2tpbGwuemlwIiBcCiAgICAgIHx8IHsgcm0gLXJmICIkVE1QIjsgZGllICJwdWJsaXNoIGZhaWxlZCAoNDAzID0gbm8gd3JpdGUgYWNjZXNzLCA0MDkgPSBuYW1lIHRha2VuKSI7IH0KICAgIHJtIC1yZiAiJFRNUCIKICAgIGluZm8gInB1Ymxpc2hlZCAkbmFtZSAoJHZpc2liaWxpdHkpIgogICAgOzsKICBjb25maWcpCiAgICBwcmludGYgJ2JhY2tlbmQ9JXNcbnNjb3BlPSVzXG5saW5rX21vZGU9JXNcbmNsaWVudHM9JXNcbmFwaV9rZXk9JXNcbicgXAogICAgICAiJEJBQ0tFTkQiICIkU0NPUEUiICIkTElOS19NT0RFIiAiJENMSUVOVFMiIFwKICAgICAgIiQoWyAtbiAiJEFQSV9LRVkiIF0gJiYgZWNobyBzZXQgfHwgZWNobyBNSVNTSU5HKSIKICAgIDs7CiAgaGVscHwtLWhlbHB8LWgpIHVzYWdlIDs7CiAgKikgaW5mbyAidW5rbm93biBjb21tYW5kOiAkY21kIjsgdXNhZ2U7IGV4aXQgMiA7Owplc2FjCg==";
21276
21730
 
21277
- // src/skills/bootstrap/exulu-skills.ts
21731
+ // src/skills/bootstrap/imp-skills.ts
21278
21732
  var BOOTSTRAP_CLIENTS_JSON = JSON.stringify(CLIENT_MANIFEST, null, 2);
21279
21733
  var DIR_FOR_CASES = CLIENT_MANIFEST.map(
21280
21734
  (c) => ` ${c.id}) printf '%s' '${c.dir}' ;;`
21281
21735
  ).join("\n");
21282
- var BOOTSTRAP_EXULU_SH = Buffer.from(EXULU_SH_B64, "base64").toString("utf8").replace("__DIR_FOR_CASES__", DIR_FOR_CASES);
21736
+ var BOOTSTRAP_IMP_SH = Buffer.from(IMP_SH_B64, "base64").toString("utf8").replace("__DIR_FOR_CASES__", DIR_FOR_CASES);
21283
21737
  var BOOTSTRAP_SKILL_MD = `---
21284
- name: exulu-skills
21285
- 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.
21738
+ name: imp-skills
21739
+ 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.
21286
21740
  ---
21287
21741
 
21288
- # Exulu Skills
21742
+ # IMP Skills
21289
21743
 
21290
- Bridge to the Exulu central skill library. **All operations go through the
21744
+ Bridge to the IMP central skill library. **All operations go through the
21291
21745
  bundled helper script \u2014 do not hand-write curl or copy files yourself.** The
21292
21746
  script reads the API token from config (keeping it out of this conversation) and
21293
21747
  handles the multi-client copy/symlink fan-out deterministically.
21294
21748
 
21295
21749
  ## The helper
21296
21750
 
21297
- Run the script next to this file, \`scripts/exulu\`, with \`sh\` and the absolute
21751
+ Run the script next to this file, \`scripts/imp\`, with \`sh\` and the absolute
21298
21752
  path of this skill's directory:
21299
21753
 
21300
21754
  \`\`\`
21301
- sh "<this-skill-dir>/scripts/exulu" <command>
21755
+ sh "<this-skill-dir>/scripts/imp" <command>
21302
21756
  \`\`\`
21303
21757
 
21304
21758
  Commands:
@@ -21306,25 +21760,27 @@ Commands:
21306
21760
  - \`get <name>\` \u2014 one skill's metadata (JSON)
21307
21761
  - \`install <name>\` \u2014 install/refresh a skill into the user's agent clients
21308
21762
  - \`update [<name>]\` \u2014 update every installed skill, or just \`<name>\`, to latest
21309
- - \`publish <name> <folder>\` \u2014 publish a local skill folder as \`<name>\`
21763
+ - \`publish <name> <folder> <public|private>\` \u2014 publish a local skill folder as \`<name>\`
21310
21764
  - \`config\` \u2014 show resolved backend / scope / clients (prints no secrets)
21311
21765
 
21312
21766
  The token, backend URL, target clients, copy-vs-symlink mode, and scope all come
21313
- from \`~/.config/exulu/skills.json\` (written by the installer). Never print the
21767
+ from \`~/.config/imp/skills.json\` (written by the installer). Never print the
21314
21768
  \`api_key\` or read it into your reply \u2014 the script uses it internally.
21315
21769
 
21316
21770
  ## Requests \u2192 commands
21317
21771
 
21318
- - "list / search skills" \u2192 \`exulu list\`, then filter the JSON for the user.
21319
- - "install skill X" / "add the X skill" \u2192 \`exulu install X\`.
21320
- - "update / get the latest version [of X]" \u2192 \`exulu update [X]\`.
21772
+ - "list / search skills" \u2192 \`imp list\`, then filter the JSON for the user.
21773
+ - "install skill X" / "add the X skill" \u2192 \`imp install X\`.
21774
+ - "update / get the latest version [of X]" \u2192 \`imp update [X]\`.
21321
21775
  - "publish / upload this skill as X" \u2192 confirm the target name with the user; for
21322
- an existing skill run \`exulu get X\` first and confirm a new version is intended;
21323
- then \`exulu publish X <folder>\`.
21776
+ an existing skill run \`imp get X\` first and confirm a new version is intended.
21777
+ For a NEW skill you MUST ask the user whether it should be \`public\` (visible
21778
+ to everyone on the instance) or \`private\` (only them) \u2014 never pick a
21779
+ visibility yourself; then \`imp publish X <folder> <public|private>\`.
21324
21780
 
21325
21781
  ## Not configured yet?
21326
21782
 
21327
- If \`exulu config\` reports it's not configured (or \`~/.config/exulu/skills.json\`
21783
+ If \`imp config\` reports it's not configured (or \`~/.config/imp/skills.json\`
21328
21784
  is missing), tell the user to run the installer \u2014 it sets everything up
21329
21785
  interactively (base URL, API key, target clients, copy/symlink):
21330
21786
 
@@ -21332,14 +21788,15 @@ interactively (base URL, API key, target clients, copy/symlink):
21332
21788
  curl -fsSL <base_url>/api/skills/install.sh | sh
21333
21789
  \`\`\`
21334
21790
 
21335
- \`<base_url>\` is their Exulu frontend URL (e.g. https://ai.open.de). They can
21336
- create an API key at \`<base_url>/token\`.
21791
+ \`<base_url>\` is their instance's frontend URL (e.g. https://ai.open.de). They
21792
+ can create an API key at \`<base_url>/token\`.
21337
21793
 
21338
21794
  ## Errors
21339
21795
 
21340
21796
  - install: \`403\` = no access to that skill; \`404\` = unknown name.
21341
- - publish: \`403\` = you can see it but lack write access; \`409\` = the name is
21342
- taken by a skill you can't access.
21797
+ - publish: \`400\` = missing/invalid visibility (new skills need public|private);
21798
+ \`403\` = you can see it but lack write access; \`409\` = the name is taken by a
21799
+ skill you can't access.
21343
21800
  `;
21344
21801
 
21345
21802
  // src/exulu/routes.ts
@@ -22370,7 +22827,7 @@ ${customInstructions}` : agent.instructions;
22370
22827
  const imageModelsByName = (() => {
22371
22828
  if (!isLiteLLMEnabled() || !config?.fileUploads) return /* @__PURE__ */ new Map();
22372
22829
  try {
22373
- const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path6.resolve)(process.cwd(), "./config.litellm.yaml");
22830
+ const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path9.resolve)(process.cwd(), "./config.litellm.yaml");
22374
22831
  const models2 = parseImageGenerationModels(configPath);
22375
22832
  return new Map(models2.map((m) => [m.model_name, m]));
22376
22833
  } catch (err) {
@@ -23648,9 +24105,9 @@ ${style.markdown}` : params.prompt;
23648
24105
  app.get("/skills/agent/bootstrap", async (_req, res) => {
23649
24106
  try {
23650
24107
  const zip = new import_jszip3.default();
23651
- zip.file("exulu-skills/SKILL.md", BOOTSTRAP_SKILL_MD);
23652
- zip.file("exulu-skills/references/clients.json", BOOTSTRAP_CLIENTS_JSON);
23653
- zip.file("exulu-skills/scripts/exulu", BOOTSTRAP_EXULU_SH, {
24108
+ zip.file("imp-skills/SKILL.md", BOOTSTRAP_SKILL_MD);
24109
+ zip.file("imp-skills/references/clients.json", BOOTSTRAP_CLIENTS_JSON);
24110
+ zip.file("imp-skills/scripts/imp", BOOTSTRAP_IMP_SH, {
23654
24111
  unixPermissions: 493
23655
24112
  });
23656
24113
  const buffer = await zip.generateAsync({
@@ -23658,7 +24115,7 @@ ${style.markdown}` : params.prompt;
23658
24115
  platform: "UNIX"
23659
24116
  });
23660
24117
  res.setHeader("Content-Type", "application/zip");
23661
- res.setHeader("Content-Disposition", 'attachment; filename="exulu-skills.zip"');
24118
+ res.setHeader("Content-Disposition", 'attachment; filename="imp-skills.zip"');
23662
24119
  res.send(buffer);
23663
24120
  } catch (err) {
23664
24121
  console.error("[SKILLS] Failed to build bootstrap zip", err);
@@ -23779,6 +24236,13 @@ ${style.markdown}` : params.prompt;
23779
24236
  return;
23780
24237
  }
23781
24238
  }
24239
+ const visibility = req.query.visibility;
24240
+ if (visibility !== "public" && visibility !== "private") {
24241
+ res.status(400).json({
24242
+ detail: "Missing or invalid 'visibility'. New skills require ?visibility=public or ?visibility=private \u2014 ask the user which they want."
24243
+ });
24244
+ return;
24245
+ }
23782
24246
  const meta = await parseSkillFrontmatter(bytes);
23783
24247
  const skillId = (0, import_node_crypto9.randomUUID)();
23784
24248
  try {
@@ -23805,7 +24269,7 @@ ${style.markdown}` : params.prompt;
23805
24269
  history: JSON.stringify([
23806
24270
  { version: 1, created_at: (/* @__PURE__ */ new Date()).toISOString(), label: "Published from agent" }
23807
24271
  ]),
23808
- rights_mode: "private",
24272
+ rights_mode: visibility,
23809
24273
  created_by: authResult.user.id
23810
24274
  });
23811
24275
  } catch (err) {
@@ -24912,7 +25376,7 @@ init_check_record_access();
24912
25376
  init_resolve_model();
24913
25377
  init_supervisor();
24914
25378
  init_client();
24915
- var import_zod14 = require("zod");
25379
+ var import_zod16 = require("zod");
24916
25380
  init_convert_exulu_tools_to_ai_sdk_tools();
24917
25381
  init_singleton();
24918
25382
  var SESSION_ID_HEADER = "mcp-session-id";
@@ -24986,7 +25450,7 @@ var ExuluMCP = class {
24986
25450
  title: tool4.name + " agent",
24987
25451
  description: tool4.description,
24988
25452
  inputSchema: {
24989
- inputs: tool4.inputSchema || import_zod14.z.object({})
25453
+ inputs: tool4.inputSchema || import_zod16.z.object({})
24990
25454
  }
24991
25455
  },
24992
25456
  async ({ inputs }, args) => {
@@ -25038,7 +25502,7 @@ var ExuluMCP = class {
25038
25502
  title: "Get List of Prompt Templates",
25039
25503
  description: "Retrieves a list of prompt templates available for this agent. Returns the name, description, and ID of each template.",
25040
25504
  inputSchema: {
25041
- inputs: import_zod14.z.object({})
25505
+ inputs: import_zod16.z.object({})
25042
25506
  }
25043
25507
  },
25044
25508
  async ({ inputs }, args) => {
@@ -25084,8 +25548,8 @@ var ExuluMCP = class {
25084
25548
  title: "Get Prompt Template Details",
25085
25549
  description: "Retrieves the full details of a specific prompt template by ID, including the actual template content with variables.",
25086
25550
  inputSchema: {
25087
- inputs: import_zod14.z.object({
25088
- id: import_zod14.z.string().describe("The ID of the prompt template to retrieve")
25551
+ inputs: import_zod16.z.object({
25552
+ id: import_zod16.z.string().describe("The ID of the prompt template to retrieve")
25089
25553
  })
25090
25554
  }
25091
25555
  },
@@ -26200,7 +26664,7 @@ var ExuluEval = class {
26200
26664
  // src/templates/evals/index.ts
26201
26665
  init_resolve_model();
26202
26666
  init_singleton();
26203
- var import_zod15 = require("zod");
26667
+ var import_zod17 = require("zod");
26204
26668
  var import_ai17 = require("ai");
26205
26669
  var llmAsJudgeEval = () => {
26206
26670
  if (process.env.REDIS_HOST?.length && process.env.REDIS_PORT?.length) {
@@ -26253,8 +26717,8 @@ var llmAsJudgeEval = () => {
26253
26717
  prompt,
26254
26718
  maxRetries: 2,
26255
26719
  output: import_ai17.Output.object({
26256
- schema: import_zod15.z.object({
26257
- score: import_zod15.z.number().min(0).max(100).describe("The score between 0 and 100.")
26720
+ schema: import_zod17.z.object({
26721
+ score: import_zod17.z.number().min(0).max(100).describe("The score between 0 and 100.")
26258
26722
  })
26259
26723
  })
26260
26724
  });
@@ -26485,15 +26949,15 @@ Usage:
26485
26949
  - If no todos exist yet, an empty list will be returned`;
26486
26950
 
26487
26951
  // src/templates/tools/todo/todo.ts
26488
- var import_zod16 = __toESM(require("zod"), 1);
26952
+ var import_zod18 = __toESM(require("zod"), 1);
26489
26953
  init_tool();
26490
26954
  init_check_record_access();
26491
26955
  init_client();
26492
- var TodoSchema = import_zod16.default.object({
26493
- content: import_zod16.default.string().describe("Brief description of the task"),
26494
- status: import_zod16.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
26495
- priority: import_zod16.default.string().describe("Priority level of the task: high, medium, low"),
26496
- id: import_zod16.default.string().describe("Unique identifier for the todo item")
26956
+ var TodoSchema = import_zod18.default.object({
26957
+ content: import_zod18.default.string().describe("Brief description of the task"),
26958
+ status: import_zod18.default.string().describe("Current status of the task: pending, in_progress, completed, cancelled"),
26959
+ priority: import_zod18.default.string().describe("Priority level of the task: high, medium, low"),
26960
+ id: import_zod18.default.string().describe("Unique identifier for the todo item")
26497
26961
  });
26498
26962
  var TodoWriteTool = new ExuluTool({
26499
26963
  id: "todo_write",
@@ -26509,8 +26973,8 @@ var TodoWriteTool = new ExuluTool({
26509
26973
  default: todowrite_default
26510
26974
  }
26511
26975
  ],
26512
- inputSchema: import_zod16.default.object({
26513
- todos: import_zod16.default.array(TodoSchema).describe("The updated todo list")
26976
+ inputSchema: import_zod18.default.object({
26977
+ todos: import_zod18.default.array(TodoSchema).describe("The updated todo list")
26514
26978
  }),
26515
26979
  execute: async (inputs) => {
26516
26980
  const { sessionID, todos, user } = inputs;
@@ -26545,7 +27009,7 @@ var TodoReadTool = new ExuluTool({
26545
27009
  id: "todo_read",
26546
27010
  name: "Todo Read",
26547
27011
  description: "Use this tool to read your todo list",
26548
- inputSchema: import_zod16.default.object({}),
27012
+ inputSchema: import_zod18.default.object({}),
26549
27013
  type: "function",
26550
27014
  category: "todo",
26551
27015
  config: [
@@ -26590,7 +27054,7 @@ init_cjs_shims();
26590
27054
  var questionread_default = 'Use this tool to read questions you\'ve asked and check if they\'ve been answered by the user. This tool helps you track the status of questions and retrieve the user\'s selected answers.\n\n## When to Use This Tool\n\nUse this tool proactively in these situations:\n- After asking a question to check if the user has responded\n- To retrieve the user\'s answer before proceeding with implementation\n- To review all questions and answers in the current session\n- When you need to reference a previous answer\n\n## How It Works\n\n- This tool takes no parameters (leave the input blank or empty)\n- Returns an array of all questions in the session\n- Each question includes:\n - `id`: Unique identifier for the question\n - `question`: The question text\n - `answerOptions`: Array of answer options with their IDs and text\n - `status`: Either "pending" (not answered) or "answered"\n - `selectedAnswerId`: The ID of the chosen answer (only present if answered)\n\n## Usage Pattern\n\nTypically you\'ll:\n1. Use Question Ask to pose a question\n2. Wait for the user to respond\n3. Use Question Read to check the answer\n4. Find the selected answer by matching the `selectedAnswerId` with an option in `answerOptions`\n5. Proceed with implementation based on the user\'s choice\n\n## Example Response\n\n```json\n[\n {\n "id": "question123",\n "question": "Which authentication method would you like to implement?",\n "answerOptions": [\n { "id": "ans1", "text": "JWT tokens" },\n { "id": "ans2", "text": "OAuth 2.0" },\n { "id": "ans3", "text": "Session-based auth" },\n { "id": "ans4", "text": "None of the above..." }\n ],\n "status": "answered",\n "selectedAnswerId": "ans1"\n }\n]\n```\n\nIn this example, the user selected "JWT tokens" (id: ans1).\n\n## Important Notes\n\n- If no questions exist in the session, an empty array will be returned\n- Questions remain in the session even after being answered for reference\n- Use the `selectedAnswerId` to find which answer option the user chose by matching it against the `id` field in `answerOptions`\n';
26591
27055
 
26592
27056
  // src/templates/tools/question/question.ts
26593
- var import_zod18 = __toESM(require("zod"), 1);
27057
+ var import_zod20 = __toESM(require("zod"), 1);
26594
27058
  init_tool();
26595
27059
  init_client();
26596
27060
 
@@ -26682,21 +27146,21 @@ After asking a question, use the Question Read tool to check if the user has ans
26682
27146
  `;
26683
27147
 
26684
27148
  // src/templates/tools/question/question-ask.ts
26685
- var import_zod17 = __toESM(require("zod"), 1);
27149
+ var import_zod19 = __toESM(require("zod"), 1);
26686
27150
  init_tool();
26687
27151
  init_check_record_access();
26688
27152
  init_client();
26689
27153
  var import_node_crypto11 = require("crypto");
26690
- var AnswerOptionSchema = import_zod17.default.object({
26691
- id: import_zod17.default.string().describe("Unique identifier for the answer option"),
26692
- text: import_zod17.default.string().describe("The text of the answer option")
27154
+ var AnswerOptionSchema = import_zod19.default.object({
27155
+ id: import_zod19.default.string().describe("Unique identifier for the answer option"),
27156
+ text: import_zod19.default.string().describe("The text of the answer option")
26693
27157
  });
26694
- var _QuestionSchema = import_zod17.default.object({
26695
- id: import_zod17.default.string().describe("Unique identifier for the question"),
26696
- question: import_zod17.default.string().describe("The question to ask the user"),
26697
- answerOptions: import_zod17.default.array(AnswerOptionSchema).describe("Array of possible answer options"),
26698
- selectedAnswerId: import_zod17.default.string().optional().describe("The ID of the answer option selected by the user"),
26699
- status: import_zod17.default.enum(["pending", "answered"]).describe("Status of the question: pending or answered")
27158
+ var _QuestionSchema = import_zod19.default.object({
27159
+ id: import_zod19.default.string().describe("Unique identifier for the question"),
27160
+ question: import_zod19.default.string().describe("The question to ask the user"),
27161
+ answerOptions: import_zod19.default.array(AnswerOptionSchema).describe("Array of possible answer options"),
27162
+ selectedAnswerId: import_zod19.default.string().optional().describe("The ID of the answer option selected by the user"),
27163
+ status: import_zod19.default.enum(["pending", "answered"]).describe("Status of the question: pending or answered")
26700
27164
  });
26701
27165
  var QuestionAskTool = new ExuluTool({
26702
27166
  id: "question_ask",
@@ -26713,9 +27177,9 @@ var QuestionAskTool = new ExuluTool({
26713
27177
  default: questionask_default
26714
27178
  }
26715
27179
  ],
26716
- inputSchema: import_zod17.default.object({
26717
- question: import_zod17.default.string().describe("The question to ask the user"),
26718
- answerOptions: import_zod17.default.array(import_zod17.default.string()).describe("Array of possible answer options (strings)")
27180
+ inputSchema: import_zod19.default.object({
27181
+ question: import_zod19.default.string().describe("The question to ask the user"),
27182
+ answerOptions: import_zod19.default.array(import_zod19.default.string()).describe("Array of possible answer options (strings)")
26719
27183
  }),
26720
27184
  execute: async (inputs) => {
26721
27185
  const { sessionID, question, answerOptions, user } = inputs;
@@ -26788,7 +27252,7 @@ var QuestionReadTool = new ExuluTool({
26788
27252
  name: "Question Read",
26789
27253
  needsApproval: false,
26790
27254
  description: "Use this tool to read questions and their answers",
26791
- inputSchema: import_zod18.default.object({}),
27255
+ inputSchema: import_zod20.default.object({}),
26792
27256
  type: "function",
26793
27257
  category: "question",
26794
27258
  config: [
@@ -26820,15 +27284,15 @@ var questionTools = [QuestionAskTool, QuestionReadTool];
26820
27284
  // src/templates/tools/perplexity.ts
26821
27285
  init_cjs_shims();
26822
27286
  init_tool();
26823
- var import_zod19 = __toESM(require("zod"), 1);
27287
+ var import_zod21 = __toESM(require("zod"), 1);
26824
27288
  var import_perplexity_ai = __toESM(require("@perplexity-ai/perplexity_ai"), 1);
26825
27289
  var internetSearchTool = new ExuluTool({
26826
27290
  id: "internet_search",
26827
27291
  name: "Internet Search",
26828
27292
  description: "Search the internet for information.",
26829
- inputSchema: import_zod19.default.object({
26830
- query: import_zod19.default.string().describe("The query to the tool."),
26831
- search_recency_filter: import_zod19.default.enum(["day", "week", "month", "year"]).optional().describe("The recency filter for the search, can be day, week, month or year.")
27293
+ inputSchema: import_zod21.default.object({
27294
+ query: import_zod21.default.string().describe("The query to the tool."),
27295
+ search_recency_filter: import_zod21.default.enum(["day", "week", "month", "year"]).optional().describe("The recency filter for the search, can be day, week, month or year.")
26832
27296
  }),
26833
27297
  category: "internet_search",
26834
27298
  type: "web_search",
@@ -26923,7 +27387,7 @@ var perplexityTools = [internetSearchTool];
26923
27387
  init_cjs_shims();
26924
27388
  init_tool();
26925
27389
  var nodemailer = __toESM(require("nodemailer"), 1);
26926
- var import_zod20 = require("zod");
27390
+ var import_zod22 = require("zod");
26927
27391
  var transporter = null;
26928
27392
  function getTransporter(config) {
26929
27393
  if (!transporter) {
@@ -26947,11 +27411,11 @@ var emailTool = new ExuluTool({
26947
27411
  id: "email",
26948
27412
  name: "Email",
26949
27413
  description: "Send an email.",
26950
- inputSchema: import_zod20.z.object({
26951
- recipient: import_zod20.z.string().describe("The recipient of the email."),
26952
- subject: import_zod20.z.string().describe("The subject of the email."),
26953
- html: import_zod20.z.string().describe("The HTML body of the email."),
26954
- text: import_zod20.z.string().describe("The text body of the email.")
27414
+ inputSchema: import_zod22.z.object({
27415
+ recipient: import_zod22.z.string().describe("The recipient of the email."),
27416
+ subject: import_zod22.z.string().describe("The subject of the email."),
27417
+ html: import_zod22.z.string().describe("The HTML body of the email."),
27418
+ text: import_zod22.z.string().describe("The text body of the email.")
26955
27419
  }),
26956
27420
  type: "function",
26957
27421
  config: [{
@@ -27022,7 +27486,7 @@ init_tool();
27022
27486
  init_supervisor();
27023
27487
  init_client();
27024
27488
  init_check_record_access();
27025
- var import_zod21 = require("zod");
27489
+ var import_zod23 = require("zod");
27026
27490
  var _cachedImageModels;
27027
27491
  var setCachedImageModels = (models2) => {
27028
27492
  _cachedImageModels = models2;
@@ -27083,8 +27547,8 @@ var createImageGenerationWidgetTool = (models2) => {
27083
27547
  needsApproval: false,
27084
27548
  type: "function",
27085
27549
  config: [],
27086
- inputSchema: import_zod21.z.object({
27087
- prompt: import_zod21.z.string().describe(
27550
+ inputSchema: import_zod23.z.object({
27551
+ prompt: import_zod23.z.string().describe(
27088
27552
  "Initial image prompt. The user can edit it before generating."
27089
27553
  )
27090
27554
  }),
@@ -27123,7 +27587,7 @@ var createImageGenerationWidgetTool = (models2) => {
27123
27587
  };
27124
27588
 
27125
27589
  // src/exulu/app/index.ts
27126
- var import_node_path7 = require("path");
27590
+ var import_node_path10 = require("path");
27127
27591
 
27128
27592
  // src/validators/postgres-name.ts
27129
27593
  init_cjs_shims();
@@ -27509,7 +27973,7 @@ var ExuluApp = class {
27509
27973
  const imageGenerationTools = [];
27510
27974
  const s3Configured = !!config?.fileUploads && !!config.fileUploads.s3region && !!config.fileUploads.s3key && !!config.fileUploads.s3secret && !!config.fileUploads.s3Bucket;
27511
27975
  if (isLiteLLMEnabled() && s3Configured) {
27512
- const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path7.resolve)(process.cwd(), "./config.litellm.yaml");
27976
+ const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path10.resolve)(process.cwd(), "./config.litellm.yaml");
27513
27977
  const imageModels = parseImageGenerationModels(configPath);
27514
27978
  if (imageModels.length > 0) {
27515
27979
  console.log(
@@ -28969,8 +29433,8 @@ init_cjs_shims();
28969
29433
  // src/exulu/litellm/db-init.ts
28970
29434
  init_cjs_shims();
28971
29435
  var import_node_fs9 = require("fs");
28972
- var import_node_path8 = require("path");
28973
- var import_node_child_process5 = require("child_process");
29436
+ var import_node_path11 = require("path");
29437
+ var import_node_child_process6 = require("child_process");
28974
29438
  var import_pg = require("pg");
28975
29439
 
28976
29440
  // src/exulu/litellm/db-setup-check.ts
@@ -29039,7 +29503,7 @@ ${WARNING_BANNER}`);
29039
29503
  };
29040
29504
  var log5 = (line) => console.log(`[EXULU-LITELLM] ${line}`);
29041
29505
  var initLiteLLMDatabase = async (packageRoot) => {
29042
- const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path8.resolve)(process.cwd(), "./config.litellm.yaml");
29506
+ const configPath = process.env.LITELLM_CONFIG_PATH ?? (0, import_node_path11.resolve)(process.cwd(), "./config.litellm.yaml");
29043
29507
  const safety = checkLiteLLMDatabaseSafety(configPath);
29044
29508
  if (safety.ok && safety.reason === "no-litellm-db-mode") return;
29045
29509
  if (!safety.ok && safety.reason === "unparseable-url") {
@@ -29177,9 +29641,9 @@ var initLiteLLMDatabase = async (packageRoot) => {
29177
29641
  ]);
29178
29642
  return;
29179
29643
  }
29180
- const venvBin = (0, import_node_path8.resolve)(packageRoot, "ee/python/.venv/bin");
29181
- const prismaCli = (0, import_node_path8.resolve)(venvBin, "prisma");
29182
- const venvLibDir = (0, import_node_path8.resolve)(packageRoot, "ee/python/.venv/lib");
29644
+ const venvBin = (0, import_node_path11.resolve)(packageRoot, "ee/python/.venv/bin");
29645
+ const prismaCli = (0, import_node_path11.resolve)(venvBin, "prisma");
29646
+ const venvLibDir = (0, import_node_path11.resolve)(packageRoot, "ee/python/.venv/lib");
29183
29647
  const pythonVersionDir = (0, import_node_fs9.existsSync)(venvLibDir) ? (0, import_node_fs9.readdirSync)(venvLibDir).find((entry) => /^python3\.\d+$/.test(entry)) : void 0;
29184
29648
  if (!pythonVersionDir) {
29185
29649
  warn2([
@@ -29189,12 +29653,12 @@ var initLiteLLMDatabase = async (packageRoot) => {
29189
29653
  ]);
29190
29654
  return;
29191
29655
  }
29192
- const litellmProxyDir = (0, import_node_path8.resolve)(
29656
+ const litellmProxyDir = (0, import_node_path11.resolve)(
29193
29657
  venvLibDir,
29194
29658
  pythonVersionDir,
29195
29659
  "site-packages/litellm/proxy"
29196
29660
  );
29197
- const schemaPath = (0, import_node_path8.resolve)(litellmProxyDir, "schema.prisma");
29661
+ const schemaPath = (0, import_node_path11.resolve)(litellmProxyDir, "schema.prisma");
29198
29662
  if (!(0, import_node_fs9.existsSync)(prismaCli)) {
29199
29663
  warn2([
29200
29664
  `Prisma CLI not found at ${prismaCli}.`,
@@ -29211,7 +29675,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
29211
29675
  return;
29212
29676
  }
29213
29677
  log5("Running `prisma db push` against LiteLLM's schema\u2026");
29214
- const result = (0, import_node_child_process5.spawnSync)(prismaCli, ["db", "push", "--skip-generate"], {
29678
+ const result = (0, import_node_child_process6.spawnSync)(prismaCli, ["db", "push", "--skip-generate"], {
29215
29679
  cwd: litellmProxyDir,
29216
29680
  env: {
29217
29681
  ...process.env,
@@ -29783,14 +30247,14 @@ init_cjs_shims();
29783
30247
  var fs4 = __toESM(require("fs"), 1);
29784
30248
  var path = __toESM(require("path"), 1);
29785
30249
  var import_ai18 = require("ai");
29786
- var import_zod22 = require("zod");
30250
+ var import_zod24 = require("zod");
29787
30251
  var import_p_limit = __toESM(require("p-limit"), 1);
29788
30252
  var import_crypto2 = require("crypto");
29789
30253
  init_with_retry();
29790
30254
  var mammoth = __toESM(require("mammoth"), 1);
29791
30255
  var import_turndown = __toESM(require("turndown"), 1);
29792
30256
  var import_word_extractor = __toESM(require("word-extractor"), 1);
29793
- var import_officeparser2 = require("officeparser");
30257
+ var import_officeparser3 = require("officeparser");
29794
30258
  init_entitlements();
29795
30259
 
29796
30260
  // src/utils/python-executor.ts
@@ -30250,15 +30714,15 @@ If the page contains a flow-chart, schematic, technical drawing or control board
30250
30714
  const result = await (0, import_ai18.generateText)({
30251
30715
  model,
30252
30716
  output: import_ai18.Output.object({
30253
- schema: import_zod22.z.object({
30254
- needs_correction: import_zod22.z.boolean(),
30255
- corrected_text: import_zod22.z.string().nullable(),
30256
- current_page_table: import_zod22.z.object({
30257
- headers: import_zod22.z.array(import_zod22.z.string()),
30258
- is_continuation: import_zod22.z.boolean()
30717
+ schema: import_zod24.z.object({
30718
+ needs_correction: import_zod24.z.boolean(),
30719
+ corrected_text: import_zod24.z.string().nullable(),
30720
+ current_page_table: import_zod24.z.object({
30721
+ headers: import_zod24.z.array(import_zod24.z.string()),
30722
+ is_continuation: import_zod24.z.boolean()
30259
30723
  }).nullable(),
30260
- confidence: import_zod22.z.enum(["high", "medium", "low"]),
30261
- reasoning: import_zod22.z.string()
30724
+ confidence: import_zod24.z.enum(["high", "medium", "low"]),
30725
+ reasoning: import_zod24.z.string()
30262
30726
  })
30263
30727
  }),
30264
30728
  messages: [
@@ -30507,7 +30971,7 @@ ${setupResult.output || ""}`);
30507
30971
  const jsonContent = await fs4.promises.readFile(paths.json, "utf-8");
30508
30972
  json = JSON.parse(jsonContent);
30509
30973
  } else if (config?.processor.name === "officeparser") {
30510
- const text = await (0, import_officeparser2.parseOfficeAsync)(buffer, {
30974
+ const text = await (0, import_officeparser3.parseOfficeAsync)(buffer, {
30511
30975
  outputErrorToConsole: false,
30512
30976
  newlineDelimiter: "\n"
30513
30977
  });