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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ olares-cli-*.tar.gz
.vscode
.DS_Store
cli/output
cli/docs/
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitignore prevents PR deliverable docs from being tracked

Medium Severity

The new cli/docs/ gitignore entry prevents all files under that directory from being committed, but the PR description explicitly lists cli/docs/notes/auth-2fa-semantics.md as a deliverable documenting the 2FA authentication semantics. This file would never be tracked by git.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4ad2cad. Configure here.

daemon/output
daemon/bin

Expand All @@ -43,3 +44,6 @@ node_modules
cli/olares-cli*

framework/app-service/bin

/files/
/market/
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gitignore entries appear accidentally committed for local dirs

Low Severity

The /files/ and /market/ entries at the repository root gitignore look like developer-local directory exclusions (likely locally-cloned sibling repos) rather than project-wide patterns. No such directories exist in the repository structure. Committing these pollutes the shared .gitignore with entries that only serve one developer's workspace layout.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f8cdfdc. Configure here.

100 changes: 100 additions & 0 deletions cli/cmd/ctl/files/cat.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package files

import (
"context"
"fmt"
"io"
"strings"

"github.com/spf13/cobra"

"github.com/beclab/Olares/cli/pkg/cmdutil"
"github.com/beclab/Olares/cli/internal/files/download"
)

// NewCatCommand: `olares-cli files cat <remote-path>`
//
// Streams the raw bytes of a single remote file to stdout. The wire
// call is GET /api/raw/<encPath>?inline=true (same path the LarePass
// web app uses for text-content previews — `inline=true` only
// affects Content-Disposition, the body is identical).
//
// Like `cat` itself, this is binary-safe: we don't sniff or
// interpret the body, we just copy it through. That means cat-ing a
// huge image will dump the bytes — the user is expected to pipe to
// `less`, `head`, or a similar tool when they care about safety.
//
// We Stat the path before fetching so a directory target produces a
// clear "is a directory" error rather than the server's terser
// "not a file, path: ..." 400.
func NewCatCommand(f *cmdutil.Factory) *cobra.Command {
cmd := &cobra.Command{
Use: "cat <remote-path>",
Short: "stream a remote file's contents to stdout",
Long: `Stream the raw bytes of a single file on the per-user files-backend to stdout.

Equivalent to ` + "`olares-cli files download <remote> -`" + ` if a future
` + "`-`" + ` -means-stdout convention is added — for now ` + "`cat`" + ` is the explicit
verb. The transfer is binary-safe (no buffering, no transformation),
so piping into ` + "`less`" + ` / ` + "`hexdump`" + ` / ` + "`head -c`" + ` works as expected.

Directories produce an error rather than a recursive concatenation
(use ` + "`files download <remote>/`" + ` if you want the contents on disk
first).

Examples:

olares-cli files cat drive/Home/Documents/notes.md
olares-cli files cat drive/Home/Logs/today.log | tail -n 50
`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runCat(cmd.Context(), f, cmd.OutOrStdout(), args[0])
},
}
return cmd
}

func runCat(ctx context.Context, f *cmdutil.Factory, out io.Writer, remoteArg string) error {
if ctx == nil {
ctx = context.Background()
}

fp, err := ParseFrontendPath(remoteArg)
if err != nil {
return err
}

rp, err := f.ResolveProfile(ctx)
if err != nil {
return err
}

httpClient := newUploadHTTPClient(rp.InsecureSkipVerify)
client := &download.Client{
HTTPClient: httpClient,
BaseURL: rp.FilesURL,
AccessToken: rp.AccessToken,
}

plain := strings.TrimSuffix(fp.String(), "/")

// Probe before streaming. Two cheap wins:
// - friendly "is a directory" message for `cat drive/Home/`
// instead of the server's terse 400;
// - 401/403/404 reformatted with the standard CTA before we
// start writing partial data to stdout.
st, err := client.Stat(ctx, plain)
if err != nil {
return reformatHTTPErr(err, rp.OlaresID, "stat", plain)
}
if st.IsDir {
return fmt.Errorf("%s is a directory: cat only works on files (use `olares-cli files ls %s` to list it)",
fp.String(), fp.String())
}

if _, err := client.StreamRaw(ctx, plain, out); err != nil {
return reformatHTTPErr(err, rp.OlaresID, "cat", plain)
}
return nil
}
Loading
Loading