@neta-art/cohub-cli 6.8.0 → 6.8.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/bootstrap.js CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { envProxyExecArgv, shouldRelaunchWithEnvProxy } from "./env-proxy.js";
3
+ import { relaunchCli } from "./launcher.js";
2
4
  import { ensureCliSelfUpdated, SELF_UPDATE_WORKER_ENV, startCliSelfUpdate } from "./self-update.js";
3
5
  const argv = process.argv.slice(2);
4
6
  const isVersionRequest = argv.some((arg) => arg === "-v" || arg === "--version");
@@ -11,6 +13,14 @@ if (process.env[SELF_UPDATE_WORKER_ENV] === "1") {
11
13
  }
12
14
  process.exit(0);
13
15
  }
16
+ if (!isVersionRequest && shouldRelaunchWithEnvProxy()) {
17
+ const entrypoint = process.argv[1];
18
+ if (entrypoint) {
19
+ process.exit(await relaunchCli(entrypoint, argv, {
20
+ execArgv: [...envProxyExecArgv, ...process.execArgv],
21
+ }));
22
+ }
23
+ }
14
24
  if (!isVersionRequest) {
15
25
  const entrypoint = process.argv[1];
16
26
  if (entrypoint)
@@ -1,5 +1,4 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { createReadStream } from "node:fs";
3
2
  import { basename } from "node:path";
4
3
  import { getCohubContext, HttpError } from "@neta-art/cohub";
5
4
  import { createClient, createClientWithAccessToken } from "../client.js";
@@ -8,6 +7,7 @@ import { resolveSpace } from "../space.js";
8
7
  import { downloadApp } from "../app-download.js";
9
8
  import { getAppByRef, parseAppRef } from "../app-ref.js";
10
9
  import { checkAppTarget } from "../app-target.js";
10
+ import { putBytes, putLocalFile } from "../http-put.js";
11
11
  import { registerAppCommerce } from "./app-commerce.js";
12
12
  import { collectPublicUpload } from "./public.js";
13
13
  const APP_STATUSES = ["published", "disabled"];
@@ -90,14 +90,13 @@ async function uploadLocalAppSource(client, spaceId, source) {
90
90
  sessionId: uploadId,
91
91
  file: { size: file.size, mimeType: file.mimeType, filename: basename(file.publicPath) },
92
92
  });
