UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

166 lines (155 loc) • 5.91 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { GSlidesAccessor } from "../../accessor/gslides.js"; import { TokenManager } from "../../core/google/client.js"; import { appDeltaHook } from "../../core/google/events.js"; import { filesContaining as googleFilesContaining } from "../../core/google/search.js"; import { MIME } from "../../core/gslides/constants.js"; import { read as gslidesRead } from "../../core/gslides/read.js"; import { readdir as gslidesReaddir } from "../../core/gslides/readdir.js"; import { rename as gslidesRename } from "../../core/gslides/rename.js"; import { stat as gslidesStat } from "../../core/gslides/stat.js"; import { unlink as gslidesUnlink } from "../../core/gslides/unlink.js"; import { VFSName } from "../../types.js"; import { SLIDES_WORDS } from "../../utils/generated/google_vocabulary.js"; import { sliceWindow } from "../../utils/ranges.js"; import { BaseVFS } from "../base.js"; import { GSlidesConfig } from "./config.js"; import { makeFilename } from "./slide_entry.js"; export const VFS_MD = `\`\`\`text {prefix} owned/ # presentations you created <YYYY-MM-DD>_<title>__<presentation-id>.gslide.json shared/ # presentations others shared with you <YYYY-MM-DD>_<title>__<presentation-id>.gslide.json \`\`\` - \`<YYYY-MM-DD>\` is the file's \`modifiedTime\`, so a date glob such as \`2026-05-*\` narrows by it. - \`<title>\` is sanitized: spaces and any character outside \`[A-Za-z0-9_.-]\` become \`_\`, at most 100 characters. - \`<presentation-id>\` is the Google Slides presentation id. - \`shared/\` holds presentations shared with you by others; ones you own and shared with others are still in \`owned/\`. A \`.gslide.json\` matches the Google Slides API \`presentations.get\` response: \`\`\`jsonc { "presentationId": "...", "title": "...", "pageSize": { "width": {...}, "height": {...} }, "slides": [ { // one element per slide "objectId": "...", "pageElements": [ { "objectId": "...", "shape": { "shapeType": "TEXT_BOX", "text": { "textElements": [ { "textRun": { "content": "the actual text\\n", "style": {...} } } ] } } }, { "image": {...} }, { "table": {...} } ] } ], "masters": [...], "layouts": [...] } \`\`\` Useful \`jq\` paths: \`\`\`jq .title .slides | length # slide count .slides[].pageElements[].shape.text.textElements[].textRun.content # all text .slides[0].objectId \`\`\` Delete, on a \`write\` path: \`\`\`sh rm {prefix}/owned/<file>.gslide.json # deletes it from Drive for good rm -f <path> # ignores a missing file \`\`\` Only \`.gslide.json\` files can be removed; \`owned/\` and \`shared/\` cannot. Rename to a new title, on a \`write\` path: \`\`\`sh mv {prefix}/owned/<date>_<old>__<id>.gslide.json \\ {prefix}/owned/<date>_<new>__<id>.gslide.json \`\`\` Keep the folder, the date and the id, and spell the title the way the listing does (spaces become \`_\`); any other name is refused.`; export class GSlidesVFS extends BaseVFS { name = VFSName.GSLIDES; cachesReads = true; indexTtl = 86_400; // Reads stamp listing metadata; a fresh stat checks Drive by file ID. readRevalidatable = true; vfsMd = VFS_MD; wholeWordSearch = true; config; accessor; constructor(config) { super(); this.config = config; const tm = new TokenManager(config); this.accessor = new GSlidesAccessor({ tokenManager: tm }); } handed = { unlink: ['entry'], rename: ['entry'], }; renderers = { '.gslide.json': 'readDeck' }; /** * Whether this VFS answers `name`; with no walk limit it reads every * file rather than ask the service's search. */ supports(name) { if (name === 'files_containing' && this.walkLimit < 0) return false; return super.supports(name); } filesContaining(path, text, opts) { if (!opts.wholeWord || this.walkLimit < 0) return Promise.resolve(null); return googleFilesContaining(this.accessor, gslidesReaddir, MIME, SLIDES_WORDS, text, [path]); } readdir(path) { return gslidesReaddir(this.accessor, path); } /** Render the file as the JSON its `.gslide.json` name holds. */ async readDeck(path, _handed, offset = 0, size = null) { const data = await gslidesRead(this.accessor, path); return offset === 0 && size === null ? data : sliceWindow(data, offset, size); } stat(path) { return gslidesStat(this.accessor, path); } unlink(path, handed) { return gslidesUnlink(this.accessor, path, handed?.entry); } rename(src, dst, handed) { return gslidesRename(this.accessor, src, dst, handed?.entry ?? null); } deltaHook() { return appDeltaHook(this.accessor, MIME, makeFilename); } getState() { return Promise.resolve(this.configState(GSlidesConfig, this.config)); } } //# sourceMappingURL=gslides.js.map