isorite 0.8.1 → 0.9.1
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/main.js +330 -68
- package/docs/setup-for-agents.md +8 -5
- package/docs/setup.md +31 -4
- package/docs/terminal.md +9 -3
- package/package.json +1 -1
- package/plugin/tui.tsx +6 -0
- package/web/_app/immutable/chunks/{Cgml88Pl.js → B3aPQAYq.js} +1 -1
- package/web/_app/immutable/chunks/{yXUzBjUq.js → BXF2XoRC.js} +1 -1
- package/web/_app/immutable/chunks/{BAkGB2fM.js → CBWiKnLz.js} +1 -1
- package/web/_app/immutable/chunks/{CMTjeXEe.js → CJ1dhP0p.js} +1 -1
- package/web/_app/immutable/chunks/CLkY0zVi.js +1 -0
- package/web/_app/immutable/chunks/{CurmVJyp.js → D-aFnd2T.js} +1 -1
- package/web/_app/immutable/chunks/{C_ER1Any.js → DDuWClBR.js} +1 -1
- package/web/_app/immutable/chunks/{C1hqerMA.js → DII7jNnq.js} +1 -1
- package/web/_app/immutable/chunks/{D29fKEzc.js → DRQEUdad.js} +1 -1
- package/web/_app/immutable/chunks/{DsIcKd8x.js → DacMdKjW.js} +1 -1
- package/web/_app/immutable/chunks/{D3BEa5-_.js → DcgxJ8bF.js} +3 -3
- package/web/_app/immutable/chunks/{DPnfDiTn.js → Ddgejj-u.js} +1 -1
- package/web/_app/immutable/chunks/{B4sg4e8i.js → GRrQoDGJ.js} +1 -1
- package/web/_app/immutable/chunks/{B2TCcw-P.js → vUsuwDSf.js} +1 -1
- package/web/_app/immutable/entry/{app.idRgnoYp.js → app.CswuY3sh.js} +2 -2
- package/web/_app/immutable/entry/start.D8ZvSyBD.js +1 -0
- package/web/_app/immutable/nodes/0.DBoa3NAQ.js +26 -0
- package/web/_app/immutable/nodes/{1.DHK7VmG0.js → 1.DrwFerb8.js} +1 -1
- package/web/_app/immutable/nodes/2.DPmbppXV.js +1 -0
- package/web/_app/immutable/nodes/3.BW7AbIrS.js +1 -0
- package/web/_app/immutable/nodes/{4.Ceb5g4Ps.js → 4.B5HAyMKs.js} +1 -1
- package/web/_app/immutable/nodes/{5.DioYOANs.js → 5.BvScSoCM.js} +1 -1
- package/web/_app/immutable/nodes/6.Bna6qY2G.js +1 -0
- package/web/_app/version.json +1 -1
- package/web/index.html +14 -14
- package/web/_app/immutable/chunks/DtPULAe0.js +0 -1
- package/web/_app/immutable/entry/start.CEx_UQO6.js +0 -1
- package/web/_app/immutable/nodes/0.BUd_gY_8.js +0 -26
- package/web/_app/immutable/nodes/2.CDAt-6SJ.js +0 -1
- package/web/_app/immutable/nodes/3.4Wv5nmaw.js +0 -1
- package/web/_app/immutable/nodes/6.CzYfNb6g.js +0 -1
package/dist/main.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { chmodSync, closeSync, copyFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import { Document, Scalar, isMap, isScalar, isSeq, parseDocument, stringify } from "yaml";
|
|
6
6
|
import MarkdownIt from "markdown-it";
|
|
7
7
|
import { parse, stringify as stringify$1 } from "smol-toml";
|
|
8
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
8
9
|
import { createHash, randomBytes } from "node:crypto";
|
|
9
10
|
import { arch, constants, homedir, hostname, platform, release, tmpdir } from "node:os";
|
|
10
11
|
import { createSecureContext, getCACertificates, setDefaultCACertificates } from "node:tls";
|
|
11
|
-
import {
|
|
12
|
+
import { get } from "node:http";
|
|
12
13
|
import { Worker } from "node:worker_threads";
|
|
13
14
|
import { Agent, request } from "node:https";
|
|
14
15
|
import { Readable } from "node:stream";
|
|
@@ -18,8 +19,10 @@ import { streamSSE } from "hono/streaming";
|
|
|
18
19
|
import { z } from "zod";
|
|
19
20
|
import { OpenCode } from "@opencode/client";
|
|
20
21
|
import { appendFile, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
22
|
+
import { createInterface } from "node:readline/promises";
|
|
21
23
|
import { createServer } from "node:net";
|
|
22
|
-
import {
|
|
24
|
+
import { isSea } from "node:sea";
|
|
25
|
+
import { createInterface as createInterface$1 } from "node:readline";
|
|
23
26
|
//#region \0rolldown/runtime.js
|
|
24
27
|
var __defProp = Object.defineProperty;
|
|
25
28
|
var __esmMin = (fn, res, err) => () => {
|
|
@@ -1944,19 +1947,50 @@ function unreachable(err, url) {
|
|
|
1944
1947
|
try {
|
|
1945
1948
|
host = new URL(url).host;
|
|
1946
1949
|
} catch {}
|
|
1947
|
-
const
|
|
1950
|
+
const proxy = /Proxy response \((\d{3})\)/.exec(said)?.[1];
|
|
1951
|
+
const advice = proxy === "407" ? "The proxy (HTTPS_PROXY), not the provider, refused with 407: it wants credentials of its own, as http://user:password@proxy:port in HTTPS_PROXY, or its own sign-in. isorite can't answer a proxy that signs in by Kerberos or NTLM." : proxy ? `The proxy (HTTPS_PROXY) answered ${proxy}, not the provider: check HTTPS_PROXY, and NO_PROXY for hosts reached directly.` : UNTRUSTED.has(code) ? `Its certificate is signed by an authority not trusted here, often a company's own. Add it to the system's authorities (isorite trusts those), or set NODE_EXTRA_CA_CERTS to its certificate (a .pem), or name that as ca in [provider].` : code === "CERT_HAS_EXPIRED" ? "Its certificate has expired: that is the gateway's to fix." : code === "ERR_TLS_CERT_ALTNAME_INVALID" ? `Its certificate is not for ${host}: check the url in [provider].` : /CERTIFICATE_REQUIRED|HANDSHAKE_FAILURE|BAD_CERTIFICATE|UNKNOWN_CA/.test(code) ? "It asks for a client certificate (mTLS) and was given none it accepts: name yours as pfx (with pfx_passphrase), or cert and cert_key, in [provider]." : code === "UND_ERR_SOCKET" && url.startsWith("https:") ? "It closed the connection as it was being set up. A gateway that asks for a client certificate (mTLS) does this: name yours as pfx, or cert and cert_key, in [provider]." : code === "ENOTFOUND" || code === "EAI_AGAIN" ? `The name ${host} could not be found: check the url in [provider], and whether it is only found on the company's network or VPN.` : code === "ECONNREFUSED" ? `Nothing answers at ${host}: check the url and its port in [provider].` : code === "ETIMEDOUT" || code === "UND_ERR_CONNECT_TIMEOUT" || code === "ENETUNREACH" || code === "EHOSTUNREACH" ? `${host} did not answer. If the company's network reaches it through a proxy, set HTTPS_PROXY.` : "";
|
|
1948
1952
|
return [
|
|
1949
1953
|
said || err?.message || "no reason given",
|
|
1950
1954
|
code && !said.includes(code) ? ` (${code})` : "",
|
|
1951
1955
|
advice ? `. ${advice}` : ""
|
|
1952
1956
|
].join("");
|
|
1953
1957
|
}
|
|
1958
|
+
/** The header the key goes in, and what goes before it: Authorization with "Bearer " unless said otherwise (M21).
|
|
1959
|
+
* `key_header = "authorization"` alone means Bearer too: a bare key in Authorization is rarely what a gateway
|
|
1960
|
+
* wants. `key_prefix = ""` sends it bare. */
|
|
1961
|
+
function keySent(p) {
|
|
1962
|
+
const header = p.keyHeader || "Authorization";
|
|
1963
|
+
return {
|
|
1964
|
+
header,
|
|
1965
|
+
prefix: p.keyPrefix ?? (header.toLowerCase() === "authorization" ? "Bearer " : "")
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
/** How the key is sent, as said to the person: `Authorization: Bearer <41 characters>`. */
|
|
1969
|
+
function keySentAs(p, key) {
|
|
1970
|
+
const { header, prefix } = keySent(p);
|
|
1971
|
+
return `${header}: ${prefix}<${key.length} characters>`;
|
|
1972
|
+
}
|
|
1973
|
+
/** What a provider said with a refusal: its `error.message` (or `message`, or `error`), else its text without
|
|
1974
|
+
* markup, cut to 300 characters, and never the key, should it say it back. */
|
|
1975
|
+
function refusal(body, key = null) {
|
|
1976
|
+
let said = "";
|
|
1977
|
+
try {
|
|
1978
|
+
const j = JSON.parse(body);
|
|
1979
|
+
said = (typeof j.error === "string" ? j.error : j.error?.message) ?? j.message ?? "";
|
|
1980
|
+
} catch {
|
|
1981
|
+
said = body.replace(/<[^>]+>/g, " ");
|
|
1982
|
+
}
|
|
1983
|
+
said = said.replace(/\s+/g, " ").trim();
|
|
1984
|
+
if (key && key.length >= 8) said = said.split(key).join("<the key>");
|
|
1985
|
+
return said.length > 300 ? said.slice(0, 299) + "…" : said;
|
|
1986
|
+
}
|
|
1954
1987
|
/** The address of `path` at the provider, and the headers that go with every request to it: its key, where it
|
|
1955
1988
|
* wants it, and its own. */
|
|
1956
1989
|
function endpoint(p, key, path) {
|
|
1957
1990
|
const url = new URL(p.url.replace(/\/+$/, "") + path);
|
|
1958
1991
|
for (const [name, value] of Object.entries(p.query ?? {})) url.searchParams.set(name, value);
|
|
1959
|
-
const
|
|
1992
|
+
const sent = keySent(p);
|
|
1993
|
+
const auth = !key ? {} : { [sent.header]: sent.prefix + key };
|
|
1960
1994
|
return {
|
|
1961
1995
|
url: url.toString(),
|
|
1962
1996
|
headers: {
|
|
@@ -2023,13 +2057,7 @@ async function completeOnce(http, p, key, call, heard) {
|
|
|
2023
2057
|
if (!res.ok || !res.body) {
|
|
2024
2058
|
let detail = "";
|
|
2025
2059
|
try {
|
|
2026
|
-
|
|
2027
|
-
try {
|
|
2028
|
-
const j = JSON.parse(said);
|
|
2029
|
-
detail = (typeof j.error === "string" ? j.error : j.error?.message) ?? j.message ?? "";
|
|
2030
|
-
} catch {
|
|
2031
|
-
detail = said.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim().slice(0, 300);
|
|
2032
|
-
}
|
|
2060
|
+
detail = refusal(await res.text(), key);
|
|
2033
2061
|
} catch {}
|
|
2034
2062
|
if (DOWN.has(res.status)) throw new ModelError(`${p.name} is unavailable (${res.status}${detail ? `: ${detail}` : ""})`, 502, {
|
|
2035
2063
|
kind: "unavailable",
|
|
@@ -2041,7 +2069,7 @@ async function completeOnce(http, p, key, call, heard) {
|
|
|
2041
2069
|
retry: true,
|
|
2042
2070
|
after: afterOf(res)
|
|
2043
2071
|
});
|
|
2044
|
-
const advice = res.status === 401 ?
|
|
2072
|
+
const advice = res.status === 401 ? ` The key was not accepted${key ? `, sent as ${keySentAs(p, key)}` : ""}: set it again in settings, or check key_header and key_prefix.` : res.status === 402 ? " The key's credit is spent." : res.status === 404 ? ` ${p.name} may have no model called ${call.model}${p.defaultTiers ? ", which is OpenRouter's name for it: name this provider's models under [tiers] in config.toml" : ""}.` : "";
|
|
2045
2073
|
throw new ModelError(`${p.name} said ${res.status}${detail ? `: ${detail}` : ""}.${advice}`, res.status === 401 || res.status === 403 ? 409 : 502);
|
|
2046
2074
|
}
|
|
2047
2075
|
let text = "", usage = null, finish = "";
|
|
@@ -6697,7 +6725,7 @@ function writeHome(dir, { gateway = null, plugin = pluginDir, instructions = []
|
|
|
6697
6725
|
recursive: true,
|
|
6698
6726
|
force: true
|
|
6699
6727
|
});
|
|
6700
|
-
symlinkSync(plugin, link, "dir");
|
|
6728
|
+
symlinkSync(plugin, link, process.platform === "win32" ? "junction" : "dir");
|
|
6701
6729
|
}
|
|
6702
6730
|
return dir;
|
|
6703
6731
|
}
|
|
@@ -6724,12 +6752,14 @@ function withLocal(config, local) {
|
|
|
6724
6752
|
};
|
|
6725
6753
|
}
|
|
6726
6754
|
function linksTo(path, target) {
|
|
6755
|
+
const plain = (p) => resolve(p).replace(/[\\/]+$/, "");
|
|
6727
6756
|
try {
|
|
6728
|
-
return lstatSync(path).isSymbolicLink() && readlinkSync(path) === target;
|
|
6757
|
+
return lstatSync(path).isSymbolicLink() && plain(readlinkSync(path)) === plain(target);
|
|
6729
6758
|
} catch {
|
|
6730
6759
|
return false;
|
|
6731
6760
|
}
|
|
6732
6761
|
}
|
|
6762
|
+
const PACKAGE$1 = `@opencode/cli@2.0.32`;
|
|
6733
6763
|
/** The version a `--version` line names, as "2.0.32", or null. */
|
|
6734
6764
|
function parseVersion(output) {
|
|
6735
6765
|
return output.match(/(\d+)\.(\d+)\.(\d+)/)?.[0] ?? null;
|
|
@@ -6749,20 +6779,57 @@ const probe = (command) => {
|
|
|
6749
6779
|
return null;
|
|
6750
6780
|
}
|
|
6751
6781
|
};
|
|
6752
|
-
/**
|
|
6753
|
-
|
|
6782
|
+
/** isorite's own prefix for OpenCode 2. Not ~/.config/isorite/opencode, which is OpenCode's configuration as
|
|
6783
|
+
* `term` writes it (home.ts): an npm prefix there would put node_modules among it. */
|
|
6784
|
+
function ownPrefix(env = process.env) {
|
|
6785
|
+
return join(env.XDG_CONFIG_HOME || join(homedir(), ".config"), "isorite", "opencode-cli");
|
|
6786
|
+
}
|
|
6787
|
+
/** The OpenCode 2 in isorite's own prefix, as npm links it; on Windows, the package's own opencode.exe, as npm's
|
|
6788
|
+
* opencode2.cmd can't be run without a shell (Node refuses .cmd files since 2024). */
|
|
6789
|
+
const ownCommand = (env = process.env, platform = process.platform) => platform === "win32" ? join(ownPrefix(env), "node_modules", "@opencode", "cli", "bin", "opencode.exe") : join(ownPrefix(env), "node_modules", ".bin", "opencode2");
|
|
6790
|
+
/** A command on the PATH as Windows can run it without a shell: `<name>.exe`, or for npm's `<name>.cmd` the
|
|
6791
|
+
* package's opencode.exe beside it, in the global node_modules. Null when there is neither. */
|
|
6792
|
+
function onWindowsPath(name, env = process.env, exists = existsSync) {
|
|
6793
|
+
for (const dir of (env.PATH ?? env.Path ?? "").split(win32.delimiter).filter(Boolean)) {
|
|
6794
|
+
if (exists(win32.join(dir, `${name}.exe`))) return win32.join(dir, `${name}.exe`);
|
|
6795
|
+
const exe = win32.join(dir, "node_modules", "@opencode", "cli", "bin", "opencode.exe");
|
|
6796
|
+
if (exists(win32.join(dir, `${name}.cmd`)) && exists(exe)) return exe;
|
|
6797
|
+
}
|
|
6798
|
+
return null;
|
|
6799
|
+
}
|
|
6800
|
+
/** The line that installs OpenCode 2 for isorite alone, to copy. */
|
|
6801
|
+
const installLine = (env = process.env) => `isorite opencode (npm install --prefix ${ownPrefix(env)} ${PACKAGE$1})`;
|
|
6802
|
+
/** OpenCode 2 wasn't found. `old` is an OpenCode 1 that was, if one was. */
|
|
6803
|
+
var NoOpenCode = class extends Error {
|
|
6804
|
+
old;
|
|
6805
|
+
constructor(tried, old, env) {
|
|
6806
|
+
super((old ? `isorite term needs OpenCode 2; ${old} is OpenCode 1 (${tried.join("; ")}).\n` : `isorite term needs OpenCode 2 (${tried.join("; ")}).\n`) + ` Install it for isorite only, leaving the global one as it is: ${installLine(env)}\n or name one with: ISORITE_OPENCODE=/path/to/opencode isorite term`);
|
|
6807
|
+
this.old = old;
|
|
6808
|
+
}
|
|
6809
|
+
};
|
|
6810
|
+
/** ISORITE_OPENCODE if set, else isorite's own prefix's, else `opencode2`, else `opencode`: the first that is
|
|
6811
|
+
* OpenCode 2. */
|
|
6812
|
+
function findOpenCode(env = process.env, run = probe, platform = process.platform, where = onWindowsPath) {
|
|
6754
6813
|
const tried = [];
|
|
6755
|
-
|
|
6756
|
-
|
|
6814
|
+
let old = null;
|
|
6815
|
+
const candidates = env.ISORITE_OPENCODE ? [[env.ISORITE_OPENCODE, "ISORITE_OPENCODE"]] : [
|
|
6816
|
+
[ownCommand(env, platform), "isorite opencode"],
|
|
6817
|
+
["opencode2", "the PATH"],
|
|
6818
|
+
["opencode", "the PATH"]
|
|
6819
|
+
];
|
|
6820
|
+
for (const [named, from] of candidates) {
|
|
6821
|
+
const command = platform === "win32" && from === "the PATH" ? where(named, env) ?? named : named;
|
|
6757
6822
|
const output = run(command);
|
|
6758
6823
|
const version = output === null ? null : parseVersion(output);
|
|
6759
6824
|
if (version && Number(version.split(".")[0]) === 2) return {
|
|
6760
6825
|
command,
|
|
6761
|
-
version
|
|
6826
|
+
version,
|
|
6827
|
+
from
|
|
6762
6828
|
};
|
|
6763
|
-
|
|
6829
|
+
if (version && Number(version.split(".")[0]) < 2) old ??= `${command} ${version}`;
|
|
6830
|
+
if (output !== null || from !== "isorite opencode") tried.push(output === null ? `${command}: not found` : `${command}: ${version ?? "unknown version"}`);
|
|
6764
6831
|
}
|
|
6765
|
-
throw new
|
|
6832
|
+
throw new NoOpenCode(tried, old, env);
|
|
6766
6833
|
}
|
|
6767
6834
|
//#endregion
|
|
6768
6835
|
//#region packages/server/src/config.ts
|
|
@@ -6850,6 +6917,7 @@ function providerFrom(config, env = process.env) {
|
|
|
6850
6917
|
...headers ? { headers } : {},
|
|
6851
6918
|
...query ? { query } : {},
|
|
6852
6919
|
...typeof p.key_header === "string" && p.key_header ? { keyHeader: p.key_header } : {},
|
|
6920
|
+
...typeof p.key_prefix === "string" ? { keyPrefix: p.key_prefix } : {},
|
|
6853
6921
|
...p.stream_usage === false ? { streamUsage: false } : {},
|
|
6854
6922
|
...typeof p.max_tokens === "number" && p.max_tokens > 0 ? { maxTokens: Math.floor(p.max_tokens) } : {}
|
|
6855
6923
|
};
|
|
@@ -6953,6 +7021,23 @@ function lendBase(name, lend, path = configPath()) {
|
|
|
6953
7021
|
}
|
|
6954
7022
|
//#endregion
|
|
6955
7023
|
//#region packages/server/src/model.ts
|
|
7024
|
+
/** Where a mirror keeps the .tgz (M21): ISORITE_MODEL_BASE_URL names the folder it's in, tried before the rest. */
|
|
7025
|
+
function mirrorOf(env = process.env) {
|
|
7026
|
+
const base = env.ISORITE_MODEL_BASE_URL?.trim();
|
|
7027
|
+
return base ? `${base.replace(/\/+$/, "")}/${RELEASE_FILE}` : null;
|
|
7028
|
+
}
|
|
7029
|
+
/** How to fetch the model by hand, to copy: the file, its SHA-256, and the command that takes it. */
|
|
7030
|
+
function byHand(env = process.env) {
|
|
7031
|
+
const mirror = mirrorOf(env);
|
|
7032
|
+
return [
|
|
7033
|
+
"Fetch this file another way (a browser, or a machine that can reach it):",
|
|
7034
|
+
` ${RELEASE}`,
|
|
7035
|
+
...mirror ? [` or from your mirror: ${mirror}`] : [],
|
|
7036
|
+
` SHA-256 ${RELEASE_SHA256}`,
|
|
7037
|
+
"then run:",
|
|
7038
|
+
` isorite model --from ~/Downloads/${RELEASE_FILE}`
|
|
7039
|
+
];
|
|
7040
|
+
}
|
|
6956
7041
|
/** Where the server looks for the model, in order, and why each place is there. */
|
|
6957
7042
|
function modelPlaces(env = process.env) {
|
|
6958
7043
|
if (env.ISORITE_EMBED_DIR) return [{
|
|
@@ -7042,7 +7127,7 @@ function unpack(pkg) {
|
|
|
7042
7127
|
}
|
|
7043
7128
|
/** Fetch the model's files into `out`, keeping only files whose SHA-256 is the one expected: from a folder if one is
|
|
7044
7129
|
* named; else from npm, then the GitHub release, then Hugging Face, each tried in turn. */
|
|
7045
|
-
async function fetchModel(out, say = console.log, { from, pkg = PACKAGE, release = RELEASE, hub = HUB, files = FILES } = {}) {
|
|
7130
|
+
async function fetchModel(out, say = console.log, { from, pkg = PACKAGE, release = RELEASE, mirror = null, url, hub = HUB, files = FILES, tgzSha = files === FILES ? RELEASE_SHA256 : null } = {}) {
|
|
7046
7131
|
mkdirSync(out, { recursive: true });
|
|
7047
7132
|
const missing = () => files.filter(([, to, want]) => !(existsSync(join(out, to)) && sha(readFileSync(join(out, to))) === want));
|
|
7048
7133
|
for (const [, to] of files) if (!missing().some((f) => f[1] === to)) say(`${to}: already here`);
|
|
@@ -7071,31 +7156,42 @@ async function fetchModel(out, say = console.log, { from, pkg = PACKAGE, release
|
|
|
7071
7156
|
}
|
|
7072
7157
|
};
|
|
7073
7158
|
const why = (e, url) => e.message === "fetch failed" || e.cause ? unreachable(e, url) : e.message;
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
} catch (e) {
|
|
7077
|
-
tried.push(`npm (${pkg}): ${e.message}`);
|
|
7078
|
-
say(`From npm (${pkg}): ${e.message}`);
|
|
7079
|
-
}
|
|
7080
|
-
if (missing().length && release) {
|
|
7159
|
+
/** The .tgz at an address, checked by its SHA-256 before it's unpacked; a failure is kept in `tried`. */
|
|
7160
|
+
const fromWeb = async (where, address) => {
|
|
7081
7161
|
const tmp = mkdtempSync(join(tmpdir(), "isorite-model-release-"));
|
|
7082
7162
|
try {
|
|
7083
|
-
say(`From
|
|
7084
|
-
const res = await fetch(
|
|
7163
|
+
say(`From ${where}: ${address}`);
|
|
7164
|
+
const res = await fetch(address);
|
|
7085
7165
|
if (!res.ok) throw new Error(`it said ${res.status}`);
|
|
7086
|
-
const
|
|
7087
|
-
|
|
7166
|
+
const buf = new Uint8Array(await res.arrayBuffer());
|
|
7167
|
+
if (tgzSha && sha(buf) !== tgzSha) throw new Error(`not the file expected: its SHA-256 is ${sha(buf)}, not ${tgzSha}, so it was not unpacked`);
|
|
7168
|
+
const tgz = join(tmp, basename(new URL(address).pathname) || "isorite-model-granite-1.0.0.tgz");
|
|
7169
|
+
writeFileSync(tgz, buf);
|
|
7088
7170
|
fromPackage(tgz);
|
|
7089
7171
|
} catch (e) {
|
|
7090
|
-
tried.push(
|
|
7091
|
-
say(`From
|
|
7172
|
+
tried.push(`${where} (${address}): ${why(e, address)}`);
|
|
7173
|
+
say(`From ${where}: ${why(e, address)}`);
|
|
7092
7174
|
} finally {
|
|
7093
7175
|
rmSync(tmp, {
|
|
7094
7176
|
recursive: true,
|
|
7095
7177
|
force: true
|
|
7096
7178
|
});
|
|
7097
7179
|
}
|
|
7180
|
+
};
|
|
7181
|
+
if (url) {
|
|
7182
|
+
await fromWeb("--url", url);
|
|
7183
|
+
if (missing().length) throw new ModelFetchError(tried);
|
|
7184
|
+
say(`The model is in ${out}`);
|
|
7185
|
+
return;
|
|
7098
7186
|
}
|
|
7187
|
+
if (missing().length && mirror) await fromWeb("the mirror (ISORITE_MODEL_BASE_URL)", mirror);
|
|
7188
|
+
if (missing().length && pkg) try {
|
|
7189
|
+
fromPackage(pkg);
|
|
7190
|
+
} catch (e) {
|
|
7191
|
+
tried.push(`npm (${pkg}): ${e.message}`);
|
|
7192
|
+
say(`From npm (${pkg}): ${e.message}`);
|
|
7193
|
+
}
|
|
7194
|
+
if (missing().length && release) await fromWeb("GitHub", release);
|
|
7099
7195
|
if (missing().length && hub) {
|
|
7100
7196
|
say("From Hugging Face");
|
|
7101
7197
|
try {
|
|
@@ -7115,6 +7211,19 @@ async function fetchModel(out, say = console.log, { from, pkg = PACKAGE, release
|
|
|
7115
7211
|
* checked by using it: it must load, and give DIM numbers for a sentence. Kept in the user's models, and used from
|
|
7116
7212
|
* then on. */
|
|
7117
7213
|
async function installFrom(from, say = console.log, env = process.env) {
|
|
7214
|
+
if (/\.(tgz|tar\.gz)$/.test(from) && existsSync(from) && statSync(from).isFile()) {
|
|
7215
|
+
const got = sha(readFileSync(from));
|
|
7216
|
+
if (got !== "8d8ab528ae289dfc81fd2bfa80c867ca0c8342a2d98f7c86141df4b15fdf028c") throw new Error(`${from} is not ${RELEASE_FILE}: its SHA-256 is ${got}, not ${RELEASE_SHA256}`);
|
|
7217
|
+
const dir = unpack(resolve(from));
|
|
7218
|
+
try {
|
|
7219
|
+
return await installFrom(join(dir, "package"), say, env);
|
|
7220
|
+
} finally {
|
|
7221
|
+
rmSync(dir, {
|
|
7222
|
+
recursive: true,
|
|
7223
|
+
force: true
|
|
7224
|
+
});
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7118
7227
|
if (!hasModel(from)) throw new Error(`${from} has no model: it needs model.onnx and tokenizer.json`);
|
|
7119
7228
|
const granite = !existsSync(join(from, "isorite-model.json")) && FILES.every(([, to, want]) => existsSync(join(from, to)) && sha(readFileSync(join(from, to))) === want);
|
|
7120
7229
|
let out = userModelDir(env);
|
|
@@ -7142,7 +7251,7 @@ async function installFrom(from, say = console.log, env = process.env) {
|
|
|
7142
7251
|
}
|
|
7143
7252
|
/** `isorite model [--from <folder>]`: says which model is installed and where, or installs one, and on failure
|
|
7144
7253
|
* where it looked, what it tried, and what to do. */
|
|
7145
|
-
async function modelCommand(from, say = console.log, env = process.env, sources = {}) {
|
|
7254
|
+
async function modelCommand(from, say = console.log, env = process.env, sources = {}, url) {
|
|
7146
7255
|
const told = (i, why) => say(`search by meaning: ${i.name} at ${i.dir} (sha256 ${i.sha.slice(0, 12)}, ${i.pooling} pooling), found by ${why}`);
|
|
7147
7256
|
if (env.ISORITE_EMBED === "off") say("ISORITE_EMBED=off: search by meaning is switched off, whatever is installed");
|
|
7148
7257
|
if (from) try {
|
|
@@ -7159,27 +7268,35 @@ async function modelCommand(from, say = console.log, env = process.env, sources
|
|
|
7159
7268
|
return 0;
|
|
7160
7269
|
}
|
|
7161
7270
|
say(`No model in ${places.map((p) => p.dir).join(", ")}`);
|
|
7271
|
+
if (!url) say(`If it can't be fetched from here: ${RELEASE} (SHA-256 ${RELEASE_SHA256}), then isorite model --from <that file>`);
|
|
7162
7272
|
try {
|
|
7163
|
-
await fetchModel(userModelDir(env), say,
|
|
7273
|
+
await fetchModel(userModelDir(env), say, {
|
|
7274
|
+
mirror: mirrorOf(env),
|
|
7275
|
+
...sources,
|
|
7276
|
+
...url ? { url } : {}
|
|
7277
|
+
});
|
|
7164
7278
|
return 0;
|
|
7165
7279
|
} catch (e) {
|
|
7166
7280
|
say(e.message);
|
|
7167
7281
|
say([
|
|
7168
|
-
|
|
7169
|
-
|
|
7170
|
-
` -
|
|
7171
|
-
` -
|
|
7282
|
+
...byHand(env),
|
|
7283
|
+
"Or:",
|
|
7284
|
+
` - a mirror: ISORITE_MODEL_BASE_URL=<the folder ${RELEASE_FILE} is in> isorite model, or isorite model --url <its address>`,
|
|
7285
|
+
` - the model's files from a machine that has them (${userModelDir()} there): isorite model --from <folder>`,
|
|
7286
|
+
` - any compatible model (ONNX, 384 numbers out, with its tokenizer.json): isorite model --from <folder>, or ISORITE_EMBED_DIR=<folder>`,
|
|
7172
7287
|
"Search by words works without it."
|
|
7173
7288
|
].join("\n"));
|
|
7174
7289
|
return 1;
|
|
7175
7290
|
}
|
|
7176
7291
|
}
|
|
7177
|
-
var NAME$2, MODEL, RELEASE, PACKAGE, HUB, FILES, sha, userModelsDir, userModelDir, hasModel, ModelFetchError, modelFiles;
|
|
7292
|
+
var NAME$2, MODEL, RELEASE_FILE, RELEASE, RELEASE_SHA256, PACKAGE, HUB, FILES, sha, userModelsDir, userModelDir, hasModel, ModelFetchError, modelFiles;
|
|
7178
7293
|
var init_model = __esmMin((() => {
|
|
7179
7294
|
init_src();
|
|
7180
7295
|
NAME$2 = "granite-embedding-small-english-r2";
|
|
7181
7296
|
MODEL = `${NAME$2}-q8`;
|
|
7182
|
-
|
|
7297
|
+
RELEASE_FILE = "isorite-model-granite-1.0.0.tgz";
|
|
7298
|
+
RELEASE = `https://github.com/lachlanjs/isorite/releases/download/model-granite-1.0.0/${RELEASE_FILE}`;
|
|
7299
|
+
RELEASE_SHA256 = "8d8ab528ae289dfc81fd2bfa80c867ca0c8342a2d98f7c86141df4b15fdf028c";
|
|
7183
7300
|
PACKAGE = "isorite-model-granite@1.0.0";
|
|
7184
7301
|
HUB = `https://huggingface.co/onnx-community/granite-embedding-small-english-r2-ONNX/resolve/1dc7835ba0cb9c76a3618d0bf0c427c97671b3c8`;
|
|
7185
7302
|
FILES = [
|
|
@@ -7982,6 +8099,7 @@ const SetupDraft = z.object({
|
|
|
7982
8099
|
url: z.string().max(2e3),
|
|
7983
8100
|
keyHeld: z.boolean(),
|
|
7984
8101
|
keyHeader: z.string().max(200),
|
|
8102
|
+
keyPrefix: z.string().max(50).default(""),
|
|
7985
8103
|
headers: z.array(Pair).max(40),
|
|
7986
8104
|
query: z.array(Pair).max(40),
|
|
7987
8105
|
pfx: Path,
|
|
@@ -8481,6 +8599,7 @@ const FORM = [
|
|
|
8481
8599
|
"auth",
|
|
8482
8600
|
"key",
|
|
8483
8601
|
"key_header",
|
|
8602
|
+
"key_prefix",
|
|
8484
8603
|
"headers",
|
|
8485
8604
|
"query",
|
|
8486
8605
|
"pfx",
|
|
@@ -8512,6 +8631,7 @@ function readSetup(config) {
|
|
|
8512
8631
|
url: gateway ? p.url : OPENROUTER.url,
|
|
8513
8632
|
keyHeld: text(p.key) !== "",
|
|
8514
8633
|
keyHeader: text(p.key_header),
|
|
8634
|
+
keyPrefix: text(p.key_prefix).trim(),
|
|
8515
8635
|
headers: pairs(p.headers),
|
|
8516
8636
|
query: pairs(p.query),
|
|
8517
8637
|
pfx: text(p.pfx),
|
|
@@ -8553,6 +8673,9 @@ function applySetup(config, draft) {
|
|
|
8553
8673
|
}
|
|
8554
8674
|
if (gateway) {
|
|
8555
8675
|
if (draft.keyHeader.trim()) p.key_header = draft.keyHeader.trim();
|
|
8676
|
+
const prefix = (draft.keyPrefix ?? "").trim();
|
|
8677
|
+
if (prefix) p.key_prefix = prefix + " ";
|
|
8678
|
+
else if (old.key_prefix === "" && draft.keyHeader.trim()) p.key_prefix = "";
|
|
8556
8679
|
const pairs = (field, given) => {
|
|
8557
8680
|
const before = table(old[field]), out = {};
|
|
8558
8681
|
for (const pair of given) {
|
|
@@ -8713,7 +8836,7 @@ var Setup = class {
|
|
|
8713
8836
|
};
|
|
8714
8837
|
//#endregion
|
|
8715
8838
|
//#region packages/server/src/version.ts
|
|
8716
|
-
const VERSION = "0.
|
|
8839
|
+
const VERSION = "0.9.1";
|
|
8717
8840
|
//#endregion
|
|
8718
8841
|
//#region packages/server/src/git.ts
|
|
8719
8842
|
/** The top of the repository `folder` is in, or null: the nearest folder holding `.git`, a directory or, in a
|
|
@@ -8946,6 +9069,27 @@ async function serve$1(path, { host = "127.0.0.1", port = 4767, allowHosts = [],
|
|
|
8946
9069
|
process.on("SIGTERM", stop);
|
|
8947
9070
|
}
|
|
8948
9071
|
//#endregion
|
|
9072
|
+
//#region packages/server/src/self.ts
|
|
9073
|
+
/** Whether this is the single executable. */
|
|
9074
|
+
const single = () => {
|
|
9075
|
+
try {
|
|
9076
|
+
return isSea();
|
|
9077
|
+
} catch {
|
|
9078
|
+
return false;
|
|
9079
|
+
}
|
|
9080
|
+
};
|
|
9081
|
+
/** What runs isorite again with `args`, given the script (npm's or a checkout's) to run when not single. */
|
|
9082
|
+
function self(script, args, execArgv = process.execArgv) {
|
|
9083
|
+
return {
|
|
9084
|
+
command: process.execPath,
|
|
9085
|
+
args: single() ? args : [
|
|
9086
|
+
...execArgv,
|
|
9087
|
+
script,
|
|
9088
|
+
...args
|
|
9089
|
+
]
|
|
9090
|
+
};
|
|
9091
|
+
}
|
|
9092
|
+
//#endregion
|
|
8949
9093
|
//#region packages/server/src/term.ts
|
|
8950
9094
|
init_src$1();
|
|
8951
9095
|
const MAIN = import.meta.url.endsWith(".ts") ? fileURLToPath(new URL("./main.ts", import.meta.url)) : fileURLToPath(import.meta.url);
|
|
@@ -8992,14 +9136,14 @@ async function ensureServer(path, { port = 4767, env = process.env, wait = 12e4,
|
|
|
8992
9136
|
if (!opened) throw new StudyError(`nothing is serving on port ${port}: name a base`);
|
|
8993
9137
|
const log = join(tmpdir(), `isorite-term-${port}.log`);
|
|
8994
9138
|
const out = openSync(log, "w");
|
|
8995
|
-
|
|
8996
|
-
MAIN,
|
|
9139
|
+
const again = self(MAIN, [
|
|
8997
9140
|
"serve",
|
|
8998
9141
|
path,
|
|
8999
9142
|
"--port",
|
|
9000
9143
|
String(port),
|
|
9001
9144
|
"--held"
|
|
9002
|
-
],
|
|
9145
|
+
], []);
|
|
9146
|
+
started = spawn(again.command, again.args, {
|
|
9003
9147
|
stdio: [
|
|
9004
9148
|
"ignore",
|
|
9005
9149
|
out,
|
|
@@ -9068,6 +9212,25 @@ async function freePort() {
|
|
|
9068
9212
|
});
|
|
9069
9213
|
});
|
|
9070
9214
|
}
|
|
9215
|
+
/** Whether `address` asks a browser that opens it for a password (M21): a 401 with Basic, as OpenCode 2 answers
|
|
9216
|
+
* for /api (its page itself is served to anyone). Asked as a browser navigates, by node:http: OpenCode leaves the
|
|
9217
|
+
* challenge out for its own page's fetches (Sec-Fetch-Mode: cors), which is all Node's fetch can send. */
|
|
9218
|
+
function asksPassword(address) {
|
|
9219
|
+
return new Promise((done) => {
|
|
9220
|
+
const req = get(address, {
|
|
9221
|
+
headers: {
|
|
9222
|
+
Accept: "text/html",
|
|
9223
|
+
"Sec-Fetch-Mode": "navigate",
|
|
9224
|
+
"Sec-Fetch-Dest": "document"
|
|
9225
|
+
},
|
|
9226
|
+
timeout: 3e3
|
|
9227
|
+
}, (res) => {
|
|
9228
|
+
res.resume();
|
|
9229
|
+
done(res.statusCode === 401 && /^\s*basic\b/i.test(String(res.headers["www-authenticate"] ?? "")));
|
|
9230
|
+
});
|
|
9231
|
+
req.on("timeout", () => req.destroy()).on("error", () => done(false));
|
|
9232
|
+
});
|
|
9233
|
+
}
|
|
9071
9234
|
/** A POST to the isorite server, made as its page makes one: from its origin, with the token it hands out. */
|
|
9072
9235
|
async function tell(url, path, body) {
|
|
9073
9236
|
const token = await tokenOf(url);
|
|
@@ -9187,8 +9350,57 @@ function where(port, models, env, tty, page = "/") {
|
|
|
9187
9350
|
async function tokenOf(url) {
|
|
9188
9351
|
return (await (await fetch(`${url}/api/session`)).json()).token;
|
|
9189
9352
|
}
|
|
9353
|
+
/** `isorite opencode` (M21): OpenCode 2, at the version isorite is tested with, in isorite's own npm prefix, the
|
|
9354
|
+
* global one left as it is. npm's registry, proxy and mirror settings apply. Run again, it installs it again. */
|
|
9355
|
+
function installOpenCode(env = process.env, say = console.log, npm = "npm") {
|
|
9356
|
+
const prefix = ownPrefix(env);
|
|
9357
|
+
say(`isorite opencode: installing ${PACKAGE$1} in ${prefix}, for isorite alone; nothing global is changed`);
|
|
9358
|
+
const win = process.platform === "win32";
|
|
9359
|
+
const done = spawnSync(npm, [
|
|
9360
|
+
"install",
|
|
9361
|
+
"--prefix",
|
|
9362
|
+
win ? `"${prefix}"` : prefix,
|
|
9363
|
+
"--no-fund",
|
|
9364
|
+
"--no-audit",
|
|
9365
|
+
"--loglevel=error",
|
|
9366
|
+
PACKAGE$1
|
|
9367
|
+
], {
|
|
9368
|
+
stdio: "inherit",
|
|
9369
|
+
env,
|
|
9370
|
+
shell: win
|
|
9371
|
+
});
|
|
9372
|
+
if (done.status !== 0) {
|
|
9373
|
+
say(`npm could not install it${done.error ? `: ${done.error.message}` : ""}. If npm's registry or mirror is the trouble, fetch an OpenCode 2 another way and name it: ISORITE_OPENCODE=/path/to/opencode isorite term`);
|
|
9374
|
+
return 1;
|
|
9375
|
+
}
|
|
9376
|
+
try {
|
|
9377
|
+
const found = findOpenCode(env);
|
|
9378
|
+
say(`OpenCode ${found.version} is ${found.command}: isorite term uses it from now on`);
|
|
9379
|
+
return 0;
|
|
9380
|
+
} catch (e) {
|
|
9381
|
+
say(e.message);
|
|
9382
|
+
return 1;
|
|
9383
|
+
}
|
|
9384
|
+
}
|
|
9385
|
+
/** OpenCode 2, or, when there's none and a person is at the terminal, the offer to install it for isorite alone. */
|
|
9386
|
+
async function openCodeFor(env) {
|
|
9387
|
+
try {
|
|
9388
|
+
return findOpenCode(env);
|
|
9389
|
+
} catch (e) {
|
|
9390
|
+
if (!(e instanceof NoOpenCode) || env.ISORITE_OPENCODE || !process.stdin.isTTY || !process.stdout.isTTY) throw e;
|
|
9391
|
+
console.error(e.message);
|
|
9392
|
+
const ask = createInterface({
|
|
9393
|
+
input: process.stdin,
|
|
9394
|
+
output: process.stdout
|
|
9395
|
+
});
|
|
9396
|
+
const yes = /^y(es)?$/i.test((await ask.question(`Install OpenCode 2 for isorite only, in ${ownPrefix(env)}? [y/N] `)).trim());
|
|
9397
|
+
ask.close();
|
|
9398
|
+
if (!yes || installOpenCode(env) !== 0) throw new StudyError("isorite term needs OpenCode 2");
|
|
9399
|
+
return findOpenCode(env);
|
|
9400
|
+
}
|
|
9401
|
+
}
|
|
9190
9402
|
async function term(path, { port = 4767, cwd = process.cwd(), env: given = process.env, wait = 12e4, onServer } = {}) {
|
|
9191
|
-
const opencode =
|
|
9403
|
+
const opencode = await openCodeFor(given);
|
|
9192
9404
|
const server = await ensureServer(path, {
|
|
9193
9405
|
port,
|
|
9194
9406
|
env: given,
|
|
@@ -9207,7 +9419,6 @@ async function term(path, { port = 4767, cwd = process.cwd(), env: given = proce
|
|
|
9207
9419
|
}
|
|
9208
9420
|
const { gateway, env } = models;
|
|
9209
9421
|
const said = where(port, models, given, void 0, new URL(server.url).pathname.replace(/\/?$/, "/"));
|
|
9210
|
-
for (const line of said) console.log(line);
|
|
9211
9422
|
const agents = path === void 0 ? null : join(locate(path).notes, AGENTS_FILE);
|
|
9212
9423
|
const home = writeHome(defaultHome(env), {
|
|
9213
9424
|
gateway,
|
|
@@ -9229,6 +9440,8 @@ async function term(path, { port = 4767, cwd = process.cwd(), env: given = proce
|
|
|
9229
9440
|
server.release();
|
|
9230
9441
|
throw e;
|
|
9231
9442
|
}
|
|
9443
|
+
if (await asksPassword(`${backend.url}/api/session`)) said.push(` OpenCode's own web page (port ${new URL(backend.url).port}) asks for a password: don't open it. isorite's page is above, and /web in the terminal opens it`);
|
|
9444
|
+
for (const line of said) console.log(line);
|
|
9232
9445
|
await tell(server.url, "/api/terminal/attach", {
|
|
9233
9446
|
url: backend.url,
|
|
9234
9447
|
password: backend.password,
|
|
@@ -9295,6 +9508,7 @@ const READ = [
|
|
|
9295
9508
|
"SSH_CONNECTION",
|
|
9296
9509
|
"ISORITE_EMBED",
|
|
9297
9510
|
"ISORITE_EMBED_DIR",
|
|
9511
|
+
"ISORITE_MODEL_BASE_URL",
|
|
9298
9512
|
"XDG_DATA_HOME",
|
|
9299
9513
|
"XDG_CACHE_HOME"
|
|
9300
9514
|
];
|
|
@@ -9341,7 +9555,10 @@ async function checkProvider(p, h, key, say) {
|
|
|
9341
9555
|
const missing = models.filter((m) => !listed.includes(m));
|
|
9342
9556
|
say("reached", "ok", `${to.url.replace(/\?.*$/, "")} answered with ${listed.length} models`);
|
|
9343
9557
|
if (listed.length && missing.length) say("models", "fail", `${missing.join(", ")} not among them; it has ${listed.slice(0, 12).join(", ")}${listed.length > 12 ? ", …" : ""}. Name its models under [tiers]`);
|
|
9344
|
-
} else
|
|
9558
|
+
} else if (res.status === 401 || res.status === 403) {
|
|
9559
|
+
const said = refusal(await res.text().catch(() => ""), key);
|
|
9560
|
+
say("reached", "fail", `it said ${res.status} to the list of models${said ? `: “${said}”` : ""}. The key${key ? `, sent as ${keySentAs(p, key)},` : ""} or a header it wants (headers), is not accepted: check key_header and key_prefix`);
|
|
9561
|
+
} else say("reached", "ok", `it answered (${res.status} for the list of models, which some gateways don't have)`);
|
|
9345
9562
|
} catch (e) {
|
|
9346
9563
|
reached = false;
|
|
9347
9564
|
say("reached", "fail", unreachable(e, p.url));
|
|
@@ -9401,7 +9618,7 @@ async function doctor(folder, o = {}) {
|
|
|
9401
9618
|
const tls = gatewayTls(path, env);
|
|
9402
9619
|
http ??= gatewayHttp(provider, tls, env);
|
|
9403
9620
|
if (existsSync(path)) say("config", "ok", `${path}: ${provider.name} at ${provider.url}`);
|
|
9404
|
-
say("key", key ? "ok" : tls ? "warn" : "fail", key ?
|
|
9621
|
+
say("key", key ? "ok" : tls ? "warn" : "fail", key ? `sent as ${keySentAs(provider, key)}${provider.keyHeader?.toLowerCase() === "authorization" && provider.keyPrefix === void 0 ? ` (key_header = "${provider.keyHeader}" alone means Bearer since 0.9.0; key_prefix = "" sends it bare)` : ""}` : tls ? "none; the client certificate alone is sent" : "none: set one in Iso's settings, or as key under [provider]");
|
|
9405
9622
|
if (tls) say("certificate", "ok", `${tls.files.join(", ")}${tls.pfx || tls.cert ? "" : " (an authority only, no client certificate)"}`);
|
|
9406
9623
|
const tiers = `low ${provider.tiers.low}, mid ${provider.tiers.mid}, max ${provider.tiers.max}`;
|
|
9407
9624
|
say("models", provider.defaultTiers ? "warn" : "ok", provider.defaultTiers ? `${tiers}: OpenRouter's names, kept because [tiers] names none for ${provider.name}; a gateway usually calls its models otherwise` : tiers);
|
|
@@ -9427,11 +9644,21 @@ async function doctor(folder, o = {}) {
|
|
|
9427
9644
|
if (!o.report) out(` asking ${p.name}: its models, then ${models.length + 1} replies of a few words (a few hundred tokens in all)`);
|
|
9428
9645
|
await checkProvider(p, h, key, say);
|
|
9429
9646
|
}
|
|
9647
|
+
const began = Date.now(), git = spawnSync(o.git ?? "git", ["--version"], {
|
|
9648
|
+
encoding: "utf8",
|
|
9649
|
+
env,
|
|
9650
|
+
timeout: 3e3,
|
|
9651
|
+
killSignal: "SIGKILL"
|
|
9652
|
+
});
|
|
9653
|
+
const took = Date.now() - began;
|
|
9654
|
+
if (git.error && git.error.code === "ENOENT") say("git", "skip", "not found: isorite doesn't need it; OpenCode works without it, as a folder that isn't a repository");
|
|
9655
|
+
else if (git.status !== 0 || took >= 3e3) say("git", "warn", `git --version ${took >= 3e3 ? "didn't answer in 3 s" : `failed after ${took} ms`}: likely a wrapper (a company's) that waits on the network. isorite no longer waits on git to start; OpenCode may, and isorite term then says it's waiting for OpenCode's server`);
|
|
9656
|
+
else say("git", took > 1e3 ? "warn" : "ok", `${git.stdout.trim()}, in ${took} ms${took > 1e3 ? ": slow, likely a wrapper; OpenCode may wait on it as it starts" : ""}`);
|
|
9430
9657
|
try {
|
|
9431
9658
|
const found = findOpenCode(env, o.probe);
|
|
9432
|
-
say("opencode", "ok", `${found.command} ${found.version}`);
|
|
9659
|
+
say("opencode", "ok", `${found.command} ${found.version}${found.from === "the PATH" ? "" : ` (${found.from})`}`);
|
|
9433
9660
|
} catch (e) {
|
|
9434
|
-
say("opencode", "warn", e.message.replace(/\n\s*/g, " "));
|
|
9661
|
+
say("opencode", e instanceof NoOpenCode && e.old ? "fail" : "warn", e.message.replace(/\n\s*/g, " "));
|
|
9435
9662
|
}
|
|
9436
9663
|
const port = o.port ?? 4767, url = `http://127.0.0.1:${port}`, served = await serving(url);
|
|
9437
9664
|
say("port", "ok", served === null ? `${port} is free: isorite term starts a server there` : `${port} is isorite's, serving ${served}`);
|
|
@@ -9449,6 +9676,12 @@ async function doctor(folder, o = {}) {
|
|
|
9449
9676
|
known = false;
|
|
9450
9677
|
}
|
|
9451
9678
|
say("terminal", known ? "ok" : "warn", terminal);
|
|
9679
|
+
const hint = "isorite term also runs OpenCode's own web server, on a port of its own, which asks for a password: open isorite's pages and panes (/web in the terminal), not OpenCode's";
|
|
9680
|
+
if (served === null) say("browser pages", "ok", hint);
|
|
9681
|
+
else {
|
|
9682
|
+
const asking = (await Promise.all([`${url}/`, `${url}/api/session`].map(async (at) => await asksPassword(at) ? at : null))).filter(Boolean);
|
|
9683
|
+
say("browser pages", asking.length ? "fail" : "ok", asking.length ? `${asking.join(" and ")} asked for a password (401, Basic): that is not isorite's server; stop it, or give isorite another port (--port <n>)` : `isorite's pages at ${url}/ ask for no password. ${hint}`);
|
|
9684
|
+
}
|
|
9452
9685
|
if (env.SSH_CONNECTION) say("browser", "ok", `over SSH: on your own machine, ssh -L ${port}:localhost:${port} <this host>, then http://localhost:${port}/`);
|
|
9453
9686
|
let model = null;
|
|
9454
9687
|
if (env.ISORITE_EMBED === "off") say("search by meaning", "skip", "switched off (ISORITE_EMBED=off)");
|
|
@@ -9460,7 +9693,23 @@ async function doctor(folder, o = {}) {
|
|
|
9460
9693
|
say("search by meaning", "fail", `${dir}: ${e.message}`);
|
|
9461
9694
|
}
|
|
9462
9695
|
if (model) say("search by meaning", "ok", `${model.name} at ${model.dir}`);
|
|
9463
|
-
else if (!dir)
|
|
9696
|
+
else if (!dir) {
|
|
9697
|
+
let reach = "";
|
|
9698
|
+
if (!o.offline) {
|
|
9699
|
+
const from = mirrorOf(env) ?? RELEASE;
|
|
9700
|
+
try {
|
|
9701
|
+
const res = await fetch(from, {
|
|
9702
|
+
method: "HEAD",
|
|
9703
|
+
redirect: "follow",
|
|
9704
|
+
signal: AbortSignal.timeout(1e4)
|
|
9705
|
+
});
|
|
9706
|
+
reach = res.ok ? ` ${from} answers from here.` : ` ${from} said ${res.status}.`;
|
|
9707
|
+
} catch (e) {
|
|
9708
|
+
reach = ` ${from} can't be reached from here: ${unreachable(e, from)}.`;
|
|
9709
|
+
}
|
|
9710
|
+
}
|
|
9711
|
+
say("search by meaning", "warn", `no model in ${modelPlaces(env).map((p) => p.dir).join(" or ")}: isorite model fetches it (53 MB).${reach} By hand: ${mirrorOf(env) ?? RELEASE} (SHA-256 ${RELEASE_SHA256}), then isorite model --from <that file>. Search by words works without it`);
|
|
9712
|
+
}
|
|
9464
9713
|
}
|
|
9465
9714
|
if (folder) try {
|
|
9466
9715
|
const { notes } = locate(folder);
|
|
@@ -9538,7 +9787,8 @@ function proxied(env, execArgv = process.execArgv) {
|
|
|
9538
9787
|
}
|
|
9539
9788
|
/** Run this command again with `env`, its input and output its own; its exit code. */
|
|
9540
9789
|
function rerun(env, argv = process.argv, execArgv = process.execArgv) {
|
|
9541
|
-
const
|
|
9790
|
+
const again = self(argv[1], argv.slice(2), execArgv);
|
|
9791
|
+
const child = spawn(again.command, again.args, {
|
|
9542
9792
|
stdio: "inherit",
|
|
9543
9793
|
env
|
|
9544
9794
|
});
|
|
@@ -9746,7 +9996,7 @@ async function mcp(path, { port = 4767, agent = "mcp", quiet = QUIET_MS, input =
|
|
|
9746
9996
|
}
|
|
9747
9997
|
throw Object.assign(/* @__PURE__ */ new Error(`no method ${rpc.method}`), { code: -32601 });
|
|
9748
9998
|
};
|
|
9749
|
-
const lines = createInterface({
|
|
9999
|
+
const lines = createInterface$1({
|
|
9750
10000
|
input,
|
|
9751
10001
|
crlfDelay: Infinity
|
|
9752
10002
|
});
|
|
@@ -11051,9 +11301,13 @@ const USAGE = `isorite: an agentic harness for understanding
|
|
|
11051
11301
|
isorite mcp [<notes>] [--port <n>] [--agent <name>]
|
|
11052
11302
|
the knowledge base's tools over MCP, on stdio, for Claude Code or Pi:
|
|
11053
11303
|
claude mcp add isorite -- isorite mcp <base> --agent claude-code
|
|
11054
|
-
isorite model [--from <folder>]
|
|
11055
|
-
|
|
11304
|
+
isorite model [--from <folder|.tgz>] [--url <url>]
|
|
11305
|
+
the model for search by meaning: which is installed, and where; else
|
|
11306
|
+
fetched (53 MB, once) from ISORITE_MODEL_BASE_URL, npm, GitHub or
|
|
11307
|
+
Hugging Face, or --url alone; or from the .tgz fetched by hand, or a
|
|
11056
11308
|
folder: its files copied there, or any compatible model
|
|
11309
|
+
isorite opencode OpenCode 2 for isorite term, installed in isorite's own folder
|
|
11310
|
+
(~/.config/isorite/opencode-cli), the global one left as it is
|
|
11057
11311
|
isorite doctor [<notes>] [--report] [--offline]
|
|
11058
11312
|
whether isorite can work here: the configuration, the network, the
|
|
11059
11313
|
provider (asked a few words, a few hundred tokens), the terminal and
|
|
@@ -11151,13 +11405,18 @@ function main(argv) {
|
|
|
11151
11405
|
});
|
|
11152
11406
|
}
|
|
11153
11407
|
if (command === "model") {
|
|
11154
|
-
const from = rest[rest.indexOf("--from") + 1];
|
|
11408
|
+
const from = rest[rest.indexOf("--from") + 1], url = rest[rest.indexOf("--url") + 1];
|
|
11155
11409
|
if (rest.includes("--from") && !from) {
|
|
11156
|
-
console.error("--from takes a folder");
|
|
11410
|
+
console.error("--from takes a folder, or the model's .tgz");
|
|
11411
|
+
return 2;
|
|
11412
|
+
}
|
|
11413
|
+
if (rest.includes("--url") && !/^https?:\/\//.test(url ?? "")) {
|
|
11414
|
+
console.error("--url takes the address of the model's .tgz");
|
|
11157
11415
|
return 2;
|
|
11158
11416
|
}
|
|
11159
|
-
return modelCommand(rest.includes("--from") ? from : void 0);
|
|
11417
|
+
return modelCommand(rest.includes("--from") ? from : void 0, console.log, process.env, {}, rest.includes("--url") ? url : void 0);
|
|
11160
11418
|
}
|
|
11419
|
+
if (command === "opencode") return installOpenCode();
|
|
11161
11420
|
if (command === "memory") return memory(rest.find((a) => !a.startsWith("--")));
|
|
11162
11421
|
if (command === "bases") return bases(rest);
|
|
11163
11422
|
if (command === "--version" || command === "-v") {
|
|
@@ -11232,16 +11491,19 @@ function starting(argv, env = process.env, write = (s) => process.stderr.write(s
|
|
|
11232
11491
|
write("isorite: starting…\n");
|
|
11233
11492
|
return true;
|
|
11234
11493
|
}
|
|
11235
|
-
|
|
11236
|
-
|
|
11494
|
+
/** The command as run: said to be starting, the proxy turned on (running itself again), the system's authorities
|
|
11495
|
+
* trusted; its exit code. The single executable calls this (M21). */
|
|
11496
|
+
async function run(argv) {
|
|
11497
|
+
starting(argv);
|
|
11237
11498
|
delete process.env.ISORITE_STARTING;
|
|
11238
11499
|
const env = proxied(process.env);
|
|
11239
|
-
if (env)
|
|
11500
|
+
if (env) return rerun({
|
|
11240
11501
|
...env,
|
|
11241
11502
|
ISORITE_STARTING: "1"
|
|
11242
|
-
})
|
|
11503
|
+
});
|
|
11243
11504
|
trustSystem();
|
|
11244
|
-
|
|
11505
|
+
return main(argv);
|
|
11245
11506
|
}
|
|
11507
|
+
if (!single() && process.argv[1] && import.meta.url === pathToFileURL(realpathSync(resolve(process.argv[1]))).href) process.exit(await run(process.argv.slice(2)));
|
|
11246
11508
|
//#endregion
|
|
11247
|
-
export { check, main, starting };
|
|
11509
|
+
export { check, main, run, starting };
|