@neta-art/cohub-cli 1.6.2 → 1.7.0

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/auth.d.ts CHANGED
@@ -28,7 +28,6 @@ export declare class AuthRequiredError extends Error {
28
28
  constructor(message?: string);
29
29
  }
30
30
  export declare const readAuthSession: () => AuthSession | null;
31
- export declare const clearDeviceCode: () => void;
32
31
  export declare const clearAuthSession: () => void;
33
32
  export declare const authSource: () => AuthSource;
34
33
  export declare function resolveAccessToken(): Promise<string | null>;
package/dist/auth.js CHANGED
@@ -110,7 +110,7 @@ const toSession = (token, config, env, previous) => {
110
110
  };
111
111
  };
112
112
  export const readAuthSession = () => readJson(sessionPath());
113
- export const clearDeviceCode = () => {
113
+ const clearDeviceCode = () => {
114
114
  removeIfExists(deviceCodePath());
115
115
  };
116
116
  export const clearAuthSession = () => {
@@ -0,0 +1,15 @@
1
+ import type { CohubHttpClient, PublicAssetPurpose } from "@neta-art/cohub";
2
+ export declare function normalizeAvatarFile(path: string): Promise<Buffer>;
3
+ export declare function uploadAvatarAsset(input: {
4
+ client: CohubHttpClient;
5
+ purpose: PublicAssetPurpose;
6
+ path: string;
7
+ spaceId?: string;
8
+ }): Promise<{
9
+ purpose: PublicAssetPurpose;
10
+ objectKey: string;
11
+ publicUrl: string;
12
+ uploadMethod: "POST";
13
+ uploadUrl: string;
14
+ uploadFields: Record<string, string>;
15
+ }>;
package/dist/avatar.js ADDED
@@ -0,0 +1,35 @@
1
+ import sharp from "sharp";
2
+ const AVATAR_SIZE = 1024;
3
+ const AVATAR_QUALITY = 86;
4
+ export async function normalizeAvatarFile(path) {
5
+ return sharp(path)
6
+ .rotate()
7
+ .resize(AVATAR_SIZE, AVATAR_SIZE, { fit: "cover", position: "centre" })
8
+ .webp({ quality: AVATAR_QUALITY })
9
+ .toBuffer();
10
+ }
11
+ export async function uploadAvatarAsset(input) {
12
+ const body = await normalizeAvatarFile(input.path);
13
+ const plan = await input.client.publicAssets.createUpload({
14
+ purpose: input.purpose,
15
+ spaceId: input.spaceId,
16
+ file: {
17
+ size: body.byteLength,
18
+ mimeType: "image/webp",
19
+ },
20
+ });
21
+ const formData = new FormData();
22
+ for (const [key, value] of Object.entries(plan.asset.uploadFields)) {
23
+ formData.append(key, value);
24
+ }
25
+ formData.append("file", new Blob([new Uint8Array(body)], { type: "image/webp" }), "avatar.webp");
26
+ const response = await fetch(plan.asset.uploadUrl, {
27
+ method: plan.asset.uploadMethod,
28
+ body: formData,
29
+ });
30
+ if (!response.ok) {
31
+ const detail = await response.text().catch(() => "");
32
+ throw new Error(`Avatar upload failed: HTTP ${response.status}${detail ? ` — ${detail}` : ""}`);
33
+ }
34
+ return plan.asset;
35
+ }
package/dist/client.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { CohubHttpClient } from "@neta-art/cohub";
2
- import { clearDeviceCode, resolveAccessToken } from "./auth.js";
2
+ import { clearAuthSession, resolveAccessToken } from "./auth.js";
3
3
  export function createClient() {
4
4
  return new CohubHttpClient({
5
5
  getAccessToken: resolveAccessToken,
6
- onUnauthorized: clearDeviceCode,
6
+ onUnauthorized: clearAuthSession,
7
7
  });
8
8
  }
@@ -91,7 +91,7 @@ async function saveOutputs(output, outputPath) {
91
91
  }
92
92
  function outputName(type, url, index) {
93
93
  const fromUrl = url ? basename(new URL(url).pathname) : "";
94
- if (fromUrl && fromUrl.includes("."))
94
+ if (fromUrl?.includes("."))
95
95
  return `generation-${index + 1}-${fromUrl}`;
96
96
  const ext = type === "video" ? "mp4" : type === "audio" ? "bin" : "png";
97
97
  return `generation-${index + 1}.${ext}`;
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerProfile(program: Command): void;
@@ -0,0 +1,23 @@
1
+ import { uploadAvatarAsset } from "../avatar.js";
2
+ import { createClient } from "../client.js";
3
+ import { json as outJson, ok, handleHttp } from "../output.js";
4
+ export function registerProfile(program) {
5
+ const profileCmd = program.command("profile").description("Manage your profile");
6
+ profileCmd
7
+ .command("avatar <path>")
8
+ .description("Upload your avatar")
9
+ .option("--json", "Output as JSON")
10
+ .action(async (path, opts) => {
11
+ const client = createClient();
12
+ try {
13
+ const asset = await uploadAvatarAsset({ client, purpose: "user_avatar", path });
14
+ const result = await client.user.updateProfile({ avatarUrl: asset.publicUrl });
15
+ if (opts.json)
16
+ return outJson({ ...result, asset });
17
+ ok("Avatar updated");
18
+ }
19
+ catch (e) {
20
+ handleHttp(e);
21
+ }
22
+ });
23
+ }
@@ -1,5 +1,5 @@
1
1
  import { createClient } from "../client.js";
2
- import { table, json as outJson, ok, handleHttp } from "../output.js";
2
+ import { table, json as outJson, handleHttp } from "../output.js";
3
3
  export function registerSessionAccess(program) {
4
4
  const cmd = program
5
5
  .command("session-access")
@@ -36,7 +36,7 @@ export function registerSessionAccess(program) {
36
36
  });
37
37
  if (opts.json)
38
38
  return outJson(policy);
39
- ok("Session access updated");
39
+ console.log("Session access updated");
40
40
  table([policy], [
41
41
  { key: "signed_in_user", label: "Signed-in" },
42
42
  { key: "anonymous_user", label: "Anonymous" },
@@ -53,7 +53,7 @@ export function registerSessionAccess(program) {
53
53
  const client = createClient();
54
54
  try {
55
55
  await client.sessionAccess.remove(id);
56
- ok(`Session access override removed: ${id}`);
56
+ console.log(`Session access override removed: ${id}`);
57
57
  }
58
58
  catch (e) {
59
59
  handleHttp(e);
@@ -3,6 +3,7 @@ import { createReadStream } from "node:fs";
3
3
  import { readdir, stat } from "node:fs/promises";
4
4
  import { basename, dirname, relative, resolve, sep } from "node:path";
5
5
  import { createClient } from "../client.js";
6
+ import { uploadAvatarAsset } from "../avatar.js";
6
7
  import { table, json as outJson, ok, error, handleHttp } from "../output.js";
7
8
  function requireSpace(program) {
8
9
  let current = program;
@@ -124,21 +125,6 @@ async function uploadFiles(command, paths, opts) {
124
125
  handleHttp(e);
125
126
  }
126
127
  }
127
- async function confirmRestart(opts) {
128
- if (opts.yes)
129
- return;
130
- if (!process.stdin.isTTY || !process.stdout.isTTY)
131
- return error("Confirmation required", "Pass --yes to restart the sandbox automatically.");
132
- process.stdout.write("Changing mods restarts the sandbox and may interrupt running work. Continue? [y/N] ");
133
- const chunks = [];
134
- for await (const chunk of process.stdin) {
135
- chunks.push(chunk);
136
- break;
137
- }
138
- const answer = Buffer.concat(chunks).toString().trim().toLowerCase();
139
- if (answer !== "y" && answer !== "yes")
140
- return error("Cancelled");
141
- }
142
128
  async function readPromptContent(words) {
143
129
  let content = words.join(" ");
144
130
  if (!content && !process.stdin.isTTY) {
@@ -295,6 +281,25 @@ export function registerSpaces(program) {
295
281
  handleHttp(e);
296
282
  }
297
283
  });
284
+ // ── spaces avatar ──
285
+ spacesCmd
286
+ .command("avatar <path>")
287
+ .description("Upload the space avatar")
288
+ .option("--json", "Output as JSON")
289
+ .action(async (path, opts) => {
290
+ const spaceId = requireSpace(spacesCmd);
291
+ const client = createClient();
292
+ try {
293
+ const asset = await uploadAvatarAsset({ client, purpose: "space_avatar", spaceId, path });
294
+ const result = await client.space(spaceId).profile({ avatarUrl: asset.publicUrl });
295
+ if (opts.json)
296
+ return outJson({ ...result, asset });
297
+ ok("Space avatar updated");
298
+ }
299
+ catch (e) {
300
+ handleHttp(e);
301
+ }
302
+ });
298
303
  // ── spaces config ──
299
304
  spacesCmd
300
305
  .command("config <id>")
@@ -398,7 +403,7 @@ function registerMods(spacesCmd) {
398
403
  table(result.items, [
399
404
  { key: "id", label: "ID" },
400
405
  { key: "modSpaceName", label: "Name" },
401
- { key: "mountPath", label: "Mount" },
406
+ { key: "modSpaceId", label: "Space" },
402
407
  { key: "enabled", label: "On" },
403
408
  ]);
404
409
  }
@@ -409,19 +414,15 @@ function registerMods(spacesCmd) {
409
414
  modsCmd
410
415
  .command("add <modSpaceId>")
411
416
  .description("Add a mod")
412
- .option("--name <name>", "Display name")
413
- .option("--slug <slug>", "Mount slug")
414
- .option("-y, --yes", "Confirm sandbox restart")
415
417
  .option("--json", "Output as JSON")
416
418
  .action(async (modSpaceId, opts) => {
417
- await confirmRestart(opts);
418
419
  const spaceId = requireSpace(spacesCmd);
419
420
  const client = createClient();
420
421
  try {
421
- const result = await client.space(spaceId).mods.create({ modSpaceId, name: opts.name, mountSlug: opts.slug });
422
+ const result = await client.space(spaceId).mods.create({ modSpaceId });
422
423
  if (opts.json)
423
424
  return outJson(result);
424
- ok(`Mod added — ${result.item.mountPath}; sandbox restarting`);
425
+ ok("Mod added");
425
426
  }
426
427
  catch (e) {
427
428
  handleHttp(e);
@@ -430,17 +431,15 @@ function registerMods(spacesCmd) {
430
431
  modsCmd
431
432
  .command("enable <modId>")
432
433
  .description("Enable a mod")
433
- .option("-y, --yes", "Confirm sandbox restart")
434
434
  .option("--json", "Output as JSON")
435
435
  .action(async (modId, opts) => {
436
- await confirmRestart(opts);
437
436
  const spaceId = requireSpace(spacesCmd);
438
437
  const client = createClient();
439
438
  try {
440
439
  const result = await client.space(spaceId).mods.update(modId, { enabled: true });
441
440
  if (opts.json)
442
441
  return outJson(result);
443
- ok("Mod enabled; sandbox restarting");
442
+ ok("Mod enabled");
444
443
  }
445
444
  catch (e) {
446
445
  handleHttp(e);
@@ -449,17 +448,15 @@ function registerMods(spacesCmd) {
449
448
  modsCmd
450
449
  .command("disable <modId>")
451
450
  .description("Disable a mod")
452
- .option("-y, --yes", "Confirm sandbox restart")
453
451
  .option("--json", "Output as JSON")
454
452
  .action(async (modId, opts) => {
455
- await confirmRestart(opts);
456
453
  const spaceId = requireSpace(spacesCmd);
457
454
  const client = createClient();
458
455
  try {
459
456
  const result = await client.space(spaceId).mods.update(modId, { enabled: false });
460
457
  if (opts.json)
461
458
  return outJson(result);
462
- ok("Mod disabled; sandbox restarting");
459
+ ok("Mod disabled");
463
460
  }
464
461
  catch (e) {
465
462
  handleHttp(e);
@@ -469,17 +466,15 @@ function registerMods(spacesCmd) {
469
466
  .command("rm <modId>")
470
467
  .alias("remove")
471
468
  .description("Remove a mod")
472
- .option("-y, --yes", "Confirm sandbox restart")
473
469
  .option("--json", "Output as JSON")
474
470
  .action(async (modId, opts) => {
475
- await confirmRestart(opts);
476
471
  const spaceId = requireSpace(spacesCmd);
477
472
  const client = createClient();
478
473
  try {
479
474
  const result = await client.space(spaceId).mods.remove(modId);
480
475
  if (opts.json)
481
476
  return outJson(result);
482
- ok("Mod removed; sandbox restarting");
477
+ ok("Mod removed");
483
478
  }
484
479
  catch (e) {
485
480
  handleHttp(e);
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { registerChannels } from "./commands/channels.js";
6
6
  import { registerCronJobs } from "./commands/cron-jobs.js";
7
7
  import { registerGenerations } from "./commands/generations.js";
8
8
  import { registerModels } from "./commands/models.js";
9
+ import { registerProfile } from "./commands/profile.js";
9
10
  import { registerSearch } from "./commands/search.js";
10
11
  import { registerPrompt, registerSpaces } from "./commands/spaces.js";
11
12
  import { registerTasks } from "./commands/tasks.js";
@@ -32,6 +33,7 @@ program
32
33
 
33
34
  Common commands:
34
35
  cohub auth login
36
+ cohub profile avatar ./avatar.png
35
37
  cohub spaces ls
36
38
  cohub -s <space-id> prompt "Fix the failing tests"
37
39
  cohub search "release notes"
@@ -46,6 +48,7 @@ Environment:
46
48
  ENV=dev Use the development Cohub environment
47
49
  `);
48
50
  registerAuth(program);
51
+ registerProfile(program);
49
52
  registerPrompt(program);
50
53
  registerSpaces(program);
51
54
  registerChannels(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -14,7 +14,8 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "commander": "^13.1.0",
17
- "@neta-art/cohub": "1.13.1"
17
+ "sharp": "^0.34.5",
18
+ "@neta-art/cohub": "1.15.0"
18
19
  },
19
20
  "publishConfig": {
20
21
  "access": "public"