Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .agents/skills/use-ocis-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: use-ocis-cli
description: Safely operate oCIS servers through the installed ocis command-line client. Use when an AI agent is asked to inspect, list, search, transfer, synchronize, share, restore, inspect activity history, manage notifications, or administer files, Spaces, shares, users, or groups in oCIS. Do not use for developing the ocis-cli source code.
description: Safely operate oCIS servers through the installed ocis command-line client. Use when an AI agent is asked to inspect, list, search, transfer, archive, synchronize, share, restore, inspect activity history, manage notifications, or administer files, Spaces, shares, users, or groups in oCIS. Do not use for developing the ocis-cli source code.
---

# Use oCIS CLI
Expand Down Expand Up @@ -52,6 +52,9 @@ protocol-level work.
## Choose the operation

- Use `upload` or `download` for a one-time transfer.
- Use `archive download` when the user wants the server to package one or more
remote resources into a single ZIP or TAR file. Check `archive formats` and
run with `--dry-run` before a broad archive download.
- Use `sync push`, `sync pull`, or `sync bidirectional` for directory trees that
should be reconciled. Run a sync with `--dry-run` first.
- Use `share received` or `share overview` to inspect shares. Never accept a
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/use-ocis-cli/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ same command.
| `rm, remove` | Move a remote resource to trash unless the selected operation states otherwise. |
| `upload` | Transfer a local file or directory to oCIS. |
| `download` | Transfer a remote file or directory to the local filesystem. |
| `archive download` | Ask the server to package selected remote resources into one local ZIP or TAR archive. |
| `archive formats` | List archive formats and source limits advertised by the server. |
| `du` | Summarize logical remote file sizes. |
| `search, find` | Search remote files and directories. |
| `batch` | Execute reviewed file operations supplied as JSONL. |
Expand Down
5 changes: 4 additions & 1 deletion .claude/skills/use-ocis-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: use-ocis-cli
description: Safely operate oCIS servers through the installed ocis command-line client. Use when an AI agent is asked to inspect, list, search, transfer, synchronize, share, restore, inspect activity history, manage notifications, or administer files, Spaces, shares, users, or groups in oCIS. Do not use for developing the ocis-cli source code.
description: Safely operate oCIS servers through the installed ocis command-line client. Use when an AI agent is asked to inspect, list, search, transfer, archive, synchronize, share, restore, inspect activity history, manage notifications, or administer files, Spaces, shares, users, or groups in oCIS. Do not use for developing the ocis-cli source code.
---

# Use oCIS CLI
Expand Down Expand Up @@ -52,6 +52,9 @@ protocol-level work.
## Choose the operation

- Use `upload` or `download` for a one-time transfer.
- Use `archive download` when the user wants the server to package one or more
remote resources into a single ZIP or TAR file. Check `archive formats` and
run with `--dry-run` before a broad archive download.
- Use `sync push`, `sync pull`, or `sync bidirectional` for directory trees that
should be reconciled. Run a sync with `--dry-run` first.
- Use `share received` or `share overview` to inspect shares. Never accept a
Expand Down
2 changes: 2 additions & 0 deletions .claude/skills/use-ocis-cli/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ same command.
| `rm, remove` | Move a remote resource to trash unless the selected operation states otherwise. |
| `upload` | Transfer a local file or directory to oCIS. |
| `download` | Transfer a remote file or directory to the local filesystem. |
| `archive download` | Ask the server to package selected remote resources into one local ZIP or TAR archive. |
| `archive formats` | List archive formats and source limits advertised by the server. |
| `du` | Summarize logical remote file sizes. |
| `search, find` | Search remote files and directories. |
| `batch` | Execute reviewed file operations supplied as JSONL. |
Expand Down
6 changes: 5 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal/
command/ Cobra command tree and input validation
app/ application use-case orchestration
apperror/ stable error categories and exit-code mapping
archiver/ authenticated archive-download protocol client
auth/ OIDC protocol implementation
config/ persisted profile model and atomic storage
credentials/ OS credential-service adapter
Expand Down Expand Up @@ -69,6 +70,9 @@ without starting a subprocess.
contains shared application wiring.
- `internal/apperror`: classify usage, authentication, not-found, and conflict
errors without coupling application services to Cobra.
- `internal/archiver`: validate same-origin server-advertised archive endpoints,
stream authenticated ZIP or TAR responses, and verify completed archives
before the application atomically installs them at a local destination.
- `internal/activities`: query account-wide or resource-scoped activity
history through the bounded oCIS Graph extension, preserving the localized
message template and structured variables returned by the server.
Expand Down Expand Up @@ -143,7 +147,7 @@ without starting a subprocess.
authentication headers, metadata and checksum response mapping, and safe
scalar custom-property `PROPFIND`/`PROPPATCH` operations.

