@crabcloud/cli 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/crab.js
CHANGED
|
@@ -2390,6 +2390,89 @@ var $ZodUnion = /* @__PURE__ */ $constructor("$ZodUnion", (inst, def) => {
|
|
|
2390
2390
|
});
|
|
2391
2391
|
};
|
|
2392
2392
|
});
|
|
2393
|
+
function discriminatorMap(def) {
|
|
2394
|
+
const map = new Map;
|
|
2395
|
+
for (const option of def.options) {
|
|
2396
|
+
const values = option._zod.propValues?.[def.discriminator];
|
|
2397
|
+
if (!values || values.size === 0)
|
|
2398
|
+
throw new Error(`Invalid discriminated union option at index "${def.options.indexOf(option)}"`);
|
|
2399
|
+
for (const value of values) {
|
|
2400
|
+
if (map.has(value)) {
|
|
2401
|
+
if (value !== undefined)
|
|
2402
|
+
throw new Error(`Duplicate discriminator value "${String(value)}"`);
|
|
2403
|
+
map.set(value, null);
|
|
2404
|
+
} else {
|
|
2405
|
+
map.set(value, option);
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
return map;
|
|
2410
|
+
}
|
|
2411
|
+
var $ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("$ZodDiscriminatedUnion", (inst, def) => {
|
|
2412
|
+
def.inclusive = false;
|
|
2413
|
+
$ZodUnion.init(inst, def);
|
|
2414
|
+
const _super = inst._zod.parse;
|
|
2415
|
+
defineLazyInternal(inst, "propValues", (zod) => {
|
|
2416
|
+
const propValues = {};
|
|
2417
|
+
let undefinedCount = 0;
|
|
2418
|
+
for (const option of zod.def.options) {
|
|
2419
|
+
const pv = option._zod.propValues;
|
|
2420
|
+
if (!pv || Object.keys(pv).length === 0)
|
|
2421
|
+
throw new Error(`Invalid discriminated union option at index "${zod.def.options.indexOf(option)}"`);
|
|
2422
|
+
if (pv[zod.def.discriminator]?.has(undefined))
|
|
2423
|
+
undefinedCount++;
|
|
2424
|
+
for (const [k, v] of Object.entries(pv)) {
|
|
2425
|
+
if (!Object.prototype.hasOwnProperty.call(propValues, k)) {
|
|
2426
|
+
assignProp(propValues, k, new Set);
|
|
2427
|
+
}
|
|
2428
|
+
for (const val of v) {
|
|
2429
|
+
propValues[k].add(val);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
if (!zod.def.unionFallback && undefinedCount > 1)
|
|
2434
|
+
propValues[zod.def.discriminator]?.delete(undefined);
|
|
2435
|
+
return propValues;
|
|
2436
|
+
});
|
|
2437
|
+
def.options.forEach((option, i) => {
|
|
2438
|
+
const propShape = rawShape(option._zod.def);
|
|
2439
|
+
if (propShape && !Object.prototype.hasOwnProperty.call(propShape, def.discriminator)) {
|
|
2440
|
+
throw new Error(`Invalid discriminated union option at index "${i}"`);
|
|
2441
|
+
}
|
|
2442
|
+
});
|
|
2443
|
+
const disc = cached(() => discriminatorMap(def));
|
|
2444
|
+
inst._zod.parse = (payload, ctx) => {
|
|
2445
|
+
const input = payload.value;
|
|
2446
|
+
if (!isObject(input)) {
|
|
2447
|
+
payload.issues.push({
|
|
2448
|
+
code: "invalid_type",
|
|
2449
|
+
expected: "object",
|
|
2450
|
+
input,
|
|
2451
|
+
inst
|
|
2452
|
+
});
|
|
2453
|
+
return payload;
|
|
2454
|
+
}
|
|
2455
|
+
const value = input?.[def.discriminator];
|
|
2456
|
+
const opt = disc.value.get(value);
|
|
2457
|
+
if (opt && (value !== undefined || ctx.direction !== "backward")) {
|
|
2458
|
+
return opt._zod.run(payload, ctx);
|
|
2459
|
+
}
|
|
2460
|
+
if (def.unionFallback || ctx.direction === "backward") {
|
|
2461
|
+
return _super(payload, ctx);
|
|
2462
|
+
}
|
|
2463
|
+
payload.issues.push({
|
|
2464
|
+
code: "invalid_union",
|
|
2465
|
+
errors: [],
|
|
2466
|
+
note: "No matching discriminator",
|
|
2467
|
+
discriminator: def.discriminator,
|
|
2468
|
+
options: Array.from(disc.value.keys()).filter((value) => disc.value.get(value) !== null),
|
|
2469
|
+
input,
|
|
2470
|
+
path: [def.discriminator],
|
|
2471
|
+
inst
|
|
2472
|
+
});
|
|
2473
|
+
return payload;
|
|
2474
|
+
};
|
|
2475
|
+
});
|
|
2393
2476
|
var $ZodIntersection = /* @__PURE__ */ $constructor("$ZodIntersection", (inst, def) => {
|
|
2394
2477
|
$ZodType.init(inst, def);
|
|
2395
2478
|
inst._zod.parse = (payload, ctx) => {
|
|
@@ -2682,6 +2765,28 @@ var $ZodEnum = /* @__PURE__ */ $constructor("$ZodEnum", (inst, def) => {
|
|
|
2682
2765
|
return payload;
|
|
2683
2766
|
};
|
|
2684
2767
|
});
|
|
2768
|
+
var $ZodLiteral = /* @__PURE__ */ $constructor("$ZodLiteral", (inst, def) => {
|
|
2769
|
+
$ZodType.init(inst, def);
|
|
2770
|
+
const values = new Set(def.values);
|
|
2771
|
+
inst._zod.values = values;
|
|
2772
|
+
defineLazyInternal(inst, "pattern", (zod) => {
|
|
2773
|
+
const vals = zod.def.values;
|
|
2774
|
+
return new RegExp(vals.length ? `^(${vals.map((o) => typeof o === "string" ? escapeRegex(o) : o ? escapeRegex(o.toString()) : String(o)).join("|")})$` : "^[^\\s\\S]$");
|
|
2775
|
+
});
|
|
2776
|
+
inst._zod.parse = (payload, _ctx) => {
|
|
2777
|
+
const input = payload.value;
|
|
2778
|
+
if (values.has(input)) {
|
|
2779
|
+
return payload;
|
|
2780
|
+
}
|
|
2781
|
+
payload.issues.push({
|
|
2782
|
+
code: "invalid_value",
|
|
2783
|
+
values: def.values,
|
|
2784
|
+
input,
|
|
2785
|
+
inst
|
|
2786
|
+
});
|
|
2787
|
+
return payload;
|
|
2788
|
+
};
|
|
2789
|
+
});
|
|
2685
2790
|
var $ZodTransform = /* @__PURE__ */ $constructor("$ZodTransform", (inst, def) => {
|
|
2686
2791
|
$ZodType.init(inst, def);
|
|
2687
2792
|
inst._zod.optin = "optional";
|
|
@@ -4587,6 +4692,45 @@ var enumProcessor = (schema, _ctx, json, _params) => {
|
|
|
4587
4692
|
json.type = "string";
|
|
4588
4693
|
json.enum = values;
|
|
4589
4694
|
};
|
|
4695
|
+
var literalProcessor = (schema, ctx, json, params) => {
|
|
4696
|
+
const def = schema._zod.def;
|
|
4697
|
+
if (def.values.length === 0) {
|
|
4698
|
+
json.not = {};
|
|
4699
|
+
return;
|
|
4700
|
+
}
|
|
4701
|
+
const vals = [];
|
|
4702
|
+
for (const val of def.values) {
|
|
4703
|
+
if (val === undefined) {
|
|
4704
|
+
if (handleUnrepresentable(schema, ctx, json, params, "Literal `undefined` cannot be represented in JSON Schema"))
|
|
4705
|
+
return;
|
|
4706
|
+
} else if (typeof val === "bigint") {
|
|
4707
|
+
if (handleUnrepresentable(schema, ctx, json, params, "BigInt literals cannot be represented in JSON Schema"))
|
|
4708
|
+
return;
|
|
4709
|
+
vals.push(Number(val));
|
|
4710
|
+
} else {
|
|
4711
|
+
vals.push(val);
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
if (vals.length === 0) {} else if (vals.length === 1) {
|
|
4715
|
+
const val = vals[0];
|
|
4716
|
+
json.type = val === null ? "null" : typeof val;
|
|
4717
|
+
if (ctx.target === "draft-04" || ctx.target === "openapi-3.0") {
|
|
4718
|
+
json.enum = [val];
|
|
4719
|
+
} else {
|
|
4720
|
+
json.const = val;
|
|
4721
|
+
}
|
|
4722
|
+
} else {
|
|
4723
|
+
if (vals.every((v) => typeof v === "number"))
|
|
4724
|
+
json.type = "number";
|
|
4725
|
+
if (vals.every((v) => typeof v === "string"))
|
|
4726
|
+
json.type = "string";
|
|
4727
|
+
if (vals.every((v) => typeof v === "boolean"))
|
|
4728
|
+
json.type = "boolean";
|
|
4729
|
+
if (vals.every((v) => v === null))
|
|
4730
|
+
json.type = "null";
|
|
4731
|
+
json.enum = vals;
|
|
4732
|
+
}
|
|
4733
|
+
};
|
|
4590
4734
|
var customProcessor = (schema, ctx, json, params) => {
|
|
4591
4735
|
handleUnrepresentable(schema, ctx, json, params, "Custom types cannot be represented in JSON Schema");
|
|
4592
4736
|
};
|
|
@@ -5618,6 +5762,18 @@ function union(options, params) {
|
|
|
5618
5762
|
...normalizeParams(params)
|
|
5619
5763
|
});
|
|
5620
5764
|
}
|
|
5765
|
+
var ZodDiscriminatedUnion = /* @__PURE__ */ $constructor("ZodDiscriminatedUnion", (inst, def) => {
|
|
5766
|
+
ZodUnion.init(inst, def);
|
|
5767
|
+
$ZodDiscriminatedUnion.init(inst, def);
|
|
5768
|
+
});
|
|
5769
|
+
function discriminatedUnion(discriminator, options, params) {
|
|
5770
|
+
return new ZodDiscriminatedUnion({
|
|
5771
|
+
type: "union",
|
|
5772
|
+
options,
|
|
5773
|
+
discriminator,
|
|
5774
|
+
...normalizeParams(params)
|
|
5775
|
+
});
|
|
5776
|
+
}
|
|
5621
5777
|
var ZodIntersection = /* @__PURE__ */ $constructor("ZodIntersection", (inst, def) => {
|
|
5622
5778
|
$ZodIntersection.init(inst, def);
|
|
5623
5779
|
ZodType.init(inst, def);
|
|
@@ -5700,6 +5856,27 @@ function _enum(values, params) {
|
|
|
5700
5856
|
...normalizeParams(params)
|
|
5701
5857
|
});
|
|
5702
5858
|
}
|
|
5859
|
+
var ZodLiteral = /* @__PURE__ */ $constructor("ZodLiteral", (inst, def) => {
|
|
5860
|
+
$ZodLiteral.init(inst, def);
|
|
5861
|
+
ZodType.init(inst, def);
|
|
5862
|
+
inst._zod.processJSONSchema = (ctx, json, params) => literalProcessor(inst, ctx, json, params);
|
|
5863
|
+
inst.values = new Set(def.values);
|
|
5864
|
+
Object.defineProperty(inst, "value", {
|
|
5865
|
+
get() {
|
|
5866
|
+
if (def.values.length > 1) {
|
|
5867
|
+
throw new Error("This schema contains multiple valid literal values. Use `.values` instead.");
|
|
5868
|
+
}
|
|
5869
|
+
return def.values[0];
|
|
5870
|
+
}
|
|
5871
|
+
});
|
|
5872
|
+
});
|
|
5873
|
+
function literal(value, params) {
|
|
5874
|
+
return new ZodLiteral({
|
|
5875
|
+
type: "literal",
|
|
5876
|
+
values: Array.isArray(value) ? value : [value],
|
|
5877
|
+
...normalizeParams(params)
|
|
5878
|
+
});
|
|
5879
|
+
}
|
|
5703
5880
|
var ZodTransform = /* @__PURE__ */ $constructor("ZodTransform", (inst, def) => {
|
|
5704
5881
|
_ensureDefaultMemoizer();
|
|
5705
5882
|
$ZodTransform.init(inst, def);
|
|
@@ -5916,14 +6093,16 @@ var scopes = [
|
|
|
5916
6093
|
"contacts.read",
|
|
5917
6094
|
"contacts.write",
|
|
5918
6095
|
"collab.read",
|
|
5919
|
-
"collab.write"
|
|
6096
|
+
"collab.write",
|
|
6097
|
+
"agent.read"
|
|
5920
6098
|
];
|
|
5921
6099
|
var scopeGroups = [
|
|
5922
6100
|
{ id: "account", scopes: ["account.read", "credits.read"] },
|
|
5923
6101
|
{ id: "mail", scopes: ["mail.read", "mail.send", "mail.search", "mail.delete"] },
|
|
5924
6102
|
{ id: "storage", scopes: ["storage.read", "storage.write", "storage.delete"] },
|
|
5925
6103
|
{ id: "collab", scopes: ["collab.read", "collab.write"] },
|
|
5926
|
-
{ id: "contacts", scopes: ["contacts.read", "contacts.write"] }
|
|
6104
|
+
{ id: "contacts", scopes: ["contacts.read", "contacts.write"] },
|
|
6105
|
+
{ id: "agent", scopes: ["agent.read"] }
|
|
5927
6106
|
];
|
|
5928
6107
|
function expandScopes(input) {
|
|
5929
6108
|
const expanded = input.flatMap((item) => {
|
|
@@ -6229,6 +6408,123 @@ var collabRecordListQuery = object({
|
|
|
6229
6408
|
cursor: string2().trim().max(200).optional(),
|
|
6230
6409
|
limit: exports_coerce.number().int().min(1).max(200).optional()
|
|
6231
6410
|
});
|
|
6411
|
+
var agentPermissionModes = ["confirm", "auto_edit", "yolo"];
|
|
6412
|
+
var agentSessionKinds = ["chat", "task"];
|
|
6413
|
+
var agentMemoryTypes = ["preference", "contact", "project"];
|
|
6414
|
+
var AGENT_TITLE_MAX = 120;
|
|
6415
|
+
var AGENT_MEMORY_MAX = 2000;
|
|
6416
|
+
var AGENT_PERSONA_MAX = 2000;
|
|
6417
|
+
var agentToolsConfigSchema = object({
|
|
6418
|
+
web: boolean2().optional(),
|
|
6419
|
+
code: boolean2().optional(),
|
|
6420
|
+
workspace: boolean2().optional(),
|
|
6421
|
+
crab: object({
|
|
6422
|
+
credits: boolean2().optional(),
|
|
6423
|
+
mail: boolean2().optional(),
|
|
6424
|
+
storage: boolean2().optional(),
|
|
6425
|
+
collab: boolean2().optional(),
|
|
6426
|
+
contacts: boolean2().optional()
|
|
6427
|
+
}).optional()
|
|
6428
|
+
});
|
|
6429
|
+
var agentConfigUpdateInput = object({
|
|
6430
|
+
activeModelId: string2().trim().min(1).max(64).optional(),
|
|
6431
|
+
persona: string2().max(AGENT_PERSONA_MAX).optional(),
|
|
6432
|
+
mode: _enum(agentPermissionModes).optional(),
|
|
6433
|
+
tools: agentToolsConfigSchema.optional()
|
|
6434
|
+
});
|
|
6435
|
+
var AGENT_PROVIDER_LABEL_MAX = 60;
|
|
6436
|
+
var agentProviderInput = object({
|
|
6437
|
+
label: string2().trim().min(1).max(AGENT_PROVIDER_LABEL_MAX),
|
|
6438
|
+
baseUrl: string2().trim().min(1).max(500),
|
|
6439
|
+
apiKey: string2().trim().min(1).max(500)
|
|
6440
|
+
});
|
|
6441
|
+
var agentProviderModelInput = object({
|
|
6442
|
+
model: string2().trim().min(1).max(200),
|
|
6443
|
+
contextWindow: number2().int().min(1000).max(1e8).nullable().optional(),
|
|
6444
|
+
maxOutput: number2().int().min(100).max(1e7).nullable().optional(),
|
|
6445
|
+
inputText: boolean2().optional(),
|
|
6446
|
+
inputImage: boolean2().optional(),
|
|
6447
|
+
inputVideo: boolean2().optional()
|
|
6448
|
+
});
|
|
6449
|
+
var agentSessionCreateInput = object({
|
|
6450
|
+
kind: _enum(agentSessionKinds).default("chat"),
|
|
6451
|
+
title: string2().trim().max(AGENT_TITLE_MAX).optional(),
|
|
6452
|
+
repeatCron: string2().trim().max(100).optional(),
|
|
6453
|
+
notifyMail: boolean2().optional()
|
|
6454
|
+
}).refine((v) => v.kind === "chat" || !!v.title && v.title.length > 0, {
|
|
6455
|
+
message: "task requires title"
|
|
6456
|
+
});
|
|
6457
|
+
var agentSessionRenameInput = object({ title: string2().trim().min(1).max(AGENT_TITLE_MAX) });
|
|
6458
|
+
var agentSessionListQuery = object({
|
|
6459
|
+
kind: _enum(agentSessionKinds).optional(),
|
|
6460
|
+
limit: exports_coerce.number().int().min(1).max(200).optional()
|
|
6461
|
+
});
|
|
6462
|
+
var agentMemoryCreateInput = object({
|
|
6463
|
+
type: _enum(agentMemoryTypes),
|
|
6464
|
+
content: string2().trim().min(1).max(AGENT_MEMORY_MAX),
|
|
6465
|
+
sessionId: string2().max(64).optional()
|
|
6466
|
+
});
|
|
6467
|
+
var agentTraceStatuses = ["running", "ok", "error", "pending", "approved", "rejected"];
|
|
6468
|
+
var agentPlanItemStates = ["done", "current", "todo"];
|
|
6469
|
+
var agentTraceCommon = {
|
|
6470
|
+
id: string2().min(1).max(64),
|
|
6471
|
+
status: _enum(agentTraceStatuses),
|
|
6472
|
+
round: number2().int().min(1),
|
|
6473
|
+
title: string2().max(200),
|
|
6474
|
+
durationMs: number2().int().min(0).optional()
|
|
6475
|
+
};
|
|
6476
|
+
var agentPlanTraceSchema = object({
|
|
6477
|
+
...agentTraceCommon,
|
|
6478
|
+
kind: literal("plan"),
|
|
6479
|
+
items: array(object({ text: string2().max(500), state: _enum(agentPlanItemStates) })).max(50)
|
|
6480
|
+
});
|
|
6481
|
+
var agentWebTraceSchema = object({
|
|
6482
|
+
...agentTraceCommon,
|
|
6483
|
+
kind: literal("web"),
|
|
6484
|
+
url: string2().max(2000),
|
|
6485
|
+
summary: string2().max(8000)
|
|
6486
|
+
});
|
|
6487
|
+
var agentArtifactSchema = object({
|
|
6488
|
+
name: string2().max(200),
|
|
6489
|
+
mime: string2().max(100),
|
|
6490
|
+
path: string2().max(500),
|
|
6491
|
+
rows: number2().int().min(0).optional()
|
|
6492
|
+
});
|
|
6493
|
+
var agentCodeTraceSchema = object({
|
|
6494
|
+
...agentTraceCommon,
|
|
6495
|
+
kind: literal("code"),
|
|
6496
|
+
code: string2().max(50000).optional(),
|
|
6497
|
+
result: string2().max(20000).optional(),
|
|
6498
|
+
artifact: agentArtifactSchema.optional()
|
|
6499
|
+
});
|
|
6500
|
+
var agentCrabTraceSchema = object({
|
|
6501
|
+
...agentTraceCommon,
|
|
6502
|
+
kind: literal("crab"),
|
|
6503
|
+
command: string2().max(2000),
|
|
6504
|
+
response: string2().max(8000).optional()
|
|
6505
|
+
});
|
|
6506
|
+
var agentApprovalTraceSchema = object({
|
|
6507
|
+
...agentTraceCommon,
|
|
6508
|
+
kind: literal("approval"),
|
|
6509
|
+
tool: string2().max(64),
|
|
6510
|
+
action: string2().max(200),
|
|
6511
|
+
detail: string2().max(8000)
|
|
6512
|
+
});
|
|
6513
|
+
var agentTraceSchema = discriminatedUnion("kind", [
|
|
6514
|
+
agentPlanTraceSchema,
|
|
6515
|
+
agentWebTraceSchema,
|
|
6516
|
+
agentCodeTraceSchema,
|
|
6517
|
+
agentCrabTraceSchema,
|
|
6518
|
+
agentApprovalTraceSchema
|
|
6519
|
+
]);
|
|
6520
|
+
var agentMemoryChipSchema = object({
|
|
6521
|
+
text: string2().max(200),
|
|
6522
|
+
memoryId: string2().max(64).optional()
|
|
6523
|
+
});
|
|
6524
|
+
var agentCompressionChipSchema = object({
|
|
6525
|
+
foldedRounds: number2().int().min(1),
|
|
6526
|
+
summary: string2().max(4000)
|
|
6527
|
+
});
|
|
6232
6528
|
|
|
6233
6529
|
// ../../packages/cloudkit/src/crypto.ts
|
|
6234
6530
|
var enc = new TextEncoder;
|
|
@@ -6598,13 +6894,15 @@ var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
|
6598
6894
|
contacts invite [--reset] [--json] 我的邀请码、链接与统计(--reset 重置,旧链接失效)
|
|
6599
6895
|
contacts invitees [--offset N] [--json]
|
|
6600
6896
|
我邀请的人(直接下级 + 间接计数)
|
|
6601
|
-
storage ls [--q 关键字] [--source files|media|mail] [--category <id|none>]
|
|
6897
|
+
storage ls [--q 关键字] [--source files|media|mail|collab|agent] [--category <id|none>]
|
|
6602
6898
|
[--type doc|image|video|archive|code] [--tag a,b] [--trash]
|
|
6603
|
-
[--shared] [--recent] [--message-id <id>]
|
|
6604
|
-
|
|
6899
|
+
[--shared] [--recent] [--encrypted only|plain] [--message-id <id>]
|
|
6900
|
+
[--limit N] [--offset N] [--json]
|
|
6901
|
+
列出云盘对象(跨来源统一视图,能力与 Web 云盘一致;
|
|
6902
|
+
--encrypted only=仅保险箱、plain=排除保险箱)
|
|
6605
6903
|
storage upload <file...> [--json] 上传一个或多个文件(直传优先,自动回退中转);
|
|
6606
|
-
|
|
6607
|
-
CRAB_PASSPHRASE
|
|
6904
|
+
默认明文上传(2026-10 基础服务化);--passphrase /
|
|
6905
|
+
CRAB_PASSPHRASE 提供时端到端加密(保险箱文件)
|
|
6608
6906
|
storage download <id|文件名> [-o 输出路径]
|
|
6609
6907
|
下载对象内容(默认按原文件名存到当前目录;
|
|
6610
6908
|
加密对象需口令,终端内运行时交互输入)
|
|
@@ -6617,7 +6915,7 @@ var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
|
6617
6915
|
公开 / 收回公开(公开时打印公开直链)
|
|
6618
6916
|
storage rm|restore|purge <id|文件名>
|
|
6619
6917
|
回收站 / 恢复 / 彻底删除(purge 不可恢复)
|
|
6620
|
-
storage quota [--json]
|
|
6918
|
+
storage quota [--json] 存储水位(统一一本账:五来源分项 + 保险箱计数)
|
|
6621
6919
|
storage share <id|文件名> [--expires 7|30|90|forever] [--passcode 提取码]
|
|
6622
6920
|
创建分享链接(加密文件链接自带解密钥匙 #fragment)
|
|
6623
6921
|
storage key [--remember|--forget]
|
|
@@ -7122,8 +7420,8 @@ async function mailSend() {
|
|
|
7122
7420
|
const hit = await resolveStorageObject(credentials, token);
|
|
7123
7421
|
if (typeof hit === "number")
|
|
7124
7422
|
return hit;
|
|
7125
|
-
if (typeof hit === "object" && hit.isEncrypted) {
|
|
7126
|
-
console.error(`「${token}
|
|
7423
|
+
if (typeof hit === "object" && hit.isEncrypted && hit.wrappedDataKey?.startsWith("u1.")) {
|
|
7424
|
+
console.error(`「${token}」是保险箱文件(端到端加密),不能作为邮件附件发送。`);
|
|
7127
7425
|
return EXIT_USAGE;
|
|
7128
7426
|
}
|
|
7129
7427
|
const id = typeof hit === "object" ? hit.id : hit;
|
|
@@ -7392,7 +7690,7 @@ async function contactsInvitees() {
|
|
|
7392
7690
|
console.log(`(还有更多:contacts invitees --offset ${page.nextOffset})`);
|
|
7393
7691
|
return 0;
|
|
7394
7692
|
}
|
|
7395
|
-
var STORAGE_SOURCES = ["files", "media", "mail"];
|
|
7693
|
+
var STORAGE_SOURCES = ["files", "media", "mail", "collab", "agent"];
|
|
7396
7694
|
var STORAGE_TYPES = ["doc", "image", "video", "archive", "code"];
|
|
7397
7695
|
var MIME_BY_EXT = {
|
|
7398
7696
|
png: "image/png",
|
|
@@ -7429,9 +7727,10 @@ function formatBytesCli(n) {
|
|
|
7429
7727
|
return `${n} B`;
|
|
7430
7728
|
}
|
|
7431
7729
|
function printStorageRow(o) {
|
|
7730
|
+
const encFlag = o.isEncrypted ? o.wrappedDataKey?.startsWith("u1.") ? "保险箱" : "平台加密" : null;
|
|
7432
7731
|
const flags = [
|
|
7433
|
-
o.source !== "files" ? o.source === "media" ? "素材" : "邮件附件" : null,
|
|
7434
|
-
|
|
7732
|
+
o.source !== "files" ? o.source === "media" ? "素材" : o.source === "mail" ? "邮件附件" : o.source === "collab" ? "协作" : "Agent 产物" : null,
|
|
7733
|
+
encFlag,
|
|
7435
7734
|
o.isPublic ? "公开" : null
|
|
7436
7735
|
].filter(Boolean);
|
|
7437
7736
|
console.log(`${o.id} ${o.filename} ${formatBytesCli(o.sizeBytes)} ${formatTime(o.createdAt)}${flags.length > 0 ? ` [${flags.join(" · ")}]` : ""}`);
|
|
@@ -7501,6 +7800,15 @@ async function storageList() {
|
|
|
7501
7800
|
const tags = readFlag("--tag");
|
|
7502
7801
|
if (tags)
|
|
7503
7802
|
params.set("tags", tags);
|
|
7803
|
+
const encrypted = readFlag("--encrypted");
|
|
7804
|
+
if (encrypted) {
|
|
7805
|
+
if (encrypted !== "only" && encrypted !== "plain") {
|
|
7806
|
+
console.error(`未知 encrypted 过滤:${encrypted}
|
|
7807
|
+
可用:only(仅保险箱)、plain(排除保险箱)`);
|
|
7808
|
+
return EXIT_USAGE;
|
|
7809
|
+
}
|
|
7810
|
+
params.set("encrypted", encrypted);
|
|
7811
|
+
}
|
|
7504
7812
|
if (hasFlag("--trash"))
|
|
7505
7813
|
params.set("trash", "1");
|
|
7506
7814
|
if (hasFlag("--shared"))
|
|
@@ -7759,7 +8067,7 @@ async function driveMasterKey(credentials) {
|
|
|
7759
8067
|
return raw;
|
|
7760
8068
|
return importAesKey(raw);
|
|
7761
8069
|
}
|
|
7762
|
-
async function storageUploadOne(credentials, filePath) {
|
|
8070
|
+
async function storageUploadOne(credentials, filePath, encrypt) {
|
|
7763
8071
|
let buf;
|
|
7764
8072
|
try {
|
|
7765
8073
|
buf = readFileSync2(filePath);
|
|
@@ -7769,22 +8077,26 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7769
8077
|
}
|
|
7770
8078
|
const filename = basename(filePath);
|
|
7771
8079
|
const mimeType = mimeFor(filename);
|
|
7772
|
-
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
7776
|
-
|
|
7777
|
-
|
|
7778
|
-
|
|
7779
|
-
|
|
8080
|
+
let body = new Uint8Array(buf);
|
|
8081
|
+
let wrappedDataKey = null;
|
|
8082
|
+
if (encrypt) {
|
|
8083
|
+
const master = await driveMasterKey(credentials);
|
|
8084
|
+
if (typeof master === "number")
|
|
8085
|
+
return master;
|
|
8086
|
+
const dataKey = randomKeyBytes(32);
|
|
8087
|
+
const sealed = await aeadEncrypt(await importAesKey(dataKey), body, DATA_AAD);
|
|
8088
|
+
body = new Uint8Array([...sealed.iv, ...sealed.ciphertext]);
|
|
8089
|
+
const wrapped = await aeadEncrypt(master, dataKey, DATA_AAD);
|
|
8090
|
+
wrappedDataKey = `u1.${sealToB64url({ iv: wrapped.iv, ciphertext: wrapped.ciphertext })}`;
|
|
8091
|
+
}
|
|
7780
8092
|
const presign = await api2(credentials.apiBase, "/v1/storage/presign", {
|
|
7781
8093
|
method: "POST",
|
|
7782
8094
|
headers: authHeaders(credentials),
|
|
7783
|
-
body: JSON.stringify({ filename, mimeType, sizeBytes:
|
|
8095
|
+
body: JSON.stringify({ filename, mimeType, sizeBytes: body.byteLength, enc: wrappedDataKey !== null })
|
|
7784
8096
|
});
|
|
7785
8097
|
if (presign.status === 200 || presign.status === 201) {
|
|
7786
8098
|
const { fileId, uploadUrl } = presign.body.data;
|
|
7787
|
-
const put = await fetch(uploadUrl, { method: "PUT", body
|
|
8099
|
+
const put = await fetch(uploadUrl, { method: "PUT", body, headers: { "content-type": mimeType } });
|
|
7788
8100
|
if (!put.ok) {
|
|
7789
8101
|
console.error(`直传失败(${put.status}):${filename}`);
|
|
7790
8102
|
return 1;
|
|
@@ -7792,7 +8104,7 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7792
8104
|
const complete = await api2(credentials.apiBase, `/v1/storage/${encodeURIComponent(fileId)}/complete`, {
|
|
7793
8105
|
method: "POST",
|
|
7794
8106
|
headers: authHeaders(credentials),
|
|
7795
|
-
body: JSON.stringify({ isEncrypted: true, wrappedDataKey })
|
|
8107
|
+
body: JSON.stringify(wrappedDataKey ? { isEncrypted: true, wrappedDataKey } : {})
|
|
7796
8108
|
});
|
|
7797
8109
|
if (complete.status !== 200 && complete.status !== 201) {
|
|
7798
8110
|
console.error(errorMessage(complete.body, `记账失败:${filename}`));
|
|
@@ -7802,28 +8114,30 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7802
8114
|
if (hasFlag("--json"))
|
|
7803
8115
|
console.log(JSON.stringify(object, null, 2));
|
|
7804
8116
|
else
|
|
7805
|
-
console.log(`✓
|
|
8117
|
+
console.log(`✓ 已上传${wrappedDataKey ? "(已加密)" : ""}:${object.filename}(id ${object.id})`);
|
|
7806
8118
|
return 0;
|
|
7807
8119
|
}
|
|
7808
8120
|
if (presign.body?.error?.code === "presign_unavailable") {
|
|
7809
8121
|
const form = new FormData;
|
|
7810
|
-
form.append("file", new Blob([
|
|
7811
|
-
|
|
7812
|
-
|
|
8122
|
+
form.append("file", new Blob([body], { type: mimeType }), filename);
|
|
8123
|
+
if (wrappedDataKey) {
|
|
8124
|
+
form.append("enc", "1");
|
|
8125
|
+
form.append("wrappedDataKey", wrappedDataKey);
|
|
8126
|
+
}
|
|
7813
8127
|
const res = await fetch(`${credentials.apiBase}/v1/storage`, {
|
|
7814
8128
|
method: "POST",
|
|
7815
8129
|
headers: authHeaders(credentials),
|
|
7816
8130
|
body: form
|
|
7817
8131
|
});
|
|
7818
|
-
const
|
|
8132
|
+
const body2 = await res.json().catch(() => ({}));
|
|
7819
8133
|
if (res.status !== 200 && res.status !== 201) {
|
|
7820
|
-
console.error(
|
|
7821
|
-
return exitCodeForApiError(
|
|
8134
|
+
console.error(body2.error?.message ?? `上传失败:${filename}`);
|
|
8135
|
+
return exitCodeForApiError(body2);
|
|
7822
8136
|
}
|
|
7823
8137
|
if (hasFlag("--json"))
|
|
7824
|
-
console.log(JSON.stringify(
|
|
8138
|
+
console.log(JSON.stringify(body2.data, null, 2));
|
|
7825
8139
|
else
|
|
7826
|
-
console.log(`✓
|
|
8140
|
+
console.log(`✓ 已上传${wrappedDataKey ? "(已加密)" : ""}:${body2.data?.filename}(id ${body2.data?.id})`);
|
|
7827
8141
|
return 0;
|
|
7828
8142
|
}
|
|
7829
8143
|
console.error(errorMessage(presign.body, `上传失败:${filename}`));
|
|
@@ -7831,16 +8145,21 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7831
8145
|
}
|
|
7832
8146
|
async function storageUpload(paths) {
|
|
7833
8147
|
const files = [];
|
|
8148
|
+
let encrypt = false;
|
|
7834
8149
|
for (let i = 0;i < paths.length; i++) {
|
|
7835
8150
|
if (paths[i].startsWith("--")) {
|
|
7836
|
-
if (paths[i] === "--passphrase")
|
|
8151
|
+
if (paths[i] === "--passphrase") {
|
|
8152
|
+
encrypt = true;
|
|
7837
8153
|
i++;
|
|
8154
|
+
}
|
|
7838
8155
|
continue;
|
|
7839
8156
|
}
|
|
7840
8157
|
files.push(paths[i]);
|
|
7841
8158
|
}
|
|
8159
|
+
if (!encrypt && process.env.CRAB_PASSPHRASE)
|
|
8160
|
+
encrypt = true;
|
|
7842
8161
|
if (files.length === 0) {
|
|
7843
|
-
console.error("用法:crab storage upload <file...> [--passphrase 口令] [--json]");
|
|
8162
|
+
console.error("用法:crab storage upload <file...> [--passphrase 口令] [--json](缺省明文上传;带口令则端到端加密)");
|
|
7844
8163
|
return EXIT_USAGE;
|
|
7845
8164
|
}
|
|
7846
8165
|
const credentials = requireCredentials();
|
|
@@ -7848,7 +8167,7 @@ async function storageUpload(paths) {
|
|
|
7848
8167
|
return credentials;
|
|
7849
8168
|
let code = 0;
|
|
7850
8169
|
for (const file of files) {
|
|
7851
|
-
const result = await storageUploadOne(credentials, file);
|
|
8170
|
+
const result = await storageUploadOne(credentials, file, encrypt);
|
|
7852
8171
|
if (result !== 0 && code === 0)
|
|
7853
8172
|
code = result;
|
|
7854
8173
|
if (result === EXIT_AUTH || result === EXIT_SCOPE)
|
|
@@ -8144,9 +8463,10 @@ async function storageQuota() {
|
|
|
8144
8463
|
return 0;
|
|
8145
8464
|
}
|
|
8146
8465
|
const pct = quota.quotaBytes > 0 ? (quota.usedBytes / quota.quotaBytes * 100).toFixed(1) : "0";
|
|
8466
|
+
const libraryCount = (quota.counts.all ?? 0) - (quota.counts.vault ?? 0);
|
|
8147
8467
|
console.log(`已用 ${formatBytesCli(quota.usedBytes)} / ${formatBytesCli(quota.quotaBytes)}(${pct}%)`);
|
|
8148
|
-
console.log(` 文件 ${formatBytesCli(quota.bySource.files)} · 素材 ${formatBytesCli(quota.bySource.media)} ·
|
|
8149
|
-
console.log(` 对象数:全部 ${quota.counts.all ?? 0} ·
|
|
8468
|
+
console.log(` 文件 ${formatBytesCli(quota.bySource.files)} · 素材 ${formatBytesCli(quota.bySource.media)} · ` + `邮件附件 ${formatBytesCli(quota.bySource.mail)} · 协作 ${formatBytesCli(quota.bySource.collab)} · ` + `Agent ${formatBytesCli(quota.bySource.agent)} · 回收站 ${formatBytesCli(quota.trashBytes)}`);
|
|
8469
|
+
console.log(` 对象数:全部 ${quota.counts.all ?? 0} · 素材库 ${libraryCount}(保险箱 ${quota.counts.vault ?? 0}) · ` + `共享 ${quota.counts.shared ?? 0} · 回收站 ${quota.counts.trash ?? 0}`);
|
|
8150
8470
|
return 0;
|
|
8151
8471
|
}
|
|
8152
8472
|
async function storageCategories() {
|
|
@@ -8407,7 +8727,7 @@ async function collabSpacesList() {
|
|
|
8407
8727
|
}
|
|
8408
8728
|
for (const s of data.spaces) {
|
|
8409
8729
|
const storage = s.kind === "independent" ? ` · 存储 ${formatBytesCli(s.storageUsedBytes)}/${formatBytesCli(s.storageAllocatedBytes)}` : "";
|
|
8410
|
-
console.log(`${collabSpaceToken(s)} ${s.status === "archived" ? "[已归档·只读] " : ""}任务 ${s.taskCount}/${s.limits.taskLimit} ·
|
|
8730
|
+
console.log(`${collabSpaceToken(s)} ${s.status === "archived" ? "[已归档·只读] " : ""}任务 ${s.taskCount}/${s.limits.taskLimit} · 成员 ${s.memberCount}/${s.limits.memberLimit}${storage}`);
|
|
8411
8731
|
console.log(` id ${s.id}`);
|
|
8412
8732
|
}
|
|
8413
8733
|
console.log(`独立空间名额:${data.independentUsed}/${data.independentLimit}(创建消耗 5 积分:crab collab space create <名称>)`);
|
|
@@ -8963,6 +9283,20 @@ switch (command) {
|
|
|
8963
9283
|
process.exit(await storageActivity());
|
|
8964
9284
|
console.error(sub ? `未知子命令:storage ${sub}
|
|
8965
9285
|
` : `缺少子命令:storage ls | upload | download | rename | tag | category | publish | unpublish | rm | restore | purge | quota | categories | mkcat | rencat | rmcat | tags | share | shares | unshare | key | activity
|
|
9286
|
+
`);
|
|
9287
|
+
console.log(usage);
|
|
9288
|
+
process.exit(EXIT_USAGE);
|
|
9289
|
+
}
|
|
9290
|
+
case "agent": {
|
|
9291
|
+
const sub = args[1];
|
|
9292
|
+
if (sub === "status")
|
|
9293
|
+
process.exit(await agentStatus());
|
|
9294
|
+
if (sub === "sessions")
|
|
9295
|
+
process.exit(await agentSessions());
|
|
9296
|
+
if (sub === "approvals")
|
|
9297
|
+
process.exit(await agentApprovals());
|
|
9298
|
+
console.error(sub ? `未知子命令:agent ${sub}
|
|
9299
|
+
` : `缺少子命令:agent status | sessions | approvals
|
|
8966
9300
|
`);
|
|
8967
9301
|
console.log(usage);
|
|
8968
9302
|
process.exit(EXIT_USAGE);
|
|
@@ -9037,3 +9371,73 @@ switch (command) {
|
|
|
9037
9371
|
console.log(usage);
|
|
9038
9372
|
process.exit(EXIT_USAGE);
|
|
9039
9373
|
}
|
|
9374
|
+
async function agentStatus() {
|
|
9375
|
+
const credentials = requireCredentials();
|
|
9376
|
+
if (typeof credentials === "number")
|
|
9377
|
+
return credentials;
|
|
9378
|
+
const [sessionsRes, approvalsRes] = await Promise.all([
|
|
9379
|
+
api2(credentials.apiBase, "/v1/agent/sessions", { headers: authHeaders(credentials) }),
|
|
9380
|
+
api2(credentials.apiBase, "/v1/agent/approvals", { headers: authHeaders(credentials) })
|
|
9381
|
+
]);
|
|
9382
|
+
if (sessionsRes.status !== 200 || approvalsRes.status !== 200) {
|
|
9383
|
+
console.error(errorMessage(sessionsRes.body ?? approvalsRes.body, "获取 Agent 状态失败。"));
|
|
9384
|
+
if (sessionsRes.body?.error?.code === "scope_denied") {
|
|
9385
|
+
console.error("提示:需要 agent.read scope——运行 crab login --scopes agent 重新授权。");
|
|
9386
|
+
}
|
|
9387
|
+
return 1;
|
|
9388
|
+
}
|
|
9389
|
+
const sessions = sessionsRes.body.data?.sessions ?? [];
|
|
9390
|
+
const approvals = approvalsRes.body.data?.approvals ?? [];
|
|
9391
|
+
const running = sessions.filter((x) => x.status === "running").length;
|
|
9392
|
+
const pending = approvals.filter((x) => x.status === "pending").length;
|
|
9393
|
+
if (hasFlag("--json")) {
|
|
9394
|
+
console.log(JSON.stringify({ sessions: sessions.length, running, pendingApprovals: pending }, null, 2));
|
|
9395
|
+
return 0;
|
|
9396
|
+
}
|
|
9397
|
+
console.log(`个人云 Agent:会话 ${sessions.length} · 运行中 ${running} · 待审批 ${pending}`);
|
|
9398
|
+
return 0;
|
|
9399
|
+
}
|
|
9400
|
+
async function agentSessions() {
|
|
9401
|
+
const credentials = requireCredentials();
|
|
9402
|
+
if (typeof credentials === "number")
|
|
9403
|
+
return credentials;
|
|
9404
|
+
const res = await api2(credentials.apiBase, "/v1/agent/sessions", { headers: authHeaders(credentials) });
|
|
9405
|
+
if (res.status !== 200) {
|
|
9406
|
+
console.error(errorMessage(res.body, "获取会话列表失败。"));
|
|
9407
|
+
return 1;
|
|
9408
|
+
}
|
|
9409
|
+
const sessions = res.body.data?.sessions ?? [];
|
|
9410
|
+
if (hasFlag("--json")) {
|
|
9411
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
9412
|
+
return 0;
|
|
9413
|
+
}
|
|
9414
|
+
for (const x of sessions) {
|
|
9415
|
+
console.log(`${x.kind === "task" ? "[任务]" : "[对话]"} ${x.title} ${x.status}${x.repeatCron ? ` cron ${x.repeatCron}` : ""} ${new Date(x.updatedAt).toLocaleString("zh-CN")}`);
|
|
9416
|
+
}
|
|
9417
|
+
console.log(`共 ${sessions.length} 个会话`);
|
|
9418
|
+
return 0;
|
|
9419
|
+
}
|
|
9420
|
+
async function agentApprovals() {
|
|
9421
|
+
const credentials = requireCredentials();
|
|
9422
|
+
if (typeof credentials === "number")
|
|
9423
|
+
return credentials;
|
|
9424
|
+
const res = await api2(credentials.apiBase, "/v1/agent/approvals", { headers: authHeaders(credentials) });
|
|
9425
|
+
if (res.status !== 200) {
|
|
9426
|
+
console.error(errorMessage(res.body, "获取审批队列失败。"));
|
|
9427
|
+
return 1;
|
|
9428
|
+
}
|
|
9429
|
+
const approvals = res.body.data?.approvals ?? [];
|
|
9430
|
+
if (hasFlag("--json")) {
|
|
9431
|
+
console.log(JSON.stringify(approvals, null, 2));
|
|
9432
|
+
return 0;
|
|
9433
|
+
}
|
|
9434
|
+
if (approvals.length === 0) {
|
|
9435
|
+
console.log("没有待处理或已过期的审批。");
|
|
9436
|
+
return 0;
|
|
9437
|
+
}
|
|
9438
|
+
for (const x of approvals) {
|
|
9439
|
+
console.log(`${x.status === "pending" ? "[待审批]" : "[已超时]"} ${x.tool} ${x.sessionTitle}`);
|
|
9440
|
+
console.log(` ${x.summary.slice(0, 160)}`);
|
|
9441
|
+
}
|
|
9442
|
+
return 0;
|
|
9443
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: crabcloud-agent
|
|
3
|
+
description: Check the status of the user's personal cloud Agent in Crab Cloud through the `crab agent` CLI — running sessions and tasks, and the outbound-action approval queue (pending / expired). Use this skill whenever the user asks about their in-cloud agent ("我的 agent 在干嘛", "agent 跑完了吗", "有没有待审批的操作", "看看 agent 审批队列"), wants a quick health check of their Agent workbench, or asks what the agent has been doing — even if they never say "crab" or "Crab Cloud" explicitly. This skill is read-only; approving or rejecting happens in the Agent workbench web UI (agent.crabcloud.cc), not from the CLI.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 个人云 Agent(查看域)
|
|
7
|
+
|
|
8
|
+
Crab Cloud 的个人云 Agent 是一个用户一个固定 agent:长记忆、权限三档、
|
|
9
|
+
能读平台数据(邮箱/云盘/协作)、写动作过审批全留痕。本 skill 覆盖**只读
|
|
10
|
+
查看**(`crab agent …`,需 `agent.read` scope);审批的批准/拒绝在网页
|
|
11
|
+
工作台完成(流内卡片),CLI 不做决策。
|
|
12
|
+
|
|
13
|
+
## 第一步:确认可用
|
|
14
|
+
|
|
15
|
+
先确认 `crab` 命令存在:`crab --help`。若命令缺失,引导安装:
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
npx @crabcloud/cli init
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
已安装但命令缺失时升级:`npx @crabcloud/cli@latest init`。所有
|
|
22
|
+
`crab <cmd>` 都可用 `npx @crabcloud/cli <cmd>` 等价执行。
|
|
23
|
+
|
|
24
|
+
## 命令
|
|
25
|
+
|
|
26
|
+
### 状态总览
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
crab agent status
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
输出:会话总数、运行中数量、待审批数量。`--json` 输出机器可读 JSON。
|
|
33
|
+
|
|
34
|
+
### 会话列表
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
crab agent sessions
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
列出对话与任务(标题 / 状态 / 定时 cron / 最近更新时间)。`--json` 可用。
|
|
41
|
+
|
|
42
|
+
### 审批队列
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
crab agent approvals
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
列出待处理与已超时的出站审批(工具名 / 参数摘要 / 来源会话)。
|
|
49
|
+
**这里只看,不批**——告诉用户:打开 Agent 工作台(agent.crabcloud.cc)→
|
|
50
|
+
对应会话的流内审批卡上点「批准执行 / 拒绝」;24 小时未处理的会自动失效,
|
|
51
|
+
且失效后即使补批也不会执行(agent 需重新发起)。
|
|
52
|
+
|
|
53
|
+
## 边界
|
|
54
|
+
|
|
55
|
+
- 只读:本 skill 的命令都不会改变 agent 状态;决策一律引导到工作台网页。
|
|
56
|
+
- BYOK 配置(API 端点 / Key)不在 CLI 视野——在「模型」管理页维护。
|
|
57
|
+
- 需要写操作走平台时,外部 agent 用既有域命令:`crab mail …` /
|
|
58
|
+
`crab storage …` / `crab collab …`(各自 skill),与本 skill 互补。
|
|
@@ -94,6 +94,8 @@ skill 覆盖协作域的 agent 通道;账号/令牌管理见 `crabcloud` skill
|
|
|
94
94
|
系人,不要换名字硬试。
|
|
95
95
|
- **附件引用不复制**:`--attach` 的 fileId 是云盘/空间已有文件的引用(≤10 个),
|
|
96
96
|
只能引用空间成员的云盘文件或本空间直传文件;跨空间文件会被 403 拒绝。
|
|
97
|
+
**只接受明文 / 平台加密(p1)对象,保险箱(u1) 文件引用会被拒**——见
|
|
98
|
+
`crabcloud-storage` skill。
|
|
97
99
|
- 单任务记录流上限 500 条(状态变更也占位):任务粒度别太粗,做完就 `done`,
|
|
98
100
|
新工作开新任务。
|
|
99
101
|
- 读记录流对 Agent 是一等审计事件(thread.read 入活动流)——正常读即可,但
|
|
@@ -76,5 +76,6 @@ description: Read, search and send the user's Crab Cloud platform email (usernam
|
|
|
76
76
|
- 附件:`mail read` 输出附件清单(id/文件名/类型/体积);附件内容归云盘管
|
|
77
77
|
——下载用 `crab storage download <id>`(见 `crabcloud-storage` skill,跨来
|
|
78
78
|
源通用),发送引用云盘对象用 `mail send --attach`(对象须未被其他邮件绑
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
定;若报错说明已被引用,改走正文链接)。**附件只接受明文 / 平台加密(p1)
|
|
80
|
+
对象,保险箱(u1) 文件不能作附件**——CLI 会明确拒绝,需要分享时改走正文
|
|
81
|
+
链接。邮件详情附件显示「已在云盘删除」表示对象已被软删,如实转述。
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: crabcloud-storage
|
|
3
|
-
description: Manage the user's Crab Cloud
|
|
3
|
+
description: Manage the user's Crab Cloud storage (云盘 — one base file service carrying drive uploads, media assets, mail attachments, collab files and agent files on one quota; vault-grade end-to-end encryption is optional) through the `crab` CLI — list and filter objects, upload and download files (including decrypting share links), rename, tag, categorize, publish/unpublish public links, create expiring share links with passcodes, manage the locally remembered encryption key, trash/restore/purge, and check the storage quota. Use this skill when the user asks to upload, find, organize, share or clean up files on Crab Cloud ("上传这个文件", "云盘里有什么", "把这个文件公开分享", "看看存储用了多少", "crab storage"), or wants files moved in or out of their drive — even if they never say "crab" or "Crab Cloud" explicitly.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Crab Cloud 云盘(crab storage)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
云盘是用户的统一存储基础服务:云盘上传 / 素材库 / 邮件附件 / 协作文件 /
|
|
9
|
+
Agent 产物五来源共用一本容量账(按档位,Free 1GB / Plus 10GB);属性优先、
|
|
10
|
+
位置无关——分类(至多一个)+ 标签(AND 检索)代替文件夹。**上传缺省明文**
|
|
11
|
+
(2026-10 基础服务化:其他应用可直接引用);提供口令时才端到端加密(保险箱
|
|
12
|
+
文件,服务器只有包裹态密钥,明文密钥只在本机)。加密三态以 `wrappedDataKey`
|
|
13
|
+
前缀区分:无前缀 = 明文、`p1.` = 平台加密(如邮件附件,服务端可透明解密)、
|
|
14
|
+
`u1.` = 保险箱(用户持钥)。本 skill 覆盖存储域的 agent 通道;账号/令牌管理见
|
|
15
|
+
`crabcloud` skill,邮箱见 `crabcloud-mail` skill。
|
|
13
16
|
|
|
14
17
|
## 第一步:确认可用
|
|
15
18
|
|
|
@@ -24,7 +27,7 @@ skill。
|
|
|
24
27
|
`storage.write`,移回收站/恢复/彻底删除 `storage.delete`。退出码 4 = scope
|
|
25
28
|
不足:如实转述缺哪个,引导 `crab login --scopes storage`(组名 = 三项整组)
|
|
26
29
|
重新授权(你只发起,不代批)。
|
|
27
|
-
- 退出码 7 = `storage.quotaExceeded
|
|
30
|
+
- 退出码 7 = `storage.quotaExceeded`(统一容量账满):告知用户,可建议
|
|
28
31
|
清理回收站或大文件(`storage quota` 看分项),不要自动重试。
|
|
29
32
|
|
|
30
33
|
## 意图 → 命令映射
|
|
@@ -32,9 +35,9 @@ skill。
|
|
|
32
35
|
| 用户意图 | 命令 |
|
|
33
36
|
|---|---|
|
|
34
37
|
| 云盘里有什么 | `crab storage ls`(按时间倒序,默认 30 条;`--limit` / `--offset` 翻页) |
|
|
35
|
-
| 找文件 | `crab storage ls --q 关键字`(文件名匹配;可叠 `--source files\|media\|mail`、`--type doc\|image\|video\|archive\|code`、`--category 分类id\|none`、`--tag a,b`、`--shared`、`--recent`、`--message-id <id
|
|
38
|
+
| 找文件 | `crab storage ls --q 关键字`(文件名匹配;可叠 `--source files\|media\|mail\|collab\|agent`、`--type doc\|image\|video\|archive\|code`、`--category 分类id\|none`、`--tag a,b`、`--shared`、`--recent`、`--encrypted only\|plain`、`--message-id <id>`;`--encrypted only` = 仅保险箱、`plain` = 排除保险箱) |
|
|
36
39
|
| 看回收站 | `crab storage ls --trash` |
|
|
37
|
-
| 上传文件 | `crab storage upload
|
|
40
|
+
| 上传文件 | `crab storage upload <路径...>`(多文件串行;直传优先,自动回退中转;**缺省明文直传**,其他应用可直接引用;`--passphrase` 或 `CRAB_PASSPHRASE` 提供时端到端加密为保险箱文件——口令三来源见下节,首次使用自动初始化并打印一次性恢复码) |
|
|
38
41
|
| 下载文件 | `crab storage download <id\|文件名\|分享链接> [-o 输出路径] [--passcode 提取码]`(默认按原文件名存当前目录;**凭完整分享链接可免登录下载并本地解密**——`#` 后是密钥片段,这是 agent→agent 的主通道;带提取码的分享加 `--passcode`) |
|
|
39
42
|
| 改名 | `crab storage rename <id\|文件名> <新名称>` |
|
|
40
43
|
| 加/删标签 | `crab storage tag <id\|文件名> --add a,b [--remove c,d]` |
|
|
@@ -46,7 +49,7 @@ skill。
|
|
|
46
49
|
| 可控分享(有效期/提取码/撤销) | `crab storage share <id\|文件名> [--expires 7\|30\|90\|forever] [--passcode 提取码]`(打印完整分享链接;加密对象链接自带 `#` 密钥片段);列表 `crab storage shares <id\|文件名>`、撤销 `crab storage unshare <shareId>` |
|
|
47
50
|
| 云盘钥匙管理 | `crab storage key`(查状态)/ `--remember`(解锁一次并记住,之后免口令)/ `--forget`(清除)——见下节 |
|
|
48
51
|
| 删除 | `crab storage rm <id\|文件名>`(进回收站,7 天后自动清理)→ `restore` 恢复 / `purge` 彻底删除 |
|
|
49
|
-
| 看水位 | `crab storage quota
|
|
52
|
+
| 看水位 | `crab storage quota`(五来源分项 + 保险箱计数 + 回收站占用 + 对象计数) |
|
|
50
53
|
| 看云盘操作流 | `crab storage activity`(读取/上传/删除/建分享,最近 50 条) |
|
|
51
54
|
|
|
52
55
|
`<id|文件名>`:文件名全库精确唯一命中时自动解析为 id(回收站内同样可解析,
|
|
@@ -59,17 +62,20 @@ restore/purge 直接用文件名即可);命中多个或查不到时按字面
|
|
|
59
62
|
程序化消费**始终加 `--json`**:
|
|
60
63
|
|
|
61
64
|
- `storage ls --json` → `{ objects: [...], nextOffset, total }`,条目含
|
|
62
|
-
`id / source / filename / mimeType / sizeBytes / categoryId / tags / isPublic / createdAt
|
|
63
|
-
|
|
65
|
+
`id / source / filename / mimeType / sizeBytes / categoryId / tags / isEncrypted / wrappedDataKey / isPublic / createdAt`
|
|
66
|
+
(`source` = `files|media|mail|collab|agent`;u1 判定 = `isEncrypted && wrappedDataKey` 以 `u1.` 开头);
|
|
67
|
+
- `storage quota --json` → `{ usedBytes, quotaBytes, bySource: { files, media, mail, collab, agent }, trashBytes, counts: { all, files, media, mail, collab, agent, shared, vault, trash } }`
|
|
68
|
+
(素材库条数 = `counts.all - counts.vault`);
|
|
64
69
|
- `storage activity --json` → 条目含 `action(object.read/object.uploaded/object.deleted/share.created)/ actorType / actorLabel / objectId / objectName / createdAt`;
|
|
65
70
|
- `upload` / `categories` / `tags` / `shares` 的 `--json` 同构(单对象 / 分类数组 /
|
|
66
71
|
标签数组 / 分享数组)。
|
|
67
72
|
|
|
68
|
-
##
|
|
73
|
+
## 保险箱口令与本机钥匙
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
保险箱(u1)是端到端加密:服务器只有口令包裹态的主密钥,**明文密钥只存在于
|
|
76
|
+
用户本机**——CLI 不可能也不允许向服务器索取。**只有涉及保险箱文件的命令才
|
|
77
|
+
需要口令**(带口令 upload / download 或 share u1 对象;明文与 p1 平台加密对象
|
|
78
|
+
全程免口令)。口令来源优先级:
|
|
73
79
|
|
|
74
80
|
1. `--passphrase <口令>`(flag)
|
|
75
81
|
2. 环境变量 `CRAB_PASSPHRASE`(**Agent/脚本推荐**——非交互终端不会弹提问,
|
|
@@ -96,6 +102,9 @@ download / share 加密文件)需要云盘口令,来源优先级:
|
|
|
96
102
|
等待即可;凭链接下载时提取码用 `--passcode` 正确携带。
|
|
97
103
|
- **purge 是彻底删除**,不可恢复;用户没明说「彻底删除」时一律用 `rm`
|
|
98
104
|
(回收站保留 7 天可找回)。
|
|
105
|
+
- **保险箱(u1)不能被其他应用引用**:邮件附件(`mail send --attach`)与协作
|
|
106
|
+
记录引用都只接受明文 / p1 平台加密对象,u1 对象会被明确拒绝——需要跨应用
|
|
107
|
+
使用时用分享链接(u1 链接自带密钥片段),或等互转功能;不要尝试绕过。
|
|
99
108
|
- 邮件附件与用户上传同账:水位告急时用 `quota` 分项定位来源再建议清理。
|
|
100
109
|
- 公开直链的呈现方式由服务端策略决定(图片/视频/音频/PDF/纯文本内联,SVG 与
|
|
101
110
|
其他类型强制下载),CLI 侧没有、也不需要有开关。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crabcloud/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Crab Cloud CLI (crab) — agent-first entry to your personal cloud, with skills bundled for Claude Code / Codex / ZCode.",
|
|
6
6
|
"bin": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"prepublishOnly": "bun run typecheck && bun run build"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@crabcloud/cloudkit": "
|
|
28
|
-
"@crabcloud/contracts": "
|
|
29
|
-
"@crabcloud/skills": "
|
|
27
|
+
"@crabcloud/cloudkit": "workspace:*",
|
|
28
|
+
"@crabcloud/contracts": "workspace:*",
|
|
29
|
+
"@crabcloud/skills": "workspace:*",
|
|
30
30
|
"@types/bun": "latest",
|
|
31
31
|
"typescript": "^5.9.3"
|
|
32
32
|
}
|