@runinfra/cli 0.1.0 → 0.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 CHANGED
@@ -3,9 +3,6 @@
3
3
  Sign in from your terminal and pull the optimized model packages your workspace
4
4
  owns.
5
5
 
6
- Node 20 or newer. **Zero runtime dependencies** by design, so it installs
7
- quickly and runs cleanly inside a slim container or a locked-down host.
8
-
9
6
  ```console
10
7
  $ npm install -g @runinfra/cli
11
8
  $ runinfra login
@@ -16,6 +13,53 @@ Replace `<slug>` with the slug shown on your package's page under
16
13
  **Optimized models** on runinfra.ai, for example
17
14
  `qwen3-6-27b-fp8cd-v3-mlponly-h100-vllm`.
18
15
 
16
+ ---
17
+
18
+ ## Install
19
+
20
+ <!-- Release note for the next editor: when the installer and the wheel are
21
+ actually published, the standalone line becomes the lead command in the
22
+ block above and its Status cell becomes "Live". Do not promote a row
23
+ before fetching its artifact and installing from it: the Status column
24
+ is the only thing standing between a reader and a command that 404s. -->
25
+
26
+ Run the npm line above if the machine already has Node 20 or newer. It is the
27
+ only channel published today.
28
+
29
+ Two more channels are on the way, because these kits get pulled onto GPU hosts
30
+ and a GPU host often has Python but no Node:
31
+
32
+ | Channel | Command | Reach for it when | Status |
33
+ | --- | --- | --- | --- |
34
+ | Standalone | `curl -fsSL https://raw.githubusercontent.com/RightNow-AI/runinfra-cli/main/install.sh \| sh` | The box is bare. No Node, no Python, the download brings its own runtime. | Not published yet |
35
+ | Python | `pip install runinfra-cli` | The machine already lives in Python. | Not published yet |
36
+ | Node | `npm install -g @runinfra/cli` | The machine already lives in Node 20 or newer. | **Live, 0.1.1** |
37
+
38
+ Only the npm row works right now. Checked on 2026-07-27:
39
+ `pip index versions runinfra-cli` answers "No matching distribution found",
40
+ and the installer URL returns 404 because the script is not on the release
41
+ repository's main branch yet. Both are written down with their real commands
42
+ rather than left out, so the release that publishes them only has to flip a
43
+ Status cell, and so a reader can tell today what works from what does not.
44
+
45
+ All three publish at the same version, **0.1.1**, and put the same `runinfra`
46
+ command on PATH: the two new channels repackage the client that is already on
47
+ npm rather than changing it. So `runinfra --version` reads the same however it
48
+ arrived, and every command in this document behaves identically. What differs
49
+ is only what has to be on the machine first:
50
+
51
+ - **Standalone and Python** install one compiled executable with the runtime
52
+ inside it. Nothing else is required, which is the whole point on a host you
53
+ did not build. Note the distribution name: `pip install runinfra-cli` is
54
+ this tool, `pip install runinfra` is the inference SDK, a different product
55
+ for calling an endpoint from your code.
56
+ - **npm** installs the same program as JavaScript and runs it on the Node you
57
+ already have. It has **zero runtime dependencies** by design, so it installs
58
+ quickly and runs cleanly inside a slim container or a locked-down host.
59
+
60
+ Rather not install anything globally? The npm channel also runs through npx,
61
+ for example `npx @runinfra/cli pull <slug>`.
62
+
19
63
  If your registry cannot find `@runinfra/cli`, the release has not reached it
