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
4 changes: 4 additions & 0 deletions .agents/skills/use-ocis-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ protocol-level work.
- Use `activity list` for read-only account-wide or resource-scoped history.
Pass a remote path or `--space` when the requested scope is narrower than the
account.
- Use `event watch` only when the user wants future real-time events. It does
not replay missed events; use `--jsonl` for automation and `--once` for a
single event. Add `--max-wait DURATION` with `--once` when the agent must not
wait indefinitely.
- Use `notification list` and `notification info` to inspect unread events.
In oCIS, `notification dismiss` is the server's mark-as-read operation; it
does not delete the resource referenced by the notification.
Expand Down
14 changes: 14 additions & 0 deletions .agents/skills/use-ocis-cli/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ remote path to use the current file root, or pass `--space SPACE` to scope the
query to that Space. Use `--depth`, `--limit`, and `--sort` for server-side
filtering. Activity history is read-only.

## Real-time events

| Command | Purpose |
| --- | --- |
| `event watch` | Watch future events visible to the authenticated user until interrupted. |
| `event watch --type TYPE` | Show only selected event names; repeat the flag or comma-separate values. |
| `event watch --once` | Exit after the first matching event. |
| `event watch --once --max-wait DURATION` | Wait for one matching event without hanging indefinitely. |
| `event types` | List event names known by this CLI. |

Use `--jsonl`, not `--json`, for the open-ended stream. SSE has no replay, so
use `activity list` or `notification list` when retained state is required.
Never describe `event watch` as a lossless audit log.

## Notifications

| Command | Purpose |
Expand Down
4 changes: 4 additions & 0 deletions .claude/skills/use-ocis-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ protocol-level work.
- Use `activity list` for read-only account-wide or resource-scoped history.
Pass a remote path or `--space` when the requested scope is narrower than the
account.
- Use `event watch` only when the user wants future real-time events. It does
not replay missed events; use `--jsonl` for automation and `--once` for a
single event. Add `--max-wait DURATION` with `--once` when the agent must not
wait indefinitely.
- Use `notification list` and `notification info` to inspect unread events.
In oCIS, `notification dismiss` is the server's mark-as-read operation; it
does not delete the resource referenced by the notification.
Expand Down
14 changes: 14 additions & 0 deletions .claude/skills/use-ocis-cli/references/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,20 @@ remote path to use the current file root, or pass `--space SPACE` to scope the
query to that Space. Use `--depth`, `--limit`, and `--sort` for server-side
filtering. Activity history is read-only.

## Real-time events

| Command | Purpose |
| --- | --- |
| `event watch` | Watch future events visible to the authenticated user until interrupted. |
| `event watch --type TYPE` | Show only selected event names; repeat the flag or comma-separate values. |
| `event watch --once` | Exit after the first matching event. |
| `event watch --once --max-wait DURATION` | Wait for one matching event without hanging indefinitely. |
| `event types` | List event names known by this CLI. |

Use `--jsonl`, not `--json`, for the open-ended stream. SSE has no replay, so
use `activity list` or `notification list` when retained state is required.
Never describe `event watch` as a lossless audit log.

## Notifications

| Command | Purpose |
Expand Down
8 changes: 6 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ internal/
auth/ OIDC protocol implementation
config/ persisted profile model and atomic storage
credentials/ OS credential-service adapter
eventstream/ authenticated server-sent-events protocol client
federation/ ScienceMesh OCM invitation and connection client
graph/ LibreGraph Spaces, directory, and permission client
httpapi/ authenticated retrying HTTP transport
Expand Down Expand Up @@ -59,7 +60,7 @@ without starting a subprocess.
`batch_service.go`, `filesystem_service.go`, `filesystem_tree_service.go`,
`filesystem_du_service.go`, `filesystem_touch_service.go`,
`filesystem_walk.go`, `metadata_service.go`,
`activity_service.go`, `notification_service.go`,
`activity_service.go`, `event_service.go`, `notification_service.go`,
`share_overview_service.go`,
`space_member_service.go`, `space_update_service.go`,
`space_lifecycle_service.go`, and
Expand All @@ -80,6 +81,9 @@ without starting a subprocess.
protected resumable-upload locations in separate size-bounded entries in
macOS Keychain, Linux Secret Service, or Windows Credential Manager;
no plaintext or legacy-format migration path exists.
- `internal/eventstream`: open one authenticated oCIS SSE connection, validate
its media type, and decode bounded standard SSE fields. Reconnect and output
policy remain in the application layer.
- `internal/federation`: create, list, and accept ScienceMesh invitation tokens
and list or remove accepted OCM user connections. It has no profile,
persistence, Cobra, or resource-sharing policy of its own.
Expand Down Expand Up @@ -140,7 +144,7 @@ without starting a subprocess.
scalar custom-property `PROPFIND`/`PROPPATCH` operations.

