@baryonlabs/cli 0.1.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/LICENSE +24 -0
- package/README.md +84 -0
- package/bin/baryon.js +93 -0
- package/package.json +54 -0
- package/src/api.js +61 -0
- package/src/commands.js +211 -0
- package/src/config.js +86 -0
- package/src/constants.js +51 -0
- package/src/index.js +44 -0
- package/src/pi.js +115 -0
- package/src/ui.js +96 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Baryon CLI — Proprietary License
|
|
2
|
+
Copyright (c) 2026 Baryon Labs. All rights reserved.
|
|
3
|
+
|
|
4
|
+
This package (@baryonlabs/cli) is distributed by Baryon Labs for use with the
|
|
5
|
+
baryon.ai platform and the bundled pi coding agent.
|
|
6
|
+
|
|
7
|
+
Permission is granted to install and use this package, in source or binary
|
|
8
|
+
form, solely to access Baryon Labs services and authorized model providers.
|
|
9
|
+
|
|
10
|
+
You may NOT, without prior written permission from Baryon Labs:
|
|
11
|
+
- redistribute, sublicense, sell, or rent this package;
|
|
12
|
+
- modify, fork, reverse engineer, or create derivative works for
|
|
13
|
+
redistribution;
|
|
14
|
+
- remove or alter this notice or any Baryon Labs branding.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
|
21
|
+
OR OTHER DEALINGS IN THE SOFTWARE.
|
|
22
|
+
|
|
23
|
+
The bundled @earendil-works/pi-coding-agent is licensed separately by its
|
|
24
|
+
respective authors. Contact: support@baryon.ai
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# @baryonlabs/cli
|
|
2
|
+
|
|
3
|
+
> Baryon CLI — **AI 코딩·학습 에이전트**. `baryon.ai` API에 기본 연결된 [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) 코딩 에이전트 래퍼.
|
|
4
|
+
|
|
5
|
+
한 줄 설치, 별도 키 설정 없이 시작, 상용·로컬 모델 무중단 전환. 교육기관 납품용으로 설계되었습니다.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @baryonlabs/cli # 또는 pnpm / yarn / bun
|
|
9
|
+
baryon setup # baryon.ai API 키 등록 (1회)
|
|
10
|
+
baryon # 코딩 에이전트 시작
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`curl` 한 줄 설치:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
curl -fsSL https://cli.baryon.ai/install.sh | sh
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 무엇을 하나요
|
|
20
|
+
|
|
21
|
+
`@baryonlabs/cli` 는 pi 코딩 에이전트를 **baryon.ai (OpenAI 호환) API** 에 연결합니다. `baryon setup` 은:
|
|
22
|
+
|
|
23
|
+
1. `~/.baryon/config.json` 에 API 키·엔드포인트를 저장하고 (권한 `600`),
|
|
24
|
+
2. `${BARYON_BASE_URL}/models` 로 모델 목록을 조회해 (오프라인이면 기본값 사용),
|
|
25
|
+
3. pi 의 `~/.pi/agent/models.json` 에 `baryon` 프로바이더를 **다른 프로바이더를 보존하며** 병합합니다.
|
|
26
|
+
|
|
27
|
+
이후 `baryon` 은 `--provider baryon` 을 자동 주입해 pi 를 실행합니다. `--provider`/`--model`/`--api-key` 를 직접 주면 그 선택을 그대로 따르므로 **모델 비교**가 자유롭습니다.
|
|
28
|
+
|
|
29
|
+
## 명령어
|
|
30
|
+
|
|
31
|
+
| 명령 | 설명 |
|
|
32
|
+
|------|------|
|
|
33
|
+
| `baryon` | 대화형 코딩 에이전트 시작 (baryon.ai 기본) |
|
|
34
|
+
| `baryon setup` | API 키 등록 + pi 프로바이더 구성 |
|
|
35
|
+
| `baryon config` | 현재 설정 보기 (`--key`, `--base-url`, `--model` 로 변경) |
|
|
36
|
+
| `baryon models` | 사용 가능한 모델 목록 (`pi --list-models` 패스스루) |
|
|
37
|
+
| `baryon doctor` | 설치·연결 진단 |
|
|
38
|
+
| `baryon update` | CLI + pi 에이전트 업데이트 |
|
|
39
|
+
| `baryon help` | 도움말 |
|
|
40
|
+
|
|
41
|
+
그 외 모든 인자(`-p`, `@file`, `--mode json`, `--continue` 등)는 pi 로 그대로 전달됩니다.
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
baryon -p "이 CSV를 분석해 차트를 만들어줘" # 단발 실행
|
|
45
|
+
baryon @report.md "요약해줘" # 파일 첨부
|
|
46
|
+
baryon --provider openai # 다른 모델로 전환·비교
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 설정
|
|
50
|
+
|
|
51
|
+
| 항목 | 기본값 | 재정의 |
|
|
52
|
+
|------|--------|--------|
|
|
53
|
+
| base URL | `https://api.baryon.ai/v1` | `BARYON_BASE_URL` 환경변수 또는 `baryon config --base-url` |
|
|
54
|
+
| API key | — | `BARYON_API_KEY` 환경변수 또는 `baryon setup` |
|
|
55
|
+
| 기본 모델 | 조회된 첫 모델 | `baryon config --model <id>` |
|
|
56
|
+
|
|
57
|
+
> 정확한 엔드포인트·모델 ID 는 기관의 baryon.ai 플랜에 따릅니다.
|
|
58
|
+
|
|
59
|
+
## 오프라인 / 폐쇄망
|
|
60
|
+
|
|
61
|
+
인터넷 없이도 동작합니다. 로컬 LLM(Ollama·LM Studio·vLLM)을 쓰려면:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
baryon config --base-url http://localhost:11434/v1 --key ollama
|
|
65
|
+
baryon setup --base-url http://localhost:11434/v1
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 프로그래밍 사용
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { configure, run } from "@baryonlabs/cli";
|
|
72
|
+
|
|
73
|
+
await configure({ apiKey: process.env.BARYON_API_KEY });
|
|
74
|
+
const code = await run(["-p", "Summarize README.md"]);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## 요구사항
|
|
78
|
+
|
|
79
|
+
- Node.js ≥ 22.19
|
|
80
|
+
- Docker 불필요, 빌드 과정 없음
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
문서: <https://cli.baryon.ai> · 문의: support@baryon.ai · © Baryon Labs
|
package/bin/baryon.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Baryon CLI entry. Built-in subcommands are handled locally; everything else
|
|
3
|
+
// is forwarded to the pi coding agent with the baryon.ai provider pre-selected.
|
|
4
|
+
import {
|
|
5
|
+
setup,
|
|
6
|
+
doctor,
|
|
7
|
+
models,
|
|
8
|
+
configCmd,
|
|
9
|
+
update,
|
|
10
|
+
help,
|
|
11
|
+
welcome,
|
|
12
|
+
} from "../src/commands.js";
|
|
13
|
+
import { loadConfig, piProviderConfigured, hasConfig } from "../src/config.js";
|
|
14
|
+
import { runPi, resolvePiEntry } from "../src/pi.js";
|
|
15
|
+
import { spawnSync } from "node:child_process";
|
|
16
|
+
import { createRequire } from "node:module";
|
|
17
|
+
import { c, err, log, sym } from "../src/ui.js";
|
|
18
|
+
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
|
|
21
|
+
function showVersion() {
|
|
22
|
+
const self = require("../package.json").version;
|
|
23
|
+
log(`@baryonlabs/cli ${c.lime("v" + self)}`);
|
|
24
|
+
const entry = resolvePiEntry();
|
|
25
|
+
if (entry) {
|
|
26
|
+
const r = spawnSync(process.execPath, [entry, "--version"], {
|
|
27
|
+
encoding: "utf8",
|
|
28
|
+
});
|
|
29
|
+
if (r.stdout) log(`pi ${c.dim(r.stdout.trim())}`);
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const argv = process.argv.slice(2);
|
|
35
|
+
const cmd = argv[0];
|
|
36
|
+
const rest = argv.slice(1);
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
switch (cmd) {
|
|
40
|
+
case "setup":
|
|
41
|
+
case "login":
|
|
42
|
+
case "init":
|
|
43
|
+
return setup(rest);
|
|
44
|
+
case "doctor":
|
|
45
|
+
return doctor();
|
|
46
|
+
case "models":
|
|
47
|
+
return models(rest);
|
|
48
|
+
case "config":
|
|
49
|
+
return configCmd(rest);
|
|
50
|
+
case "update":
|
|
51
|
+
case "upgrade":
|
|
52
|
+
return update();
|
|
53
|
+
case "_welcome": // internal: postinstall hint
|
|
54
|
+
return welcome();
|
|
55
|
+
case "help":
|
|
56
|
+
case "--help":
|
|
57
|
+
case "-h":
|
|
58
|
+
return help();
|
|
59
|
+
case "version":
|
|
60
|
+
case "--version":
|
|
61
|
+
case "-v":
|
|
62
|
+
return showVersion();
|
|
63
|
+
default: {
|
|
64
|
+
// Everything else → pi. Nudge to setup if nothing is configured yet.
|
|
65
|
+
const unconfigured =
|
|
66
|
+
!hasConfig() && !piProviderConfigured() && !process.env.BARYON_API_KEY;
|
|
67
|
+
const userTargets = argv.some((a) =>
|
|
68
|
+
["--provider", "--model", "--api-key"].includes(a),
|
|
69
|
+
);
|
|
70
|
+
if (unconfigured && !userTargets) {
|
|
71
|
+
if (process.stdin.isTTY) {
|
|
72
|
+
log(
|
|
73
|
+
` ${sym.warn} ${c.yellow("아직 설정되지 않았습니다.")} ${c.lime("baryon setup")} ${c.dim("을 먼저 실행합니다.")}\n`,
|
|
74
|
+
);
|
|
75
|
+
return setup([]);
|
|
76
|
+
}
|
|
77
|
+
log(
|
|
78
|
+
` ${sym.warn} ${c.yellow("설정이 없습니다.")} ${c.lime("baryon setup")} ${c.dim("을 먼저 실행하세요.")}`,
|
|
79
|
+
);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
const cfg = loadConfig();
|
|
83
|
+
return runPi(argv, cfg);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main()
|
|
89
|
+
.then((code) => process.exit(typeof code === "number" ? code : 0))
|
|
90
|
+
.catch((e) => {
|
|
91
|
+
err(e?.message || String(e));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@baryonlabs/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Baryon CLI — AI 코딩·학습 에이전트. baryon.ai API에 기본 연결된 pi 코딩 에이전트 래퍼. 한 줄 설치, 상용·로컬 모델 전환.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"baryon": "bin/baryon.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./src/index.js"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.19.0"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "node --test",
|
|
23
|
+
"postinstall": "node ./bin/baryon.js _welcome || true"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@earendil-works/pi-coding-agent": "^0.78.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"baryon",
|
|
30
|
+
"baryon.ai",
|
|
31
|
+
"ai",
|
|
32
|
+
"coding-agent",
|
|
33
|
+
"cli",
|
|
34
|
+
"pi",
|
|
35
|
+
"llm",
|
|
36
|
+
"education",
|
|
37
|
+
"openai-compatible"
|
|
38
|
+
],
|
|
39
|
+
"homepage": "https://cli.baryon.ai",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/baryonlabs/cli.baryon.ai/issues",
|
|
42
|
+
"email": "support@baryon.ai"
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://github.com/baryonlabs/cli.baryon.ai.git",
|
|
47
|
+
"directory": "packages/cli"
|
|
48
|
+
},
|
|
49
|
+
"author": "Baryon Labs <support@baryon.ai> (https://baryon.ai)",
|
|
50
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Minimal baryon.ai (OpenAI-compatible) helpers: model discovery + reachability.
|
|
2
|
+
import { DEFAULT_MODELS } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
function joinUrl(base, suffix) {
|
|
5
|
+
return base.replace(/\/+$/, "") + suffix;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Fetch the institution's model catalog from `${baseUrl}/models`.
|
|
10
|
+
* Returns pi-shaped model entries, or null when discovery fails
|
|
11
|
+
* (offline / endpoint without a /models route).
|
|
12
|
+
*/
|
|
13
|
+
export async function discoverModels(baseUrl, apiKey, { timeoutMs = 8000 } = {}) {
|
|
14
|
+
const ctrl = new AbortController();
|
|
15
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
16
|
+
try {
|
|
17
|
+
const res = await fetch(joinUrl(baseUrl, "/models"), {
|
|
18
|
+
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
19
|
+
signal: ctrl.signal,
|
|
20
|
+
});
|
|
21
|
+
if (!res.ok) return null;
|
|
22
|
+
const payload = await res.json();
|
|
23
|
+
const list = Array.isArray(payload?.data)
|
|
24
|
+
? payload.data
|
|
25
|
+
: Array.isArray(payload)
|
|
26
|
+
? payload
|
|
27
|
+
: null;
|
|
28
|
+
if (!list || !list.length) return null;
|
|
29
|
+
return list.map((m) => ({
|
|
30
|
+
id: m.id,
|
|
31
|
+
name: m.name || m.id,
|
|
32
|
+
reasoning: Boolean(m.reasoning),
|
|
33
|
+
input: Array.isArray(m.input) ? m.input : ["text"],
|
|
34
|
+
contextWindow: m.context_window || m.contextWindow || 128000,
|
|
35
|
+
maxTokens: m.max_tokens || m.maxTokens || 8192,
|
|
36
|
+
}));
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
} finally {
|
|
40
|
+
clearTimeout(t);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Lightweight reachability probe used by `baryon doctor`. */
|
|
45
|
+
export async function ping(baseUrl, apiKey, { timeoutMs = 6000 } = {}) {
|
|
46
|
+
const ctrl = new AbortController();
|
|
47
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(joinUrl(baseUrl, "/models"), {
|
|
50
|
+
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
51
|
+
signal: ctrl.signal,
|
|
52
|
+
});
|
|
53
|
+
return { ok: res.ok, status: res.status };
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return { ok: false, status: 0, error: e?.message || "network error" };
|
|
56
|
+
} finally {
|
|
57
|
+
clearTimeout(t);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { DEFAULT_MODELS };
|
package/src/commands.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
// Built-in baryon subcommands. Anything not matched here is passed to pi.
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { discoverModels, ping } from "./api.js";
|
|
4
|
+
import {
|
|
5
|
+
hasConfig,
|
|
6
|
+
loadConfig,
|
|
7
|
+
piProviderConfigured,
|
|
8
|
+
saveConfig,
|
|
9
|
+
syncPiModels,
|
|
10
|
+
BARYON_CONFIG,
|
|
11
|
+
PI_MODELS_JSON,
|
|
12
|
+
} from "./config.js";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_BASE_URL,
|
|
15
|
+
DEFAULT_MODELS,
|
|
16
|
+
HOMEPAGE,
|
|
17
|
+
PI_PACKAGE,
|
|
18
|
+
PROVIDER,
|
|
19
|
+
SUPPORT_EMAIL,
|
|
20
|
+
} from "./constants.js";
|
|
21
|
+
import { runPi, resolvePiEntry } from "./pi.js";
|
|
22
|
+
import { banner, c, info, log, ok, err, warn, prompt, promptHidden, sym } from "./ui.js";
|
|
23
|
+
|
|
24
|
+
/** Parse `--flag value` / `--flag=value` pairs out of argv. */
|
|
25
|
+
function parseFlags(args) {
|
|
26
|
+
const out = {};
|
|
27
|
+
for (let i = 0; i < args.length; i++) {
|
|
28
|
+
const a = args[i];
|
|
29
|
+
if (a.startsWith("--")) {
|
|
30
|
+
const eq = a.indexOf("=");
|
|
31
|
+
if (eq !== -1) out[a.slice(2, eq)] = a.slice(eq + 1);
|
|
32
|
+
else if (args[i + 1] && !args[i + 1].startsWith("-"))
|
|
33
|
+
out[a.slice(2)] = args[++i];
|
|
34
|
+
else out[a.slice(2)] = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function setup(args) {
|
|
41
|
+
const flags = parseFlags(args);
|
|
42
|
+
banner();
|
|
43
|
+
log(c.bold(" baryon.ai 연결 설정\n"));
|
|
44
|
+
|
|
45
|
+
const existing = loadConfig();
|
|
46
|
+
const baseUrl = flags["base-url"] || existing.baseUrl || DEFAULT_BASE_URL;
|
|
47
|
+
|
|
48
|
+
let apiKey = flags.key || flags["api-key"] || process.env.BARYON_API_KEY || "";
|
|
49
|
+
if (!apiKey) {
|
|
50
|
+
apiKey = await promptHidden(` ${sym.info} baryon.ai API key: `);
|
|
51
|
+
}
|
|
52
|
+
if (!apiKey) {
|
|
53
|
+
warn("API 키 없이 저장합니다. 나중에 `baryon setup --key <KEY>` 로 추가하세요.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
saveConfig({ apiKey, baseUrl });
|
|
57
|
+
ok(`config 저장 → ${c.dim(BARYON_CONFIG)}`);
|
|
58
|
+
|
|
59
|
+
// Try live model discovery; fall back to defaults offline.
|
|
60
|
+
let models = null;
|
|
61
|
+
if (apiKey) {
|
|
62
|
+
info("모델 목록을 조회하는 중…");
|
|
63
|
+
models = await discoverModels(baseUrl, apiKey);
|
|
64
|
+
}
|
|
65
|
+
if (models) {
|
|
66
|
+
ok(`${models.length}개 모델 발견 (${c.lime(models.map((m) => m.id).slice(0, 4).join(", "))}${models.length > 4 ? "…" : ""})`);
|
|
67
|
+
} else {
|
|
68
|
+
models = DEFAULT_MODELS;
|
|
69
|
+
warn(`모델 자동 조회 실패 — 기본 모델 ${models.length}개로 구성 (오프라인/폐쇄망 정상)`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
saveConfig({ defaultModel: models[0].id });
|
|
73
|
+
const file = syncPiModels({ baseUrl, models });
|
|
74
|
+
ok(`pi 프로바이더 ${c.lime(PROVIDER)} 구성 → ${c.dim(file)}`);
|
|
75
|
+
|
|
76
|
+
log(`\n ${sym.ok} 준비 완료. ${c.lime("baryon")} 으로 시작하세요.\n`);
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function doctor() {
|
|
81
|
+
banner();
|
|
82
|
+
log(c.bold(" 진단 (baryon doctor)\n"));
|
|
83
|
+
let problems = 0;
|
|
84
|
+
|
|
85
|
+
// node
|
|
86
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
87
|
+
if (major >= 22) ok(`Node.js ${process.version}`);
|
|
88
|
+
else {
|
|
89
|
+
err(`Node.js ${process.version} — 22 이상 필요`);
|
|
90
|
+
problems++;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// pi installed?
|
|
94
|
+
const entry = resolvePiEntry();
|
|
95
|
+
if (entry) ok(`${PI_PACKAGE} 설치됨`);
|
|
96
|
+
else {
|
|
97
|
+
err(`${PI_PACKAGE} 미설치 — npm install -g @baryonlabs/cli`);
|
|
98
|
+
problems++;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// config?
|
|
102
|
+
if (hasConfig()) ok(`config 존재 → ${c.dim(BARYON_CONFIG)}`);
|
|
103
|
+
else {
|
|
104
|
+
warn("config 없음 — `baryon setup` 실행 필요");
|
|
105
|
+
problems++;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const cfg = loadConfig();
|
|
109
|
+
if (cfg.apiKey) ok(`API 키 설정됨 (${cfg.apiKey.slice(0, 4)}${"•".repeat(6)})`);
|
|
110
|
+
else warn("API 키 없음");
|
|
111
|
+
|
|
112
|
+
// pi provider registered?
|
|
113
|
+
if (piProviderConfigured()) ok(`pi 프로바이더 ${c.lime(PROVIDER)} 등록됨 → ${c.dim(PI_MODELS_JSON)}`);
|
|
114
|
+
else {
|
|
115
|
+
warn("pi 프로바이더 미등록 — `baryon setup` 실행");
|
|
116
|
+
problems++;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// connectivity
|
|
120
|
+
info(`연결 확인 중 → ${c.dim(cfg.baseUrl)}`);
|
|
121
|
+
const r = await ping(cfg.baseUrl, cfg.apiKey);
|
|
122
|
+
if (r.ok) ok(`baryon.ai 연결 정상 (HTTP ${r.status})`);
|
|
123
|
+
else if (r.status) warn(`엔드포인트 응답 HTTP ${r.status} — 키/권한 확인`);
|
|
124
|
+
else warn(`연결 불가 (${r.error}) — 오프라인이면 로컬 LLM 사용 가능`);
|
|
125
|
+
|
|
126
|
+
log(
|
|
127
|
+
problems === 0
|
|
128
|
+
? `\n ${sym.ok} ${c.teal("모든 점검 통과")}\n`
|
|
129
|
+
: `\n ${sym.warn} ${c.yellow(`${problems}건 확인 필요`)}\n`,
|
|
130
|
+
);
|
|
131
|
+
return problems === 0 ? 0 : 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function models(args) {
|
|
135
|
+
const cfg = loadConfig();
|
|
136
|
+
// Delegate to pi's own --list-models so output matches the real catalog.
|
|
137
|
+
return runPi(["--list-models", ...args], cfg, { injectTargeting: false });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function configCmd(args) {
|
|
141
|
+
const flags = parseFlags(args);
|
|
142
|
+
if (flags.key || flags["api-key"] || flags["base-url"] || flags.model) {
|
|
143
|
+
const patch = {};
|
|
144
|
+
if (flags.key || flags["api-key"]) patch.apiKey = flags.key || flags["api-key"];
|
|
145
|
+
if (flags["base-url"]) patch.baseUrl = flags["base-url"];
|
|
146
|
+
if (flags.model) patch.defaultModel = flags.model;
|
|
147
|
+
saveConfig(patch);
|
|
148
|
+
ok("config 업데이트됨");
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
const cfg = loadConfig();
|
|
152
|
+
banner();
|
|
153
|
+
log(c.bold(" 현재 설정\n"));
|
|
154
|
+
info(`base URL ${c.lime(cfg.baseUrl)}`);
|
|
155
|
+
info(`default ${c.lime(cfg.defaultModel)}`);
|
|
156
|
+
info(`API key ${cfg.apiKey ? cfg.apiKey.slice(0, 4) + "•".repeat(6) : c.dim("(없음)")}`);
|
|
157
|
+
info(`config 파일 ${c.dim(BARYON_CONFIG)}`);
|
|
158
|
+
info(`pi models ${c.dim(PI_MODELS_JSON)}`);
|
|
159
|
+
log("");
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function update() {
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
log(` ${sym.info} 업데이트: ${c.lime(`npm install -g @baryonlabs/cli ${PI_PACKAGE}`)}\n`);
|
|
166
|
+
const child = spawn(
|
|
167
|
+
"npm",
|
|
168
|
+
["install", "-g", "@baryonlabs/cli", PI_PACKAGE],
|
|
169
|
+
{ stdio: "inherit", shell: process.platform === "win32" },
|
|
170
|
+
);
|
|
171
|
+
child.on("exit", (code) => resolve(code ?? 0));
|
|
172
|
+
child.on("error", () => {
|
|
173
|
+
err("npm 실행 실패 — 수동으로 위 명령을 실행하세요.");
|
|
174
|
+
resolve(1);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function help() {
|
|
180
|
+
banner();
|
|
181
|
+
log(`${c.bold("USAGE")}
|
|
182
|
+
${c.lime("baryon")} ${c.dim("[options] [@files...] [messages...]")} 코딩 에이전트 시작 (baryon.ai 기본)
|
|
183
|
+
|
|
184
|
+
${c.bold("COMMANDS")}
|
|
185
|
+
${c.lime("baryon setup")} baryon.ai API 키 등록 + pi 프로바이더 구성
|
|
186
|
+
${c.lime("baryon config")} 현재 설정 보기 ${c.dim("(--key/--base-url/--model 로 변경)")}
|
|
187
|
+
${c.lime("baryon models")} 사용 가능한 모델 목록
|
|
188
|
+
${c.lime("baryon doctor")} 설치·연결 진단
|
|
189
|
+
${c.lime("baryon update")} CLI + pi 에이전트 업데이트
|
|
190
|
+
${c.lime("baryon help")} 이 도움말
|
|
191
|
+
|
|
192
|
+
${c.bold("EXAMPLES")}
|
|
193
|
+
${c.dim("$")} baryon ${c.dim("# 대화형 시작")}
|
|
194
|
+
${c.dim("$")} baryon -p "CSV 분석해 차트 만들어줘" ${c.dim("# 단발 실행")}
|
|
195
|
+
${c.dim("$")} baryon --provider openai ${c.dim("# 다른 모델로 전환·비교")}
|
|
196
|
+
${c.dim("$")} baryon --list-models ${c.dim("# pi 패스스루")}
|
|
197
|
+
|
|
198
|
+
${c.dim(`그 외 모든 옵션은 pi 에이전트로 그대로 전달됩니다.`)}
|
|
199
|
+
${c.dim(`문서: ${HOMEPAGE} · 문의: ${SUPPORT_EMAIL}`)}
|
|
200
|
+
`);
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Quiet first-run hint shown by postinstall (never fails the install). */
|
|
205
|
+
export function welcome() {
|
|
206
|
+
if (!process.stdout.isTTY) return 0;
|
|
207
|
+
log(`\n${c.lime("✔")} ${c.bold("@baryonlabs/cli")} 설치 완료`);
|
|
208
|
+
log(` ${c.dim("다음 단계:")} ${c.lime("baryon setup")} ${c.dim("→")} ${c.lime("baryon")}`);
|
|
209
|
+
log(` ${c.dim(HOMEPAGE)}\n`);
|
|
210
|
+
return 0;
|
|
211
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Reads/writes Baryon config and merges a `baryon` provider into pi's
|
|
2
|
+
// ~/.pi/agent/models.json without clobbering the user's other providers.
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import {
|
|
6
|
+
API_KEY_ENV,
|
|
7
|
+
BARYON_CONFIG,
|
|
8
|
+
BARYON_DIR,
|
|
9
|
+
DEFAULT_BASE_URL,
|
|
10
|
+
DEFAULT_MODELS,
|
|
11
|
+
PI_AGENT_DIR,
|
|
12
|
+
PI_MODELS_JSON,
|
|
13
|
+
PROVIDER,
|
|
14
|
+
} from "./constants.js";
|
|
15
|
+
|
|
16
|
+
function readJson(file, fallback) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
19
|
+
} catch {
|
|
20
|
+
return fallback;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeJson(file, data) {
|
|
25
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
26
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Baryon config: { apiKey, baseUrl, defaultModel } */
|
|
30
|
+
export function loadConfig() {
|
|
31
|
+
const cfg = readJson(BARYON_CONFIG, {});
|
|
32
|
+
return {
|
|
33
|
+
apiKey: process.env[API_KEY_ENV] || cfg.apiKey || "",
|
|
34
|
+
baseUrl: process.env.BARYON_BASE_URL || cfg.baseUrl || DEFAULT_BASE_URL,
|
|
35
|
+
defaultModel: cfg.defaultModel || DEFAULT_MODELS[0].id,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function saveConfig(patch) {
|
|
40
|
+
const current = readJson(BARYON_CONFIG, {});
|
|
41
|
+
const next = { ...current, ...patch };
|
|
42
|
+
fs.mkdirSync(BARYON_DIR, { recursive: true, mode: 0o700 });
|
|
43
|
+
writeJson(BARYON_CONFIG, next);
|
|
44
|
+
try {
|
|
45
|
+
fs.chmodSync(BARYON_CONFIG, 0o600);
|
|
46
|
+
} catch {
|
|
47
|
+
/* best effort on non-POSIX */
|
|
48
|
+
}
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function hasConfig() {
|
|
53
|
+
return fs.existsSync(BARYON_CONFIG);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Merge the baryon provider into pi's models.json. Preserves every other
|
|
58
|
+
* provider already present. apiKey is stored as the `$BARYON_API_KEY`
|
|
59
|
+
* interpolation token (never the literal key) so the key lives only in
|
|
60
|
+
* ~/.baryon/config.json + the process env.
|
|
61
|
+
*/
|
|
62
|
+
export function syncPiModels({ baseUrl, models }) {
|
|
63
|
+
const root = readJson(PI_MODELS_JSON, {});
|
|
64
|
+
if (!root.providers || typeof root.providers !== "object") root.providers = {};
|
|
65
|
+
|
|
66
|
+
root.providers[PROVIDER] = {
|
|
67
|
+
name: "Baryon (baryon.ai)",
|
|
68
|
+
baseUrl: baseUrl || DEFAULT_BASE_URL,
|
|
69
|
+
api: "openai-completions",
|
|
70
|
+
apiKey: `$${API_KEY_ENV}`,
|
|
71
|
+
authHeader: true,
|
|
72
|
+
models:
|
|
73
|
+
Array.isArray(models) && models.length ? models : DEFAULT_MODELS,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
fs.mkdirSync(PI_AGENT_DIR, { recursive: true });
|
|
77
|
+
writeJson(PI_MODELS_JSON, root);
|
|
78
|
+
return PI_MODELS_JSON;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function piProviderConfigured() {
|
|
82
|
+
const root = readJson(PI_MODELS_JSON, {});
|
|
83
|
+
return Boolean(root?.providers?.[PROVIDER]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { PI_MODELS_JSON, BARYON_CONFIG };
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Provider id registered inside pi's models.json */
|
|
5
|
+
export const PROVIDER = "baryon";
|
|
6
|
+
|
|
7
|
+
/** Default OpenAI-compatible endpoint for baryon.ai. Override with BARYON_BASE_URL. */
|
|
8
|
+
export const DEFAULT_BASE_URL =
|
|
9
|
+
process.env.BARYON_BASE_URL || "https://api.baryon.ai/v1";
|
|
10
|
+
|
|
11
|
+
/** Env var pi resolves at request time (apiKey: "$BARYON_API_KEY"). */
|
|
12
|
+
export const API_KEY_ENV = "BARYON_API_KEY";
|
|
13
|
+
|
|
14
|
+
/** Underlying coding agent package + binary. */
|
|
15
|
+
export const PI_PACKAGE = "@earendil-works/pi-coding-agent";
|
|
16
|
+
export const PI_BIN = "pi";
|
|
17
|
+
|
|
18
|
+
/** Baryon-side config (api key, base url, default model). */
|
|
19
|
+
export const BARYON_DIR = path.join(os.homedir(), ".baryon");
|
|
20
|
+
export const BARYON_CONFIG = path.join(BARYON_DIR, "config.json");
|
|
21
|
+
|
|
22
|
+
/** pi's custom-provider/model registry. */
|
|
23
|
+
export const PI_AGENT_DIR = path.join(os.homedir(), ".pi", "agent");
|
|
24
|
+
export const PI_MODELS_JSON = path.join(PI_AGENT_DIR, "models.json");
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fallback model catalog used when /models discovery is unavailable
|
|
28
|
+
* (e.g. offline setup). Institutions adjust these to match their plan.
|
|
29
|
+
* `context` / `max` are conservative defaults; pi tolerates overrides.
|
|
30
|
+
*/
|
|
31
|
+
export const DEFAULT_MODELS = [
|
|
32
|
+
{
|
|
33
|
+
id: "baryon-coder",
|
|
34
|
+
name: "Baryon Coder",
|
|
35
|
+
reasoning: true,
|
|
36
|
+
input: ["text", "image"],
|
|
37
|
+
contextWindow: 128000,
|
|
38
|
+
maxTokens: 16384,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "baryon-coder-mini",
|
|
42
|
+
name: "Baryon Coder Mini",
|
|
43
|
+
reasoning: false,
|
|
44
|
+
input: ["text"],
|
|
45
|
+
contextWindow: 128000,
|
|
46
|
+
maxTokens: 8192,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const HOMEPAGE = "https://cli.baryon.ai";
|
|
51
|
+
export const SUPPORT_EMAIL = "support@baryon.ai";
|
package/src/index.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Programmatic API for @baryonlabs/cli.
|
|
2
|
+
//
|
|
3
|
+
// import { configure, run, discover } from "@baryonlabs/cli";
|
|
4
|
+
//
|
|
5
|
+
// Lets host apps register the baryon provider and launch pi without shelling
|
|
6
|
+
// out to the `baryon` binary.
|
|
7
|
+
import { discoverModels, ping } from "./api.js";
|
|
8
|
+
import {
|
|
9
|
+
loadConfig,
|
|
10
|
+
saveConfig,
|
|
11
|
+
syncPiModels,
|
|
12
|
+
piProviderConfigured,
|
|
13
|
+
} from "./config.js";
|
|
14
|
+
import { DEFAULT_BASE_URL, DEFAULT_MODELS, PROVIDER } from "./constants.js";
|
|
15
|
+
import { runPi, resolvePiEntry } from "./pi.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Configure the baryon provider: persist key/baseUrl, discover (or default)
|
|
19
|
+
* models, and write pi's models.json. Returns the resolved config + model ids.
|
|
20
|
+
*/
|
|
21
|
+
export async function configure({ apiKey, baseUrl = DEFAULT_BASE_URL } = {}) {
|
|
22
|
+
if (apiKey) saveConfig({ apiKey, baseUrl });
|
|
23
|
+
const models = (apiKey && (await discoverModels(baseUrl, apiKey))) || DEFAULT_MODELS;
|
|
24
|
+
saveConfig({ defaultModel: models[0].id });
|
|
25
|
+
syncPiModels({ baseUrl, models });
|
|
26
|
+
return { baseUrl, models: models.map((m) => m.id), defaultModel: models[0].id };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Launch pi with the current baryon config. Resolves to the exit code. */
|
|
30
|
+
export function run(args = []) {
|
|
31
|
+
return runPi(args, loadConfig());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
loadConfig,
|
|
36
|
+
saveConfig,
|
|
37
|
+
discoverModels as discover,
|
|
38
|
+
ping,
|
|
39
|
+
piProviderConfigured,
|
|
40
|
+
resolvePiEntry,
|
|
41
|
+
PROVIDER,
|
|
42
|
+
DEFAULT_BASE_URL,
|
|
43
|
+
DEFAULT_MODELS,
|
|
44
|
+
};
|
package/src/pi.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Resolves the bundled pi binary and launches it with the baryon provider
|
|
2
|
+
// pre-selected and BARYON_API_KEY injected into the environment.
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import {
|
|
9
|
+
API_KEY_ENV,
|
|
10
|
+
PI_BIN,
|
|
11
|
+
PI_PACKAGE,
|
|
12
|
+
PROVIDER,
|
|
13
|
+
} from "./constants.js";
|
|
14
|
+
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
|
|
17
|
+
/** Find the package root (dir holding the matching package.json) above a file. */
|
|
18
|
+
function findPackageRoot(startFile, name) {
|
|
19
|
+
let dir = path.dirname(startFile);
|
|
20
|
+
for (let i = 0; i < 8; i++) {
|
|
21
|
+
const pj = path.join(dir, "package.json");
|
|
22
|
+
if (fs.existsSync(pj)) {
|
|
23
|
+
try {
|
|
24
|
+
if (JSON.parse(fs.readFileSync(pj, "utf8")).name === name) return dir;
|
|
25
|
+
} catch {
|
|
26
|
+
/* keep walking */
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const parent = path.dirname(dir);
|
|
30
|
+
if (parent === dir) break;
|
|
31
|
+
dir = parent;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Absolute path to pi's entry script (dist/cli.js), or null if not installed.
|
|
38
|
+
* pi's `exports` map hides ./package.json, so we resolve its main entry and
|
|
39
|
+
* walk up to the package root to read `bin.pi`.
|
|
40
|
+
*/
|
|
41
|
+
export function resolvePiEntry() {
|
|
42
|
+
try {
|
|
43
|
+
// pi's "." export only defines `import` (ESM), so the CJS require.resolve
|
|
44
|
+
// fails with ERR_PACKAGE_PATH_NOT_EXPORTED. Prefer the ESM resolver.
|
|
45
|
+
let main;
|
|
46
|
+
try {
|
|
47
|
+
main = fileURLToPath(import.meta.resolve(PI_PACKAGE));
|
|
48
|
+
} catch {
|
|
49
|
+
main = require.resolve(PI_PACKAGE);
|
|
50
|
+
}
|
|
51
|
+
const root = findPackageRoot(main, PI_PACKAGE) || path.dirname(main);
|
|
52
|
+
const pkg = JSON.parse(
|
|
53
|
+
fs.readFileSync(path.join(root, "package.json"), "utf8"),
|
|
54
|
+
);
|
|
55
|
+
const rel =
|
|
56
|
+
typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.[PI_BIN] || pkg.bin?.pi;
|
|
57
|
+
if (!rel) return null;
|
|
58
|
+
return path.join(root, rel);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const TARGETING_FLAGS = ["--provider", "--model", "--api-key"];
|
|
65
|
+
|
|
66
|
+
/** True if the user already chose a provider/model/key themselves. */
|
|
67
|
+
function userOverridesTargeting(args) {
|
|
68
|
+
return args.some((a) => TARGETING_FLAGS.includes(a));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Launch pi. By default injects `--provider baryon` (and the configured
|
|
73
|
+
* default model) plus the API key env, while passing every other arg through
|
|
74
|
+
* untouched. If the user supplies their own --provider/--model/--api-key we
|
|
75
|
+
* leave targeting entirely to them (so model comparison still works).
|
|
76
|
+
*
|
|
77
|
+
* @returns {Promise<number>} pi's exit code
|
|
78
|
+
*/
|
|
79
|
+
export function runPi(args, config, { injectTargeting = true } = {}) {
|
|
80
|
+
const entry = resolvePiEntry();
|
|
81
|
+
if (!entry) {
|
|
82
|
+
return Promise.reject(
|
|
83
|
+
new Error(
|
|
84
|
+
`${PI_PACKAGE} is not installed. Run: npm install -g @baryonlabs/cli`,
|
|
85
|
+
),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const finalArgs = [...args];
|
|
90
|
+
if (injectTargeting && !userOverridesTargeting(args)) {
|
|
91
|
+
finalArgs.unshift("--provider", PROVIDER);
|
|
92
|
+
if (config.defaultModel) {
|
|
93
|
+
finalArgs.unshift("--model", `${PROVIDER}/${config.defaultModel}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const env = { ...process.env };
|
|
98
|
+
if (config.apiKey) env[API_KEY_ENV] = config.apiKey;
|
|
99
|
+
if (config.baseUrl) env.BARYON_BASE_URL = config.baseUrl;
|
|
100
|
+
|
|
101
|
+
return new Promise((resolve, reject) => {
|
|
102
|
+
const child = spawn(process.execPath, [entry, ...finalArgs], {
|
|
103
|
+
stdio: "inherit",
|
|
104
|
+
env,
|
|
105
|
+
});
|
|
106
|
+
child.on("error", reject);
|
|
107
|
+
child.on("exit", (code, signal) => {
|
|
108
|
+
if (signal) {
|
|
109
|
+
process.kill(process.pid, signal);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
resolve(code ?? 0);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
package/src/ui.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// Tiny zero-dependency ANSI helpers + prompts.
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
|
|
4
|
+
const useColor =
|
|
5
|
+
process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
6
|
+
|
|
7
|
+
const wrap = (open, close) => (s) =>
|
|
8
|
+
useColor ? `\x1b[${open}m${s}\x1b[${close}m` : String(s);
|
|
9
|
+
|
|
10
|
+
export const c = {
|
|
11
|
+
lime: wrap("38;5;191", "39"),
|
|
12
|
+
teal: wrap("38;5;43", "39"),
|
|
13
|
+
red: wrap("38;5;203", "39"),
|
|
14
|
+
yellow: wrap("38;5;221", "39"),
|
|
15
|
+
dim: wrap("2", "22"),
|
|
16
|
+
bold: wrap("1", "22"),
|
|
17
|
+
gray: wrap("38;5;245", "39"),
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const sym = {
|
|
21
|
+
ok: c.teal("✔"),
|
|
22
|
+
err: c.red("✖"),
|
|
23
|
+
warn: c.yellow("▲"),
|
|
24
|
+
info: c.lime("›"),
|
|
25
|
+
arrow: c.lime("→"),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const log = (...a) => console.log(...a);
|
|
29
|
+
export const ok = (m) => log(` ${sym.ok} ${m}`);
|
|
30
|
+
export const err = (m) => log(` ${sym.err} ${c.red(m)}`);
|
|
31
|
+
export const warn = (m) => log(` ${sym.warn} ${c.yellow(m)}`);
|
|
32
|
+
export const info = (m) => log(` ${sym.info} ${m}`);
|
|
33
|
+
|
|
34
|
+
export function banner() {
|
|
35
|
+
const art = [
|
|
36
|
+
" ___ ___ _ ___",
|
|
37
|
+
" / _ )___ _______ _____ ___ / __| | |_ _|",
|
|
38
|
+
" / _ / _ `/ __/ // / _ \\/ _ \\ (__| |__ | |",
|
|
39
|
+
"/____/\\_,_/_/ \\_, /\\___/_//_/\\___|____|___|",
|
|
40
|
+
" /___/",
|
|
41
|
+
].join("\n");
|
|
42
|
+
log(c.lime(c.bold(art)));
|
|
43
|
+
log(c.dim(" AI coding & learning agent · baryon.ai\n"));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Hidden-input prompt (for API keys). Falls back to visible if no TTY. */
|
|
47
|
+
export function promptHidden(question) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
if (!process.stdin.isTTY) {
|
|
50
|
+
// non-interactive: read one line from stdin
|
|
51
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
52
|
+
rl.question(question, (a) => {
|
|
53
|
+
rl.close();
|
|
54
|
+
resolve(a.trim());
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const stdin = process.stdin;
|
|
59
|
+
process.stdout.write(question);
|
|
60
|
+
stdin.setRawMode(true);
|
|
61
|
+
stdin.resume();
|
|
62
|
+
let buf = "";
|
|
63
|
+
const onData = (ch) => {
|
|
64
|
+
const s = ch.toString("utf8");
|
|
65
|
+
if (s === "\r" || s === "\n") {
|
|
66
|
+
stdin.setRawMode(false);
|
|
67
|
+
stdin.pause();
|
|
68
|
+
stdin.removeListener("data", onData);
|
|
69
|
+
process.stdout.write("\n");
|
|
70
|
+
resolve(buf.trim());
|
|
71
|
+
} else if (s === "") {
|
|
72
|
+
// Ctrl+C
|
|
73
|
+
process.stdout.write("\n");
|
|
74
|
+
process.exit(130);
|
|
75
|
+
} else if (s === "" || s === "\b") {
|
|
76
|
+
buf = buf.slice(0, -1);
|
|
77
|
+
} else {
|
|
78
|
+
buf += s;
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
stdin.on("data", onData);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function prompt(question) {
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
const rl = readline.createInterface({
|
|
88
|
+
input: process.stdin,
|
|
89
|
+
output: process.stdout,
|
|
90
|
+
});
|
|
91
|
+
rl.question(question, (a) => {
|
|
92
|
+
rl.close();
|
|
93
|
+
resolve(a.trim());
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
}
|