@neta-art/cohub-cli 5.0.0 → 6.0.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/app-download.d.ts +11 -0
- package/dist/{work-download.js → app-download.js} +23 -23
- package/dist/app-ref.d.ts +5 -0
- package/dist/app-ref.js +8 -0
- package/dist/commands/app-commerce.d.ts +2 -0
- package/dist/commands/{work-commerce.js → app-commerce.js} +27 -27
- package/dist/commands/apps.d.ts +4 -0
- package/dist/commands/{works.js → apps.js} +136 -127
- package/dist/commands/desktop.d.ts +3 -0
- package/dist/commands/desktop.js +219 -0
- package/dist/commands/profile.js +5 -5
- package/dist/index.js +7 -6
- package/package.json +2 -2
- package/dist/commands/ui.d.ts +0 -2
- package/dist/commands/ui.js +0 -199
- package/dist/commands/work-commerce.d.ts +0 -2
- package/dist/commands/works.d.ts +0 -4
- package/dist/work-download.d.ts +0 -11
- package/dist/work-ref.d.ts +0 -5
- package/dist/work-ref.js +0 -8
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { HttpError, } from "@neta-art/cohub";
|
|
3
|
+
import { parseAppRef, DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS, DESKTOP_COMMAND_MAX_TIMEOUT_MS, } from "@neta-art/cohub";
|
|
4
|
+
import { createClient } from "../client.js";
|
|
5
|
+
import { error, handleHttp, json as outJson, jsonRequested, ok } from "../output.js";
|
|
6
|
+
import { getAppByRef } from "../app-ref.js";
|
|
7
|
+
const FILE_SCHEME = "file://";
|
|
8
|
+
const APP_SCHEME = "app://";
|
|
9
|
+
const LEGACY_WORK_SCHEME = "work://";
|
|
10
|
+
function optionalSpaceId(command) {
|
|
11
|
+
let current = command;
|
|
12
|
+
while (current) {
|
|
13
|
+
const opts = current.opts();
|
|
14
|
+
if (typeof opts.space === "string" && opts.space.trim())
|
|
15
|
+
return opts.space.trim();
|
|
16
|
+
current = current.parent ?? null;
|
|
17
|
+
}
|
|
18
|
+
return process.env.COHUB_SPACE_ID?.trim() || undefined;
|
|
19
|
+
}
|
|
20
|
+
function parseFilePath(value) {
|
|
21
|
+
const path = value.slice(FILE_SCHEME.length).trim();
|
|
22
|
+
if (!path ||
|
|
23
|
+
path.startsWith("/") ||
|
|
24
|
+
path.includes("\0") ||
|
|
25
|
+
path.split("/").some((segment) => segment === "..") ||
|
|
26
|
+
path.includes("\\")) {
|
|
27
|
+
return error("Invalid file path", "Use a relative Space path after file://.");
|
|
28
|
+
}
|
|
29
|
+
return path;
|
|
30
|
+
}
|
|
31
|
+
function hasFileScheme(value) {
|
|
32
|
+
return value.toLowerCase().startsWith(FILE_SCHEME);
|
|
33
|
+
}
|
|
34
|
+
function hasAppScheme(value) {
|
|
35
|
+
const lowered = value.toLowerCase();
|
|
36
|
+
return lowered.startsWith(APP_SCHEME) || lowered.startsWith(LEGACY_WORK_SCHEME);
|
|
37
|
+
}
|
|
38
|
+
function readCallInput(opts) {
|
|
39
|
+
if (opts.data !== undefined && opts.input !== undefined) {
|
|
40
|
+
return error("Conflicting input", "Use either --data or --input, not both.");
|
|
41
|
+
}
|
|
42
|
+
const raw = opts.data !== undefined
|
|
43
|
+
? opts.data
|
|
44
|
+
: opts.input !== undefined
|
|
45
|
+
? opts.input === "-"
|
|
46
|
+
? readFileSync(0, "utf-8")
|
|
47
|
+
: readFileSync(opts.input, "utf-8")
|
|
48
|
+
: undefined;
|
|
49
|
+
if (raw === undefined)
|
|
50
|
+
return undefined;
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(raw);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return error("Invalid input", "Call input must be valid JSON.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function parseTimeout(value) {
|
|
59
|
+
if (!value)
|
|
60
|
+
return DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS;
|
|
61
|
+
const parsed = Math.floor(Number(value));
|
|
62
|
+
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > DESKTOP_COMMAND_MAX_TIMEOUT_MS) {
|
|
63
|
+
return error("Invalid timeout", `--timeout-ms must be between 1 and ${DESKTOP_COMMAND_MAX_TIMEOUT_MS} milliseconds.`);
|
|
64
|
+
}
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
async function resolveAppTarget(client, ref) {
|
|
68
|
+
const normalized = hasAppScheme(ref) ? ref.replace(/^[a-zA-Z]+:\/\//, "") : ref;
|
|
69
|
+
const parsed = parseAppRef(normalized);
|
|
70
|
+
const detail = await getAppByRef(client, normalized);
|
|
71
|
+
return {
|
|
72
|
+
kind: "app",
|
|
73
|
+
appId: detail.app.id,
|
|
74
|
+
label: detail.app.slug,
|
|
75
|
+
launch: {
|
|
76
|
+
...(parsed.search ? { search: parsed.search } : {}),
|
|
77
|
+
...(parsed.hash ? { hash: parsed.hash } : {}),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
async function resolveOpenTarget(client, command, value) {
|
|
82
|
+
if (hasFileScheme(value))
|
|
83
|
+
return { kind: "file", path: parseFilePath(value) };
|
|
84
|
+
if (hasAppScheme(value))
|
|
85
|
+
return resolveAppTarget(client, value);
|
|
86
|
+
const spaceId = optionalSpaceId(command);
|
|
87
|
+
if (spaceId) {
|
|
88
|
+
try {
|
|
89
|
+
await client.space(spaceId).files.read(value);
|
|
90
|
+
return { kind: "file", path: value };
|
|
91
|
+
}
|
|
92
|
+
catch (cause) {
|
|
93
|
+
if (!(cause instanceof HttpError) || cause.status !== 404)
|
|
94
|
+
throw cause;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return resolveAppTarget(client, value);
|
|
98
|
+
}
|
|
99
|
+
function reportDispatch(record) {
|
|
100
|
+
if (record.status !== "pending") {
|
|
101
|
+
reportOutcome(record, Boolean(record.command.call));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
ok(`Desktop command dispatched (${record.commandId})`);
|
|
105
|
+
}
|
|
106
|
+
function reportOutcome(record, called) {
|
|
107
|
+
if (record.status === "applied") {
|
|
108
|
+
ok(called ? "App window shown and method called" : "Window shown");
|
|
109
|
+
if (record.result !== undefined) {
|
|
110
|
+
console.log(typeof record.result === "string" ? record.result : JSON.stringify(record.result, null, 2));
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
error(`Desktop command ${record.status}`, record.error?.message ?? "The Cohub desktop did not apply this command.");
|
|
115
|
+
}
|
|
116
|
+
async function openWindow(target, opts, command) {
|
|
117
|
+
const callInput = opts.call ? readCallInput(opts) : undefined;
|
|
118
|
+
if (!opts.call && (opts.data !== undefined || opts.input !== undefined)) {
|
|
119
|
+
return error("Missing --call", "--data and --input only apply together with --call.");
|
|
120
|
+
}
|
|
121
|
+
if (opts.noWait && opts.timeoutMs !== undefined) {
|
|
122
|
+
return error("Conflicting wait options", "Use either --no-wait or --timeout-ms, not both.");
|
|
123
|
+
}
|
|
124
|
+
const timeoutMs = parseTimeout(opts.timeoutMs);
|
|
125
|
+
const client = createClient();
|
|
126
|
+
try {
|
|
127
|
+
const resolved = await resolveOpenTarget(client, command, target);
|
|
128
|
+
if (resolved.kind === "file" && opts.call) {
|
|
129
|
+
return error("Unsupported option", "--call only applies to app targets.");
|
|
130
|
+
}
|
|
131
|
+
const call = opts.call
|
|
132
|
+
? { method: opts.call, ...(callInput === undefined ? {} : { input: callInput }) }
|
|
133
|
+
: undefined;
|
|
134
|
+
const input = {
|
|
135
|
+
command: {
|
|
136
|
+
type: "desktop.open",
|
|
137
|
+
target: resolved.kind === "file"
|
|
138
|
+
? resolved
|
|
139
|
+
: {
|
|
140
|
+
kind: "app",
|
|
141
|
+
appId: resolved.appId,
|
|
142
|
+
label: resolved.label,
|
|
143
|
+
...(resolved.launch.search || resolved.launch.hash ? { launch: resolved.launch } : {}),
|
|
144
|
+
},
|
|
145
|
+
...(call ? { call } : {}),
|
|
146
|
+
},
|
|
147
|
+
...(opts.client ? { targetClientId: opts.client } : {}),
|
|
148
|
+
...(opts.commandId ? { commandId: opts.commandId } : {}),
|
|
149
|
+
};
|
|
150
|
+
const record = opts.noWait
|
|
151
|
+
? (await client.desktop.create(input)).command
|
|
152
|
+
: await client.desktop.run(input, { timeoutMs });
|
|
153
|
+
if (jsonRequested(opts))
|
|
154
|
+
return outJson(record);
|
|
155
|
+
if (opts.noWait)
|
|
156
|
+
return reportDispatch(record);
|
|
157
|
+
reportOutcome(record, Boolean(call));
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
if (e instanceof Error && e.name === "AppRefParseError")
|
|
161
|
+
return error(e.message);
|
|
162
|
+
handleHttp(e);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const OPEN_HELP = `
|
|
166
|
+
Commands reach only the desktop that originated the current chat, resolved
|
|
167
|
+
from request provenance. Nothing else can be targeted.
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
cohub desktop open <app-or-file>
|
|
171
|
+
cohub desktop open file://src/main.ts
|
|
172
|
+
cohub desktop open app://alice/studio/launch
|
|
173
|
+
cohub desktop open alice/studio/launch
|
|
174
|
+
cohub desktop open https://cohub.live/alice/studio/w/launch?view=timeline
|
|
175
|
+
cohub desktop open <app-id> --call selection.get
|
|
176
|
+
cohub desktop open <app-id> --call board.focus --data '{"nodeId":"n1"}'
|
|
177
|
+
`;
|
|
178
|
+
const OPEN_NOTES = `
|
|
179
|
+
Notes:
|
|
180
|
+
- Use file:// and app:// to make the target explicit; the legacy work://
|
|
181
|
+
scheme is still accepted.
|
|
182
|
+
- A plain target checks the current Space for a file before resolving an app.
|
|
183
|
+
- Opening a window is idempotent; repeating it re-activates the same tab.
|
|
184
|
+
- --call waits for the app to announce readiness, then invokes the method.
|
|
185
|
+
- Which methods exist is up to the app author.
|
|
186
|
+
`;
|
|
187
|
+
function registerOpen(parent, deprecated) {
|
|
188
|
+
const open = parent
|
|
189
|
+
.command(deprecated ? "preview <app-or-file>" : "open <app-or-file>")
|
|
190
|
+
.description(deprecated ? "Deprecated: use `cohub desktop open`" : "Open a file or app window on the Cohub desktop, optionally calling an app method")
|
|
191
|
+
.option("--call <method>", "Method the app registered via client.app.surface.handle()")
|
|
192
|
+
.option("--data <json>", "Inline JSON input for --call")
|
|
193
|
+
.option("-i, --input <file>", "JSON input file for --call; use - for stdin")
|
|
194
|
+
.option("--client <clientId>", "Target a specific desktop instance of your account")
|
|
195
|
+
.option("--command-id <id>", "Stable id so retries never dispatch twice")
|
|
196
|
+
.option("--no-wait", "Dispatch the command and exit without waiting for a result")
|
|
197
|
+
.option("--timeout-ms <ms>", `How long to wait for the desktop (default: ${DESKTOP_COMMAND_DEFAULT_TIMEOUT_MS}; max: ${DESKTOP_COMMAND_MAX_TIMEOUT_MS})`)
|
|
198
|
+
.option("--json", "Output as JSON")
|
|
199
|
+
.action(async (target, opts, thisCommand) => {
|
|
200
|
+
if (deprecated) {
|
|
201
|
+
ok("Deprecated: `cohub ui preview` is now `cohub desktop open`.");
|
|
202
|
+
}
|
|
203
|
+
await openWindow(target, opts, thisCommand);
|
|
204
|
+
});
|
|
205
|
+
open.addHelpText("after", deprecated ? OPEN_NOTES : OPEN_HELP + OPEN_NOTES);
|
|
206
|
+
}
|
|
207
|
+
export function registerDesktop(program) {
|
|
208
|
+
const desktop = program
|
|
209
|
+
.command("desktop")
|
|
210
|
+
.description("Drive the Cohub desktop that started this chat");
|
|
211
|
+
registerOpen(desktop, false);
|
|
212
|
+
}
|
|
213
|
+
export function registerLegacyUi(program) {
|
|
214
|
+
const ui = program
|
|
215
|
+
.command("ui", { hidden: true })
|
|
216
|
+
.description("Deprecated: use `cohub desktop open`")
|
|
217
|
+
.addHelpText("after", OPEN_HELP);
|
|
218
|
+
registerOpen(ui, true);
|
|
219
|
+
}
|
package/dist/commands/profile.js
CHANGED
|
@@ -16,7 +16,7 @@ export function registerProfile(program) {
|
|
|
16
16
|
const handle = result.profile.username ? `@${result.profile.username}` : result.profile.userUuid;
|
|
17
17
|
console.log(`\n ${result.profile.displayName} (${handle})`);
|
|
18
18
|
console.log(` Spaces: ${result.spaces.length}`);
|
|
19
|
-
console.log(`
|
|
19
|
+
console.log(` Apps: ${result.apps.length}\n`);
|
|
20
20
|
if (result.spaces.length > 0) {
|
|
21
21
|
console.log(" Spaces:");
|
|
22
22
|
for (const space of result.spaces) {
|
|
@@ -24,10 +24,10 @@ export function registerProfile(program) {
|
|
|
24
24
|
}
|
|
25
25
|
console.log("");
|
|
26
26
|
}
|
|
27
|
-
if (result.
|
|
28
|
-
console.log("
|
|
29
|
-
for (const
|
|
30
|
-
console.log(` - ${
|
|
27
|
+
if (result.apps.length > 0) {
|
|
28
|
+
console.log(" Apps:");
|
|
29
|
+
for (const app of result.apps) {
|
|
30
|
+
console.log(` - ${app.title} ${app.publicUrl}`);
|
|
31
31
|
}
|
|
32
32
|
console.log("");
|
|
33
33
|
}
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,8 @@ import { registerPrompt, registerSpaces } from "./commands/spaces.js";
|
|
|
19
19
|
import { maybeHandleRunCommand } from "./commands/run.js";
|
|
20
20
|
import { registerSandbox } from "./commands/sandbox.js";
|
|
21
21
|
import { registerTasks } from "./commands/tasks.js";
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
22
|
+
import { registerDesktop, registerLegacyUi } from "./commands/desktop.js";
|
|
23
|
+
import { registerApps } from "./commands/apps.js";
|
|
24
24
|
const VERSION = (() => {
|
|
25
25
|
try {
|
|
26
26
|
const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
@@ -55,8 +55,8 @@ Common commands:
|
|
|
55
55
|
cohub -s <space-id> spaces sessions turns ls <session-id>
|
|
56
56
|
cohub -s <space-id> spaces files ls
|
|
57
57
|
cohub -s <space-id> public upload ./dist demo
|
|
58
|
-
cohub -s <space-id>
|
|
59
|
-
cohub
|
|
58
|
+
cohub -s <space-id> apps publish demo --file dist/index.html
|
|
59
|
+
cohub desktop open <app-id> --call selection.get
|
|
60
60
|
cohub -s <space-id> spaces commerce products list
|
|
61
61
|
cohub models ls
|
|
62
62
|
cohub models ls --model-type multimodal
|
|
@@ -84,8 +84,9 @@ registerReferences(program);
|
|
|
84
84
|
registerReferrals(program);
|
|
85
85
|
registerTasks(program);
|
|
86
86
|
registerCronJobs(program);
|
|
87
|
-
|
|
88
|
-
|
|
87
|
+
registerApps(program);
|
|
88
|
+
registerDesktop(program);
|
|
89
|
+
registerLegacyUi(program);
|
|
89
90
|
const argv = process.argv.slice(2);
|
|
90
91
|
if (await maybeHandleRunCommand(argv)) {
|
|
91
92
|
process.exit();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
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": "
|
|
22
|
+
"@neta-art/cohub": "8.0.0"
|
|
23
23
|
},
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
package/dist/commands/ui.d.ts
DELETED
package/dist/commands/ui.js
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { HttpError, } from "@neta-art/cohub";
|
|
3
|
-
import { parseWorkRef, UI_COMMAND_DEFAULT_TIMEOUT_MS, UI_COMMAND_MAX_TIMEOUT_MS, } from "@neta-art/cohub";
|
|
4
|
-
import { createClient } from "../client.js";
|
|
5
|
-
import { error, handleHttp, json as outJson, jsonRequested, ok } from "../output.js";
|
|
6
|
-
import { getWorkByRef } from "../work-ref.js";
|
|
7
|
-
const FILE_SCHEME = "file://";
|
|
8
|
-
const WORK_SCHEME = "work://";
|
|
9
|
-
function optionalSpaceId(command) {
|
|
10
|
-
let current = command;
|
|
11
|
-
while (current) {
|
|
12
|
-
const opts = current.opts();
|
|
13
|
-
if (typeof opts.space === "string" && opts.space.trim())
|
|
14
|
-
return opts.space.trim();
|
|
15
|
-
current = current.parent ?? null;
|
|
16
|
-
}
|
|
17
|
-
return process.env.COHUB_SPACE_ID?.trim() || undefined;
|
|
18
|
-
}
|
|
19
|
-
function parseFilePath(value) {
|
|
20
|
-
const path = value.slice(FILE_SCHEME.length).trim();
|
|
21
|
-
if (!path ||
|
|
22
|
-
path.startsWith("/") ||
|
|
23
|
-
path.includes("\0") ||
|
|
24
|
-
path.split("/").some((segment) => segment === "..") ||
|
|
25
|
-
path.includes("\\")) {
|
|
26
|
-
return error("Invalid file path", "Use a relative Space path after file://.");
|
|
27
|
-
}
|
|
28
|
-
return path;
|
|
29
|
-
}
|
|
30
|
-
function hasFileScheme(value) {
|
|
31
|
-
return value.toLowerCase().startsWith(FILE_SCHEME);
|
|
32
|
-
}
|
|
33
|
-
function hasWorkScheme(value) {
|
|
34
|
-
return value.toLowerCase().startsWith(WORK_SCHEME);
|
|
35
|
-
}
|
|
36
|
-
function readCallInput(opts) {
|
|
37
|
-
if (opts.data !== undefined && opts.input !== undefined) {
|
|
38
|
-
return error("Conflicting input", "Use either --data or --input, not both.");
|
|
39
|
-
}
|
|
40
|
-
const raw = opts.data !== undefined
|
|
41
|
-
? opts.data
|
|
42
|
-
: opts.input !== undefined
|
|
43
|
-
? opts.input === "-"
|
|
44
|
-
? readFileSync(0, "utf-8")
|
|
45
|
-
: readFileSync(opts.input, "utf-8")
|
|
46
|
-
: undefined;
|
|
47
|
-
if (raw === undefined)
|
|
48
|
-
return undefined;
|
|
49
|
-
try {
|
|
50
|
-
return JSON.parse(raw);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return error("Invalid input", "Call input must be valid JSON.");
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function parseTimeout(value) {
|
|
57
|
-
if (!value)
|
|
58
|
-
return UI_COMMAND_DEFAULT_TIMEOUT_MS;
|
|
59
|
-
const parsed = Math.floor(Number(value));
|
|
60
|
-
if (!Number.isFinite(parsed) || parsed <= 0 || parsed > UI_COMMAND_MAX_TIMEOUT_MS) {
|
|
61
|
-
return error("Invalid timeout", `--timeout-ms must be between 1 and ${UI_COMMAND_MAX_TIMEOUT_MS} milliseconds.`);
|
|
62
|
-
}
|
|
63
|
-
return parsed;
|
|
64
|
-
}
|
|
65
|
-
async function resolveWorkTarget(client, ref) {
|
|
66
|
-
const normalized = hasWorkScheme(ref) ? ref.slice(WORK_SCHEME.length) : ref;
|
|
67
|
-
const parsed = parseWorkRef(normalized);
|
|
68
|
-
const detail = await getWorkByRef(client, normalized);
|
|
69
|
-
return {
|
|
70
|
-
kind: "work",
|
|
71
|
-
workId: detail.work.id,
|
|
72
|
-
label: detail.work.slug,
|
|
73
|
-
launch: {
|
|
74
|
-
...(parsed.search ? { search: parsed.search } : {}),
|
|
75
|
-
...(parsed.hash ? { hash: parsed.hash } : {}),
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
async function resolvePreviewTarget(client, command, value) {
|
|
80
|
-
if (hasFileScheme(value))
|
|
81
|
-
return { kind: "file", path: parseFilePath(value) };
|
|
82
|
-
if (hasWorkScheme(value))
|
|
83
|
-
return resolveWorkTarget(client, value);
|
|
84
|
-
const spaceId = optionalSpaceId(command);
|
|
85
|
-
if (spaceId) {
|
|
86
|
-
try {
|
|
87
|
-
await client.space(spaceId).files.read(value);
|
|
88
|
-
return { kind: "file", path: value };
|
|
89
|
-
}
|
|
90
|
-
catch (cause) {
|
|
91
|
-
if (!(cause instanceof HttpError) || cause.status !== 404)
|
|
92
|
-
throw cause;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
return resolveWorkTarget(client, value);
|
|
96
|
-
}
|
|
97
|
-
function reportDispatch(record) {
|
|
98
|
-
if (record.status !== "pending") {
|
|
99
|
-
reportOutcome(record, Boolean(record.command.request));
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
ok(`UI command dispatched (${record.commandId})`);
|
|
103
|
-
}
|
|
104
|
-
function reportOutcome(record, called) {
|
|
105
|
-
if (record.status === "applied") {
|
|
106
|
-
ok(called ? "Work preview shown and method called" : "Preview shown");
|
|
107
|
-
if (record.result !== undefined) {
|
|
108
|
-
console.log(typeof record.result === "string" ? record.result : JSON.stringify(record.result, null, 2));
|
|
109
|
-
}
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
error(`UI command ${record.status}`, record.error?.message ?? "The Cohub frontend did not apply this command.");
|
|
113
|
-
}
|
|
114
|
-
export function registerUi(program) {
|
|
115
|
-
const ui = program
|
|
116
|
-
.command("ui")
|
|
117
|
-
.description("Drive the Cohub frontend that started this work")
|
|
118
|
-
.addHelpText("after", `
|
|
119
|
-
Commands reach only the frontend instance that originated the current chat,
|
|
120
|
-
resolved from request provenance. Nothing else can be targeted.
|
|
121
|
-
|
|
122
|
-
Examples:
|
|
123
|
-
cohub ui preview <work-or-file>
|
|
124
|
-
cohub ui preview file://src/main.ts
|
|
125
|
-
cohub ui preview work://alice/studio/launch
|
|
126
|
-
cohub ui preview alice/studio/launch
|
|
127
|
-
cohub ui preview https://cohub.live/alice/studio/w/launch?view=timeline
|
|
128
|
-
cohub ui preview <work-id> --call selection.get
|
|
129
|
-
cohub ui preview <work-id> --call board.focus --data '{"nodeId":"n1"}'
|
|
130
|
-
`);
|
|
131
|
-
const preview = ui
|
|
132
|
-
.command("preview <work-or-file>")
|
|
133
|
-
.description("Show a file or Work preview tab, optionally calling a Work method")
|
|
134
|
-
.option("--call <method>", "Method the Work registered via client.work.surface.handle()")
|
|
135
|
-
.option("--data <json>", "Inline JSON input for --call")
|
|
136
|
-
.option("-i, --input <file>", "JSON input file for --call; use - for stdin")
|
|
137
|
-
.option("--client <clientId>", "Target a specific frontend instance of your account")
|
|
138
|
-
.option("--command-id <id>", "Stable id so retries never dispatch twice")
|
|
139
|
-
.option("--no-wait", "Dispatch the command and exit without waiting for a result")
|
|
140
|
-
.option("--timeout-ms <ms>", `How long to wait for the frontend (default: ${UI_COMMAND_DEFAULT_TIMEOUT_MS}; max: ${UI_COMMAND_MAX_TIMEOUT_MS})`)
|
|
141
|
-
.option("--json", "Output as JSON")
|
|
142
|
-
.action(async (work, opts) => {
|
|
143
|
-
const callInput = opts.call ? readCallInput(opts) : undefined;
|
|
144
|
-
if (!opts.call && (opts.data !== undefined || opts.input !== undefined)) {
|
|
145
|
-
return error("Missing --call", "--data and --input only apply together with --call.");
|
|
146
|
-
}
|
|
147
|
-
if (opts.noWait && opts.timeoutMs !== undefined) {
|
|
148
|
-
return error("Conflicting wait options", "Use either --no-wait or --timeout-ms, not both.");
|
|
149
|
-
}
|
|
150
|
-
const timeoutMs = parseTimeout(opts.timeoutMs);
|
|
151
|
-
const client = createClient();
|
|
152
|
-
try {
|
|
153
|
-
const target = await resolvePreviewTarget(client, preview, work);
|
|
154
|
-
if (target.kind === "file" && opts.call) {
|
|
155
|
-
return error("Unsupported option", "--call only applies to Work previews.");
|
|
156
|
-
}
|
|
157
|
-
const request = opts.call
|
|
158
|
-
? { method: opts.call, ...(callInput === undefined ? {} : { input: callInput }) }
|
|
159
|
-
: undefined;
|
|
160
|
-
const input = {
|
|
161
|
-
command: {
|
|
162
|
-
type: "preview.show",
|
|
163
|
-
preview: target.kind === "file"
|
|
164
|
-
? target
|
|
165
|
-
: {
|
|
166
|
-
kind: "work",
|
|
167
|
-
workId: target.workId,
|
|
168
|
-
label: target.label,
|
|
169
|
-
...(target.launch.search || target.launch.hash ? { launch: target.launch } : {}),
|
|
170
|
-
},
|
|
171
|
-
...(request ? { request } : {}),
|
|
172
|
-
},
|
|
173
|
-
...(opts.client ? { targetClientId: opts.client } : {}),
|
|
174
|
-
...(opts.commandId ? { commandId: opts.commandId } : {}),
|
|
175
|
-
};
|
|
176
|
-
const record = opts.noWait
|
|
177
|
-
? (await client.ui.create(input)).command
|
|
178
|
-
: await client.ui.run(input, { timeoutMs });
|
|
179
|
-
if (jsonRequested(opts))
|
|
180
|
-
return outJson(record);
|
|
181
|
-
if (opts.noWait)
|
|
182
|
-
return reportDispatch(record);
|
|
183
|
-
reportOutcome(record, Boolean(request));
|
|
184
|
-
}
|
|
185
|
-
catch (e) {
|
|
186
|
-
if (e instanceof Error && e.name === "WorkRefParseError")
|
|
187
|
-
return error(e.message);
|
|
188
|
-
handleHttp(e);
|
|
189
|
-
}
|
|
190
|
-
});
|
|
191
|
-
preview.addHelpText("after", `
|
|
192
|
-
Notes:
|
|
193
|
-
- Use file:// and work:// to make the target explicit.
|
|
194
|
-
- A plain target checks the current Space for a file before resolving a Work.
|
|
195
|
-
- Showing a preview is idempotent; repeating it re-activates the same tab.
|
|
196
|
-
- --call waits for the Work to announce readiness, then invokes the method.
|
|
197
|
-
- Which methods exist is up to the Work author.
|
|
198
|
-
`);
|
|
199
|
-
}
|
package/dist/commands/works.d.ts
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import { type CohubHttpClient, type WorkViewStatsResponse } from "@neta-art/cohub";
|
|
2
|
-
import type { Command } from "commander";
|
|
3
|
-
export declare function getWorkStatsByRef(client: CohubHttpClient, work: string): Promise<WorkViewStatsResponse>;
|
|
4
|
-
export declare function registerWorks(program: Command): void;
|
package/dist/work-download.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { WorkGetResponse } from "@neta-art/cohub";
|
|
2
|
-
type DownloadResult = {
|
|
3
|
-
workId: string;
|
|
4
|
-
version: number;
|
|
5
|
-
kind: "file" | "directory";
|
|
6
|
-
output: string;
|
|
7
|
-
files: number;
|
|
8
|
-
bytes: number;
|
|
9
|
-
};
|
|
10
|
-
export declare function downloadWork(detail: WorkGetResponse, outputOption?: string, fetcher?: typeof fetch): Promise<DownloadResult>;
|
|
11
|
-
export {};
|
package/dist/work-ref.d.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { CohubHttpClient, ParsedWorkRef, WorkGetResponse } from "@neta-art/cohub";
|
|
2
|
-
import { formatWorkRef, parseWorkRef } from "@neta-art/cohub";
|
|
3
|
-
export type { ParsedWorkRef };
|
|
4
|
-
export { formatWorkRef, parseWorkRef };
|
|
5
|
-
export declare function getWorkByRef(client: CohubHttpClient, input: string): Promise<WorkGetResponse>;
|
package/dist/work-ref.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { formatWorkRef, parseWorkRef } from "@neta-art/cohub";
|
|
2
|
-
export { formatWorkRef, parseWorkRef };
|
|
3
|
-
export function getWorkByRef(client, input) {
|
|
4
|
-
const ref = parseWorkRef(input);
|
|
5
|
-
return "id" in ref
|
|
6
|
-
? client.works.get(ref.id)
|
|
7
|
-
: client.works.getBySlug(ref.username, ref.spaceSlug, ref.workSlug);
|
|
8
|
-
}
|