@baryonlabs/cli 0.2.1 → 0.3.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/bin/baryon.js CHANGED
@@ -14,10 +14,24 @@ import {
14
14
  } from "../src/commands.js";
15
15
  import { loadConfig, piProviderConfigured, hasConfig } from "../src/config.js";
16
16
  import { runPi, resolvePiEntry } from "../src/pi.js";
17
+ import { checkLatest } from "../src/api.js";
17
18
  import { spawnSync } from "node:child_process";
18
19
  import { createRequire } from "node:module";
19
20
  import { c, err, log, sym } from "../src/ui.js";
20
21
 
22
+ /** Best-effort: warn loudly when a newer CLI exists. The gateway enforces the
23
+ * minimum version (426), so cloud use is blocked until you update; this is the
24
+ * friendly heads-up. Silent when offline / opted out. */
25
+ async function warnIfOutdated() {
26
+ const r = await checkLatest();
27
+ if (r?.outdated) {
28
+ log(
29
+ `\n ${sym.warn} ${c.yellow(`업데이트 필요: @baryonlabs/cli ${r.current} → ${r.latest}`)}\n` +
30
+ ` ${c.dim("baryon.ai 사용에 최신 버전이 필요합니다.")} ${c.lime("baryon update")} ${c.dim("를 실행하세요.\n")}`,
31
+ );
32
+ }
33
+ }
34
+
21
35
  const require = createRequire(import.meta.url);
22
36
 
23
37
  function showVersion() {
@@ -87,6 +101,7 @@ async function main() {
87
101
  );
88
102
  return 1;
89
103
  }
104
+ await warnIfOutdated();
90
105
  const cfg = loadConfig();
91
106
  return runPi(argv, cfg);
92
107
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baryonlabs/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Baryon CLI — AI 코딩·학습 에이전트. baryon.ai API에 기본 연결된 pi 코딩 에이전트 래퍼. 한 줄 설치, 상용·로컬 모델 전환.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -1,10 +1,51 @@
1
1
  // Minimal baryon.ai (OpenAI-compatible) helpers: model discovery + reachability.
2
- import { DEFAULT_MODELS } from "./constants.js";
2
+ import { CLIENT_VERSION, DEFAULT_MODELS } from "./constants.js";
3
3
 
4
4
  function joinUrl(base, suffix) {
5
5
  return base.replace(/\/+$/, "") + suffix;
6
6
  }
7
7
 
