ofiere-openclaw-plugin 4.14.0 → 4.15.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tools.ts +66 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ofiere-openclaw-plugin",
3
- "version": "4.14.0",
3
+ "version": "4.15.0",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin for Ofiere PM - 11 meta-tools covering tasks, agents, projects, scheduling, knowledge, workflows, notifications, memory, prompts, constellation, and space file management",
6
6
  "keywords": ["openclaw", "ofiere", "project-management", "agents", "plugin"],
package/src/tools.ts CHANGED
@@ -3232,6 +3232,23 @@ function registerFileOps(
3232
3232
  });
3233
3233
  }
3234
3234
 
3235
+ // ── File Ops helpers ─────────────────────────────────────────────────────────
3236
+
3237
+ /**
3238
+ * Sanitize a filename for safe use as a Supabase storage object key.
3239
+ * Strips characters that break storage APIs (parens, em-dashes, exclamation marks, etc.)
3240
+ * while preserving extension and readability. The original filename is kept in DB metadata.
3241
+ */
3242
+ function sanitizeStorageFileName(fileName: string): string {
3243
+ // Replace spaces with hyphens, strip anything not alphanumeric, hyphen, underscore, or dot
3244
+ return fileName
3245
+ .replace(/\s+/g, "-")
3246
+ .replace(/[^a-zA-Z0-9._-]/g, "")
3247
+ .replace(/-{2,}/g, "-") // collapse multiple hyphens
3248
+ .replace(/^-+|-+$/g, "") // trim leading/trailing hyphens
3249
+ || "file"; // fallback if everything was stripped
3250
+ }
3251
+
3235
3252
  // ── File Ops handlers ────────────────────────────────────────────────────────
3236
3253
 
