@neta-art/cohub-cli 10.0.0 → 10.2.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/README.md +24 -2
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +39 -28
- package/dist/board-kernel.js +1908 -1267
- package/dist/command-input.d.ts +4 -0
- package/dist/command-input.js +16 -0
- package/dist/commands/app-commerce.js +1 -3
- package/dist/commands/decision.d.ts +4 -0
- package/dist/commands/decision.js +113 -0
- package/dist/commands/run.js +14 -2
- package/dist/commands/spaces.js +4 -15
- package/dist/endpoint-options.d.ts +4 -0
- package/dist/endpoint-options.js +26 -0
- package/dist/help-path.js +2 -2
- package/dist/index.js +7 -2
- package/dist/runtime/supervisor.js +3 -3
- package/dist/space.d.ts +1 -0
- package/dist/space.js +26 -18
- package/dist/web.js +2 -4
- package/package.json +3 -3
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { error } from "./output.js";
|
|
2
|
+
export function collectOption(value, previous = []) {
|
|
3
|
+
return [...previous, value];
|
|
4
|
+
}
|
|
5
|
+
export async function readPromptContent(words, options = {}) {
|
|
6
|
+
let content = words.join(" ");
|
|
7
|
+
if (!content && !process.stdin.isTTY) {
|
|
8
|
+
const chunks = [];
|
|
9
|
+
for await (const chunk of process.stdin)
|
|
10
|
+
chunks.push(chunk);
|
|
11
|
+
content = Buffer.concat(chunks).toString().trim();
|
|
12
|
+
}
|
|
13
|
+
if (!content && !options.allowEmpty)
|
|
14
|
+
return error("No content", "Pass as argument or pipe via stdin");
|
|
15
|
+
return content;
|
|
16
|
+
}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { resolveExecutionAppId, } from "@neta-art/cohub";
|
|
2
2
|
import { createClient } from "../client.js";
|
|
3
|
+
import { collectOption } from "../command-input.js";
|
|
3
4
|
import { error, formatLocalDateTime, handleHttp, json as outJson, jsonRequested, ok, table } from "../output.js";
|
|
4
|
-
function collectOption(value, previous = []) {
|
|
5
|
-
return [...previous, value];
|
|
6
|
-
}
|
|
7
5
|
function requireText(value, label, flag) {
|
|
8
6
|
const text = value?.trim();
|
|
9
7
|
if (text)
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DecisionStateImage } from "@neta-art/cohub";
|
|
2
|
+
import type { Command } from "commander";
|
|
3
|
+
export declare function mergeDecisionState(state: unknown, text: string, images: DecisionStateImage[]): unknown;
|
|
4
|
+
export declare function registerDecisionCommand(parent: Command): void;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { uploadChatImageAsset } from "../avatar.js";
|
|
3
|
+
import { createClient } from "../client.js";
|
|
4
|
+
import { collectOption, readPromptContent } from "../command-input.js";
|
|
5
|
+
import { error, handleHttp, json as outJson, jsonRequested, table } from "../output.js";
|
|
6
|
+
import { resolveSpace } from "../space.js";
|
|
7
|
+
const REMOTE_URL = /^https?:\/\//i;
|
|
8
|
+
async function readJsonObject(source, label) {
|
|
9
|
+
const text = source.trim().startsWith("{")
|
|
10
|
+
? source
|
|
11
|
+
: await readFile(source, "utf-8").catch(() => error(`Cannot read ${label}`, source));
|
|
12
|
+
let value;
|
|
13
|
+
try {
|
|
14
|
+
value = JSON.parse(text);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return error(`Invalid ${label}`, "Expected a JSON object or a path to a JSON file");
|
|
18
|
+
}
|
|
19
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
20
|
+
return error(`Invalid ${label}`, "Expected a JSON object");
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
export function mergeDecisionState(state, text, images) {
|
|
25
|
+
if (!text && images.length === 0)
|
|
26
|
+
return state;
|
|
27
|
+
if (state !== undefined && typeof state !== "string" && !Array.isArray(state)) {
|
|
28
|
+
throw new Error("Cannot combine a JSON object state with text or --image; use a text/image array in --input instead.");
|
|
29
|
+
}
|
|
30
|
+
const parts = [
|
|
31
|
+
...(Array.isArray(state) ? state : typeof state === "string" ? [state] : []),
|
|
32
|
+
...(text ? [text] : []),
|
|
33
|
+
...images,
|
|
34
|
+
];
|
|
35
|
+
return parts.length === 1 && typeof parts[0] === "string" ? parts[0] : parts;
|
|
36
|
+
}
|
|
37
|
+
function formatAnswer(answer) {
|
|
38
|
+
if (answer.type === "noul")
|
|
39
|
+
return (answer.noul ?? 0).toFixed(2);
|
|
40
|
+
if (answer.type === "choice")
|
|
41
|
+
return answer.choice ?? "";
|
|
42
|
+
const top = Object.entries(answer.probabilities ?? {}).sort(([, a], [, b]) => b - a)[0]?.[0];
|
|
43
|
+
const label = top === undefined ? undefined : answer.legend?.[top];
|
|
44
|
+
return `${(answer.score ?? 0).toFixed(2)}${label ? ` ${label}` : ""}`;
|
|
45
|
+
}
|
|
46
|
+
function printResult(result) {
|
|
47
|
+
table(Object.entries(result.answers).map(([key, answer]) => ({
|
|
48
|
+
question: key,
|
|
49
|
+
type: answer.type,
|
|
50
|
+
answer: formatAnswer(answer),
|
|
51
|
+
confidence: answer.confidence,
|
|
52
|
+
})), [
|
|
53
|
+
{ key: "question", label: "QUESTION" },
|
|
54
|
+
{ key: "type", label: "TYPE" },
|
|
55
|
+
{ key: "answer", label: "ANSWER" },
|
|
56
|
+
{ key: "confidence", label: "CONFIDENCE", format: (value) => (typeof value === "number" ? value.toFixed(2) : "") },
|
|
57
|
+
]);
|
|
58
|
+
const cost = result.usage.cost?.total;
|
|
59
|
+
process.stderr.write(`\n ${result.provider}/${result.model}${typeof cost === "number" ? ` cost: $${cost}` : ""}\n`);
|
|
60
|
+
}
|
|
61
|
+
async function runDecisionCommand(command, words, opts) {
|
|
62
|
+
const base = opts.input ? await readJsonObject(opts.input, "input") : {};
|
|
63
|
+
const questions = opts.questions ? await readJsonObject(opts.questions, "questions") : base.questions;
|
|
64
|
+
if (!questions)
|
|
65
|
+
return error("Questions required", "Pass --questions or --input");
|
|
66
|
+
const spaceId = await resolveSpace(command, { home: true });
|
|
67
|
+
const client = createClient();
|
|
68
|
+
try {
|
|
69
|
+
const images = opts.image ?? [];
|
|
70
|
+
const text = words.length > 0 || base.state === undefined
|
|
71
|
+
? await readPromptContent(words, { allowEmpty: images.length > 0 })
|
|
72
|
+
: "";
|
|
73
|
+
const imageParts = images.map((url) => ({ type: "image_url", image_url: { url } }));
|
|
74
|
+
const state = mergeDecisionState(base.state, text, imageParts);
|
|
75
|
+
await Promise.all(imageParts.map(async (part) => {
|
|
76
|
+
if (!REMOTE_URL.test(part.image_url.url)) {
|
|
77
|
+
part.image_url.url = (await uploadChatImageAsset({ client, spaceId, path: part.image_url.url })).publicUrl;
|
|
78
|
+
}
|
|
79
|
+
}));
|
|
80
|
+
const input = {
|
|
81
|
+
...base,
|
|
82
|
+
provider: opts.provider ?? base.provider,
|
|
83
|
+
model: opts.model ?? base.model,
|
|
84
|
+
state,
|
|
85
|
+
questions,
|
|
86
|
+
};
|
|
87
|
+
const result = await client.space(spaceId).decision(input);
|
|
88
|
+
if (jsonRequested(opts))
|
|
89
|
+
return outJson(result);
|
|
90
|
+
printResult(result);
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
handleHttp(e);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export function registerDecisionCommand(parent) {
|
|
97
|
+
parent
|
|
98
|
+
.command("decision [state...]")
|
|
99
|
+
.description("Answer yes/no, choice and score questions about text or images")
|
|
100
|
+
.option("-q, --questions <json|path>", "Questions as inline JSON or a JSON file")
|
|
101
|
+
.option("--input <path>", "Full request JSON file (state, questions, model)")
|
|
102
|
+
.option("--image <path-or-url>", "Attach an image to the state", collectOption, [])
|
|
103
|
+
.option("-m, --model <model>", "Decision model ID")
|
|
104
|
+
.option("-p, --provider <provider>", "Provider name")
|
|
105
|
+
.option("--json", "Output as JSON")
|
|
106
|
+
.addHelpText("after", `
|
|
107
|
+
Examples:
|
|
108
|
+
cohub decision "I was charged twice" -q '{"refund":{"type":"noul","instructions":"Refund?"}}'
|
|
109
|
+
cat ticket.txt | cohub decision -q questions.json --json
|
|
110
|
+
cohub decision "Duplicate charge?" --image receipt.png -q questions.json
|
|
111
|
+
cohub decision --input request.json`)
|
|
112
|
+
.action((words, opts) => runDecisionCommand(parent, words, opts));
|
|
113
|
+
}
|
package/dist/commands/run.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { consumeBaseUrlOption } from "../endpoint-options.js";
|
|
1
2
|
import { createClient } from "../client.js";
|
|
2
3
|
import { error, handleHttp, json as outJson, spinner } from "../output.js";
|
|
3
4
|
import { resolveSpaceTarget } from "../space.js";
|
|
@@ -18,7 +19,7 @@ function topLevelRunIndex(argv) {
|
|
|
18
19
|
const token = argv[index] ?? "";
|
|
19
20
|
if (token === "--")
|
|
20
21
|
return -1;
|
|
21
|
-
if (token === "-s" || token === "--space") {
|
|
22
|
+
if (token === "-s" || token === "--space" || token === "--base-url") {
|
|
22
23
|
index += 1;
|
|
23
24
|
continue;
|
|
24
25
|
}
|
|
@@ -50,6 +51,11 @@ export async function parseRunCliOptions(argv, spaceOptions = {}) {
|
|
|
50
51
|
return error("Invalid invocation", "Use `cohub run [options] <command>`");
|
|
51
52
|
const beforeRun = argv.slice(0, runIndex);
|
|
52
53
|
const afterRun = argv.slice(runIndex + 1);
|
|
54
|
+
for (let index = 0; index < beforeRun.length; index += 1) {
|
|
55
|
+
index = consumeBaseUrlOption(beforeRun, index) ?? index;
|
|
56
|
+
if (beforeRun[index] === "-s" || beforeRun[index] === "--space")
|
|
57
|
+
index += 1;
|
|
58
|
+
}
|
|
53
59
|
const explicitSpaceId = parseSpaceId(beforeRun) ?? process.env.COHUB_SPACE_ID?.trim() ?? "";
|
|
54
60
|
let json = beforeRun.includes("--json");
|
|
55
61
|
let async = false;
|
|
@@ -57,6 +63,11 @@ export async function parseRunCliOptions(argv, spaceOptions = {}) {
|
|
|
57
63
|
let positionalTokens = [];
|
|
58
64
|
for (let index = 0; index < afterRun.length; index += 1) {
|
|
59
65
|
const token = afterRun[index] ?? "";
|
|
66
|
+
const endpointIndex = consumeBaseUrlOption(afterRun, index);
|
|
67
|
+
if (endpointIndex !== null) {
|
|
68
|
+
index = endpointIndex;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
60
71
|
if (token === "-h" || token === "--help") {
|
|
61
72
|
printRunHelp();
|
|
62
73
|
process.exit(0);
|
|
@@ -110,10 +121,11 @@ export async function parseRunCliOptions(argv, spaceOptions = {}) {
|
|
|
110
121
|
export function printRunHelp() {
|
|
111
122
|
process.stdout.write(`
|
|
112
123
|
Usage:
|
|
113
|
-
cohub [-s <space>] run [options] -- <shell command>
|
|
124
|
+
cohub [--base-url <url>] [-s <space>] run [options] -- <shell command>
|
|
114
125
|
cohub [-s <space>] run --command <shell command>
|
|
115
126
|
|
|
116
127
|
Options:
|
|
128
|
+
--base-url <url> API base URL (overrides COHUB_BASE_URL)
|
|
117
129
|
-c, --command <command> Shell command to execute in the space workspace
|
|
118
130
|
--async Queue the command and return immediately
|
|
119
131
|
--json Output as JSON
|
package/dist/commands/spaces.js
CHANGED
|
@@ -3,10 +3,12 @@ import { basename, dirname, relative, resolve, sep } from "node:path";
|
|
|
3
3
|
import { readSentTurns, readSessionTurnOrigin, resolveCohubEnvironment } from "@neta-art/cohub";
|
|
4
4
|
import { uploadAvatarAsset, uploadChatImageAsset } from "../avatar.js";
|
|
5
5
|
import { createClient } from "../client.js";
|
|
6
|
+
import { collectOption, readPromptContent } from "../command-input.js";
|
|
6
7
|
import { table, json as outJson, jsonRequested, ok, error, handleHttp, formatEpochMs, truncateText } from "../output.js";
|
|
7
8
|
import { SESSION_STATUS_COLUMN } from "../session-status.js";
|
|
8
9
|
import { resolveSpace } from "../space.js";
|
|
9
10
|
import { uploadLocalFiles } from "../space-files/upload.js";
|
|
11
|
+
import { registerDecisionCommand } from "./decision.js";
|
|
10
12
|
import { registerSpaceCommerce } from "./space-commerce.js";
|
|
11
13
|
import { registerSpaceFileTransfer } from "./space-files.js";
|
|
12
14
|
import { registerSpaceActivity } from "./space-activity.js";
|
|
@@ -33,9 +35,6 @@ function parseInteger(value, name, options = {}) {
|
|
|
33
35
|
return error(`Invalid ${name}`, `${name} must be at most ${options.max}`);
|
|
34
36
|
return parsed;
|
|
35
37
|
}
|
|
36
|
-
function collectOption(value, previous = []) {
|
|
37
|
-
return [...previous, value];
|
|
38
|
-
}
|
|
39
38
|
function parseEnvOptions(values) {
|
|
40
39
|
if (!values?.length)
|
|
41
40
|
return undefined;
|
|
@@ -181,18 +180,6 @@ async function confirmRestart(opts) {
|
|
|
181
180
|
if (answer !== "y" && answer !== "yes")
|
|
182
181
|
return error("Cancelled");
|
|
183
182
|
}
|
|
184
|
-
async function readPromptContent(words, options = {}) {
|
|
185
|
-
let content = words.join(" ");
|
|
186
|
-
if (!content && !process.stdin.isTTY) {
|
|
187
|
-
const chunks = [];
|
|
188
|
-
for await (const chunk of process.stdin)
|
|
189
|
-
chunks.push(chunk);
|
|
190
|
-
content = Buffer.concat(chunks).toString().trim();
|
|
191
|
-
}
|
|
192
|
-
if (!content && !options.allowEmpty)
|
|
193
|
-
return error("No content", "Pass as argument or pipe via stdin");
|
|
194
|
-
return content;
|
|
195
|
-
}
|
|
196
183
|
async function sendPrompt(command, words, opts) {
|
|
197
184
|
const content = await readPromptContent(words, { allowEmpty: Boolean(opts.image?.length) });
|
|
198
185
|
const scheduleFlags = [opts.delayMs, opts.at, opts.cron].filter((value) => value !== undefined);
|
|
@@ -379,6 +366,7 @@ export function registerPrompt(program) {
|
|
|
379
366
|
.option("--thinking-level <level>", "Thinking level: off|minimal|low|medium|high|xhigh")
|
|
380
367
|
.option("--json", "Output as JSON")
|
|
381
368
|
.action((words, opts) => runCompletionCommand(program, words, opts));
|
|
369
|
+
registerDecisionCommand(program);
|
|
382
370
|
}
|
|
383
371
|
export function registerSpaceCreate(spacesCmd, dependencies = {}) {
|
|
384
372
|
const getClient = dependencies.createClient ?? createClient;
|
|
@@ -699,6 +687,7 @@ export function registerSpaces(program) {
|
|
|
699
687
|
.option("--thinking-level <level>", "Thinking level: off|minimal|low|medium|high|xhigh")
|
|
700
688
|
.option("--json", "Output as JSON")
|
|
701
689
|
.action((words, opts) => runCompletionCommand(spacesCmd, words, opts));
|
|
690
|
+
registerDecisionCommand(spacesCmd);
|
|
702
691
|
// ── spaces files ──
|
|
703
692
|
registerFiles(spacesCmd);
|
|
704
693
|
registerSpaceDisplays(spacesCmd);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type Command } from "commander";
|
|
2
|
+
export declare function setApiBaseUrl(value: string): string;
|
|
3
|
+
export declare function registerEndpointOptions(program: Command): Command;
|
|
4
|
+
export declare function consumeBaseUrlOption(tokens: string[], index: number): number | null;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { normalizeBaseUrl } from "@neta-art/cohub";
|
|
2
|
+
import { InvalidArgumentError } from "commander";
|
|
3
|
+
export function setApiBaseUrl(value) {
|
|
4
|
+
const url = normalizeBaseUrl(value);
|
|
5
|
+
if (!url)
|
|
6
|
+
throw new InvalidArgumentError("API base URL must not be empty");
|
|
7
|
+
process.env.COHUB_BASE_URL = url;
|
|
8
|
+
return url;
|
|
9
|
+
}
|
|
10
|
+
export function registerEndpointOptions(program) {
|
|
11
|
+
return program.option("--base-url <url>", "API base URL (overrides COHUB_BASE_URL)", setApiBaseUrl);
|
|
12
|
+
}
|
|
13
|
+
export function consumeBaseUrlOption(tokens, index) {
|
|
14
|
+
const token = tokens[index];
|
|
15
|
+
if (token?.startsWith("--base-url=")) {
|
|
16
|
+
setApiBaseUrl(token.slice("--base-url=".length));
|
|
17
|
+
return index;
|
|
18
|
+
}
|
|
19
|
+
if (token !== "--base-url")
|
|
20
|
+
return null;
|
|
21
|
+
const value = tokens[index + 1];
|
|
22
|
+
if (value === undefined)
|
|
23
|
+
throw new InvalidArgumentError("--base-url requires a value");
|
|
24
|
+
setApiBaseUrl(value);
|
|
25
|
+
return index + 1;
|
|
26
|
+
}
|
package/dist/help-path.js
CHANGED
|
@@ -73,11 +73,11 @@ function runRemainderRequestsHelp(argv) {
|
|
|
73
73
|
return true;
|
|
74
74
|
if (token === "--async" || token === "--json")
|
|
75
75
|
continue;
|
|
76
|
-
if (token === "-c" || token === "--command") {
|
|
76
|
+
if (token === "-c" || token === "--command" || token === "--base-url") {
|
|
77
77
|
index += 1;
|
|
78
78
|
continue;
|
|
79
79
|
}
|
|
80
|
-
if (token.startsWith("--command="))
|
|
80
|
+
if (token.startsWith("--command=") || token.startsWith("--base-url="))
|
|
81
81
|
continue;
|
|
82
82
|
return false;
|
|
83
83
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
|
+
import { registerEndpointOptions } from "./endpoint-options.js";
|
|
4
5
|
import { registerAuth } from "./commands/auth.js";
|
|
5
6
|
import { registerBoards } from "./commands/boards.js";
|
|
6
7
|
import { registerChannels } from "./commands/channels.js";
|
|
@@ -32,7 +33,7 @@ const VERSION = (() => {
|
|
|
32
33
|
return "1.0.0";
|
|
33
34
|
}
|
|
34
35
|
})();
|
|
35
|
-
const program = new Command("cohub");
|
|
36
|
+
const program = registerEndpointOptions(new Command("cohub"));
|
|
36
37
|
program
|
|
37
38
|
.name("cohub")
|
|
38
39
|
.summary("Work with Cohub from your terminal")
|
|
@@ -53,6 +54,7 @@ Common commands:
|
|
|
53
54
|
cohub spaces ls
|
|
54
55
|
cohub prompt "Fix the failing tests"
|
|
55
56
|
cohub completion "Summarize AGENTS.md" --system-prompt AGENTS.md --stream
|
|
57
|
+
cohub decision "I was charged twice" -q questions.json
|
|
56
58
|
cohub run -- git status
|
|
57
59
|
cohub runtime up ./my-project
|
|
58
60
|
cohub runtime logs --follow
|
|
@@ -73,12 +75,15 @@ Common commands:
|
|
|
73
75
|
Target space:
|
|
74
76
|
-s <space>, then COHUB_SPACE_ID, then the current directory Runtime binding.
|
|
75
77
|
<space> is an ID, a slug you own such as home, or username/slug.
|
|
76
|
-
prompt, completion, generate, apps, and public fall back to your Home Space;
|
|
78
|
+
prompt, completion, decision, generate, apps, and public fall back to your Home Space;
|
|
77
79
|
other Space commands need a target.
|
|
78
80
|
|
|
79
81
|
Environment:
|
|
80
82
|
COHUB_SPACE_ID Target Space when -s is omitted
|
|
81
83
|
COHUB_EXECUTION_TOKEN Use this token instead of the stored Logto session
|
|
84
|
+
COHUB_BASE_URL Override the API base URL
|
|
85
|
+
COHUB_WS_URL Override the realtime WebSocket URL
|
|
86
|
+
COHUB_WEB_URL Override the web URL used in links
|
|
82
87
|
ENV=dev Use the development Cohub environment
|
|
83
88
|
HTTPS_PROXY Honored for API and uploads (also HTTP_PROXY, NO_PROXY)
|
|
84
89
|
Runtime logs ~/.local/state/cohub/runtime/<space-id>/diagnostics
|
|
@@ -102,10 +102,10 @@ export async function runRuntime(config, onState, externalSignal, onDiagnostic)
|
|
|
102
102
|
config.capabilities ?? discoverHarnesses(config.harnesses, config.executables, config.root),
|
|
103
103
|
]);
|
|
104
104
|
signal.throwIfAborted();
|
|
105
|
-
const url = new URL(resolveWebsocketUrl(
|
|
106
|
-
url.pathname = "/runtime/relay";
|
|
105
|
+
const url = new URL(resolveWebsocketUrl());
|
|
106
|
+
url.pathname = url.pathname.replace(/\/ws$/, "/runtime/relay");
|
|
107
107
|
const relay = new URL(url);
|
|
108
|
-
relay.pathname = "/sandbox/relay";
|
|
108
|
+
relay.pathname = relay.pathname.replace(/\/runtime\/relay$/, "/sandbox/relay");
|
|
109
109
|
const runBridge = async () => {
|
|
110
110
|
let attempts = 0;
|
|
111
111
|
while (!signal.aborted) {
|
package/dist/space.d.ts
CHANGED
package/dist/space.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { HOME_SPACE_SLUG, parseSpaceRef, resolveCohubEnvironment } from "@neta-art/cohub";
|
|
4
|
+
import { COHUB_ENVIRONMENTS, HOME_SPACE_SLUG, normalizeBaseUrl, parseSpaceRef, resolveApiBaseUrl, resolveCohubEnvironment } from "@neta-art/cohub";
|
|
5
5
|
import { readAuthSession } from "./auth.js";
|
|
6
6
|
import { createClient } from "./client.js";
|
|
7
7
|
import { getRuntimeSpaceBinding } from "./runtime/space-binding.js";
|
|
@@ -40,17 +40,21 @@ function jwtClaim(token, key) {
|
|
|
40
40
|
* Execution grants identify the actor as `actorUserId`, not `sub`.
|
|
41
41
|
*/
|
|
42
42
|
export function identityKeyFrom(input) {
|
|
43
|
+
const baseUrl = input.baseUrl ? normalizeBaseUrl(input.baseUrl) : "";
|
|
44
|
+
const defaultBaseUrl = COHUB_ENVIRONMENTS[input.env === "dev" ? "dev" : "prod"].apiBaseUrl;
|
|
45
|
+
const namespace = baseUrl && baseUrl !== defaultBaseUrl ? `${input.env}:${baseUrl}` : input.env;
|
|
43
46
|
if (input.executionToken) {
|
|
44
47
|
const actor = jwtClaim(input.executionToken, "actorUserId") ?? jwtClaim(input.executionToken, "sub");
|
|
45
|
-
return actor ? `${
|
|
48
|
+
return actor ? `${namespace}:${actor}` : null;
|
|
46
49
|
}
|
|
47
50
|
const sub = jwtClaim(input.idToken, "sub") ?? jwtClaim(input.accessToken, "sub");
|
|
48
|
-
return sub ? `${
|
|
51
|
+
return sub ? `${namespace}:${sub}` : null;
|
|
49
52
|
}
|
|
50
53
|
export function currentIdentityKey() {
|
|
51
54
|
const session = readAuthSession();
|
|
52
55
|
return identityKeyFrom({
|
|
53
56
|
env: resolveCohubEnvironment(),
|
|
57
|
+
baseUrl: resolveApiBaseUrl(),
|
|
54
58
|
executionToken: process.env.COHUB_EXECUTION_TOKEN?.trim(),
|
|
55
59
|
idToken: session?.idToken,
|
|
56
60
|
accessToken: session?.accessToken,
|
|
@@ -81,10 +85,10 @@ function writeHomeSpaceCache(key, spaceId) {
|
|
|
81
85
|
// Cache is best-effort; never fail the command over it.
|
|
82
86
|
}
|
|
83
87
|
}
|
|
84
|
-
|
|
88
|
+
const homeSpaces = new Map();
|
|
85
89
|
const spaceRefs = new Map();
|
|
86
90
|
export function clearHomeSpaceCache() {
|
|
87
|
-
|
|
91
|
+
homeSpaces.clear();
|
|
88
92
|
spaceRefs.clear();
|
|
89
93
|
try {
|
|
90
94
|
rmSync(CACHE_PATH, { force: true });
|
|
@@ -107,17 +111,21 @@ export function explicitSpace(program) {
|
|
|
107
111
|
}
|
|
108
112
|
/** Cached per identity and memoized per process. */
|
|
109
113
|
export function resolveHomeSpace() {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
114
|
+
const key = currentIdentityKey();
|
|
115
|
+
const memoKey = key ?? resolveApiBaseUrl();
|
|
116
|
+
let resolved = homeSpaces.get(memoKey);
|
|
117
|
+
if (!resolved)
|
|
118
|
+
resolved = (async () => {
|
|
119
|
+
const cached = key ? readHomeSpaceCache(CACHE_PATH, key) : null;
|
|
120
|
+
if (cached)
|
|
121
|
+
return cached;
|
|
122
|
+
const space = await createClient().spaces.ensureHome();
|
|
123
|
+
if (key)
|
|
124
|
+
writeHomeSpaceCache(key, space.id);
|
|
125
|
+
return space.id;
|
|
126
|
+
})();
|
|
127
|
+
homeSpaces.set(memoKey, resolved);
|
|
128
|
+
return resolved;
|
|
121
129
|
}
|
|
122
130
|
async function lookupSpaceRef(value) {
|
|
123
131
|
const ref = parseSpaceRef(value);
|
|
@@ -141,10 +149,10 @@ async function lookupSpaceRef(value) {
|
|
|
141
149
|
}
|
|
142
150
|
}
|
|
143
151
|
export function resolveSpaceRef(value) {
|
|
144
|
-
const key = value.trim()
|
|
152
|
+
const key = `${currentIdentityKey() ?? resolveApiBaseUrl()}:${value.trim()}`;
|
|
145
153
|
let resolved = spaceRefs.get(key);
|
|
146
154
|
if (!resolved) {
|
|
147
|
-
resolved = lookupSpaceRef(
|
|
155
|
+
resolved = lookupSpaceRef(value.trim());
|
|
148
156
|
spaceRefs.set(key, resolved);
|
|
149
157
|
}
|
|
150
158
|
return resolved;
|
package/dist/web.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveWebBaseUrl } from "@neta-art/cohub";
|
|
2
2
|
export function webUrl(path) {
|
|
3
|
-
|
|
4
|
-
`https://${resolveCohubEnvironment() === "prod" ? "" : "dev."}cohub.live`;
|
|
5
|
-
return `${origin}${path}`;
|
|
3
|
+
return `${resolveWebBaseUrl()}${path}`;
|
|
6
4
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neta-art/cohub-cli",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.2.0",
|
|
4
4
|
"description": "CLI for Cohub — spaces, sessions, and agent collaboration.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
"NOTICE"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@neta-art/cohub": "11.
|
|
22
|
-
"@neta-art/generation": "^0.1.
|
|
21
|
+
"@neta-art/cohub": "11.2.0",
|
|
22
|
+
"@neta-art/generation": "^0.1.35",
|
|
23
23
|
"commander": "^15.0.0",
|
|
24
24
|
"sharp": "^0.35.4",
|
|
25
25
|
"ws": "^8.21.3"
|