oss
GitHub提供S3兼容对象存储的CLI操作技能,支持通过命令语法列出、读取、上传、复制、移动、删除及生成预签名URL等文件管理任务。
Trigger Scenarios
Install
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 only — the bucket is part of every command's target, not part of the asset.
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":[…]}.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; the audit log stores it with the signature parameters redacted.
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 part of the target rather than of the asset — there is no per-asset default bucket. 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 | Stored encrypted; never echoed back |
credential_id |
number | no | Reference to a managed credential instead of an inline secret_access_key; 0 means none |
provider |
string | no | UI provider preset label (e.g. "s3", "minio"); does not change connection behavior |
region |
string | no | |
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 |
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"})
Version History
-
d6c7a6e
Current 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


