@inferall/cli 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/bin/build-info.json +1 -1
- package/bin/inferall.js +172 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,12 @@ The wizard:
|
|
|
35
35
|
|
|
36
36
|
## Commands
|
|
37
37
|
|
|
38
|
+
For a project with no existing AI client, `init` writes a first-request example:
|
|
39
|
+
`node inferall-hello.mjs` or `python3 inferall_hello.py`. Both use built-in
|
|
40
|
+
libraries, read your exported key or the `.env` beside the example, and select
|
|
41
|
+
the free model explicitly. A refusal or empty response exits nonzero and prints
|
|
42
|
+
an explanation to stderr. Existing example files are left intact.
|
|
43
|
+
|
|
38
44
|
- `init`: the wizard above (default).
|
|
39
45
|
- `doctor`: health-check an existing setup. Key present and valid, `.env`
|
|
40
46
|
gitignore hygiene, whether every client call site points at the gateway, and
|
package/bin/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"sha":"
|
|
1
|
+
{"sha":"51fd6a7","ref":"main","offMain":null}
|
package/bin/inferall.js
CHANGED
|
@@ -1877,9 +1877,175 @@ export function isPlausiblePatch(text, site, base) {
|
|
|
1877
1877
|
export function writeExample(root, kind) {
|
|
1878
1878
|
const p = join(root, kind === "python" ? "inferall_hello.py" : "inferall-hello.mjs");
|
|
1879
1879
|
if (existsSync(p)) return { path: null, problem: null }; // never clobber theirs
|
|
1880
|
+
// The examples are dependency-free (Node fetch, Python urllib) and read the
|
|
1881
|
+
// key from the environment, else from the .env next to the file, so a plain
|
|
1882
|
+
// `node`/`python3` run works right after init. String.raw keeps the regex
|
|
1883
|
+
// backslashes; the generated code avoids backticks and "${" for that reason.
|
|
1880
1884
|
const body = kind === "python"
|
|
1881
|
-
? `"""InferAll hello. Run:
|
|
1882
|
-
|
|
1885
|
+
? String.raw`"""InferAll hello. Run: python3 inferall_hello.py (no packages needed)"""
|
|
1886
|
+
import json
|
|
1887
|
+
import http.client
|
|
1888
|
+
import os
|
|
1889
|
+
import re
|
|
1890
|
+
import sys
|
|
1891
|
+
import urllib.error
|
|
1892
|
+
import urllib.request
|
|
1893
|
+
from pathlib import Path
|
|
1894
|
+
|
|
1895
|
+
API_BASE = ${JSON.stringify(API_BASE)}
|
|
1896
|
+
MODEL = ${JSON.stringify(FREE_MODEL)}
|
|
1897
|
+
|
|
1898
|
+
|
|
1899
|
+
def fail(message):
|
|
1900
|
+
print("inferall: " + message, file=sys.stderr)
|
|
1901
|
+
sys.exit(1)
|
|
1902
|
+
|
|
1903
|
+
|
|
1904
|
+
def read_key():
|
|
1905
|
+
# INFERALL_API_KEY from the environment first, else the .env beside this file.
|
|
1906
|
+
key = os.environ.get("INFERALL_API_KEY", "").strip()
|
|
1907
|
+
if key:
|
|
1908
|
+
return key
|
|
1909
|
+
try:
|
|
1910
|
+
text = (Path(__file__).resolve().parent / ".env").read_text(encoding="utf-8-sig")
|
|
1911
|
+
except FileNotFoundError:
|
|
1912
|
+
return ""
|
|
1913
|
+
except OSError as e:
|
|
1914
|
+
fail("could not read .env: %s" % e)
|
|
1915
|
+
pattern = re.compile(r"^\s*(?:export\s+)?INFERALL_API_KEY\s*=(.*)$")
|
|
1916
|
+
for line in text.splitlines():
|
|
1917
|
+
m = pattern.match(line)
|
|
1918
|
+
if m:
|
|
1919
|
+
value = m.group(1).strip()
|
|
1920
|
+
if value[:1] in ('"', "'"):
|
|
1921
|
+
close = value.find(value[0], 1)
|
|
1922
|
+
return value[1:close] if close != -1 else value
|
|
1923
|
+
return re.sub(r"\s+#.*$", "", value).strip()
|
|
1924
|
+
return ""
|
|
1925
|
+
|
|
1926
|
+
|
|
1927
|
+
key = read_key()
|
|
1928
|
+
if not key:
|
|
1929
|
+
fail("INFERALL_API_KEY is not set. Export it or put it in the .env next to this file.")
|
|
1930
|
+
|
|
1931
|
+
request = urllib.request.Request(
|
|
1932
|
+
API_BASE + "/chat/completions",
|
|
1933
|
+
data=json.dumps({
|
|
1934
|
+
"model": MODEL,
|
|
1935
|
+
"messages": [{"role": "user", "content": "Explain InferAll in one sentence."}],
|
|
1936
|
+
}).encode(),
|
|
1937
|
+
headers={
|
|
1938
|
+
"Authorization": "Bearer " + key,
|
|
1939
|
+
"Content-Type": "application/json",
|
|
1940
|
+
"User-Agent": "inferall-hello-example",
|
|
1941
|
+
},
|
|
1942
|
+
method="POST",
|
|
1943
|
+
)
|
|
1944
|
+
try:
|
|
1945
|
+
try:
|
|
1946
|
+
res = urllib.request.urlopen(request, timeout=120)
|
|
1947
|
+
except urllib.error.HTTPError as e:
|
|
1948
|
+
res = e
|
|
1949
|
+
with res:
|
|
1950
|
+
status, raw = res.status, res.read()
|
|
1951
|
+
except (OSError, http.client.HTTPException) as e:
|
|
1952
|
+
fail("request failed: %s" % e)
|
|
1953
|
+
|
|
1954
|
+
text = raw.decode("utf-8", "replace")
|
|
1955
|
+
try:
|
|
1956
|
+
data = json.loads(text)
|
|
1957
|
+
except ValueError:
|
|
1958
|
+
data = None
|
|
1959
|
+
if not isinstance(data, dict):
|
|
1960
|
+
data = {}
|
|
1961
|
+
|
|
1962
|
+
if not 200 <= status < 300:
|
|
1963
|
+
err = data.get("error")
|
|
1964
|
+
detail = err.get("message") if isinstance(err, dict) else err
|
|
1965
|
+
detail = detail or data.get("message") or text.strip()[:300] or "no response body"
|
|
1966
|
+
fail("HTTP %d: %s" % (status, detail))
|
|
1967
|
+
|
|
1968
|
+
try:
|
|
1969
|
+
answer = data["choices"][0]["message"]["content"]
|
|
1970
|
+
except (KeyError, IndexError, TypeError):
|
|
1971
|
+
answer = None
|
|
1972
|
+
if not isinstance(answer, str) or not answer.strip():
|
|
1973
|
+
fail("the gateway answered HTTP %d but the response had no answer text" % status)
|
|
1974
|
+
print(answer)
|
|
1975
|
+
`
|
|
1976
|
+
: String.raw`// InferAll hello. Run: node inferall-hello.mjs (Node 18+, no packages needed)
|
|
1977
|
+
import { readFileSync } from "node:fs";
|
|
1978
|
+
|
|
1979
|
+
const API_BASE = ${JSON.stringify(API_BASE)};
|
|
1980
|
+
const MODEL = ${JSON.stringify(FREE_MODEL)};
|
|
1981
|
+
|
|
1982
|
+
function fail(message) {
|
|
1983
|
+
console.error("inferall: " + message);
|
|
1984
|
+
process.exit(1);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
// INFERALL_API_KEY from the environment first, else the .env beside this file.
|
|
1988
|
+
function readKey() {
|
|
1989
|
+
const fromEnv = (process.env.INFERALL_API_KEY || "").trim();
|
|
1990
|
+
if (fromEnv) return fromEnv;
|
|
1991
|
+
let text;
|
|
1992
|
+
try {
|
|
1993
|
+
text = readFileSync(new URL(".env", import.meta.url), "utf8");
|
|
1994
|
+
} catch (err) {
|
|
1995
|
+
if (err.code === "ENOENT") return "";
|
|
1996
|
+
fail("could not read .env: " + err.message);
|
|
1997
|
+
}
|
|
1998
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1999
|
+
const m = line.match(/^\s*(?:export\s+)?INFERALL_API_KEY\s*=(.*)$/);
|
|
2000
|
+
if (m) {
|
|
2001
|
+
const value = m[1].trim();
|
|
2002
|
+
if (value[0] === '"' || value[0] === "'") {
|
|
2003
|
+
const close = value.indexOf(value[0], 1);
|
|
2004
|
+
return close === -1 ? value : value.slice(1, close);
|
|
2005
|
+
}
|
|
2006
|
+
return value.replace(/\s+#.*$/, "").trim();
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
return "";
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
const key = readKey();
|
|
2013
|
+
if (!key) fail("INFERALL_API_KEY is not set. Export it or put it in the .env next to this file.");
|
|
2014
|
+
|
|
2015
|
+
let res;
|
|
2016
|
+
let raw;
|
|
2017
|
+
try {
|
|
2018
|
+
res = await fetch(API_BASE + "/chat/completions", {
|
|
2019
|
+
method: "POST",
|
|
2020
|
+
headers: { Authorization: "Bearer " + key, "Content-Type": "application/json" },
|
|
2021
|
+
body: JSON.stringify({
|
|
2022
|
+
model: MODEL,
|
|
2023
|
+
messages: [{ role: "user", content: "Explain InferAll in one sentence." }],
|
|
2024
|
+
}),
|
|
2025
|
+
signal: AbortSignal.timeout(120_000),
|
|
2026
|
+
});
|
|
2027
|
+
raw = await res.text();
|
|
2028
|
+
} catch (err) {
|
|
2029
|
+
fail("request failed: " + (err.name === "TimeoutError" ? "timed out after 120s" : err.message));
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
let data;
|
|
2033
|
+
try {
|
|
2034
|
+
data = JSON.parse(raw);
|
|
2035
|
+
} catch {}
|
|
2036
|
+
|
|
2037
|
+
if (!res.ok) {
|
|
2038
|
+
const e = data?.error;
|
|
2039
|
+
const detail = (typeof e === "string" ? e : e?.message) || data?.message || raw.trim().slice(0, 300) || res.statusText;
|
|
2040
|
+
fail("HTTP " + res.status + ": " + detail);
|
|
2041
|
+
}
|
|
2042
|
+
|
|
2043
|
+
const answer = data?.choices?.[0]?.message?.content;
|
|
2044
|
+
if (typeof answer !== "string" || !answer.trim()) {
|
|
2045
|
+
fail("the gateway answered HTTP " + res.status + " but the response had no answer text");
|
|
2046
|
+
}
|
|
2047
|
+
console.log(answer);
|
|
2048
|
+
`;
|
|
1883
2049
|
try {
|
|
1884
2050
|
writeFileSync(p, body);
|
|
1885
2051
|
return { path: p, problem: null };
|
|
@@ -2858,6 +3024,10 @@ async function main() {
|
|
|
2858
3024
|
console.log(` ${c.red("✗")} ${example.problem}`);
|
|
2859
3025
|
envResult.problems.push(example.problem);
|
|
2860
3026
|
}
|
|
3027
|
+
if (example.path) {
|
|
3028
|
+
const run = project.kind === "node" ? "node inferall-hello.mjs" : "python3 inferall_hello.py";
|
|
3029
|
+
console.log(` ${c.dim("run:")} ${c.bold(run)} ${c.dim("(reads INFERALL_API_KEY from the environment or .env)")}`);
|
|
3030
|
+
}
|
|
2861
3031
|
if (project.kind === "node") console.log(` ${c.dim("optional:")} npm install @inferall/sdk`);
|
|
2862
3032
|
else console.log(` ${c.dim("optional:")} pip install inferall-ai`);
|
|
2863
3033
|
}
|