$ cat node-template.py

D

Drive Folder Reader

// Reads all documents from a Drive folder recursively. Returns document references that can be passed to a Drive Document Reader.

Input
Storage
#drive#storage#folder
template.py
1"""drive-folder-reader — list document DriveItem ids in a Drive folder.23Migrated from the legacy monolithic template to the new multi-file format.4Privileged Drive IO is delegated to the gais.drive SDK (Gais.drive.list);5this node imports no HTTP client. Returns DriveItem ids (NOT Document refIds)6so downstream nodes can validate permissions via the DriveItem path.7"""89from __future__ import annotations1011import json12import re13import sys14import traceback15from typing import Any1617from gais import Gais1819_UUID_RE = re.compile(20    r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",21    re.IGNORECASE,22)232425def _normalize_drive_item_id(value: Any) -> str:26    """Accept a bare UUID or a mention://drive_{file,folder}/<uuid> URI."""27    if not isinstance(value, str):28        raise ValueError(f"Drive item id must be a string, got {type(value).__name__}")29    s = value.strip()30    for prefix in ("mention://drive_file/", "mention://drive_folder/"):31        if s.startswith(prefix):32            s = s[len(prefix):]33            break34    if not _UUID_RE.match(s):35        raise ValueError(f"Drive item id is not a valid UUID: {value!r}")36    return s373839def process(inputs: dict[str, Any]) -> dict[str, Any]:40    """List every descendant file/document of the folder via gais.drive.list."""41    folder_id = inputs.get("driveItemId")42    if not folder_id:43        raise ValueError("driveItemId input is required")4445    folder_id = _normalize_drive_item_id(folder_id)4647    # Recursive query: every descendant that is a file/document (refId != null),48    # NOT subfolders. The Drive API applies the user's permissions automatically49    # (gais.drive carries the user token).50    result = Gais.drive.list(51        query=f"path descendantOf '{folder_id}' and refId!=null",52        page_size=1000,53    )5455    # Return DriveItem ids (NOT Document refIds) so downstream nodes can56    # validate permissions via the DriveItem path.57    drive_item_ids = [item["id"] for item in result.data.get("items", []) if item.get("id")]58    return {"driveItemIds": drive_item_ids}596061def main() -> None:62    try:63        envelope = json.loads(sys.stdin.read() or "{}")64        if not isinstance(envelope, dict):65            envelope = {}66        inputs = envelope.get("inputs", {}) or {}67        json.dump(process(inputs), sys.stdout)68    except Exception as e:69        print(70            json.dumps({71                "error": str(e),72                "errorType": type(e).__name__,73                "traceback": traceback.format_exc(),74            }),75            file=sys.stderr,76        )77        sys.exit(1)787980if __name__ == "__main__":81    main()

$ git log --oneline

v1.3.0
HEAD
2026-08-18
v1.1.22026-05-22
v1.1.12026-05-07
v1.1.02026-04-09

Updated description to clarify the node returns document IDs recursively.