93
- const response = await fetch(plan.asset.uploadUrl, {
94
- method: "PUT",
93
+ await putLocalFile({
94
+ url: plan.asset.uploadUrl,
95
+ filePath: file.localPath,
96
+ size: file.size,
95
97
  headers: plan.asset.uploadHeaders,
96
- body: createReadStream(file.localPath),
97
- duplex: "half",
98
+ label: file.publicPath,
98
99
  });
99
- if (!response.ok)
100
- throw new Error(`Failed to upload ${file.publicPath}: HTTP ${response.status}`);
101
100
  const path = directoryPrefix && file.publicPath.startsWith(`${directoryPrefix}/`)
102
101
  ? file.publicPath.slice(directoryPrefix.length + 1)
103
102
  : basename(file.publicPath);
@@ -114,13 +113,13 @@ async function uploadLocalAppSource(client, spaceId, source) {
114
113
  sessionId: uploadId,
115
114
  file: { size: manifestBlob.size, mimeType: "application/json", filename: "manifest.json" },
116
115
  });
117
- const manifestResponse = await fetch(manifestPlan.asset.uploadUrl, {
118
- method: "PUT",
119
- headers: manifestPlan.asset.uploadHeaders,
116
+ await putBytes({
117
+ url: manifestPlan.asset.uploadUrl,
120
118
  body: manifestBlob,
119
+ size: manifestBlob.size,
120
+ headers: manifestPlan.asset.uploadHeaders,
121
+ label: "app source manifest",
121
122
  });
122
- if (!manifestResponse.ok)
123
- throw new Error(`Failed to upload app source manifest: HTTP ${manifestResponse.status}`);
124
123
  const manifestAsset = manifestPlan.asset;
125
124
  return { sourceRef: manifestAsset.objectKey, targetRef: source.targetType === "file" ? files[0]?.path ?? "" : "." };
126
125
  }
@@ -1,8 +1,8 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { createReadStream } from "node:fs";
3
2
  import { lstat, readdir } from "node:fs/promises";
4
3
  import { basename, extname, relative, resolve } from "node:path";
5
4
  import { createClient } from "../client.js";
5
+ import { HttpPutError, putLocalFile } from "../http-put.js";
6
6
  import { error, handleHttp, json as outJson, jsonRequested } from "../output.js";
7
7
  import { resolveSpace } from "../space.js";
8
8
  const UPLOAD_CONCURRENCY = 4;
@@ -149,19 +149,22 @@ async function mapSettledWithConcurrency(items, concurrency, mapper) {
149
149
  return errors;
150
150
  }
151
151
  async function putPublicFile(file, plan, fetchImpl) {
152
- const response = await fetchImpl(plan.uploadUrl, {
153
- method: "PUT",
154
- headers: plan.headers,
155
- body: createReadStream(file.localPath),
156
- duplex: "half",
157
- });
158
- if (response.ok)
159
- return;
160
- const detail = await response.text().catch(() => "");
161
- if (response.status === 409 || response.status === 412) {
162
- throw new Error(`${file.publicPath} already exists. Use --overwrite.`);
152
+ try {
153
+ await putLocalFile({
154
+ url: plan.uploadUrl,
155
+ filePath: file.localPath,
156
+ size: file.size,
157
+ headers: plan.headers,
158
+ label: file.publicPath,
159
+ fetch: fetchImpl,
160
+ });
161
+ }
162
+ catch (error) {
163
+ if (error instanceof HttpPutError && (error.status === 409 || error.status === 412)) {
164
+ throw new Error(`${file.publicPath} already exists. Use --overwrite.`);
165
+ }
166
+ throw error;
163
167
  }
164
- throw new Error(`Failed to upload ${file.publicPath}: HTTP ${response.status}${detail ? ` — ${detail}` : ""}`);
165
168
  }
166
169
  function uploadFailure(errors) {
167
170
  if (errors.length === 1)
@@ -1 +1,2 @@
1
+ export declare function printRunHelp(): void;
1
2
  export declare function maybeHandleRunCommand(argv: string[]): Promise<boolean>;
@@ -107,7 +107,7 @@ async function parseRunCliOptions(argv) {
107
107
  const spaceId = explicitSpaceId || (await resolveDefaultSpace().catch(handleHttp)) || missingSpaceError();
108
108
  return { spaceId, json, async, command };
109
109
  }
110
- function printRunHelp() {
110
+ export function printRunHelp() {
111
111
  process.stdout.write(`
112
112
  Usage:
113
113
  cohub [-s <spaceId>] run [options] -- <shell command>
@@ -1,10 +1,10 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { createReadStream } from "node:fs";
3
2
  import { readdir, stat } from "node:fs/promises";
4
3
  import { basename, dirname, relative, resolve, sep } from "node:path";
5
4
  import { resolveCohubEnvironment } from "@neta-art/cohub";
6
5
  import { uploadAvatarAsset, uploadChatImageAsset } from "../avatar.js";
7
6
  import { createClient } from "../client.js";
7
+ import { putLocalFile } from "../http-put.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";
@@ -156,16 +156,13 @@ export async function collectUploadFiles(paths) {
156
156
  return files;
157
157
  }
158
158
  async function putUploadEntry(entry, uploadUrl, headers) {
159
- const response = await fetch(uploadUrl, {
160
- method: "PUT",
159
+ await putLocalFile({
160
+ url: uploadUrl,
161
+ filePath: entry.localPath,
162
+ size: entry.size,
161
163
  headers,
162
- body: createReadStream(entry.localPath),
163
- duplex: "half",
164
+ label: entry.relativePath,
164
165
  });
165
- if (!response.ok) {
166
- const detail = await response.text().catch(() => "");
167
- throw new Error(`Failed to upload ${entry.relativePath}: HTTP ${response.status}${detail ? ` — ${detail}` : ""}`);
168
- }
169
166
  }
170
167
  async function uploadFiles(command, paths, opts) {
171
168
  const spaceId = await resolveSpace(command);
@@ -0,0 +1,5 @@
1
+ export declare function proxyEnvPresent(env?: NodeJS.ProcessEnv): boolean;
2
+ export declare function envProxyAlreadyEnabled(execArgv?: readonly string[], env?: NodeJS.ProcessEnv): boolean;
3
+ export declare function supportsUseEnvProxy(version?: string): boolean;
4
+ export declare function shouldRelaunchWithEnvProxy(execArgv?: readonly string[], env?: NodeJS.ProcessEnv, nodeVersion?: string): boolean;
5
+ export declare const envProxyExecArgv: readonly ["--use-env-proxy"];
@@ -0,0 +1,20 @@
1
+ const PROXY_ENV_KEYS = ["http_proxy", "https_proxy", "HTTP_PROXY", "HTTPS_PROXY", "all_proxy", "ALL_PROXY"];
2
+ const USE_ENV_PROXY_FLAG = "--use-env-proxy";
3
+ export function proxyEnvPresent(env = process.env) {
4
+ return PROXY_ENV_KEYS.some((key) => Boolean(env[key]?.trim()));
5
+ }
6
+ export function envProxyAlreadyEnabled(execArgv = process.execArgv, env = process.env) {
7
+ if (execArgv.includes(USE_ENV_PROXY_FLAG) || env.NODE_USE_ENV_PROXY === "1")
8
+ return true;
9
+ return (env.NODE_OPTIONS ?? "").split(/\s+/).includes(USE_ENV_PROXY_FLAG);
10
+ }
11
+ export function supportsUseEnvProxy(version = process.versions.node) {
12
+ const [major = 0, minor = 0] = version.split(".").map((part) => Number.parseInt(part, 10));
13
+ if (!Number.isFinite(major) || !Number.isFinite(minor))
14
+ return false;
15
+ return major >= 24 || (major === 22 && minor >= 21);
16
+ }
17
+ export function shouldRelaunchWithEnvProxy(execArgv = process.execArgv, env = process.env, nodeVersion = process.versions.node) {
18
+ return supportsUseEnvProxy(nodeVersion) && proxyEnvPresent(env) && !envProxyAlreadyEnabled(execArgv, env);
19
+ }
20
+ export const envProxyExecArgv = [USE_ENV_PROXY_FLAG];
@@ -0,0 +1,18 @@
1
+ import type { Command } from "commander";
2
+ export type HelpResolution = {
3
+ kind: "passthrough";
4
+ } | {
5
+ kind: "help";
6
+ command: Command;
7
+ } | {
8
+ kind: "run-help";
9
+ } | {
10
+ kind: "unknown";
11
+ token: string;
12
+ tryCommand?: string;
13
+ suggestion?: string;
14
+ };
15
+ export declare function resolveHelpPath(program: Command, argv: string[]): HelpResolution;
16
+ export declare function formatUnknownCommandError(result: Extract<HelpResolution, {
17
+ kind: "unknown";
18
+ }>): string;
@@ -0,0 +1,223 @@
1
+ function isHelpFlag(token) {
2
+ return token === "-h" || token === "--help";
3
+ }
4
+ function findChild(command, name) {
5
+ return command.commands.find((child) => {
6
+ if (child.name() === "help")
7
+ return false;
8
+ return child.name() === name || child.aliases().includes(name);
9
+ });
10
+ }
11
+ function hasSubcommands(command) {
12
+ return command.commands.some((child) => child.name() !== "help");
13
+ }
14
+ function knownOptions(command) {
15
+ const options = [];
16
+ let current = command;
17
+ while (current) {
18
+ options.push(...current.options);
19
+ current = current.parent;
20
+ }
21
+ return options;
22
+ }
23
+ function matchOption(command, token) {
24
+ if (!token.startsWith("-") || token === "-")
25
+ return undefined;
26
+ const eq = token.indexOf("=");
27
+ const inline = eq >= 0;
28
+ const flag = inline ? token.slice(0, eq) : token;
29
+ for (const option of knownOptions(command)) {
30
+ if (option.short === flag || option.long === flag)
31
+ return { option, inline };
32
+ }
33
+ return undefined;
34
+ }
35
+ function skipOption(command, argv, index) {
36
+ const token = argv[index] ?? "";
37
+ const matched = matchOption(command, token);
38
+ const next = index + 1;
39
+ if (matched?.inline)
40
+ return next;
41
+ const value = argv[next];
42
+ if (!value)
43
+ return next;
44
+ if (matched?.option.required)
45
+ return next + 1;
46
+ if (matched?.option.optional && !value.startsWith("-"))
47
+ return next + 1;
48
+ return next;
49
+ }
50
+ function remainderRequestsHelp(command, argv) {
51
+ let index = 0;
52
+ while (index < argv.length) {
53
+ const token = argv[index] ?? "";
54
+ if (token === "--")
55
+ break;
56
+ if (isHelpFlag(token))
57
+ return true;
58
+ if (token.startsWith("-")) {
59
+ index = skipOption(command, argv, index);
60
+ continue;
61
+ }
62
+ index += 1;
63
+ }
64
+ return false;
65
+ }
66
+ /** Match `run.ts`: Cohub flags first, then the rest is the shell command. */
67
+ function runRemainderRequestsHelp(argv) {
68
+ for (let index = 0; index < argv.length; index += 1) {
69
+ const token = argv[index] ?? "";
70
+ if (token === "--")
71
+ return false;
72
+ if (isHelpFlag(token))
73
+ return true;
74
+ if (token === "--async" || token === "--json")
75
+ continue;
76
+ if (token === "-c" || token === "--command") {
77
+ index += 1;
78
+ continue;
79
+ }
80
+ if (token.startsWith("--command="))
81
+ continue;
82
+ return false;
83
+ }
84
+ return false;
85
+ }
86
+ function walkCommandPath(program, argv) {
87
+ let command = program;
88
+ const path = [];
89
+ let helpRequested = false;
90
+ let index = 0;
91
+ while (index < argv.length) {
92
+ const token = argv[index] ?? "";
93
+ if (token === "--")
94
+ break;
95
+ if (isHelpFlag(token)) {
96
+ helpRequested = true;
97
+ index += 1;
98
+ continue;
99
+ }
100
+ if (token.startsWith("-")) {
101
+ index = skipOption(command, argv, index);
102
+ continue;
103
+ }
104
+ if (token === "help" && hasSubcommands(command)) {
105
+ helpRequested = true;
106
+ index += 1;
107
+ continue;
108
+ }
109
+ const child = findChild(command, token);
110
+ if (child) {
111
+ command = child;
112
+ path.push(child.name());
113
+ index += 1;
114
+ continue;
115
+ }
116
+ if (hasSubcommands(command)) {
117
+ const remainder = argv.slice(index + 1);
118
+ const helpAfter = token === "run" && path.length === 0
119
+ ? runRemainderRequestsHelp(remainder)
120
+ : remainderRequestsHelp(command, remainder);
121
+ return {
122
+ command,
123
+ path,
124
+ helpRequested: helpRequested || helpAfter,
125
+ unknown: token,
126
+ };
127
+ }
128
+ index += 1;
129
+ }
130
+ return { command, path, helpRequested, unknown: null };
131
+ }
132
+ function stripRootCommand(program, argv, name) {
133
+ let index = 0;
134
+ while (index < argv.length) {
135
+ const token = argv[index] ?? "";
136
+ if (token === "--")
137
+ return undefined;
138
+ if (isHelpFlag(token) || token === "help") {
139
+ index += 1;
140
+ continue;
141
+ }
142
+ if (token.startsWith("-")) {
143
+ index = skipOption(program, argv, index);
144
+ continue;
145
+ }
146
+ if (token !== name)
147
+ return undefined;
148
+ return [...argv.slice(0, index), ...argv.slice(index + 1)];
149
+ }
150
+ return undefined;
151
+ }
152
+ function editDistance(a, b) {
153
+ if (a === b)
154
+ return 0;
155
+ if (Math.abs(a.length - b.length) > 3)
156
+ return Math.max(a.length, b.length);
157
+ const rows = a.length + 1;
158
+ const cols = b.length + 1;
159
+ const previous = Array.from({ length: cols }, (_, index) => index);
160
+ const current = new Array(cols);
161
+ for (let i = 1; i < rows; i += 1) {
162
+ current[0] = i;
163
+ for (let j = 1; j < cols; j += 1) {
164
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
165
+ current[j] = Math.min((previous[j] ?? 0) + 1, (current[j - 1] ?? 0) + 1, (previous[j - 1] ?? 0) + cost);
166
+ }
167
+ for (let j = 0; j < cols; j += 1)
168
+ previous[j] = current[j] ?? 0;
169
+ }
170
+ return previous[b.length] ?? Math.max(a.length, b.length);
171
+ }
172
+ function suggestCommand(command, token) {
173
+ const names = [...new Set(command.commands.flatMap((child) => {
174
+ if (child.name() === "help" || child.name().length <= 1)
175
+ return [];
176
+ return [child.name(), ...child.aliases().filter((alias) => alias.length > 1)];
177
+ }))];
178
+ let best;
179
+ let bestDistance = 3;
180
+ for (const name of names) {
181
+ const distance = editDistance(token, name);
182
+ const length = Math.max(token.length, name.length);
183
+ if (distance >= bestDistance || (length - distance) / length <= 0.4)
184
+ continue;
185
+ bestDistance = distance;
186
+ best = name;
187
+ }
188
+ return best;
189
+ }
190
+ export function resolveHelpPath(program, argv) {
191
+ const walk = walkCommandPath(program, argv);
192
+ const cliAtRoot = walk.unknown === "cli" && walk.path.length === 0;
193
+ if (cliAtRoot) {
194
+ const stripped = stripRootCommand(program, argv, "cli");
195
+ return {
196
+ kind: "unknown",
197
+ token: "cli",
198
+ tryCommand: stripped && stripped.length > 0 ? stripped.join(" ") : undefined,
199
+ };
200
+ }
201
+ if (!walk.helpRequested)
202
+ return { kind: "passthrough" };
203
+ if (walk.unknown === "run" && walk.path.length === 0)
204
+ return { kind: "run-help" };
205
+ if (walk.unknown) {
206
+ return {
207
+ kind: "unknown",
208
+ token: walk.unknown,
209
+ suggestion: suggestCommand(walk.command, walk.unknown),
210
+ };
211
+ }
212
+ return { kind: "help", command: walk.command };
213
+ }
214
+ export function formatUnknownCommandError(result) {
215
+ const lines = [`error: unknown command '${result.token}'`];
216
+ if (result.tryCommand)
217
+ lines.push(`Try: cohub ${result.tryCommand}`);
218
+ else if (result.token === "cli")
219
+ lines.push(`cohub has no "cli" subcommand. See cohub --help`);
220
+ else if (result.suggestion)
221
+ lines.push(`(Did you mean ${result.suggestion}?)`);
222
+ return `${lines.join("\n")}\n`;
223
+ }
@@ -0,0 +1,25 @@
1
+ export declare class HttpPutError extends Error {
2
+ readonly status: number;
3
+ readonly body: string;
4
+ constructor(message: string, status: number, body?: string);
5
+ }
6
+ export type PutRetryOptions = {
7
+ attempts?: number;
8
+ delayMs?: number;
9
+ sleep?: (ms: number) => Promise<void>;
10
+ fetch?: typeof fetch;
11
+ };
12
+ export declare function putLocalFile(input: PutRetryOptions & {
13
+ url: string;
14
+ filePath: string;
15
+ size: number;
16
+ headers?: HeadersInit;
17
+ label: string;
18
+ }): Promise<void>;
19
+ export declare function putBytes(input: PutRetryOptions & {
20
+ url: string;
21
+ body: Blob;
22
+ size?: number;
23
+ headers?: HeadersInit;
24
+ label: string;
25
+ }): Promise<void>;
@@ -0,0 +1,132 @@
1
+ import { createReadStream } from "node:fs";
2
+ const DEFAULT_ATTEMPTS = 3;
3
+ const DEFAULT_DELAY_MS = 200;
4
+ const RETRYABLE_STATUS = new Set([408, 411, 425, 429, 500, 502, 503, 504]);
5
+ const RETRYABLE_CODES = new Set([
6
+ "BodyTimeoutError",
7
+ "ConnectTimeoutError",
8
+ "EAI_AGAIN",
9
+ "ECONNREFUSED",
10
+ "ECONNRESET",
11
+ "EHOSTUNREACH",
12
+ "ENETUNREACH",
13
+ "ENOTFOUND",
14
+ "EPIPE",
15
+ "ETIMEDOUT",
16
+ "HeadersTimeoutError",
17
+ "UND_ERR_BODY_TIMEOUT",
18
+ "UND_ERR_CONNECT",
19
+ "UND_ERR_CONNECT_TIMEOUT",
20
+ "UND_ERR_HEADERS_TIMEOUT",
21
+ "UND_ERR_SOCKET",
22
+ ]);
23
+ export class HttpPutError extends Error {
24
+ status;
25
+ body;
26
+ constructor(message, status, body = "") {
27
+ super(message);
28
+ this.name = "HttpPutError";
29
+ this.status = status;
30
+ this.body = body;
31
+ }
32
+ }
33
+ function withContentLength(headers, contentLength) {
34
+ const result = new Headers(headers);
35
+ if (contentLength !== undefined && !result.has("content-length")) {
36
+ result.set("content-length", String(contentLength));
37
+ }
38
+ return result;
39
+ }
40
+ function errorCode(error) {
41
+ if (!error || typeof error !== "object")
42
+ return undefined;
43
+ const record = error;
44
+ if (typeof record.code === "string")
45
+ return record.code;
46
+ if (typeof record.cause?.code === "string")
47
+ return record.cause.code;
48
+ if (typeof record.cause?.name === "string")
49
+ return record.cause.name;
50
+ if (typeof record.name === "string")
51
+ return record.name;
52
+ return undefined;
53
+ }
54
+ function isRetryableError(error) {
55
+ const code = errorCode(error);
56
+ return Boolean(code && RETRYABLE_CODES.has(code));
57
+ }
58
+ function isRetryableStatus(status) {
59
+ return RETRYABLE_STATUS.has(status);
60
+ }
61
+ function defaultSleep(ms) {
62
+ return new Promise((resolve) => setTimeout(resolve, ms));
63
+ }
64
+ function failureMessage(label, status, detail) {
65
+ return `Failed to upload ${label}: HTTP ${status}${detail ? ` — ${detail}` : ""}`;
66
+ }
67
+ async function putWithRetry(input) {
68
+ const attempts = input.attempts ?? DEFAULT_ATTEMPTS;
69
+ const delayMs = input.delayMs ?? DEFAULT_DELAY_MS;
70
+ const sleep = input.sleep ?? defaultSleep;
71
+ const fetchImpl = input.fetch ?? fetch;
72
+ let lastError;
73
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
74
+ try {
75
+ const response = await fetchImpl(input.url, {
76
+ method: "PUT",
77
+ headers: withContentLength(input.headers, input.contentLength),
78
+ body: input.body(),
79
+ ...(input.duplex ? { duplex: "half" } : {}),
80
+ });
81
+ if (response.ok)
82
+ return response;
83
+ const detail = await response.text().catch(() => "");
84
+ const error = new HttpPutError(failureMessage(input.label, response.status, detail), response.status, detail);
85
+ if (attempt < attempts - 1 && isRetryableStatus(response.status)) {
86
+ lastError = error;
87
+ await sleep(delayMs * 2 ** attempt);
88
+ continue;
89
+ }
90
+ throw error;
91
+ }
92
+ catch (error) {
93
+ if (error instanceof HttpPutError)
94
+ throw error;
95
+ lastError = error;
96
+ if (attempt < attempts - 1 && isRetryableError(error)) {
97
+ await sleep(delayMs * 2 ** attempt);
98
+ continue;
99
+ }
100
+ throw error;
101
+ }
102
+ }
103
+ throw lastError;
104
+ }
105
+ export async function putLocalFile(input) {
106
+ await putWithRetry({
107
+ url: input.url,
108
+ body: () => createReadStream(input.filePath),
109
+ headers: input.headers,
110
+ contentLength: input.size,
111
+ duplex: true,
112
+ label: input.label,
113
+ attempts: input.attempts,
114
+ delayMs: input.delayMs,
115
+ sleep: input.sleep,
116
+ fetch: input.fetch,
117
+ });
118
+ }
119
+ export async function putBytes(input) {
120
+ const body = input.body;
121
+ await putWithRetry({
122
+ url: input.url,
123
+ body: () => body,
124
+ headers: input.headers,
125
+ contentLength: input.size,
126
+ label: input.label,
127
+ attempts: input.attempts,
128
+ delayMs: input.delayMs,
129
+ sleep: input.sleep,
130
+ fetch: input.fetch,
131
+ });
132
+ }
package/dist/index.js CHANGED
@@ -16,11 +16,12 @@ import { registerSearch } from "./commands/search.js";
16
16
  import { registerReferences } from "./commands/references.js";
17
17
  import { registerReferrals } from "./commands/referrals.js";
18
18
  import { registerPrompt, registerSpaces } from "./commands/spaces.js";
19
- import { maybeHandleRunCommand } from "./commands/run.js";
19
+ import { maybeHandleRunCommand, printRunHelp } from "./commands/run.js";
20
20
  import { registerSandbox } from "./commands/sandbox.js";
21
21
  import { registerTasks } from "./commands/tasks.js";
22
22
  import { registerDesktop, registerLegacyUi } from "./commands/desktop.js";
23
23
  import { registerApps } from "./commands/apps.js";
24
+ import { formatUnknownCommandError, resolveHelpPath } from "./help-path.js";
24
25
  const VERSION = (() => {
25
26
  try {
26
27
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
@@ -41,6 +42,10 @@ program
41
42
  .helpOption("-h, --help", "Show help")
42
43
  .addHelpText("after", `
43
44
 
45
+ Help:
46
+ cohub apps publish --help
47
+ cohub help apps publish
48
+
44
49
  Common commands:
45
50
  cohub auth login
46
51
  cohub profile avatar ./avatar.png
@@ -69,6 +74,7 @@ Environment:
69
74
  COHUB_SPACE_ID Target Space ID when -s is omitted
70
75
  COHUB_EXECUTION_TOKEN Use this token instead of the stored Logto session
71
76
  ENV=dev Use the development Cohub environment
77
+ HTTPS_PROXY Honored for API and uploads (also HTTP_PROXY, NO_PROXY)
72
78
  `);
73
79
  registerAuth(program);
74
80
  registerBoards(program);
@@ -92,7 +98,20 @@ registerApps(program);
92
98
  registerDesktop(program);
93
99
  registerLegacyUi(program);
94
100
  const argv = process.argv.slice(2);
95
- if (await maybeHandleRunCommand(argv)) {
101
+ const help = resolveHelpPath(program, argv);
102
+ if (help.kind === "help") {
103
+ help.command.outputHelp();
104
+ }
105
+ else if (help.kind === "run-help") {
106
+ printRunHelp();
107
+ }
108
+ else if (help.kind === "unknown") {
109
+ process.stderr.write(formatUnknownCommandError(help));
110
+ process.exit(1);
111
+ }
112
+ else if (await maybeHandleRunCommand(argv)) {
96
113
  process.exit();
97
114
  }
98
- program.parse();
115
+ else {
116
+ program.parse();
117
+ }
@@ -1,2 +1,4 @@
1
1
  export declare function exitCodeForChild(code: number | null, signal: NodeJS.Signals | null): number;
2
- export declare function relaunchCli(entrypoint: string, argv: string[]): Promise<number>;
2
+ export declare function relaunchCli(entrypoint: string, argv: string[], options?: {
3
+ execArgv?: readonly string[];
4
+ }): Promise<number>;
package/dist/launcher.js CHANGED
@@ -8,9 +8,9 @@ export function exitCodeForChild(code, signal) {
8
8
  return 1;
9
9
  return 128 + (constants.signals[signal] ?? 1);
10
10
  }
11
- export function relaunchCli(entrypoint, argv) {
11
+ export function relaunchCli(entrypoint, argv, options) {
12
12
  return new Promise((resolve, reject) => {
13
- const child = spawn(process.execPath, [entrypoint, ...argv], {
13
+ const child = spawn(process.execPath, [...(options?.execArgv ?? []), entrypoint, ...argv], {
14
14
  env: process.env,
15
15
  stdio: "inherit",
16
16
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neta-art/cohub-cli",
3
- "version": "6.8.0",
3
+ "version": "6.8.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.20.1",
21
21
  "sharp": "^0.35.4",
22
- "@neta-art/cohub": "8.10.1"
22
+ "@neta-art/cohub": "8.11.0"
23
23
  },
24
24
  "publishConfig": {
25
25
  "access": "public"