@neta-art/cohub-cli 6.1.2 → 6.1.4
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/README.md +16 -0
- package/dist/app-target.d.ts +26 -0
- package/dist/app-target.js +55 -0
- package/dist/commands/apps.js +47 -4
- package/dist/commands/space-activity.d.ts +26 -0
- package/dist/commands/space-activity.js +149 -0
- package/dist/commands/spaces.js +3 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -144,6 +144,17 @@ cohub -s <spaceId> spaces turns ls --cursor <nextCursor> --json
|
|
|
144
144
|
cohub -s <spaceId> spaces turns ls --after <snapshotCursor> --before <snapshotAt> --json
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
+
## Space activity
|
|
148
|
+
|
|
149
|
+
One-shot overview of a Space: usage summary, per-user contributors, top
|
|
150
|
+
models, and most viewed Apps. Cost figures require space-management access.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
cohub -s <spaceId> spaces activity
|
|
154
|
+
cohub -s <spaceId> spaces activity 7
|
|
155
|
+
cohub -s <spaceId> spaces activity 365 --json
|
|
156
|
+
```
|
|
157
|
+
|
|
147
158
|
## Boards
|
|
148
159
|
|
|
149
160
|
Board commands use the selected Space and support `-h` at every level:
|
|
@@ -273,6 +284,11 @@ Confirm before deleting files or directories.
|
|
|
273
284
|
|
|
274
285
|
Publish and manage Work entries from a Space workspace. Public Work URLs require a username and a Space slug.
|
|
275
286
|
|
|
287
|
+
`--file` and `--dir` take paths relative to the Space workspace — the same paths
|
|
288
|
+
`spaces files ls` shows, not your local filesystem. To publish local build
|
|
289
|
+
output, upload it first (`spaces files upload <dir>`), then publish the
|
|
290
|
+
Space-side path.
|
|
291
|
+
|
|
276
292
|
```bash
|
|
277
293
|
cohub profile update --username <username>
|
|
278
294
|
cohub spaces update <spaceId> --slug <space-slug>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type CohubHttpClient } from "@neta-art/cohub";
|
|
2
|
+
/**
|
|
3
|
+
* `--file` / `--dir` publish targets are paths inside the target Space's
|
|
4
|
+
* workspace, not local filesystem paths. The publish worker resolves them
|
|
5
|
+
* under `{storageRoot}/{spaceId}/workspace` and snapshots the bytes into an
|
|
6
|
+
* immutable artifact.
|
|
7
|
+
*
|
|
8
|
+
* The worker surfaces a bare "file or directory not found" without context, so
|
|
9
|
+
* the CLI checks the target against the Space files API first and fails with
|
|
10
|
+
* an explicit, self-explanatory error before anything is snapshotted.
|
|
11
|
+
*/
|
|
12
|
+
export type AppTargetCheckError = {
|
|
13
|
+
status: number;
|
|
14
|
+
code: string;
|
|
15
|
+
message: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Verify a Space-relative publish target exists with the expected node type.
|
|
19
|
+
* Empty targetRef (the Space workspace root, directory publishes only) passes.
|
|
20
|
+
* Returns null when the target is valid or the check itself cannot run —
|
|
21
|
+
* the preflight is advisory and must never block a publish the worker can do.
|
|
22
|
+
*/
|
|
23
|
+
export declare function checkAppTarget(client: CohubHttpClient, spaceId: string, target: {
|
|
24
|
+
targetType: "file" | "directory";
|
|
25
|
+
targetRef: string;
|
|
26
|
+
}): Promise<AppTargetCheckError | null>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isHttpErrorCode } from "@neta-art/cohub";
|
|
2
|
+
/** Normalize a user-supplied Space path for comparison with fs entries. */
|
|
3
|
+
function normalizeSpacePath(path) {
|
|
4
|
+
return path.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
5
|
+
}
|
|
6
|
+
function posixDirname(path) {
|
|
7
|
+
const index = path.lastIndexOf("/");
|
|
8
|
+
return index <= 0 ? "" : path.slice(0, index);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Verify a Space-relative publish target exists with the expected node type.
|
|
12
|
+
* Empty targetRef (the Space workspace root, directory publishes only) passes.
|
|
13
|
+
* Returns null when the target is valid or the check itself cannot run —
|
|
14
|
+
* the preflight is advisory and must never block a publish the worker can do.
|
|
15
|
+
*/
|
|
16
|
+
export async function checkAppTarget(client, spaceId, target) {
|
|
17
|
+
const targetPath = normalizeSpacePath(target.targetRef);
|
|
18
|
+
if (!targetPath)
|
|
19
|
+
return null;
|
|
20
|
+
try {
|
|
21
|
+
const tree = await client.space(spaceId).files.list(posixDirname(targetPath));
|
|
22
|
+
const entry = tree.entries.find((candidate) => candidate.path === targetPath);
|
|
23
|
+
if (!entry) {
|
|
24
|
+
return {
|
|
25
|
+
status: 404,
|
|
26
|
+
code: "path_not_found",
|
|
27
|
+
message: `"${targetPath}" does not exist in the Space workspace`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const wanted = target.targetType === "directory" ? "dir" : "file";
|
|
31
|
+
if (entry.type !== wanted) {
|
|
32
|
+
const foundKind = entry.type === "dir" ? "a directory" : entry.type === "symlink" ? "a symlink" : "a file";
|
|
33
|
+
const wantedKind = wanted === "dir" ? "a directory" : "a file";
|
|
34
|
+
return {
|
|
35
|
+
status: 400,
|
|
36
|
+
code: entry.type === "symlink" ? "symlink_not_supported" : wanted === "dir" ? "not_a_directory" : "not_a_file",
|
|
37
|
+
message: `"${targetPath}" is ${foundKind}, but the publish target must be ${wantedKind}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
// The parent directory itself is missing.
|
|
44
|
+
if (isHttpErrorCode(e, "path_not_found")) {
|
|
45
|
+
return {
|
|
46
|
+
status: 404,
|
|
47
|
+
code: "path_not_found",
|
|
48
|
+
message: `"${targetPath}" does not exist in the Space workspace`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Advisory preflight: ignore auth/visibility/network noise here and let the
|
|
52
|
+
// publish request proceed — the worker still reports real failures.
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/commands/apps.js
CHANGED
|
@@ -4,6 +4,7 @@ import { error, handleHttp, json as outJson, jsonRequested, ok, table } from "..
|
|
|
4
4
|
import { resolveSpace } from "../space.js";
|
|
5
5
|
import { downloadApp } from "../app-download.js";
|
|
6
6
|
import { getAppByRef, parseAppRef } from "../app-ref.js";
|
|
7
|
+
import { checkAppTarget } from "../app-target.js";
|
|
7
8
|
import { registerAppCommerce } from "./app-commerce.js";
|
|
8
9
|
const APP_STATUSES = ["published", "disabled"];
|
|
9
10
|
const APP_VISIBILITIES = ["public", "space"];
|
|
@@ -64,6 +65,28 @@ function resolveStatus(opts) {
|
|
|
64
65
|
return error("Conflicting status", "Use only one of --status or --disabled");
|
|
65
66
|
return values[0] ? parseChoice(values[0], "status", APP_STATUSES) : "published";
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Fail early when a Space-relative `--file` / `--dir` target cannot be a valid
|
|
70
|
+
* publish source. The publish worker resolves targets inside the target
|
|
71
|
+
* Space's workspace, not on the local filesystem.
|
|
72
|
+
*/
|
|
73
|
+
async function guardAppTarget(client, spaceId, target) {
|
|
74
|
+
const failure = await checkAppTarget(client, spaceId, target);
|
|
75
|
+
if (failure) {
|
|
76
|
+
error(failure.status === 404 ? "Publish target not found" : "Publish target is invalid", `${failure.message} (--${target.targetType === "directory" ? "dir" : "file"} takes a Space workspace path, not a local path).`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Translate the publish worker's bare fs errors (e.g. a target removed between
|
|
81
|
+
* preflight and snapshot) into the same explicit wording as the preflight.
|
|
82
|
+
*/
|
|
83
|
+
function translateTargetWorkerError(e) {
|
|
84
|
+
if (!(e instanceof HttpError))
|
|
85
|
+
return;
|
|
86
|
+
if (e.code !== "path_not_found" && e.code !== "not_a_directory" && e.code !== "not_a_file" && e.code !== "symlink_not_supported")
|
|
87
|
+
return;
|
|
88
|
+
error(e.code === "path_not_found" ? "Publish target not found" : "Publish target is invalid", "The publish target is a Space workspace path; the Space workspace no longer contains a valid target at that path.");
|
|
89
|
+
}
|
|
67
90
|
function resolveVisibility(value) {
|
|
68
91
|
return value ? parseChoice(value, "visibility", APP_VISIBILITIES) : undefined;
|
|
69
92
|
}
|
|
@@ -143,6 +166,7 @@ async function publishAppVersion(id, opts) {
|
|
|
143
166
|
printApp(result.app);
|
|
144
167
|
}
|
|
145
168
|
catch (e) {
|
|
169
|
+
translateTargetWorkerError(e);
|
|
146
170
|
handleHttp(e);
|
|
147
171
|
}
|
|
148
172
|
}
|
|
@@ -261,8 +285,8 @@ export function registerApps(program) {
|
|
|
261
285
|
appsCmd
|
|
262
286
|
.command("publish <slug>")
|
|
263
287
|
.description("Create or publish an app in the target space")
|
|
264
|
-
.option("--file <path>", "Publish a file (HTML page, board, or any other file)")
|
|
265
|
-
.option("--dir <path>", "Publish a directory site")
|
|
288
|
+
.option("--file <path>", "Publish a file (HTML page, board, or any other file) from the Space workspace")
|
|
289
|
+
.option("--dir <path>", "Publish a directory site from the Space workspace")
|
|
266
290
|
.option("--port <port>", "Publish a public sandbox port")
|
|
267
291
|
.option("--disabled", "Create as disabled")
|
|
268
292
|
.option("--status <status>", "App status: published, disabled")
|
|
@@ -281,6 +305,9 @@ export function registerApps(program) {
|
|
|
281
305
|
return error("Missing target", "Use one of --file, --dir, or --port.");
|
|
282
306
|
const spaceId = resolveSpace(appsCmd);
|
|
283
307
|
const client = createClient();
|
|
308
|
+
const { targetType, targetRef } = target;
|
|
309
|
+
if (targetType !== "port")
|
|
310
|
+
await guardAppTarget(client, spaceId, { targetType, targetRef });
|
|
284
311
|
const status = resolveStatus(opts);
|
|
285
312
|
const meta = withCohubBarMeta({
|
|
286
313
|
meta: parseJsonObject(opts.meta, "meta"),
|
|
@@ -306,6 +333,7 @@ export function registerApps(program) {
|
|
|
306
333
|
printApp(result.app);
|
|
307
334
|
}
|
|
308
335
|
catch (e) {
|
|
336
|
+
translateTargetWorkerError(e);
|
|
309
337
|
if (!(e instanceof HttpError) || e.status !== 409)
|
|
310
338
|
handleHttp(e);
|
|
311
339
|
try {
|
|
@@ -332,6 +360,7 @@ export function registerApps(program) {
|
|
|
332
360
|
printApp(result.app);
|
|
333
361
|
}
|
|
334
362
|
catch (fallbackError) {
|
|
363
|
+
translateTargetWorkerError(fallbackError);
|
|
335
364
|
handleHttp(fallbackError);
|
|
336
365
|
}
|
|
337
366
|
}
|
|
@@ -340,8 +369,8 @@ export function registerApps(program) {
|
|
|
340
369
|
.command("update <id>")
|
|
341
370
|
.description("Update app settings")
|
|
342
371
|
.option("--slug <slug>", "New app slug")
|
|
343
|
-
.option("--file <path>", "Use a file target (HTML page, board, or any other file)")
|
|
344
|
-
.option("--dir <path>", "Use a directory site target")
|
|
372
|
+
.option("--file <path>", "Use a file target (HTML page, board, or any other file) from the Space workspace")
|
|
373
|
+
.option("--dir <path>", "Use a directory site target from the Space workspace")
|
|
345
374
|
.option("--port <port>", "Use a public sandbox port target")
|
|
346
375
|
.option("--disabled", "Set status to disabled")
|
|
347
376
|
.option("--status <status>", "App status: published, disabled")
|
|
@@ -364,6 +393,19 @@ export function registerApps(program) {
|
|
|
364
393
|
return error("Conflicting viewer scopes", "Use either --viewer-scope or --clear-viewer-scopes.");
|
|
365
394
|
const hasMetaUpdate = opts.meta !== undefined || opts.hideCohubBar || opts.showCohubBar;
|
|
366
395
|
const client = createClient();
|
|
396
|
+
if (target) {
|
|
397
|
+
// Resolve the app's home Space so the preflight checks the same
|
|
398
|
+
// workspace the publish worker will read from.
|
|
399
|
+
try {
|
|
400
|
+
const current = await client.apps.get(id);
|
|
401
|
+
const { targetType, targetRef } = target;
|
|
402
|
+
if (targetType !== "port")
|
|
403
|
+
await guardAppTarget(client, current.app.spaceId, { targetType, targetRef });
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// The update request below surfaces errors for unknown apps.
|
|
407
|
+
}
|
|
408
|
+
}
|
|
367
409
|
let meta;
|
|
368
410
|
if (hasMetaUpdate) {
|
|
369
411
|
let baseMeta = opts.meta !== undefined ? parseJsonObject(opts.meta, "meta") ?? null : undefined;
|
|
@@ -407,6 +449,7 @@ export function registerApps(program) {
|
|
|
407
449
|
printApp(result.app);
|
|
408
450
|
}
|
|
409
451
|
catch (e) {
|
|
452
|
+
translateTargetWorkerError(e);
|
|
410
453
|
handleHttp(e);
|
|
411
454
|
}
|
|
412
455
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SpaceActivityAppRanking, SpaceActivityContributor, SpaceActivityResponse } from "@neta-art/cohub";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
import { type Row } from "../output.js";
|
|
4
|
+
export type SpaceActivityCliOptions = {
|
|
5
|
+
json?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type SpaceActivityCommandClient = {
|
|
8
|
+
space(spaceId: string): {
|
|
9
|
+
activity: {
|
|
10
|
+
get(days: number): Promise<SpaceActivityResponse>;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare class InvalidSpaceActivityDaysError extends Error {
|
|
15
|
+
readonly detail: string;
|
|
16
|
+
constructor(message: string, detail: string);
|
|
17
|
+
}
|
|
18
|
+
/** Shared with the API: any positive day count up to a full leap year. */
|
|
19
|
+
export declare function parseActivityDays(value: string | undefined): number;
|
|
20
|
+
export declare function toContributorRows(items: SpaceActivityContributor[]): Row[];
|
|
21
|
+
export declare function toAppRankingRows(apps: SpaceActivityAppRanking[]): Row[];
|
|
22
|
+
export declare function printActivityReport(activity: SpaceActivityResponse): void;
|
|
23
|
+
export declare function registerSpaceActivity(spacesCmd: Command, dependencies?: {
|
|
24
|
+
createClient?: () => SpaceActivityCommandClient;
|
|
25
|
+
}): void;
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createClient } from "../client.js";
|
|
2
|
+
import { error, handleHttp, json as outJson, jsonRequested, table, } from "../output.js";
|
|
3
|
+
import { resolveSpace } from "../space.js";
|
|
4
|
+
const DEFAULT_DAYS = 30;
|
|
5
|
+
const MAX_DAYS = 365;
|
|
6
|
+
export class InvalidSpaceActivityDaysError extends Error {
|
|
7
|
+
detail;
|
|
8
|
+
constructor(message, detail) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.detail = detail;
|
|
11
|
+
this.name = "InvalidSpaceActivityDaysError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Shared with the API: any positive day count up to a full leap year. */
|
|
15
|
+
export function parseActivityDays(value) {
|
|
16
|
+
const raw = (value ?? String(DEFAULT_DAYS)).trim();
|
|
17
|
+
if (!/^\d+$/.test(raw)) {
|
|
18
|
+
throw new InvalidSpaceActivityDaysError("Invalid days", "days must be a positive integer");
|
|
19
|
+
}
|
|
20
|
+
const days = Number.parseInt(raw, 10);
|
|
21
|
+
if (days < 1 || days > MAX_DAYS) {
|
|
22
|
+
throw new InvalidSpaceActivityDaysError("Invalid days", `days must be between 1 and ${MAX_DAYS}`);
|
|
23
|
+
}
|
|
24
|
+
return days;
|
|
25
|
+
}
|
|
26
|
+
const formatNumber = (value) => new Intl.NumberFormat("en-US").format(Number(value) || 0);
|
|
27
|
+
const formatCost = (value) => `$${(Number(value) || 0).toFixed(2)}`;
|
|
28
|
+
function relativeTime(timestamp) {
|
|
29
|
+
if (!timestamp)
|
|
30
|
+
return "";
|
|
31
|
+
const at = new Date(timestamp).getTime();
|
|
32
|
+
if (!Number.isFinite(at))
|
|
33
|
+
return "";
|
|
34
|
+
const deltaMs = Date.now() - at;
|
|
35
|
+
const minutes = Math.round(deltaMs / 60_000);
|
|
36
|
+
if (minutes < 1)
|
|
37
|
+
return "just now";
|
|
38
|
+
if (minutes < 60)
|
|
39
|
+
return `${minutes}m ago`;
|
|
40
|
+
const hours = Math.round(minutes / 60);
|
|
41
|
+
if (hours < 24)
|
|
42
|
+
return `${hours}h ago`;
|
|
43
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
44
|
+
}
|
|
45
|
+
export function toContributorRows(items) {
|
|
46
|
+
return items.map((contributor) => ({
|
|
47
|
+
name: contributor.profile?.displayName ||
|
|
48
|
+
contributor.profile?.username ||
|
|
49
|
+
contributor.userUuid,
|
|
50
|
+
role: contributor.role ?? "",
|
|
51
|
+
tokens: formatNumber(contributor.tokens),
|
|
52
|
+
requests: formatNumber(contributor.requests),
|
|
53
|
+
sessions: contributor.sessionCount,
|
|
54
|
+
cost: formatCost(contributor.costTotal),
|
|
55
|
+
lastActive: relativeTime(contributor.lastActiveAt),
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
export function toAppRankingRows(apps) {
|
|
59
|
+
return apps.map((app) => ({
|
|
60
|
+
title: app.title,
|
|
61
|
+
status: app.status,
|
|
62
|
+
views: formatNumber(app.viewCount),
|
|
63
|
+
id: app.appId,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
function hasCost(activity) {
|
|
67
|
+
return activity.summary.costTotal !== 0;
|
|
68
|
+
}
|
|
69
|
+
export function printActivityReport(activity) {
|
|
70
|
+
const showCost = hasCost(activity);
|
|
71
|
+
console.log(`\n Summary (last ${activity.days} days):`);
|
|
72
|
+
table([activity.summary], [
|
|
73
|
+
{ key: "totalTokens", label: "Tokens", format: formatNumber },
|
|
74
|
+
{ key: "requestCount", label: "Requests", format: formatNumber },
|
|
75
|
+
...(showCost ? [{ key: "costTotal", label: "Cost", format: formatCost }] : []),
|
|
76
|
+
{ key: "successCount", label: "Success", format: formatNumber },
|
|
77
|
+
{ key: "errorCount", label: "Errors", format: formatNumber },
|
|
78
|
+
]);
|
|
79
|
+
if (activity.contributors.items.length > 0) {
|
|
80
|
+
console.log(`\n Contributors (${activity.contributors.memberCount} members):`);
|
|
81
|
+
table(toContributorRows(activity.contributors.items), [
|
|
82
|
+
{ key: "name", label: "Name" },
|
|
83
|
+
{ key: "role", label: "Role" },
|
|
84
|
+
{ key: "tokens", label: "Tokens" },
|
|
85
|
+
{ key: "requests", label: "Requests" },
|
|
86
|
+
{ key: "sessions", label: "Sessions" },
|
|
87
|
+
...(showCost ? [{ key: "cost", label: "Cost" }] : []),
|
|
88
|
+
{ key: "lastActive", label: "Last active" },
|
|
89
|
+
]);
|
|
90
|
+
}
|
|
91
|
+
const { llmModels, generationModels, apps } = activity.rankings;
|
|
92
|
+
if (llmModels.length > 0) {
|
|
93
|
+
console.log("\n Top LLM models:");
|
|
94
|
+
table(llmModels, [
|
|
95
|
+
{ key: "model", label: "Model" },
|
|
96
|
+
{ key: "provider", label: "Provider" },
|
|
97
|
+
{ key: "totalTokens", label: "Tokens", format: formatNumber },
|
|
98
|
+
{ key: "requestCount", label: "Requests", format: formatNumber },
|
|
99
|
+
...(showCost ? [{ key: "costTotal", label: "Cost", format: formatCost }] : []),
|
|
100
|
+
]);
|
|
101
|
+
}
|
|
102
|
+
if (generationModels.length > 0) {
|
|
103
|
+
console.log("\n Top generation models:");
|
|
104
|
+
table(generationModels, [
|
|
105
|
+
{ key: "model", label: "Model" },
|
|
106
|
+
{ key: "provider", label: "Provider" },
|
|
107
|
+
{ key: "requestCount", label: "Calls", format: formatNumber },
|
|
108
|
+
...(showCost ? [{ key: "costTotal", label: "Cost", format: formatCost }] : []),
|
|
109
|
+
]);
|
|
110
|
+
}
|
|
111
|
+
if (apps.length > 0) {
|
|
112
|
+
console.log("\n Most viewed apps:");
|
|
113
|
+
table(toAppRankingRows(apps), [
|
|
114
|
+
{ key: "title", label: "App" },
|
|
115
|
+
{ key: "views", label: "Views" },
|
|
116
|
+
{ key: "status", label: "Status" },
|
|
117
|
+
{ key: "id", label: "ID" },
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export function registerSpaceActivity(spacesCmd, dependencies = {}) {
|
|
122
|
+
spacesCmd
|
|
123
|
+
.command("activity [days]")
|
|
124
|
+
.description("Space activity overview: usage, contributors, rankings")
|
|
125
|
+
.option("--json", "Output as JSON")
|
|
126
|
+
.action(async (days, opts) => {
|
|
127
|
+
const spaceId = resolveSpace(spacesCmd);
|
|
128
|
+
let parsedDays;
|
|
129
|
+
try {
|
|
130
|
+
parsedDays = parseActivityDays(days);
|
|
131
|
+
}
|
|
132
|
+
catch (cause) {
|
|
133
|
+
if (cause instanceof InvalidSpaceActivityDaysError) {
|
|
134
|
+
return error(cause.message, cause.detail);
|
|
135
|
+
}
|
|
136
|
+
throw cause;
|
|
137
|
+
}
|
|
138
|
+
const client = dependencies.createClient?.() ?? createClient();
|
|
139
|
+
try {
|
|
140
|
+
const activity = await client.space(spaceId).activity.get(parsedDays);
|
|
141
|
+
if (jsonRequested(opts))
|
|
142
|
+
return outJson(activity);
|
|
143
|
+
printActivityReport(activity);
|
|
144
|
+
}
|
|
145
|
+
catch (cause) {
|
|
146
|
+
handleHttp(cause);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
}
|
package/dist/commands/spaces.js
CHANGED
|
@@ -8,6 +8,7 @@ import { createClient } from "../client.js";
|
|
|
8
8
|
import { table, json as outJson, jsonRequested, ok, error, handleHttp, formatEpochMs } from "../output.js";
|
|
9
9
|
import { resolveSpace } from "../space.js";
|
|
10
10
|
import { registerSpaceCommerce } from "./space-commerce.js";
|
|
11
|
+
import { registerSpaceActivity } from "./space-activity.js";
|
|
11
12
|
import { registerSpaceInvitations } from "./space-invitations.js";
|
|
12
13
|
import { registerSpaceTurns } from "./space-turns.js";
|
|
13
14
|
const cliEnv = resolveCohubEnvironment();
|
|
@@ -677,6 +678,8 @@ export function registerSpaces(program) {
|
|
|
677
678
|
});
|
|
678
679
|
// ── spaces commerce ──
|
|
679
680
|
registerSpaceCommerce(spacesCmd);
|
|
681
|
+
// ── spaces activity ──
|
|
682
|
+
registerSpaceActivity(spacesCmd);
|
|
680
683
|
// ── spaces usage ──
|
|
681
684
|
spacesCmd
|
|
682
685
|
.command("usage [days]")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.4",
|
|
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": "8.3.
|
|
22
|
+
"@neta-art/cohub": "8.3.1"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|