{"id":51411627,"url":"https://github.com/excelano/xfiles","last_synced_at":"2026-07-04T15:02:40.482Z","repository":{"id":365071246,"uuid":"1269727329","full_name":"excelano/xfiles","owner":"excelano","description":"Unix-shaped command-line tools for SharePoint document libraries over Microsoft Graph: ftp (xftp), cp (xcp), find (xfind), tree (xtree)","archived":false,"fork":false,"pushed_at":"2026-06-25T16:42:35.000Z","size":155,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-25T18:18:09.491Z","etag":null,"topics":["cli","command-line-tool","file-transfer","ftp","go","golang","microsoft-365","microsoft-graph","oauth","sharepoint"],"latest_commit_sha":null,"homepage":"https://excelano.com/xftp/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/excelano.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-15T03:29:16.000Z","updated_at":"2026-06-25T16:42:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/excelano/xfiles","commit_stats":null,"previous_names":["excelano/xftp","excelano/xfiles"],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/excelano/xfiles","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/excelano%2Fxfiles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/excelano%2Fxfiles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/excelano%2Fxfiles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/excelano%2Fxfiles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/excelano","download_url":"https://codeload.github.com/excelano/xfiles/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/excelano%2Fxfiles/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35125718,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-04T02:00:05.987Z","response_time":113,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","command-line-tool","file-transfer","ftp","go","golang","microsoft-365","microsoft-graph","oauth","sharepoint"],"created_at":"2026-07-04T15:02:37.800Z","updated_at":"2026-07-04T15:02:40.473Z","avatar_url":"https://github.com/excelano.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# xfiles\n\nxfiles is a family of command-line tools that give a SharePoint document library the feel of the Unix file utilities your fingers already know. There is no SSH, FTP, SCP, or rsync server behind SharePoint to connect to — those protocols simply don't exist there — so each tool recreates the experience on top of the Microsoft Graph drive API and hides the Graph plumbing entirely.\n\n| Tool | Unix kin | What it does |\n|---|---|---|\n| `xftp` | ftp | Interactive session: browse and move files with `ls`, `cd`, `get`, `put`, `mkdir`, `rm`, `mv`. |\n| `xcp` | scp | One-shot copy of a single file to or from a library; streams through `-` for pipes. |\n| `xsync` | rsync | Recursively mirror a directory tree to or from a library, transferring only what changed. |\n| `xfind` | find | Walk a library and print matching paths, filtered by name, type, or depth. |\n| `xtree` | tree | Print a library as an indented tree with a directory and file count. |\n\nEach is a single static Go binary with no daemon and no mounted filesystem. Authentication is device-code OAuth shared across the whole suite: the first time a tool connects it prints a short code and a URL, you sign in once in a browser, and the refresh token is cached under `~/.config` so later runs are silent. All five share one app registration, so a single consent covers the family (and the sibling tool [xql](https://github.com/excelano/xql)).\n\n## Install\n\n### Debian and Ubuntu\n\nAdd the [Excelano apt repository](https://excelano.com/apt/) once (one-time setup):\n\n```sh\ncurl -fsSL https://excelano.com/apt/setup.sh | sudo sh\n```\n\nThen install the whole suite as a single metapackage, so `apt upgrade` keeps everything current:\n\n```sh\nsudo apt install xfiles\n```\n\n`xfiles` is a metapackage that pulls in all five tools. To install just one, name it instead — `sudo apt install xsync`, for example.\n\n### Homebrew\n\nOn macOS or Linux, tap and trust the repository once — Homebrew gates third-party taps behind explicit trust (one-time setup):\n\n```sh\nbrew tap excelano/tap\nbrew trust excelano/tap\n```\n\nThere's no metapackage, so name the tools — all five, or just the ones you want. `brew upgrade` keeps them current:\n\n```sh\nbrew install xftp xcp xsync xfind xtree\n```\n\n### Prebuilt binary (Linux and macOS, x86_64 and arm64)\n\nThe install script fetches prebuilt binaries and drops all five into one directory:\n\n```\ncurl -fsSL https://raw.githubusercontent.com/excelano/xfiles/main/install.sh | sh\n```\n\nIf the installer needs to write to a root-owned directory like `/usr/local/bin`, wrap `sh`, not `curl`:\n\n```\ncurl -fsSL https://raw.githubusercontent.com/excelano/xfiles/main/install.sh | sudo sh\n```\n\nPin a version with `XFILES_VERSION=v1.5.0`, or install elsewhere with `XFILES_INSTALL_DIR=$HOME/bin`. To uninstall, run the matching `uninstall.sh` the same way, which removes all five binaries.\n\n### Go\n\nFrom source (Go 1.24 or later):\n\n```\ngo install github.com/excelano/xfiles/cmd/xftp@latest\ngo install github.com/excelano/xfiles/cmd/xcp@latest\ngo install github.com/excelano/xfiles/cmd/xsync@latest\ngo install github.com/excelano/xfiles/cmd/xfind@latest\ngo install github.com/excelano/xfiles/cmd/xtree@latest\n```\n\n## Pointing at a library\n\nEvery tool takes a SharePoint URL — a site, a document library, or a folder, including the link you copy straight from the browser address bar.\n\n```\nhttps://contoso.sharepoint.com/sites/Marketing\nhttps://contoso.sharepoint.com/sites/Marketing/Shared%20Documents/Reports\n```\n\nWrap the URL in single quotes if it contains `?` or `\u0026`, which the \"Copy link\" button's URLs always do — otherwise the shell splits the command on the `\u0026` before the tool ever sees it. A plain site or folder URL like the ones above needs no quoting.\n\nThe library is worked out from the URL. A bare site URL binds the site's default document library. A URL that points into a specific library binds that one, and where it points at a folder within the library, the tool starts there. To force a particular library regardless of the URL, name it by its display name with `--library`, which every tool accepts:\n\n```\nxftp --library \"Project Files\" https://contoso.sharepoint.com/sites/Marketing\n```\n\n## xftp — interactive sessions\n\n`xftp` connects to a library and drops you at a prompt that shows your position, where you move files with the verbs your fingers already know:\n\n```\nxftp:/\u003e ls\nxftp:/\u003e cd Reports\nxftp:/Reports\u003e get \"Q1 Plan.xlsx\"\nxftp:/Reports\u003e put report.pdf Archive/report.pdf\n```\n\nPaths may be relative to the current folder or absolute with a leading `/`, and `.`/`..` work as you'd expect. Names containing spaces can be quoted (`\"Phase 2\"` or `'Phase 2'`) or escaped (`Phase\\ 2`), the same way you would in a shell.\n\n| Command | What it does |\n|---|---|\n| `ls [path]` | List a remote folder. Defaults to the current folder. |\n| `cd [path]` | Change remote folder. With no argument, prints the current folder. |\n| `pwd` | Print the current remote folder. |\n| `get \u003cremote\u003e [local]` | Download a file. Defaults the local name to the remote's. |\n| `put \u003clocal\u003e [remote]` | Upload a file. Files over 250 MB upload in chunks, with progress. Defaults the remote name to the local's. |\n| `mkdir \u003cpath\u003e` | Create a remote folder. |\n| `rm \u003cpath\u003e` | Delete a file. Folders are recursive, so they prompt for confirmation first. |\n| `mv \u003csrc\u003e \u003cdst\u003e` | Move or rename a remote item. |\n| `lcd [dir]` | Change the local working folder for `get`/`put`. With no argument, prints it. |\n| `lpwd` | Print the local working folder. |\n| `lls [dir]` | List a local folder. |\n| `help` | Show the command list. |\n| `quit` | Exit. |\n\nDeleting a single file goes straight through, since SharePoint routes it to the recycle bin and it can be recovered there. Deleting a folder is recursive and irreversible from xftp's side, so it asks first.\n\n## xcp — one-shot copies\n\nWhen you just need to move a single file and don't want a session, use `xcp`. It mirrors `scp`: two arguments, a source and a destination, where exactly one of them is a SharePoint URL. Which side carries the URL decides the direction, the same way `scp` keys off which side carries `host:`.\n\nUpload a local file to a library folder:\n\n```\nxcp report.xlsx \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports\"\n```\n\nDownload a file from a library to the current directory:\n\n```\nxcp \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports/Q1 Plan.xlsx\" ./\n```\n\nThe destination follows `cp`/`scp` habits. On upload, a URL that points at a folder copies the file into it under its own name, a URL that points at an existing file overwrites it, and any other path is taken as the new name. On download, a destination that is an existing directory receives the file under its remote name, and otherwise the destination is the path to write.\n\nUse `-` as the local side to stream instead of naming a file. A `-` destination cats the remote file to stdout, which keeps the byte stream clean for piping; a `-` source uploads from stdin, in which case the URL must name the target file since stdin has no name of its own:\n\n```\nxcp \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports/Q1.xlsx\" - | in2csv | head\ngenerate-report | xcp - \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports/report.csv\"\n```\n\nRecursive directory copies aren't part of xcp — that job belongs to `xsync` below, which moves whole trees and transfers only what changed. xcp keeps its own token cache under `~/.config/xcp`.\n\n## xsync — recursive mirror\n\n`xsync` mirrors a whole directory tree between the local filesystem and a library, the way `rsync` does. Two arguments, a source and a destination, exactly one of them a SharePoint URL; which side carries the URL sets the direction.\n\nPush a local folder up to a library:\n\n```\nxsync ./reports \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports\"\n```\n\nPull a library folder down to a local directory:\n\n```\nxsync \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports\" ./reports\n```\n\nOnly files that are new or changed are transferred, compared by size and modification time, so a second run with nothing changed transfers nothing. To make that comparison hold across runs, xsync records each uploaded file's modification time on the SharePoint side and restores the local modification time on download. When a file is the same size but its timestamps disagree — which happens on document libraries that don't preserve the recorded time — xsync compares SharePoint's QuickXorHash against the same hash computed locally before deciding, so a file is never re-sent on a drifted timestamp alone, and never wrongly skipped when its contents actually changed.\n\nBy default xsync only adds and updates; it never deletes. Pass `--delete` to make the destination an exact mirror, removing items that no longer exist in the source — when run in a terminal it asks for confirmation first. Pass `--dry-run` (`-n`) to print the full plan and change nothing, which is the safe way to preview a `--delete`:\n\n```\nxsync --dry-run --delete ./reports \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports\"\nxsync --delete ./reports \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports\"\n```\n\nxsync keeps its own token cache under `~/.config/xsync`.\n\n## xfind and xtree — recursive listing\n\nWhere `ls` shows one folder, `xfind` and `xtree` walk a whole library. Both take a single SharePoint URL — site, library, or folder — and recurse from there, the same URL shapes the other tools accept, copy links included. Both are read-only: they only ever list.\n\n`xfind` prints one path per line, relative to the folder the URL points at, the way `find` prints paths relative to its starting directory. With no flags it lists everything; `--name` (or case-insensitive `--iname`) filters by a glob on the file or folder name, `--type f` or `--type d` restricts to files or folders, and `--maxdepth` limits how deep it descends.\n\n```\nxfind https://contoso.sharepoint.com/sites/Marketing\nxfind --type f --name '*.xlsx' \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents/Reports\"\nxfind --type d --maxdepth 2 https://contoso.sharepoint.com/sites/Marketing\n```\n\nBecause the output is plain paths on stdout, it pipes into the usual tools — `xfind --name '*.pdf' \u003curl\u003e | wc -l` counts the PDFs in a library.\n\n`xtree` prints the same walk as an indented tree and finishes with a `N directories, M files` summary. `-L` caps the depth shown and `-d` lists folders only.\n\n```\nxtree https://contoso.sharepoint.com/sites/Marketing\nxtree -L 2 -d \"https://contoso.sharepoint.com/sites/Marketing/Shared Documents\"\n```\n\nEach keeps its own token cache (`~/.config/xfind`, `~/.config/xtree`).\n\n## Authentication and tenants\n\nThe suite authenticates through a multi-tenant Azure app registration (\"Excelano SharePoint tools\"), shared across `xftp`, `xcp`, `xsync`, `xfind`, `xtree`, and the sibling tool [xql](https://github.com/excelano/xql), so consenting once covers them all. Pointing a tool at another organization's site uses that same registration — nobody sets up their own. The first connection to a new tenant raises a one-time consent prompt; depending on that tenant's policy, either the user or an administrator clears it, after which everyone in the tenant is covered. The single scope requested is `Sites.ReadWrite.All`. If your organization restricts user consent, [ADMINS.md](ADMINS.md) has everything your IT department needs to review and approve the application.\n\nTo use your own app registration instead, change `defaultClientID` in `internal/spauth/auth.go` and rebuild.\n\n## Large files and transfers\n\nFiles up to 250 MB upload in a single request. Above that, the transfer opens a Graph upload session and streams the file in 10 MiB chunks. Downloads stream straight to disk as well, into a temporary file that's renamed into place only once the transfer completes, so an interrupted download never leaves a corrupt file at the real name. Either direction reads or writes directly to disk rather than buffering the whole file in memory, so transfer size is bounded by the library's quota and your local disk, not by RAM.\n\nTransfers over 50 MB print a progress line. Ctrl-C interrupts a transfer in progress and cleans up after itself: a partial download is discarded, and an aborted upload session is cancelled on the server.\n\n## Building\n\n```\ngo build -o xftp ./cmd/xftp\ngo build -o xcp ./cmd/xcp\ngo build -o xsync ./cmd/xsync\ngo build -o xfind ./cmd/xfind\ngo build -o xtree ./cmd/xtree\n```\n\n---\n\nBuilt by David M. Anderson, with AI assistance.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexcelano%2Fxfiles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fexcelano%2Fxfiles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fexcelano%2Fxfiles/lists"}