@neta-art/cohub-cli 2.1.0 → 2.2.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.
|
@@ -5,6 +5,7 @@ const PRODUCT_STATUSES = ["draft", "active"];
|
|
|
5
5
|
const PRODUCT_UPDATE_STATUSES = ["draft", "active", "archived"];
|
|
6
6
|
const PRODUCT_VISIBILITIES = ["public", "private"];
|
|
7
7
|
const BENEFIT_STATUSES = ["active", "archived"];
|
|
8
|
+
const MIN_PRODUCT_AMOUNT_USD = 0.5;
|
|
8
9
|
function requireText(value, label, flag) {
|
|
9
10
|
const text = value?.trim();
|
|
10
11
|
if (text)
|
|
@@ -35,11 +36,13 @@ function parseInteger(value, label, options) {
|
|
|
35
36
|
function parseAmountUsd(value) {
|
|
36
37
|
const text = requireText(value, "amount", "--amount-usd <amount>");
|
|
37
38
|
if (!/^(?:\d+|\d+\.\d{1,2}|\.\d{1,2})$/.test(text)) {
|
|
38
|
-
return error("Invalid amount", "--amount-usd must be a
|
|
39
|
+
return error("Invalid amount", "--amount-usd must be a USD amount with at most 2 decimals.");
|
|
39
40
|
}
|
|
40
41
|
const amount = Number(text);
|
|
41
42
|
if (!Number.isFinite(amount))
|
|
42
43
|
return error("Invalid amount", "--amount-usd must be a finite USD amount.");
|
|
44
|
+
if (amount < MIN_PRODUCT_AMOUNT_USD)
|
|
45
|
+
return error("Invalid amount", "--amount-usd must be at least 0.5.");
|
|
43
46
|
return amount;
|
|
44
47
|
}
|
|
45
48
|
function parseMetadataJson(value) {
|
|
@@ -83,25 +86,39 @@ function printProducts(products) {
|
|
|
83
86
|
status: product.status,
|
|
84
87
|
visibility: product.visibility,
|
|
85
88
|
price: formatUsd(product.pricing.amountUsd),
|
|
89
|
+
credits: product.display.creditsAmount ?? "",
|
|
86
90
|
})), [
|
|
87
91
|
{ key: "key", label: "Key" },
|
|
88
92
|
{ key: "name", label: "Name" },
|
|
89
93
|
{ key: "status", label: "Status" },
|
|
90
94
|
{ key: "visibility", label: "Visibility" },
|
|
91
95
|
{ key: "price", label: "Price" },
|
|
96
|
+
{ key: "credits", label: "Credits" },
|
|
92
97
|
]);
|
|
93
98
|
}
|
|
94
99
|
function printBenefits(benefits) {
|
|
95
|
-
table(benefits.map((benefit) =>
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
table(benefits.map((benefit) => {
|
|
101
|
+
let detail;
|
|
102
|
+
if (benefit.type === "credits") {
|
|
103
|
+
const config = benefit.config;
|
|
104
|
+
detail = `${config.amount} credits${config.expiresInDays != null ? ` · ${config.expiresInDays}d` : ""}`;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
detail = JSON.stringify(benefit.config.metadata);
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
key: benefit.key,
|
|
111
|
+
name: benefit.name,
|
|
112
|
+
type: benefit.type,
|
|
113
|
+
status: benefit.status,
|
|
114
|
+
detail,
|
|
115
|
+
};
|
|
116
|
+
}), [
|
|
101
117
|
{ key: "key", label: "Key" },
|
|
102
118
|
{ key: "name", label: "Name" },
|
|
119
|
+
{ key: "type", label: "Type" },
|
|
103
120
|
{ key: "status", label: "Status" },
|
|
104
|
-
{ key: "
|
|
121
|
+
{ key: "detail", label: "Detail" },
|
|
105
122
|
]);
|
|
106
123
|
}
|
|
107
124
|
function printOrders(orders) {
|
|
@@ -303,13 +320,46 @@ Examples:
|
|
|
303
320
|
.option("--benefit-key <key>", "Benefit key override")
|
|
304
321
|
.option("--name <name>", "Benefit name")
|
|
305
322
|
.option("--description <text>", "Benefit description")
|
|
306
|
-
.option("--
|
|
323
|
+
.option("--type <type>", "Benefit type: feature, credits (default: feature)")
|
|
324
|
+
.option("--amount <n>", "Credit amount (credits type only)")
|
|
325
|
+
.option("--expires-in-days <n>", "Credit expiry in days (credits type only)")
|
|
326
|
+
.option("--metadata-json <json>", "Benefit metadata JSON object (feature type only)")
|
|
307
327
|
.option("--json", "Output as JSON")
|
|
308
328
|
.action(async (opts) => {
|
|
329
|
+
const name = requireText(opts.name, "name", "--name <name>");
|
|
330
|
+
const type = parseChoice(opts.type, "type", ["feature", "credits"]) ?? "feature";
|
|
331
|
+
if (type === "credits") {
|
|
332
|
+
const amount = parseInteger(opts.amount, "amount", { fallback: 0, min: 1 });
|
|
333
|
+
if (amount < 1)
|
|
334
|
+
return error("Missing amount", "Pass --amount <n> with a positive integer for credits benefits.");
|
|
335
|
+
const input = {
|
|
336
|
+
key: opts.benefitKey?.trim() || undefined,
|
|
337
|
+
name,
|
|
338
|
+
description: opts.description,
|
|
339
|
+
type: "credits",
|
|
340
|
+
amount,
|
|
341
|
+
expiresInDays: opts.expiresInDays !== undefined
|
|
342
|
+
? parseInteger(opts.expiresInDays, "expires-in-days", { fallback: 0, min: 1 })
|
|
343
|
+
: undefined,
|
|
344
|
+
};
|
|
345
|
+
const { commerce } = commerceClient(spacesCmd);
|
|
346
|
+
try {
|
|
347
|
+
const result = await commerce.createBenefit(input);
|
|
348
|
+
if (jsonRequested(opts))
|
|
349
|
+
return outJson(result);
|
|
350
|
+
ok(`Benefit created: ${result.benefit.key}`);
|
|
351
|
+
printBenefits([result.benefit]);
|
|
352
|
+
}
|
|
353
|
+
catch (e) {
|
|
354
|
+
handleHttp(e);
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
309
358
|
const input = {
|
|
310
359
|
key: opts.benefitKey?.trim() || undefined,
|
|
311
|
-
name
|
|
360
|
+
name,
|
|
312
361
|
description: opts.description,
|
|
362
|
+
type: "feature",
|
|
313
363
|
metadata: parseMetadataJson(opts.metadataJson),
|
|
314
364
|
};
|
|
315
365
|
const { commerce } = commerceClient(spacesCmd);
|
|
@@ -28,26 +28,27 @@ function printProducts(products) {
|
|
|
28
28
|
status: product.status,
|
|
29
29
|
visibility: product.visibility,
|
|
30
30
|
price: formatUsd(product.pricing.amountUsd),
|
|
31
|
+
credits: product.display.creditsAmount ?? "",
|
|
31
32
|
})), [
|
|
32
33
|
{ key: "key", label: "Key" },
|
|
33
34
|
{ key: "name", label: "Name" },
|
|
34
35
|
{ key: "status", label: "Status" },
|
|
35
36
|
{ key: "visibility", label: "Visibility" },
|
|
36
37
|
{ key: "price", label: "Price" },
|
|
38
|
+
{ key: "credits", label: "Credits" },
|
|
37
39
|
]);
|
|
38
40
|
}
|
|
39
|
-
function printEntitlements(
|
|
40
|
-
table(entitlements.map((item) => ({
|
|
41
|
+
function printEntitlements(result) {
|
|
42
|
+
table(result.entitlements.map((item) => ({
|
|
41
43
|
benefitKey: item.benefitKey,
|
|
42
44
|
enabled: item.enabled ? "yes" : "no",
|
|
43
|
-
reason: item.reason,
|
|
44
45
|
metadata: item.metadata ? JSON.stringify(item.metadata) : "",
|
|
45
46
|
})), [
|
|
46
47
|
{ key: "benefitKey", label: "Benefit" },
|
|
47
48
|
{ key: "enabled", label: "Enabled" },
|
|
48
|
-
{ key: "reason", label: "Reason" },
|
|
49
49
|
{ key: "metadata", label: "Metadata" },
|
|
50
50
|
]);
|
|
51
|
+
console.log(`\nCredits available: ${result.credits.available} (net: ${result.credits.net})`);
|
|
51
52
|
}
|
|
52
53
|
function printOrder(order) {
|
|
53
54
|
table([{
|
|
@@ -71,15 +72,16 @@ function printOrder(order) {
|
|
|
71
72
|
export function registerWorkCommerce(worksCmd) {
|
|
72
73
|
const commerceCmd = worksCmd
|
|
73
74
|
.command("commerce")
|
|
74
|
-
.description("
|
|
75
|
+
.description("Work commerce operations")
|
|
75
76
|
.addHelpText("after", `
|
|
76
77
|
Examples:
|
|
77
78
|
cohub works commerce products resolve --work-id <work-id> --product-key pro_pack
|
|
78
|
-
cohub works commerce entitlements
|
|
79
|
+
cohub works commerce entitlements --work-id <work-id>
|
|
80
|
+
cohub works commerce credits consume --work-id <work-id> --amount 100
|
|
79
81
|
`);
|
|
80
82
|
const productsCmd = commerceCmd
|
|
81
83
|
.command("products")
|
|
82
|
-
.description("
|
|
84
|
+
.description("Resolve commerce products");
|
|
83
85
|
productsCmd
|
|
84
86
|
.command("resolve")
|
|
85
87
|
.description("Resolve public products for a work")
|
|
@@ -100,25 +102,64 @@ Examples:
|
|
|
100
102
|
handleHttp(e);
|
|
101
103
|
}
|
|
102
104
|
});
|
|
103
|
-
|
|
105
|
+
commerceCmd
|
|
104
106
|
.command("entitlements")
|
|
105
|
-
.description("
|
|
106
|
-
entitlementsCmd
|
|
107
|
-
.command("check")
|
|
108
|
-
.description("Check viewer entitlements for a work")
|
|
107
|
+
.description("Show viewer entitlements and credit balance for a work")
|
|
109
108
|
.option("--work-id <id>", "Work ID")
|
|
110
|
-
.option("--benefit-key <key>", "Benefit key", collectOption)
|
|
111
109
|
.option("--json", "Output as JSON")
|
|
112
110
|
.action(async (opts) => {
|
|
113
111
|
const workId = requireText(opts.workId, "work ID", "--work-id <id>");
|
|
114
|
-
const benefitKeys = requireList(opts.benefitKey, "benefit key", "--benefit-key <key>");
|
|
115
112
|
const client = createClient();
|
|
116
113
|
try {
|
|
117
|
-
const result = await client.workCommerce.
|
|
114
|
+
const result = await client.workCommerce.getEntitlements(workId);
|
|
118
115
|
if (jsonRequested(opts))
|
|
119
116
|
return outJson(result);
|
|
120
|
-
printEntitlements(result
|
|
121
|
-
|
|
117
|
+
printEntitlements(result);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
handleHttp(e);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const creditsCmd = commerceCmd
|
|
124
|
+
.command("credits")
|
|
125
|
+
.description("Consume credits for a work");
|
|
126
|
+
creditsCmd
|
|
127
|
+
.command("consume")
|
|
128
|
+
.description("Consume credits for a work (self by default)")
|
|
129
|
+
.option("--work-id <id>", "Work ID")
|
|
130
|
+
.option("--amount <n>", "Positive integer credit amount")
|
|
131
|
+
.option("--operation-id <id>", "Idempotency key (generated when omitted)")
|
|
132
|
+
.option("--reason <text>", "Reason for the consumption")
|
|
133
|
+
.option("--json", "Output as JSON")
|
|
134
|
+
.action(async (opts) => {
|
|
135
|
+
const workId = requireText(opts.workId, "work ID", "--work-id <id>");
|
|
136
|
+
const amountText = requireText(opts.amount, "amount", "--amount <n>");
|
|
137
|
+
const amount = Number.parseInt(amountText, 10);
|
|
138
|
+
if (!Number.isSafeInteger(amount) || amount <= 0) {
|
|
139
|
+
return error("Invalid amount", "--amount must be a positive integer.");
|
|
140
|
+
}
|
|
141
|
+
const operationId = opts.operationId?.trim() || crypto.randomUUID();
|
|
142
|
+
const client = createClient();
|
|
143
|
+
try {
|
|
144
|
+
const result = await client.workCommerce.consumeCredits(workId, {
|
|
145
|
+
amount,
|
|
146
|
+
operationId,
|
|
147
|
+
reason: opts.reason,
|
|
148
|
+
});
|
|
149
|
+
if (jsonRequested(opts))
|
|
150
|
+
return outJson({ ...result, operationId });
|
|
151
|
+
ok(`Consumed ${result.amount} credits (operation: ${operationId})`);
|
|
152
|
+
table([{
|
|
153
|
+
status: result.status,
|
|
154
|
+
amount: result.amount,
|
|
155
|
+
remaining: result.remaining,
|
|
156
|
+
shortfall: result.shortfall ?? "",
|
|
157
|
+
}], [
|
|
158
|
+
{ key: "status", label: "Status" },
|
|
159
|
+
{ key: "amount", label: "Amount" },
|
|
160
|
+
{ key: "remaining", label: "Remaining" },
|
|
161
|
+
{ key: "shortfall", label: "Shortfall" },
|
|
162
|
+
]);
|
|
122
163
|
}
|
|
123
164
|
catch (e) {
|
|
124
165
|
handleHttp(e);
|
|
@@ -161,7 +202,7 @@ Examples:
|
|
|
161
202
|
});
|
|
162
203
|
const ordersCmd = commerceCmd
|
|
163
204
|
.command("orders")
|
|
164
|
-
.description("
|
|
205
|
+
.description("Look up commerce orders");
|
|
165
206
|
ordersCmd
|
|
166
207
|
.command("get")
|
|
167
208
|
.description("Show a work commerce order")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "2.1
|
|
3
|
+
"version": "2.2.1",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"@neta-art/generation": "^0.1.10",
|
|
17
17
|
"commander": "^14.0.3",
|
|
18
18
|
"sharp": "^0.34.5",
|
|
19
|
-
"@neta-art/cohub": "2.1
|
|
19
|
+
"@neta-art/cohub": "2.2.1"
|
|
20
20
|
},
|
|
21
21
|
"publishConfig": {
|
|
22
22
|
"access": "public"
|