Protocol-specific behavior belongs in dedicated `internal/activities`, `internal/auth`,
Protocol-specific behavior belongs in dedicated `internal/activities`, `internal/archiver`, `internal/auth`,
`internal/eventstream`, `internal/federation`, `internal/graph`, `internal/notifications`,
`internal/search`, `internal/sharing`, `internal/trash`, `internal/versions`,
and `internal/webdav` adapters. Recursive local/remote
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ check: fmt

coverage:
go run ./tools/covercheck -min $(COVERAGE_MIN) \
activities app auth eventstream federation graph httpapi notifications retry search sharing sync trash transfer versions \
activities app archiver auth eventstream federation graph httpapi notifications retry search sharing sync trash transfer versions \
webdav

fmt:
Expand Down
33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ The current profile is marked with `*` in `server list`.

Run `ocis doctor [PROFILE]` to validate the config schema, operating-system
credential service, authentication, advertised WebDAV capabilities, Spaces,
public-link support, and resumable-upload support.
public-link support, resumable uploads, archive downloads, and real-time event
support.

## Spaces

Expand Down Expand Up @@ -653,6 +654,36 @@ directory. For example, `ocis download /demo ./ --recursive` creates
`./demo/demo`. A destination that does not exist is created as the downloaded
directory itself.

## Archive downloads

Ask the oCIS server to package one or more files and directories into a single
ZIP or TAR download:

```sh
ocis archive formats
ocis archive download /Documents /Photos/trip.jpg --output backup.zip
ocis archive download /Documents --output backup.tar --format tar
ocis archive download /Documents --output backup.zip --dry-run
```

The command is different from `download --recursive`: oCIS creates one archive
on the server and the CLI downloads that one stream. `archive formats` shows
the formats and source limits advertised by the selected server. The output
format is inferred from `.tar`; otherwise it defaults to ZIP. A conflicting
`.zip` or `.tar` extension is rejected.

Before downloading, the CLI resolves every selected resource, walks selected
directories, and checks the advertised entry-count and logical-size limits.
Nested or duplicate selections are rejected so resources are not archived
twice. `--dry-run` performs that complete preflight without requesting an
archive or creating a local file. The server remains authoritative for access
permissions, including within a selected Space.

Archive downloads use the same authenticated profile as other commands. The
CLI refuses a cross-origin archive endpoint advertised by a server, validates
the complete ZIP or TAR stream, and atomically installs the result. Existing
destinations are protected unless `--overwrite` is explicitly supplied.

## One-way synchronization

Reconcile complete directory trees from one authoritative source:
Expand Down
5 changes: 5 additions & 0 deletions internal/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ func TestDoctorValidatesProfileAndCapabilities(t *testing.T) {
case request.URL.Path == "/ocs/v2.php/cloud/capabilities":
writeAppOCS(writer, `{"capabilities":{
"core":{"support-sse":true},
"files":{"archivers":[{
"enabled":true,"version":"2.0.0","formats":["zip","tar"],
"archiver_url":"/archiver","max_num_files":"1000","max_size":"1000000"
}]},
"files_sharing":{"api_enabled":true,"public":{
"enabled":true,"password":{"enforced":false},
"expire_date":{"enabled":true}
Expand Down Expand Up @@ -338,6 +342,7 @@ func TestDoctorValidatesProfileAndCapabilities(t *testing.T) {
if !strings.Contains(rendered.String(), `"type": "diagnostic"`) ||
!strings.Contains(rendered.String(), `"DAV capabilities"`) ||
!strings.Contains(rendered.String(), `"public links"`) ||
!strings.Contains(rendered.String(), `"archive downloads"`) ||
!strings.Contains(rendered.String(), `"real-time events"`) {
t.Fatalf("output: %s", rendered.String())
}
Expand Down
40 changes: 40 additions & 0 deletions internal/app/archive_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package app

import "context"

// ArchiveDownloadRequest describes one server-side archive download.
type ArchiveDownloadRequest struct {
Paths []string
Destination string
Format string
Overwrite bool
DryRun bool
}

// RunArchiveDownloadWithOptions creates and downloads a server-side archive.
func RunArchiveDownloadWithOptions(
ctx context.Context,
request ArchiveDownloadRequest,
selectedProfile string,
options RunOptions,
) error {
return classifyProtocolError(
"archive download",
runArchiveDownload(
ctx, request, selectedProfile, options.normalized(),
),
)
}

// RunArchiveFormatsWithOptions lists formats advertised by the selected
// server's preferred enabled archive service.
func RunArchiveFormatsWithOptions(
ctx context.Context,
selectedProfile string,
options RunOptions,
) error {
return classifyProtocolError(
"archive formats",
runArchiveFormats(ctx, selectedProfile, options.normalized()),
)
}
Loading