ente-cli 0.4.104 → 0.4.105

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/README.md CHANGED
@@ -192,7 +192,7 @@ Every command supports `--json`. Run `ente --help` or `ente <group> --help`.
192
192
  | `recording` | `list`, `show`, `inspect`, `rerun`, `orphans`, `backups` (platform key) |
193
193
  | `admin` | `tables`, `get`, `page` — read-only inspector over any table (platform key) |
194
194
  | `canvas` | `boards`, `html list`, `html get`, `html create`, `html update`, `html delete` |
195
- | `files` | `list`, `upload` (local file at most 10 MiB, or an activity attachment), `link`, `create-folder` — the entity's Nextcloud folder via your own connection; personal agent key only (MCP: `entity_files_list`, `entity_file_upload`, `entity_folder_link`, `entity_folder_create`) |
195
+ | `files` | `list`, `search` (by name, all subfolders), `read` (text of a PDF, DOCX or plain-text file up to 10 MiB, in windows of 50,000 characters), `upload` (local file at most 10 MiB, or an activity attachment), `link`, `create-folder` — the entity's Nextcloud folder via your own connection; personal agent key only (MCP: `entity_files_list`, `entity_files_search`, `entity_file_read`, `entity_file_upload`, `entity_folder_link`, `entity_folder_create`) |
196
196
  | `notification` | `send` (emails only your own Ente account address; optional HTML, file attachments, and CID inline images) |
197
197
  | `agent` | `permissions`, `list`, `threads`, `show`, `new`, `send`, `config show`, `config set-prompt`, `schedules configure` |
198
198
  | `agent mail` | `address`, `threads`, `show`, `send`, `reply`, `mark` |
package/dist/SKILL.md CHANGED
@@ -254,8 +254,10 @@ ente canvas html delete <boardId> <elementId> [--json]
254
254
 
255
255
  # Entity files: the entity's linked Nextcloud folder, through YOUR own Nextcloud
256
256
  # connection. Works only with your personal agent's key; shared agents are refused.
257
- # MCP/REST: entity_files_list, entity_file_upload, entity_folder_link, entity_folder_create.
257
+ # MCP/REST: entity_files_list, entity_files_search, entity_file_read, entity_file_upload, entity_folder_link, entity_folder_create.
258
258
  ente files list <entityId> [--path "03 Unterlagen"] [--json] # --json: linkedFolderPath = the linked folder's path from your Nextcloud root; join it with an item path for the exact file
259
+ ente files search <entityId> "Mietvertrag" [--json] # name search in all subfolders of the linked folder
260
+ ente files read <entityId> "03 Unterlagen/Mietvertrag.pdf" [--offset N] [--max-characters N] [--json] # text of a PDF (text layer), DOCX or plain-text file up to 100 MiB, 50,000 characters per call; a scan without text layer returns notice no_text_layer
259
261
  ente files upload <entityId> <local-file> [--folder "03 Unterlagen"] [--name "Vertrag.pdf"] [--mime-type application/pdf] [--json] # at most 10 MiB
260
262
  ente files upload <entityId> --activity <activityId> --storage-id <storageId> --name "Vertrag.pdf" [--folder …] [--json] # copy an activity attachment
261
263
  ente files link <entityId> "Mandate/2026-017 Zebrafink" [--space <spaceId>] [--json]
package/dist/cli.mjs CHANGED
@@ -8055,7 +8055,7 @@ async function runCli(cli2, argv, context) {
8055
8055
  }
8056
8056
 
8057
8057
  // ente/version.ts
8058
- var VERSION = "0.4.104";
8058
+ var VERSION = "0.4.105";
8059
8059
 
8060
8060
  // ente/branding.ts
8061
8061
  var NPM_REGISTRY = "https://registry.npmjs.org";
@@ -47336,11 +47336,51 @@ var entityFileListResultValidator = v.union(
47336
47336
  }),
47337
47337
  v.object({ status: v.union(...ENTITY_FILE_LIST_PROBLEM_STATUSES.map((status2) => v.literal(status2))) })
47338
47338
  );