8
+ /** Compare two semver strings. -1 / 0 / 1, prerelease-insensitive. */
9
+ function cmpSemver(a, b) {
10
+ const pa = String(a).split("-")[0].split(".").map(Number);
11
+ const pb = String(b).split("-")[0].split(".").map(Number);
12
+ for (let i = 0; i < 3; i++) {
13
+ const x = pa[i] || 0;
14
+ const y = pb[i] || 0;
15
+ if (x !== y) return x < y ? -1 : 1;
16
+ }
17
+ return 0;
18
+ }
19
+
20
+ /**
21
+ * Best-effort latest-version check against the npm registry. Returns
22
+ * { current, latest, outdated } or null on any failure (offline / 폐쇄망 /
23
+ * timeout / opt-out via BARYON_SKIP_UPDATE_CHECK). Never throws.
24
+ */
25
+ export async function checkLatest({ timeoutMs = 2500 } = {}) {
26
+ if (process.env.BARYON_SKIP_UPDATE_CHECK) return null;
27
+ const ctrl = new AbortController();
28
+ const t = setTimeout(() => ctrl.abort(), timeoutMs);
29
+ try {
30
+ const res = await fetch(
31
+ "https://registry.npmjs.org/@baryonlabs/cli/latest",
32
+ { signal: ctrl.signal, headers: { accept: "application/json" } },
33
+ );
34
+ if (!res.ok) return null;
35
+ const latest = (await res.json())?.version;
36
+ if (!latest) return null;
37
+ return {
38
+ current: CLIENT_VERSION,
39
+ latest,
40
+ outdated: cmpSemver(CLIENT_VERSION, latest) < 0,
41
+ };
42
+ } catch {
43
+ return null;
44
+ } finally {
45
+ clearTimeout(t);
46
+ }
47
+ }
48
+
8
49
  /**
9
50
  * Fetch the institution's model catalog from `${baseUrl}/models`.
10
51
  * Returns pi-shaped model entries, or null when discovery fails
package/src/commands.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Built-in baryon subcommands. Anything not matched here is passed to pi.
2
2
  import { spawn } from "node:child_process";
3
- import { discoverModels, ping } from "./api.js";
3
+ import { checkLatest, discoverModels, ping } from "./api.js";
4
4
  import {
5
5
  hasConfig,
6
6
  loadConfig,
@@ -124,6 +124,15 @@ export async function doctor() {
124
124
  if (cfg.apiKey) ok(`API 키 설정됨 (${cfg.apiKey.slice(0, 4)}${"•".repeat(6)})`);
125
125
  else warn("API 키 없음");
126
126
 
127
+ // CLI version currency (best-effort; silent offline)
128
+ const ver = await checkLatest();
129
+ if (ver?.outdated) {
130
+ warn(`CLI 구버전 ${ver.current} — 최신 ${ver.latest}. baryon.ai 사용에 업데이트 필요 (\`baryon update\`)`);
131
+ problems++;
132
+ } else if (ver) {
133
+ ok(`CLI 최신 버전 (${ver.current})`);
134
+ }
135
+
127
136
  // pi provider registered?
128
137
  if (piProviderConfigured()) ok(`pi 프로바이더 ${c.lime(PROVIDER)} 등록됨 → ${c.dim(PI_MODELS_JSON)}`);
129
138
  else {
package/src/config.js CHANGED
@@ -11,8 +11,18 @@ import {
11
11
  PI_AGENT_DIR,
12
12
  PI_MODELS_JSON,
13
13
  PROVIDER,
14
+ SESSION_HEADER,
15
+ SESSION_ID_ENV,
16
+ CLIENT_HEADER,
17
+ CLIENT_ENV,
14
18
  } from "./constants.js";
15
19
 
20
+ /** Headers the baryon provider must always send (resolved by pi from env). */
21
+ const PROVIDER_HEADERS = {
22
+ [SESSION_HEADER]: `$${SESSION_ID_ENV}`,
23
+ [CLIENT_HEADER]: `$${CLIENT_ENV}`,
24
+ };
25
+
16
26
  function readJson(file, fallback) {
17
27
  try {
18
28
  return JSON.parse(fs.readFileSync(file, "utf8"));
@@ -69,6 +79,10 @@ export function syncPiModels({ baseUrl, models }) {
69
79
  api: "openai-completions",
70
80
  apiKey: `$${API_KEY_ENV}`,
71
81
  authHeader: true,
82
+ // Per-launch session id + client version (resolved by pi from env at request
83
+ // time): the gateway groups a run's turns into one session and enforces a
84
+ // minimum CLI version. Both are required by the gateway.
85
+ headers: { ...PROVIDER_HEADERS },
72
86
  models:
73
87
  Array.isArray(models) && models.length ? models : DEFAULT_MODELS,
74
88
  };
@@ -83,4 +97,22 @@ export function piProviderConfigured() {
83
97
  return Boolean(root?.providers?.[PROVIDER]);
84
98
  }
85
99
 
100
+ /**
101
+ * Auto-heal older installs: ensure the baryon provider sends the session +
102
+ * client-version headers. Safe to call on every launch — only writes when
103
+ * something changed. Returns true if the provider exists (after any patch).
104
+ */
105
+ export function ensurePiSessionHeader() {
106
+ const root = readJson(PI_MODELS_JSON, {});
107
+ const p = root?.providers?.[PROVIDER];
108
+ if (!p) return false;
109
+ const need = Object.entries(PROVIDER_HEADERS).some(
110
+ ([k, v]) => p.headers?.[k] !== v,
111
+ );
112
+ if (!need) return true;
113
+ p.headers = { ...(p.headers || {}), ...PROVIDER_HEADERS };
114
+ writeJson(PI_MODELS_JSON, root);
115
+ return true;
116
+ }
117
+
86
118
  export { PI_MODELS_JSON, BARYON_CONFIG };
package/src/constants.js CHANGED
@@ -1,5 +1,16 @@
1
1
  import os from "node:os";
2
2
  import path from "node:path";
3
+ import { createRequire } from "node:module";
4
+
5
+ /** This CLI's version (from package.json). Sent to the gateway for the
6
+ * minimum-version gate, so old clients are forced to update. */
7
+ export const CLIENT_VERSION = (() => {
8
+ try {
9
+ return createRequire(import.meta.url)("../package.json").version;
10
+ } catch {
11
+ return "0.0.0";
12
+ }
13
+ })();
3
14
 
4
15
  /** Provider id registered inside pi's models.json */
5
16
  export const PROVIDER = "baryon";
@@ -11,6 +22,22 @@ export const DEFAULT_BASE_URL =
11
22
  /** Env var pi resolves at request time (apiKey: "$BARYON_API_KEY"). */
12
23
  export const API_KEY_ENV = "BARYON_API_KEY";
13
24
 
25
+ /**
26
+ * Session id env var. The baryon provider sends it as `X-Baryon-Session` so the
27
+ * gateway can group every turn of one `baryon` run into a single session. A
28
+ * fresh id is minted per launch (see pi.js). The gateway requires it.
29
+ */
30
+ export const SESSION_ID_ENV = "BARYON_SESSION_ID";
31
+ export const SESSION_HEADER = "X-Baryon-Session";
32
+
33
+ /**
34
+ * Client-identity header. The baryon provider sends `baryon-cli/<version>` so
35
+ * the gateway can enforce a minimum CLI version (BARYON_MIN_CLI_VERSION). The
36
+ * value is resolved per launch from BARYON_CLIENT (see pi.js).
37
+ */
38
+ export const CLIENT_ENV = "BARYON_CLIENT";
39
+ export const CLIENT_HEADER = "X-Baryon-Client";
40
+
14
41
  /** Underlying coding agent package + binary. */
15
42
  export const PI_PACKAGE = "@earendil-works/pi-coding-agent";
16
43
  export const PI_BIN = "pi";
package/src/pi.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { spawn } from "node:child_process";
4
4
  import { createRequire } from "node:module";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { randomUUID } from "node:crypto";
6
7
  import fs from "node:fs";
7
8
  import path from "node:path";
8
9
  import {
@@ -10,7 +11,11 @@ import {
10
11
  PI_BIN,
11
12
  PI_PACKAGE,
12
13
  PROVIDER,
14
+ SESSION_ID_ENV,
15
+ CLIENT_ENV,
16
+ CLIENT_VERSION,
13
17
  } from "./constants.js";
18
+ import { ensurePiSessionHeader } from "./config.js";
14
19
 
15
20
  const require = createRequire(import.meta.url);
16
21
 
@@ -98,6 +103,13 @@ export function runPi(args, config, { injectTargeting = true } = {}) {
98
103
  if (config.apiKey) env[API_KEY_ENV] = config.apiKey;
99
104
  if (config.baseUrl) env.BARYON_BASE_URL = config.baseUrl;
100
105
 
106
+ // One session per launch: mint an id (unless the caller pinned one) and make
107
+ // sure the provider forwards it + this CLI's version. The gateway requires a
108
+ // session id and enforces a minimum CLI version.
109
+ if (!env[SESSION_ID_ENV]) env[SESSION_ID_ENV] = `cli_${randomUUID()}`;
110
+ env[CLIENT_ENV] = `baryon-cli/${CLIENT_VERSION}`;
111
+ ensurePiSessionHeader();
112
+
101
113
  return new Promise((resolve, reject) => {
102
114
  const child = spawn(process.execPath, [entry, ...finalArgs], {
103
115
  stdio: "inherit",