Recipe: folder navigation (resolve a path, list folders/files, create+verify)
Recipe: folder navigation (resolve a path, list folders/files, create+verify)
Recipe: folder navigation (resolve a path, list folders/files, create+verify)
Prompt exampleList the folders in Content/Adam, list the.txt files in it, and create three named folders then confirm they exist.
Vibe prompts name folders by path ("the Content/Adam folder") but every component
takes a <folder-uuid>. These helpers bridge that gap with the list_children,
create_folder, and search components. All reads are Class A (safe anywhere); the create helper
is only for a confined parent (your run-root anchor).
Key facts
list_children(get_asset_child_nodes) is a folder-navigation tree: it returns only child folders — uploaded files never appear, even after they settle. Items exposename(notdisplayName),parentId,assetType(1== folder), andhasChildren. Use it for folders only.- To list files, use
searchscoped byparentId Equals <folder-id>— it returns both files and folders under the parent (names live underitem["identifiers"]). There is no server-side filename/extension filter, so match the name client-side. - Top-level bucket names repeat on a deployment (several
Content/content). A path resolver must follow every matching branch and require a unique leaf, else it can silently pick the wrong tree.resolve_folder_pathraises on ambiguity (fail-closed).
Python
# components: list_children, create_folder, search
ROOT_ID = "00000000-0000-0000-0000-000000000000" # asset-tree root sentinel
def resolve_folder_path(sdk, path, root_id=ROOT_ID):
"""Resolve a '/'-separated folder path to one id; None if absent.
Follows every branch whose folder name matches each segment and requires a
unique leaf, because top-level bucket names can repeat. Raises on ambiguity.
"""
frontier = [root_id]
for seg in (s for s in path.strip("/").split("/") if s):
nxt = []
for cur in frontier:
for c in list_children(sdk, cur):
if c.get("assetType") == 1 and (c.get("name") or c.get("displayName")) == seg:
nxt.append(c["id"])
frontier = nxt
unique = list(dict.fromkeys(frontier))
if len(unique) > 1:
raise ValueError(f"Ambiguous folder path {path!r}: {len(unique)} matches")
return unique[0] if unique else None
def list_folders(sdk, folder_id):
"""Immediate sub-folders of folder_id (assetType == 1)."""
return [c for c in list_children(sdk, folder_id) if c.get("assetType") == 1]
def list_files_by_extension(sdk, folder_id, ext):
"""Immediate files whose name ends with ext (e.g. '.txt'); case-insensitive.
Files are NOT in the navigation tree, so list them via search scoped to the
folder (parentId Equals), then filter on the name client-side (no server-side
extension filter). Freshly uploaded files appear only once indexed.
"""
ext = ext.lower()
flt = [{"fieldName": "parentId", "operator": "Equals", "values": folder_id}]
items, offset, page = [], 0, 100
while True:
batch = (search(sdk, filters=flt, size=page, offset=offset) or {}).get("items", [])
items.extend(batch)
if len(batch) < page:
break
offset += 1 # offset is in PAGES, not records (see search-patterns.md)
return [
it for it in items
if (it.get("identifiers") or {}).get("assetType") != 1
and ((it.get("identifiers") or {}).get("displayName") or "").lower().endswith(ext)
]
def create_folders_and_verify(sdk, parent_id, names):
"""Create each name under parent_id, then confirm all exist. Returns {name: id}.
Confinement: parent_id MUST be your run-root anchor (or a folder beneath it) —
never an arbitrary shared folder. Raises if any expected folder is missing.
"""
created = {name: create_folder(sdk, parent_id, name) for name in names}
present = {(c.get("name") or c.get("displayName")) for c in list_folders(sdk, parent_id)}
missing = [n for n in names if n not in present]
if missing:
raise AssertionError(f"folders not found after create: {missing}")
return createdJavaScript
// components: listChildren, createFolder, search
const ROOT_ID = "00000000-0000-0000-0000-000000000000"; // asset-tree root sentinel
export async function resolveFolderPath(sdk, path, rootId = ROOT_ID) {
let frontier = [rootId];
for (const seg of path.split("/").filter(Boolean)) {
const nxt = [];
for (const cur of frontier) {
for (const c of await listChildren(sdk, cur)) {
if (c.assetType === 1 && (c.name ?? c.displayName) === seg) nxt.push(c.id);
}
}
frontier = nxt;
}
const unique = [...new Set(frontier)];
if (unique.length > 1) throw new Error(`Ambiguous folder path '${path}': ${unique.length} matches`);
return unique.length ? unique[0] : null;
}
export async function listFolders(sdk, folderId) {
return (await listChildren(sdk, folderId)).filter((c) => c.assetType === 1);
}
export async function listFilesByExtension(sdk, folderId, ext) {
// Files are NOT in the navigation tree — list them via search (parentId Equals),
// then filter on the name client-side (no server-side extension filter).
const e = ext.toLowerCase();
const flt = [{ fieldName: "parentId", operator: "Equals", values: folderId }];
const items = [];
for (let offset = 0; ; offset += 1) {
const res = await search(sdk, null, flt, null, 100, offset);
const batch = res ? res.items : []; // JS returns false when empty
items.push(...batch);
if (batch.length < 100) break;
}
return items.filter(
(it) => it.identifiers?.assetType !== 1 &&
(it.identifiers?.displayName ?? "").toLowerCase().endsWith(e),
);
}
export async function createFoldersAndVerify(sdk, parentId, names) {
// Confinement: parentId MUST be your run-root anchor — never a shared folder.
const created = {};
for (const name of names) created[name] = await createFolder(sdk, parentId, name);
const present = new Set((await listFolders(sdk, parentId)).map((c) => c.name ?? c.displayName));
const missing = names.filter((n) => !present.has(n));
if (missing.length) throw new Error(`folders not found after create: ${missing}`);
return created;
}Notes
- Folders vs files:
list_foldersreads the navigation tree (folders appear instantly, no indexing lag);list_files_by_extensionusessearch, so freshly uploaded files surface only once indexed — poll if you just uploaded. resolve_folder_pathislist_childrenper path segment — fine for shallow paths; for deep trees prefer caching the resolved id and reusing it.- To resolve under a known bucket, pass its id as
root_id/rootIdto skip the ambiguous top level entirely. - Pair this with
recipes/search-patterns.mdonce you hold the folder id (e.g. sort or page large folders viasearchwith aparentId Equalsfilter).