47339
+ var problemStatusValidator = v.object({ status: v.union(...ENTITY_FILE_LIST_PROBLEM_STATUSES.map((status2) => v.literal(status2))) });
47340
+ var entityFileSearchResultValidator = v.union(
47341
+ v.object({
47342
+ status: v.literal("ok"),
47343
+ /** The linked folder itself, relative to the caller's own Nextcloud root; read live, never stored. */
47344
+ linkedFolderPath: v.string(),
47345
+ items: v.array(entityFileItemValidator),
47346
+ /** More hits exist than returned; narrow the query. */
47347
+ truncated: v.boolean()
47348
+ }),
47349
+ problemStatusValidator
47350
+ );
47351
+ var entityFileReadResultValidator = v.union(
47352
+ v.object({
47353
+ status: v.literal("ok"),
47354
+ /** The file, relative to the linked folder. */
47355
+ path: v.string(),
47356
+ name: v.string(),
47357
+ mimeType: v.union(v.string(), v.null()),
47358
+ size: v.union(v.number(), v.null()),
47359
+ modifiedAt: v.union(v.number(), v.null()),
47360
+ format: v.union(v.literal("text"), v.literal("pdf"), v.literal("docx")),
47361
+ /** The returned window of the extracted text. */
47362
+ text: v.string(),
47363
+ /** Character offset of `text` in the whole extracted text. */
47364
+ offset: v.number(),
47365
+ totalCharacters: v.number(),
47366
+ /** Text continues after this window; call again with offset + text.length. */
47367
+ truncated: v.boolean(),
47368
+ /**
47369
+ * Set when a PDF has (almost) no text layer, typically a scan: the text is
47370
+ * not the document's content. OCR in Nextcloud (e.g. workflow_ocr) may add
47371
+ * a layer as a new file version shortly after upload.
47372
+ */
47373
+ notice: v.optional(v.object({ code: v.literal("no_text_layer"), message: v.string() }))
47374
+ }),
47375
+ problemStatusValidator
47376
+ );
47339
47377
  var entityFileUploadResultValidator = v.object({ fileId: v.string(), name: v.string(), path: v.string() });
47340
47378
  var entityFileResults = {
47341
47379
  createEntityFolder: entityFolderLinkResultValidator,
47342
47380
  linkEntityFolder: entityFolderLinkResultValidator,
47343
47381
  listEntityFiles: entityFileListResultValidator,
47382
+ searchEntityFiles: entityFileSearchResultValidator,
47383
+ readEntityFile: entityFileReadResultValidator,
47344
47384
  uploadEntityFile: entityFileUploadResultValidator
47345
47385
  };