20
64
  yet. Run the CLI from a checkout instead, see [Development](#development).
21
65
 
package/dist/endpoints.js CHANGED
@@ -29,7 +29,7 @@ export function resolveApiBase(env) {
29
29
  const base = `${url.origin}${url.pathname}`.replace(/\/+$/u, "");
30
30
  return { ok: true, base };
31
31
  }
32
- function isLoopbackHostname(hostname) {
32
+ export function isLoopbackHostname(hostname) {
33
33
  return (hostname === "localhost" ||
34
34
  hostname === "127.0.0.1" ||
35
35
  hostname === "[::1]" ||
package/dist/http.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { request as httpRequest } from "node:http";
2
2
  import { request as httpsRequest } from "node:https";
3
+ import { isLoopbackHostname } from "./endpoints.js";
3
4
  import { CliError } from "./errors.js";
5
+ import { CLI_LATEST_VERSION_HEADER, recordLatest } from "./update-notice.js";
4
6
  import { userAgent } from "./version.js";
5
7
  export const DEFAULT_IDLE_TIMEOUT_MS = 30_000;
6
8
  export const DEFAULT_MAX_BODY_BYTES = 2 * 1024 * 1024;
@@ -12,6 +14,7 @@ function normalizeHeaders(message) {
12
14
  continue;
13
15
  out[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
14
16
  }
17
+ recordLatest(out[CLI_LATEST_VERSION_HEADER]);
15
18
  return out;
16
19
  }
17
20
  function networkError(url, cause) {
@@ -24,11 +27,30 @@ function networkError(url, cause) {
24
27
  }
25
28
  return new CliError("network", `Could not reach ${host}: ${detail}`, "Check your connection or proxy settings, then run the command again.");
26
29
  }
30
+ const CREDENTIAL_HEADERS = new Set([
31
+ "authorization",
32
+ "cookie",
33
+ "proxy-authorization",
34
+ ]);
35
+ function headersForHop(requested, origin, originalOrigin) {
36
+ if (!requested || origin === originalOrigin)
37
+ return { ...(requested ?? {}) };
38
+ const carried = {};
39
+ for (const [name, value] of Object.entries(requested)) {
40
+ if (CREDENTIAL_HEADERS.has(name.toLowerCase()))
41
+ continue;
42
+ carried[name] = value;
43
+ }
44
+ return carried;
45
+ }
27
46
  async function dispatch(options) {
28
47
  const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
29
48
  let currentUrl = options.url;
30
49
  let method = options.method;
31
- let body = options.json === undefined ? null : Buffer.from(JSON.stringify(options.json), "utf8");
50
+ let body = options.json === undefined
51
+ ? null
52
+ : Buffer.from(JSON.stringify(options.json), "utf8");
53
+ const originalOrigin = new URL(options.url).origin;
32
54
  for (let hop = 0; hop <= maxRedirects; hop += 1) {
33
55
  const parsed = new URL(currentUrl);
34
56
  const secure = parsed.protocol === "https:";
@@ -36,7 +58,7 @@ async function dispatch(options) {
36
58
  const headers = {
37
59
  accept: "application/json",
38
60
  "user-agent": userAgent(),
39
- ...(options.headers ?? {}),
61
+ ...headersForHop(options.headers, parsed.origin, originalOrigin),
40
62
  };
41
63
  if (body) {
42
64
  headers["content-type"] = "application/json";
@@ -64,7 +86,11 @@ async function dispatch(options) {
64
86
  });
65
87
  const status = message.statusCode ?? 0;
66
88
  const location = message.headers.location;
67
- const isRedirect = (status === 301 || status === 302 || status === 303 || status === 307 || status === 308) &&
89
+ const isRedirect = (status === 301 ||
90
+ status === 302 ||
91
+ status === 303 ||
92
+ status === 307 ||
93
+ status === 308) &&
68
94
  typeof location === "string";
69
95
  if (!isRedirect) {
70
96
  return { message, finalUrl: currentUrl };
@@ -74,7 +100,11 @@ async function dispatch(options) {
74
100
  if (parsed.protocol === "https:" && next.protocol !== "https:") {
75
101
  throw new CliError("network", `${parsed.host} redirected to an insecure URL (${next.protocol}//${next.host}).`, "This is not something the CLI can safely follow. Report it to support.");
76
102
  }
77
- if (status === 303 || ((status === 301 || status === 302) && method !== "HEAD")) {
103
+ if (next.protocol === "http:" && !isLoopbackHostname(next.hostname)) {
104
+ throw new CliError("network", `${parsed.host} redirected to a plaintext URL off this machine (${next.protocol}//${next.host}).`, "This is not something the CLI can safely follow. Report it to support.");
105
+ }
106
+ if (status === 303 ||
107
+ ((status === 301 || status === 302) && method !== "HEAD")) {
78
108
  method = "GET";
79
109
  body = null;
80
110
  }
@@ -101,8 +131,13 @@ async function bufferBody(message, url, maxBytes) {
101
131
  }
102
132
  export async function requestRaw(options) {
103
133
  const { message, finalUrl } = await dispatch(options);
134
+ const headers = normalizeHeaders(message);
104
135
  const body = await bufferBody(message, finalUrl, options.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES);
105
- return { status: message.statusCode ?? 0, headers: normalizeHeaders(message), body };
136
+ return {
137
+ status: message.statusCode ?? 0,
138
+ headers,
139
+ body,
140
+ };
106
141
  }
107
142
  export async function requestJson(options) {
108
143
  const raw = await requestRaw(options);
@@ -124,18 +159,25 @@ export async function headResource(url, options = {}) {
124
159
  const { message } = await dispatch({
125
160
  method: "HEAD",
126
161
  url,
127
- ...(options.idleTimeoutMs === undefined ? {} : { idleTimeoutMs: options.idleTimeoutMs }),
162
+ ...(options.idleTimeoutMs === undefined
163
+ ? {}
164
+ : { idleTimeoutMs: options.idleTimeoutMs }),
128
165
  ...(options.signal ? { signal: options.signal } : {}),
129
166
  });
130
167
  message.resume();
131
- return { status: message.statusCode ?? 0, headers: normalizeHeaders(message) };
168
+ return {
169
+ status: message.statusCode ?? 0,
170
+ headers: normalizeHeaders(message),
171
+ };
132
172
  }
133
173
  export async function streamRange(request) {
134
174
  const { message, finalUrl } = await dispatch({
135
175
  method: "GET",
136
176
  url: request.url,
137
177
  headers: { range: `bytes=${request.start}-${request.endInclusive}` },
138
- ...(request.idleTimeoutMs === undefined ? {} : { idleTimeoutMs: request.idleTimeoutMs }),
178
+ ...(request.idleTimeoutMs === undefined
179
+ ? {}
180
+ : { idleTimeoutMs: request.idleTimeoutMs }),
139
181
  ...(request.signal ? { signal: request.signal } : {}),
140
182
  });
141
183
  const status = message.statusCode ?? 0;
@@ -151,7 +193,9 @@ export async function streamWhole(request) {
151
193
  const { message, finalUrl } = await dispatch({
152
194
  method: "GET",
153
195
  url: request.url,
154
- ...(request.idleTimeoutMs === undefined ? {} : { idleTimeoutMs: request.idleTimeoutMs }),
196
+ ...(request.idleTimeoutMs === undefined
197
+ ? {}
198
+ : { idleTimeoutMs: request.idleTimeoutMs }),
155
199
  ...(request.signal ? { signal: request.signal } : {}),
156
200
  });
157
201
  const status = message.statusCode ?? 0;
package/dist/main.js CHANGED
@@ -6,7 +6,8 @@ import { login } from "./login.js";
6
6
  import { logout } from "./logout.js";
7
7
  import { createOutput } from "./output.js";
8
8
  import { pull } from "./pull.js";
9
- import { CLI_NAME, CLI_VERSION, isSupportedNodeVersion, MINIMUM_NODE_MAJOR } from "./version.js";
9
+ import { maybePrintUpdateNotice } from "./update-notice.js";
10
+ import { CLI_NAME, CLI_VERSION, isSupportedNodeVersion, MINIMUM_NODE_MAJOR, } from "./version.js";
10
11
  import { whoami } from "./whoami.js";
11
12
  export async function run(argv, options = {}) {
12
13
  const output = options.output ?? createOutput();
@@ -35,22 +36,32 @@ export async function run(argv, options = {}) {
35
36
  ...(options.env ? { env: options.env } : {}),
36
37
  ...(options.platform ? { platform: options.platform } : {}),
37
38
  });
39
+ let exitCode;
38
40
  switch (parsed.args.command) {
39
41
  case "login":
40
- return await login(context, { device: parsed.args.device });
42
+ exitCode = await login(context, { device: parsed.args.device });
43
+ break;
41
44
  case "logout":
42
- return await logout(context);
45
+ exitCode = await logout(context);
46
+ break;
43
47
  case "whoami":
44
- return await whoami(context);
48
+ exitCode = await whoami(context);
49
+ break;
45
50
  case "pull": {
46
51
  const slug = parsed.args.slug;
47
52
  if (slug === null) {
48
53
  output.error("runinfra pull needs a package slug.");
49
54
  return 2;
50
55
  }
51
- return await pull(context, { slug, out: parsed.args.out, concurrency: parsed.args.concurrency }, signal);
56
+ exitCode = await pull(context, { slug, out: parsed.args.out, concurrency: parsed.args.concurrency }, signal);
57
+ break;
52
58
  }
53
59
  }
60
+ await maybePrintUpdateNotice(output, {
61
+ env: options.env ?? process.env,
62
+ platform: options.platform ?? process.platform,
63
+ });
64
+ return exitCode;
54
65
  }
55
66
  catch (error) {
56
67
  output.endStatus();
@@ -61,7 +72,12 @@ export async function run(argv, options = {}) {
61
72
  if (described.code === "unexpected") {
62
73
  output.detail("This is a bug in the CLI. Report it with the command you ran and this message.");
63
74
  }
64
- return exitCodeFor(error);
75
+ const exitCode = exitCodeFor(error);
76
+ await maybePrintUpdateNotice(output, {
77
+ env: options.env ?? process.env,
78
+ platform: options.platform ?? process.platform,
79
+ });
80
+ return exitCode;
65
81
  }
66
82
  }
67
83
  function neverAborted() {
package/dist/pull.js CHANGED
@@ -37,7 +37,25 @@ export async function pull(context, options, signal) {
37
37
  const existing = await statOrNull(finalPath);
38
38
  if (existing !== null) {
39
39
  if (existing.size === artifact.sizeBytes) {
40
- output.info(`${fileName} is already downloaded.`);
40
+ if (lease.checksumSha256) {
41
+ let lastTickMs = 0;
42
+ const actual = await sha256File(finalPath, (hashed) => {
43
+ const nowMs = Date.now();
44
+ if (nowMs - lastTickMs < 500)
45
+ return;
46
+ lastTickMs = nowMs;
47
+ const percent = artifact.sizeBytes > 0 ? (hashed / artifact.sizeBytes) * 100 : 0;
48
+ output.status(` Checking the file already here ${percent.toFixed(1)}%`);
49
+ }, signal);
50
+ output.endStatus();
51
+ if (judgeChecksum(lease.checksumSha256, actual) === "mismatch") {
52
+ throw new CliError("checksum_mismatch", `${finalPath} is the published size but does not match the published checksum.`, "Move or delete that file, then run the same command again to download it fresh.");
53
+ }
54
+ output.info(`${fileName} is already downloaded, and its checksum matches.`);
55
+ }
56
+ else {
57
+ output.info(`${fileName} is already downloaded. UNVERIFIED: this package publishes no checksum.`);
58
+ }
41
59
  output.result(finalPath);
42
60
  return 0;
43
61
  }
@@ -0,0 +1,216 @@
1
+ import { chmod, mkdir, open, readFile, rename, rm, writeFile, } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { credentialLocation } from "./paths.js";
5
+ import { CLI_VERSION } from "./version.js";
6
+ export const CLI_LATEST_VERSION_HEADER = "x-runinfra-cli-latest";
7
+ export const UPDATE_CHECK_FILE_NAME = "update-check.json";
8
+ const NOTICE_INTERVAL_MS = 24 * 60 * 60 * 1_000;
9
+ const PYPI_CHANNEL = "pypi";
10
+ const POSIX_INSTALLER = "curl -fsSL https://raw.githubusercontent.com/RightNow-AI/runinfra-cli/main/install.sh | sh";
11
+ const WINDOWS_INSTALLER = "irm https://raw.githubusercontent.com/RightNow-AI/runinfra-cli/main/install.ps1 | iex";
12
+ let latestSeen = null;
13
+ function parseVersion(version) {
14
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u.exec(version);
15
+ if (!match)
16
+ return null;
17
+ const core = [match[1], match[2], match[3]].map((part) => Number.parseInt(part ?? "", 10));
18
+ if (core.some((part) => !Number.isSafeInteger(part) || part < 0))
19
+ return null;
20
+ const rawPrerelease = match[4];
21
+ const prerelease = rawPrerelease?.split(".") ?? null;
22
+ if (prerelease?.some((part) => part.length === 0 ||
23
+ !/^[0-9A-Za-z-]+$/u.test(part) ||
24
+ (/^\d+$/u.test(part) && part.length > 1 && part.startsWith("0")))) {
25
+ return null;
26
+ }
27
+ return {
28
+ core: [core[0] ?? 0, core[1] ?? 0, core[2] ?? 0],
29
+ prerelease,
30
+ };
31
+ }
32
+ function comparePrerelease(a, b) {
33
+ if (a === null && b === null)
34
+ return 0;
35
+ if (a === null)
36
+ return 1;
37
+ if (b === null)
38
+ return -1;
39
+ const length = Math.max(a.length, b.length);
40
+ for (let index = 0; index < length; index += 1) {
41
+ const aPart = a[index];
42
+ const bPart = b[index];
43
+ if (aPart === undefined)
44
+ return -1;
45
+ if (bPart === undefined)
46
+ return 1;
47
+ if (aPart === bPart)
48
+ continue;
49
+ const aNumeric = /^\d+$/u.test(aPart);
50
+ const bNumeric = /^\d+$/u.test(bPart);
51
+ if (aNumeric && bNumeric) {
52
+ if (aPart.length !== bPart.length) {
53
+ return aPart.length < bPart.length ? -1 : 1;
54
+ }
55
+ return aPart < bPart ? -1 : 1;
56
+ }
57
+ if (aNumeric !== bNumeric)
58
+ return aNumeric ? -1 : 1;
59
+ return aPart < bPart ? -1 : 1;
60
+ }
61
+ return 0;
62
+ }
63
+ export function compareVersions(a, b) {
64
+ const parsedA = parseVersion(a);
65
+ const parsedB = parseVersion(b);
66
+ if (!parsedA || !parsedB)
67
+ return 0;
68
+ for (let index = 0; index < parsedA.core.length; index += 1) {
69
+ const aPart = parsedA.core[index] ?? 0;
70
+ const bPart = parsedB.core[index] ?? 0;
71
+ if (aPart === bPart)
72
+ continue;
73
+ return aPart < bPart ? -1 : 1;
74
+ }
75
+ return comparePrerelease(parsedA.prerelease, parsedB.prerelease);
76
+ }
77
+ export function recordLatest(version) {
78
+ if (version === undefined || parseVersion(version) === null)
79
+ return;
80
+ if (latestSeen === null || compareVersions(version, latestSeen) === 1) {
81
+ latestSeen = version;
82
+ }
83
+ }
84
+ export function __resetLatestForTest() {
85
+ latestSeen = null;
86
+ }
87
+ function isRecord(value) {
88
+ return typeof value === "object" && value !== null && !Array.isArray(value);
89
+ }
90
+ function parseState(raw) {
91
+ let value;
92
+ try {
93
+ value = JSON.parse(raw);
94
+ }
95
+ catch {
96
+ return null;
97
+ }
98
+ if (!isRecord(value))
99
+ return null;
100
+ if (typeof value.version !== "string" ||
101
+ parseVersion(value.version) === null) {
102
+ return null;
103
+ }
104
+ if (typeof value.lastNotifiedAt !== "number" ||
105
+ !Number.isSafeInteger(value.lastNotifiedAt) ||
106
+ value.lastNotifiedAt < 0) {
107
+ return null;
108
+ }
109
+ return { version: value.version, lastNotifiedAt: value.lastNotifiedAt };
110
+ }
111
+ async function readState(file) {
112
+ let raw;
113
+ try {
114
+ raw = await readFile(file, "utf8");
115
+ }
116
+ catch (error) {
117
+ if (error.code === "ENOENT") {
118
+ return { kind: "missing" };
119
+ }
120
+ throw error;
121
+ }
122
+ const state = parseState(raw);
123
+ return state ? { kind: "valid", state } : { kind: "invalid" };
124
+ }
125
+ function shouldNotify(latest, state, nowMs) {
126
+ if (compareVersions(latest, CLI_VERSION) !== 1)
127
+ return false;
128
+ if (state.kind === "invalid")
129
+ return false;
130
+ if (state.kind === "missing")
131
+ return true;
132
+ const alreadyNotified = compareVersions(state.state.version, latest);
133
+ if (alreadyNotified === 1)
134
+ return false;
135
+ if (alreadyNotified === -1)
136
+ return true;
137
+ return nowMs - state.state.lastNotifiedAt >= NOTICE_INTERVAL_MS;
138
+ }
139
+ function updateMessage(latest, env, platform, versions) {
140
+ if (env.RUNINFRA_DIST_CHANNEL === PYPI_CHANNEL) {
141
+ return `RunInfra CLI ${latest} is available. Upgrade: pip install --upgrade runinfra-cli`;
142
+ }
143
+ if (versions.bun !== undefined) {
144
+ const installer = platform === "win32" ? WINDOWS_INSTALLER : POSIX_INSTALLER;
145
+ return `RunInfra CLI ${latest} is available. Re-run the RunInfra standalone installer: ${installer}`;
146
+ }
147
+ return `RunInfra CLI ${latest} is available. Upgrade: npm install -g @runinfra/cli@latest`;
148
+ }
149
+ async function persistState(file, temp, state, platform) {
150
+ try {
151
+ await writeFile(temp, `${JSON.stringify(state, null, 2)}\n`, {
152
+ encoding: "utf8",
153
+ mode: 0o600,
154
+ });
155
+ if (platform !== "win32")
156
+ await chmod(temp, 0o600);
157
+ await rename(temp, file);
158
+ if (platform !== "win32")
159
+ await chmod(file, 0o600);
160
+ }
161
+ catch (error) {
162
+ await rm(temp, { force: true }).catch(() => undefined);
163
+ throw error;
164
+ }
165
+ }
166
+ async function releaseLock(lock, lockFile) {
167
+ if (lock === null)
168
+ return;
169
+ await lock.close().catch(() => undefined);
170
+ await rm(lockFile, { force: true }).catch(() => undefined);
171
+ }
172
+ async function printUpdateNotice(output, deps) {
173
+ const latest = latestSeen;
174
+ if (latest === null || compareVersions(latest, CLI_VERSION) !== 1)
175
+ return;
176
+ const env = deps.env ?? process.env;
177
+ const platform = deps.platform ?? process.platform;
178
+ const location = credentialLocation({
179
+ platform,
180
+ env,
181
+ homeDir: deps.homeDir ?? homedir(),
182
+ });
183
+ const file = join(location.dir, UPDATE_CHECK_FILE_NAME);
184
+ const temp = `${file}.${process.pid}.tmp`;
185
+ const lockFile = `${file}.lock`;
186
+ const nowMs = (deps.now ?? Date.now)();
187
+ if (!Number.isSafeInteger(nowMs) || nowMs < 0)
188
+ return;
189
+ await mkdir(location.dir, {
190
+ recursive: true,
191
+ ...(platform === "win32" ? {} : { mode: 0o700 }),
192
+ });
193
+ if (platform !== "win32")
194
+ await chmod(location.dir, 0o700);
195
+ let lock = null;
196
+ try {
197
+ lock = await open(lockFile, "wx", 0o600);
198
+ if (platform !== "win32")
199
+ await chmod(lockFile, 0o600);
200
+ const state = await readState(file);
201
+ if (!shouldNotify(latest, state, nowMs))
202
+ return;
203
+ await persistState(file, temp, { version: latest, lastNotifiedAt: nowMs }, platform);
204
+ output.info(updateMessage(latest, env, platform, deps.versions ?? process.versions));
205
+ }
206
+ finally {
207
+ await releaseLock(lock, lockFile);
208
+ }
209
+ }
210
+ export async function maybePrintUpdateNotice(output, deps = {}) {
211
+ try {
212
+ await printUpdateNotice(output, deps);
213
+ }
214
+ catch {
215
+ }
216
+ }
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export const CLI_NAME = "runinfra";
2
2
  export const CLI_PACKAGE = "@runinfra/cli";
3
- export const CLI_VERSION = "0.1.0";
3
+ export const CLI_VERSION = "0.2.0";
4
4
  export const CLI_CLIENT_ID = "runinfra-cli";
5
5
  export const MINIMUM_NODE_MAJOR = 20;
6
6
  export function isSupportedNodeVersion(version) {
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@runinfra/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "RunInfra CLI: browser-approved sign-in and resumable downloads for optimized model packages",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
- "homepage": "https://runinfra.ai/optimized-models",
6
+ "homepage": "https://runinfra.ai/catalog",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/RightNow-AI/runinfra-cli.git"
10
+ },
7
11
  "bugs": {
8
12
  "url": "https://runinfra.ai/contact"
9
13
  },