@neta-art/cohub-cli 3.10.0 → 3.10.2
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/commands/public.js +24 -5
- package/dist/commands/references.js +2 -2
- package/dist/commands/search.js +2 -2
- package/dist/commands/works.js +114 -0
- package/package.json +2 -2
package/dist/commands/public.js
CHANGED
|
@@ -169,6 +169,14 @@ function uploadFailure(errors) {
|
|
|
169
169
|
const first = errors[0]?.message ?? "Upload failed";
|
|
170
170
|
return new Error(`${first} (${errors.length} files failed)`);
|
|
171
171
|
}
|
|
172
|
+
function publicUrlPrefix(destination, entry) {
|
|
173
|
+
const encodedPath = entry.path.split("/").map(encodeURIComponent).join("/");
|
|
174
|
+
if (!entry.publicUrl.endsWith(encodedPath)) {
|
|
175
|
+
throw new Error(`Invalid public URL for ${entry.path}`);
|
|
176
|
+
}
|
|
177
|
+
const encodedDestination = destination.split("/").map(encodeURIComponent).join("/");
|
|
178
|
+
return `${entry.publicUrl.slice(0, -encodedPath.length)}${encodedDestination}`;
|
|
179
|
+
}
|
|
172
180
|
async function uploadPublic(command, source, destination, opts, deps) {
|
|
173
181
|
const client = deps.createClient?.() ?? createClient();
|
|
174
182
|
const spaceId = resolveSpace(command);
|
|
@@ -206,17 +214,28 @@ async function uploadPublic(command, source, destination, opts, deps) {
|
|
|
206
214
|
if (entryErrors.length > 0)
|
|
207
215
|
throw uploadFailure(entryErrors);
|
|
208
216
|
}
|
|
209
|
-
const
|
|
217
|
+
const firstPlan = plan.entries[0];
|
|
218
|
+
if (!firstPlan)
|
|
219
|
+
throw new Error("Upload plan contains no files");
|
|
210
220
|
if (jsonRequested(opts)) {
|
|
211
221
|
outJson({
|
|
212
|
-
uploaded: upload.files.length,
|
|
213
|
-
overwrite: Boolean(opts.overwrite),
|
|
214
222
|
destination: upload.destination,
|
|
215
|
-
|
|
223
|
+
urlPrefix: publicUrlPrefix("", firstPlan),
|
|
224
|
+
files: upload.files.map((file) => ({
|
|
225
|
+
path: file.publicPath,
|
|
226
|
+
size: file.size,
|
|
227
|
+
mimeType: file.mimeType,
|
|
228
|
+
})),
|
|
216
229
|
});
|
|
217
230
|
return;
|
|
218
231
|
}
|
|
219
|
-
|
|
232
|
+
if (entryPlan) {
|
|
233
|
+
console.log(entryPlan.publicUrl);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const fileLabel = upload.files.length === 1 ? "file" : "files";
|
|
237
|
+
console.log(`Uploaded ${upload.files.length} ${fileLabel} to ${upload.destination}`);
|
|
238
|
+
console.log(`URL prefix: ${publicUrlPrefix(upload.destination, firstPlan)}`);
|
|
220
239
|
}
|
|
221
240
|
catch (exception) {
|
|
222
241
|
handleHttp(exception);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isUuid, } from "@neta-art/cohub";
|
|
1
2
|
import { createClient } from "../client.js";
|
|
2
3
|
import { table, json as outJson, jsonRequested, error, handleHttp } from "../output.js";
|
|
3
4
|
const RESOURCE_TYPES = new Set([
|
|
@@ -22,7 +23,6 @@ const REFERENCE_KINDS = new Set([
|
|
|
22
23
|
]);
|
|
23
24
|
const DIRECTIONS = new Set(["out", "in", "both"]);
|
|
24
25
|
const GROUP_BYS = new Set(["kind", "targetType", "target", "sourceType", "day"]);
|
|
25
|
-
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
26
26
|
function parseSource(value) {
|
|
27
27
|
const idx = value.indexOf(":");
|
|
28
28
|
if (idx <= 0)
|
|
@@ -146,7 +146,7 @@ Examples:
|
|
|
146
146
|
.action(async (spaceId, opts) => {
|
|
147
147
|
const client = createClient();
|
|
148
148
|
try {
|
|
149
|
-
if (!
|
|
149
|
+
if (!isUuid(spaceId.trim()))
|
|
150
150
|
return error("Invalid space id");
|
|
151
151
|
const groupBy = (opts.groupBy ?? "kind");
|
|
152
152
|
if (!GROUP_BYS.has(groupBy))
|
package/dist/commands/search.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { isUuid } from "@neta-art/cohub";
|
|
1
2
|
import { createClient } from "../client.js";
|
|
2
3
|
import { table, json as outJson, jsonRequested, error, handleHttp } from "../output.js";
|
|
3
4
|
const DEFAULT_LIMIT = 20;
|
|
4
5
|
const MAX_TITLE_LENGTH = 72;
|
|
5
6
|
const MAX_CONTEXT_LENGTH = 42;
|
|
6
7
|
const SEARCH_TYPES = new Set(["turn", "session", "space", "label"]);
|
|
7
|
-
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
8
8
|
function clampLimit(value) {
|
|
9
9
|
const parsed = Number(value ?? DEFAULT_LIMIT);
|
|
10
10
|
if (!Number.isFinite(parsed))
|
|
@@ -40,7 +40,7 @@ function parseTypes(value) {
|
|
|
40
40
|
function parseSearchInput(opts) {
|
|
41
41
|
const types = parseTypes(opts.types);
|
|
42
42
|
const spaceId = opts.spaceId?.trim();
|
|
43
|
-
if (spaceId && !
|
|
43
|
+
if (spaceId && !isUuid(spaceId))
|
|
44
44
|
throw new Error("Invalid space id");
|
|
45
45
|
return { types, spaceId: spaceId || undefined, labelRef: opts.labelRef?.trim() || undefined };
|
|
46
46
|
}
|
package/dist/commands/works.js
CHANGED
|
@@ -87,6 +87,15 @@ function printWorkUrls(result) {
|
|
|
87
87
|
if (lines.length)
|
|
88
88
|
console.log(`\n${lines.join("\n")}`);
|
|
89
89
|
}
|
|
90
|
+
function promotionUrl(publicUrl, promotion) {
|
|
91
|
+
if (!publicUrl)
|
|
92
|
+
return null;
|
|
93
|
+
const url = new URL(publicUrl);
|
|
94
|
+
url.searchParams.set("cohub_campaign", promotion.id);
|
|
95
|
+
for (const [key, value] of Object.entries(promotion.parameters))
|
|
96
|
+
url.searchParams.set(key, value);
|
|
97
|
+
return url.toString();
|
|
98
|
+
}
|
|
90
99
|
function printWorkStats(stats) {
|
|
91
100
|
table([stats.summary], [
|
|
92
101
|
{ key: "totalViews", label: "Total" },
|
|
@@ -424,6 +433,111 @@ export function registerWorks(program) {
|
|
|
424
433
|
handleHttp(e);
|
|
425
434
|
}
|
|
426
435
|
});
|
|
436
|
+
const promotionsCmd = worksCmd.command("promotions").description("Work promotion links and analytics");
|
|
437
|
+
promotionsCmd
|
|
438
|
+
.command("list <work>")
|
|
439
|
+
.alias("ls")
|
|
440
|
+
.description("List promotion links")
|
|
441
|
+
.option("--json", "Output as JSON")
|
|
442
|
+
.action(async (workRef, opts) => {
|
|
443
|
+
const client = createClient();
|
|
444
|
+
try {
|
|
445
|
+
const detail = await getWorkByRef(client, workRef);
|
|
446
|
+
const result = await client.works.listPromotions(detail.work.id);
|
|
447
|
+
const promotions = result.promotions.map((promotion) => ({
|
|
448
|
+
...promotion,
|
|
449
|
+
url: promotionUrl(detail.publicUrl, promotion),
|
|
450
|
+
}));
|
|
451
|
+
if (jsonRequested(opts))
|
|
452
|
+
return outJson({ ...result, promotions });
|
|
453
|
+
table(promotions, [
|
|
454
|
+
{ key: "name", label: "Name" },
|
|
455
|
+
{ key: "provider", label: "Provider" },
|
|
456
|
+
{ key: "id", label: "ID" },
|
|
457
|
+
{ key: "url", label: "URL" },
|
|
458
|
+
]);
|
|
459
|
+
}
|
|
460
|
+
catch (e) {
|
|
461
|
+
handleHttp(e);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
promotionsCmd
|
|
465
|
+
.command("create <work>")
|
|
466
|
+
.description("Create an immutable promotion link")
|
|
467
|
+
.requiredOption("--name <name>", "Promotion name")
|
|
468
|
+
.option("--provider <provider>", "Promotion provider", "generic")
|
|
469
|
+
.option("--utm-id <value>", "UTM campaign ID")
|
|
470
|
+
.option("--utm-source <value>", "UTM source")
|
|
471
|
+
.option("--utm-medium <value>", "UTM medium")
|
|
472
|
+
.option("--utm-campaign <value>", "UTM campaign")
|
|
473
|
+
.option("--utm-term <value>", "UTM term")
|
|
474
|
+
.option("--utm-content <value>", "UTM content")
|
|
475
|
+
.option("--json", "Output as JSON")
|
|
476
|
+
.action(async (workRef, opts) => {
|
|
477
|
+
const client = createClient();
|
|
478
|
+
try {
|
|
479
|
+
const detail = await getWorkByRef(client, workRef);
|
|
480
|
+
const parameters = Object.fromEntries(Object.entries({
|
|
481
|
+
utm_id: opts.utmId,
|
|
482
|
+
utm_source: opts.utmSource,
|
|
483
|
+
utm_medium: opts.utmMedium,
|
|
484
|
+
utm_campaign: opts.utmCampaign,
|
|
485
|
+
utm_term: opts.utmTerm,
|
|
486
|
+
utm_content: opts.utmContent,
|
|
487
|
+
}).filter((entry) => typeof entry[1] === "string"));
|
|
488
|
+
const result = await client.works.createPromotion(detail.work.id, {
|
|
489
|
+
name: opts.name,
|
|
490
|
+
provider: opts.provider,
|
|
491
|
+
parameters,
|
|
492
|
+
});
|
|
493
|
+
const url = promotionUrl(detail.publicUrl, result.promotion);
|
|
494
|
+
if (jsonRequested(opts))
|
|
495
|
+
return outJson({ ...result, url });
|
|
496
|
+
ok("Promotion created");
|
|
497
|
+
table([{ ...result.promotion, url }], [
|
|
498
|
+
{ key: "name", label: "Name" },
|
|
499
|
+
{ key: "provider", label: "Provider" },
|
|
500
|
+
{ key: "id", label: "ID" },
|
|
501
|
+
{ key: "url", label: "URL" },
|
|
502
|
+
]);
|
|
503
|
+
}
|
|
504
|
+
catch (e) {
|
|
505
|
+
handleHttp(e);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
promotionsCmd
|
|
509
|
+
.command("stats <work> <promotionId>")
|
|
510
|
+
.description("Show promotion analytics")
|
|
511
|
+
.option("--json", "Output as JSON")
|
|
512
|
+
.action(async (workRef, promotionId, opts) => {
|
|
513
|
+
const client = createClient();
|
|
514
|
+
try {
|
|
515
|
+
const detail = await getWorkByRef(client, workRef);
|
|
516
|
+
const result = await client.works.getPromotionStats(detail.work.id, promotionId);
|
|
517
|
+
if (jsonRequested(opts))
|
|
518
|
+
return outJson(result);
|
|
519
|
+
table([{
|
|
520
|
+
name: result.promotion.name,
|
|
521
|
+
landing: result.summary.landing,
|
|
522
|
+
ready: result.summary.ready,
|
|
523
|
+
registered: result.summary.registrationCompleted,
|
|
524
|
+
paywall: result.summary.paywallViewed,
|
|
525
|
+
checkout: result.summary.checkoutStarted,
|
|
526
|
+
readyRate: `${(result.summary.readyRate * 100).toFixed(1)}%`,
|
|
527
|
+
}], [
|
|
528
|
+
{ key: "name", label: "Name" },
|
|
529
|
+
{ key: "landing", label: "Landing" },
|
|
530
|
+
{ key: "ready", label: "Ready" },
|
|
531
|
+
{ key: "registered", label: "Registered" },
|
|
532
|
+
{ key: "paywall", label: "Paywall" },
|
|
533
|
+
{ key: "checkout", label: "Checkout" },
|
|
534
|
+
{ key: "readyRate", label: "Ready rate" },
|
|
535
|
+
]);
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
handleHttp(e);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
427
541
|
registerWorkCommerce(worksCmd);
|
|
428
542
|
worksCmd
|
|
429
543
|
.command("rm <id>")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "3.10.
|
|
3
|
+
"version": "3.10.2",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"commander": "^15.0.0",
|
|
20
20
|
"pixi.js": "^8.19.0",
|
|
21
21
|
"sharp": "^0.35.3",
|
|
22
|
-
"@neta-art/cohub": "5.8.
|
|
22
|
+
"@neta-art/cohub": "5.8.2"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|