47346
47386
  function parseEntityFileResult(operation, raw) {
@@ -47360,6 +47400,19 @@ var ENTITY_FILE_LIMITS = {
47360
47400
  uploadBytes: 20 * 1024 * 1024,
47361
47401
  /** A file the panel sends in parts (Nextcloud chunked upload v2); larger ones go into Nextcloud directly. */
47362
47402
  chunkedUploadBytes: 500 * 1024 * 1024,
47403
+ /**
47404
+ * Largest file readEntityFile extracts text from, set by the Node action's
47405
+ * memory (512 MiB on Convex cloud). Measured 02.11.2026 in a local Convex
47406
+ * backend (Node action, PDF.js 6.4, synthetic scan-like PDF with a 250 KB
47407
+ * image and a text layer per page): 100 MB / 400 pages raised RSS from 306 to
47408
+ * 499 MiB (+193 MiB, 5.6 s); 200 MB / 800 pages added 114 MiB more on a warm
47409
+ * process (7.1 s). About 1.9 bytes per file byte on a ~306 MiB base gives
47410
+ * (512 - 306) / 1.9 = ~106 MiB, hence 100 MiB. Before, bytes went to Node as
47411
+ * an argument, which Convex caps at 5 MiB for Node actions.
47412
+ */
47413
+ readBytes: 100 * 1024 * 1024,
47414
+ /** Characters readEntityFile returns per call; longer texts are read in windows. */
47415
+ readCharacters: 5e4,
47363
47416
  nameCharacters: 250,
47364
47417
  pathCharacters: 4096,
47365
47418
  mimeTypeCharacters: 255
@@ -47393,6 +47446,16 @@ var entityFileInputs = {
47393
47446
  entityId: id17,
47394
47447
  path: path6("Folder inside the linked folder; omit or empty string for the linked folder itself.").optional()
47395
47448
  }),
47449
+ searchEntityFiles: external_exports.strictObject({
47450
+ entityId: id17,
47451
+ query: external_exports.string().trim().min(2).max(200).describe("Part of a file or folder name, 2 to 200 characters; matched anywhere below the linked folder.")
47452
+ }),
47453
+ readEntityFile: external_exports.strictObject({
47454
+ entityId: id17,
47455
+ path: external_exports.string().min(1).max(ENTITY_FILE_LIMITS.pathCharacters).describe("File inside the linked folder, as entity_files_list or entity_files_search return it, for example 03 Unterlagen/Mietvertrag.pdf."),
47456
+ offset: external_exports.number().int().min(0).optional().describe("Character offset to continue a truncated text; defaults to 0."),
47457
+ maxCharacters: external_exports.number().int().min(1).max(ENTITY_FILE_LIMITS.readCharacters).optional().describe(`Characters to return, at most ${ENTITY_FILE_LIMITS.readCharacters}; defaults to that maximum.`)
47458
+ }),
47396
47459
  uploadEntityFile: external_exports.strictObject({
47397
47460
  entityId: id17,
47398
47461
  folderPath: path6("Target folder inside the linked folder; defaults to the linked folder itself.").optional(),
@@ -47409,6 +47472,8 @@ var entityFileAliases = {
47409
47472
  entity_folder_create: "createEntityFolder",
47410
47473
  entity_folder_link: "linkEntityFolder",
47411
47474
  entity_files_list: "listEntityFiles",
47475
+ entity_files_search: "searchEntityFiles",
47476
+ entity_file_read: "readEntityFile",
47412
47477
  entity_file_upload: "uploadEntityFile"
47413
47478
  };
47414
47479
  function isEntityFileOperationName(value) {
@@ -47419,8 +47484,14 @@ var descriptions5 = {
47419
47484
  createEntityFolder: `Create a new folder in your Nextcloud below parentPath and link it as the entity's file folder. ${personalOnly}`,
47420
47485
  linkEntityFolder: `Link an existing folder of your Nextcloud, given by its path, as the entity's file folder. A folder can belong to one entity only. ${personalOnly}`,
47421
47486
  listEntityFiles: `List the files and folders in the entity's linked Nextcloud folder. status explains a missing link or connection instead of an empty list. Item paths are relative to the linked folder; linkedFolderPath is the linked folder's own path from your Nextcloud root, so join it with an item path to address that exact file in Nextcloud instead of searching by name. ${personalOnly}`,
47487
+ searchEntityFiles: `Find files and folders by name anywhere below the entity's linked Nextcloud folder, also in subfolders. status explains a missing link or connection; truncated means more hits exist, so narrow the query. Item paths are relative to the linked folder and go unchanged into entity_file_read. ${personalOnly}`,
47488
+ readEntityFile: `Read the text of one file in the entity's linked Nextcloud folder: PDF (text layer only), DOCX and plain-text formats such as TXT, MD, CSV, JSON or XML, up to ${ENTITY_FILE_LIMITS.readBytes / (1024 * 1024)} MiB. Other formats fail with unsupported_format. Long texts come in windows: when truncated is true, call again with offset set to offset plus the returned text's length. A PDF without a usable text layer (a scan) returns notice.code no_text_layer: its text is not the content; OCR in Nextcloud may add a layer shortly after upload, so read it again later instead of concluding the file is empty. ${personalOnly}`,
47422
47489
  uploadEntityFile: `Upload a file into the entity's linked Nextcloud folder; a taken name gets a numbered suffix. Send exactly one of base64Content (at most 10 MiB decoded) or activityAttachment (an existing Ente activity attachment). ${personalOnly}`
47423
47490
  };
47491
+ var READ_OPERATIONS = /* @__PURE__ */ new Set(["listEntityFiles", "searchEntityFiles", "readEntityFile"]);
47492
+ function isEntityFileReadOperation(operationName) {
47493
+ return READ_OPERATIONS.has(operationName);
47494
+ }
47424
47495
  function definition3(operationName) {
47425
47496
  const schema = external_exports.toJSONSchema(entityFileInputs[operationName], { io: "input" });
47426
47497
  return {
@@ -47432,7 +47503,7 @@ function definition3(operationName) {
47432
47503
  errorSchema: operationErrorSchema,
47433
47504
  resultSchema: operationResultSchema(entityFileResults[operationName]),
47434
47505
  outputSchema: { type: "object", properties: { data: operationResultSchema(entityFileResults[operationName]) }, required: ["data"], additionalProperties: false },
47435
- capability: operationName === "listEntityFiles" ? "read" : "write",
47506
+ capability: isEntityFileReadOperation(operationName) ? "read" : "write",
47436
47507
  executionProfile: "full",
47437
47508
  resultProjection: "full",
47438
47509
  environment: "content",
@@ -47459,6 +47530,12 @@ var linkFolderRef = makeFunctionReference2(
47459
47530
  var listRef = makeFunctionReference2(
47460
47531
  "entityFileOperations:list"
47461
47532
  );
47533
+ var searchRef = makeFunctionReference2(
47534
+ "entityFileOperations:search"
47535
+ );
47536
+ var readRef = makeFunctionReference2(
47537
+ "entityFileOperations:read"
47538
+ );
47462
47539
  var uploadRef = makeFunctionReference2(
47463
47540
  "entityFileOperations:upload"
47464
47541
  );
@@ -47474,6 +47551,14 @@ async function listEntityFiles(caller, input2) {
47474
47551
  const args = parseOperationInput("listEntityFiles", entityFileInputs.listEntityFiles, input2);
47475
47552
  return parseEntityFileResult("listEntityFiles", await caller.action(listRef, args));
47476
47553
  }
47554
+ async function searchEntityFiles(caller, input2) {
47555
+ const args = parseOperationInput("searchEntityFiles", entityFileInputs.searchEntityFiles, input2);
47556
+ return parseEntityFileResult("searchEntityFiles", await caller.action(searchRef, args));
47557
+ }
47558
+ async function readEntityFile(caller, input2) {
47559
+ const args = parseOperationInput("readEntityFile", entityFileInputs.readEntityFile, input2);
47560
+ return parseEntityFileResult("readEntityFile", await caller.action(readRef, args));
47561
+ }
47477
47562
  async function uploadEntityFile(caller, input2) {
47478
47563
  const args = parseOperationInput("uploadEntityFile", entityFileInputs.uploadEntityFile, input2);
47479
47564
  return parseEntityFileResult("uploadEntityFile", await caller.action(uploadRef, args));
@@ -47519,6 +47604,38 @@ var FilesListCmd = class extends ProfileCommand {
47519
47604
  this.emitText({ data }, () => data.status !== "ok" ? `Unavailable: ${data.status}` : [`Linked folder: ${data.linkedFolderPath}`, ...data.items.length ? data.items.map((item) => `${item.isFolder ? "dir " : "file"} ${item.path}${item.size === null ? "" : ` ${item.size} B`}`) : ["Empty folder."]].join("\n"));
47520
47605
  }
47521
47606
  };
47607
+ var FilesSearchCmd = class extends ProfileCommand {
47608
+ static paths = [["files", "search"]];
47609
+ static usage = Command.Usage({ category: "Files", description: "Find files and folders by name anywhere below the entity's linked Nextcloud folder" });
47610
+ entityId = options_exports.String({ required: true });
47611
+ query = options_exports.String({ required: true });
47612
+ async run() {
47613
+ const data = await searchEntityFiles(await resolveCaller(), { entityId: this.entityId, query: this.query });
47614
+ this.emitText({ data }, () => data.status !== "ok" ? `Unavailable: ${data.status}` : [
47615
+ ...data.items.length ? data.items.map((item) => `${item.isFolder ? "dir " : "file"} ${item.path}`) : ["No matches."],
47616
+ ...data.truncated ? ["More matches exist; narrow the query."] : []
47617
+ ].join("\n"));
47618
+ }
47619
+ };
47620
+ var FilesReadCmd = class extends ProfileCommand {
47621
+ static paths = [["files", "read"]];
47622
+ static usage = Command.Usage({ category: "Files", description: "Print the text of a PDF, DOCX or plain-text file in the entity's linked Nextcloud folder" });
47623
+ entityId = options_exports.String({ required: true });
47624
+ filePath = options_exports.String({ required: true });
47625
+ offset = options_exports.String("--offset", { description: "Character offset to continue a truncated text" });
47626
+ maxCharacters = options_exports.String("--max-characters", { description: "Characters to return per call" });
47627
+ async run() {
47628
+ const data = await readEntityFile(await resolveCaller(), {
47629
+ entityId: this.entityId,
47630
+ path: this.filePath,
47631
+ ...this.offset === void 0 ? {} : { offset: Number(this.offset) },
47632
+ ...this.maxCharacters === void 0 ? {} : { maxCharacters: Number(this.maxCharacters) }
47633
+ });
47634
+ this.emitText({ data }, () => data.status !== "ok" ? `Unavailable: ${data.status}` : [...data.notice ? [`[${data.notice.code}] ${data.notice.message}
47635
+ `] : [], data.text, ...data.truncated ? [`
47636
+ [${data.offset + data.text.length} of ${data.totalCharacters} characters; continue with --offset ${data.offset + data.text.length}]`] : []].join("\n"));
47637
+ }
47638
+ };
47522
47639
  var FilesUploadCmd = class extends ProfileCommand {
47523
47640
  static paths = [["files", "upload"]];
47524
47641
  static usage = Command.Usage({
@@ -47580,7 +47697,7 @@ var FilesCreateFolderCmd = class extends ProfileCommand {
47580
47697
  this.emitText({ data }, () => `Created and linked ${data.path}`);
47581
47698
  }
47582
47699
  };
47583
- var FILES_COMMANDS = [FilesListCmd, FilesUploadCmd, FilesLinkCmd, FilesCreateFolderCmd];
47700
+ var FILES_COMMANDS = [FilesListCmd, FilesSearchCmd, FilesReadCmd, FilesUploadCmd, FilesLinkCmd, FilesCreateFolderCmd];
47584
47701
 
47585
47702
  // ente/commands/notification.ts
47586
47703
  import { readFileSync as readFileSync15 } from "node:fs";