Protocol-specific behavior belongs in dedicated `internal/activities`, `internal/auth`,
`internal/federation`, `internal/graph`, `internal/notifications`,
`internal/eventstream`, `internal/federation`, `internal/graph`, `internal/notifications`,
`internal/search`, `internal/sharing`, `internal/trash`, `internal/versions`,
and `internal/webdav` adapters. Recursive local/remote
traversal belongs in `internal/transfer`.
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 federation graph httpapi notifications retry search sharing sync trash transfer versions \
activities app auth eventstream federation graph httpapi notifications retry search sharing sync trash transfer versions \
webdav

fmt:
Expand Down
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1242,6 +1242,48 @@ have permission to list grants on the selected resource, so access can differ
between users and Spaces. The CLI reports that authorization decision instead
of assuming that every authenticated user can inspect every activity.

## Real-time events

Watch events delivered to the authenticated user as they happen:

```sh
ocis event watch
ocis event watch --type userlog-notification
ocis event watch --type share-created --type share-removed
ocis event watch --once
ocis event watch --type postprocessing-finished --once --max-wait 30s
ocis --jsonl event watch
```

`--type` filters locally and can be repeated or given a comma-separated list.
`--once` exits after the first matching event, which is useful in scripts.
Combine `--once` with `--max-wait DURATION` to avoid waiting indefinitely when
no matching event arrives. Shell completion suggests known event names, but
manually entered names remain accepted for compatibility with newer servers.
`ocis event types` lists the event names known by this CLI; a server may add
other names without requiring a CLI update. The list includes a short
description for each known type.

Human mode reports when the connection is ready, explains what it is watching,
and shows reconnect progress on stderr. Events on stdout contain the UTC
receive time, a readable description, and the useful fields actually sent by
oCIS. File events currently carry stable item and Space IDs rather than remote
paths, so those values are labeled explicitly. Notification events show the
server's subject and message. A watch is an open-ended stream, so `--json` is
rejected; use `--jsonl` for the complete payload in one versioned JSON envelope
per event. Press Ctrl-C to stop cleanly. Unexpected disconnects use the global
bounded `--retries` policy. Unlike ordinary commands, a watch has no overall
HTTP timeout once connected.

oCIS SSE streams contain only future events and do not replay events missed
before the command started or while it was disconnected. Use `activity list`
for retained file/Space history and `notification list` for the current unread
userlog. A `backchannel-logout` event ends the watch with an authentication
error; the CLI does not silently delete the saved profile or credentials.

The command first checks the server's `core.support-sse` capability. Event
availability and visibility remain server-controlled and user-specific.

## Notifications

List and inspect the authenticated user's unread in-app notifications:
Expand Down
4 changes: 3 additions & 1 deletion internal/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ func TestDoctorValidatesProfileAndCapabilities(t *testing.T) {
switch {
case request.URL.Path == "/ocs/v2.php/cloud/capabilities":
writeAppOCS(writer, `{"capabilities":{
"core":{"support-sse":true},
"files_sharing":{"api_enabled":true,"public":{
"enabled":true,"password":{"enforced":false},
"expire_date":{"enabled":true}
Expand Down Expand Up @@ -336,7 +337,8 @@ 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(), `"public links"`) ||
!strings.Contains(rendered.String(), `"real-time events"`) {
t.Fatalf("output: %s", rendered.String())
}
}
Expand Down
8 changes: 8 additions & 0 deletions internal/app/doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ func RunDoctorWithOptions(
Name: "resumable uploads", Status: tusStatus,
Detail: resumableUploadCapabilityDetail(features),
})
eventStatus := "unsupported"
if features.Core.SupportSSE {
eventStatus = "ok"
}
checks = append(checks, DoctorCheck{
Name: "real-time events", Status: eventStatus,
Detail: "core.support-sse",
})
if _, err := client.stat("/"); err != nil {
return classifyProtocolError("check DAV authentication", err)
}
Expand Down
32 changes: 32 additions & 0 deletions internal/app/event_api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package app

import (
"context"
"time"
)

// EventWatchRequest selects real-time events to print.
type EventWatchRequest struct {
Types []string
Once bool
MaxWait time.Duration
}

// RunEventWatchWithOptions watches authenticated real-time server events.
func RunEventWatchWithOptions(
ctx context.Context,
request EventWatchRequest,
selectedProfile string,
options RunOptions,
) error {
return classifyProtocolError(
"event watch",
runEventWatch(ctx, request, selectedProfile, options.normalized()),
)
}

// RunEventTypesWithOptions prints the event names known by this CLI. The
// server may emit additional names.
func RunEventTypesWithOptions(options RunOptions) error {
return runEventTypes(options.normalized())
}
Loading