@neta-art/cohub-cli 1.1.4 → 1.3.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.
@@ -1,8 +1,7 @@
1
- import { resolveToken } from "../auth.js";
2
1
  import { createClient } from "../client.js";
3
- import { table, json as outJson, error, handleHttp } from "../output.js";
2
+ import { table, json as outJson, handleHttp } from "../output.js";
4
3
  export function registerTasks(program) {
5
- const cmd = program.command("tasks").description("Task management");
4
+ const cmd = program.command("tasks", { hidden: true }).description("Task runs");
6
5
  cmd
7
6
  .command("ls")
8
7
  .alias("list")
@@ -11,10 +10,7 @@ export function registerTasks(program) {
11
10
  .option("--space <id>", "Filter by space")
12
11
  .option("--json", "Output as JSON")
13
12
  .action(async (opts) => {
14
- const token = resolveToken();
15
- if (!token)
16
- return error("Not authenticated", "Run 'cohub auth login <token>'");
17
- const client = createClient(token);
13
+ const client = createClient();
18
14
  try {
19
15
  const filters = {};
20
16
  if (opts.cronJob)
@@ -42,10 +38,7 @@ export function registerTasks(program) {
42
38
  .description("Task run details")
43
39
  .option("--json", "Output as JSON")
44
40
  .action(async (id, opts) => {
45
- const token = resolveToken();
46
- if (!token)
47
- return error("Not authenticated");
48
- const client = createClient(token);
41
+ const client = createClient();
49
42
  try {
50
43
  const result = await client.tasks.get(id);
51
44
  if (opts.json)
package/dist/index.js CHANGED
@@ -4,11 +4,12 @@ import { readFileSync } from "node:fs";
4
4
  import { registerAuth } from "./commands/auth.js";
5
5
  import { registerChannels } from "./commands/channels.js";
6
6
  import { registerCronJobs } from "./commands/cron-jobs.js";
7
+ import { registerGenerations } from "./commands/generations.js";
7
8
  import { registerModels } from "./commands/models.js";
8
- import { registerPrompts } from "./commands/prompts.js";
9
- import { registerSessionAccess } from "./commands/session-access.js";
10
- import { registerSpaces } from "./commands/spaces.js";
9
+ import { registerSearch } from "./commands/search.js";
10
+ import { registerPrompt, registerSpaces } from "./commands/spaces.js";
11
11
  import { registerTasks } from "./commands/tasks.js";
12
+ import { ensureCliSelfUpdated } from "./self-update.js";
12
13
  const VERSION = (() => {
13
14
  try {
14
15
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
@@ -20,17 +21,48 @@ const VERSION = (() => {
20
21
  })();
21
22
  const program = new Command("cohub");
22
23
  program
23
- .version(VERSION)
24
- .description("CLI for Cohub spaces, sessions, and agent collaboration.")
25
- .option("-s, --space <id>", "Target space ID")
26
- .option("--json", "Output as JSON")
27
- .helpOption("-h, --help", "Show help");
24
+ .name("cohub")
25
+ .summary("Work with Cohub from your terminal")
26
+ .description("Send prompts, inspect sessions, manage space files, and generate multimodal outputs.")
27
+ .version(VERSION, "-v, --version", "Show version")
28
+ .option("-s, --space <id>", "Target space ID for prompt, files, sessions, and space-scoped commands")
29
+ .option("--json", "Print machine-readable JSON when supported")
30
+ .helpOption("-h, --help", "Show help")
31
+ .addHelpText("after", `
32
+
33
+ Common commands:
34
+ cohub auth login
35
+ cohub spaces ls
36
+ cohub -s <space-id> prompt "Fix the failing tests"
37
+ cohub search "release notes"
38
+ cohub -s <space-id> spaces sessions turns ls <session-id>
39
+ cohub -s <space-id> spaces files ls
40
+ cohub models ls
41
+ cohub models ls --model-type multimodal
42
+ cohub generate "A calm lake at sunrise" --model <model> --output lake.png
43
+
44
+ Environment:
45
+ COHUB_EXECUTION_TOKEN Use this token instead of the stored Logto session
46
+ ENV=dev Use the development Cohub environment
47
+ `);
28
48
  registerAuth(program);
49
+ registerPrompt(program);
29
50
  registerSpaces(program);
30
51
  registerChannels(program);
52
+ registerGenerations(program);
31
53
  registerModels(program);
32
- registerPrompts(program);
54
+ registerSearch(program);
33
55
  registerTasks(program);
34
56
  registerCronJobs(program);
35
- registerSessionAccess(program);
57
+ const isVersionRequest = (argv) => argv.some((arg) => arg === "-v" || arg === "--version");
58
+ try {
59
+ if (!isVersionRequest(process.argv.slice(2))) {
60
+ await ensureCliSelfUpdated();
61
+ }
62
+ }
63
+ catch (error) {
64
+ const message = error instanceof Error ? error.message : String(error);
65
+ process.stderr.write(`cohub self-update failed: ${message}\n`);
66
+ process.exit(1);
67
+ }
36
68
  program.parse();
package/dist/output.js CHANGED
@@ -47,6 +47,9 @@ export function error(msg, detail) {
47
47
  }
48
48
  // -- HTTP error handler ------------------------------------------------------
49
49
  export function handleHttp(e) {
50
+ if (e instanceof Error && e.name === "AuthRequiredError") {
51
+ return error("Not authenticated", "Run `cohub auth login`.");
52
+ }
50
53
  const status = e.status;
51
54
  const body = e.body;
52
55
  const msg = e instanceof Error ? e.message : String(e);
@@ -0,0 +1 @@
1
+ export declare function ensureCliSelfUpdated(): Promise<void>;
@@ -0,0 +1,136 @@
1
+ import { spawn } from "node:child_process";
2
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ const PACKAGE_NAME = "@neta-art/cohub-cli";
6
+ const DEFAULT_INTERVAL_MS = 6 * 60 * 60 * 1000;
7
+ const DEFAULT_TIMEOUT_MS = 60_000;
8
+ const LOCK_STALE_MS = 5 * 60 * 1000;
9
+ const STATE_PATH = join(homedir(), ".cache", "cohub-cli", "self-update.json");
10
+ const LOCK_PATH = join(homedir(), ".cache", "cohub-cli", "self-update.lock");
11
+ const parsePositiveIntEnv = (name, fallback) => {
12
+ const raw = process.env[name]?.trim();
13
+ if (!raw)
14
+ return fallback;
15
+ const value = Number.parseInt(raw, 10);
16
+ return Number.isFinite(value) && value > 0 ? value : fallback;
17
+ };
18
+ const getIntervalMs = () => {
19
+ const explicitMs = process.env.COHUB_CLI_UPDATE_INTERVAL_MS;
20
+ if (explicitMs?.trim())
21
+ return parsePositiveIntEnv("COHUB_CLI_UPDATE_INTERVAL_MS", DEFAULT_INTERVAL_MS);
22
+ const hours = process.env.COHUB_CLI_UPDATE_INTERVAL_HOURS;
23
+ if (hours?.trim()) {
24
+ const value = Number.parseFloat(hours);
25
+ if (Number.isFinite(value) && value > 0)
26
+ return Math.round(value * 60 * 60 * 1000);
27
+ }
28
+ return DEFAULT_INTERVAL_MS;
29
+ };
30
+ const getTimeoutMs = () => parsePositiveIntEnv("COHUB_CLI_UPDATE_TIMEOUT_MS", DEFAULT_TIMEOUT_MS);
31
+ const readState = () => {
32
+ try {
33
+ return JSON.parse(readFileSync(STATE_PATH, "utf-8"));
34
+ }
35
+ catch {
36
+ return {};
37
+ }
38
+ };
39
+ const writeState = () => {
40
+ mkdirSync(dirname(STATE_PATH), { recursive: true });
41
+ writeFileSync(STATE_PATH, `${JSON.stringify({ lastUpdatedAt: new Date().toISOString() }, null, 2)}\n`);
42
+ };
43
+ const isDue = () => {
44
+ const state = readState();
45
+ if (!state.lastUpdatedAt)
46
+ return true;
47
+ const lastUpdatedAt = Date.parse(state.lastUpdatedAt);
48
+ if (!Number.isFinite(lastUpdatedAt))
49
+ return true;
50
+ return Date.now() - lastUpdatedAt >= getIntervalMs();
51
+ };
52
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
53
+ const acquireLock = async (timeoutMs) => {
54
+ mkdirSync(dirname(LOCK_PATH), { recursive: true });
55
+ const startedAt = Date.now();
56
+ while (Date.now() - startedAt < timeoutMs) {
57
+ try {
58
+ mkdirSync(LOCK_PATH);
59
+ writeFileSync(join(LOCK_PATH, "pid"), `${process.pid}\n${Date.now()}\n`);
60
+ return true;
61
+ }
62
+ catch {
63
+ try {
64
+ const raw = readFileSync(join(LOCK_PATH, "pid"), "utf-8");
65
+ const timestamp = Number.parseInt(raw.trim().split(/\s+/).at(-1) ?? "0", 10);
66
+ if (!Number.isFinite(timestamp) || Date.now() - timestamp > LOCK_STALE_MS) {
67
+ rmSync(LOCK_PATH, { recursive: true, force: true });
68
+ continue;
69
+ }
70
+ }
71
+ catch {
72
+ rmSync(LOCK_PATH, { recursive: true, force: true });
73
+ continue;
74
+ }
75
+ await sleep(250);
76
+ }
77
+ }
78
+ return false;
79
+ };
80
+ const releaseLock = () => {
81
+ rmSync(LOCK_PATH, { recursive: true, force: true });
82
+ };
83
+ const runNpmUpdate = (timeoutMs) => {
84
+ return new Promise((resolve, reject) => {
85
+ const child = spawn("npm", ["install", "-g", `${PACKAGE_NAME}@latest`, "--silent"], {
86
+ stdio: ["ignore", "pipe", "pipe"],
87
+ env: process.env,
88
+ });
89
+ const stdoutChunks = [];
90
+ const stderrChunks = [];
91
+ child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
92
+ child.stderr.on("data", (chunk) => stderrChunks.push(chunk));
93
+ const timer = setTimeout(() => {
94
+ child.kill("SIGTERM");
95
+ setTimeout(() => child.kill("SIGKILL"), 3000).unref();
96
+ }, timeoutMs);
97
+ timer.unref();
98
+ child.on("error", (error) => {
99
+ clearTimeout(timer);
100
+ reject(error);
101
+ });
102
+ child.on("close", (code, signal) => {
103
+ clearTimeout(timer);
104
+ if (code === 0) {
105
+ resolve();
106
+ return;
107
+ }
108
+ const stdout = Buffer.concat(stdoutChunks).toString().trim();
109
+ const stderr = Buffer.concat(stderrChunks).toString().trim();
110
+ const detail = [stderr, stdout].filter(Boolean).join("\n");
111
+ reject(new Error(`npm install -g ${PACKAGE_NAME}@latest failed${signal ? ` (${signal})` : code !== null ? ` (exit ${code})` : ""}${detail ? `:\n${detail}` : ""}`));
112
+ });
113
+ });
114
+ };
115
+ export async function ensureCliSelfUpdated() {
116
+ if (process.env.COHUB_CLI_AUTO_UPDATE === "0")
117
+ return;
118
+ if (!isDue())
119
+ return;
120
+ const timeoutMs = getTimeoutMs();
121
+ const locked = await acquireLock(timeoutMs);
122
+ if (!locked) {
123
+ throw new Error(`Timed out waiting for ${PACKAGE_NAME} self-update lock after ${timeoutMs}ms`);
124
+ }
125
+ try {
126
+ // Another process may have completed the update while this process was
127
+ // waiting for the lock.
128
+ if (!isDue())
129
+ return;
130
+ await runNpmUpdate(timeoutMs);
131
+ writeState();
132
+ }
133
+ finally {
134
+ releaseLock();
135
+ }
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "1.1.4",
3
+ "version": "1.3.0",
4
4
  "description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "commander": "^13.1.0",
17
- "@neta-art/cohub": "1.7.1"
17
+ "@neta-art/cohub": "1.9.0"
18
18
  },
19
19
  "publishConfig": {
20
20
  "access": "public"