Skip to content

Files & Mirrors

Files in Portuni live in two places at once: the remote (the source of truth for the team) and the local mirror on each device. The metadata row in files binds a node to a remote location; the path on the current device is derived from the per-device mirror root, the file’s remote_path, and the node’s sync_key. There is no persisted local_path column on files – it would go stale across devices and renames.

Create a local folder for a node on this device and register it.

Parameter Type Required Description
node_id string yes Node ID
targets string[] yes Mirror targets (only "local" supported in Phase 1)
custom_path string no Override default path

Default path: {PORTUNI_WORKSPACE_ROOT}/{org-slug}/{type-plural}/{node-sync-key}/ (organizations mirror directly to {PORTUNI_WORKSPACE_ROOT}/{org-slug}/).

Locally, every mirror gets the outputs/, wip/, resources/ subdirectories. The org-plural subfolders (projects/, processes/, areas/, principles/) are scaffolded on the remote when an organization is mirrored; locally they appear only as parent directories once child nodes are mirrored.

Returns: { node_id, local_path, subdirs, remote_scaffold, scope_config }remote_scaffold lists the remote folders created and the resolved remote_name; scope_config lists the per-mirror config files written.

Mirror registrations are per device. Each machine keeps its own copy of the registry in {PORTUNI_WORKSPACE_ROOT}/.portuni/sync.db; the shared Turso DB does NOT store per-device paths.

Copy a file into the node’s local mirror, upload it via the routed remote, and persist a files row + file_state cache.

Parameter Type Required Description
node_id string yes Node ID
local_path string yes Absolute path of the source file on this device
status enum no wip (default) or output
subpath string no Optional subfolder within the section

The file is copied to {mirror}/wip/... or {mirror}/outputs/... based on status (or detected from the source path if it already lives inside the mirror), then uploaded to the remote at {org-sync-key}/{type-plural}/{node-sync-key}/{section}/{subpath}/{filename}.

The remote is resolved through remote_routing (priority-ordered). The sync_key-anchored path means renaming a node does NOT change the remote location, so existing references stay valid.

Returns: { file_id, remote_name, remote_path, local_path, hash }

Two modes:

  • file_id – download the remote version into the mirror, refresh the local hash cache. Used to restore a deleted local copy or pull a teammate’s update.
  • node_id – preview only. Classifies each file as unchanged | updated | conflict | orphan | native without modifying anything. Use this before pulling to see what would change.
Parameter Type Required Description
file_id string one of File ID (download mode)
node_id string one of Node ID (preview mode)
force boolean no Download mode only. Overwrite the local file even when it has unpushed local changes. Default false

List files across all nodes with optional filtering. Each row includes a derived local_path (from the current mirror + remote_path + sync_key); it is null when the node has no mirror on this device.

local_path is the node’s real mirror for the home node and its depth-1 neighbours (the seatbelt grants read on those real paths). For an ad-hoc in-scope node (deeper than depth-1) it is null — the files are not on disk; read their content with portuni_read_file (below). See disk read scope.

Scope gating: with node_id the node must be in session scope (out of scope returns scope_expansion_required). Without node_id the call is a global query — mode-gated, and results are restricted to the session scope set (empty scope returns an empty array) unless the mode is permissive. The same gating applies to the other list tools (portuni_list_events, …).

Parameter Type Required Description
node_id string no Filter by node
status enum no Filter by status (wip or output)
limit number no Max rows, newest first (default 500, max 2000)

Returns: Array of files, each with: id, node_id, node_name, filename, status, remote_name, remote_path, current_remote_hash, last_pushed_at, is_native_format, the derived local_path, and updated_at.

Read a file’s content from an in-scope node that the seatbelt does not expose on disk — an ad-hoc node reached by deeper graph traversal (beyond the home node and its depth-1 neighbours, whose folders you read natively via the local_path returned by portuni_get_context / portuni_get_node). The server reads the live file from the node’s local mirror and returns it, so there is no stale copy and no .portuni-scope staging.

Parameter Type Required Description
node_id string yes Node the file belongs to
path string yes File path within the node, e.g. wip/notes.md

Returns the file content as UTF-8 text, or [binary file, N bytes, base64] followed by base64 for non-text files. Scope-gated exactly like portuni_get_node: reading a node not yet in scope returns scope_expansion_required (call portuni_expand_scope first). Errors when the node has no mirror on this device (portuni_pull it first), the file does not exist, or the file exceeds the 1 MB inline limit.

Scan tracked files and (optionally) discover new local / new remote files. Call this at session end when files were touched, before major migrations, or whenever the user asks about sync state.

Parameter Type Required Description
node_id string no Restrict to one node
remote_name string no Restrict to one remote
include_discovery boolean no Walk the mirror + list the remote for new files (default: true)

Returns: classified buckets (clean, push_candidates, pull_candidates, conflicts, orphan, native, new_local, new_remote, deleted_local, deleted_remote, moved).

portuni_list_remotes / portuni_setup_remote / portuni_set_routing_policy

Section titled “portuni_list_remotes / portuni_setup_remote / portuni_set_routing_policy”

Manage the pluggable remote backends and the priority-ordered routing rules that pick a remote for each (node_type, org_slug) combination. See concepts/mirrors for the per-device mirror model.