3237
3254
  async function handleListFiles(
@@ -3339,8 +3356,9 @@ async function handleCreateTextFile(
3339
3356
  };
3340
3357
  const mimeType = mimeMap[ext] || "text/plain";
3341
3358
 
3342
- // Upload to storage
3343
- const storagePath = `${userId}/${spaceId}/${Date.now()}-${fileName}`;
3359
+ // Upload to storage — sanitize filename for storage key safety
3360
+ const safeFileName = sanitizeStorageFileName(fileName);
3361
+ const storagePath = `${userId}/${spaceId}/${Date.now()}-${safeFileName}`;
3344
3362
  const blob = new Blob([content], { type: mimeType });
3345
3363
  const buffer = await blob.arrayBuffer();
3346
3364
 
@@ -3388,8 +3406,32 @@ async function handleUploadFile(
3388
3406
  const bytes = new Uint8Array(binaryStr.length);
3389
3407
  for (let i = 0; i < binaryStr.length; i++) bytes[i] = binaryStr.charCodeAt(i);
3390
3408
 
3391
- const mimeType = (params.file_type as string) || "application/octet-stream";
3392
- const storagePath = `${userId}/${spaceId}/${Date.now()}-${fileName}`;
3409
+ // Auto-detect MIME from extension if not explicitly provided
3410
+ let mimeType = params.file_type as string;
3411
+ if (!mimeType) {
3412
+ const ext = fileName.split(".").pop()?.toLowerCase() || "";
3413
+ const binaryMimeMap: Record<string, string> = {
3414
+ png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif",
3415
+ webp: "image/webp", bmp: "image/bmp", ico: "image/x-icon",
3416
+ svg: "image/svg+xml", avif: "image/avif",
3417
+ pdf: "application/pdf", zip: "application/zip", gz: "application/gzip",
3418
+ tar: "application/x-tar", "7z": "application/x-7z-compressed",
3419
+ mp3: "audio/mpeg", wav: "audio/wav", ogg: "audio/ogg", flac: "audio/flac",
3420
+ mp4: "video/mp4", webm: "video/webm", avi: "video/x-msvideo", mov: "video/quicktime",
3421
+ woff: "font/woff", woff2: "font/woff2", ttf: "font/ttf", otf: "font/otf",
3422
+ doc: "application/msword", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
3423
+ xls: "application/vnd.ms-excel", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
3424
+ ppt: "application/vnd.ms-powerpoint", pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
3425
+ md: "text/markdown", txt: "text/plain", json: "application/json",
3426
+ csv: "text/csv", html: "text/html", css: "text/css",
3427
+ js: "text/javascript", ts: "text/typescript", yaml: "text/yaml",
3428
+ yml: "text/yaml", xml: "text/xml",
3429
+ };
3430
+ mimeType = binaryMimeMap[ext] || "application/octet-stream";
3431
+ }
3432
+
3433
+ const safeFileName = sanitizeStorageFileName(fileName);
3434
+ const storagePath = `${userId}/${spaceId}/${Date.now()}-${safeFileName}`;
3393
3435
 
3394
3436
  const { error: uploadErr } = await supabase.storage
3395
3437
  .from("space-files")
@@ -3463,13 +3505,16 @@ async function handleRenameFile(
3463
3505
  if (!fileId) return err("Missing required field: file_id");
3464
3506
  if (!newName) return err("Missing required field: new_name");
3465
3507
 
3466
- const { error } = await supabase
3508
+ const { data, error } = await supabase
3467
3509
  .from("pm_space_files")
3468
3510
  .update({ file_name: newName, updated_at: new Date().toISOString() })
3469
3511
  .eq("id", fileId)
3470
- .eq("user_id", userId);
3512
+ .eq("user_id", userId)
3513
+ .select("id")
3514
+ .maybeSingle();
3471
3515
 
3472
3516
  if (error) return err(error.message);
3517
+ if (!data) return err(`File not found: ${fileId}`);
3473
3518
  return ok({ message: `File renamed to "${newName}"` });
3474
3519
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
3475
3520
  }
@@ -3483,13 +3528,16 @@ async function handleRenameSpaceFolder(
3483
3528
  if (!folderId) return err("Missing required field: folder_id");
3484
3529
  if (!newName) return err("Missing required field: new_name");
3485
3530
 
3486
- const { error } = await supabase
3531
+ const { data, error } = await supabase
3487
3532
  .from("pm_space_folders")
3488
3533
  .update({ name: newName, updated_at: new Date().toISOString() })
3489
3534
  .eq("id", folderId)
3490
- .eq("user_id", userId);
3535
+ .eq("user_id", userId)
3536
+ .select("id")
3537
+ .maybeSingle();
3491
3538
 
3492
3539
  if (error) return err(error.message);
3540
+ if (!data) return err(`Folder not found: ${folderId}`);
3493
3541
  return ok({ message: `Folder renamed to "${newName}"` });
3494
3542
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
3495
3543
  }
@@ -3505,13 +3553,16 @@ async function handleMoveFile(
3505
3553
  ? (params.target_folder_id as string | null)
3506
3554
  : null;
3507
3555
 
3508
- const { error } = await supabase
3556
+ const { data, error } = await supabase
3509
3557
  .from("pm_space_files")
3510
3558
  .update({ folder_id: targetFolderId, updated_at: new Date().toISOString() })
3511
3559
  .eq("id", fileId)
3512
- .eq("user_id", userId);
3560
+ .eq("user_id", userId)
3561
+ .select("id")
3562
+ .maybeSingle();
3513
3563
 
3514
3564
  if (error) return err(error.message);
3565
+ if (!data) return err(`File not found: ${fileId}`);
3515
3566
  return ok({ message: `File moved to ${targetFolderId || "root"}` });
3516
3567
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
3517
3568
  }
@@ -3527,13 +3578,16 @@ async function handleMoveSpaceFolder(
3527
3578
  ? (params.target_parent_id as string | null)
3528
3579
  : null;
3529
3580
 
3530
- const { error } = await supabase
3581
+ const { data, error } = await supabase
3531
3582
  .from("pm_space_folders")
3532
3583
  .update({ parent_folder_id: targetParentId, updated_at: new Date().toISOString() })
3533
3584
  .eq("id", folderId)
3534
- .eq("user_id", userId);
3585
+ .eq("user_id", userId)
3586
+ .select("id")
3587
+ .maybeSingle();
3535
3588
 
3536
3589
  if (error) return err(error.message);
3590
+ if (!data) return err(`Folder not found: ${folderId}`);
3537
3591
  return ok({ message: `Folder moved to ${targetParentId || "root"}` });
3538
3592
  } catch (e) { return err(e instanceof Error ? e.message : String(e)); }
3539
3593
  }