oss
GitHub提供S3兼容对象存储的命令行操作能力,支持列表、读写、复制、移动、删除及生成预签名URL等文件管理功能。
触发场景
安装
npx skills add opskat/opskat --skill oss -g -y
SKILL.md
Frontmatter
{
"name": "oss",
"description": "List, read, upload, copy, move, delete and presign objects in S3-compatible object storage via exec, using a family + verb + target command syntax."
}
OSS assets
OSS assets connect to an S3-compatible object storage endpoint (AWS S3, MinIO, Aliyun OSS,
…). The asset names the endpoint and credentials — the bucket is part of every command's
target. An asset may also carry a default bucket (bucket in its config) for credentials
scoped to one bucket; it never fills in a missing bucket in a target.
Command syntax
<family> <verb> [target] [--flags] — family is bucket or object, verb is the
operation, and target is <bucket>/<key> (no leading slash).
Every example below is literally executable as written. Optional flags are not marked with brackets; each useful combination is spelled out on its own line.
bucket
bucket list
object
object list backupsobject list backups/2026/object list backups/2026/ --max-keys=100object list backups/2026/ --max-keys=100 --after=2026/03/db.sql.gzobject stat backups/2026/db.sql.gzobject get backups/app.conf(returns the content inline, truncated to 64 KiB)object get backups/app.conf --max-bytes=4096object get backups/2026/db.sql.gz --file=/tmp/db.sql.gz(streams to a local file instead)object put backups/2026/db.sql.gz --file=/tmp/db.sql.gzobject put backups/app.conf --file=/tmp/app.conf --content-type=text/plainobject copy backups/2026/db.sql.gz --to=archive/db-2026.sql.gzobject move backups/2026/db.sql.gz --to=archive/db-2026.sql.gzobject delete backups/2026/old.logobject presign backups/2026/db.sql.gzobject presign backups/2026/db.sql.gz --expiry=600object presign uploads/inbox/report.pdf --method=put --expiry=600(denied by the default policy, see Notes)
Flag reference
Every flag each verb accepts — anything else is rejected rather than ignored. Bold flags are required: omitting one does not default it, the command is rejected before it runs. Verbs not listed here take no flags at all.
object list:--max-keys,--afterobject get:--file,--max-bytesobject put:--file,--content-typeobject copy:--toobject move:--toobject presign:--expiry,--method
Flag values
Checked before any approval dialog, so a wrong value costs nothing:
--max-keys,--max-bytesand--expirymust be plain decimal integers:1000, not1,000,1_000,1e3or3.0.--methodisgetorput, lower case exactly. Nothing else is accepted, and there is no case folding:GETis an error rather than a synonym. Omitting it meansget, so a bareobject presignrequestsobject.presign.read.--expirydefaults to 3600 seconds when omitted.--filemust be an absolute local path. The approved command string is all that identifies the file, and a relative path resolves against a working directory that is not part of it, so it would land somewhere unpredictable.--fileand--max-bytescannot be given together: they select two different behaviors ofobject get(stream the whole object to disk, or return the first bytes inline), so one of them would have to be ignored.--max-bytesdefaults to 64 KiB and is capped at 1 MiB. A larger value is reduced to the cap rather than rejected, andtruncatedthen reports that the object was longer.--max-keysis capped at 1000. A larger value is rejected, not reduced: silently returning fewer keys than asked would break--afterpagination against what the caller expected to page through.--expiryis capped at 604800 seconds (7 days) and is also rejected rather than reduced: a longer-lived presigned URL is what S3-compatible backends refuse to sign in the first place, so a value over the cap would otherwise be approved and then fail.--tois a second<bucket>/<key>and follows the same rules as the target, including the requirement that it name a single object.
Not checked on this side at all — the storage service is the only thing that will reject a wrong value, after approval and a round trip:
--content-typeis passed through verbatim as the object's MIME type; omitting it lets the service decide.--afteris a continuation cursor, not a filter: pass back thenextContinuationTokenfrom the previousobject listresult to read the next page.
Results
bucket listreturns{"buckets":[…]}. On an asset with a default bucket it returns that one bucket without asking the storage service, since a bucket-scoped credential is not allowed to list the account's buckets.object listreturns the raw listing:prefixes(one level of "folders"),objects, plusisTruncatedandnextContinuationTokenfor paging.object statreturns the object's metadata.object getwithout--filereturnscontenttogether withencoding(utf-8, orbase64when the bytes are not valid UTF-8) andtruncated. Whentruncatedis true you read only the first bytes — re-run with--filerather than guessing at the rest.object get --filereturns the byte count, the object and the local file path;object putreturns the byte count and the object (bucket/key), with nofile. The content never passes through the conversation either way.object copy,object moveandobject deletereturn{"status":"ok",…}.object presignreturns the signedurl, itsmethodandexpiresInseconds. The URL is fully usable. Under raw-by-default Audit the complete signed URL — signature parameters included — enters the Audit result unchanged, subject only to the existing result capture/truncation; nothing is redacted, so treat the URL as a capability that grants access until it expires.
Approval and policy rules
Approval is granted per <action> <resource>, where resource is <bucket>/<key>:
| command | permission(s) requested |
|---|---|
bucket list |
bucket.list * |
object list B/P |
object.list B/P |
object stat B/K, object get B/K |
object.read B/K |
object get B/K --file=P |
object.read B/K and a local-write check on P (the same gate cp uses when writing to this machine) |
object put B/K |
object.write B/K |
object copy S --to=D |
object.read S and object.write D |
object move S --to=D |
object.read S, object.write D and object.delete S |
object delete B/K |
object.delete B/K |
object presign B/K --method=get |
object.presign.read B/K |
object presign B/K --method=put |
object.presign.write B/K |
copy and move request every one of their resources: any one of them being denied
rejects the whole command, and where an allow list exists all of them must match.
Rules are written the same way, with * wildcards that do not cross / inside a key:
| rule | meaning |
|---|---|
* |
everything |
object.read mybucket or object.read mybucket/ |
every object in the bucket, at any depth |
object.read mybucket/logs/ |
everything under the logs/ prefix, at any depth |
object.read mybucket/logs/*.gz |
only *.gz directly under logs/ |
object.* mybucket/ |
any object operation on that bucket |
object.presign.* * |
any presigned URL on any bucket |
Notes
- Presigned PUT URLs are denied by the default policy and cannot be enabled by an
allow rule: a signed upload URL moves write access outside this app entirely — anyone
holding it can write that key until it expires, with no further policy, approval or
audit. Use
object putinstead — no asset or group policy change can enable it, because the deny is a floor merged into every effective policy. - Object keys may contain spaces; quote the whole target when they do, e.g.
object stat 'mybucket/My Report.pdf'. Leading or trailing whitespace is rejected — permission rules split<action> <resource>at the first whitespace, so a padded name could never be authorized. - Every
objectverb takes exactly one target and it must name a real key:object stat mybucketis rejected, becausemybucketas a rule would authorize the whole bucket rather than one object.object listis the exception — listing is always by prefix, so a bare bucket name is normalized tomybucket/. - A trailing
/names the zero-byte "folder marker" object, soobject delete mybucket/logs/deletes exactly that marker. Because the same string read as a rule means the whole prefix, such an approval is never turned into a standing grant: repeating it asks again.object listis the exception — a listing approval onmybucket/logs/does become a standing grant, because there the command and the rule cover the same range. - Targets must not start with
/or--, and the bucket is always part of the target: an asset's default bucket is not implied, soobject listwith no target is still an error. object copyandobject moveare single-object and server-side; both endpoints must be on this same asset. Use thecptool to move data between different assets or between object storage and a server, and to copy whole prefixes.- Unknown flags are rejected rather than ignored, so a typo such as
--max_keys=100fails instead of silently listing the default page size. - The command line is shell-tokenized but never shell-executed:
$,|,>and&produce an error rather than expanding or redirecting. - The
scopeparameter is not used by OSS assets; the target names bucket and key.
Asset config (for put_asset)
| field | type | required | notes |
|---|---|---|---|
endpoint |
string | yes | Host, or scheme://host[:port] |
access_key_id |
string | yes | |
secret_access_key |
string | no | Write-only. Encrypted in the asset; does not create a credential |
credential_id |
number | no | Existing managed password credential ID; mutually exclusive with secret_access_key |
provider |
string | no | UI provider preset label (e.g. "s3", "minio"); does not change connection behavior |
region |
string | no | |
bucket |
string | no | Default bucket, a bare bucket name (no /). When set, the asset is limited to this bucket in the desktop browser and in bucket list, and the connection test probes it instead of listing buckets. Set it for credentials scoped to one bucket (e.g. Cloudflare R2 object tokens); "" clears it |
use_path_style |
bool | no | true to force path-style addressing (needed by most self-hosted S3-compatible services) |
use_ssl |
bool | no | true to connect over HTTPS |
connect_timeout |
number | no | Seconds; 0 uses the default |
Plaintext is never returned, is encrypted in the asset, and never creates a managed credential.
Example:
put_asset(name="backups-bucket", type="oss", config={"endpoint":"s3.us-east-1.amazonaws.com","region":"us-east-1","access_key_id":"AKIAEXAMPLE","secret_access_key":"...","use_ssl":"true"})
版本历史
-
2b9abfb
当前 2026-10-19 03:58
新增默认桶配置支持,优化无ListBuckets权限时的体验;修复Cloudflare R2等服务的文件夹路径编码解码问题。
- b159e91 2026-08-19 20:21
-
d6c7a6e
2026-08-16 07:29
修正文档契约漂移:明确presign PUT受内置deny组限制,object list前缀审批机制,oss get --file需过本地写入门禁,presign默认method=get且expiry=3600,put返回无file字段等实现细节。
- aeb4bc3 2026-07-24 16:28


