@crabcloud/cli 0.2.4 → 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 +570 -57
- package/dist/skills/crabcloud/SKILL.md +8 -8
- package/dist/skills/crabcloud-agent/SKILL.md +58 -0
- package/dist/skills/crabcloud-collab/SKILL.md +6 -0
- package/dist/skills/crabcloud-mail/SKILL.md +12 -6
- package/dist/skills/crabcloud-storage/SKILL.md +27 -16
- package/package.json +1 -1
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) => {
|
|
@@ -6034,6 +6213,10 @@ var oauthBindLoginInput = object({
|
|
|
6034
6213
|
password: string2().min(1).max(256)
|
|
6035
6214
|
});
|
|
6036
6215
|
var adminSettingsInput = object({ registrationOpen: boolean2() });
|
|
6216
|
+
var collabContentViewInput = object({
|
|
6217
|
+
reason: string2().trim().min(1).max(200),
|
|
6218
|
+
cursor: string2().trim().max(200).optional()
|
|
6219
|
+
});
|
|
6037
6220
|
var creditBuckets = ["granted", "topup"];
|
|
6038
6221
|
var creditPackStatuses = ["draft", "live", "delisted"];
|
|
6039
6222
|
var adminCreditAdjustInput = object({
|
|
@@ -6140,6 +6323,7 @@ var mailSendInput = object({
|
|
|
6140
6323
|
cc: mailRecipientList.default([]),
|
|
6141
6324
|
subject: string2().trim().min(1).max(200),
|
|
6142
6325
|
body: string2().min(1).max(1e5),
|
|
6326
|
+
bodyHtml: string2().max(300000).optional(),
|
|
6143
6327
|
inReplyTo: string2().max(998).optional(),
|
|
6144
6328
|
attachmentIds: array(string2().min(1).max(64)).max(10).optional()
|
|
6145
6329
|
});
|
|
@@ -6147,7 +6331,8 @@ var mailDraftInput = object({
|
|
|
6147
6331
|
to: mailRecipientList.default([]),
|
|
6148
6332
|
cc: mailRecipientList.default([]),
|
|
6149
6333
|
subject: string2().trim().max(200).default(""),
|
|
6150
|
-
body: string2().max(1e5).default("")
|
|
6334
|
+
body: string2().max(1e5).default(""),
|
|
6335
|
+
bodyHtml: string2().max(1e6).optional()
|
|
6151
6336
|
});
|
|
6152
6337
|
var mailReadInput = object({ unread: boolean2() });
|
|
6153
6338
|
var mailDevInboundInput = object({
|
|
@@ -6155,6 +6340,7 @@ var mailDevInboundInput = object({
|
|
|
6155
6340
|
to: mailAddressSchema,
|
|
6156
6341
|
subject: string2().trim().min(1).max(200),
|
|
6157
6342
|
body: string2().max(1e5),
|
|
6343
|
+
html: string2().max(1e6).optional(),
|
|
6158
6344
|
attachments: array(object({
|
|
6159
6345
|
filename: string2().min(1).max(120),
|
|
6160
6346
|
mimeType: string2().regex(/^[\w.+-]+\/[\w.+-]+$/).max(100),
|
|
@@ -6222,6 +6408,123 @@ var collabRecordListQuery = object({
|
|
|
6222
6408
|
cursor: string2().trim().max(200).optional(),
|
|
6223
6409
|
limit: exports_coerce.number().int().min(1).max(200).optional()
|
|
6224
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
|
+
});
|
|
6225
6528
|
|
|
6226
6529
|
// ../../packages/cloudkit/src/crypto.ts
|
|
6227
6530
|
var enc = new TextEncoder;
|
|
@@ -6548,9 +6851,6 @@ function formatTime(ms) {
|
|
|
6548
6851
|
function fmtCredits(cents) {
|
|
6549
6852
|
return (cents / 100).toLocaleString("en-US");
|
|
6550
6853
|
}
|
|
6551
|
-
function fmtUsd(cents) {
|
|
6552
|
-
return `$${cents / 100}`;
|
|
6553
|
-
}
|
|
6554
6854
|
var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
6555
6855
|
发布物经 npm 分发:npx @crabcloud/cli <command>(全局安装后短名 crab 等价)
|
|
6556
6856
|
|
|
@@ -6574,7 +6874,10 @@ var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
|
6574
6874
|
mail list [--folder inbox|sent|drafts|archive|trash] [--unread] [--limit N] [--json]
|
|
6575
6875
|
列出邮件(cursor 分页契约与 Web 工作台一致)
|
|
6576
6876
|
mail read <id> [--json] 读取邮件正文(自动置已读)
|
|
6877
|
+
mail unread <id> 标记为未读
|
|
6577
6878
|
mail search <query> [--json] 全文搜索(主题/发件人/收件人/正文)
|
|
6879
|
+
mail folders [--json] 邮箱概览(各夹总数/未读 + 本月收发统计)
|
|
6880
|
+
mail activity [--json] 邮箱操作流(谁读过/谁代发,最近 50 条)
|
|
6578
6881
|
mail send --to a@x.cc|联系人名,b@y.cn [--cc c@z.io] --subject "..." --body "..." | --body-file <path>
|
|
6579
6882
|
[--attach <云盘id|文件名>,...] [--in-reply-to <message-id>]
|
|
6580
6883
|
回信线程锚点(原信 RFC Message-ID,crab mail read 可见)
|
|
@@ -6582,7 +6885,7 @@ var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
|
6582
6885
|
命中采用,多候选报错列出);--attach 引用云盘已有对象
|
|
6583
6886
|
(id 或文件名精确唯一命中,未绑信对象,≤10 个,大文件
|
|
6584
6887
|
自动转下载链接);平台内(@crabcloud.cc)免费,外部出站
|
|
6585
|
-
|
|
6888
|
+
0.02 积分/封
|
|
6586
6889
|
mail archive|trash|delete <id> 移夹 / 废纸篓 / 彻底删除(delete 不可恢复)
|
|
6587
6890
|
contacts list [query] [--offset N] [--json] 列出联系人(地址前缀/显示名匹配)
|
|
6588
6891
|
contacts add <address> [--name 名] [--note 备注] [--json]
|
|
@@ -6591,13 +6894,15 @@ var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
|
6591
6894
|
contacts invite [--reset] [--json] 我的邀请码、链接与统计(--reset 重置,旧链接失效)
|
|
6592
6895
|
contacts invitees [--offset N] [--json]
|
|
6593
6896
|
我邀请的人(直接下级 + 间接计数)
|
|
6594
|
-
storage ls [--q 关键字] [--source files|media|mail] [--category <id|none>]
|
|
6897
|
+
storage ls [--q 关键字] [--source files|media|mail|collab|agent] [--category <id|none>]
|
|
6595
6898
|
[--type doc|image|video|archive|code] [--tag a,b] [--trash]
|
|
6596
|
-
[--shared] [--recent] [--message-id <id>]
|
|
6597
|
-
|
|
6899
|
+
[--shared] [--recent] [--encrypted only|plain] [--message-id <id>]
|
|
6900
|
+
[--limit N] [--offset N] [--json]
|
|
6901
|
+
列出云盘对象(跨来源统一视图,能力与 Web 云盘一致;
|
|
6902
|
+
--encrypted only=仅保险箱、plain=排除保险箱)
|
|
6598
6903
|
storage upload <file...> [--json] 上传一个或多个文件(直传优先,自动回退中转);
|
|
6599
|
-
|
|
6600
|
-
CRAB_PASSPHRASE
|
|
6904
|
+
默认明文上传(2026-10 基础服务化);--passphrase /
|
|
6905
|
+
CRAB_PASSPHRASE 提供时端到端加密(保险箱文件)
|
|
6601
6906
|
storage download <id|文件名> [-o 输出路径]
|
|
6602
6907
|
下载对象内容(默认按原文件名存到当前目录;
|
|
6603
6908
|
加密对象需口令,终端内运行时交互输入)
|
|
@@ -6610,7 +6915,7 @@ var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
|
6610
6915
|
公开 / 收回公开(公开时打印公开直链)
|
|
6611
6916
|
storage rm|restore|purge <id|文件名>
|
|
6612
6917
|
回收站 / 恢复 / 彻底删除(purge 不可恢复)
|
|
6613
|
-
storage quota [--json]
|
|
6918
|
+
storage quota [--json] 存储水位(统一一本账:五来源分项 + 保险箱计数)
|
|
6614
6919
|
storage share <id|文件名> [--expires 7|30|90|forever] [--passcode 提取码]
|
|
6615
6920
|
创建分享链接(加密文件链接自带解密钥匙 #fragment)
|
|
6616
6921
|
storage key [--remember|--forget]
|
|
@@ -6625,10 +6930,11 @@ var usage = `crab — Crab Cloud CLI(Agent-first 入口)
|
|
|
6625
6930
|
分类重命名 / 改色
|
|
6626
6931
|
storage rmcat <id|分类名> 删除分类(其中文件回落未分类)
|
|
6627
6932
|
storage tags [--json] 标签汇总
|
|
6933
|
+
storage activity [--json] 云盘操作流(读取/上传/删除/建分享,最近 50 条)
|
|
6628
6934
|
collab spaces [--json] 列出我所在的协作空间(任务/成员计数与限额)
|
|
6629
6935
|
collab space create <名称> [--allocation-mb N] [--json]
|
|
6630
6936
|
创建独立协作空间(消耗 5 积分;默认自分配 500MB
|
|
6631
|
-
|
|
6937
|
+
存储占订阅总池;后续调整在网页端空间设置)
|
|
6632
6938
|
collab tasks [--space <arg>] [--status open|done] [--q 关键字]
|
|
6633
6939
|
[--limit N] [--cursor <cursor>] [--json]
|
|
6634
6940
|
列出任务(含摘要/记录数;--q 搜标题与记录正文)
|
|
@@ -6859,10 +7165,10 @@ async function creditsPricing() {
|
|
|
6859
7165
|
console.log("暂无动作计价项。");
|
|
6860
7166
|
return 0;
|
|
6861
7167
|
}
|
|
6862
|
-
console.log("动作计价(1 积分 = $1
|
|
7168
|
+
console.log("动作计价(1 积分 = $1,价目为积分数):");
|
|
6863
7169
|
for (const item of actions) {
|
|
6864
7170
|
const label = CREDIT_ACTION_LABELS[item.action] ?? item.action;
|
|
6865
|
-
console.log(` ${item.action.padEnd(24)} ${label} ${
|
|
7171
|
+
console.log(` ${item.action.padEnd(24)} ${label} ${fmtCredits(item.creditCents)} 积分`);
|
|
6866
7172
|
}
|
|
6867
7173
|
return 0;
|
|
6868
7174
|
}
|
|
@@ -7114,8 +7420,8 @@ async function mailSend() {
|
|
|
7114
7420
|
const hit = await resolveStorageObject(credentials, token);
|
|
7115
7421
|
if (typeof hit === "number")
|
|
7116
7422
|
return hit;
|
|
7117
|
-
if (typeof hit === "object" && hit.isEncrypted) {
|
|
7118
|
-
console.error(`「${token}
|
|
7423
|
+
if (typeof hit === "object" && hit.isEncrypted && hit.wrappedDataKey?.startsWith("u1.")) {
|
|
7424
|
+
console.error(`「${token}」是保险箱文件(端到端加密),不能作为邮件附件发送。`);
|
|
7119
7425
|
return EXIT_USAGE;
|
|
7120
7426
|
}
|
|
7121
7427
|
const id = typeof hit === "object" ? hit.id : hit;
|
|
@@ -7140,7 +7446,7 @@ async function mailSend() {
|
|
|
7140
7446
|
if (attachmentIds.length > 0)
|
|
7141
7447
|
console.log(` 附件 ${attachmentIds.length} 个(云盘引用,不复制)`);
|
|
7142
7448
|
if (result.chargedCredits > 0)
|
|
7143
|
-
console.log(` 消耗:${
|
|
7449
|
+
console.log(` 消耗:${fmtCredits(result.chargedCredits)} 积分`);
|
|
7144
7450
|
if (result.failedExternal > 0)
|
|
7145
7451
|
console.log(` ⚠ ${result.failedExternal} 个外部收件人发送失败,对应积分已退回`);
|
|
7146
7452
|
return 0;
|
|
@@ -7161,6 +7467,72 @@ async function mailMove(action, id) {
|
|
|
7161
7467
|
console.log(`✓ ${action === "archive" ? "已归档" : action === "trash" ? "已移入废纸篓" : "已彻底删除"}(${id})`);
|
|
7162
7468
|
return 0;
|
|
7163
7469
|
}
|
|
7470
|
+
async function mailUnread(id) {
|
|
7471
|
+
if (!id) {
|
|
7472
|
+
console.error("用法:crab mail unread <id>(id 见 crab mail list)");
|
|
7473
|
+
return EXIT_USAGE;
|
|
7474
|
+
}
|
|
7475
|
+
const credentials = requireCredentials();
|
|
7476
|
+
if (typeof credentials === "number")
|
|
7477
|
+
return credentials;
|
|
7478
|
+
const res = await api2(credentials.apiBase, `/v1/mail/messages/${encodeURIComponent(id)}/read`, {
|
|
7479
|
+
method: "POST",
|
|
7480
|
+
headers: authHeaders(credentials),
|
|
7481
|
+
body: JSON.stringify({ unread: true })
|
|
7482
|
+
});
|
|
7483
|
+
if (res.status !== 200) {
|
|
7484
|
+
console.error(errorMessage(res.body, "标记未读失败。"));
|
|
7485
|
+
return exitCodeForApiError(res.body);
|
|
7486
|
+
}
|
|
7487
|
+
console.log(`✓ 已标为未读(${id})。`);
|
|
7488
|
+
return 0;
|
|
7489
|
+
}
|
|
7490
|
+
var MAILBOX_FOLDER_LABELS = { inbox: "收件箱", sent: "已发送", drafts: "草稿", archive: "归档", trash: "废纸篓" };
|
|
7491
|
+
async function mailFoldersOverview() {
|
|
7492
|
+
const credentials = requireCredentials();
|
|
7493
|
+
if (typeof credentials === "number")
|
|
7494
|
+
return credentials;
|
|
7495
|
+
const res = await api2(credentials.apiBase, "/v1/mail/folders", { headers: authHeaders(credentials) });
|
|
7496
|
+
if (res.status !== 200) {
|
|
7497
|
+
console.error(errorMessage(res.body, "获取邮箱概览失败。"));
|
|
7498
|
+
return exitCodeForApiError(res.body);
|
|
7499
|
+
}
|
|
7500
|
+
const overview = res.body.data;
|
|
7501
|
+
if (hasFlag("--json")) {
|
|
7502
|
+
console.log(JSON.stringify(overview, null, 2));
|
|
7503
|
+
return 0;
|
|
7504
|
+
}
|
|
7505
|
+
console.log(overview.address);
|
|
7506
|
+
for (const folder of overview.folders) {
|
|
7507
|
+
console.log(` ${MAILBOX_FOLDER_LABELS[folder.folder] ?? folder.folder} ${folder.total} 封${folder.unread > 0 ? `(未读 ${folder.unread})` : ""}`);
|
|
7508
|
+
}
|
|
7509
|
+
console.log(`本月:收 ${overview.month.received} · 发 ${overview.month.sent}(外部出站 ${overview.month.outbound})`);
|
|
7510
|
+
return 0;
|
|
7511
|
+
}
|
|
7512
|
+
var MAIL_ACTIVITY_LABELS = { read: "读取", drafted: "撰写", sent: "发送" };
|
|
7513
|
+
async function mailActivity() {
|
|
7514
|
+
const credentials = requireCredentials();
|
|
7515
|
+
if (typeof credentials === "number")
|
|
7516
|
+
return credentials;
|
|
7517
|
+
const res = await api2(credentials.apiBase, "/v1/mail/agent-activity", { headers: authHeaders(credentials) });
|
|
7518
|
+
if (res.status !== 200) {
|
|
7519
|
+
console.error(errorMessage(res.body, "获取邮箱活动失败。"));
|
|
7520
|
+
return exitCodeForApiError(res.body);
|
|
7521
|
+
}
|
|
7522
|
+
const activity = res.body.data.activity;
|
|
7523
|
+
if (hasFlag("--json")) {
|
|
7524
|
+
console.log(JSON.stringify(activity, null, 2));
|
|
7525
|
+
return 0;
|
|
7526
|
+
}
|
|
7527
|
+
if (activity.length === 0) {
|
|
7528
|
+
console.log("还没有邮箱操作记录。");
|
|
7529
|
+
return 0;
|
|
7530
|
+
}
|
|
7531
|
+
for (const entry of activity) {
|
|
7532
|
+
console.log(`${formatTime(entry.createdAt)} ${(MAIL_ACTIVITY_LABELS[entry.action] ?? entry.action).padEnd(4)} ${entry.actorType === "agent" ? "agent" : "user "} ${entry.actorLabel ?? "—"} ${entry.subject || "(无主题)"}`);
|
|
7533
|
+
}
|
|
7534
|
+
return 0;
|
|
7535
|
+
}
|
|
7164
7536
|
function contactDisplayName(contact) {
|
|
7165
7537
|
return contact.displayName ?? contact.resolvedName ?? contact.address;
|
|
7166
7538
|
}
|
|
@@ -7272,10 +7644,7 @@ async function contactsInvite(reset) {
|
|
|
7272
7644
|
const credentials = requireCredentials();
|
|
7273
7645
|
if (typeof credentials === "number")
|
|
7274
7646
|
return credentials;
|
|
7275
|
-
const res = await api2(credentials.apiBase, "/v1/contacts/invite/reset", {
|
|
7276
|
-
method: reset ? "POST" : "GET",
|
|
7277
|
-
headers: authHeaders(credentials)
|
|
7278
|
-
});
|
|
7647
|
+
const res = await api2(credentials.apiBase, reset ? "/v1/contacts/invite/reset" : "/v1/contacts/invite", { method: reset ? "POST" : "GET", headers: authHeaders(credentials) });
|
|
7279
7648
|
if (res.status !== 200) {
|
|
7280
7649
|
console.error(errorMessage(res.body, "获取邀请链接失败。"));
|
|
7281
7650
|
return exitCodeForApiError(res.body);
|
|
@@ -7321,7 +7690,7 @@ async function contactsInvitees() {
|
|
|
7321
7690
|
console.log(`(还有更多:contacts invitees --offset ${page.nextOffset})`);
|
|
7322
7691
|
return 0;
|
|
7323
7692
|
}
|
|
7324
|
-
var STORAGE_SOURCES = ["files", "media", "mail"];
|
|
7693
|
+
var STORAGE_SOURCES = ["files", "media", "mail", "collab", "agent"];
|
|
7325
7694
|
var STORAGE_TYPES = ["doc", "image", "video", "archive", "code"];
|
|
7326
7695
|
var MIME_BY_EXT = {
|
|
7327
7696
|
png: "image/png",
|
|
@@ -7358,9 +7727,10 @@ function formatBytesCli(n) {
|
|
|
7358
7727
|
return `${n} B`;
|
|
7359
7728
|
}
|
|
7360
7729
|
function printStorageRow(o) {
|
|
7730
|
+
const encFlag = o.isEncrypted ? o.wrappedDataKey?.startsWith("u1.") ? "保险箱" : "平台加密" : null;
|
|
7361
7731
|
const flags = [
|
|
7362
|
-
o.source !== "files" ? o.source === "media" ? "素材" : "邮件附件" : null,
|
|
7363
|
-
|
|
7732
|
+
o.source !== "files" ? o.source === "media" ? "素材" : o.source === "mail" ? "邮件附件" : o.source === "collab" ? "协作" : "Agent 产物" : null,
|
|
7733
|
+
encFlag,
|
|
7364
7734
|
o.isPublic ? "公开" : null
|
|
7365
7735
|
].filter(Boolean);
|
|
7366
7736
|
console.log(`${o.id} ${o.filename} ${formatBytesCli(o.sizeBytes)} ${formatTime(o.createdAt)}${flags.length > 0 ? ` [${flags.join(" · ")}]` : ""}`);
|
|
@@ -7430,6 +7800,15 @@ async function storageList() {
|
|
|
7430
7800
|
const tags = readFlag("--tag");
|
|
7431
7801
|
if (tags)
|
|
7432
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
|
+
}
|
|
7433
7812
|
if (hasFlag("--trash"))
|
|
7434
7813
|
params.set("trash", "1");
|
|
7435
7814
|
if (hasFlag("--shared"))
|
|
@@ -7688,7 +8067,7 @@ async function driveMasterKey(credentials) {
|
|
|
7688
8067
|
return raw;
|
|
7689
8068
|
return importAesKey(raw);
|
|
7690
8069
|
}
|
|
7691
|
-
async function storageUploadOne(credentials, filePath) {
|
|
8070
|
+
async function storageUploadOne(credentials, filePath, encrypt) {
|
|
7692
8071
|
let buf;
|
|
7693
8072
|
try {
|
|
7694
8073
|
buf = readFileSync2(filePath);
|
|
@@ -7698,22 +8077,26 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7698
8077
|
}
|
|
7699
8078
|
const filename = basename(filePath);
|
|
7700
8079
|
const mimeType = mimeFor(filename);
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
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
|
+
}
|
|
7709
8092
|
const presign = await api2(credentials.apiBase, "/v1/storage/presign", {
|
|
7710
8093
|
method: "POST",
|
|
7711
8094
|
headers: authHeaders(credentials),
|
|
7712
|
-
body: JSON.stringify({ filename, mimeType, sizeBytes:
|
|
8095
|
+
body: JSON.stringify({ filename, mimeType, sizeBytes: body.byteLength, enc: wrappedDataKey !== null })
|
|
7713
8096
|
});
|
|
7714
8097
|
if (presign.status === 200 || presign.status === 201) {
|
|
7715
8098
|
const { fileId, uploadUrl } = presign.body.data;
|
|
7716
|
-
const put = await fetch(uploadUrl, { method: "PUT", body
|
|
8099
|
+
const put = await fetch(uploadUrl, { method: "PUT", body, headers: { "content-type": mimeType } });
|
|
7717
8100
|
if (!put.ok) {
|
|
7718
8101
|
console.error(`直传失败(${put.status}):${filename}`);
|
|
7719
8102
|
return 1;
|
|
@@ -7721,7 +8104,7 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7721
8104
|
const complete = await api2(credentials.apiBase, `/v1/storage/${encodeURIComponent(fileId)}/complete`, {
|
|
7722
8105
|
method: "POST",
|
|
7723
8106
|
headers: authHeaders(credentials),
|
|
7724
|
-
body: JSON.stringify({ isEncrypted: true, wrappedDataKey })
|
|
8107
|
+
body: JSON.stringify(wrappedDataKey ? { isEncrypted: true, wrappedDataKey } : {})
|
|
7725
8108
|
});
|
|
7726
8109
|
if (complete.status !== 200 && complete.status !== 201) {
|
|
7727
8110
|
console.error(errorMessage(complete.body, `记账失败:${filename}`));
|
|
@@ -7731,28 +8114,30 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7731
8114
|
if (hasFlag("--json"))
|
|
7732
8115
|
console.log(JSON.stringify(object, null, 2));
|
|
7733
8116
|
else
|
|
7734
|
-
console.log(`✓
|
|
8117
|
+
console.log(`✓ 已上传${wrappedDataKey ? "(已加密)" : ""}:${object.filename}(id ${object.id})`);
|
|
7735
8118
|
return 0;
|
|
7736
8119
|
}
|
|
7737
8120
|
if (presign.body?.error?.code === "presign_unavailable") {
|
|
7738
8121
|
const form = new FormData;
|
|
7739
|
-
form.append("file", new Blob([
|
|
7740
|
-
|
|
7741
|
-
|
|
8122
|
+
form.append("file", new Blob([body], { type: mimeType }), filename);
|
|
8123
|
+
if (wrappedDataKey) {
|
|
8124
|
+
form.append("enc", "1");
|
|
8125
|
+
form.append("wrappedDataKey", wrappedDataKey);
|
|
8126
|
+
}
|
|
7742
8127
|
const res = await fetch(`${credentials.apiBase}/v1/storage`, {
|
|
7743
8128
|
method: "POST",
|
|
7744
8129
|
headers: authHeaders(credentials),
|
|
7745
8130
|
body: form
|
|
7746
8131
|
});
|
|
7747
|
-
const
|
|
8132
|
+
const body2 = await res.json().catch(() => ({}));
|
|
7748
8133
|
if (res.status !== 200 && res.status !== 201) {
|
|
7749
|
-
console.error(
|
|
7750
|
-
return exitCodeForApiError(
|
|
8134
|
+
console.error(body2.error?.message ?? `上传失败:${filename}`);
|
|
8135
|
+
return exitCodeForApiError(body2);
|
|
7751
8136
|
}
|
|
7752
8137
|
if (hasFlag("--json"))
|
|
7753
|
-
console.log(JSON.stringify(
|
|
8138
|
+
console.log(JSON.stringify(body2.data, null, 2));
|
|
7754
8139
|
else
|
|
7755
|
-
console.log(`✓
|
|
8140
|
+
console.log(`✓ 已上传${wrappedDataKey ? "(已加密)" : ""}:${body2.data?.filename}(id ${body2.data?.id})`);
|
|
7756
8141
|
return 0;
|
|
7757
8142
|
}
|
|
7758
8143
|
console.error(errorMessage(presign.body, `上传失败:${filename}`));
|
|
@@ -7760,16 +8145,21 @@ async function storageUploadOne(credentials, filePath) {
|
|
|
7760
8145
|
}
|
|
7761
8146
|
async function storageUpload(paths) {
|
|
7762
8147
|
const files = [];
|
|
8148
|
+
let encrypt = false;
|
|
7763
8149
|
for (let i = 0;i < paths.length; i++) {
|
|
7764
8150
|
if (paths[i].startsWith("--")) {
|
|
7765
|
-
if (paths[i] === "--passphrase")
|
|
8151
|
+
if (paths[i] === "--passphrase") {
|
|
8152
|
+
encrypt = true;
|
|
7766
8153
|
i++;
|
|
8154
|
+
}
|
|
7767
8155
|
continue;
|
|
7768
8156
|
}
|
|
7769
8157
|
files.push(paths[i]);
|
|
7770
8158
|
}
|
|
8159
|
+
if (!encrypt && process.env.CRAB_PASSPHRASE)
|
|
8160
|
+
encrypt = true;
|
|
7771
8161
|
if (files.length === 0) {
|
|
7772
|
-
console.error("用法:crab storage upload <file...> [--passphrase 口令] [--json]");
|
|
8162
|
+
console.error("用法:crab storage upload <file...> [--passphrase 口令] [--json](缺省明文上传;带口令则端到端加密)");
|
|
7773
8163
|
return EXIT_USAGE;
|
|
7774
8164
|
}
|
|
7775
8165
|
const credentials = requireCredentials();
|
|
@@ -7777,7 +8167,7 @@ async function storageUpload(paths) {
|
|
|
7777
8167
|
return credentials;
|
|
7778
8168
|
let code = 0;
|
|
7779
8169
|
for (const file of files) {
|
|
7780
|
-
const result = await storageUploadOne(credentials, file);
|
|
8170
|
+
const result = await storageUploadOne(credentials, file, encrypt);
|
|
7781
8171
|
if (result !== 0 && code === 0)
|
|
7782
8172
|
code = result;
|
|
7783
8173
|
if (result === EXIT_AUTH || result === EXIT_SCOPE)
|
|
@@ -8034,7 +8424,8 @@ async function storageShares(token) {
|
|
|
8034
8424
|
}
|
|
8035
8425
|
for (const s of rows) {
|
|
8036
8426
|
const state = s.revokedAt ? "已撤销" : s.expiresAt !== null && s.expiresAt <= Date.now() ? "已过期" : "生效中";
|
|
8037
|
-
|
|
8427
|
+
const srcLabel = { drive: "云盘", mail: "邮件引用", collab: "协作引用", api: "API" }[s.source ?? "drive"];
|
|
8428
|
+
console.log(`${s.id} …${s.token.slice(-12)} ${state} ${srcLabel} 下载 ${s.downloadCount} 次 ${formatTime(s.createdAt)}${s.hasPasscode ? " [提取码]" : ""}`);
|
|
8038
8429
|
}
|
|
8039
8430
|
return 0;
|
|
8040
8431
|
}
|
|
@@ -8072,9 +8463,10 @@ async function storageQuota() {
|
|
|
8072
8463
|
return 0;
|
|
8073
8464
|
}
|
|
8074
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);
|
|
8075
8467
|
console.log(`已用 ${formatBytesCli(quota.usedBytes)} / ${formatBytesCli(quota.quotaBytes)}(${pct}%)`);
|
|
8076
|
-
console.log(` 文件 ${formatBytesCli(quota.bySource.files)} · 素材 ${formatBytesCli(quota.bySource.media)} ·
|
|
8077
|
-
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}`);
|
|
8078
8470
|
return 0;
|
|
8079
8471
|
}
|
|
8080
8472
|
async function storageCategories() {
|
|
@@ -8230,6 +8622,35 @@ async function storageTags() {
|
|
|
8230
8622
|
console.log(`${row.tag} (${row.count})`);
|
|
8231
8623
|
return 0;
|
|
8232
8624
|
}
|
|
8625
|
+
var STORAGE_ACTIVITY_LABELS = {
|
|
8626
|
+
"object.read": "读取",
|
|
8627
|
+
"object.uploaded": "上传",
|
|
8628
|
+
"object.deleted": "删除",
|
|
8629
|
+
"share.created": "建分享"
|
|
8630
|
+
};
|
|
8631
|
+
async function storageActivity() {
|
|
8632
|
+
const credentials = requireCredentials();
|
|
8633
|
+
if (typeof credentials === "number")
|
|
8634
|
+
return credentials;
|
|
8635
|
+
const res = await api2(credentials.apiBase, "/v1/storage/agent-activity", { headers: authHeaders(credentials) });
|
|
8636
|
+
if (res.status !== 200) {
|
|
8637
|
+
console.error(errorMessage(res.body, "获取云盘活动失败。"));
|
|
8638
|
+
return exitCodeForApiError(res.body);
|
|
8639
|
+
}
|
|
8640
|
+
const activity = res.body.data.activity;
|
|
8641
|
+
if (hasFlag("--json")) {
|
|
8642
|
+
console.log(JSON.stringify(activity, null, 2));
|
|
8643
|
+
return 0;
|
|
8644
|
+
}
|
|
8645
|
+
if (activity.length === 0) {
|
|
8646
|
+
console.log("还没有云盘操作记录。");
|
|
8647
|
+
return 0;
|
|
8648
|
+
}
|
|
8649
|
+
for (const entry of activity) {
|
|
8650
|
+
console.log(`${formatTime(entry.createdAt)} ${(STORAGE_ACTIVITY_LABELS[entry.action] ?? entry.action).padEnd(4)} ${entry.actorType === "agent" ? "agent" : "user "} ${entry.actorLabel ?? "—"} ${entry.objectName || entry.objectId}`);
|
|
8651
|
+
}
|
|
8652
|
+
return 0;
|
|
8653
|
+
}
|
|
8233
8654
|
var COLLAB_TASK_STATUSES = ["open", "done"];
|
|
8234
8655
|
function collabSpaceToken(space) {
|
|
8235
8656
|
return space.kind === "default" ? "default" : `space:${space.name}`;
|
|
@@ -8306,7 +8727,7 @@ async function collabSpacesList() {
|
|
|
8306
8727
|
}
|
|
8307
8728
|
for (const s of data.spaces) {
|
|
8308
8729
|
const storage = s.kind === "independent" ? ` · 存储 ${formatBytesCli(s.storageUsedBytes)}/${formatBytesCli(s.storageAllocatedBytes)}` : "";
|
|
8309
|
-
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}`);
|
|
8310
8731
|
console.log(` id ${s.id}`);
|
|
8311
8732
|
}
|
|
8312
8733
|
console.log(`独立空间名额:${data.independentUsed}/${data.independentLimit}(创建消耗 5 积分:crab collab space create <名称>)`);
|
|
@@ -8339,7 +8760,7 @@ async function collabSpaceCreate(name) {
|
|
|
8339
8760
|
return 0;
|
|
8340
8761
|
}
|
|
8341
8762
|
console.log(`✓ 独立协作空间已创建(id ${data.spaceId})`);
|
|
8342
|
-
console.log(` 消耗:${
|
|
8763
|
+
console.log(` 消耗:${fmtCredits(data.chargedCredits)} 积分 · --space space:${name} 即可指向它`);
|
|
8343
8764
|
return 0;
|
|
8344
8765
|
}
|
|
8345
8766
|
async function collabTasksList() {
|
|
@@ -8782,14 +9203,20 @@ switch (command) {
|
|
|
8782
9203
|
process.exit(await mailList());
|
|
8783
9204
|
if (sub === "read")
|
|
8784
9205
|
process.exit(await mailRead(args[2]));
|
|
9206
|
+
if (sub === "unread")
|
|
9207
|
+
process.exit(await mailUnread(args[2]));
|
|
8785
9208
|
if (sub === "search")
|
|
8786
9209
|
process.exit(await mailSearch(args.slice(2).join(" ") || undefined));
|
|
8787
9210
|
if (sub === "send")
|
|
8788
9211
|
process.exit(await mailSend());
|
|
8789
9212
|
if (sub === "archive" || sub === "trash" || sub === "delete")
|
|
8790
9213
|
process.exit(await mailMove(sub, args[2]));
|
|
9214
|
+
if (sub === "folders")
|
|
9215
|
+
process.exit(await mailFoldersOverview());
|
|
9216
|
+
if (sub === "activity")
|
|
9217
|
+
process.exit(await mailActivity());
|
|
8791
9218
|
console.error(sub ? `未知子命令:mail ${sub}
|
|
8792
|
-
` : `缺少子命令:mail list | read | search | send | archive | trash | delete
|
|
9219
|
+
` : `缺少子命令:mail list | read | unread | search | send | archive | trash | delete | folders | activity
|
|
8793
9220
|
`);
|
|
8794
9221
|
console.log(usage);
|
|
8795
9222
|
process.exit(EXIT_USAGE);
|
|
@@ -8852,8 +9279,24 @@ switch (command) {
|
|
|
8852
9279
|
process.exit(await storageUnshare(args[2]));
|
|
8853
9280
|
if (sub === "key")
|
|
8854
9281
|
process.exit(await storageKeyCmd());
|
|
9282
|
+
if (sub === "activity")
|
|
9283
|
+
process.exit(await storageActivity());
|
|
8855
9284
|
console.error(sub ? `未知子命令:storage ${sub}
|
|
8856
|
-
` : `缺少子命令:storage ls | upload | download | rename | tag | category | publish | unpublish | rm | restore | purge | quota | categories | mkcat | rencat | rmcat | tags | share | shares | unshare | key
|
|
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
|
|
8857
9300
|
`);
|
|
8858
9301
|
console.log(usage);
|
|
8859
9302
|
process.exit(EXIT_USAGE);
|
|
@@ -8928,3 +9371,73 @@ switch (command) {
|
|
|
8928
9371
|
console.log(usage);
|
|
8929
9372
|
process.exit(EXIT_USAGE);
|
|
8930
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
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: crabcloud
|
|
3
|
-
description: Manage the user's Crab Cloud personal cloud account and agent tokens through the `crab` CLI — check identity/subscription/credits, list and revoke authorized agent tokens, and re-authorize with specific scopes or scope groups. Use this skill whenever the user asks about their Crab Cloud account ("我的账号信息", "who am I on crabcloud"), wants to see or clean up authorized agents/tokens ("看看我授权了哪些 agent", "revoke that old token", "撤销那个旧令牌"), manages their contacts ("我的联系人", "把 zhangsan 加到联系人", "我的邀请码"), or asks how to connect their coding agent to Crab Cloud — even if they never say "crab" or "Crab Cloud" explicitly. Contacts are managed with `crab contacts …` in this skill; email lives in the crabcloud-mail skill (crab mail …)
|
|
3
|
+
description: Manage the user's Crab Cloud personal cloud account and agent tokens through the `crab` CLI — check identity/subscription/credits, list and revoke authorized agent tokens, and re-authorize with specific scopes or scope groups. Use this skill whenever the user asks about their Crab Cloud account ("我的账号信息", "who am I on crabcloud"), wants to see or clean up authorized agents/tokens ("看看我授权了哪些 agent", "revoke that old token", "撤销那个旧令牌"), manages their contacts ("我的联系人", "把 zhangsan 加到联系人", "我的邀请码"), or asks how to connect their coding agent to Crab Cloud — even if they never say "crab" or "Crab Cloud" explicitly. Contacts are managed with `crab contacts …` in this skill; email lives in the crabcloud-mail skill (crab mail …), the Drive in crabcloud-storage (crab storage …), and collab in crabcloud-collab (crab collab …).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Crab Cloud(平台 / 账号域)
|
|
@@ -10,8 +10,8 @@ Crab Cloud 是用户的个人云底座:账号、订阅与积分是平台层,
|
|
|
10
10
|
(`~/.config/crabcloud/credentials.json`,0600),服务端只存令牌哈希、全程审计。
|
|
11
11
|
本 skill 覆盖**平台、账号域与联系人**(`crab contacts …`);邮箱见
|
|
12
12
|
`crabcloud-mail` skill(`crab mail …`),
|
|
13
|
-
云盘见 `crabcloud-storage` skill(`crab storage
|
|
14
|
-
|
|
13
|
+
云盘见 `crabcloud-storage` skill(`crab storage …`);协作见
|
|
14
|
+
`crabcloud-collab` skill(`crab collab …`)。
|
|
15
15
|
|
|
16
16
|
## 第一步:确认 CLI 可用
|
|
17
17
|
|
|
@@ -45,7 +45,7 @@ Crab Cloud 是用户的个人云底座:账号、订阅与积分是平台层,
|
|
|
45
45
|
|
|
46
46
|
积分命令需 `credits.read` scope(`account` 组含它):退出码 4 报缺该 scope 时,
|
|
47
47
|
引导 `crab login --scopes account` 重新授权。积分额度一律以**积分数**表述
|
|
48
|
-
|
|
48
|
+
(**1 积分 = $1**,仅价格类口径用美元);充值/下单是 Web 会话能力,CLI/Agent
|
|
49
49
|
令牌不动账——用户要充值时引导网页账单页(crabcloud.cc/account/billing)。
|
|
50
50
|
拿不准令牌 id 时先 `crab tokens list` 核对;`revoke` 只接受列表里的 id。
|
|
51
51
|
|
|
@@ -76,11 +76,11 @@ Crab Cloud 是用户的个人云底座:账号、订阅与积分是平台层,
|
|
|
76
76
|
不要怂恿用户一次性给全量 scope。`--scopes` 支持组名(`mail` = 读写搜删四项
|
|
77
77
|
整组)与单个 scope 混用;组目录见 `crab help`,可用组:account / mail /
|
|
78
78
|
storage(云盘,含素材与附件)/ collab / contacts。
|
|
79
|
-
-
|
|
80
|
-
用网页(crabcloud.cc),不要编造命令或输出;联系人已上线,用 `crab contacts`
|
|
79
|
+
- 如实转述能力边界,不要编造命令或输出:联系人用 `crab contacts`
|
|
81
80
|
(list / add / rm / invite / invitees;scope contacts.read/write,程序消费加
|
|
82
|
-
`--json`)处理;邮箱请求转交 `crabcloud-mail`
|
|
83
|
-
skill
|
|
81
|
+
`--json`)处理;邮箱请求转交 `crabcloud-mail` skill,云盘请求转交
|
|
82
|
+
`crabcloud-storage` skill,协作请求转交 `crabcloud-collab` skill
|
|
83
|
+
(`crab collab …`,已上线)。
|
|
84
84
|
- 账号安全操作(修改密码、登录设备/会话的查看与吊销)**有意不开放给 agent
|
|
85
85
|
通道**:改密对令牌持有者是账号接管面,会话属于"人的浏览器会话"。用户让
|
|
86
86
|
agent 做这些时,如实说明并引导到网页个人中心(crabcloud.cc/account)操作,
|
|
@@ -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 互补。
|
|
@@ -83,6 +83,10 @@ skill 覆盖协作域的 agent 通道;账号/令牌管理见 `crabcloud` skill
|
|
|
83
83
|
补一条更正记录,不要试图「撤回」。
|
|
84
84
|
- **摘要是可变当前态**:表达「现在进展到哪」,不是流水账;每次推送记录后若
|
|
85
85
|
摘要已过时,顺手 `crab collab task summary` 刷新,让不看全程的人一眼看懂。
|
|
86
|
+
- **空间级管理走网页端**:改名/归档/调整自分配存储/扩容(任务+20、成员+5)/
|
|
87
|
+
移除成员是 owner 的网页操作,Agent 令牌一律被服务端拒绝——用户要这些时如实
|
|
88
|
+
说明并引导网页端,**不要换姿势重试**;建独立空间(`collab space create`)与
|
|
89
|
+
邀请成员(`collab invite`)例外,Agent 可执行。
|
|
86
90
|
- **建独立空间是付费动作(5 积分)**:先向用户复述名称与自分配存储(默认
|
|
87
91
|
500MB,占订阅总池),确认后再执行;`collab space create` 不要当作顺手操作。
|
|
88
92
|
- **邀请有前置**:目标必须是联系人簿里已关联的平台用户(`crab contacts add`
|
|
@@ -90,6 +94,8 @@ skill 覆盖协作域的 agent 通道;账号/令牌管理见 `crabcloud` skill
|
|
|
90
94
|
系人,不要换名字硬试。
|
|
91
95
|
- **附件引用不复制**:`--attach` 的 fileId 是云盘/空间已有文件的引用(≤10 个),
|
|
92
96
|
只能引用空间成员的云盘文件或本空间直传文件;跨空间文件会被 403 拒绝。
|
|
97
|
+
**只接受明文 / 平台加密(p1)对象,保险箱(u1) 文件引用会被拒**——见
|
|
98
|
+
`crabcloud-storage` skill。
|
|
93
99
|
- 单任务记录流上限 500 条(状态变更也占位):任务粒度别太粗,做完就 `done`,
|
|
94
100
|
新工作开新任务。
|
|
95
101
|
- 读记录流对 Agent 是一等审计事件(thread.read 入活动流)——正常读即可,但
|
|
@@ -7,8 +7,8 @@ description: Read, search and send the user's Crab Cloud platform email (usernam
|
|
|
7
7
|
|
|
8
8
|
用户的 Crab Cloud 账号自带平台邮箱 `username@crabcloud.cc`(用户名即地址本地部分)。
|
|
9
9
|
本 skill 覆盖邮箱域的 agent 通道;账号/令牌管理见 `crabcloud` skill。云盘见
|
|
10
|
-
`crabcloud-storage` skill(`crab storage
|
|
11
|
-
|
|
10
|
+
`crabcloud-storage` skill(`crab storage …`),协作见 `crabcloud-collab` skill
|
|
11
|
+
(`crab collab …`)。
|
|
12
12
|
|
|
13
13
|
## 第一步:确认可用
|
|
14
14
|
|
|
@@ -32,6 +32,9 @@ description: Read, search and send the user's Crab Cloud platform email (usernam
|
|
|
32
32
|
| 只看未读 | `crab mail list --unread` |
|
|
33
33
|
| 翻页 | `crab mail list --cursor <nextCursor>`(列表末尾会提示) |
|
|
34
34
|
| 读某封邮件 | `crab mail read <id>`(自动置已读;正文 + 操作记录) |
|
|
35
|
+
| 标回未读 | `crab mail unread <id>` |
|
|
36
|
+
| 看各文件夹总量/未读、本月收发 | `crab mail folders`(判断「有没有新邮件」先看这个,别翻列表) |
|
|
37
|
+
| 看邮箱操作流(谁读过/谁代发) | `crab mail activity`(最近 50 条,审计源) |
|
|
35
38
|
| 找邮件 | `crab mail search <关键词>`(主题/发件人/收件人/正文,不含废纸篓) |
|
|
36
39
|
| 发邮件 | `crab mail send --to a@x.cc --subject "..." --body "..."`(长正文用 `--body-file`) |
|
|
37
40
|
| 发邮件带附件 | `crab mail send … --attach <云盘id\|文件名>[,…]`(引用云盘已有对象,不复制,≤10 个;先 `crab storage ls` 核对对象,文件名需精确唯一命中) |
|
|
@@ -44,7 +47,7 @@ description: Read, search and send the user's Crab Cloud platform email (usernam
|
|
|
44
47
|
## 计费口径(必须如实转述)
|
|
45
48
|
|
|
46
49
|
- **平台内互发免费**:收件人也是 `@crabcloud.cc` 地址时不消耗积分。
|
|
47
|
-
- **外部出站
|
|
50
|
+
- **外部出站 0.02 积分/封($0.02)**:发送前向用户复述收件人与计费;退出码 7 = 积分不足,
|
|
48
51
|
告知用户不要自动重试。
|
|
49
52
|
- 发送结果会报告 `消耗积分` 与失败数;部分失败时失败部分的积分自动退回
|
|
50
53
|
(`发送:partial` 标记)。
|
|
@@ -57,7 +60,9 @@ description: Read, search and send the user's Crab Cloud platform email (usernam
|
|
|
57
60
|
`id/folder/fromAddr/to/subject/snippet/isUnread/hasAttachments/createdAt`;
|
|
58
61
|
- `mail read --json` → 完整正文 + `attachments`(id/filename/mimeType/sizeBytes)
|
|
59
62
|
+ `actors`(谁读过/谁代发,审计源);
|
|
60
|
-
- `mail send --json` → `{ message, chargedCredits, failedExternal }
|
|
63
|
+
- `mail send --json` → `{ message, chargedCredits, failedExternal }`;
|
|
64
|
+
- `mail folders --json` → `{ address, folders: [{ folder, total, unread }], month: { received, sent, outbound } }`;
|
|
65
|
+
- `mail activity --json` → 条目含 `action(read/drafted/sent)/ actorType / actorLabel / subject / messageId / createdAt`。
|
|
61
66
|
|
|
62
67
|
## 纪律
|
|
63
68
|
|
|
@@ -71,5 +76,6 @@ description: Read, search and send the user's Crab Cloud platform email (usernam
|
|
|
71
76
|
- 附件:`mail read` 输出附件清单(id/文件名/类型/体积);附件内容归云盘管
|
|
72
77
|
——下载用 `crab storage download <id>`(见 `crabcloud-storage` skill,跨来
|
|
73
78
|
源通用),发送引用云盘对象用 `mail send --attach`(对象须未被其他邮件绑
|
|
74
|
-
|
|
75
|
-
|
|
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,8 @@ 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`(五来源分项 + 保险箱计数 + 回收站占用 + 对象计数) |
|
|
53
|
+
| 看云盘操作流 | `crab storage activity`(读取/上传/删除/建分享,最近 50 条) |
|
|
50
54
|
|
|
51
55
|
`<id|文件名>`:文件名全库精确唯一命中时自动解析为 id(回收站内同样可解析,
|
|
52
56
|
restore/purge 直接用文件名即可);命中多个或查不到时按字面当 id 用——拿不准
|
|
@@ -58,16 +62,20 @@ restore/purge 直接用文件名即可);命中多个或查不到时按字面
|
|
|
58
62
|
程序化消费**始终加 `--json`**:
|
|
59
63
|
|
|
60
64
|
- `storage ls --json` → `{ objects: [...], nextOffset, total }`,条目含
|
|
61
|
-
`id / source / filename / mimeType / sizeBytes / categoryId / tags / isPublic / createdAt
|
|
62
|
-
|
|
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`);
|
|
69
|
+
- `storage activity --json` → 条目含 `action(object.read/object.uploaded/object.deleted/share.created)/ actorType / actorLabel / objectId / objectName / createdAt`;
|
|
63
70
|
- `upload` / `categories` / `tags` / `shares` 的 `--json` 同构(单对象 / 分类数组 /
|
|
64
71
|
标签数组 / 分享数组)。
|
|
65
72
|
|
|
66
|
-
##
|
|
73
|
+
## 保险箱口令与本机钥匙
|
|
67
74
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
保险箱(u1)是端到端加密:服务器只有口令包裹态的主密钥,**明文密钥只存在于
|
|
76
|
+
用户本机**——CLI 不可能也不允许向服务器索取。**只有涉及保险箱文件的命令才
|
|
77
|
+
需要口令**(带口令 upload / download 或 share u1 对象;明文与 p1 平台加密对象
|
|
78
|
+
全程免口令)。口令来源优先级:
|
|
71
79
|
|
|
72
80
|
1. `--passphrase <口令>`(flag)
|
|
73
81
|
2. 环境变量 `CRAB_PASSPHRASE`(**Agent/脚本推荐**——非交互终端不会弹提问,
|
|
@@ -94,6 +102,9 @@ download / share 加密文件)需要云盘口令,来源优先级:
|
|
|
94
102
|
等待即可;凭链接下载时提取码用 `--passcode` 正确携带。
|
|
95
103
|
- **purge 是彻底删除**,不可恢复;用户没明说「彻底删除」时一律用 `rm`
|
|
96
104
|
(回收站保留 7 天可找回)。
|
|
105
|
+
- **保险箱(u1)不能被其他应用引用**:邮件附件(`mail send --attach`)与协作
|
|
106
|
+
记录引用都只接受明文 / p1 平台加密对象,u1 对象会被明确拒绝——需要跨应用
|
|
107
|
+
使用时用分享链接(u1 链接自带密钥片段),或等互转功能;不要尝试绕过。
|
|
97
108
|
- 邮件附件与用户上传同账:水位告急时用 `quota` 分项定位来源再建议清理。
|
|
98
109
|
- 公开直链的呈现方式由服务端策略决定(图片/视频/音频/PDF/纯文本内联,SVG 与
|
|
99
110
|
其他类型强制下载),CLI 侧没有、也不需要有开关。
|
package/package.json
CHANGED